Compare commits

...

87 Commits

Author SHA1 Message Date
Alex Yang
c59d1e67ab v0.8.0-canary.24 2023-08-16 17:42:16 -05:00
danielchim
9ab9c0c70d feat: new workspace switch dropdown design (#3700)
Co-authored-by: Alex Yang <himself65@outlook.com>
2023-08-16 17:18:43 -05:00
Alex Yang
f369ca39f7 fix(core): correct the suspense behavior (#3789) 2023-08-16 16:42:35 -05:00
Rohit Yadav
804b8f38b8 fix(core): unused z-index (#3781)
Co-authored-by: Alex Yang <himself65@outlook.com>
2023-08-16 16:25:51 -05:00
Alex Yang
dd23917e3e docs: rename to upstreams section 2023-08-16 16:09:29 -05:00
Alex Yang
b604d9b47e docs: update README.md 2023-08-16 16:06:37 -05:00
Alex Yang
1e5a4a6849 feat(storybook): improve code (#3786) 2023-08-16 15:07:55 -05:00
LongYinan
64656c3c98 fix(native): static link msvc runtime on Windows (#3773)
Co-authored-by: Alex Yang <himself65@outlook.com>
2023-08-16 14:55:37 -05:00
Alex Yang
61ba85e1f3 chore: bump version (#3784) 2023-08-16 14:53:33 -05:00
Peng Xiao
61ffc4220c fix: ignore some files to be bundled (#3770) 2023-08-16 18:29:47 +00:00
danielchim
866408015e fix: tooltip arrow (#3769)
Co-authored-by: Alex Yang <himself65@outlook.com>
2023-08-16 17:48:13 +00:00
Alex Yang
651e815b42 chore: bump version (#3771)
Co-authored-by: Mirone <Saul-Mirone@outlook.com>
2023-08-16 12:02:07 -05:00
Alex Yang
645a300112 ci: add environment 2023-08-16 11:31:13 -05:00
Peng Xiao
e0a3c7f2bc fix: disable secondary db test (#3774) 2023-08-17 00:11:04 +08:00
Alex Yang
3dbefda6ed feat(storybook): import plugins (#3768) 2023-08-16 03:01:14 -05:00
Alex Yang
73eddc2386 v0.8.0-canary.23 2023-08-16 02:43:10 -05:00
Alex Yang
6f9dfcc3c1 feat: add outline plugin (#3624)
Co-authored-by: codert <codert.sn@gmail.com>
2023-08-16 02:34:26 -05:00
Alex Yang
93d352f3d8 ci: checkout pull request ref 2023-08-16 00:54:57 -05:00
Alex Yang
7546b080ea ci: add name 2023-08-16 00:38:45 -05:00
Alex Yang
6988b6f034 ci: publish storybook on push to master 2023-08-16 00:37:53 -05:00
Alex Yang
de2cb1a3bc ci: add publish-storybook.yml 2023-08-16 00:36:24 -05:00
KaranPant
08f01ea1b3 fix: add min height to footer (#3717) 2023-08-16 03:03:30 +00:00
Alex Yang
0df30e43c6 feat(storybook): add not found page (#3767) 2023-08-15 16:58:14 -05:00
Alex Yang
67b33d9b8f feat(storybook): preview app (#3765) 2023-08-15 15:34:02 -05:00
Alex Yang
42dfd0a4bb fix(core): default page mode (#3745) 2023-08-15 14:49:53 -05:00
Alex Yang
25052220a4 feat: add chromatic (#3764) 2023-08-15 14:32:24 -05:00
Qi
48e96cd399 fix: wrong style of cancel button in create workspace modal (#3761) 2023-08-15 12:44:03 -05:00
JimmFly
ca016f1dd1 chore: adjust preloading page (#3753) 2023-08-15 05:53:53 +00:00
Qi
a4fe7dd119 fix: ui issues (#3755) 2023-08-15 05:53:19 +00:00
JimmFly
8d2df468ee chore: update en.json (#3754) 2023-08-15 05:08:48 +00:00
Peng Xiao
2830cb19fe fix: show recursive items (#3750) 2023-08-15 04:01:46 +00:00
Alex Yang
8487b2c4af fix(electron): type on handlers (#3747) 2023-08-15 01:06:25 +00:00
Alex Yang
720a90fe93 v0.8.0-canary.22 2023-08-14 20:37:04 -04:00
Alex Yang
623fa87d5c fix(core): first page (#3744) 2023-08-14 23:16:46 +00:00
Alex Yang
4ad50bf8cf docs: update badge in README.md (#3743) 2023-08-14 16:41:37 -04:00
Alex Yang
efd02a015a chore: bump version (#3742) 2023-08-14 19:23:19 +00:00
Qi
75a2bbdfac fix: ui issues (#3738)
Co-authored-by: Alex Yang <himself65@outlook.com>
2023-08-14 18:48:18 +00:00
Quincy Qiu
52102ee792 fix(plugin): allow multiple loads assets (#3741) 2023-08-14 18:44:52 +00:00
Qi
58dae07b5f fix: error style of empty page (#3733) 2023-08-14 18:28:39 +00:00
Qi
d0e33c748b fix: shaky header (#3727) 2023-08-14 18:03:14 +00:00
Peng Xiao
08da58aa1e fix: allow multiple versions to be installed on windows (#3740) 2023-08-14 15:40:00 +00:00
JimmFly
1072db632e chore: adjust translation (#3734) 2023-08-14 14:34:42 +00:00
Alex Yang
f33fb98912 v0.8.0-canary.21 2023-08-13 22:06:36 -04:00
JimmFly
4d254f3967 fix: get back the missing scrollbar (#3718) 2023-08-13 13:36:29 +00:00
Alex Yang
8833584756 v0.8.0-canary.20 2023-08-11 21:26:29 -04:00
Alex Yang
582059f6d6 fix(electron): download tip (#3711) 2023-08-12 01:11:19 +00:00
Alex Yang
1484818974 v0.8.0-canary.19 2023-08-11 16:42:24 -04:00
Alex Yang
1d41687786 chore: bump version (#3710) 2023-08-11 16:42:00 -04:00
Qi
401fb48b86 feat: refator header (#3685)
Co-authored-by: JimmFly <yangjinfei001@gmail.com>
Co-authored-by: Alex Yang <himself65@outlook.com>
2023-08-11 16:27:24 -04:00
Alex Yang
91619b87db chore: bump version (#3706) 2023-08-11 15:36:10 -04:00
Alex Yang
844e73ca29 docs: remove .all-contributorsrc (#3704) 2023-08-11 14:25:03 -04:00
fourdim
ee91964998 feat: add Intl.Segmenter polyfill (#3688) 2023-08-11 17:14:09 +00:00
Peng Xiao
1eaf228a30 fix: page references in list/database (#3702) 2023-08-11 16:20:33 +00:00
Alex Yang
9639143df4 chore: prohibit using mergeUpdates (#3701) 2023-08-11 15:55:17 +00:00
Garfield Lee
e9f4912665 refactor: remove React.FC (#3694) 2023-08-11 14:58:44 +00:00
Garfield Lee
ce21ea78eb fix: remove invisible unicode for dynamicImportKey (#3695) 2023-08-11 14:58:15 +00:00
xiaodong zuo
13ac9d18af fix: update blocksuite version (#3693) 2023-08-11 14:24:42 +00:00
JimmFly
1a8f849693 feat: add duplicate feature (#3675)
Co-authored-by: Alex Yang <himself65@outlook.com>
2023-08-11 13:58:59 +00:00
JimmFly
f0cbbc3a84 fix: test case (#3686) 2023-08-11 02:59:06 -04:00
Alex Yang
4834f99da9 chore: bump version (#3677) 2023-08-11 02:05:49 +00:00
dependabot[bot]
394469a807 chore: bump ses from 0.18.5 to 0.18.7 (#3630)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Alex Yang <himself65@outlook.com>
2023-08-10 22:02:21 -04:00
Alex Yang
b7be91e04d v0.8.0-canary.18 2023-08-10 21:25:51 -04:00
Alex Yang
278ffa3372 fix: upload blobs (#3676) 2023-08-10 21:25:22 -04:00
Alex Yang
601cbd83ff v0.8.0-canary.17 2023-08-10 15:59:59 -04:00
Alex Yang
43b35a77bb feat: new preload pages (#3674) 2023-08-10 15:56:16 -04:00
Mirone
f06efd4d02 chore: remove dependency of blocksuite/blocks/std (#3667) 2023-08-10 16:36:28 +00:00
dependabot[bot]
5887071319 chore: bump @storybook/test-runner from 0.11.0 to 0.13.0 (#3673)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Alex Yang <himself65@outlook.com>
2023-08-10 15:45:41 +00:00
dependabot[bot]
fa111db91b chore: bump lib0 from 0.2.78 to 0.2.80 (#3670)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-08-10 15:35:15 +00:00
Alex Yang
b6cdabff36 chore: bump version (#3672) 2023-08-10 15:21:11 +00:00
dependabot[bot]
af314dabfb chore: bump @clack/core from 0.3.2 to 0.3.3 (#3671)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Alex Yang <himself65@outlook.com>
2023-08-10 15:05:32 +00:00
dependabot[bot]
11f6273a3a chore: bump nodemon from 2.0.22 to 3.0.1 (#3657)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Alex Yang <himself65@outlook.com>
2023-08-10 14:51:20 +00:00
dependabot[bot]
4b51eb7e06 chore: bump eslint-config-prettier from 8.8.0 to 9.0.0 (#3655)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Alex Yang <himself65@outlook.com>
2023-08-10 14:06:46 +00:00
Elisha Frain
671129bc32 chore: make dev:electron work (#3644)
Co-authored-by: Peng Xiao <pengxiao@outlook.com>
2023-08-10 13:58:46 +00:00
Garfield Lee
358c3a5bb2 feat: add no match route (#3659) 2023-08-10 13:43:51 +00:00
Alex Yang
7c4b8866d3 ci: dependabot.yml update weekly 2023-08-10 09:52:11 -04:00
Alex Yang
dafd5619e6 feat: support get datasource status (#3645) 2023-08-10 05:05:34 +00:00
Alex Yang
05144abd6a v0.8.0-canary.16 2023-08-10 00:34:30 -04:00
Mirone
629a3d11c6 chore: bump blocksuite version (#3654) 2023-08-10 00:19:24 -04:00
Alex Yang
bbd49252cf v0.8.0-canary.15 2023-08-09 17:29:27 -04:00
dependabot[bot]
7c3a7513e6 chore: bump storybook-dark-mode from 3.0.0 to 3.0.1 (#3632)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-08-09 12:37:40 +00:00
JimmFly
e180398637 chore: bump icon version and change plugins icon (#3640) 2023-08-09 11:41:25 +00:00
Garfield Lee
cbd4420fe9 fix: help island icons height (#3639) 2023-08-09 11:40:59 +00:00
dependabot[bot]
f7408dede9 chore: bump @vitejs/plugin-react from 4.0.3 to 4.0.4 (#3631)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-08-09 03:53:19 +00:00
Alex Yang
c324ccc12a chore: bump version (#3634) 2023-08-09 00:13:48 -04:00
Alex Yang
84bbc2d7ce chore: bump version (#3627) 2023-08-09 03:04:42 +00:00
dependabot[bot]
e22d6924cd chore: bump vite from 4.4.7 to 4.4.9 (#3607)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Alex Yang <himself65@outlook.com>
2023-08-09 01:10:54 +00:00
Alex Yang
4472a2a0b0 fix: page validation logic (#3626) 2023-08-09 00:37:53 +00:00
265 changed files with 23337 additions and 14429 deletions

View File

@@ -1,641 +0,0 @@
{
"projectName": "AFFiNE",
"projectOwner": "toeverything",
"repoType": "github",
"repoHost": "https://github.com",
"files": [
"README.md"
],
"imageSize": 50,
"commit": false,
"commitConvention": "angular",
"contributorsPerLine": 7,
"badgeTemplate": "\n[all-contributors-badge]: https://img.shields.io/badge/all_contributors-<%= contributors.length %>-orange.svg?style=flat-square\n",
"contributors": [
{
"login": "doodlewind",
"name": "Yifeng Wang",
"avatar_url": "https://avatars.githubusercontent.com/u/7312949?v=4",
"profile": "https://github.com/doodlewind",
"contributions": [
"code",
"doc"
]
},
{
"login": "darkskygit",
"name": "DarkSky",
"avatar_url": "https://avatars.githubusercontent.com/u/25152247?v=4",
"profile": "https://darksky.eu.org/",
"contributions": [
"code",
"doc"
]
},
{
"login": "tzhangchi",
"name": "Chi Zhang",
"avatar_url": "https://avatars.githubusercontent.com/u/5910926?v=4",
"profile": "http://zhangchi.page/",
"contributions": [
"code",
"doc"
]
},
{
"login": "alt1o",
"name": "wang xinglong",
"avatar_url": "https://avatars.githubusercontent.com/u/21084335?v=4",
"profile": "https://github.com/alt1o",
"contributions": [
"code",
"doc"
]
},
{
"login": "Brooooooklyn",
"name": "LongYinan",
"avatar_url": "https://avatars.githubusercontent.com/u/3468483?v=4",
"profile": "https://github.com/Brooooooklyn",
"contributions": [
"code",
"doc"
]
},
{
"login": "hwangdev97",
"name": "Hwang",
"avatar_url": "https://avatars.githubusercontent.com/u/24713927?v=4",
"profile": "https://github.com/hwangdev97",
"contributions": [
"code",
"doc"
]
},
{
"login": "kobeshanks",
"name": "kobeshanks",
"avatar_url": "https://avatars.githubusercontent.com/u/82570088?v=4",
"profile": "https://github.com/kobeshanks",
"contributions": [
"code",
"doc"
]
},
{
"login": "pengx17",
"name": "Peng Xiao",
"avatar_url": "https://avatars.githubusercontent.com/u/584378?v=4",
"profile": "https://pengx17.vercel.app/",
"contributions": [
"code",
"doc"
]
},
{
"login": "Saul-Mirone",
"name": "Mirone",
"avatar_url": "https://avatars.githubusercontent.com/u/10047788?v=4",
"profile": "https://mirone.me/",
"contributions": [
"code",
"doc"
]
},
{
"login": "zqran",
"name": "zqran",
"avatar_url": "https://avatars.githubusercontent.com/u/15389209?v=4",
"profile": "https://github.com/zqran",
"contributions": [
"code",
"doc"
]
},
{
"login": "SuneBear",
"name": "Shule Hsiung",
"avatar_url": "https://avatars.githubusercontent.com/u/7693264?v=4",
"profile": "https://sunebear.com/",
"contributions": [
"code",
"doc"
]
},
{
"login": "fundon",
"name": "Fangdun Tsai",
"avatar_url": "https://avatars.githubusercontent.com/u/27926?v=4",
"profile": "https://fundon.viz.rs/",
"contributions": [
"code",
"doc"
]
},
{
"login": "lawvs",
"name": "Whitewater",
"avatar_url": "https://avatars.githubusercontent.com/u/18554747?v=4",
"profile": "https://lawvs.github.io/profile/",
"contributions": [
"code",
"doc"
]
},
{
"login": "zuoxiaodong0815",
"name": "xiaodong zuo",
"avatar_url": "https://avatars.githubusercontent.com/u/53252747?v=4",
"profile": "https://github.com/zuoxiaodong0815",
"contributions": [
"code",
"doc"
]
},
{
"login": "Himself65",
"name": "Himself65",
"avatar_url": "https://avatars.githubusercontent.com/u/14026360?v=4",
"profile": "https://github.com/Himself65",
"contributions": [
"code",
"doc"
]
},
{
"login": "DiamondThree",
"name": "DiamondThree",
"avatar_url": "https://avatars.githubusercontent.com/u/24630517?v=4",
"profile": "https://github.com/DiamondThree",
"contributions": [
"code",
"doc"
]
},
{
"login": "QiShaoXuan",
"name": "Qi",
"avatar_url": "https://avatars.githubusercontent.com/u/22772830?v=4",
"profile": "https://github.com/QiShaoXuan",
"contributions": [
"code",
"doc"
]
},
{
"login": "colelawrence",
"name": "Cole Lawrence",
"avatar_url": "https://avatars.githubusercontent.com/u/2925395?v=4",
"profile": "https://colelawrence.com/",
"contributions": [
"code",
"doc"
]
},
{
"login": "linonetwo",
"name": "lin onetwo",
"avatar_url": "https://avatars.githubusercontent.com/u/3746270?v=4",
"profile": "https://onetwo.ren/wiki",
"contributions": [
"code",
"doc"
]
},
{
"login": "thorseraq",
"name": "x1a0t",
"avatar_url": "https://avatars.githubusercontent.com/u/20554850?v=4",
"profile": "https://github.com/thorseraq",
"contributions": [
"code",
"doc"
]
},
{
"login": "HeJiachen-PM",
"name": "HeJiachen-PM",
"avatar_url": "https://avatars.githubusercontent.com/u/79301703?v=4",
"profile": "https://github.com/HeJiachen-PM",
"contributions": [
"research",
"doc"
]
},
{
"login": "joebeijing",
"name": "houjoe",
"avatar_url": "https://avatars.githubusercontent.com/u/22443345?v=4",
"profile": "https://www.notion.so/houjoe/Joe-2a85f5be01004cd2b6a5ad26fbb948b1",
"contributions": [
"research",
"doc"
]
},
{
"login": "Yipei-Operation",
"name": "Yipei Wei",
"avatar_url": "https://avatars.githubusercontent.com/u/79373028?v=4",
"profile": "https://github.com/Yipei-Operation",
"contributions": [
"doc"
]
},
{
"login": "VelikaHF",
"name": "Velika",
"avatar_url": "https://avatars.githubusercontent.com/u/121547898?v=4",
"profile": "https://github.com/VelikaHF",
"contributions": [
"design"
]
},
{
"login": "Svaney-ssman",
"name": "Svaney",
"avatar_url": "https://avatars.githubusercontent.com/u/110808979?v=4",
"profile": "https://github.com/Svaney-ssman",
"contributions": [
"design"
]
},
{
"login": "xell",
"name": "Guozhu Liu",
"avatar_url": "https://avatars.githubusercontent.com/u/132558?v=4",
"profile": "http://xell.me/",
"contributions": [
"design"
]
},
{
"login": "fyZheng07",
"name": "fyZheng07",
"avatar_url": "https://avatars.githubusercontent.com/u/63830919?v=4",
"profile": "https://github.com/fyZheng07",
"contributions": [
"eventOrganizing",
"userTesting"
]
},
{
"login": "CJSS",
"name": "CJSS",
"avatar_url": "https://avatars.githubusercontent.com/u/4605025?v=4",
"profile": "https://github.com/CJSS",
"contributions": [
"doc"
]
},
{
"login": "JimmFly",
"name": "JimmFly",
"avatar_url": "https://avatars.githubusercontent.com/u/102217452?v=4",
"profile": "https://github.com/JimmFly",
"contributions": [
"code"
]
},
{
"login": "mitsuhatu",
"name": "mitsuhatu",
"avatar_url": "https://avatars.githubusercontent.com/u/110213079?v=4",
"profile": "https://github.com/mitsuhatu",
"contributions": [
"code",
"doc"
]
},
{
"login": "Austaras",
"name": "Austaras",
"avatar_url": "https://avatars.githubusercontent.com/u/15013925?v=4",
"profile": "https://shockwave.me/",
"contributions": [
"code",
"doc"
]
},
{
"login": "uptonking",
"name": "Jin Yao",
"avatar_url": "https://avatars.githubusercontent.com/u/11391549?v=4",
"profile": "https://github.com/uptonking",
"contributions": [
"code",
"doc"
]
},
{
"login": "CarlosZoft",
"name": "Carlos Rafael ",
"avatar_url": "https://avatars.githubusercontent.com/u/62192072?v=4",
"profile": "https://github.com/CarlosZoft",
"contributions": [
"code"
]
},
{
"login": "caleboleary",
"name": "Caleb OLeary",
"avatar_url": "https://avatars.githubusercontent.com/u/12816579?v=4",
"profile": "https://github.com/caleboleary",
"contributions": [
"code"
]
},
{
"login": "westongraham",
"name": "Weston Graham",
"avatar_url": "https://avatars.githubusercontent.com/u/89493023?v=4",
"profile": "https://github.com/westongraham",
"contributions": [
"doc"
]
},
{
"login": "SaikaSakura",
"name": "MingLIang Wang",
"avatar_url": "https://avatars.githubusercontent.com/u/11530942?v=4",
"profile": "https://github.com/SaikaSakura",
"contributions": [
"code",
"doc"
]
},
{
"login": "fanjing22",
"name": "fanjing22",
"avatar_url": "https://avatars.githubusercontent.com/u/109729699?v=4",
"profile": "https://github.com/fanjing22",
"contributions": [
"design"
]
},
{
"login": "pointmax",
"name": "pointmax",
"avatar_url": "https://avatars.githubusercontent.com/u/49361135?v=4",
"profile": "https://github.com/pointmax",
"contributions": [
"doc",
"code"
]
},
{
"login": "liby",
"name": "Bryan Lee",
"avatar_url": "https://avatars.githubusercontent.com/u/38807139?v=4",
"profile": "https://liby.github.io/notes",
"contributions": [
"code"
]
},
{
"login": "chenmoonmo",
"name": "Simon Li",
"avatar_url": "https://avatars.githubusercontent.com/u/36295999?v=4",
"profile": "https://github.com/chenmoonmo",
"contributions": [
"code"
]
},
{
"login": "githbq",
"name": "Bob Hu",
"avatar_url": "https://avatars.githubusercontent.com/u/10009709?v=4",
"profile": "https://github.com/githbq",
"contributions": [
"code"
]
},
{
"login": "lucky-chap",
"name": "Quavo",
"avatar_url": "https://avatars.githubusercontent.com/u/67266933?v=4",
"profile": "https://quavo.vercel.app/",
"contributions": [
"doc",
"code"
]
},
{
"login": "LuciNyan",
"name": "子瞻 Luci",
"avatar_url": "https://avatars.githubusercontent.com/u/22126563?v=4",
"profile": "https://github.com/LuciNyan",
"contributions": [
"code"
]
},
{
"login": "m1911star",
"name": "Horus",
"avatar_url": "https://avatars.githubusercontent.com/u/4948120?v=4",
"profile": "http://blog.ipili.me/",
"contributions": [
"code",
"platform"
]
},
{
"login": "fanshyiis",
"name": "Super.x",
"avatar_url": "https://avatars.githubusercontent.com/u/15103283?v=4",
"profile": "https://segmentfault.com/u/qzuser_584786517d31a",
"contributions": [
"code"
]
},
{
"login": "wangyu-1999",
"name": "Wang Yu",
"avatar_url": "https://avatars.githubusercontent.com/u/80874770?v=4",
"profile": "https://wangyu-1999.github.io/",
"contributions": [
"code"
]
},
{
"login": "felixonmars",
"name": "Felix Yan",
"avatar_url": "https://avatars.githubusercontent.com/u/1006477?v=4",
"profile": "https://felixc.at/",
"contributions": [
"code"
]
},
{
"login": "lynettelopez",
"name": "Lynette Lopez",
"avatar_url": "https://avatars.githubusercontent.com/u/32908859?v=4",
"profile": "https://github.com/lynettelopez",
"contributions": [
"code"
]
},
{
"login": "Zheaoli",
"name": "Manjusaka",
"avatar_url": "https://avatars.githubusercontent.com/u/7054676?v=4",
"profile": "http://manjusaka.itscoder.com/",
"contributions": [
"code"
]
},
{
"login": "sudongyuer",
"name": "Frozen FIsh",
"avatar_url": "https://avatars.githubusercontent.com/u/76603360?v=4",
"profile": "https://juejin.cn/user/2867982785579102/posts?sort=popular",
"contributions": [
"code"
]
},
{
"login": "MuhammedFaraz",
"name": "Mohammed Faraz",
"avatar_url": "https://avatars.githubusercontent.com/u/92734739?v=4",
"profile": "https://github.com/MuhammedFaraz",
"contributions": [
"doc",
"code"
]
},
{
"login": "Pranav4399",
"name": "Pranav Sriram ",
"avatar_url": "https://avatars.githubusercontent.com/u/28348429?v=4",
"profile": "https://pranavsriram.dev/",
"contributions": [
"code"
]
},
{
"login": "Reson-a",
"name": "Reson-a",
"avatar_url": "https://avatars.githubusercontent.com/u/20806266?v=4",
"profile": "https://github.com/Reson-a",
"contributions": [
"code"
]
},
{
"login": "hezhizhen",
"name": "Zhizhen He",
"avatar_url": "https://avatars.githubusercontent.com/u/7611700?v=4",
"profile": "https://t.me/littlepoint",
"contributions": [
"code"
]
},
{
"login": "AkaraChen",
"name": "AkaraChen",
"avatar_url": "https://avatars.githubusercontent.com/u/85140972?v=4",
"profile": "https://akr.moe/",
"contributions": [
"code"
]
},
{
"login": "suyanhanx",
"name": "Suyan",
"avatar_url": "https://avatars.githubusercontent.com/u/24221472?v=4",
"profile": "https://github.com/suyanhanx",
"contributions": [
"code"
]
},
{
"login": "hehex9",
"name": "hehe",
"avatar_url": "https://avatars.githubusercontent.com/u/9209882?v=4",
"profile": "https://github.com/hehex9",
"contributions": [
"code"
]
},
{
"login": "albertodlc",
"name": "Alberto de la Cruz",
"avatar_url": "https://avatars.githubusercontent.com/u/32411964?v=4",
"profile": "https://github.com/albertodlc",
"contributions": [
"code"
]
},
{
"login": "AlessioGr",
"name": "Alessio Gravili",
"avatar_url": "https://avatars.githubusercontent.com/u/70709113?v=4",
"profile": "https://github.com/AlessioGr",
"contributions": [
"code"
]
},
{
"login": "lzlme",
"name": "Zhilin Liu",
"avatar_url": "https://avatars.githubusercontent.com/u/117659326?v=4",
"profile": "https://github.com/lzlme",
"contributions": [
"code"
]
},
{
"login": "suica",
"name": "Sg",
"avatar_url": "https://avatars.githubusercontent.com/u/8041462?v=4",
"profile": "https://github.com/suica",
"contributions": [
"code"
]
},
{
"login": "sinchang",
"name": "Jeff Wen",
"avatar_url": "https://avatars.githubusercontent.com/u/3297859?v=4",
"profile": "https://sinchang.me/",
"contributions": [
"code"
]
},
{
"login": "m1212e",
"name": "m1212e",
"avatar_url": "https://avatars.githubusercontent.com/u/14091540?v=4",
"profile": "https://m1212e.github.io/portfolio/",
"contributions": [
"code"
]
},
{
"login": "adityash1",
"name": "Aditya Sharma",
"avatar_url": "https://avatars.githubusercontent.com/u/65771169?v=4",
"profile": "https://adityash1.github.io/",
"contributions": [
"code"
]
},
{
"login": "sheben404",
"name": "Kehan Wang",
"avatar_url": "https://avatars.githubusercontent.com/u/61317160?v=4",
"profile": "https://github.com/sheben404",
"contributions": [
"code"
]
},
{
"login": "VictorNanka",
"name": "VictorNanka",
"avatar_url": "https://avatars.githubusercontent.com/u/30154366?v=4",
"profile": "https://github.com/VictorNanka",
"contributions": [
"code"
]
}
]
}

2
.cargo/config.toml Normal file
View File

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

View File

@@ -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'],
},
],
},
],

View File

@@ -3,7 +3,7 @@ updates:
- package-ecosystem: 'npm'
directory: '/'
schedule:
interval: 'daily'
interval: 'weekly'
versioning-strategy: increase
commit-message:
prefix: 'chore'

View File

@@ -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
@@ -216,28 +235,6 @@ jobs:
name: affine
fail_ci_if_error: false
storybook-test:
name: Storybook Test
runs-on: ubuntu-latest
environment: development
needs: [build-storybook]
steps:
- uses: actions/checkout@v3
- name: Setup Node.js
uses: ./.github/actions/setup-node
with:
playwright-install: true
electron-install: false
- name: Download storybook artifact
uses: actions/download-artifact@v3
with:
name: storybook
path: ./apps/storybook/storybook-static
- name: Run storybook tests
working-directory: ./apps/storybook
run: |
yarn exec concurrently -k -s first -n "SB,TEST" -c "magenta,blue" "yarn exec serve ./storybook-static -l 6006" "yarn exec wait-on tcp:6006 && yarn test"
e2e-plugin-test:
name: E2E Plugin Test
runs-on: ubuntu-latest
@@ -280,6 +277,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

38
.github/workflows/publish-storybook.yml vendored Normal file
View File

@@ -0,0 +1,38 @@
name: Publish Storybook
on:
push:
branches:
- master
pull_request_target:
branches:
- master
paths-ignore:
- README.md
- .github/**
- '!.github/workflows/publish-storybook.yml'
jobs:
publish-storybook:
name: Publish Storybook
runs-on: ubuntu-latest
environment: development
steps:
- uses: actions/checkout@v3
with:
ref: ${{ github.event.pull_request.merge_commit_sha }}
# This is required to fetch all commits for chromatic
fetch-depth: 0
- name: Setup Node.js
uses: ./.github/actions/setup-node
with:
electron-install: false
- name: Build Plugins
run: yarn run build:plugins
- name: Publish to Chromatic
uses: chromaui/action@v1
with:
workingDir: apps/storybook
buildScriptName: build
onlyChanged: true
projectToken: ${{ secrets.CHROMATIC_PROJECT_TOKEN }}

View File

@@ -127,7 +127,7 @@ If you have questions, you are welcome to contact us. One of the best places to
| [@affine/copilot-plugin](plugins/copilot) | AI Copilot that help you document writing |
| [@affine/image-preview-plugin](plugins/image-preview) | Component for previewing an image |
## Thanks
## Upstreams
We would also like to give thanks to open-source projects that make AFFiNE possible:
@@ -178,11 +178,19 @@ See [BUILDING.md] for instructions on how to build AFFiNE from source code.
We welcome contributions from everyone.
See [docs/contributing/tutorial.md](./docs/contributing/tutorial.md) for details.
## Thanks
<a href="https://www.chromatic.com/"><img src="https://user-images.githubusercontent.com/321738/84662277-e3db4f80-af1b-11ea-88f5-91d67a5e59f6.png" width="153" height="30" alt="Chromatic" /></a>
Thanks to [Chromatic](https://www.chromatic.com/) for providing the visual testing platform that helps us review UI changes and catch visual regressions.
## License
See [LICENSE] for details.
[all-contributors-badge]: https://img.shields.io/github/all-contributors/toeverything/AFFiNE/master?color=orange
[![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2Ftoeverything%2FAFFiNE.svg?type=large)](https://app.fossa.com/projects/git%2Bgithub.com%2Ftoeverything%2FAFFiNE?ref=badge_large)
[all-contributors-badge]: https://img.shields.io/github/contributors/toeverything/AFFiNE
[license]: ./LICENSE
[building.md]: ./docs/BUILDING.md
[update page]: https://affine.pro/blog?tag=Release%20Note
@@ -196,5 +204,3 @@ See [LICENSE] for details.
[typescript-version-icon]: https://img.shields.io/github/package-json/dependency-version/toeverything/affine/dev/typescript
[react-version-icon]: https://img.shields.io/github/package-json/dependency-version/toeverything/AFFiNE/react?filename=apps%2Fcore%2Fpackage.json&color=rgb(97%2C228%2C251)
[blocksuite-icon]: https://img.shields.io/github/package-json/dependency-version/toeverything/AFFiNE/@blocksuite/store?color=6880ff&filename=apps%2Fcore%2Fpackage.json&label=blocksuite
[![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2Ftoeverything%2FAFFiNE.svg?type=large)](https://app.fossa.com/projects/git%2Bgithub.com%2Ftoeverything%2FAFFiNE?ref=badge_large)

View File

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

View File

@@ -84,6 +84,7 @@ export const createConfiguration: (
experiments: {
topLevelAwait: true,
outputModule: false,
syncWebAssembly: true,
},
output: {
environment: {

View File

@@ -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',
}),
],

View File

@@ -2,12 +2,18 @@
"name": "@affine/core",
"type": "module",
"private": true,
"version": "0.8.0-canary.14",
"version": "0.8.0-canary.24",
"scripts": {
"build": "yarn -T run build-core",
"dev": "yarn -T run dev-core",
"static-server": "ts-node-esm ./server.mts"
},
"exports": {
"./app": "./src/app.tsx",
"./router": "./src/router.ts",
"./bootstrap/setup": "./src/bootstrap/setup.ts",
"./bootstrap/register-plugins": "./src/bootstrap/register-plugins.ts"
},
"dependencies": {
"@affine-test/fixtures": "workspace:*",
"@affine/component": "workspace:*",
@@ -18,30 +24,32 @@
"@affine/jotai": "workspace:*",
"@affine/templates": "workspace:*",
"@affine/workspace": "workspace:*",
"@blocksuite/block-std": "0.0.0-20230808131052-ef3445a9-nightly",
"@blocksuite/blocks": "0.0.0-20230808131052-ef3445a9-nightly",
"@blocksuite/editor": "0.0.0-20230808131052-ef3445a9-nightly",
"@blocksuite/global": "0.0.0-20230808131052-ef3445a9-nightly",
"@blocksuite/icons": "^2.1.30",
"@blocksuite/lit": "0.0.0-20230808131052-ef3445a9-nightly",
"@blocksuite/store": "0.0.0-20230808131052-ef3445a9-nightly",
"@blocksuite/block-std": "0.0.0-20230816084758-3be96d22-nightly",
"@blocksuite/blocks": "0.0.0-20230816084758-3be96d22-nightly",
"@blocksuite/editor": "0.0.0-20230816084758-3be96d22-nightly",
"@blocksuite/global": "0.0.0-20230816084758-3be96d22-nightly",
"@blocksuite/icons": "^2.1.31",
"@blocksuite/lit": "0.0.0-20230816084758-3be96d22-nightly",
"@blocksuite/store": "0.0.0-20230816084758-3be96d22-nightly",
"@dnd-kit/core": "^6.0.8",
"@dnd-kit/sortable": "^7.0.2",
"@emotion/cache": "^11.11.0",
"@emotion/react": "^11.11.1",
"@emotion/server": "^11.11.0",
"@emotion/styled": "^11.11.0",
"@mui/material": "^5.14.2",
"@mui/material": "^5.14.5",
"@react-hookz/web": "^23.1.0",
"@toeverything/components": "^0.0.10",
"@toeverything/components": "^0.0.11",
"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",
"graphql": "^16.8.0",
"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.debounce": "^4.0.8",
"lottie-web": "^5.12.2",
"mini-css-extract-plugin": "^2.7.6",
"next-themes": "^0.2.1",
@@ -49,21 +57,22 @@
"react": "18.2.0",
"react-dom": "18.2.0",
"react-is": "18.2.0",
"react-resizable-panels": "^0.0.54",
"react-router-dom": "^6.14.2",
"react-resizable-panels": "^0.0.55",
"react-router-dom": "^6.15.0",
"rxjs": "^7.8.1",
"ses": "^0.18.5",
"swr": "2.1.5",
"ses": "^0.18.7",
"swr": "2.2.1",
"y-protocols": "^1.0.5",
"yjs": "^13.6.7",
"zod": "^3.21.4"
"zod": "^3.22.1"
},
"devDependencies": {
"@perfsee/webpack": "^1.8.2",
"@pmmmwh/react-refresh-webpack-plugin": "^0.5.10",
"@sentry/webpack-plugin": "^2.5.0",
"@svgr/webpack": "^8.0.1",
"@swc/core": "^1.3.74",
"@perfsee/webpack": "^1.8.4",
"@pmmmwh/react-refresh-webpack-plugin": "^0.5.11",
"@sentry/webpack-plugin": "^2.6.2",
"@svgr/webpack": "^8.1.0",
"@swc/core": "^1.3.77",
"@types/lodash.debounce": "^4.0.7",
"@types/webpack-env": "^1.18.1",
"copy-webpack-plugin": "^11.0.0",
"css-loader": "^6.8.1",
@@ -73,7 +82,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",

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 90 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 132 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 280 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 330 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 723 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 224 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 971 KiB

View File

@@ -1,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,7 +20,10 @@ 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 { rootStore } from '@toeverything/infra/atom';
import { buildShowcaseWorkspace } from '@toeverything/infra/blocksuite';
import { setPageModeAtom } from '../../atoms';
import {
BlockSuitePageList,
NewWorkspaceSettingDetail,
@@ -41,21 +44,23 @@ export const LocalAdapter: WorkspaceAdapter<WorkspaceFlavour.LOCAL> = {
WorkspaceFlavour.LOCAL
);
blockSuiteWorkspace.meta.setName(DEFAULT_WORKSPACE_NAME);
const page = blockSuiteWorkspace.createPage({
id: `${blockSuiteWorkspace.id}-${DEFAULT_HELLO_WORLD_PAGE_ID_SUFFIX}`,
});
if (runtimeConfig.enablePreloading) {
initPageWithPreloading(page).catch(err => {
buildShowcaseWorkspace(blockSuiteWorkspace, {
store: rootStore,
atoms: {
pageMode: setPageModeAtom,
},
}).catch(err => {
logger.error('init page with preloading failed', err);
});
} else {
const page = blockSuiteWorkspace.createPage({
id: `${blockSuiteWorkspace.id}-${DEFAULT_HELLO_WORLD_PAGE_ID_SUFFIX}`,
});
initEmptyPage(page).catch(error => {
logger.error('init page with empty failed', error);
});
}
blockSuiteWorkspace.setPageMeta(page.id, {
jumpOnce: true,
});
const provider = createIndexedDBDownloadProvider(
blockSuiteWorkspace.id,
blockSuiteWorkspace.doc,

View File

@@ -70,12 +70,16 @@ export const guideOnboardingAtom = atom<
}));
}
);
export const guideDownloadClientTipAtom = atom<
Guide['downloadClientTip'],
[open: boolean],
void
>(
get => {
if (environment.isDesktop) {
return false;
}
return get(guidePrimitiveAtom).downloadClientTip;
},
(_, set, open) => {

View File

@@ -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');
@@ -36,9 +36,13 @@ const importLogger = new DebugLogger('plugins:import');
const pushLayoutAtom = atom<
null,
// fixme: check plugin name here
[pluginName: string, create: (root: HTMLElement) => () => void],
[
pluginName: string,
create: (root: HTMLElement) => () => void,
options: { maxWidth: (number | undefined)[] } | undefined,
],
void
>(null, (_, set, pluginName, callback) => {
>(null, (_, set, pluginName, callback, options) => {
set(pluginWindowAtom, items => ({
...items,
[pluginName]: callback,
@@ -50,20 +54,20 @@ const pushLayoutAtom = atom<
first: 'editor',
second: pluginName,
splitPercentage: 70,
maxWidth: options?.maxWidth,
};
} else {
return {
...layout,
direction: 'horizontal',
first: 'editor',
splitPercentage: 70,
second: {
direction: 'horizontal',
// fixme: incorrect type here
first: layout.second,
second: pluginName,
splitPercentage: 70,
first: pluginName,
second: layout.second,
splitPercentage: 50,
},
} as ExpectedLayout;
} satisfies ExpectedLayout;
}
});
addCleanup(pluginName, () => {
@@ -77,36 +81,27 @@ const deleteLayoutAtom = atom<null, [string], void>(null, (_, set, id) => {
delete newItems[id];
return newItems;
});
const removeLayout = (layout: LayoutNode): LayoutNode => {
if (layout === 'editor') {
return 'editor';
const removeLayout = (layout: LayoutNode): LayoutNode | string => {
if (typeof layout === 'string') {
return layout;
}
if (layout.first === id) {
return layout.second;
} else if (layout.second === id) {
return layout.first;
} else {
if (typeof layout === 'string') {
return layout as ExpectedLayout;
}
if (layout.first === id) {
return layout.second;
} else if (layout.second === id) {
return layout.first;
} else {
return removeLayout(layout.second);
}
return {
...layout,
second: removeLayout(layout.second),
};
}
};
set(contentLayoutAtom, layout => {
if (layout === 'editor') {
return 'editor';
} else {
if (typeof layout === 'string') {
return layout as ExpectedLayout;
}
if (layout.first === id) {
return layout.second as ExpectedLayout;
} else if (layout.second === id) {
return layout.first as ExpectedLayout;
} else {
return removeLayout(layout.second) as ExpectedLayout;
}
return removeLayout(layout) as ExpectedLayout;
}
});
});
@@ -123,6 +118,7 @@ const rootImportsMapSetupPromise = setupImportsMap(_rootImportsMap, {
swr: import('swr'),
'@affine/component': import('@affine/component'),
'@blocksuite/icons': import('@blocksuite/icons'),
'@blocksuite/blocks': import('@blocksuite/blocks'),
'@affine/sdk/entry': {
rootStore: rootStore,
currentWorkspaceAtom: currentWorkspaceAtom,
@@ -130,7 +126,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 +190,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,

View File

@@ -85,12 +85,13 @@ export const pluginRegisterPromise = Promise.all(
if (assets.length > 0) {
await Promise.all(
assets.map(async (asset: string) => {
const loadedAssetName = `${pluginName}_${asset}`;
// todo(himself65): add assets into shadow dom
if (loadedAssets.has(asset)) {
if (loadedAssets.has(loadedAssetName)) {
return Promise.resolve();
}
if (asset.endsWith('.css')) {
loadedAssets.add(asset);
loadedAssets.add(loadedAssetName);
const res = await fetch(`${baseURL}/${asset}`);
if (res.ok) {
// todo: how to put css file into sandbox?

View File

@@ -169,7 +169,14 @@ function createFirstAppData() {
rootStore.set(rootWorkspacesMetadataAtom, result);
}
let isSetup = false;
export async function setup() {
if (isSetup) {
console.warn('already setup');
return;
}
isSetup = true;
rootStore.set(
workspaceAdaptersAtom,
WorkspaceAdapters as Record<

View File

@@ -81,11 +81,7 @@ const NameWorkspaceContent = ({
onChange={setWorkspaceName}
/>
<div className={style.buttonGroup}>
<Button
data-testid="create-workspace-close-button"
type="primary"
onClick={onClose}
>
<Button data-testid="create-workspace-close-button" onClick={onClose}>
{t.Cancel()}
</Button>
<Button

View File

@@ -1,4 +1,10 @@
import { Menu, MenuItem, MenuTrigger, styled } from '@affine/component';
import {
Menu,
MenuItem,
type MenuProps,
MenuTrigger,
styled,
} from '@affine/component';
import { LOCALES } from '@affine/i18n';
import { useI18N } from '@affine/i18n';
import type { ButtonProps } from '@toeverything/components/button';
@@ -6,7 +12,6 @@ import type { ReactElement } from 'react';
import { useCallback } from 'react';
export const StyledListItem = styled(MenuItem)(() => ({
width: '132px',
height: '38px',
textTransform: 'capitalize',
}));
@@ -46,11 +51,14 @@ const LanguageMenuContent = ({ currentLanguage }: LanguageMenuContentProps) => {
);
};
interface LanguageMenuProps {
interface LanguageMenuProps extends Omit<MenuProps, 'children'> {
triggerProps?: ButtonProps;
}
export const LanguageMenu = ({ triggerProps }: LanguageMenuProps) => {
export const LanguageMenu = ({
triggerProps,
...menuProps
}: LanguageMenuProps) => {
const i18n = useI18N();
const currentLanguage = LOCALES.find(item => item.tag === i18n.language);
@@ -67,6 +75,7 @@ export const LanguageMenu = ({ triggerProps }: LanguageMenuProps) => {
placement="bottom-end"
trigger="click"
disablePortal={true}
{...menuProps}
>
<MenuTrigger
data-testid="language-menu-button"

View File

@@ -30,11 +30,11 @@ export const DeleteLeaveWorkspace = ({
name={
<span style={{ color: 'var(--affine-error-color)' }}>
{isOwner
? t['com.affine.settings.workspace.remove']()
? t['com.affine.settings.remove-workspace']()
: t['Leave Workspace']()}
</span>
}
desc={t['com.affine.settings.workspace.remove.message']()}
desc={t['com.affine.settings.remove-workspace-description']()}
style={{ cursor: 'pointer' }}
onClick={() => {
setShowDelete(true);

View File

@@ -38,9 +38,8 @@ export const ExportPanel = ({ workspace }: ExportPanelProps) => {
const t = useAFFiNEI18N();
const onExport = useCallback(async () => {
await syncBlobsToSqliteDb(workspace);
const result: SaveDBFileResult = await window.apis?.dialog.saveDBFileAs(
workspaceId
);
const result: SaveDBFileResult =
await window.apis?.dialog.saveDBFileAs(workspaceId);
if (result?.error) {
toast(t[result.error]());
} else if (!result?.canceled) {

View File

@@ -59,14 +59,12 @@ export const WorkspaceSettingDetail = ({
<>
<SettingHeader
title={t[`Workspace Settings with name`]({ name })}
subtitle={t['You can customize your workspace here.']()}
subtitle={t['com.affine.settings.workspace.description']()}
/>
<SettingWrapper title={t['Info']()}>
<SettingRow
name={t['Workspace Profile']()}
desc={t[
'Only an owner can edit the the Workspace avatar and name.Changes will be shown for everyone.'
]()}
desc={t['com.affine.settings.workspace.not-owner']()}
spreadCol={false}
>
<ProfilePanel workspace={workspace} />

View File

@@ -1,7 +1,7 @@
import { Input, toast } from '@affine/component';
import { FlexWrapper, Input, toast, Wrapper } from '@affine/component';
import { WorkspaceAvatar } from '@affine/component/workspace-avatar';
import { useAFFiNEI18N } from '@affine/i18n/hooks';
import { DoneIcon } from '@blocksuite/icons';
import { CameraIcon, DoneIcon } from '@blocksuite/icons';
import { IconButton } from '@toeverything/components/button';
import { useBlockSuiteWorkspaceAvatarUrl } from '@toeverything/hooks/use-block-suite-workspace-avatar-url';
import { useBlockSuiteWorkspaceName } from '@toeverything/hooks/use-block-suite-workspace-name';
@@ -11,25 +11,6 @@ import type { AffineOfficialWorkspace } from '../../../shared';
import { Upload } from '../../pure/file-upload';
import * as style from './style.css';
const CameraIcon = () => {
return (
<svg
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M10.6236 4.25001C10.635 4.25001 10.6467 4.25002 10.6584 4.25002H13.3416C13.3533 4.25002 13.365 4.25001 13.3764 4.25001C13.5609 4.24995 13.7105 4.2499 13.8543 4.26611C14.5981 4.34997 15.2693 4.75627 15.6826 5.38026C15.7624 5.50084 15.83 5.63398 15.9121 5.79586C15.9173 5.80613 15.9226 5.81652 15.9279 5.82703C15.9538 5.87792 15.9679 5.90562 15.9789 5.9261C15.9832 5.9341 15.9857 5.93861 15.9869 5.94065C16.0076 5.97069 16.0435 5.99406 16.0878 5.99905L16.0849 5.99877C16.0849 5.99877 16.0907 5.99918 16.1047 5.99947C16.1286 5.99998 16.1604 6.00002 16.2181 6.00002L17.185 6.00001C17.6577 6 18.0566 5.99999 18.3833 6.02627C18.7252 6.05377 19.0531 6.11364 19.3656 6.27035C19.8402 6.50842 20.2283 6.88944 20.4723 7.36077C20.6336 7.67233 20.6951 7.99944 20.7232 8.33858C20.75 8.66166 20.75 9.05554 20.75 9.51992V16.2301C20.75 16.6945 20.75 17.0884 20.7232 17.4114C20.6951 17.7506 20.6336 18.0777 20.4723 18.3893C20.2283 18.8606 19.8402 19.2416 19.3656 19.4797C19.0531 19.6364 18.7252 19.6963 18.3833 19.7238C18.0566 19.75 17.6578 19.75 17.185 19.75H6.81497C6.34225 19.75 5.9434 19.75 5.61668 19.7238C5.27477 19.6963 4.94688 19.6364 4.63444 19.4797C4.15978 19.2416 3.77167 18.8606 3.52771 18.3893C3.36644 18.0777 3.30494 17.7506 3.27679 17.4114C3.24998 17.0884 3.24999 16.6945 3.25 16.2302V9.51987C3.24999 9.05551 3.24998 8.66164 3.27679 8.33858C3.30494 7.99944 3.36644 7.67233 3.52771 7.36077C3.77167 6.88944 4.15978 6.50842 4.63444 6.27035C4.94688 6.11364 5.27477 6.05377 5.61668 6.02627C5.9434 5.99999 6.34225 6 6.81498 6.00001L7.78191 6.00002C7.83959 6.00002 7.87142 5.99998 7.8953 5.99947C7.90607 5.99924 7.91176 5.99897 7.91398 5.99884C7.95747 5.99343 7.99267 5.9703 8.01312 5.94066C8.01429 5.93863 8.01684 5.93412 8.02113 5.9261C8.0321 5.90561 8.04622 5.87791 8.07206 5.82703C8.07739 5.81653 8.08266 5.80615 8.08787 5.79588C8.17004 5.63397 8.23759 5.50086 8.31745 5.38026C8.73067 4.75627 9.40192 4.34997 10.1457 4.26611C10.2895 4.2499 10.4391 4.24995 10.6236 4.25001ZM10.6584 5.75002C10.422 5.75002 10.3627 5.75114 10.3138 5.75666C10.0055 5.79142 9.73316 5.95919 9.56809 6.20845C9.54218 6.24758 9.51544 6.29761 9.40943 6.50633C9.40611 6.51287 9.40274 6.5195 9.39934 6.52622C9.36115 6.60161 9.31758 6.68761 9.26505 6.76694C8.9964 7.17261 8.56105 7.4354 8.08026 7.48961C7.98625 7.50021 7.89021 7.50011 7.80434 7.50003C7.79678 7.50002 7.7893 7.50002 7.78191 7.50002H6.84445C6.33444 7.50002 5.99634 7.50058 5.73693 7.52144C5.48594 7.54163 5.37478 7.57713 5.30693 7.61115C5.11257 7.70864 4.95675 7.86306 4.85983 8.05029C4.82733 8.11308 4.79194 8.21816 4.77165 8.46266C4.7506 8.71626 4.75 9.0474 4.75 9.55001V16.2C4.75 16.7026 4.7506 17.0338 4.77165 17.2874C4.79194 17.5319 4.82733 17.6369 4.85983 17.6997C4.95675 17.887 5.11257 18.0414 5.30693 18.1389C5.37478 18.1729 5.48594 18.2084 5.73693 18.2286C5.99634 18.2494 6.33444 18.25 6.84445 18.25H17.1556C17.6656 18.25 18.0037 18.2494 18.2631 18.2286C18.5141 18.2084 18.6252 18.1729 18.6931 18.1389C18.8874 18.0414 19.0433 17.887 19.1402 17.6997C19.1727 17.6369 19.2081 17.5319 19.2283 17.2874C19.2494 17.0338 19.25 16.7026 19.25 16.2V9.55001C19.25 9.0474 19.2494 8.71626 19.2283 8.46266C19.2081 8.21816 19.1727 8.11308 19.1402 8.05029C19.0433 7.86306 18.8874 7.70864 18.6931 7.61115C18.6252 7.57713 18.5141 7.54163 18.2631 7.52144C18.0037 7.50058 17.6656 7.50002 17.1556 7.50002H16.2181C16.2107 7.50002 16.2032 7.50002 16.1957 7.50003C16.1098 7.50011 16.0138 7.50021 15.9197 7.48961C15.4389 7.4354 15.0036 7.17261 14.735 6.76694C14.6824 6.68761 14.6389 6.60163 14.6007 6.52622C14.5973 6.5195 14.5939 6.51287 14.5906 6.50633C14.4846 6.29763 14.4578 6.24758 14.4319 6.20846C14.2668 5.95919 13.9945 5.79142 13.6862 5.75666C13.6373 5.75114 13.578 5.75002 13.3416 5.75002H10.6584ZM12 11C10.9303 11 10.0833 11.8506 10.0833 12.875C10.0833 13.8995 10.9303 14.75 12 14.75C13.0697 14.75 13.9167 13.8995 13.9167 12.875C13.9167 11.8506 13.0697 11 12 11ZM8.58333 12.875C8.58333 11 10.1242 9.50002 12 9.50002C13.8758 9.50002 15.4167 11 15.4167 12.875C15.4167 14.7501 13.8758 16.25 12 16.25C10.1242 16.25 8.58333 14.7501 8.58333 12.875Z"
fill="white"
/>
</svg>
);
};
interface ProfilePanelProps {
workspace: AffineOfficialWorkspace;
}
@@ -74,32 +55,35 @@ export const ProfilePanel = ({ workspace }: ProfilePanelProps) => {
</>
</Upload>
</div>
<div className={style.profileHandlerWrapper}>
<Input
width={280}
height={32}
defaultValue={input}
data-testid="workspace-name-input"
placeholder={t['Workspace Name']()}
maxLength={64}
minLength={0}
onChange={setInput}
/>
{input === workspace.blockSuiteWorkspace.meta.name ? null : (
<IconButton
data-testid="save-workspace-name"
onClick={() => {
handleUpdateWorkspaceName(input);
}}
active={true}
style={{
marginLeft: '12px',
}}
>
<DoneIcon />
</IconButton>
)}
</div>
<Wrapper marginLeft={20}>
<div className={style.label}>{t['Workspace Name']()}</div>
<FlexWrapper alignItems="center" flexGrow="1">
<Input
width={280}
height={32}
defaultValue={input}
data-testid="workspace-name-input"
placeholder={t['Workspace Name']()}
maxLength={64}
minLength={0}
onChange={setInput}
/>
{input === workspace.blockSuiteWorkspace.meta.name ? null : (
<IconButton
data-testid="save-workspace-name"
onClick={() => {
handleUpdateWorkspaceName(input);
}}
active={true}
style={{
marginLeft: '12px',
}}
>
<DoneIcon />
</IconButton>
)}
</FlexWrapper>
</Wrapper>
</div>
);
};

View File

@@ -94,7 +94,7 @@ const FakePublishPanelAffine = (_props: FakePublishPanelAffineProps) => {
return (
<Tooltip
content={t['com.affine.settings.workspace.publish.local-tooltip']()}
content={t['com.affine.settings.workspace.publish-tooltip']()}
placement="top"
>
<div className={style.fakeWrapper}>

View File

@@ -5,12 +5,6 @@ export const profileWrapper = style({
alignItems: 'flex-end',
marginTop: '12px',
});
export const profileHandlerWrapper = style({
flexGrow: '1',
display: 'flex',
alignItems: 'center',
marginLeft: '20px',
});
export const avatarWrapper = style({
width: '56px',
@@ -39,6 +33,8 @@ globalStyle(`${avatarWrapper} .camera-icon-wrapper`, {
alignItems: 'center',
backgroundColor: 'rgba(60, 61, 63, 0.5)',
zIndex: '1',
color: 'var(--affine-white)',
fontSize: '24px',
});
export const urlButton = style({
@@ -71,3 +67,9 @@ export const fakeWrapper = style({
},
},
});
export const label = style({
fontSize: 'var(--affine-font-xs)',
color: 'var(--affine-text-secondary-color)',
marginBottom: '5px',
});

View File

@@ -58,7 +58,7 @@ export const AboutAffine = () => {
</SettingRow>
<SettingRow
name={t[`Discover what's new`]()}
desc={t['View the AFFiNE Changelog.']()}
desc={t['Changelog description']()}
style={{ cursor: 'pointer' }}
onClick={() => {
window.open(runtimeConfig.changelogUrl, '_blank');

View File

@@ -35,10 +35,10 @@ export const ThemeSettings = () => {
<RadioButton value="system" data-testid="system-theme-trigger">
{t['system']()}
</RadioButton>
<RadioButton bold={true} value="light" data-testid="light-theme-trigger">
<RadioButton value="light" data-testid="light-theme-trigger">
{t['light']()}
</RadioButton>
<RadioButton bold={true} value="dark" data-testid="dark-theme-trigger">
<RadioButton value="dark" data-testid="dark-theme-trigger">
{t['dark']()}
</RadioButton>
</RadioButtonGroup>
@@ -63,7 +63,6 @@ const FontFamilySettings = () => {
return (
<RadioButton
key={key}
bold={true}
value={key}
data-testid="system-font-style-trigger"
style={{
@@ -110,16 +109,28 @@ export const AppearanceSettings = () => {
</SettingRow>
<SettingRow
name={t['Display Language']()}
desc={t['Select the language for the interface.']()}
desc={t['com.affine.settings.appearance.language-description']()}
>
<div className={settingWrapper}>
<LanguageMenu />
<LanguageMenu
triggerContainerStyle={{ width: '100%' }}
triggerProps={{
style: {
width: '100%',
justifyContent: 'space-between',
fontWeight: 600,
padding: '0 10px',
},
}}
/>
</div>
</SettingRow>
{environment.isDesktop ? (
<SettingRow
name={t['Client Border Style']()}
desc={t['Customize the appearance of the client.']()}
desc={t[
'com.affine.settings.appearance.border-style-description'
]()}
>
<Switch
checked={appSettings.clientBorder}
@@ -130,7 +141,7 @@ export const AppearanceSettings = () => {
<SettingRow
name={t['Full width Layout']()}
desc={t['Maximum display of content within a page.']()}
desc={t['com.affine.settings.appearance.full-width-description']()}
>
<Switch
data-testid="full-width-layout-trigger"
@@ -141,7 +152,9 @@ export const AppearanceSettings = () => {
{runtimeConfig.enableNewSettingUnstableApi && environment.isDesktop ? (
<SettingRow
name={t['Window frame style']()}
desc={t['Customize appearance of Windows Client.']()}
desc={t[
'com.affine.settings.appearance.window-frame-description'
]()}
>
<RadioButtonGroup
className={settingWrapper}
@@ -166,7 +179,7 @@ export const AppearanceSettings = () => {
<SettingWrapper title={t['Date']()}>
<SettingRow
name={t['Date Format']()}
desc={t['Customize your date style.']()}
desc={t['com.affine.settings.appearance.date-format-description']()}
>
<div className={settingWrapper}>
<DateFormatSetting />
@@ -174,7 +187,7 @@ export const AppearanceSettings = () => {
</SettingRow>
<SettingRow
name={t['Start Week On Monday']()}
desc={t['By default, the week starts on Sunday.']()}
desc={t['com.affine.settings.appearance.start-week-description']()}
>
<Switch
checked={appSettings.startWeekOnMonday}
@@ -187,8 +200,8 @@ export const AppearanceSettings = () => {
{environment.isDesktop ? (
<SettingWrapper title={t['Sidebar']()}>
<SettingRow
name={t['com.affine.settings.appearance.sidebar.noise']()}
desc={t['com.affine.settings.appearance.sidebar.noise.message']()}
name={t['com.affine.settings.noise-style']()}
desc={t['com.affine.settings.noise-style-description']()}
>
<Switch
checked={appSettings.enableNoisyBackground}
@@ -198,10 +211,8 @@ export const AppearanceSettings = () => {
/>
</SettingRow>
<SettingRow
name={t['com.affine.settings.appearance.sidebar.translucent']()}
desc={t[
'com.affine.settings.appearance.sidebar.translucent.message'
]()}
name={t['com.affine.settings.translucent-style']()}
desc={t['com.affine.settings.translucent-style-description']()}
>
<Switch
checked={appSettings.enableBlurBackground}

View File

@@ -1,9 +1,9 @@
import { useAFFiNEI18N } from '@affine/i18n/hooks';
import {
AiIcon,
AppearanceIcon,
InformationIcon,
KeyboardIcon,
PluginIcon,
} from '@blocksuite/icons';
import type { ReactElement, SVGProps } from 'react';
@@ -46,7 +46,7 @@ export const useGeneralSettingList = (): GeneralSettingList => {
{
key: 'plugins',
title: 'Plugins',
icon: AiIcon,
icon: PluginIcon,
testId: 'plugins-panel-trigger',
},
{

View File

@@ -1,10 +1,11 @@
import {
SettingModal as SettingModalBase,
type SettingModalProps as SettingModalBaseProps,
WorkspaceDetailSkeleton,
} from '@affine/component/setting-components';
import { useAFFiNEI18N } from '@affine/i18n/hooks';
import { ContactWithUsIcon } from '@blocksuite/icons';
import { useCallback } from 'react';
import { Suspense, useCallback } from 'react';
import { AccountSetting } from './account-setting';
import {
@@ -13,7 +14,7 @@ import {
useGeneralSettingList,
} from './general-setting';
import { SettingSidebar } from './setting-sidebar';
import { settingContent } from './style.css';
import { footerIconWrapper, settingContent } from './style.css';
import { WorkspaceSetting } from './workspace-setting';
type ActiveTab = GeneralSettingKeys | 'workspace' | 'account';
@@ -77,7 +78,9 @@ export const SettingModal = ({
<div className="wrapper">
<div className="content">
{activeTab === 'workspace' && workspaceId ? (
<WorkspaceSetting key={workspaceId} workspaceId={workspaceId} />
<Suspense fallback={<WorkspaceDetailSkeleton />}>
<WorkspaceSetting key={workspaceId} workspaceId={workspaceId} />
</Suspense>
) : null}
{generalSettingList.find(v => v.key === activeTab) ? (
<GeneralSetting generalKey={activeTab as GeneralSettingKeys} />
@@ -85,15 +88,15 @@ export const SettingModal = ({
{activeTab === 'account' ? <AccountSetting /> : null}
</div>
<div className="footer">
<ContactWithUsIcon />
<div className={footerIconWrapper}>
<ContactWithUsIcon />
</div>
<a
href="https://community.affine.pro/home"
target="_blank"
rel="noreferrer"
>
{t[
'Need more customization options? You can suggest them to us in the community.'
]()}
{t['com.affine.settings.suggestion']()}
</a>
</div>
</div>

View File

@@ -28,16 +28,18 @@ globalStyle(`${settingContent} .footer`, {
marginTop: '-80px',
fontSize: 'var(--affine-font-sm)',
display: 'flex',
minHeight: '100px',
});
globalStyle(`${settingContent} .footer a`, {
color: 'var(--affine-text-primary-color)',
lineHeight: 'normal',
});
globalStyle(`${settingContent} .footer > svg`, {
export const footerIconWrapper = style({
fontSize: 'var(--affine-font-base)',
color: 'var(--affine-icon-color)',
marginRight: '12px',
marginTop: '1px',
height: '19px',
display: 'flex',
alignItems: 'center',
});

View File

@@ -1,7 +1,6 @@
import { WorkspaceDetailSkeleton } from '@affine/component/setting-components';
import { usePassiveWorkspaceEffect } from '@toeverything/infra/__internal__/react';
import { useSetAtom } from 'jotai';
import { Suspense, useCallback } from 'react';
import { useCallback } from 'react';
import { getUIAdapter } from '../../../../adapters/workspace';
import { openSettingModalAtom } from '../../../../atoms';
@@ -33,12 +32,10 @@ export const WorkspaceSetting = ({ workspaceId }: { workspaceId: string }) => {
const onTransformWorkspace = useOnTransformWorkspace();
return (
<Suspense fallback={<WorkspaceDetailSkeleton />}>
<NewSettingsDetail
onTransformWorkspace={onTransformWorkspace}
onDeleteWorkspace={onDeleteWorkspace}
currentWorkspaceId={workspaceId}
/>
</Suspense>
<NewSettingsDetail
onTransformWorkspace={onTransformWorkspace}
onDeleteWorkspace={onDeleteWorkspace}
currentWorkspaceId={workspaceId}
/>
);
};

View File

@@ -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';

View File

@@ -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 />;
};

View File

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

View File

@@ -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';

View File

@@ -3,6 +3,8 @@ import type { HTMLAttributes } from 'react';
import type React from 'react';
import { cloneElement, useState } from 'react';
import edgelessHover from './animation-data/edgeless-hover.json';
import pageHover from './animation-data/page-hover.json';
import { StyledSwitchItem } from './style';
type HoverAnimateControllerProps = {
@@ -52,7 +54,7 @@ export const PageSwitchItem = (
options={{
loop: false,
autoplay: false,
animationData: require('./animation-data/page-hover.json'),
animationData: pageHover,
rendererSettings: {
preserveAspectRatio: 'xMidYMid slice',
},
@@ -71,7 +73,7 @@ export const EdgelessSwitchItem = (
options={{
loop: false,
autoplay: false,
animationData: require('./animation-data/edgeless-hover.json'),
animationData: edgelessHover,
rendererSettings: {
preserveAspectRatio: 'xMidYMid slice',
},

View File

@@ -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;

View File

@@ -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)',
};
});

View File

@@ -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>
);
};

View File

@@ -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;

View File

@@ -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;
//},
`;
});

View File

@@ -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;

View File

@@ -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';

View File

@@ -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';

View File

@@ -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%',
});

View File

@@ -17,15 +17,15 @@ import { contentLayoutAtom, rootStore } from '@toeverything/infra/atom';
import clsx from 'clsx';
import { useAtomValue, useSetAtom } from 'jotai';
import type { CSSProperties, ReactElement } from 'react';
import { memo, Suspense, useCallback, useMemo } from 'react';
import { memo, startTransition, Suspense, useCallback, useMemo } from 'react';
import { Panel, PanelGroup, PanelResizeHandle } from 'react-resizable-panels';
import { 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;
@@ -76,7 +76,6 @@ const EditorWrapper = memo(function EditorWrapper({
'--affine-font-family': value,
} as CSSProperties
}
key={`${workspace.id}-${pageId}`}
mode={isPublic ? 'page' : currentMode}
page={page}
onInit={useCallback(
@@ -141,12 +140,14 @@ const PluginContentAdapter = memo<PluginContentAdapterProps>(
ref={useCallback(
(ref: HTMLDivElement | null) => {
if (ref) {
const div = document.createElement('div');
const cleanup = windowItem(div);
ref.appendChild(div);
addCleanup(pluginName, () => {
cleanup();
ref.removeChild(div);
startTransition(() => {
const div = document.createElement('div');
const cleanup = windowItem(div);
ref.appendChild(div);
addCleanup(pluginName, () => {
cleanup();
ref.removeChild(div);
});
});
}
},
@@ -182,7 +183,12 @@ const LayoutPanel = memo(function LayoutPanel(
}}
direction={node.direction}
>
<Panel defaultSize={node.splitPercentage}>
<Panel
defaultSize={node.splitPercentage}
style={{
maxWidth: node.maxWidth?.[0],
}}
>
<Suspense>
<LayoutPanel node={node.first} editorProps={props.editorProps} />
</Suspense>
@@ -192,6 +198,7 @@ const LayoutPanel = memo(function LayoutPanel(
defaultSize={100 - node.splitPercentage}
style={{
overflow: 'scroll',
maxWidth: node.maxWidth?.[1],
}}
>
<Suspense>

View File

@@ -1,11 +1,12 @@
import { useAFFiNEI18N } from '@affine/i18n/hooks';
import { CloudWorkspaceIcon } from '@blocksuite/icons';
import { Button } from '@toeverything/components/button';
import { useSetAtom } from 'jotai';
import { type CSSProperties, forwardRef } from 'react';
import { openDisableCloudAlertModalAtom } from '../../../atoms';
import { stringToColour } from '../../../utils';
import { StyledFooter, StyledSignInButton } from './styles';
import { StyledFooter } from './styles';
export const Footer = () => {
const t = useAFFiNEI18N();
@@ -13,13 +14,13 @@ export const Footer = () => {
return (
<StyledFooter data-testid="workspace-list-modal-footer">
<StyledSignInButton
<Button
data-testid="sign-in-button"
type="plain"
icon={
<div className="circle">
<CloudWorkspaceIcon />
</div>
<CloudWorkspaceIcon
style={{ color: 'var(--affine-primary-color)' }}
/>
}
onClick={async () => {
if (!runtimeConfig.enableCloud) {
@@ -28,7 +29,7 @@ export const Footer = () => {
}}
>
{t['Sign in']()}
</StyledSignInButton>
</Button>
</StyledFooter>
);
};

View File

@@ -1,10 +1,4 @@
import {
displayFlex,
displayInlineFlex,
styled,
textEllipsis,
} from '@affine/component';
import { Button } from '@toeverything/components/button';
import { displayFlex, styled, textEllipsis } from '@affine/component';
export const StyleWorkspaceInfo = styled('div')(() => {
return {
marginLeft: '15px',
@@ -116,21 +110,3 @@ export const StyledModalHeader = styled('div')(() => {
...displayFlex('space-between', 'center'),
};
});
export const StyledSignInButton = styled(Button)(() => {
return {
fontWeight: 600,
paddingLeft: 0,
'.circle': {
width: '40px',
height: '40px',
borderRadius: '20px',
backgroundColor: 'var(--affine-hover-color)',
color: 'var(--affine-primary-color)',
fontSize: '24px',
flexShrink: 0,
marginRight: '16px',
...displayInlineFlex('center', 'center'),
},
};
});

View File

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

View File

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

View File

@@ -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);
}}
/>
);
};

View File

@@ -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>
);
};

View File

@@ -57,10 +57,14 @@ export const HelpIsland = ({
inEdgelessPage={mode === 'edgeless'}
>
<StyledAnimateWrapper
style={{ height: spread ? `${showList.length * 44}px` : 0 }}
style={{ height: spread ? `${showList.length * 40 + 4}px` : 0 }}
>
{showList.includes('whatNew') && (
<Tooltip content={t["Discover what's new!"]()} placement="left-end">
<Tooltip
content={t["Discover what's new!"]()}
placement="left-end"
showArrow={true}
>
<StyledIconWrapper
data-testid="right-bottom-change-log-icon"
onClick={() => {
@@ -72,7 +76,11 @@ export const HelpIsland = ({
</Tooltip>
)}
{showList.includes('contact') && (
<Tooltip content={t['Contact Us']()} placement="left-end">
<Tooltip
content={t['Contact Us']()}
placement="left-end"
showArrow={true}
>
<StyledIconWrapper
data-testid="right-bottom-contact-us-icon"
onClick={openAbout}
@@ -82,7 +90,11 @@ export const HelpIsland = ({
</Tooltip>
)}
{showList.includes('shortcuts') && (
<Tooltip content={t['Keyboard Shortcuts']()} placement="left-end">
<Tooltip
content={t['Keyboard Shortcuts']()}
placement="left-end"
showArrow={true}
>
<StyledIconWrapper
data-testid="shortcuts-icon"
onClick={() => {
@@ -98,6 +110,7 @@ export const HelpIsland = ({
<Tooltip
content={t['com.affine.helpIsland.gettingStarted']()}
placement="left-end"
showArrow={true}
>
<StyledIconWrapper
data-testid="easy-guide"
@@ -112,7 +125,11 @@ export const HelpIsland = ({
)}
</StyledAnimateWrapper>
<Tooltip content={t['Help and Feedback']()} placement="left-end">
<Tooltip
content={t['Help and Feedback']()}
placement={'left-end'}
showArrow={true}
>
<MuiFade in={!spread} data-testid="faq-icon">
<StyledTriggerWrapper>
<HelpIcon />

View 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]
)}
/>
);
};

View File

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

View File

@@ -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);
}}

View File

@@ -1,11 +1,4 @@
import {
Menu,
MenuItem,
Modal,
ModalCloseButton,
ModalWrapper,
Tooltip,
} from '@affine/component';
import { Menu, MenuItem, Tooltip } from '@affine/component';
import { ScrollableContainer } from '@affine/component';
import { WorkspaceList } from '@affine/component/workspace-list';
import type {
@@ -15,28 +8,38 @@ import type {
import { WorkspaceFlavour } from '@affine/env/workspace';
import { useAFFiNEI18N } from '@affine/i18n/hooks';
import type { RootWorkspaceMetadata } from '@affine/workspace/atom';
import { HelpIcon, ImportIcon, PlusIcon } from '@blocksuite/icons';
import type { DragEndEvent } from '@dnd-kit/core';
import { useCallback, useRef } from 'react';
import type { AllWorkspace } from '../../../shared';
import { Footer } from '../footer';
import {
StyledCreateWorkspaceCard,
CloudWorkspaceIcon,
HelpIcon,
ImportIcon,
MoreHorizontalIcon,
PlusIcon,
} from '@blocksuite/icons';
import type { DragEndEvent } from '@dnd-kit/core';
import { Popover } from '@mui/material';
import { IconButton } from '@toeverything/components/button';
import { Divider } from '@toeverything/components/divider';
import { useSetAtom } from 'jotai';
import { useCallback } from 'react';
import { openDisableCloudAlertModalAtom } from '../../../atoms';
import type { AllWorkspace } from '../../../shared';
import {
StyledCreateWorkspaceCardPill,
StyledCreateWorkspaceCardPillContainer,
StyledCreateWorkspaceCardPillContent,
StyledCreateWorkspaceCardPillIcon,
StyledCreateWorkspaceCardPillTextSecondary,
StyledHelperContainer,
StyledImportWorkspaceCardPill,
StyledModalBody,
StyledModalContent,
StyledModalHeader,
StyledModalHeaderLeft,
StyledModalTitle,
StyledOperationWrapper,
StyleWorkspaceAdd,
StyleWorkspaceInfo,
StyleWorkspaceTitle,
StyledSignInCardPill,
StyledSignInCardPillTextCotainer,
StyledSignInCardPillTextPrimary,
StyledSignInCardPillTextSecondary,
} from './styles';
interface WorkspaceModalProps {
@@ -52,105 +55,94 @@ interface WorkspaceModalProps {
onMoveWorkspace: (activeId: string, overId: string) => void;
}
const CreateWorkspaceCard = ({
onNewWorkspace,
onAddWorkspace,
}: {
onNewWorkspace: () => void;
onAddWorkspace: () => void;
}) => {
const AccountMenu = () => {
const t = useAFFiNEI18N();
const anchorEL = useRef<HTMLDivElement>(null);
return (
<div>
<div>Unlimted</div>
<Divider></Divider>
<MenuItem icon={<ImportIcon />} data-testid="editor-option-menu-import">
{t['com.affine.workspace.cloud.join']()}
</MenuItem>
<MenuItem icon={<ImportIcon />} data-testid="editor-option-menu-import">
{t['com.affine.workspace.cloud.account.settings']()}
</MenuItem>
<Divider></Divider>
<MenuItem icon={<ImportIcon />} data-testid="editor-option-menu-import">
{t['com.affine.workspace.cloud.account.logout']()}
</MenuItem>
</div>
);
};
if (runtimeConfig.enableSQLiteProvider && environment.isDesktop) {
return (
<Menu
placement="auto"
trigger={['click']}
zIndex={1000}
content={
<StyledCreateWorkspaceCardPillContainer>
<StyledCreateWorkspaceCardPill>
<MenuItem
style={{
height: 'auto',
padding: '8px 12px',
}}
onClick={onNewWorkspace}
data-testid="new-workspace"
>
<StyledCreateWorkspaceCardPillContent>
<div>
<p>{t['New Workspace']()}</p>
<StyledCreateWorkspaceCardPillTextSecondary>
<p>{t['Create your own workspace']()}</p>
</StyledCreateWorkspaceCardPillTextSecondary>
</div>
<StyledCreateWorkspaceCardPillIcon>
<PlusIcon />
</StyledCreateWorkspaceCardPillIcon>
</StyledCreateWorkspaceCardPillContent>
</MenuItem>
</StyledCreateWorkspaceCardPill>
<StyledCreateWorkspaceCardPill>
<MenuItem
onClick={onAddWorkspace}
data-testid="add-workspace"
style={{
height: 'auto',
padding: '8px 12px',
}}
>
<StyledCreateWorkspaceCardPillContent>
<div>
<p>{t['Add Workspace']()}</p>
<StyledCreateWorkspaceCardPillTextSecondary>
<p>{t['Add Workspace Hint']()}</p>
</StyledCreateWorkspaceCardPillTextSecondary>
</div>
<StyledCreateWorkspaceCardPillIcon>
<ImportIcon />
</StyledCreateWorkspaceCardPillIcon>
</StyledCreateWorkspaceCardPillContent>
</MenuItem>
</StyledCreateWorkspaceCardPill>
</StyledCreateWorkspaceCardPillContainer>
}
>
<StyledCreateWorkspaceCard
ref={anchorEL}
data-testid="add-or-new-workspace"
>
<StyleWorkspaceAdd className="add-icon">
<PlusIcon />
</StyleWorkspaceAdd>
const CloudWorkSpaceList = ({
disabled,
workspaces,
onClickWorkspace,
onClickWorkspaceSetting,
currentWorkspaceId,
onMoveWorkspace,
}: WorkspaceModalProps) => {
const t = useAFFiNEI18N();
<StyleWorkspaceInfo>
<StyleWorkspaceTitle>{t['New Workspace']()}</StyleWorkspaceTitle>
<p>{t['Create Or Import']()}</p>
</StyleWorkspaceInfo>
</StyledCreateWorkspaceCard>
</Menu>
);
} else {
return (
<div>
<StyledCreateWorkspaceCard
onClick={onNewWorkspace}
data-testid="new-workspace"
>
<StyleWorkspaceAdd className="add-icon">
<PlusIcon />
</StyleWorkspaceAdd>
return (
<>
<StyledModalHeader>
<StyledModalHeaderLeft>
<StyledModalTitle>
{t['com.affine.workspace.cloud.sync']()}
</StyledModalTitle>
<Tooltip
content={t['Workspace description']()}
placement="top-start"
disablePortal={true}
>
<StyledHelperContainer>
<HelpIcon />
</StyledHelperContainer>
</Tooltip>
</StyledModalHeaderLeft>
<StyleWorkspaceInfo>
<StyleWorkspaceTitle>{t['New Workspace']()}</StyleWorkspaceTitle>
<p>{t['Create Or Import']()}</p>
</StyleWorkspaceInfo>
</StyledCreateWorkspaceCard>
</div>
);
}
<StyledOperationWrapper>
<Menu
placement="bottom-end"
trigger={['click']}
content={<AccountMenu />}
zIndex={1000}
>
<IconButton
data-testid="previous-image-button"
icon={<MoreHorizontalIcon />}
type="plain"
/>
</Menu>
</StyledOperationWrapper>
</StyledModalHeader>
<StyledModalContent>
<WorkspaceList
disabled={disabled}
items={
workspaces.filter(
({ flavour }) => flavour !== WorkspaceFlavour.PUBLIC
) as (AffineCloudWorkspace | LocalWorkspace)[]
}
currentWorkspaceId={currentWorkspaceId}
onClick={onClickWorkspace}
onSettingClick={onClickWorkspaceSetting}
onDragEnd={useCallback(
(event: DragEndEvent) => {
const { active, over } = event;
if (active.id !== over?.id) {
onMoveWorkspace(active.id as string, over?.id as string);
}
},
[onMoveWorkspace]
)}
/>
<Divider />
</StyledModalContent>
</>
);
};
export const WorkspaceListModal = ({
@@ -166,19 +158,77 @@ export const WorkspaceListModal = ({
onMoveWorkspace,
}: WorkspaceModalProps) => {
const t = useAFFiNEI18N();
const setOpen = useSetAtom(openDisableCloudAlertModalAtom);
// TODO: AFFiNE Cloud support
const isLoggedIn = false;
const anchorEl = document.getElementById('current-workspace');
return (
<Modal open={open} onClose={onClose}>
<ModalWrapper
width={720}
height={690}
style={{
display: 'flex',
flexDirection: 'column',
}}
>
<StyledModalHeader>
<StyledModalHeaderLeft>
<StyledModalTitle>{t['My Workspaces']()}</StyledModalTitle>
<Popover
sx={{
color: 'success.main',
zIndex: 999,
'& .MuiPopover-paper': {
borderRadius: '8px',
boxShadow: 'var(--affine-shadow-1)',
},
}}
open={open}
anchorEl={anchorEl}
onClose={onClose}
>
<StyledModalBody>
<ScrollableContainer>
{isLoggedIn ? (
<CloudWorkSpaceList
disabled={disabled}
open={open}
onClose={onClose}
workspaces={workspaces}
onClickWorkspace={onClickWorkspace}
onClickWorkspaceSetting={onClickWorkspaceSetting}
onNewWorkspace={onNewWorkspace}
onAddWorkspace={onAddWorkspace}
currentWorkspaceId={currentWorkspaceId}
onMoveWorkspace={onMoveWorkspace}
/>
) : (
<>
<StyledModalContent>
<StyledSignInCardPill>
<MenuItem
style={{
height: 'auto',
padding: '8px 12px',
}}
onClick={async () => {
if (!runtimeConfig.enableCloud) {
setOpen(true);
}
}}
data-testid="cloud-signin-button"
>
<StyledCreateWorkspaceCardPillContent>
<StyledCreateWorkspaceCardPillIcon>
<CloudWorkspaceIcon />
</StyledCreateWorkspaceCardPillIcon>
<StyledSignInCardPillTextCotainer>
<StyledSignInCardPillTextPrimary>
{t['com.affine.workspace.cloud.auth']()}
</StyledSignInCardPillTextPrimary>
<StyledSignInCardPillTextSecondary>
Sync with AFFiNE Cloud
</StyledSignInCardPillTextSecondary>
</StyledSignInCardPillTextCotainer>
</StyledCreateWorkspaceCardPillContent>
</MenuItem>
</StyledSignInCardPill>
<Divider />
</StyledModalContent>
</>
)}
<StyledModalHeader>
<StyledModalTitle>{t['Local Workspace']()}</StyledModalTitle>
<Tooltip
content={t['Workspace description']()}
placement="top-start"
@@ -188,19 +238,7 @@ export const WorkspaceListModal = ({
<HelpIcon />
</StyledHelperContainer>
</Tooltip>
</StyledModalHeaderLeft>
<StyledOperationWrapper>
<ModalCloseButton
data-testid="close-workspace-modal"
onClick={() => {
onClose();
}}
absolute={false}
/>
</StyledOperationWrapper>
</StyledModalHeader>
<ScrollableContainer>
</StyledModalHeader>
<StyledModalContent>
<WorkspaceList
disabled={disabled}
@@ -222,14 +260,51 @@ export const WorkspaceListModal = ({
[onMoveWorkspace]
)}
/>
<CreateWorkspaceCard
onNewWorkspace={onNewWorkspace}
onAddWorkspace={onAddWorkspace}
/>
</StyledModalContent>
<StyledModalContent>
{runtimeConfig.enableSQLiteProvider && environment.isDesktop ? (
<StyledImportWorkspaceCardPill>
<MenuItem
onClick={onAddWorkspace}
data-testid="add-workspace"
style={{
height: 'auto',
padding: '8px 12px',
}}
>
<StyledCreateWorkspaceCardPillContent>
<StyledCreateWorkspaceCardPillIcon>
<ImportIcon />
</StyledCreateWorkspaceCardPillIcon>
<div>
<p>{t['com.affine.workspace.local.import']()}</p>
</div>
</StyledCreateWorkspaceCardPillContent>
</MenuItem>
</StyledImportWorkspaceCardPill>
) : null}
<StyledCreateWorkspaceCardPill>
<MenuItem
style={{
height: 'auto',
padding: '8px 12px',
}}
onClick={onNewWorkspace}
data-testid="new-workspace"
>
<StyledCreateWorkspaceCardPillContent>
<StyledCreateWorkspaceCardPillIcon>
<PlusIcon />
</StyledCreateWorkspaceCardPillIcon>
<div>
<p>{t['New Workspace']()}</p>
</div>
</StyledCreateWorkspaceCardPillContent>
</MenuItem>
</StyledCreateWorkspaceCardPill>
</StyledModalContent>
</ScrollableContainer>
<Footer />
</ModalWrapper>
</Modal>
</StyledModalBody>
</Popover>
);
};

View File

@@ -81,11 +81,33 @@ export const StyledCreateWorkspaceCardPillContainer = styled('div')(() => {
});
export const StyledCreateWorkspaceCardPill = styled('div')(() => {
return {
borderRadius: '8px',
display: 'flex',
width: '100%',
height: '58px',
background: 'var(--affine-background-primary-color)',
border: `1px solid var(--affine-border-color)`,
};
});
export const StyledSignInCardPill = styled('div')(() => {
return {
borderRadius: '8px',
display: 'flex',
width: '100%',
height: '58px',
marginBottom: '6px',
background: 'var(--affine-background-primary-color)',
};
});
export const StyledImportWorkspaceCardPill = styled('div')(() => {
return {
borderRadius: '5px',
display: 'flex',
boxShadow: '0px 0px 6px 0px rgba(0, 0, 0, 0.1)',
background: 'var(--affine-background-primary-color)',
width: '100%',
marginBottom: '12px',
};
});
@@ -94,7 +116,6 @@ export const StyledCreateWorkspaceCardPillContent = styled('div')(() => {
display: 'flex',
gap: '12px',
alignItems: 'center',
justifyContent: 'space-between',
};
});
@@ -106,13 +127,30 @@ export const StyledCreateWorkspaceCardPillIcon = styled('div')(() => {
};
});
export const StyledCreateWorkspaceCardPillTextSecondary = styled('div')(() => {
export const StyledSignInCardPillTextCotainer = styled('div')(() => {
return {
display: 'flex',
flexDirection: 'column',
};
});
export const StyledSignInCardPillTextSecondary = styled('div')(() => {
return {
fontSize: '12px',
color: 'var(--affine-text-secondary-color)',
};
});
export const StyledSignInCardPillTextPrimary = styled('div')(() => {
return {
fontSize: 'var(--affine-font-base)',
fontWeight: 600,
lineHeight: '24px',
maxWidth: '200px',
...textEllipsis(1),
};
});
export const StyledModalHeaderLeft = styled('div')(() => {
return { ...displayFlex('flex-start', 'center') };
});
@@ -120,6 +158,7 @@ export const StyledModalTitle = styled('div')(() => {
return {
fontWeight: 600,
fontSize: 'var(--affine-font-h6)',
color: 'var(--affine-text-primary-color)',
};
});
@@ -134,10 +173,9 @@ export const StyledHelperContainer = styled('div')(() => {
});
export const StyledModalContent = styled('div')({
height: '540px',
padding: '8px 40px',
...displayFlex('space-between', 'flex-start', 'flex-start'),
flexWrap: 'wrap',
flexDirection: 'column',
});
export const StyledOperationWrapper = styled('div')(() => {
return {
@@ -162,11 +200,22 @@ export const StyleWorkspaceAdd = styled('div')(() => {
export const StyledModalHeader = styled('div')(() => {
return {
width: '100%',
marginTop: '10px',
left: 0,
top: 0,
borderRadius: '24px 24px 0 0',
padding: '10px 40px',
padding: '12px 14px',
...displayFlex('space-between', 'center'),
};
});
export const StyledModalBody = styled('div')(() => {
return {
borderRadius: '8px',
padding: '12px',
display: 'inline-flex',
flexDirection: 'column',
alignItems: 'flex-start',
gap: '12px',
background: 'var(--affine-white)',
};
});

View File

@@ -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>
);
};

View File

@@ -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,12 +27,12 @@ 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
// TODO: Delete this?
const handleKeyDown = useCallback(
(e: React.KeyboardEvent) => {
if (e.key === 'Enter' || e.key === ' ') {
@@ -52,27 +51,26 @@ export const WorkspaceSelector = ({
onClick={onClick}
onKeyDown={handleKeyDown}
data-testid="current-workspace"
id="current-workspace"
>
<WorkspaceAvatar
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>
);

View File

@@ -257,6 +257,7 @@ export const CollectionsList = ({ workspace }: CollectionsListProps) => {
() => savedCollections.filter(v => v.pinned),
[savedCollections]
);
const t = useAFFiNEI18N();
if (pinedCollections.length === 0) {
return (
<MenuItem
@@ -264,7 +265,7 @@ export const CollectionsList = ({ workspace }: CollectionsListProps) => {
icon={<InformationIcon />}
disabled
>
<span>Create a collection</span>
<span>{t['Create a collection']()}</span>
</MenuItem>
);
}

View File

@@ -29,14 +29,8 @@ export const ReferencePage = ({
const icon = setting?.mode === 'edgeless' ? <EdgelessIcon /> : <PageIcon />;
const references = useBlockSuitePageReferences(workspace, pageId);
const referencesToShow = useMemo(() => {
return [
...new Set(
references.filter(
ref => !parentIds.has(ref) && !metaMapping[ref]?.trash
)
),
];
}, [references, parentIds, metaMapping]);
return [...new Set(references.filter(ref => !metaMapping[ref]?.trash))];
}, [references, metaMapping]);
const [collapsed, setCollapsed] = useState(true);
const collapsible = referencesToShow.length > 0;
const nestedItem = parentIds.size > 0;

View File

@@ -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>
);
};

View File

@@ -115,7 +115,7 @@ export const RootAppSidebar = ({
const [sidebarOpen, setSidebarOpen] = useAtom(appSidebarOpenAtom);
useEffect(() => {
if (isDesktop && typeof sidebarOpen === 'boolean') {
if (isDesktop) {
window.apis?.ui.handleSidebarVisibilityChange(sidebarOpen).catch(err => {
console.error(err);
});

View File

@@ -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;
}

View File

@@ -1,9 +1,4 @@
import type { WorkspaceSubPath } from '@affine/env/workspace';
import {
currentPageIdAtom,
currentWorkspaceIdAtom,
} from '@toeverything/infra/atom';
import { useSetAtom } from 'jotai';
import { useCallback } from 'react';
// eslint-disable-next-line @typescript-eslint/no-restricted-imports
import { useLocation, useNavigate } from 'react-router-dom';
@@ -16,8 +11,6 @@ export enum RouteLogic {
export function useNavigateHelper() {
const location = useLocation();
const navigate = useNavigate();
const setWorkspaceId = useSetAtom(currentWorkspaceIdAtom);
const setCurrentPageId = useSetAtom(currentPageIdAtom);
const jumpToPage = useCallback(
(
@@ -25,13 +18,11 @@ export function useNavigateHelper() {
pageId: string,
logic: RouteLogic = RouteLogic.PUSH
) => {
setWorkspaceId(workspaceId);
setCurrentPageId(pageId);
return navigate(`/workspace/${workspaceId}/${pageId}`, {
replace: logic === RouteLogic.REPLACE,
});
},
[navigate, setCurrentPageId, setWorkspaceId]
[navigate]
);
const jumpToPublicWorkspacePage = useCallback(
(
@@ -39,13 +30,11 @@ export function useNavigateHelper() {
pageId: string,
logic: RouteLogic = RouteLogic.PUSH
) => {
setWorkspaceId(workspaceId);
setCurrentPageId(pageId);
return navigate(`/public-workspace/${workspaceId}/${pageId}`, {
replace: logic === RouteLogic.REPLACE,
});
},
[navigate, setCurrentPageId, setWorkspaceId]
[navigate]
);
const jumpToSubPath = useCallback(
(
@@ -53,18 +42,14 @@ export function useNavigateHelper() {
subPath: WorkspaceSubPath,
logic: RouteLogic = RouteLogic.PUSH
) => {
setWorkspaceId(workspaceId);
setCurrentPageId(null);
return navigate(`/workspace/${workspaceId}/${subPath}`, {
replace: logic === RouteLogic.REPLACE,
});
},
[navigate, setCurrentPageId, setWorkspaceId]
[navigate]
);
const openPage = useCallback(
(workspaceId: string, pageId: string) => {
setWorkspaceId(workspaceId);
setCurrentPageId(pageId);
const isPublicWorkspace =
location.pathname.indexOf('/public-workspace') === 0;
if (isPublicWorkspace) {
@@ -73,35 +58,25 @@ export function useNavigateHelper() {
return jumpToPage(workspaceId, pageId);
}
},
[
jumpToPage,
jumpToPublicWorkspacePage,
location.pathname,
setCurrentPageId,
setWorkspaceId,
]
[jumpToPage, jumpToPublicWorkspacePage, location.pathname]
);
const jumpToIndex = useCallback(
(logic: RouteLogic = RouteLogic.PUSH) => {
setWorkspaceId(null);
setCurrentPageId(null);
return navigate('/', {
replace: logic === RouteLogic.REPLACE,
});
},
[navigate, setCurrentPageId, setWorkspaceId]
[navigate]
);
const jumpTo404 = useCallback(
(logic: RouteLogic = RouteLogic.PUSH) => {
setWorkspaceId(null);
setCurrentPageId(null);
return navigate('/404', {
replace: logic === RouteLogic.REPLACE,
});
},
[navigate, setCurrentPageId, setWorkspaceId]
[navigate]
);
return {

View File

@@ -1,18 +1,18 @@
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 { rootStore } from '@toeverything/infra/atom';
import { buildShowcaseWorkspace } from '@toeverything/infra/blocksuite';
import { useAtomValue, useSetAtom } from 'jotai';
import { useCallback } from 'react';
import { LocalAdapter } from '../adapters/local';
import { WorkspaceAdapters } from '../adapters/workspace';
import { setPageModeAtom } from '../atoms';
const logger = new DebugLogger('use-workspaces');
@@ -54,22 +54,11 @@ 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, {
store: rootStore,
atoms: {
pageMode: setPageModeAtom,
},
});
}
set(workspaces => [

View File

@@ -89,15 +89,6 @@ export const QuickSearch = () => {
);
};
declare global {
// eslint-disable-next-line no-var
var HALTING_PROBLEM_TIMEOUT: number;
}
if (globalThis.HALTING_PROBLEM_TIMEOUT === undefined) {
globalThis.HALTING_PROBLEM_TIMEOUT = 1000;
}
const showList: IslandItemNames[] = environment.isDesktop
? ['whatNew', 'contact', 'guide']
: ['whatNew', 'contact'];

View File

@@ -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 {

View File

@@ -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 (
<>

View File

@@ -7,12 +7,16 @@ import { WorkspaceSubPath } from '@affine/env/workspace';
import type { EditorContainer } from '@blocksuite/editor';
import { assertExists } from '@blocksuite/global/utils';
import type { Page } from '@blocksuite/store';
import { currentPageIdAtom, rootStore } from '@toeverything/infra/atom';
import {
currentPageIdAtom,
currentWorkspaceAtom,
currentWorkspaceIdAtom,
rootStore,
} from '@toeverything/infra/atom';
import { useAtomValue } from 'jotai';
import { useAtom } from 'jotai/react';
import { type ReactElement, useCallback, useEffect } from 'react';
import { type ReactElement, useCallback } from 'react';
import type { LoaderFunction } from 'react-router-dom';
import { useLocation, useParams } from 'react-router-dom';
import { redirect } from 'react-router-dom';
import { getUIAdapter } from '../../adapters/workspace';
import { useCurrentWorkspace } from '../../hooks/current/use-current-workspace';
@@ -32,7 +36,7 @@ const DetailPageImpl = (): ReactElement => {
return openPage(blockSuiteWorkspace.id, pageId);
});
const disposeTagClick = editor.slots.tagClicked.on(async ({ tagId }) => {
await jumpToSubPath(currentWorkspace.id, WorkspaceSubPath.ALL);
jumpToSubPath(currentWorkspace.id, WorkspaceSubPath.ALL);
collectionManager.backToAll();
collectionManager.setTemporaryFilter([createTagFilter(tagId)]);
});
@@ -69,61 +73,39 @@ const DetailPageImpl = (): ReactElement => {
};
export const DetailPage = (): ReactElement => {
const { workspaceId, pageId } = useParams();
const location = useLocation();
const { jumpTo404 } = useNavigateHelper();
const [currentWorkspace] = useCurrentWorkspace();
const [currentPageId, setCurrentPageId] = useAtom(currentPageIdAtom);
const currentPageId = useAtomValue(currentPageIdAtom);
const page = currentPageId
? currentWorkspace.blockSuiteWorkspace.getPage(currentPageId)
: null;
//#region check if page is valid
useEffect(() => {
// if the workspace changed, ignore the page check
if (currentWorkspace.id !== workspaceId) {
return;
}
if (typeof pageId === 'string' && currentPageId) {
if (currentPageId !== pageId) {
setCurrentPageId(pageId);
} else {
const page =
currentWorkspace.blockSuiteWorkspace.getPage(currentPageId);
if (!page) {
jumpTo404();
} else {
// fixme: cleanup jumpOnce in the right time
if (page.meta.jumpOnce) {
currentWorkspace.blockSuiteWorkspace.setPageMeta(currentPageId, {
jumpOnce: false,
});
}
}
}
}
}, [
currentPageId,
currentWorkspace.blockSuiteWorkspace,
currentWorkspace.id,
jumpTo404,
location.pathname,
pageId,
setCurrentPageId,
workspaceId,
]);
//#endregion
if (!currentPageId || !page) {
return <PageDetailSkeleton key="current-page-is-null" />;
}
return <DetailPageImpl />;
};
export const loader: LoaderFunction = args => {
export const loader: LoaderFunction = async args => {
if (args.params.workspaceId) {
localStorage.setItem('last_workspace_id', args.params.workspaceId);
rootStore.set(currentWorkspaceIdAtom, args.params.workspaceId);
}
if (args.params.pageId) {
localStorage.setItem('last_page_id', args.params.pageId);
rootStore.set(currentPageIdAtom, args.params.pageId);
const pageId = args.params.pageId;
localStorage.setItem('last_page_id', pageId);
const currentWorkspace = await rootStore.get(currentWorkspaceAtom);
const page = currentWorkspace.getPage(pageId);
if (!page) {
return redirect('/404');
}
if (page.meta.jumpOnce) {
currentWorkspace.setPageMeta(page.id, {
jumpOnce: false,
});
}
rootStore.set(currentPageIdAtom, pageId);
} else {
return redirect('/404');
}
return null;
};

View File

@@ -1,5 +1,9 @@
import { rootWorkspacesMetadataAtom } from '@affine/workspace/atom';
import { currentWorkspaceIdAtom, rootStore } from '@toeverything/infra/atom';
import {
currentPageIdAtom,
currentWorkspaceIdAtom,
rootStore,
} from '@toeverything/infra/atom';
import type { ReactElement } from 'react';
import { type LoaderFunction, Outlet, redirect } from 'react-router-dom';
@@ -14,6 +18,9 @@ export const loader: LoaderFunction = async args => {
localStorage.setItem('last_workspace_id', args.params.workspaceId);
rootStore.set(currentWorkspaceIdAtom, args.params.workspaceId);
}
if (!args.params.pageId) {
rootStore.set(currentPageIdAtom, null);
}
return null;
};

View 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 {};

View File

@@ -126,6 +126,7 @@ export const AllWorkspaceModals = (): ReactElement => {
);
const setCurrentPageId = useSetAtom(currentPageIdAtom);
const [isPending, startTransition] = useTransition();
const [, startCloseTransition] = useTransition();
const [, setOpenSettingModalAtom] = useAtom(openSettingModalAtom);
const handleOpenSettingModal = useCallback(
@@ -153,7 +154,9 @@ export const AllWorkspaceModals = (): ReactElement => {
isOpenCreateWorkspaceModal === false
}
onClose={useCallback(() => {
setOpenWorkspacesModal(false);
startCloseTransition(() => {
setOpenWorkspacesModal(false);
});
}, [setOpenWorkspacesModal])}
onMoveWorkspace={useCallback(
(activeId, overId) => {
@@ -169,10 +172,12 @@ export const AllWorkspaceModals = (): ReactElement => {
)}
onClickWorkspace={useCallback(
workspaceId => {
setOpenWorkspacesModal(false);
setCurrentWorkspaceId(workspaceId);
setCurrentPageId(null);
jumpToSubPath(workspaceId, WorkspaceSubPath.ALL);
startCloseTransition(() => {
setOpenWorkspacesModal(false);
setCurrentWorkspaceId(workspaceId);
setCurrentPageId(null);
jumpToSubPath(workspaceId, WorkspaceSubPath.ALL);
});
},
[
jumpToSubPath,

View File

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

View File

@@ -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>
);
};

View File

@@ -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(

View File

@@ -5,7 +5,7 @@
"typeRoots": ["../../node_modules", "../../node_modules/@types"],
"types": ["webpack-env", "ses", "affine__env"]
},
"include": ["src/**/*.ts", "src/**/*.tsx"],
"include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.json"],
"exclude": ["node_modules"],
"references": [
{

View File

@@ -1,6 +1,6 @@
{
"name": "@affine/docs",
"version": "0.8.0-canary.14",
"version": "0.8.0-canary.24",
"type": "module",
"private": true,
"scripts": {
@@ -10,25 +10,25 @@
},
"dependencies": {
"@affine/component": "workspace:*",
"@blocksuite/block-std": "0.0.0-20230808131052-ef3445a9-nightly",
"@blocksuite/blocks": "0.0.0-20230808131052-ef3445a9-nightly",
"@blocksuite/editor": "0.0.0-20230808131052-ef3445a9-nightly",
"@blocksuite/global": "0.0.0-20230808131052-ef3445a9-nightly",
"@blocksuite/lit": "0.0.0-20230808131052-ef3445a9-nightly",
"@blocksuite/store": "0.0.0-20230808131052-ef3445a9-nightly",
"@blocksuite/block-std": "0.0.0-20230816084758-3be96d22-nightly",
"@blocksuite/blocks": "0.0.0-20230816084758-3be96d22-nightly",
"@blocksuite/editor": "0.0.0-20230816084758-3be96d22-nightly",
"@blocksuite/global": "0.0.0-20230816084758-3be96d22-nightly",
"@blocksuite/lit": "0.0.0-20230816084758-3be96d22-nightly",
"@blocksuite/store": "0.0.0-20230816084758-3be96d22-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",
"autoprefixer": "^10.4.14",
"autoprefixer": "^10.4.15",
"tailwindcss": "^3.3.3",
"typescript": "^5.1.6"
}

View File

@@ -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();

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