Compare commits

..

98 Commits

Author SHA1 Message Date
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
Alex Yang
4d6e28c725 v0.8.0-canary.14 2023-08-08 17:01:22 -04:00
Alex Yang
881424d03a chore: bump version (#3623) 2023-08-08 16:57:22 -04:00
JimmFly
5842619206 refactor: optimize the use of notification center (#3621) 2023-08-08 19:00:44 +00:00
Prajna
73272e266b fix: collection-list pin icon not changed (#3625) 2023-08-08 17:54:20 +00:00
JimmFly
4e84b9a121 refactor: header options menu (#3615) 2023-08-08 17:14:24 +00:00
Camol
7d16a8348c chore: add @types/addfine__env to the devDep (#3616) 2023-08-08 15:46:23 +00:00
LIUCHAO
4d1692db7b docs: fix typo (#3618) 2023-08-08 03:25:56 -04:00
Garfield Lee
3009d729fa refactor: remove React.FC for affine core (#3617) 2023-08-08 06:54:31 +00:00
dependabot[bot]
ceeabfeb00 chore: bump eslint and @types/eslint (#3611)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-08-08 05:35:10 +00:00
dependabot[bot]
bfa5180c1a chore: bump marked from 5.1.2 to 7.0.1 (#3613)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-08-08 05:12:52 +00:00
Qi
6efe29f7ef feat: replace button from @toeverything/components (#3608) 2023-08-08 04:38:02 +00:00
fourdim
7826ecfa58 fix: cache key on blocksuite local debug (#3610) 2023-08-08 04:37:49 +00:00
dependabot[bot]
d615ac23ea chore: bump jest-mock from 29.6.1 to 29.6.2 (#3612)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-08-08 04:36:48 +00:00
Alex Yang
4a0089dbfc v0.8.0-canary.13 2023-08-07 23:02:09 -04:00
Alex Yang
d101db2a39 chore: bump version (#3606) 2023-08-07 23:01:47 -04:00
Alex Yang
b147624f1c feat: support enable/disable plugin (#3605) 2023-08-08 00:58:31 +00:00
fourdim
ec05bd3f53 feat: add local blocksuite debug support (#3591)
Co-authored-by: Alex Yang <himself65@outlook.com>
2023-08-07 17:44:31 +00:00
dependabot[bot]
952283fe16 chore: bump eslint-plugin-sonarjs from 0.19.0 to 0.20.0 (#3597)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-08-07 17:32:48 +00:00
Garfield Lee
47c0b1f680 fix: correct contact with us icon alignment (#3600) 2023-08-07 17:30:48 +00:00
dependabot[bot]
cdef5f5826 chore: bump prisma from 5.0.0 to 5.1.1 (#3596)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-08-07 17:11:00 +00:00
Alex Yang
6d01495bc7 feat(infra): lazy load plugin modules (#3604) 2023-08-07 17:06:44 +00:00
JimmFly
d9f323874d style: adjust page list header style (#3599) 2023-08-07 16:03:54 +00:00
Peng Xiao
45ffad45b0 fix: eslint stack overflow issue (#3601) 2023-08-07 07:00:57 +00:00
dependabot[bot]
dd4612bcef chore: bump vite-plugin-dts from 3.3.1 to 3.5.1 (#3598)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-08-07 04:20:55 +00:00
Alex Yang
a0ce75b1ae v0.8.0-canary.12 2023-08-06 20:55:41 -04:00
Alex Yang
031eb35b86 chore: bump version (#3594) 2023-08-06 20:54:28 -04:00
Pratik Kumar
5bc923d58e feat(cli): build infra and plugins before dev-core (#3470) 2023-08-06 17:02:30 +00:00
Alex Yang
a74a549b8c docs: update README.md 2023-08-06 12:47:24 -04:00
Alex Yang
50df137c7f docs: update README.md 2023-08-06 01:10:56 -04:00
Alex Yang
7bf77b566d feat(plugin): add vue example (#3592) 2023-08-05 23:59:14 -04:00
Zeb Wu
48350d7654 fix: disable button when value is empty (#3590)
Co-authored-by: Alex Yang <himself65@outlook.com>
2023-08-05 11:27:14 +00:00
dependabot[bot]
72c4aa0078 chore: bump @nestjs/common from 10.1.2 to 10.1.3 (#3586)
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-05 04:55:01 +00:00
dependabot[bot]
c3c40225d6 chore: bump @typescript-eslint/parser from 6.2.0 to 6.2.1 (#3589)
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-05 04:50:05 +00:00
dependabot[bot]
b4acbb90b7 chore: bump electron from 25.3.2 to 25.4.0 (#3585)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-08-05 03:20:08 +00:00
dependabot[bot]
d1b2018700 chore: bump @perfsee/sdk from 1.8.3 to 1.8.5 (#3582)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-08-05 01:43:21 +00:00
dependabot[bot]
d087bad9de chore: bump happy-dom from 10.5.2 to 10.8.0 (#3584)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-08-05 01:19:25 +00:00
dependabot[bot]
2da6c4a0ec chore: bump jotai-devtools from 0.6.0 to 0.6.1 (#3583)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-08-05 01:18:45 +00:00
Alex Yang
ede6637a14 ci: fix dependabot.yml 2023-08-04 17:54:02 -07:00
Alex Yang
36bd0b02d0 v0.8.0-canary.11 2023-08-04 17:34:31 -07:00
JimmFly
3a92c4f798 feat: modify sidebar floating logic and header responsive style (#3550) 2023-08-05 00:15:17 +00:00
Alex Yang
97de0ef5b4 build: use tsconfig bundler (#3581) 2023-08-05 00:00:36 +00:00
Alex Yang
bbf5f0efe0 chore: bump version (#3567) 2023-08-04 23:55:28 +00:00
Alex Yang
ea76936508 feat: update 404 page (#3580)
Co-authored-by: QiShaoXuan <qishaoxuan777@gmail.com>
2023-08-04 23:11:30 +00:00
Alex Yang
f076cb0ead docs: fix all-contributors count 2023-08-04 13:48:43 -07:00
fossabot
0f230253a8 docs: add license scan report and status (#3576)
Co-authored-by: Alex Yang <himself65@outlook.com>
2023-08-04 13:44:15 -07:00
Alex Yang
13d2dde501 fix: only run migration in local workspace (#3570) 2023-08-04 16:09:55 +00:00
Garfield Lee
65fc0ed59c refactor: remove React.FC for component package (#3575) 2023-08-04 15:00:28 +00:00
Chi Zhang
7ec4b8fb8c Update README.md (#3578) 2023-08-04 14:46:10 +00:00
Peng Xiao
6415f0093b fix: optimize types for infra/electron (#3574) 2023-08-04 09:23:56 +00:00
LongYinan
5795020403 style: add no-misused-promises rule (#3547)
Co-authored-by: Peng Xiao <pengxiao@outlook.com>
2023-08-04 08:08:10 +00:00
Alex Yang
f8e49ee3be v0.8.0-canary.10 2023-08-03 20:08:52 -07:00
Pratik Kumar
1012807c65 fix: added scrollbar at the correct position (#3506)
Co-authored-by: JimmFly <yangjinfei001@gmail.com>
2023-08-04 02:50:22 +00:00
Alex Yang
1c7c27712e ci: update cancel.yml 2023-08-03 19:11:32 -07:00
Alex Yang
7f28c78d8c ci: skip build in the non-darwin platform 2023-08-03 18:12:00 -07:00
Alex Yang
4bb874756d refactor: lazy download macos maker (#3564) 2023-08-03 17:58:42 -07:00
358 changed files with 21612 additions and 14032 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"
]
}
]
}

View File

@@ -25,7 +25,8 @@
"storage",
"infra",
"plugin-cli",
"sdk"
"sdk",
"plugin"
]
]
}

View File

@@ -12,3 +12,5 @@ e2e-dist-*
static
web-static
public
packages/sdk/src/*.d.ts
packages/sdk/src/*.js

View File

@@ -22,7 +22,7 @@ const createPattern = packageName => [
allowTypeImports: false,
},
{
group: ['@blocksuite /store'],
group: ['@blocksuite/store'],
message: "Import from '@blocksuite/global/utils'",
importNames: ['assertExists', 'assertEquals'],
},
@@ -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'],
},
],
},
],
@@ -214,6 +224,7 @@ const config = {
ignoreIIFE: false,
},
],
'@typescript-eslint/no-misused-promises': ['error'],
},
})),
{
@@ -239,6 +250,7 @@ const config = {
},
],
'@typescript-eslint/no-floating-promises': 0,
'@typescript-eslint/no-misused-promises': 0,
},
},
],

View File

@@ -1,12 +1,16 @@
name: Setup maker
description: 'Setup maker dmg for electron'
runs:
using: 'composite'
steps:
- name: 'Install create-dmg from brew'
- name: 'Install @electron-forge/maker-dmg'
if: runner.os == 'macos'
shell: bash
run: brew install create-dmg
- name: 'Build @affine/makder-dmg'
shell: bash
run: yarn nx build @affine/maker-dmg
working-directory: ./apps/electron
run: yarn add @electron-forge/maker-dmg --dev
env:
HUSKY: '0'
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: '1'
ELECTRON_SKIP_BINARY_DOWNLOAD: '1'
SENTRYCLI_SKIP_DOWNLOAD: '1'

View File

@@ -17,10 +17,6 @@ inputs:
description: 'Download the Electron binary'
required: false
default: 'true'
npm-token:
description: 'The NPM token to use for private packages.'
required: false
default: ''
hard-link-nm:
description: 'set nmMode to hardlinks-local in .yarnrc.yml'
required: false
@@ -48,7 +44,6 @@ runs:
shell: bash
run: yarn install ${{ inputs.extra-flags }}
env:
NODE_AUTH_TOKEN: ${{ inputs.npm-token }}
HUSKY: '0'
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: '1'
ELECTRON_SKIP_BINARY_DOWNLOAD: '1'
@@ -59,7 +54,6 @@ runs:
shell: bash
run: yarn install ${{ inputs.extra-flags }}
env:
NODE_AUTH_TOKEN: ${{ inputs.npm-token }}
HUSKY: '0'
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: '1'
ELECTRON_SKIP_BINARY_DOWNLOAD: '1'

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
@@ -280,6 +299,49 @@ jobs:
path: ./test-results
if-no-files-found: ignore
e2e-prototype-test:
name: E2E Prototype Test
runs-on: ubuntu-latest
environment: development
needs: build-prototype
steps:
- uses: actions/checkout@v3
- name: Setup Node.js
uses: ./.github/actions/setup-node
with:
playwright-install: true
electron-install: false
- name: Download prototype artifact
uses: actions/download-artifact@v3
with:
name: prototype
path: ./apps/prototype/dist
- name: Run playwright tests
run: yarn e2e --forbid-only
working-directory: tests/affine-prototype
env:
COVERAGE: true
# - name: Collect code coverage report
# run: yarn exec nyc report -t .nyc_output --report-dir .coverage --reporter=lcov
# - name: Upload e2e test coverage results
# uses: codecov/codecov-action@v3
# with:
# token: ${{ secrets.CODECOV_TOKEN }}
# files: ./.coverage/lcov.info
# flags: e2etest-prototype
# name: affine
# fail_ci_if_error: false
- name: Upload test results
if: ${{ failure() }}
uses: actions/upload-artifact@v3
with:
name: test-results-e2e-prototype
path: ./test-results
if-no-files-found: ignore
e2e-test:
name: E2E Test
runs-on: ubuntu-latest
@@ -415,8 +477,6 @@ jobs:
with:
playwright-install: true
hard-link-nm: false
- name: Setup Maker
uses: ./.github/actions/setup-maker
- name: Build AFFiNE native
uses: ./.github/actions/build-rust
with:

View File

@@ -14,5 +14,5 @@ jobs:
- uses: styfle/cancel-workflow-action@0.11.0
with:
# See https://api.github.com/repos/toeverything/AFFiNE/actions/workflows
workflow_id: 44038251, 61883931
workflow_id: 44038251, 61883931, 65188160
access_token: ${{ github.token }}

View File

@@ -112,8 +112,10 @@ jobs:
steps:
- uses: actions/checkout@v3
- name: Setup Node.js
timeout-minutes: 10
uses: ./.github/actions/setup-node
- name: Setup Maker
timeout-minutes: 10
uses: ./.github/actions/setup-maker
- name: Build AFFiNE native
uses: ./.github/actions/build-rust

View File

@@ -114,8 +114,10 @@ jobs:
steps:
- uses: actions/checkout@v3
- name: Setup Node.js
timeout-minutes: 10
uses: ./.github/actions/setup-node
- name: Setup Maker
timeout-minutes: 10
uses: ./.github/actions/setup-maker
- name: Build AFFiNE native
uses: ./.github/actions/build-rust

View File

@@ -14,15 +14,6 @@
</div>
<div align="center">
<!--
Make New Badge Pattern badges inline
See https://github.com/all-?/all-contributors/issues/361#issuecomment-637166066
-->
<!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->
[all-contributors-badge]: https://img.shields.io/badge/all_contributors-66-orange.svg?style=flat-square
<!-- ALL-CONTRIBUTORS-BADGE:END -->
[![AFFiNE Web](<https://img.shields.io/badge/-Try%20It%20Online%20%E2%86%92-rgb(84,56,255)?style=flat-square&logoColor=white&logo=affine>)](https://app.affine.pro)
[![AFFiNE macOS M1/M2 Chip](https://img.shields.io/badge/-macOS_M_Chip%20%E2%86%92-black?style=flat-square&logo=apple&logoColor=white)](https://affine.pro/download)
@@ -39,6 +30,7 @@ See https://github.com/all-?/all-contributors/issues/361#issuecomment-637166066
[![React-version-icon]](https://reactjs.org/)
[![blocksuite-icon]](https://github.com/toeverything/blocksuite)
[![Rust-version-icon]](https://www.rust-lang.org/)
[![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2Ftoeverything%2FAFFiNE.svg?type=shield)](https://app.fossa.com/projects/git%2Bgithub.com%2Ftoeverything%2FAFFiNE?ref=badge_shield)
</div>
@@ -122,9 +114,12 @@ If you have questions, you are welcome to contact us. One of the best places to
## Plugins
> Plugins are a way to extend the functionality of AFFiNE.
> Plugins are a way to extend the functionality of AFFiNE. You can use plugins to add new blocks, new features, and even new ways to edit content.
>
> (Currently, plugins are under heavy development, and the SDK is not yet available.)
> (Currently, the plugin system is under heavy development. You will see the plugin system in the canary release.)
- [@affine/sdk](./packages/sdk) - SDK for developing plugins
- [@affine/plugin-cli](./packages/plugin-cli) - CLI for developing plugins
| Official Plugin | Description |
| ----------------------------------------------------- | ----------------------------------------- |
@@ -136,15 +131,15 @@ If you have questions, you are welcome to contact us. One of the best places to
We would also like to give thanks to open-source projects that make AFFiNE possible:
- [BlockSuite](https://github.com/toeverything/BlockSuite) - 💠 BlockSuite is the open-source collaborative editor project behind AFFiNE.
- [blocksuite](https://github.com/toeverything/BlockSuite) - 💠 BlockSuite is the open-source collaborative editor project behind AFFiNE.
- [OctoBase](https://github.com/toeverything/OctoBase) - 🐙 OctoBase is the open-source database behind AFFiNE, local-first, yet collaborative. A light-weight, scalable, data engine written in Rust.
- [Yjs](https://github.com/yjs/yjs) - Fundamental support of CRDTs for our implementation on state management and data sync.
- [Electron](https://github.com/electron/electron) - Build cross-platform desktop apps with JavaScript, HTML, and CSS.
- [React](https://github.com/facebook/react) - View layer support and web GUI framework.
- [Rust](https://github.com/rust-lang/rust) - High performance language that extends the ability and availability of our real-time backend, OctoBase.
- [yjs](https://github.com/yjs/yjs) - Fundamental support of CRDTs for our implementation on state management and data sync.
- [electron](https://github.com/electron/electron) - Build cross-platform desktop apps with JavaScript, HTML, and CSS.
- [React](https://github.com/facebook/react) - The library for web and native user interfaces.
- [napi-rs](https://github.com/napi-rs/napi-rs) - A framework for building compiled Node.js add-ons in Rust via Node-API.
- [Jotai](https://github.com/pmndrs/jotai) - Primitive and flexible state management for React.
- [MUI](https://github.com/mui/material-ui) - Our most used graphic UI component library.
- [async-call-rpc](https://github.com/Jack-Works/async-call-rpc) - A lightweight JSON RPC client & server.
- [Vite](https://github.com/vitejs/vite) - Next generation frontend tooling.
- Other upstream [dependencies](https://github.com/toeverything/AFFiNE/network/dependencies).
Thanks a lot to the community for providing such powerful and simple libraries, so that we can focus more on the implementation of the product logic, and we hope that in the future our projects will also provide a more easy-to-use knowledge base for everyone.
@@ -168,15 +163,11 @@ Some amazing companies including AFFiNE are looking for developers! Are you inte
## Upgrading
For upgrading information please see our [update page].
For upgrading information, please see our [update page].
## Feature Request
For feature request please see [community.affine.pro](https://community.affine.pro/c/feature-requests/).
## Is it awesome?
[These people] seem to like it.
For feature request, please see [community.affine.pro](https://community.affine.pro/c/feature-requests/).
## Building
@@ -191,9 +182,9 @@ See [docs/contributing/tutorial.md](./docs/contributing/tutorial.md) for details
See [LICENSE] for details.
[all-contributors-badge]: https://img.shields.io/github/all-contributors/toeverything/AFFiNE/master?color=orange
[license]: ./LICENSE
[building.md]: ./docs/BUILDING.md
[these people]: https://twitter.com/AffineOfficial/followers
[update page]: https://affine.pro/blog?tag=Release%20Note
[jobs available]: ./docs/jobs.md
[latest packages]: https://github.com/toeverything/AFFiNE/pkgs/container/affine-self-hosted
@@ -205,3 +196,5 @@ 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

@@ -73,6 +73,8 @@ export const createConfiguration: (
) => webpack.Configuration = (buildFlags, runtimeConfig) => {
let publicPath = process.env.PUBLIC_PATH ?? '/';
const blocksuiteBaseDir = buildFlags.localBlockSuite;
const cacheKey = computeCacheKey(buildFlags);
const config = {
@@ -82,6 +84,7 @@ export const createConfiguration: (
experiments: {
topLevelAwait: true,
outputModule: false,
syncWebAssembly: true,
},
output: {
environment: {
@@ -121,6 +124,60 @@ export const createConfiguration: (
'.mjs': ['.mjs', '.mts'],
},
extensions: ['.js', '.ts', '.tsx'],
fallback:
blocksuiteBaseDir === undefined
? undefined
: {
events: false,
},
alias:
blocksuiteBaseDir === undefined
? undefined
: {
yjs: require.resolve('yjs'),
'@blocksuite/block-std': resolve(
blocksuiteBaseDir,
'packages',
'block-std'
),
'@blocksuite/blocks': resolve(
blocksuiteBaseDir,
'packages',
'blocks'
),
'@blocksuite/editor': resolve(
blocksuiteBaseDir,
'packages',
'editor'
),
'@blocksuite/global': resolve(
blocksuiteBaseDir,
'packages',
'global'
),
'@blocksuite/lit': resolve(blocksuiteBaseDir, 'packages', 'lit'),
'@blocksuite/phasor': resolve(
blocksuiteBaseDir,
'packages',
'phasor'
),
'@blocksuite/store/providers/broadcast-channel': resolve(
blocksuiteBaseDir,
'packages',
'store',
'src/providers/broadcast-channel'
),
'@blocksuite/store': resolve(
blocksuiteBaseDir,
'packages',
'store'
),
'@blocksuite/virgo': resolve(
blocksuiteBaseDir,
'packages',
'virgo'
),
},
},
cache: {
@@ -167,8 +224,7 @@ export const createConfiguration: (
oneOf: [
{
test: /\.tsx?$/,
// Compile all ts files in the workspace
include: resolve(rootPath, '..', '..'),
exclude: /node_modules/,
loader: require.resolve('swc-loader'),
options: {
// https://swc.rs/docs/configuring-swc/
@@ -179,9 +235,10 @@ export const createConfiguration: (
dynamicImport: true,
topLevelAwait: false,
tsx: true,
decorators: true,
},
target: 'es2022',
externalHelpers: true,
externalHelpers: false,
transform: {
react: {
runtime: 'automatic',
@@ -191,6 +248,7 @@ export const createConfiguration: (
emitFullSignatures: true,
},
},
useDefineForClassFields: false,
},
experimental: {
keepImportAssertions: true,

View File

@@ -7,5 +7,6 @@ export function computeCacheKey(buildFlags: BuildFlags) {
buildFlags.mode,
buildFlags.distribution,
buildFlags.channel,
...(buildFlags.localBlockSuite ? [buildFlags.localBlockSuite] : []),
].join('-');
}

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,7 +2,7 @@
"name": "@affine/core",
"type": "module",
"private": true,
"version": "0.8.0-canary.9",
"version": "0.8.0-canary.20",
"scripts": {
"build": "yarn -T run build-core",
"dev": "yarn -T run dev-core",
@@ -18,29 +18,33 @@
"@affine/jotai": "workspace:*",
"@affine/templates": "workspace:*",
"@affine/workspace": "workspace:*",
"@blocksuite/block-std": "0.0.0-20230802200139-381599c0-nightly",
"@blocksuite/blocks": "0.0.0-20230802200139-381599c0-nightly",
"@blocksuite/editor": "0.0.0-20230802200139-381599c0-nightly",
"@blocksuite/global": "0.0.0-20230802200139-381599c0-nightly",
"@blocksuite/icons": "^2.1.29",
"@blocksuite/lit": "0.0.0-20230802200139-381599c0-nightly",
"@blocksuite/store": "0.0.0-20230802200139-381599c0-nightly",
"@blocksuite/block-std": "0.0.0-20230811201552-f37162ea-nightly",
"@blocksuite/blocks": "0.0.0-20230811201552-f37162ea-nightly",
"@blocksuite/editor": "0.0.0-20230811201552-f37162ea-nightly",
"@blocksuite/global": "0.0.0-20230811201552-f37162ea-nightly",
"@blocksuite/icons": "^2.1.31",
"@blocksuite/lit": "0.0.0-20230811201552-f37162ea-nightly",
"@blocksuite/store": "0.0.0-20230811201552-f37162ea-nightly",
"@dnd-kit/core": "^6.0.8",
"@dnd-kit/sortable": "^7.0.2",
"@emotion/cache": "^11.11.0",
"@emotion/react": "^11.11.1",
"@emotion/server": "^11.11.0",
"@emotion/styled": "^11.11.0",
"@mui/material": "^5.14.2",
"@mui/material": "^5.14.4",
"@react-hookz/web": "^23.1.0",
"@toeverything/components": "^0.0.10",
"@types/lodash.throttle": "^4.1.7",
"async-call-rpc": "^6.3.1",
"cmdk": "^0.2.0",
"css-spring": "^4.1.0",
"cssnano": "^6.0.1",
"graphql": "^16.7.1",
"jotai": "^2.2.2",
"jotai-devtools": "^0.6.0",
"lit": "^2.7.6",
"intl-segmenter-polyfill-rs": "^0.1.5",
"jotai": "^2.3.1",
"jotai-devtools": "^0.6.1",
"lit": "^2.8.0",
"lodash.throttle": "^4.1.1",
"lottie-web": "^5.12.2",
"mini-css-extract-plugin": "^2.7.6",
"next-themes": "^0.2.1",
@@ -49,20 +53,20 @@
"react-dom": "18.2.0",
"react-is": "18.2.0",
"react-resizable-panels": "^0.0.54",
"react-router-dom": "^6.14.2",
"react-router-dom": "^6.15.0",
"rxjs": "^7.8.1",
"ses": "^0.18.5",
"swr": "2.1.5",
"ses": "^0.18.7",
"swr": "2.2.1",
"y-protocols": "^1.0.5",
"yjs": "^13.6.7",
"zod": "^3.21.4"
},
"devDependencies": {
"@perfsee/webpack": "^1.8.2",
"@perfsee/webpack": "^1.8.4",
"@pmmmwh/react-refresh-webpack-plugin": "^0.5.10",
"@sentry/webpack-plugin": "^2.5.0",
"@sentry/webpack-plugin": "^2.6.2",
"@svgr/webpack": "^8.0.1",
"@swc/core": "^1.3.71",
"@swc/core": "^1.3.76",
"@types/webpack-env": "^1.18.1",
"copy-webpack-plugin": "^11.0.0",
"css-loader": "^6.8.1",
@@ -72,7 +76,7 @@
"source-map-loader": "^4.0.1",
"style-loader": "^3.3.3",
"swc-loader": "^0.2.3",
"swc-plugin-coverage-instrument": "^0.0.19",
"swc-plugin-coverage-instrument": "^0.0.20",
"thread-loader": "^4.0.2",
"ts-node": "^10.9.1",
"webpack": "^5.88.2",

View File

@@ -16,6 +16,7 @@
"{projectRoot}/.webpack/**/*",
"{projectRoot}/**/*",
"{projectRoot}/public/**/*",
"{workspaceRoot}/packages/env/src/**/*",
"{workspaceRoot}/packages/component/src/**/*",
"{workspaceRoot}/packages/debug/src/**/*",
"{workspaceRoot}/packages/graphql/src/**/*",

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 { assertExists } from '@blocksuite/global/utils';
import { registeredPluginAtom, rootStore } from '@toeverything/infra/atom';
import { loadedPluginNameAtom, rootStore } from '@toeverything/infra/atom';
import { use } from 'foxact/use';
import { useAtomValue } from 'jotai';
import { Provider } from 'jotai/react';
@@ -17,7 +17,7 @@ async function main() {
const App = () => {
use(pluginRegisterPromise);
const plugins = useAtomValue(registeredPluginAtom);
const plugins = useAtomValue(loadedPluginNameAtom);
_pluginNestedImportsMap.forEach(value => {
const exports = value.get('index.js');
assertExists(exports);

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,6 +20,7 @@ import { getOrCreateWorkspace } from '@affine/workspace/manager';
import { createIndexedDBDownloadProvider } from '@affine/workspace/providers';
import { nanoid } from '@blocksuite/store';
import { useStaticBlockSuiteWorkspace } from '@toeverything/infra/__internal__/react';
import { buildShowcaseWorkspace } from '@toeverything/infra/blocksuite';
import {
BlockSuitePageList,
@@ -41,21 +42,18 @@ export const LocalAdapter: WorkspaceAdapter<WorkspaceFlavour.LOCAL> = {
WorkspaceFlavour.LOCAL
);
blockSuiteWorkspace.meta.setName(DEFAULT_WORKSPACE_NAME);
const page = blockSuiteWorkspace.createPage({
id: `${blockSuiteWorkspace.id}-${DEFAULT_HELLO_WORLD_PAGE_ID_SUFFIX}`,
});
if (runtimeConfig.enablePreloading) {
initPageWithPreloading(page).catch(err => {
buildShowcaseWorkspace(blockSuiteWorkspace).catch(err => {
logger.error('init page with preloading failed', err);
});
} else {
const page = blockSuiteWorkspace.createPage({
id: `${blockSuiteWorkspace.id}-${DEFAULT_HELLO_WORLD_PAGE_ID_SUFFIX}`,
});
initEmptyPage(page).catch(error => {
logger.error('init page with empty failed', error);
});
}
blockSuiteWorkspace.setPageMeta(page.id, {
jumpOnce: true,
});
const provider = createIndexedDBDownloadProvider(
blockSuiteWorkspace.id,
blockSuiteWorkspace.doc,

View File

@@ -1,5 +1,6 @@
import '@affine/component/theme/global.css';
import '@affine/component/theme/theme.css';
import '@toeverything/components/style.css';
import { AffineContext } from '@affine/component/context';
import { WorkspaceFallback } from '@affine/component/workspace';

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

@@ -1,5 +1,7 @@
import type { PrimitiveAtom } from 'jotai';
import { atom } from 'jotai';
import { atomFamily, atomWithStorage } from 'jotai/utils';
import type { AtomFamily } from 'jotai/vanilla/utils/atomFamily';
import type { CreateWorkspaceMode } from '../components/affine/create-workspace-modal';
import type { SettingProps } from '../components/affine/setting-modal';
@@ -59,19 +61,16 @@ const defaultPageSetting = {
mode: 'page',
} satisfies PageLocalSetting;
export const pageSettingFamily = atomFamily((pageId: string) =>
export const pageSettingFamily: AtomFamily<
string,
PrimitiveAtom<PageLocalSetting>
> = atomFamily((pageId: string) =>
atom(
get =>
get(pageSettingsBaseAtom)[pageId] ?? {
...defaultPageSetting,
},
(
get,
set,
patch:
| Partial<PageLocalSetting>
| ((prevSetting: PageLocalSetting | undefined) => void)
) => {
(get, set, patch) => {
set(recentPageSettingsBaseAtom, ids => {
// pick 3 recent page ids
return [...new Set([pageId, ...ids]).values()].slice(0, 3);

View File

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

View File

@@ -1,83 +1,140 @@
import * as AFFiNEComponent from '@affine/component';
import { DebugLogger } from '@affine/debug';
import type { CallbackMap, PluginContext } from '@affine/sdk/entry';
import { FormatQuickBar } from '@blocksuite/blocks';
import * as BlockSuiteBlocksStd from '@blocksuite/blocks/std';
import * as BlockSuiteGlobalUtils from '@blocksuite/global/utils';
import type {
CallbackMap,
ExpectedLayout,
LayoutNode,
PluginContext,
} from '@affine/sdk/entry';
import { AffineFormatBarWidget } from '@blocksuite/blocks';
import { assertExists } from '@blocksuite/global/utils';
import { DisposableGroup } from '@blocksuite/global/utils';
import * as Icons from '@blocksuite/icons';
import {
addCleanup,
pluginEditorAtom,
pluginHeaderItemAtom,
pluginSettingAtom,
pluginWindowAtom,
} from '@toeverything/infra/__internal__/plugin';
import {
contentLayoutAtom,
currentPageAtom,
currentWorkspaceAtom,
editorItemsAtom,
headerItemsAtom,
rootStore,
settingItemsAtom,
windowItemsAtom,
} from '@toeverything/infra/atom';
import * as Jotai from 'jotai/index';
import { atom } from 'jotai';
import { Provider } from 'jotai/react';
import * as JotaiUtils from 'jotai/utils';
import * as React from 'react';
import { createElement, type PropsWithChildren } from 'react';
import * as ReactJSXRuntime from 'react/jsx-runtime';
import * as ReactDom from 'react-dom';
import * as ReactDomClient from 'react-dom/client';
import * as SWR from 'swr';
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');
const setupRootImportsMap = () => {
_rootImportsMap.set('react', new Map(Object.entries(React)));
_rootImportsMap.set(
'react/jsx-runtime',
new Map(Object.entries(ReactJSXRuntime))
);
_rootImportsMap.set('react-dom', new Map(Object.entries(ReactDom)));
_rootImportsMap.set(
'react-dom/client',
new Map(Object.entries(ReactDomClient))
);
_rootImportsMap.set('@blocksuite/icons', new Map(Object.entries(Icons)));
_rootImportsMap.set(
'@affine/component',
new Map(Object.entries(AFFiNEComponent))
);
_rootImportsMap.set(
'@blocksuite/blocks/std',
new Map(Object.entries(BlockSuiteBlocksStd))
);
_rootImportsMap.set(
'@blocksuite/global/utils',
new Map(Object.entries(BlockSuiteGlobalUtils))
);
_rootImportsMap.set('jotai', new Map(Object.entries(Jotai)));
_rootImportsMap.set('jotai/utils', new Map(Object.entries(JotaiUtils)));
_rootImportsMap.set(
'@affine/sdk/entry',
new Map(
Object.entries({
rootStore: rootStore,
currentWorkspaceAtom: currentWorkspaceAtom,
currentPageAtom: currentPageAtom,
contentLayoutAtom: contentLayoutAtom,
})
)
);
_rootImportsMap.set('swr', new Map(Object.entries(SWR)));
};
const pushLayoutAtom = atom<
null,
// fixme: check plugin name here
[pluginName: string, create: (root: HTMLElement) => () => void],
void
>(null, (_, set, pluginName, callback) => {
set(pluginWindowAtom, items => ({
...items,
[pluginName]: callback,
}));
set(contentLayoutAtom, layout => {
if (layout === 'editor') {
return {
direction: 'horizontal',
first: 'editor',
second: pluginName,
splitPercentage: 70,
};
} else {
return {
...layout,
direction: 'horizontal',
first: 'editor',
second: {
direction: 'horizontal',
// fixme: incorrect type here
first: layout.second,
second: pluginName,
splitPercentage: 70,
},
} as ExpectedLayout;
}
});
addCleanup(pluginName, () => {
set(deleteLayoutAtom, pluginName);
});
});
const deleteLayoutAtom = atom<null, [string], void>(null, (_, set, id) => {
set(pluginWindowAtom, items => {
const newItems = { ...items };
delete newItems[id];
return newItems;
});
const removeLayout = (layout: LayoutNode): LayoutNode => {
if (layout === 'editor') {
return 'editor';
} else {
if (typeof layout === 'string') {
return layout as ExpectedLayout;
}
if (layout.first === id) {
return layout.second;
} else if (layout.second === id) {
return layout.first;
} else {
return removeLayout(layout.second);
}
}
};
set(contentLayoutAtom, layout => {
if (layout === 'editor') {
return 'editor';
} else {
if (typeof layout === 'string') {
return layout as ExpectedLayout;
}
if (layout.first === id) {
return layout.second as ExpectedLayout;
} else if (layout.second === id) {
return layout.first as ExpectedLayout;
} else {
return removeLayout(layout.second) as ExpectedLayout;
}
}
});
});
// module -> importName -> updater[]
export const _rootImportsMap = new Map<string, Map<string, any>>();
setupRootImportsMap();
const rootImportsMapSetupPromise = setupImportsMap(_rootImportsMap, {
react: import('react'),
'react/jsx-runtime': import('react/jsx-runtime'),
'react-dom': import('react-dom'),
'react-dom/client': import('react-dom/client'),
jotai: import('jotai'),
'jotai/utils': import('jotai/utils'),
swr: import('swr'),
'@affine/component': import('@affine/component'),
'@blocksuite/icons': import('@blocksuite/icons'),
'@affine/sdk/entry': {
rootStore: rootStore,
currentWorkspaceAtom: currentWorkspaceAtom,
currentPageAtom: currentPageAtom,
pushLayoutAtom: pushLayoutAtom,
deleteLayoutAtom: deleteLayoutAtom,
},
'@blocksuite/global/utils': import('@blocksuite/global/utils'),
'@toeverything/infra/atom': import('@toeverything/infra/atom'),
'@toeverything/components/button': import('@toeverything/components/button'),
});
// pluginName -> module -> importName -> updater[]
export const _pluginNestedImportsMap = new Map<
string,
@@ -135,7 +192,15 @@ const pluginFetch = createFetch({});
const timer = createTimers(abortController.signal);
const sharedGlobalThis = Object.assign(Object.create(null), timer, {
Object: globalThis.Object,
fetch: pluginFetch,
ReadableStream: globalThis.ReadableStream,
Symbol: globalThis.Symbol,
Error: globalThis.Error,
TypeError: globalThis.TypeError,
RangeError: globalThis.RangeError,
console: globalThis.console,
crypto: globalThis.crypto,
});
const dynamicImportMap = new Map<
@@ -206,6 +271,7 @@ export const createOrGetGlobalThis = (
Object.create(null),
sharedGlobalThis,
{
// fixme: vite build output bundle will have this, we should remove it
process: Object.freeze({
env: {
NODE_ENV: process.env.NODE_ENV,
@@ -222,6 +288,9 @@ export const createOrGetGlobalThis = (
if (sharedGlobalThis[key]) return sharedGlobalThis[key];
const result = Reflect.get(window, key);
if (typeof result === 'function') {
if (result === ShadowRoot) {
return result;
}
return function (...args: any[]) {
permissionLogger.debug(
`${pluginName} is calling window`,
@@ -262,15 +331,11 @@ export const createOrGetGlobalThis = (
userAgent: navigator.userAgent,
},
// safe to use for all plugins
Error: globalThis.Error,
TypeError: globalThis.TypeError,
RangeError: globalThis.RangeError,
console: globalThis.console,
crypto: globalThis.crypto,
MouseEvent: globalThis.MouseEvent,
KeyboardEvent: globalThis.KeyboardEvent,
CustomEvent: globalThis.CustomEvent,
// copilot uses these
CustomEvent: globalThis.CustomEvent,
Date: globalThis.Date,
Math: globalThis.Math,
URL: globalThis.URL,
@@ -284,6 +349,10 @@ export const createOrGetGlobalThis = (
Blob: globalThis.Blob,
ClipboardItem: globalThis.ClipboardItem,
// vue uses these
Element: globalThis.Element,
SVGElement: globalThis.SVGElement,
// fixme: use our own db api
indexedDB: globalThis.indexedDB,
IDBRequest: globalThis.IDBRequest,
@@ -299,6 +368,7 @@ export const createOrGetGlobalThis = (
IDBVersionChangeEvent: globalThis.IDBVersionChangeEvent,
}
);
pluginGlobalThis.global = pluginGlobalThis;
globalThisMap.set(pluginName, pluginGlobalThis);
return pluginGlobalThis;
};
@@ -308,6 +378,7 @@ export const setupPluginCode = async (
pluginName: string,
filename: string
) => {
await rootImportsMapSetupPromise;
if (!_pluginNestedImportsMap.has(pluginName)) {
_pluginNestedImportsMap.set(pluginName, new Map());
}
@@ -396,7 +467,6 @@ const PluginProvider = ({ children }: PropsWithChildren) =>
children
);
const group = new DisposableGroup();
const entryLogger = new DebugLogger('plugin:entry');
export const evaluatePluginEntry = (pluginName: string) => {
@@ -409,31 +479,59 @@ export const evaluatePluginEntry = (pluginName: string) => {
register: (part, callback) => {
entryLogger.info(`Registering ${pluginName} to ${part}`);
if (part === 'headerItem') {
rootStore.set(headerItemsAtom, items => ({
rootStore.set(pluginHeaderItemAtom, items => ({
...items,
[pluginName]: callback as CallbackMap['headerItem'],
}));
addCleanup(pluginName, () => {
rootStore.set(pluginHeaderItemAtom, items => {
const newItems = { ...items };
delete newItems[pluginName];
return newItems;
});
});
} else if (part === 'editor') {
rootStore.set(editorItemsAtom, items => ({
rootStore.set(pluginEditorAtom, items => ({
...items,
[pluginName]: callback as CallbackMap['editor'],
}));
} else if (part === 'window') {
rootStore.set(windowItemsAtom, items => ({
...items,
[pluginName]: callback as CallbackMap['window'],
}));
addCleanup(pluginName, () => {
rootStore.set(pluginEditorAtom, items => {
const newItems = { ...items };
delete newItems[pluginName];
return newItems;
});
});
} else if (part === 'setting') {
rootStore.set(settingItemsAtom, items => ({
rootStore.set(pluginSettingAtom, items => ({
...items,
[pluginName]: callback as CallbackMap['setting'],
}));
} else if (part === 'formatBar') {
FormatQuickBar.customElements.push((page, getBlockRange) => {
const div = document.createElement('div');
(callback as CallbackMap['formatBar'])(div, page, getBlockRange);
return div;
addCleanup(pluginName, () => {
rootStore.set(pluginSettingAtom, items => {
const newItems = { ...items };
delete newItems[pluginName];
return newItems;
});
});
} else if (part === 'formatBar') {
const register = (widget: AffineFormatBarWidget) => {
const div = document.createElement('div');
const root = widget.root;
const cleanup = (callback as CallbackMap['formatBar'])(
div,
widget.page,
() => {
return root.selectionManager.value;
}
);
addCleanup(pluginName, () => {
AffineFormatBarWidget.customElements.delete(register);
cleanup();
});
return div;
};
AffineFormatBarWidget.customElements.add(register);
} else {
throw new Error(`Unknown part: ${part}`);
}
@@ -445,5 +543,5 @@ export const evaluatePluginEntry = (pluginName: string) => {
if (typeof cleanup !== 'function') {
throw new Error('Plugin entry must return a function');
}
group.add(cleanup);
addCleanup(pluginName, cleanup);
};

View File

@@ -1,15 +1,16 @@
import { DebugLogger } from '@affine/debug';
import { registeredPluginAtom, rootStore } from '@toeverything/infra/atom';
import {
builtinPluginPaths,
enabledPluginAtom,
invokeCleanup,
pluginPackageJson,
} from '@toeverything/infra/__internal__/plugin';
import { loadedPluginNameAtom, rootStore } from '@toeverything/infra/atom';
import { packageJsonOutputSchema } from '@toeverything/infra/type';
import type { z } from 'zod';
import { evaluatePluginEntry, setupPluginCode } from './plugins/setup';
const builtinPluginUrl = new Set([
'/plugins/bookmark',
'/plugins/copilot',
'/plugins/hello-world',
'/plugins/image-preview',
]);
const logger = new DebugLogger('register-plugins');
declare global {
@@ -17,13 +18,50 @@ declare global {
var __pluginPackageJson__: unknown[];
}
globalThis.__pluginPackageJson__ = [];
Object.defineProperty(globalThis, '__pluginPackageJson__', {
get() {
return rootStore.get(pluginPackageJson);
},
});
rootStore.sub(enabledPluginAtom, () => {
const added = new Set<string>();
const removed = new Set<string>();
const enabledPlugin = new Set(rootStore.get(enabledPluginAtom));
enabledPlugin.forEach(pluginName => {
if (!enabledPluginSet.has(pluginName)) {
added.add(pluginName);
}
});
enabledPluginSet.forEach(pluginName => {
if (!enabledPlugin.has(pluginName)) {
removed.add(pluginName);
}
});
// update plugins
enabledPluginSet.clear();
enabledPlugin.forEach(pluginName => {
enabledPluginSet.add(pluginName);
});
added.forEach(pluginName => {
evaluatePluginEntry(pluginName);
});
removed.forEach(pluginName => {
invokeCleanup(pluginName);
});
});
const enabledPluginSet = new Set(rootStore.get(enabledPluginAtom));
const loadedAssets = new Set<string>();
// we will load all plugins in parallel from builtinPlugins
export const pluginRegisterPromise = Promise.all(
[...builtinPluginUrl].map(url => {
[...builtinPluginPaths].map(url => {
return fetch(`${url}/package.json`)
.then(async res => {
const packageJson = await res.json();
const packageJson = (await res.json()) as z.infer<
typeof packageJsonOutputSchema
>;
packageJsonOutputSchema.parse(packageJson);
const {
name: pluginName,
affinePlugin: {
@@ -32,7 +70,7 @@ export const pluginRegisterPromise = Promise.all(
assets,
},
} = packageJson;
globalThis.__pluginPackageJson__.push(packageJson);
rootStore.set(pluginPackageJson, json => [...json, packageJson]);
logger.debug(`registering plugin ${pluginName}`);
logger.debug(`package.json: ${packageJson}`);
if (!release && !runtimeConfig.enablePlugin) {
@@ -40,14 +78,19 @@ export const pluginRegisterPromise = Promise.all(
}
const baseURL = url;
const entryURL = `${baseURL}/${core}`;
rootStore.set(registeredPluginAtom, prev => [...prev, pluginName]);
rootStore.set(loadedPluginNameAtom, prev => [...prev, pluginName]);
await setupPluginCode(baseURL, pluginName, core);
console.log(`prepareImports for ${pluginName} done`);
await fetch(entryURL).then(async () => {
if (assets.length > 0) {
await Promise.all(
assets.map(async (asset: string) => {
// todo(himself65): add assets into shadow dom
if (loadedAssets.has(asset)) {
return Promise.resolve();
}
if (asset.endsWith('.css')) {
loadedAssets.add(asset);
const res = await fetch(`${baseURL}/${asset}`);
if (res.ok) {
// todo: how to put css file into sandbox?
@@ -65,7 +108,12 @@ export const pluginRegisterPromise = Promise.all(
})
);
}
evaluatePluginEntry(pluginName);
if (!enabledPluginSet.has(pluginName)) {
logger.debug(`plugin ${pluginName} is not enabled`);
} else {
logger.debug(`plugin ${pluginName} is enabled`);
evaluatePluginEntry(pluginName);
}
});
})
.catch(e => {

View File

@@ -14,6 +14,7 @@ import {
rootWorkspacesMetadataAtom,
workspaceAdaptersAtom,
} from '@affine/workspace/atom';
import { globalBlockSuiteSchema } from '@affine/workspace/manager';
import {
migrateLocalBlobStorage,
upgradeV1ToV2,
@@ -36,15 +37,15 @@ async function tryMigration() {
const adapter = WorkspaceAdapters[oldMeta.flavour];
assertExists(adapter);
const upgrade = async () => {
if (oldMeta.flavour !== WorkspaceFlavour.LOCAL) {
console.warn('not supported');
return;
}
const workspace = await adapter.CRUD.get(oldMeta.id);
if (!workspace) {
console.warn('cannot find workspace', oldMeta.id);
return;
}
if (workspace.flavour !== WorkspaceFlavour.LOCAL) {
console.warn('not supported');
return;
}
const doc = workspace.blockSuiteWorkspace.doc;
const provider = createIndexedDBDownloadProvider(
workspace.id,
@@ -62,7 +63,10 @@ async function tryMigration() {
return;
}
const newWorkspace = upgradeV1ToV2(workspace);
await migrateDatabaseBlockTo3(newWorkspace.blockSuiteWorkspace.doc);
await migrateDatabaseBlockTo3(
newWorkspace.blockSuiteWorkspace.doc,
globalBlockSuiteSchema
);
const newId = await adapter.CRUD.create(
newWorkspace.blockSuiteWorkspace
@@ -83,11 +87,14 @@ async function tryMigration() {
// create a new workspace and push it to metadata
promises.push(upgrade());
} else if (oldMeta.version < WorkspaceVersion.DatabaseV3) {
console.log('migrate to v3');
const adapter = WorkspaceAdapters[oldMeta.flavour];
assertExists(adapter);
promises.push(
(async () => {
if (oldMeta.flavour !== WorkspaceFlavour.LOCAL) {
console.warn('not supported');
return;
}
const workspace = await adapter.CRUD.get(oldMeta.id);
if (workspace) {
const provider = createIndexedDBDownloadProvider(
@@ -101,17 +108,18 @@ async function tryMigration() {
provider.sync();
await provider.whenReady;
await migrateDatabaseBlockTo3(
workspace.blockSuiteWorkspace.doc
workspace.blockSuiteWorkspace.doc,
globalBlockSuiteSchema
);
}
const index = newMetadata.findIndex(
meta => meta.id === oldMeta.id
);
console.log('migrate to v3');
newMetadata[index] = {
...oldMeta,
version: WorkspaceVersion.DatabaseV3,
};
console.log('migrate to v3');
})()
);
}
@@ -121,8 +129,8 @@ async function tryMigration() {
.then(() => {
console.log('migration done');
})
.catch(() => {
console.error('migration failed');
.catch(e => {
console.error('migration failed', e);
})
.finally(() => {
localStorage.setItem('jotai-workspaces', JSON.stringify(newMetadata));

View File

@@ -3,7 +3,6 @@ import { WorkspaceFlavour } from '@affine/env/workspace';
import { getOrCreateWorkspace } from '@affine/workspace/manager';
import type { EditorContainer } from '@blocksuite/editor';
import type { Page } from '@blocksuite/store';
import type React from 'react';
import { useCallback } from 'react';
import { BlockSuiteEditor } from '../../blocksuite/block-suite-editor';
@@ -15,7 +14,7 @@ const blockSuiteWorkspace = getOrCreateWorkspace(
const page = blockSuiteWorkspace.createPage({ id: 'page0' });
const Editor: React.FC = () => {
const Editor = () => {
const onLoad = useCallback((page: Page, editor: EditorContainer) => {
// @ts-expect-error
globalThis.page = page;

View File

@@ -1,5 +1,4 @@
import {
Button,
Input,
Modal,
ModalCloseButton,
@@ -10,6 +9,11 @@ import {
import { DebugLogger } from '@affine/debug';
import { useAFFiNEI18N } from '@affine/i18n/hooks';
import { HelpIcon } from '@blocksuite/icons';
import { Button } from '@toeverything/components/button';
import type {
LoadDBFileResult,
SelectDBFileLocationResult,
} from '@toeverything/infra/type';
import { useSetAtom } from 'jotai';
import type { KeyboardEvent } from 'react';
import { useEffect } from 'react';
@@ -134,12 +138,12 @@ const SetDBLocationContent = ({
}
setOpening(true);
(async function () {
const result = await window.apis?.dialog.selectDBFileLocation();
const result: SelectDBFileLocationResult =
await window.apis?.dialog.selectDBFileLocation();
setOpening(false);
if (result?.filePath) {
onConfirmLocation(result.filePath);
} else if (result?.error) {
// @ts-expect-error: result.error is dynamic so the type is unknown
toast(t[result.error]());
}
})().catch(err => {
@@ -267,13 +271,12 @@ export const CreateWorkspaceModal = ({
}
logger.info('load db file');
setStep(undefined);
const result = await window.apis.dialog.loadDBFile();
const result: LoadDBFileResult = await window.apis.dialog.loadDBFile();
if (result.workspaceId && !canceled) {
setAddedId(result.workspaceId);
setStep('set-syncing-mode');
} else if (result.error || result.canceled) {
if (result.error) {
// @ts-expect-error: result.error is dynamic so the type is unknown
toast(t[result.error]());
}
onClose();

View File

@@ -1,9 +1,9 @@
import { IconButton, Modal, ModalWrapper } from '@affine/component';
import { Modal, ModalWrapper, Wrapper } from '@affine/component';
import { useAFFiNEI18N } from '@affine/i18n/hooks';
import { CloseIcon } from '@blocksuite/icons';
import type React from 'react';
import { Button, IconButton } from '@toeverything/components/button';
import { Content, ContentTitle, Header, StyleButton, StyleTips } from './style';
import { Content, ContentTitle, Header, StyleTips } from './style';
interface EnableAffineCloudModalProps {
open: boolean;
@@ -11,11 +11,11 @@ interface EnableAffineCloudModalProps {
onClose: () => void;
}
export const EnableAffineCloudModal: React.FC<EnableAffineCloudModalProps> = ({
export const EnableAffineCloudModal = ({
onConfirm,
open,
onClose,
}) => {
}: EnableAffineCloudModalProps) => {
const t = useAFFiNEI18N();
return (
@@ -30,19 +30,22 @@ export const EnableAffineCloudModal: React.FC<EnableAffineCloudModalProps> = ({
<ContentTitle>{t['Enable AFFiNE Cloud']()}?</ContentTitle>
<StyleTips>{t['Enable AFFiNE Cloud Description']()}</StyleTips>
{/* <StyleTips>{t('Retain cached cloud data')}</StyleTips> */}
<div>
<StyleButton
<Wrapper width={284} margin="auto">
<Button
data-testid="confirm-enable-affine-cloud-button"
shape="round"
type="primary"
block
onClick={onConfirm}
style={{
marginBottom: '16px',
}}
>
{t['Sign in and Enable']()}
</StyleButton>
<StyleButton shape="round" onClick={onClose}>
</Button>
<Button onClick={onClose} block>
{t['Not now']()}
</StyleButton>
</div>
</Button>
</Wrapper>
</Content>
</ModalWrapper>
</Modal>

View File

@@ -1,4 +1,4 @@
import { Button, styled } from '@affine/component';
import { styled } from '@affine/component';
export const Header = styled('div')({
height: '44px',
@@ -29,12 +29,3 @@ export const StyleTips = styled('div')(() => {
marginTop: '12px',
};
});
export const StyleButton = styled(Button)(() => {
return {
width: '284px',
display: 'block',
margin: 'auto',
marginTop: '16px',
};
});

View File

@@ -1,13 +1,8 @@
import {
type ButtonProps,
Menu,
MenuItem,
MenuTrigger,
styled,
} from '@affine/component';
import { Menu, MenuItem, MenuTrigger, styled } from '@affine/component';
import { LOCALES } from '@affine/i18n';
import { useI18N } from '@affine/i18n';
import type { FC, ReactElement } from 'react';
import type { ButtonProps } from '@toeverything/components/button';
import type { ReactElement } from 'react';
import { useCallback } from 'react';
export const StyledListItem = styled(MenuItem)(() => ({
@@ -16,9 +11,11 @@ export const StyledListItem = styled(MenuItem)(() => ({
textTransform: 'capitalize',
}));
const LanguageMenuContent: FC<{
interface LanguageMenuContentProps {
currentLanguage?: string;
}> = ({ currentLanguage }) => {
}
const LanguageMenuContent = ({ currentLanguage }: LanguageMenuContentProps) => {
const i18n = useI18N();
const changeLanguage = useCallback(
(event: string) => {
@@ -26,6 +23,7 @@ const LanguageMenuContent: FC<{
},
[i18n]
);
return (
<>
{LOCALES.map(option => {
@@ -47,9 +45,12 @@ const LanguageMenuContent: FC<{
</>
);
};
export const LanguageMenu: FC<{ triggerProps?: ButtonProps }> = ({
triggerProps,
}) => {
interface LanguageMenuProps {
triggerProps?: ButtonProps;
}
export const LanguageMenu = ({ triggerProps }: LanguageMenuProps) => {
const i18n = useI18N();
const currentLanguage = LOCALES.find(item => item.tag === i18n.language);

View File

@@ -1,7 +1,8 @@
import { Button, Input, Modal, ModalCloseButton } from '@affine/component';
import { Input, Modal, ModalCloseButton } from '@affine/component';
import { WorkspaceFlavour } from '@affine/env/workspace';
import { Trans } from '@affine/i18n';
import { useAFFiNEI18N } from '@affine/i18n/hooks';
import { Button } from '@toeverything/components/button';
import { useBlockSuiteWorkspaceName } from '@toeverything/hooks/use-block-suite-workspace-name';
import { useCallback, useState } from 'react';

View File

@@ -1,23 +1,29 @@
import { SettingRow } from '@affine/component/setting-components';
import { useAFFiNEI18N } from '@affine/i18n/hooks';
import { ArrowRightSmallIcon } from '@blocksuite/icons';
import { type FC, useState } from 'react';
import { useState } from 'react';
import type { AffineOfficialWorkspace } from '../../../../shared';
import type { WorkspaceSettingDetailProps } from '../index';
import { WorkspaceDeleteModal } from './delete';
import { WorkspaceLeave } from './leave';
export const DeleteLeaveWorkspace: FC<{
interface DeleteLeaveWorkspaceProps {
workspace: AffineOfficialWorkspace;
onDeleteWorkspace: WorkspaceSettingDetailProps['onDeleteWorkspace'];
}> = ({ workspace, onDeleteWorkspace }) => {
}
export const DeleteLeaveWorkspace = ({
workspace,
onDeleteWorkspace,
}: DeleteLeaveWorkspaceProps) => {
const t = useAFFiNEI18N();
// fixme: cloud regression
const isOwner = true;
const [showDelete, setShowDelete] = useState(false);
const [showLeave, setShowLeave] = useState(false);
return (
<>
<SettingRow

View File

@@ -1,7 +1,7 @@
import { Modal } from '@affine/component';
import { ModalCloseButton } from '@affine/component';
import { Button } from '@affine/component';
import { useAFFiNEI18N } from '@affine/i18n/hooks';
import { Button } from '@toeverything/components/button';
import {
StyledButtonContent,
@@ -38,7 +38,6 @@ export const WorkspaceLeave = ({ open, onClose }: WorkspaceDeleteProps) => {
<Button
onClick={handleLeave}
type="error"
shape="circle"
style={{ marginLeft: '24px' }}
>
{t['Leave']()}

View File

@@ -1,8 +1,10 @@
import { Button, toast } from '@affine/component';
import { toast } from '@affine/component';
import { SettingRow } from '@affine/component/setting-components';
import { isDesktop } from '@affine/env/constant';
import { useAFFiNEI18N } from '@affine/i18n/hooks';
import { type FC, useCallback } from 'react';
import { Button } from '@toeverything/components/button';
import type { SaveDBFileResult } from '@toeverything/infra/type';
import { useCallback } from 'react';
import type { AffineOfficialWorkspace } from '../../../shared';
@@ -27,21 +29,25 @@ async function syncBlobsToSqliteDb(workspace: AffineOfficialWorkspace) {
}
}
export const ExportPanel: FC<{
interface ExportPanelProps {
workspace: AffineOfficialWorkspace;
}> = ({ workspace }) => {
}
export const ExportPanel = ({ workspace }: ExportPanelProps) => {
const workspaceId = workspace.id;
const t = useAFFiNEI18N();
const onExport = useCallback(async () => {
await syncBlobsToSqliteDb(workspace);
const result = await window.apis?.dialog.saveDBFileAs(workspaceId);
const result: SaveDBFileResult = await window.apis?.dialog.saveDBFileAs(
workspaceId
);
if (result?.error) {
// @ts-expect-error: result.error is dynamic
toast(t[result.error]());
} else if (!result?.canceled) {
toast(t['Export success']());
}
}, [t, workspace, workspaceId]);
return (
<>
<SettingRow name={t['Export']()} desc={t['Export Description']()}>

View File

@@ -9,7 +9,7 @@ import type {
} from '@affine/env/workspace';
import { useAFFiNEI18N } from '@affine/i18n/hooks';
import { useBlockSuiteWorkspaceName } from '@toeverything/hooks/use-block-suite-workspace-name';
import { type FC, useMemo } from 'react';
import { useMemo } from 'react';
import { useWorkspace } from '../../../hooks/use-workspace';
import { DeleteLeaveWorkspace } from './delete-leave-workspace';
@@ -18,7 +18,7 @@ import { ProfilePanel } from './profile';
import { PublishPanel } from './publish';
import { StoragePanel } from './storage';
export type WorkspaceSettingDetailProps = {
export interface WorkspaceSettingDetailProps {
workspaceId: string;
onDeleteWorkspace: (id: string) => Promise<void>;
onTransferWorkspace: <
@@ -29,13 +29,13 @@ export type WorkspaceSettingDetailProps = {
to: To,
workspace: WorkspaceRegistry[From]
) => void;
};
}
export const WorkspaceSettingDetail: FC<WorkspaceSettingDetailProps> = ({
export const WorkspaceSettingDetail = ({
workspaceId,
onDeleteWorkspace,
...props
}) => {
}: WorkspaceSettingDetailProps) => {
const t = useAFFiNEI18N();
const workspace = useWorkspace(workspaceId);
const [name] = useBlockSuiteWorkspaceName(workspace.blockSuiteWorkspace);

View File

@@ -1,10 +1,11 @@
import { IconButton, Input, toast } from '@affine/component';
import { Input, toast } from '@affine/component';
import { WorkspaceAvatar } from '@affine/component/workspace-avatar';
import { useAFFiNEI18N } from '@affine/i18n/hooks';
import { DoneIcon } from '@blocksuite/icons';
import { IconButton } from '@toeverything/components/button';
import { useBlockSuiteWorkspaceAvatarUrl } from '@toeverything/hooks/use-block-suite-workspace-avatar-url';
import { useBlockSuiteWorkspaceName } from '@toeverything/hooks/use-block-suite-workspace-name';
import { type FC, useCallback, useState } from 'react';
import { useCallback, useState } from 'react';
import type { AffineOfficialWorkspace } from '../../../shared';
import { Upload } from '../../pure/file-upload';
@@ -29,9 +30,11 @@ const CameraIcon = () => {
);
};
export const ProfilePanel: FC<{
interface ProfilePanelProps {
workspace: AffineOfficialWorkspace;
}> = ({ workspace }) => {
}
export const ProfilePanel = ({ workspace }: ProfilePanelProps) => {
const t = useAFFiNEI18N();
const [, update] = useBlockSuiteWorkspaceAvatarUrl(

View File

@@ -1,4 +1,4 @@
import { Button, FlexWrapper, Switch, Tooltip } from '@affine/component';
import { FlexWrapper, Switch, Tooltip } from '@affine/component';
import { SettingRow } from '@affine/component/setting-components';
import { Unreachable } from '@affine/env/constant';
import type {
@@ -7,8 +7,8 @@ import type {
} from '@affine/env/workspace';
import { WorkspaceFlavour } from '@affine/env/workspace';
import { useAFFiNEI18N } from '@affine/i18n/hooks';
import { Button } from '@toeverything/components/button';
import { useBlockSuiteWorkspaceName } from '@toeverything/hooks/use-block-suite-workspace-name';
import type { FC } from 'react';
import { useCallback, useEffect, useState } from 'react';
import type { AffineOfficialWorkspace } from '../../../shared';
@@ -18,26 +18,20 @@ import { TmpDisableAffineCloudModal } from '../tmp-disable-affine-cloud-modal';
import type { WorkspaceSettingDetailProps } from './index';
import * as style from './style.css';
export type PublishPanelProps = Omit<
WorkspaceSettingDetailProps,
'workspaceId'
> & {
export interface PublishPanelProps
extends Omit<WorkspaceSettingDetailProps, 'workspaceId'> {
workspace: AffineOfficialWorkspace;
};
export type PublishPanelLocalProps = Omit<
WorkspaceSettingDetailProps,
'workspaceId'
> & {
}
export interface PublishPanelLocalProps
extends Omit<WorkspaceSettingDetailProps, 'workspaceId'> {
workspace: LocalWorkspace;
};
export type PublishPanelAffineProps = Omit<
WorkspaceSettingDetailProps,
'workspaceId'
> & {
}
export interface PublishPanelAffineProps
extends Omit<WorkspaceSettingDetailProps, 'workspaceId'> {
workspace: AffineCloudWorkspace;
};
}
const PublishPanelAffine: FC<PublishPanelAffineProps> = props => {
const PublishPanelAffine = (props: PublishPanelAffineProps) => {
const { workspace } = props;
const t = useAFFiNEI18N();
// const toggleWorkspacePublish = useToggleWorkspacePublish(workspace);
@@ -57,6 +51,7 @@ const PublishPanelAffine: FC<PublishPanelAffineProps> = props => {
await navigator.clipboard.writeText(shareUrl);
toast(t['Copied link to clipboard']());
}, [shareUrl, t]);
return (
<>
<SettingRow
@@ -90,10 +85,13 @@ const PublishPanelAffine: FC<PublishPanelAffineProps> = props => {
);
};
const FakePublishPanelAffine: FC<{
interface FakePublishPanelAffineProps {
workspace: AffineOfficialWorkspace;
}> = () => {
}
const FakePublishPanelAffine = (_props: FakePublishPanelAffineProps) => {
const t = useAFFiNEI18N();
return (
<Tooltip
content={t['com.affine.settings.workspace.publish.local-tooltip']()}
@@ -107,10 +105,11 @@ const FakePublishPanelAffine: FC<{
</Tooltip>
);
};
const PublishPanelLocal: FC<PublishPanelLocalProps> = ({
const PublishPanelLocal = ({
workspace,
onTransferWorkspace,
}) => {
}: PublishPanelLocalProps) => {
const t = useAFFiNEI18N();
const [name] = useBlockSuiteWorkspaceName(workspace.blockSuiteWorkspace);
@@ -166,7 +165,7 @@ const PublishPanelLocal: FC<PublishPanelLocalProps> = ({
);
};
export const PublishPanel: FC<PublishPanelProps> = props => {
export const PublishPanel = (props: PublishPanelProps) => {
if (props.workspace.flavour === WorkspaceFlavour.AFFINE_CLOUD) {
return <PublishPanelAffine {...props} workspace={props.workspace} />;
} else if (props.workspace.flavour === WorkspaceFlavour.LOCAL) {

View File

@@ -1,8 +1,10 @@
import { Button, FlexWrapper, toast, Tooltip } from '@affine/component';
import { FlexWrapper, toast, Tooltip } from '@affine/component';
import { SettingRow } from '@affine/component/setting-components';
import { useAFFiNEI18N } from '@affine/i18n/hooks';
import { Button } from '@toeverything/components/button';
import type { MoveDBFileResult } from '@toeverything/infra/type';
import { useMemo } from 'react';
import { type FC, useCallback, useEffect, useState } from 'react';
import { useCallback, useEffect, useState } from 'react';
import type { AffineOfficialWorkspace } from '../../../shared';
import * as style from './style.css';
@@ -30,9 +32,11 @@ const useDBFileSecondaryPath = (workspaceId: string) => {
return path;
};
export const StoragePanel: FC<{
interface StoragePanelProps {
workspace: AffineOfficialWorkspace;
}> = ({ workspace }) => {
}
export const StoragePanel = ({ workspace }: StoragePanelProps) => {
const workspaceId = workspace.id;
const t = useAFFiNEI18N();
const secondaryPath = useDBFileSecondaryPath(workspaceId);
@@ -51,11 +55,10 @@ export const StoragePanel: FC<{
setMoveToInProgress(true);
window.apis?.dialog
.moveDBFile(workspaceId)
.then(result => {
.then((result: MoveDBFileResult) => {
if (!result?.error && !result?.canceled) {
toast(t['Move folder success']());
} else if (result?.error) {
// @ts-expect-error: result.error is dynamic
toast(t[result.error]());
}
})

View File

@@ -1,12 +1,11 @@
import { TourModal } from '@affine/component/tour-modal';
import { useAtom } from 'jotai';
import type { FC } from 'react';
import { memo, useCallback } from 'react';
import { openOnboardingModalAtom } from '../../atoms';
import { guideOnboardingAtom } from '../../atoms/guide';
export const OnboardingModal: FC = memo(function OnboardingModal() {
export const OnboardingModal = memo(function OnboardingModal() {
const [open, setOpen] = useAtom(openOnboardingModalAtom);
const [guideOpen, setShowOnboarding] = useAtom(guideOnboardingAtom);
const onCloseTourModal = useCallback(() => {

View File

@@ -1,6 +1,6 @@
import { Menu, MenuItem, MenuTrigger } from '@affine/component';
import dayjs from 'dayjs';
import { type FC, useCallback } from 'react';
import { useCallback } from 'react';
import {
dateFormatOptions,
@@ -8,10 +8,15 @@ import {
useAppSetting,
} from '../../../../../atoms/settings';
const DateFormatMenuContent: FC<{
interface DateFormatMenuContentProps {
currentOption: DateFormats;
onSelect: (option: DateFormats) => void;
}> = ({ onSelect, currentOption }) => {
}
const DateFormatMenuContent = ({
onSelect,
currentOption,
}: DateFormatMenuContentProps) => {
return (
<>
{dateFormatOptions.map(option => {
@@ -30,6 +35,7 @@ const DateFormatMenuContent: FC<{
</>
);
};
export const DateFormatSetting = () => {
const [appearanceSettings, setAppSettings] = useAppSetting();
const handleSelect = useCallback(
@@ -38,6 +44,7 @@ export const DateFormatSetting = () => {
},
[setAppSettings]
);
return (
<Menu
content={

View File

@@ -1,11 +1,11 @@
import { useAFFiNEI18N } from '@affine/i18n/hooks';
import {
AiIcon,
AppearanceIcon,
InformationIcon,
KeyboardIcon,
PluginIcon,
} from '@blocksuite/icons';
import type { FC, SVGProps } from 'react';
import type { ReactElement, SVGProps } from 'react';
import { AboutAffine } from './about';
import { AppearanceSettings } from './appearance';
@@ -18,15 +18,18 @@ export type GeneralSettingKeys =
| 'plugins'
| 'about';
export type GeneralSettingList = {
interface GeneralSettingListItem {
key: GeneralSettingKeys;
title: string;
icon: FC<SVGProps<SVGSVGElement>>;
icon: (props: SVGProps<SVGSVGElement>) => ReactElement;
testId: string;
}[];
}
export type GeneralSettingList = GeneralSettingListItem[];
export const useGeneralSettingList = (): GeneralSettingList => {
const t = useAFFiNEI18N();
return [
{
key: 'appearance',
@@ -43,7 +46,7 @@ export const useGeneralSettingList = (): GeneralSettingList => {
{
key: 'plugins',
title: 'Plugins',
icon: AiIcon,
icon: PluginIcon,
testId: 'plugins-panel-trigger',
},
{
@@ -55,11 +58,11 @@ export const useGeneralSettingList = (): GeneralSettingList => {
];
};
export const GeneralSetting = ({
generalKey,
}: {
interface GeneralSettingProps {
generalKey: GeneralSettingKeys;
}) => {
}
export const GeneralSetting = ({ generalKey }: GeneralSettingProps) => {
switch (generalKey) {
case 'shortcuts':
return <Shortcuts />;

View File

@@ -1,55 +1,96 @@
import { Switch } from '@affine/component';
import { SettingHeader } from '@affine/component/setting-components';
import { useAFFiNEI18N } from '@affine/i18n/hooks';
import type { CallbackMap } from '@affine/sdk/entry';
import {
registeredPluginAtom,
settingItemsAtom,
} from '@toeverything/infra/atom';
import { useAtomValue } from 'jotai';
import type { FC, ReactNode } from 'react';
import { useRef } from 'react';
addCleanup,
enabledPluginAtom,
pluginPackageJson,
pluginSettingAtom,
} from '@toeverything/infra/__internal__/plugin';
import { loadedPluginNameAtom } from '@toeverything/infra/atom';
import type { packageJsonOutputSchema } from '@toeverything/infra/type';
import { useAtom, useAtomValue } from 'jotai/react';
import { startTransition, useCallback, useMemo } from 'react';
import type { z } from 'zod';
import { pluginItem } from './style.css';
import { pluginItemStyle } from './style.css';
const PluginSettingWrapper: FC<{
id: string;
title?: ReactNode;
}> = ({ title, id }) => {
const Setting = useAtomValue(settingItemsAtom)[id];
const disposeRef = useRef<(() => void) | null>(null);
type PluginItemProps = {
json: z.infer<typeof packageJsonOutputSchema>;
};
type PluginSettingDetailProps = {
pluginName: string;
create: CallbackMap['setting'];
};
const PluginSettingDetail = ({
pluginName,
create,
}: PluginSettingDetailProps) => {
return (
<div>
{title ? <div className="title">{title}</div> : null}
<div
ref={ref => {
if (ref && Setting) {
setTimeout(() => {
disposeRef.current = Setting(ref);
});
} else if (ref === null) {
setTimeout(() => {
disposeRef.current?.();
});
<div
ref={useCallback(
(ref: HTMLDivElement | null) => {
if (ref) {
const cleanup = create(ref);
addCleanup(pluginName, cleanup);
}
}}
/>
},
[pluginName, create]
)}
/>
);
};
const PluginItem = ({ json }: PluginItemProps) => {
const [plugins, setEnabledPlugins] = useAtom(enabledPluginAtom);
const checked = useMemo(
() => plugins.includes(json.name),
[json.name, plugins]
);
const create = useAtomValue(pluginSettingAtom)[json.name];
return (
<div className={pluginItemStyle} key={json.name}>
<div>
{json.name}
<Switch
checked={checked}
onChange={useCallback(
(checked: boolean) => {
startTransition(() => {
setEnabledPlugins(plugins => {
if (checked) {
return [...plugins, json.name];
} else {
return plugins.filter(plugin => plugin !== json.name);
}
});
});
},
[json.name, setEnabledPlugins]
)}
/>
</div>
<div>{json.description}</div>
{create && <PluginSettingDetail pluginName={json.name} create={create} />}
</div>
);
};
export const Plugins = () => {
const t = useAFFiNEI18N();
const allowedPlugins = useAtomValue(registeredPluginAtom);
const loadedPlugins = useAtomValue(loadedPluginNameAtom);
return (
<>
<SettingHeader
title={'Plugins'}
subtitle={allowedPlugins.length === 0 && t['None yet']()}
subtitle={loadedPlugins.length === 0 && t['None yet']()}
data-testid="plugins-title"
/>
{allowedPlugins.map(plugin => (
<div className={pluginItem} key={plugin}>
<PluginSettingWrapper key={plugin} id={plugin} title={plugin} />
</div>
{useAtomValue(pluginPackageJson).map(json => (
<PluginItem json={json} key={json.name} />
))}
</>
);

View File

@@ -1,6 +1,6 @@
import { style } from '@vanilla-extract/css';
export const settingWrapper = style({
export const settingWrapperStyle = style({
flexGrow: 1,
display: 'flex',
justifyContent: 'flex-end',
@@ -8,7 +8,7 @@ export const settingWrapper = style({
maxWidth: '250px',
});
export const pluginItem = style({
export const pluginItemStyle = style({
borderBottom: '1px solid var(--affine-border-color)',
transition: '0.3s',
padding: '24px 8px',

View File

@@ -1,10 +1,9 @@
import {
SettingModal as SettingModalBase,
type SettingModalProps,
type SettingModalProps as SettingModalBaseProps,
} from '@affine/component/setting-components';
import { useAFFiNEI18N } from '@affine/i18n/hooks';
import { ContactWithUsIcon } from '@blocksuite/icons';
import type React from 'react';
import { useCallback } from 'react';
import { AccountSetting } from './account-setting';
@@ -18,21 +17,25 @@ import { settingContent } from './style.css';
import { WorkspaceSetting } from './workspace-setting';
type ActiveTab = GeneralSettingKeys | 'workspace' | 'account';
export type SettingProps = {
export interface SettingProps {
activeTab: ActiveTab;
workspaceId: string | null;
onSettingClick: (params: {
activeTab: ActiveTab;
workspaceId: string | null;
}) => void;
};
export const SettingModal: React.FC<SettingModalProps & SettingProps> = ({
}
type SettingModalProps = SettingModalBaseProps & SettingProps;
export const SettingModal = ({
open,
setOpen,
activeTab = 'appearance',
workspaceId = null,
onSettingClick,
}) => {
}: SettingModalProps) => {
const t = useAFFiNEI18N();
const generalSettingList = useGeneralSettingList();

View File

@@ -1,4 +1,4 @@
import { Tooltip } from '@affine/component';
import { ScrollableContainer, Tooltip } from '@affine/component';
import {
WorkspaceListItemSkeleton,
WorkspaceListSkeleton,
@@ -11,7 +11,6 @@ import { useBlockSuiteWorkspaceName } from '@toeverything/hooks/use-block-suite-
import { useStaticBlockSuiteWorkspace } from '@toeverything/infra/__internal__/react';
import clsx from 'clsx';
import { useAtomValue } from 'jotai';
import type { FC } from 'react';
import { Suspense } from 'react';
import { useCurrentWorkspace } from '../../../../hooks/current/use-current-workspace';
@@ -28,21 +27,24 @@ import {
sidebarTitle,
} from './style.css';
export const SettingSidebar: FC<{
interface SettingSidebarProps {
generalSettingList: GeneralSettingList;
onGeneralSettingClick: (key: GeneralSettingKeys) => void;
onWorkspaceSettingClick: (workspaceId: string) => void;
selectedWorkspaceId: string | null;
selectedGeneralKey: string | null;
onAccountSettingClick: () => void;
}> = ({
}
export const SettingSidebar = ({
generalSettingList,
onGeneralSettingClick,
onWorkspaceSettingClick,
selectedWorkspaceId,
selectedGeneralKey,
}) => {
}: SettingSidebarProps) => {
const t = useAFFiNEI18N();
return (
<div className={settingSlideBar} data-testid="settings-sidebar">
<div className={sidebarTitle}>{t['Settings']()}</div>
@@ -76,20 +78,27 @@ export const SettingSidebar: FC<{
</div>
<div className={clsx(sidebarItemsWrapper, 'scroll')}>
<Suspense fallback={<WorkspaceListSkeleton />}>
<WorkspaceList
onWorkspaceSettingClick={onWorkspaceSettingClick}
selectedWorkspaceId={selectedWorkspaceId}
/>
<ScrollableContainer>
<WorkspaceList
onWorkspaceSettingClick={onWorkspaceSettingClick}
selectedWorkspaceId={selectedWorkspaceId}
/>
</ScrollableContainer>
</Suspense>
</div>
</div>
);
};
export const WorkspaceList: FC<{
interface WorkspaceListProps {
onWorkspaceSettingClick: (workspaceId: string) => void;
selectedWorkspaceId: string | null;
}> = ({ onWorkspaceSettingClick, selectedWorkspaceId }) => {
}
export const WorkspaceList = ({
onWorkspaceSettingClick,
selectedWorkspaceId,
}: WorkspaceListProps) => {
const workspaces = useAtomValue(rootWorkspacesMetadataAtom);
const [currentWorkspace] = useCurrentWorkspace();
return (
@@ -112,19 +121,22 @@ export const WorkspaceList: FC<{
);
};
interface WorkspaceListItemProps {
meta: RootWorkspaceMetadata;
onClick: () => void;
isCurrent: boolean;
isActive: boolean;
}
const WorkspaceListItem = ({
meta,
onClick,
isCurrent,
isActive,
}: {
meta: RootWorkspaceMetadata;
onClick: () => void;
isCurrent: boolean;
isActive: boolean;
}) => {
}: WorkspaceListItemProps) => {
const workspace = useStaticBlockSuiteWorkspace(meta.id);
const [workspaceName] = useBlockSuiteWorkspaceName(workspace);
return (
<div
className={clsx(sidebarSelectItem, { active: isActive })}

View File

@@ -4,7 +4,7 @@ export const settingSlideBar = style({
width: '25%',
maxWidth: '242px',
background: 'var(--affine-background-secondary-color)',
padding: '20px 16px',
padding: '20px 0px',
height: '100%',
flexShrink: 0,
display: 'flex',
@@ -15,14 +15,14 @@ export const sidebarTitle = style({
fontSize: 'var(--affine-font-h-6)',
fontWeight: '600',
lineHeight: 'var(--affine-line-height)',
paddingLeft: '8px',
padding: '0px 16px 0px 24px',
});
export const sidebarSubtitle = style({
fontSize: 'var(--affine-font-sm)',
lineHeight: 'var(--affine-line-height)',
color: 'var(--affine-text-secondary-color)',
paddingLeft: '8px',
padding: '0px 16px 0px 24px',
marginTop: '20px',
marginBottom: '4px',
display: 'flex',
@@ -34,7 +34,7 @@ export const sidebarItemsWrapper = style({
selectors: {
'&.scroll': {
flexGrow: 1,
overflowY: 'auto',
overflowY: 'hidden',
},
},
});
@@ -42,9 +42,9 @@ export const sidebarItemsWrapper = style({
export const sidebarSelectItem = style({
display: 'flex',
alignItems: 'center',
padding: '0 8px',
margin: '0px 16px 4px 16px',
padding: '0px 8px',
height: '30px',
marginBottom: '4px',
fontSize: 'var(--affine-font-sm)',
borderRadius: '8px',
cursor: 'pointer',

View File

@@ -32,11 +32,12 @@ globalStyle(`${settingContent} .footer`, {
globalStyle(`${settingContent} .footer a`, {
color: 'var(--affine-text-primary-color)',
lineHeight: 'normal',
});
globalStyle(`${settingContent} .footer > svg`, {
fontSize: 'var(--affine-font-base)',
color: 'var(--affine-icon-color)',
marginRight: '12px',
marginTop: '2px',
marginTop: '1px',
});

View File

@@ -1,8 +1,8 @@
import { Empty, IconButton, Modal, ModalWrapper } from '@affine/component';
import { Empty, Modal, ModalWrapper } from '@affine/component';
import { Trans } from '@affine/i18n';
import { useAFFiNEI18N } from '@affine/i18n/hooks';
import { CloseIcon } from '@blocksuite/icons';
import type React from 'react';
import { IconButton } from '@toeverything/components/button';
import {
Content,
@@ -19,10 +19,12 @@ interface TmpDisableAffineCloudModalProps {
onClose: () => void;
}
export const TmpDisableAffineCloudModal: React.FC<
TmpDisableAffineCloudModalProps
> = ({ open, onClose }) => {
export const TmpDisableAffineCloudModal = ({
open,
onClose,
}: TmpDisableAffineCloudModalProps) => {
const t = useAFFiNEI18N();
return (
<Modal
data-testid="disable-affine-cloud-modal"
@@ -66,7 +68,7 @@ export const TmpDisableAffineCloudModal: React.FC<
/>
</StyleImage>
<StyleButtonContainer>
<StyleButton shape="round" type="primary" onClick={onClose}>
<StyleButton type="primary" onClick={onClose}>
{t['Got it']()}
</StyleButton>
</StyleButtonContainer>

View File

@@ -1,4 +1,5 @@
import { Button, displayFlex, styled } from '@affine/component';
import { displayFlex, styled } from '@affine/component';
import { Button } from '@toeverything/components/button';
export const Header = styled('div')({
height: '44px',

View File

@@ -1,19 +1,21 @@
import { IconButton, Modal, ModalWrapper } from '@affine/component';
import { Modal, ModalWrapper } from '@affine/component';
import { useAFFiNEI18N } from '@affine/i18n/hooks';
import { CloseIcon } from '@blocksuite/icons';
import type React from 'react';
import { IconButton } from '@toeverything/components/button';
import { Content, ContentTitle, Header, StyleButton, StyleTips } from './style';
export type TransformWorkspaceToAffineModalProps = {
export interface TransformWorkspaceToAffineModalProps {
open: boolean;
onClose: () => void;
onConform: () => void;
};
}
export const TransformWorkspaceToAffineModal: React.FC<
TransformWorkspaceToAffineModalProps
> = ({ open, onClose, onConform }) => {
export const TransformWorkspaceToAffineModal = ({
open,
onClose,
onConform,
}: TransformWorkspaceToAffineModalProps) => {
const t = useAFFiNEI18N();
return (
@@ -35,7 +37,6 @@ export const TransformWorkspaceToAffineModal: React.FC<
<div>
<StyleButton
data-testid="confirm-enable-cloud-button"
shape="round"
type="primary"
onClick={onConform}
>

View File

@@ -1,4 +1,5 @@
import { Button, styled } from '@affine/component';
import { styled } from '@affine/component';
import { Button } from '@toeverything/components/button';
export const Header = styled('div')({
height: '44px',

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

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

View File

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

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

@@ -11,7 +11,6 @@ import { useBlockSuitePageMeta } from '@toeverything/hooks/use-block-suite-page-
import { useBlockSuitePagePreview } from '@toeverything/hooks/use-block-suite-page-preview';
import { useBlockSuiteWorkspacePage } from '@toeverything/hooks/use-block-suite-workspace-page';
import { useAtom, useAtomValue } from 'jotai';
import type React from 'react';
import { Suspense, useCallback, useMemo } from 'react';
import { allPageModeSelectAtom } from '../../../atoms';
@@ -23,13 +22,13 @@ import { filterPage } from '../../../utils/filter';
import { emptyDescButton, emptyDescKbd, pageListEmptyStyle } from './index.css';
import { usePageHelper } from './utils';
export type BlockSuitePageListProps = {
export interface BlockSuitePageListProps {
blockSuiteWorkspace: BlockSuiteWorkspace;
listType: 'all' | 'trash' | 'shared' | 'public';
isPublic?: true;
isPublic?: boolean;
onOpenPage: (pageId: string, newTab?: boolean) => void;
collection?: Collection;
};
}
const filter = {
all: (pageMeta: PageMeta) => !pageMeta.trash,
@@ -41,13 +40,12 @@ const filter = {
shared: (pageMeta: PageMeta) => pageMeta.isPublic && !pageMeta.trash,
};
const PagePreviewInner = ({
workspace,
pageId,
}: {
interface PagePreviewInnerProps {
workspace: Workspace;
pageId: string;
}) => {
}
const PagePreviewInner = ({ workspace, pageId }: PagePreviewInnerProps) => {
const page = useBlockSuiteWorkspacePage(workspace, pageId);
assertExists(page);
const previewAtom = useBlockSuitePagePreview(page);
@@ -55,13 +53,12 @@ const PagePreviewInner = ({
return preview;
};
const PagePreview = ({
workspace,
pageId,
}: {
interface PagePreviewProps {
workspace: Workspace;
pageId: string;
}) => {
}
const PagePreview = ({ workspace, pageId }: PagePreviewProps) => {
return (
<Suspense>
<PagePreviewInner workspace={workspace} pageId={pageId} />
@@ -69,10 +66,12 @@ const PagePreview = ({
);
};
const PageListEmpty = (props: {
interface PageListEmptyProps {
createPage?: ReturnType<typeof usePageHelper>['createPage'];
listType: BlockSuitePageListProps['listType'];
}) => {
}
const PageListEmpty = (props: PageListEmptyProps) => {
const { listType, createPage } = props;
const t = useAFFiNEI18N();
@@ -124,13 +123,13 @@ const PageListEmpty = (props: {
);
};
export const BlockSuitePageList: React.FC<BlockSuitePageListProps> = ({
export const BlockSuitePageList = ({
blockSuiteWorkspace,
onOpenPage,
listType,
isPublic = false,
collection,
}) => {
}: BlockSuitePageListProps) => {
const pageMetas = useBlockSuitePageMeta(blockSuiteWorkspace);
const {
toggleFavorite,
@@ -262,6 +261,7 @@ export const BlockSuitePageList: React.FC<BlockSuitePageListProps> = ({
},
};
});
return (
<PageList
workspaceId={blockSuiteWorkspace.id}

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,154 +0,0 @@
// fixme(himself65): refactor this file
import { FlexWrapper, IconButton, Menu, MenuItem } from '@affine/component';
import { Export, MoveToTrash } from '@affine/component/page-list';
import { useAFFiNEI18N } from '@affine/i18n/hooks';
import { assertExists } from '@blocksuite/global/utils';
import {
EdgelessIcon,
FavoritedIcon,
FavoriteIcon,
MoreVerticalIcon,
PageIcon,
} from '@blocksuite/icons';
import {
useBlockSuitePageMeta,
usePageMetaHelper,
} from '@toeverything/hooks/use-block-suite-page-meta';
import { currentPageIdAtom } from '@toeverything/infra/atom';
import { useAtom, useAtomValue } from 'jotai';
import { useCallback, useState } from 'react';
import { useParams } from 'react-router-dom';
import { pageSettingFamily } from '../../../../atoms';
import { useBlockSuiteMetaHelper } from '../../../../hooks/affine/use-block-suite-meta-helper';
import { useCurrentWorkspace } from '../../../../hooks/current/use-current-workspace';
import { toast } from '../../../../utils';
import { MenuThemeModeSwitch } from '../header-right-items/theme-mode-switch';
import { LanguageMenu } from './language-menu';
const CommonMenu = () => {
const content = (
<div
onClick={e => {
e.stopPropagation();
}}
>
<MenuThemeModeSwitch />
<LanguageMenu />
</div>
);
return (
<FlexWrapper alignItems="center" justifyContent="center">
<Menu
content={content}
placement="bottom"
disablePortal={true}
trigger="click"
>
<IconButton data-testid="editor-option-menu">
<MoreVerticalIcon />
</IconButton>
</Menu>
</FlexWrapper>
);
};
const PageMenu = () => {
const t = useAFFiNEI18N();
// fixme(himself65): remove these hooks ASAP
const [workspace] = useCurrentWorkspace();
const pageId = useAtomValue(currentPageIdAtom);
assertExists(workspace);
assertExists(pageId);
const blockSuiteWorkspace = workspace.blockSuiteWorkspace;
const pageMeta = useBlockSuitePageMeta(blockSuiteWorkspace).find(
meta => meta.id === pageId
);
assertExists(pageMeta);
const [setting, setSetting] = useAtom(pageSettingFamily(pageId));
const mode = setting?.mode ?? 'page';
const favorite = pageMeta.favorite ?? false;
const { setPageMeta } = usePageMetaHelper(blockSuiteWorkspace);
const [openConfirm, setOpenConfirm] = useState(false);
const { removeToTrash } = useBlockSuiteMetaHelper(blockSuiteWorkspace);
const handleFavorite = useCallback(() => {
setPageMeta(pageId, { favorite: !favorite });
toast(favorite ? t['Removed from Favorites']() : t['Added to Favorites']());
}, [favorite, pageId, setPageMeta, t]);
const handleSwitchMode = useCallback(() => {
setSetting(setting => ({
mode: setting?.mode === 'page' ? 'edgeless' : 'page',
}));
toast(
mode === 'page'
? t['com.affine.edgelessMode']()
: t['com.affine.pageMode']()
);
}, [mode, setSetting, t]);
const handleOnConfirm = useCallback(() => {
removeToTrash(pageMeta.id);
toast(t['Moved to Trash']());
setOpenConfirm(false);
}, [pageMeta.id, removeToTrash, t]);
const EditMenu = (
<>
<MenuItem
data-testid="editor-option-menu-favorite"
onClick={handleFavorite}
icon={
favorite ? (
<FavoritedIcon style={{ color: 'var(--affine-primary-color)' }} />
) : (
<FavoriteIcon />
)
}
>
{favorite ? t['Remove from favorites']() : t['Add to Favorites']()}
</MenuItem>
<MenuItem
icon={mode === 'page' ? <EdgelessIcon /> : <PageIcon />}
data-testid="editor-option-menu-edgeless"
onClick={handleSwitchMode}
>
{t['Convert to ']()}
{mode === 'page' ? t['Edgeless']() : t['Page']()}
</MenuItem>
<Export />
<MoveToTrash
data-testid="editor-option-menu-delete"
onItemClick={() => {
setOpenConfirm(true);
}}
/>
</>
);
return (
<>
<FlexWrapper alignItems="center" justifyContent="center">
<Menu
content={EditMenu}
placement="bottom-end"
disablePortal={true}
trigger="click"
>
<IconButton data-testid="editor-option-menu">
<MoreVerticalIcon />
</IconButton>
</Menu>
<MoveToTrash.ConfirmModal
open={openConfirm}
title={pageMeta.title}
onConfirm={handleOnConfirm}
onCancel={() => {
setOpenConfirm(false);
}}
/>
</FlexWrapper>
</>
);
};
export const EditorOptionMenu = () => {
const { pageId } = useParams();
return pageId ? <PageMenu /> : <CommonMenu />;
};

View File

@@ -1,135 +0,0 @@
import { Button, displayFlex, Menu, MenuItem, styled } from '@affine/component';
import { LOCALES } from '@affine/i18n';
import { useI18N } from '@affine/i18n';
import { ArrowDownSmallIcon, LanguageIcon } from '@blocksuite/icons';
import type { FC, ReactElement } from 'react';
import { useCallback } from 'react';
const LanguageMenuContent: FC = () => {
const i18n = useI18N();
const changeLanguage = useCallback(
(event: string) => {
i18n.changeLanguage(event).catch(err => {
console.error(err);
});
},
[i18n]
);
return (
<>
{LOCALES.map(option => {
return (
<StyledListItem
key={option.name}
title={option.name}
onClick={() => {
changeLanguage(option.tag);
}}
>
{option.originalName}
</StyledListItem>
);
})}
</>
);
};
export const LanguageMenu: React.FC = () => {
const i18n = useI18N();
const currentLanguage = LOCALES.find(item => item.tag === i18n.language);
return (
<StyledContainer>
<StyledIconContainer>
<LanguageIcon />
</StyledIconContainer>
<StyledButtonContainer>
<Menu
content={(<LanguageMenuContent />) as ReactElement}
placement="bottom"
trigger="click"
disablePortal={true}
>
<StyledButton
type="plain"
icon={
<StyledArrowDownContainer>
<ArrowDownSmallIcon />
</StyledArrowDownContainer>
}
iconPosition="end"
data-testid="language-menu-button"
>
<StyledCurrentLanguage>
{currentLanguage?.originalName}
</StyledCurrentLanguage>
</StyledButton>
</Menu>
</StyledButtonContainer>
</StyledContainer>
);
};
const StyledListItem = styled(MenuItem)(() => ({
width: '132px',
height: '38px',
fontSize: 'var(--affine-font-base)',
textTransform: 'capitalize',
}));
const StyledContainer = styled('div')(() => {
return {
width: '100%',
height: '48px',
backgroundColor: 'transparent',
...displayFlex('flex-start', 'center'),
padding: '0 14px',
};
});
const StyledIconContainer = styled('div')(() => {
return {
width: '20px',
height: '20px',
color: 'var(--affine-icon-color)',
fontSize: '20px',
...displayFlex('flex-start', 'center'),
};
});
const StyledButtonContainer = styled('div')(() => {
return {
width: '100%',
height: '32px',
borderRadius: '4px',
border: `1px solid var(--affine-border-color)`,
backgroundColor: 'transparent',
...displayFlex('flex-start', 'center'),
marginLeft: '12px',
};
});
const StyledButton = styled(Button)(() => {
return {
width: '100%',
height: '32px',
borderRadius: '4px',
backgroundColor: 'transparent',
...displayFlex('space-between', 'center'),
textTransform: 'capitalize',
padding: '0',
};
});
const StyledArrowDownContainer = styled('div')(() => {
return {
height: '32px',
borderLeft: `1px solid var(--affine-border-color)`,
backgroundColor: 'transparent',
...displayFlex('flex-start', 'center'),
padding: '4px 6px',
fontSize: '24px',
};
});
const StyledCurrentLanguage = styled('div')(() => {
return {
marginLeft: '12px',
color: 'var(--affine-text-color)',
};
});

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,235 +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 { headerItemsAtom } from '@toeverything/infra/atom';
import { useAtom, useAtomValue } from 'jotai';
import type { FC, HTMLAttributes, PropsWithChildren, ReactNode } from 'react';
import {
forwardRef,
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 type BaseHeaderProps<
Workspace extends AffineOfficialWorkspace = AffineOfficialWorkspace,
> = {
workspace: Workspace;
currentPage: Page | null;
isPublic: boolean;
leftSlot?: ReactNode;
};
export enum HeaderRightItemName {
EditorOptionMenu = 'editorOptionMenu',
// some windows only items
WindowsAppControls = 'windowsAppControls',
}
type HeaderItem = {
Component: FC<BaseHeaderProps>;
// 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: (_, currentPage, { isPublic }) => {
return (
!isPublic && currentPage?.meta.trash !== true && currentPage !== null
);
},
},
[HeaderRightItemName.WindowsAppControls]: {
Component: () => {
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>
);
},
availableWhen: () => {
return isDesktop && globalThis.platform === 'win32';
},
},
};
export type HeaderProps = BaseHeaderProps;
const PluginHeader = () => {
const rootRef = useRef<HTMLDivElement>(null);
const headerItems = useAtomValue(headerItemsAtom);
useEffect(() => {
const root = rootRef.current;
if (!root) {
return;
}
let disposes: (() => void)[] = [];
const renderTimeout = setTimeout(() => {
disposes = Object.entries(headerItems).map(([id, headerItem]) => {
const div = document.createElement('div');
div.setAttribute('plugin-id', id);
const cleanup = headerItem(div);
root.appendChild(div);
return () => {
cleanup();
root.removeChild(div);
};
});
});
return () => {
clearTimeout(renderTimeout);
setTimeout(() => {
disposes.forEach(dispose => dispose());
});
};
}, [headerItems]);
return <div className={styles.pluginHeaderItems} ref={rootRef} />;
};
export const Header = forwardRef<
HTMLDivElement,
PropsWithChildren<HeaderProps> & HTMLAttributes<HTMLDivElement>
>((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);
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'}
>
<div className={styles.headerLeftSide}>
{!open && <SidebarSwitch />}
{props.leftSlot}
</div>
{props.children}
<div className={styles.headerRightSide}>
<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>
</div>
</div>
);
});
Header.displayName = 'Header';

View File

@@ -1,114 +0,0 @@
import { Button } from '@affine/component';
import { assertExists } from '@blocksuite/global/utils';
import {
useBlockSuitePageMeta,
usePageMetaHelper,
} from '@toeverything/hooks/use-block-suite-page-meta';
import { useSetAtom } from 'jotai';
import type {
FC,
HTMLAttributes,
PropsWithChildren,
ReactElement,
} from 'react';
import { useCallback, useRef, useState } from 'react';
import { openQuickSearchModalAtom } from '../../../atoms';
import { QuickSearchButton } from '../../pure/quick-search-button';
import { EditorModeSwitch } from './editor-mode-switch';
import type { BaseHeaderProps } from './header';
import { Header } from './header';
import * as styles from './styles.css';
export type WorkspaceHeaderProps = BaseHeaderProps;
export const BlockSuiteEditorHeader: FC<
PropsWithChildren<WorkspaceHeaderProps> & HTMLAttributes<HTMLDivElement>
> = (props): ReactElement => {
const { workspace, currentPage, children, isPublic } = props;
// fixme(himself65): remove this atom and move it to props
const setOpenQuickSearch = useSetAtom(openQuickSearchModalAtom);
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 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>
{isEditable ? (
<div>
<input
autoFocus={true}
className={styles.title}
type="text"
data-testid="title-content"
defaultValue={pageMeta?.title}
onBlur={handleClick}
ref={inputRef}
/>
<Button
onClick={handleClick}
data-testid="save-edit-button"
style={{
marginLeft: '12px',
}}
>
Save
</Button>
</div>
) : (
<span
data-testid="title-edit-button"
onClick={handleClick}
style={{ cursor: 'pointer' }}
>
{title || 'Untitled'}
</span>
)}
</div>
<div className={styles.searchArrowWrapper}>
<QuickSearchButton
onClick={() => {
setOpenQuickSearch(true);
}}
/>
</div>
</div>
</div>
)}
</Header>
);
};
BlockSuiteEditorHeader.displayName = 'BlockSuiteEditorHeader';

View File

@@ -1,256 +0,0 @@
import type { ComplexStyleRule } from '@vanilla-extract/css';
import { style } from '@vanilla-extract/css';
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({
flexShrink: 0,
height: '52px',
width: '100%',
padding: '0 20px',
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
background: 'var(--affine-background-primary-color)',
zIndex: 99,
position: 'relative',
selectors: {
'&[data-is-edgeless="true"]': {
borderBottom: `1px solid var(--affine-border-color)`,
},
},
});
export const titleContainer = style({
width: '100%',
height: '100%',
margin: 'auto',
position: 'absolute',
inset: 'auto auto auto 50%',
transform: 'translate(-50%, 0px)',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
alignContent: 'unset',
fontSize: 'var(--affine-font-base)',
});
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 titleWrapper = style({
height: '100%',
position: 'relative',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
});
export const headerLeftSide = style({
display: 'flex',
alignItems: 'center',
width: '150px',
'@media': {
'(max-width: 900px)': {
width: 'auto',
},
},
});
export const headerRightSide = style({
height: '100%',
display: 'flex',
alignItems: 'center',
gap: '12px',
zIndex: 1,
justifyContent: 'flex-end',
});
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({
position: 'absolute',
right: '100%',
top: 0,
bottom: 0,
margin: 'auto',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
});
export const searchArrowWrapper = style({
position: 'absolute',
left: 'calc(100% + 4px)',
top: 0,
bottom: 0,
margin: 'auto',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
});
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',
'::after': {
content: '""',
display: 'block',
width: '100%',
height: '1px',
background: 'var(--affine-border-color)',
position: 'absolute',
bottom: 0,
left: 0,
margin: '0 1px',
},
});
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%',
});
export const windowAppControl = style({
WebkitAppRegion: 'no-drag',
cursor: 'pointer',
display: 'inline-flex',
width: '42px',
height: 'calc(100% - 10px)',
paddingTop: '10px',
alignItems: 'center',
justifyContent: 'center',
borderRadius: '0',
selectors: {
'&[data-type="close"]': {
width: '56px',
paddingRight: '14px',
marginRight: '-14px',
},
'&[data-type="close"]:hover': {
background: 'var(--affine-error-color)',
color: '#FFFFFF',
},
'&:hover': {
background: 'var(--affine-background-tertiary-color)',
},
},
} as ComplexStyleRule);
export const pluginHeaderItems = style({
display: 'flex',
gap: '12px',
alignItems: 'center',
height: '100%',
});

View File

@@ -1,7 +1,7 @@
import './page-detail-editor.css';
import { PageNotFoundError } from '@affine/env/constant';
import type { CallbackMap, LayoutNode } from '@affine/sdk//entry';
import type { LayoutNode } from '@affine/sdk//entry';
import { rootBlockHubAtom } from '@affine/workspace/atom';
import type { EditorContainer } from '@blocksuite/editor';
import { assertExists } from '@blocksuite/global/utils';
@@ -9,31 +9,31 @@ import type { Page, Workspace } from '@blocksuite/store';
import { useBlockSuitePageMeta } from '@toeverything/hooks/use-block-suite-page-meta';
import { useBlockSuiteWorkspacePage } from '@toeverything/hooks/use-block-suite-workspace-page';
import {
contentLayoutAtom,
editorItemsAtom,
rootStore,
windowItemsAtom,
} from '@toeverything/infra/atom';
addCleanup,
pluginEditorAtom,
pluginWindowAtom,
} from '@toeverything/infra/__internal__/plugin';
import { contentLayoutAtom, rootStore } from '@toeverything/infra/atom';
import clsx from 'clsx';
import { useAtomValue, useSetAtom } from 'jotai';
import type { CSSProperties, FC, ReactElement } from 'react';
import { memo, Suspense, useCallback, useEffect, useMemo, useRef } from 'react';
import type { CSSProperties, ReactElement } from 'react';
import { memo, 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 type PageDetailEditorProps = {
export interface PageDetailEditorProps {
isPublic?: boolean;
workspace: Workspace;
pageId: string;
onInit: (page: Page, editor: Readonly<EditorContainer>) => void;
onLoad?: (page: Page, editor: EditorContainer) => () => void;
};
}
const EditorWrapper = memo(function EditorWrapper({
workspace,
@@ -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(
@@ -96,7 +95,7 @@ const EditorWrapper = memo(function EditorWrapper({
if (onLoad) {
dispose = onLoad(page, editor);
}
const editorItems = rootStore.get(editorItemsAtom);
const editorItems = rootStore.get(pluginEditorAtom);
let disposes: (() => void)[] = [];
const renderTimeout = setTimeout(() => {
disposes = Object.entries(editorItems).map(([id, editorItem]) => {
@@ -128,53 +127,51 @@ const EditorWrapper = memo(function EditorWrapper({
);
});
const PluginContentAdapter = memo<{
windowItem: CallbackMap['window'];
}>(function PluginContentAdapter({ windowItem }) {
const ref = useRef<HTMLDivElement>(null);
useEffect(() => {
const root = ref.current;
if (!root) {
return;
}
let cleanup: () => void = () => {};
let childDiv: HTMLDivElement | null = null;
const renderTimeout = setTimeout(() => {
const div = document.createElement('div');
cleanup = windowItem(div);
root.appendChild(div);
childDiv = div;
});
interface PluginContentAdapterProps {
windowItem: (div: HTMLDivElement) => () => void;
pluginName: string;
}
return () => {
clearTimeout(renderTimeout);
setTimeout(() => {
cleanup();
if (childDiv) {
root.removeChild(childDiv);
}
});
};
}, [windowItem]);
return <div className={pluginContainer} ref={ref} />;
});
const PluginContentAdapter = memo<PluginContentAdapterProps>(
function PluginContentAdapter({ windowItem, pluginName }) {
return (
<div
className={pluginContainer}
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);
});
}
},
[pluginName, windowItem]
)}
/>
);
}
);
type LayoutPanelProps = {
interface LayoutPanelProps {
node: LayoutNode;
editorProps: PageDetailEditorProps;
};
}
const LayoutPanel = memo(function LayoutPanel(
props: LayoutPanelProps
): ReactElement {
const node = props.node;
const windowItems = useAtomValue(windowItemsAtom);
const windowItems = useAtomValue(pluginWindowAtom);
if (typeof node === 'string') {
if (node === 'editor') {
return <EditorWrapper {...props.editorProps} />;
} else {
const windowItem = windowItems[node];
return <PluginContentAdapter windowItem={windowItem} />;
return <PluginContentAdapter pluginName={node} windowItem={windowItem} />;
}
} else {
return (
@@ -205,7 +202,7 @@ const LayoutPanel = memo(function LayoutPanel(
}
});
export const PageDetailEditor: FC<PageDetailEditorProps> = props => {
export const PageDetailEditor = (props: PageDetailEditorProps) => {
const { workspace, pageId } = props;
const page = useBlockSuiteWorkspacePage(workspace, pageId);
if (!page) {

View File

@@ -1,22 +1,21 @@
import { Button } from '@affine/component';
import { styled } from '@affine/component';
import { useAFFiNEI18N } from '@affine/i18n/hooks';
import type { ChangeEvent } from 'react';
import type React from 'react';
import { Button } from '@toeverything/components/button';
import type { ChangeEvent, PropsWithChildren } from 'react';
import { useRef } from 'react';
export type UploadProps = React.PropsWithChildren<{
export interface UploadProps {
uploadType?: string;
accept?: string;
fileChange: (file: File) => void;
}>;
}
export const Upload: React.FC<UploadProps> = ({
export const Upload = ({
fileChange,
accept,
children,
...props
}) => {
}: PropsWithChildren<UploadProps>) => {
const t = useAFFiNEI18N();
const input_ref = useRef<HTMLInputElement>(null);
const _chooseFile = () => {
@@ -35,6 +34,7 @@ export const Upload: React.FC<UploadProps> = ({
input_ref.current.value = '';
}
};
return (
<UploadStyle onClick={_chooseFile}>
{children ?? <Button>{t['Upload']()}</Button>}

View File

@@ -1,15 +1,16 @@
import { useAFFiNEI18N } from '@affine/i18n/hooks';
import { CloudWorkspaceIcon } from '@blocksuite/icons';
import { useSetAtom } from 'jotai';
import { type CSSProperties, type FC, forwardRef } from 'react';
import { type CSSProperties, forwardRef } from 'react';
import { openDisableCloudAlertModalAtom } from '../../../atoms';
import { stringToColour } from '../../../utils';
import { StyledFooter, StyledSignInButton } from './styles';
export const Footer: FC = () => {
export const Footer = () => {
const t = useAFFiNEI18N();
const setOpen = useSetAtom(openDisableCloudAlertModalAtom);
return (
<StyledFooter data-testid="workspace-list-modal-footer">
<StyledSignInButton

View File

@@ -4,17 +4,7 @@ import {
styled,
textEllipsis,
} from '@affine/component';
import { Button } from '@affine/component';
export const StyledSplitLine = styled('div')(() => {
return {
width: '1px',
height: '20px',
background: 'var(--affine-border-color)',
marginRight: '24px',
};
});
import { Button } from '@toeverything/components/button';
export const StyleWorkspaceInfo = styled('div')(() => {
return {
marginLeft: '15px',

View File

@@ -1,7 +1,9 @@
import type { IconButtonProps } from '@affine/component';
import { IconButton } from '@affine/component';
import { styled } from '@affine/component';
import { ArrowDownSmallIcon } from '@blocksuite/icons';
import {
IconButton,
type IconButtonProps,
} from '@toeverything/components/button';
const StyledIconButtonWithAnimate = styled(IconButton)(() => {
return {
@@ -12,21 +14,19 @@ const StyledIconButtonWithAnimate = styled(IconButton)(() => {
svg: {
transform: 'translateY(3px)',
},
'::after': {
background: 'var(--affine-background-primary-color)',
},
backgroundColor: 'transparent !important',
},
};
});
// fixme(himself65): need to refactor
export const QuickSearchButton = ({
export const HeaderDropDownButton = ({
onClick,
...props
}: Omit<IconButtonProps, 'children'>) => {
return (
<StyledIconButtonWithAnimate
data-testid="header-quickSearchButton"
data-testid="header-dropDownButton"
{...props}
onClick={e => {
onClick?.(e);

View File

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

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%',
position: 'absolute',
transform: 'translateX(-50%)',
left: '50%',
zIndex: 1,
selectors: {
'&.is-window': {
maxWidth: '50%',
},
'&.is-window.has-min-width': {
minWidth: '400px',
},
'&.shadow': {
position: 'static',
visibility: 'hidden',
},
},
});
export const headerSideContainer = style({
display: 'flex',
flexShrink: 0,
alignItems: 'center',
selectors: {
'&.right': {
flexDirection: 'row-reverse',
},
'&.block': {
display: 'block',
},
},
});
export const windowAppControlsWrapper = style({
display: 'flex',
marginLeft: '20px',
});
export const windowAppControl = style({
WebkitAppRegion: 'no-drag',
cursor: 'pointer',
display: 'inline-flex',
width: '52px',
height: '52px',
alignItems: 'center',
justifyContent: 'center',
borderRadius: '0',
selectors: {
'&[data-type="close"]': {
width: '56px',
paddingRight: '5px',
marginRight: '-12px',
},
'&[data-type="close"]:hover': {
background: 'var(--affine-windows-close-button)',
color: 'var(--affine-pure-white)',
},
'&:hover': {
background: 'var(--affine-hover-color)',
},
},
} as ComplexStyleRule);

View File

@@ -1,12 +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 type React from 'react';
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
@@ -23,7 +27,7 @@ export const shouldShowWarning = () => {
}
};
export const OSWarningMessage: React.FC = () => {
const OSWarningMessage = () => {
const t = useAFFiNEI18N();
const [notChrome, setNotChrome] = useState(false);
const [notGoodVersion, setNotGoodVersion] = useState(false);
@@ -50,3 +54,34 @@ export const OSWarningMessage: React.FC = () => {
}
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,7 +57,7 @@ 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">

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

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