Compare commits

...

60 Commits

Author SHA1 Message Date
Alex Yang b944d80fa8 chore: bump version 2023-08-18 00:39:44 -05:00
Alex Yang 1f563c7cca v0.8.0-beta.0 2023-08-18 00:24:03 -05:00
Alex Yang 78caa7cebc chore: update changelog url (#3823)
(cherry picked from commit 55c512942d)
2023-08-17 23:44:45 -05:00
Alex Yang 6a59320db3 fix: cleanup editor layout (#3822)
(cherry picked from commit 71cf36a300)
2023-08-17 23:38:45 -05:00
Peng Xiao 04b174f7b7 fix: disable updater for internal (#3819)
(cherry picked from commit e4e17ff606)
2023-08-17 23:38:45 -05:00
Alex Yang ff843b450a chore: bump version (#3816)
(cherry picked from commit f1cb2fc6d6)
2023-08-17 21:35:38 -05:00
Alex Yang 41cdb411a0 chore: bump version (#3815)
(cherry picked from commit 96b64e1c78)
2023-08-17 21:35:38 -05:00
JimmFly 36e59d84fa fix: wrong cascading relationship (#3800)
(cherry picked from commit 4d58f2b4c7)
2023-08-17 21:35:37 -05:00
fourdim 1337943917 chore: update the top tip (#3797)
Co-authored-by: Alex Yang <himself65@outlook.com>
(cherry picked from commit ab9452969b)
2023-08-17 21:35:37 -05:00
JimmFly b118335a27 chore: adjust preloading tags (#3803)
(cherry picked from commit aea508573b)
2023-08-17 21:35:37 -05:00
Peng Xiao 76c3d4d814 fix: app sidebar ui issues (#3783)
Co-authored-by: Alex Yang <himself65@outlook.com>
(cherry picked from commit 068c697be9)
2023-08-17 21:35:36 -05:00
Qi 791c70d66f feat: modify shortcut key style (#3807)
(cherry picked from commit 7a31089c4b)
2023-08-17 21:35:36 -05:00
Alex Yang 5a2d9426ed build: fix turbosnap rootDir
(cherry picked from commit d50fcaa94e)
2023-08-17 21:35:36 -05:00
danielchim 0fb5c9c7b9 fix: workspace dropdown fix (#3808)
(cherry picked from commit 7f8dfc17a0)
2023-08-17 21:35:35 -05:00
Hongtao Lye c664a0f071 fix: toc tooltip (#3812)
(cherry picked from commit fb47a04f55)
2023-08-17 21:35:35 -05:00
Alex Yang dc6c3809e7 fix(core): cleanup layout when switch page (#3794)
(cherry picked from commit da3dd1e324)
2023-08-17 21:35:35 -05:00
Alex Yang cf2cca86a3 fix(core): editor height incorrect (#3799)
(cherry picked from commit c3e465d644)
2023-08-17 21:35:35 -05:00
Mirone b4ccd808fd chore: bump blocksuite version (#3798)
(cherry picked from commit d8d6620c5f)
2023-08-17 21:35:34 -05:00
Alex Yang 08e7d75ddd fix: disable unstable snapshot (#3791)
(cherry picked from commit 9853d0f6ef)
2023-08-17 21:35:34 -05:00
Alex Yang bbddd2ef70 build: fix file ignore
(cherry picked from commit ef7ad4f111)
2023-08-16 20:09:36 -05:00
danielchim ca6c0519d0 feat: new workspace switch dropdown design (#3700)
Co-authored-by: Alex Yang <himself65@outlook.com>
(cherry picked from commit 9ab9c0c70d)
2023-08-16 17:40:44 -05:00
Alex Yang d3baf5a401 fix(core): correct the suspense behavior (#3789)
(cherry picked from commit f369ca39f7)
2023-08-16 17:40:44 -05:00
Rohit Yadav a83e16fdca fix(core): unused z-index (#3781)
Co-authored-by: Alex Yang <himself65@outlook.com>
(cherry picked from commit 804b8f38b8)
2023-08-16 17:40:44 -05:00
Alex Yang 82f94b0294 docs: rename to upstreams section
(cherry picked from commit dd23917e3e)
2023-08-16 17:40:43 -05:00
Alex Yang c23d0dd917 docs: update README.md
(cherry picked from commit b604d9b47e)
2023-08-16 17:40:43 -05:00
Alex Yang 85f670e02e feat(storybook): improve code (#3786)
(cherry picked from commit 1e5a4a6849)
2023-08-16 17:40:43 -05:00
LongYinan 51ced217db fix(native): static link msvc runtime on Windows (#3773)
Co-authored-by: Alex Yang <himself65@outlook.com>
(cherry picked from commit 64656c3c98)
2023-08-16 17:40:43 -05:00
Alex Yang efcd106ea1 chore: bump version (#3784)
(cherry picked from commit 61ba85e1f3)
2023-08-16 17:40:43 -05:00
Peng Xiao 2dbee6b3eb fix: ignore some files to be bundled (#3770)
(cherry picked from commit 61ffc4220c)
2023-08-16 17:40:43 -05:00
danielchim abf743ccd1 fix: tooltip arrow (#3769)
Co-authored-by: Alex Yang <himself65@outlook.com>
(cherry picked from commit 866408015e)
2023-08-16 17:40:42 -05:00
Alex Yang bda913e334 chore: bump version (#3771)
Co-authored-by: Mirone <Saul-Mirone@outlook.com>
(cherry picked from commit 651e815b42)
2023-08-16 17:40:42 -05:00
Alex Yang d3240a2787 ci: add environment
(cherry picked from commit 645a300112)
2023-08-16 17:40:42 -05:00
Peng Xiao dda087de08 fix: disable secondary db test (#3774)
(cherry picked from commit e0a3c7f2bc)
2023-08-16 17:40:42 -05:00
Alex Yang 555feb59d1 feat(storybook): import plugins (#3768)
(cherry picked from commit 3dbefda6ed)
2023-08-16 17:40:42 -05:00
Alex Yang ab70ab2126 feat: add outline plugin (#3624)
Co-authored-by: codert <codert.sn@gmail.com>
(cherry picked from commit 6f9dfcc3c1)
2023-08-16 17:40:26 -05:00
Alex Yang 7f7bf6fef9 ci: checkout pull request ref
(cherry picked from commit 93d352f3d8)
2023-08-16 17:40:26 -05:00
Alex Yang 01de16a3ae ci: add name
(cherry picked from commit 7546b080ea)
2023-08-16 17:40:26 -05:00
Alex Yang 28779c73c2 ci: publish storybook on push to master
(cherry picked from commit 6988b6f034)
2023-08-16 17:40:26 -05:00
Alex Yang 46b5d2bf1a ci: add publish-storybook.yml
(cherry picked from commit de2cb1a3bc)
2023-08-16 17:40:25 -05:00
KaranPant 5f8084137d fix: add min height to footer (#3717)
(cherry picked from commit 08f01ea1b3)
2023-08-16 17:40:25 -05:00
Alex Yang 936f588db4 feat(storybook): add not found page (#3767)
(cherry picked from commit 0df30e43c6)
2023-08-16 17:40:25 -05:00
Alex Yang 3ec108b60c feat(storybook): preview app (#3765)
(cherry picked from commit 67b33d9b8f)
2023-08-16 17:40:25 -05:00
Alex Yang dbbd83dd1e fix(core): default page mode (#3745)
(cherry picked from commit 42dfd0a4bb)
2023-08-16 17:40:24 -05:00
Alex Yang 7e83593d5e feat: add chromatic (#3764)
(cherry picked from commit 25052220a4)
2023-08-16 17:40:24 -05:00
Qi d4fa24a4b0 fix: wrong style of cancel button in create workspace modal (#3761)
(cherry picked from commit 48e96cd399)
2023-08-16 17:40:24 -05:00
JimmFly 05b28e386f chore: adjust preloading page (#3753)
(cherry picked from commit ca016f1dd1)
2023-08-16 17:40:24 -05:00
Qi 2694891574 fix: ui issues (#3755)
(cherry picked from commit a4fe7dd119)
2023-08-16 17:40:24 -05:00
JimmFly 044ea4ae64 chore: update en.json (#3754)
(cherry picked from commit 8d2df468ee)
2023-08-16 17:40:24 -05:00
Peng Xiao 6ded3664ea fix: show recursive items (#3750)
(cherry picked from commit 2830cb19fe)
2023-08-16 17:40:24 -05:00
Alex Yang c64cce61f9 fix(electron): type on handlers (#3747)
(cherry picked from commit 8487b2c4af)
2023-08-16 17:40:24 -05:00
Alex Yang 207343c923 fix(core): first page (#3744)
(cherry picked from commit 623fa87d5c)
2023-08-14 20:38:20 -04:00
Alex Yang 296092323a docs: update badge in README.md (#3743)
(cherry picked from commit 4ad50bf8cf)
2023-08-14 20:38:20 -04:00
Alex Yang 314f126e4f chore: bump version (#3742)
(cherry picked from commit efd02a015a)
2023-08-14 20:38:20 -04:00
Qi 04172c5b04 fix: ui issues (#3738)
Co-authored-by: Alex Yang <himself65@outlook.com>
(cherry picked from commit 75a2bbdfac)
2023-08-14 20:38:19 -04:00
Quincy Qiu 90f3fe0e29 fix(plugin): allow multiple loads assets (#3741)
(cherry picked from commit 52102ee792)
2023-08-14 20:38:19 -04:00
Qi d9cce14b1f fix: error style of empty page (#3733)
(cherry picked from commit 58dae07b5f)
2023-08-14 20:38:19 -04:00
Qi 4e6af63751 fix: shaky header (#3727)
(cherry picked from commit d0e33c748b)
2023-08-14 20:38:19 -04:00
Peng Xiao dd7e701276 fix: allow multiple versions to be installed on windows (#3740)
(cherry picked from commit 08da58aa1e)
2023-08-14 20:38:19 -04:00
JimmFly d158c5a0dc chore: adjust translation (#3734)
(cherry picked from commit 1072db632e)
2023-08-14 20:38:19 -04:00
Alex Yang 8dd491784d v0.8.0 2023-08-13 22:09:17 -04:00
181 changed files with 6854 additions and 4852 deletions
+2
View File
@@ -0,0 +1,2 @@
[target.x86_64-pc-windows-msvc]
rustflags = ["-C", "target-feature=+crt-static"]
-22
View File
@@ -235,28 +235,6 @@ jobs:
name: affine
fail_ci_if_error: false
storybook-test:
name: Storybook Test
runs-on: ubuntu-latest
environment: development
needs: [build-storybook]
steps:
- uses: actions/checkout@v3
- name: Setup Node.js
uses: ./.github/actions/setup-node
with:
playwright-install: true
electron-install: false
- name: Download storybook artifact
uses: actions/download-artifact@v3
with:
name: storybook
path: ./apps/storybook/storybook-static
- name: Run storybook tests
working-directory: ./apps/storybook
run: |
yarn exec concurrently -k -s first -n "SB,TEST" -c "magenta,blue" "yarn exec serve ./storybook-static -l 6006" "yarn exec wait-on tcp:6006 && yarn test"
e2e-plugin-test:
name: E2E Plugin Test
runs-on: ubuntu-latest
+38
View File
@@ -0,0 +1,38 @@
name: Publish Storybook
on:
push:
branches:
- master
pull_request_target:
branches:
- master
paths-ignore:
- README.md
- .github/**
- '!.github/workflows/publish-storybook.yml'
jobs:
publish-storybook:
name: Publish Storybook
runs-on: ubuntu-latest
environment: development
steps:
- uses: actions/checkout@v3
with:
ref: ${{ github.event.pull_request.merge_commit_sha }}
# This is required to fetch all commits for chromatic
fetch-depth: 0
- name: Setup Node.js
uses: ./.github/actions/setup-node
with:
electron-install: false
- name: Build Plugins
run: yarn run build:plugins
- name: Publish to Chromatic
uses: chromaui/action@v1
with:
workingDir: apps/storybook
buildScriptName: build
onlyChanged: true
projectToken: ${{ secrets.CHROMATIC_PROJECT_TOKEN }}
+10 -4
View File
@@ -127,7 +127,7 @@ If you have questions, you are welcome to contact us. One of the best places to
| [@affine/copilot-plugin](plugins/copilot) | AI Copilot that help you document writing |
| [@affine/image-preview-plugin](plugins/image-preview) | Component for previewing an image |
## Thanks
## Upstreams
We would also like to give thanks to open-source projects that make AFFiNE possible:
@@ -178,11 +178,19 @@ See [BUILDING.md] for instructions on how to build AFFiNE from source code.
We welcome contributions from everyone.
See [docs/contributing/tutorial.md](./docs/contributing/tutorial.md) for details.
## Thanks
<a href="https://www.chromatic.com/"><img src="https://user-images.githubusercontent.com/321738/84662277-e3db4f80-af1b-11ea-88f5-91d67a5e59f6.png" width="153" height="30" alt="Chromatic" /></a>
Thanks to [Chromatic](https://www.chromatic.com/) for providing the visual testing platform that helps us review UI changes and catch visual regressions.
## License
See [LICENSE] for details.
[all-contributors-badge]: https://img.shields.io/github/all-contributors/toeverything/AFFiNE/master?color=orange
[![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2Ftoeverything%2FAFFiNE.svg?type=large)](https://app.fossa.com/projects/git%2Bgithub.com%2Ftoeverything%2FAFFiNE?ref=badge_large)
[all-contributors-badge]: https://img.shields.io/github/contributors/toeverything/AFFiNE
[license]: ./LICENSE
[building.md]: ./docs/BUILDING.md
[update page]: https://affine.pro/blog?tag=Release%20Note
@@ -196,5 +204,3 @@ See [LICENSE] for details.
[typescript-version-icon]: https://img.shields.io/github/package-json/dependency-version/toeverything/affine/dev/typescript
[react-version-icon]: https://img.shields.io/github/package-json/dependency-version/toeverything/AFFiNE/react?filename=apps%2Fcore%2Fpackage.json&color=rgb(97%2C228%2C251)
[blocksuite-icon]: https://img.shields.io/github/package-json/dependency-version/toeverything/AFFiNE/@blocksuite/store?color=6880ff&filename=apps%2Fcore%2Fpackage.json&label=blocksuite
[![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2Ftoeverything%2FAFFiNE.svg?type=large)](https://app.fossa.com/projects/git%2Bgithub.com%2Ftoeverything%2FAFFiNE?ref=badge_large)
+12 -5
View File
@@ -6,14 +6,21 @@ const require = createRequire(import.meta.url);
const packageJson = require('../package.json');
const editorFlags: BlockSuiteFeatureFlags = {
enable_database: true,
enable_slash_menu: true,
enable_edgeless_toolbar: true,
enable_block_hub: true,
enable_drag_handle: true,
enable_block_hub: true,
enable_surface: true,
enable_edgeless_toolbar: true,
enable_slash_menu: true,
enable_database: true,
enable_database_filter: false,
enable_data_view: false,
enable_page_tags: false,
enable_toggle_block: false,
enable_linked_page: true,
enable_bookmark_operation: false,
enable_note_index: false,
enable_attachment_block: true,
};
export function getRuntimeConfig(buildFlags: BuildFlags): RuntimeConfig {
@@ -23,7 +30,7 @@ export function getRuntimeConfig(buildFlags: BuildFlags): RuntimeConfig {
enableTestProperties: false,
enableBroadcastChannelProvider: true,
enableDebugPage: true,
changelogUrl: 'https://affine.pro/blog/what-is-new-affine-0728',
changelogUrl: 'https://affine.pro/blog/what-is-new-affine-0818',
imageProxyUrl: 'https://workers.toeverything.workers.dev/proxy/image',
enablePreloading: true,
enableNewSettingModal: true,
+23 -17
View File
@@ -2,12 +2,18 @@
"name": "@affine/core",
"type": "module",
"private": true,
"version": "0.8.0-canary.21",
"version": "0.8.0-beta.0",
"scripts": {
"build": "yarn -T run build-core",
"dev": "yarn -T run dev-core",
"static-server": "ts-node-esm ./server.mts"
},
"exports": {
"./app": "./src/app.tsx",
"./router": "./src/router.ts",
"./bootstrap/setup": "./src/bootstrap/setup.ts",
"./bootstrap/register-plugins": "./src/bootstrap/register-plugins.ts"
},
"dependencies": {
"@affine-test/fixtures": "workspace:*",
"@affine/component": "workspace:*",
@@ -18,33 +24,32 @@
"@affine/jotai": "workspace:*",
"@affine/templates": "workspace:*",
"@affine/workspace": "workspace:*",
"@blocksuite/block-std": "0.0.0-20230811201552-f37162ea-nightly",
"@blocksuite/blocks": "0.0.0-20230811201552-f37162ea-nightly",
"@blocksuite/editor": "0.0.0-20230811201552-f37162ea-nightly",
"@blocksuite/global": "0.0.0-20230811201552-f37162ea-nightly",
"@blocksuite/block-std": "0.0.0-20230818053411-bc330b35-nightly",
"@blocksuite/blocks": "0.0.0-20230818053411-bc330b35-nightly",
"@blocksuite/editor": "0.0.0-20230818053411-bc330b35-nightly",
"@blocksuite/global": "0.0.0-20230818053411-bc330b35-nightly",
"@blocksuite/icons": "^2.1.31",
"@blocksuite/lit": "0.0.0-20230811201552-f37162ea-nightly",
"@blocksuite/store": "0.0.0-20230811201552-f37162ea-nightly",
"@blocksuite/lit": "0.0.0-20230818053411-bc330b35-nightly",
"@blocksuite/store": "0.0.0-20230818053411-bc330b35-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.4",
"@mui/material": "^5.14.5",
"@react-hookz/web": "^23.1.0",
"@toeverything/components": "^0.0.10",
"@types/lodash.throttle": "^4.1.7",
"@toeverything/components": "^0.0.11",
"async-call-rpc": "^6.3.1",
"cmdk": "^0.2.0",
"css-spring": "^4.1.0",
"cssnano": "^6.0.1",
"graphql": "^16.7.1",
"graphql": "^16.8.0",
"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",
"lodash.debounce": "^4.0.8",
"lottie-web": "^5.12.2",
"mini-css-extract-plugin": "^2.7.6",
"next-themes": "^0.2.1",
@@ -52,21 +57,22 @@
"react": "18.2.0",
"react-dom": "18.2.0",
"react-is": "18.2.0",
"react-resizable-panels": "^0.0.54",
"react-resizable-panels": "^0.0.55",
"react-router-dom": "^6.15.0",
"rxjs": "^7.8.1",
"ses": "^0.18.7",
"swr": "2.2.1",
"y-protocols": "^1.0.5",
"yjs": "^13.6.7",
"zod": "^3.21.4"
"zod": "^3.22.1"
},
"devDependencies": {
"@perfsee/webpack": "^1.8.4",
"@pmmmwh/react-refresh-webpack-plugin": "^0.5.10",
"@pmmmwh/react-refresh-webpack-plugin": "^0.5.11",
"@sentry/webpack-plugin": "^2.6.2",
"@svgr/webpack": "^8.0.1",
"@swc/core": "^1.3.76",
"@svgr/webpack": "^8.1.0",
"@swc/core": "^1.3.77",
"@types/lodash.debounce": "^4.0.7",
"@types/webpack-env": "^1.18.1",
"copy-webpack-plugin": "^11.0.0",
"css-loader": "^6.8.1",
+8 -1
View File
@@ -20,8 +20,10 @@ import { getOrCreateWorkspace } from '@affine/workspace/manager';
import { createIndexedDBDownloadProvider } from '@affine/workspace/providers';
import { nanoid } from '@blocksuite/store';
import { useStaticBlockSuiteWorkspace } from '@toeverything/infra/__internal__/react';
import { rootStore } from '@toeverything/infra/atom';
import { buildShowcaseWorkspace } from '@toeverything/infra/blocksuite';
import { setPageModeAtom } from '../../atoms';
import {
BlockSuitePageList,
NewWorkspaceSettingDetail,
@@ -43,7 +45,12 @@ export const LocalAdapter: WorkspaceAdapter<WorkspaceFlavour.LOCAL> = {
);
blockSuiteWorkspace.meta.setName(DEFAULT_WORKSPACE_NAME);
if (runtimeConfig.enablePreloading) {
buildShowcaseWorkspace(blockSuiteWorkspace).catch(err => {
buildShowcaseWorkspace(blockSuiteWorkspace, {
store: rootStore,
atoms: {
pageMode: setPageModeAtom,
},
}).catch(err => {
logger.error('init page with preloading failed', err);
});
} else {
+27 -31
View File
@@ -36,9 +36,13 @@ const importLogger = new DebugLogger('plugins:import');
const pushLayoutAtom = atom<
null,
// fixme: check plugin name here
[pluginName: string, create: (root: HTMLElement) => () => void],
[
pluginName: string,
create: (root: HTMLElement) => () => void,
options: { maxWidth: (number | undefined)[] } | undefined,
],
void
>(null, (_, set, pluginName, callback) => {
>(null, (_, set, pluginName, callback, options) => {
set(pluginWindowAtom, items => ({
...items,
[pluginName]: callback,
@@ -50,20 +54,20 @@ const pushLayoutAtom = atom<
first: 'editor',
second: pluginName,
splitPercentage: 70,
maxWidth: options?.maxWidth,
};
} else {
return {
...layout,
direction: 'horizontal',
first: 'editor',
splitPercentage: 70,
second: {
direction: 'horizontal',
// fixme: incorrect type here
first: layout.second,
second: pluginName,
splitPercentage: 70,
first: pluginName,
second: layout.second,
splitPercentage: 50,
},
} as ExpectedLayout;
} satisfies ExpectedLayout;
}
});
addCleanup(pluginName, () => {
@@ -77,36 +81,27 @@ const deleteLayoutAtom = atom<null, [string], void>(null, (_, set, id) => {
delete newItems[id];
return newItems;
});
const removeLayout = (layout: LayoutNode): LayoutNode => {
if (layout === 'editor') {
return 'editor';
const removeLayout = (layout: LayoutNode): LayoutNode | string => {
if (typeof layout === 'string') {
return layout;
}
if (layout.first === id) {
return layout.second;
} else if (layout.second === id) {
return layout.first;
} else {
if (typeof layout === 'string') {
return layout as ExpectedLayout;
}
if (layout.first === id) {
return layout.second;
} else if (layout.second === id) {
return layout.first;
} else {
return removeLayout(layout.second);
}
return {
...layout,
second: removeLayout(layout.second),
};
}
};
set(contentLayoutAtom, layout => {
if (layout === 'editor') {
return 'editor';
} else {
if (typeof layout === 'string') {
return layout as ExpectedLayout;
}
if (layout.first === id) {
return layout.second as ExpectedLayout;
} else if (layout.second === id) {
return layout.first as ExpectedLayout;
} else {
return removeLayout(layout.second) as ExpectedLayout;
}
return removeLayout(layout) as ExpectedLayout;
}
});
});
@@ -123,6 +118,7 @@ const rootImportsMapSetupPromise = setupImportsMap(_rootImportsMap, {
swr: import('swr'),
'@affine/component': import('@affine/component'),
'@blocksuite/icons': import('@blocksuite/icons'),
'@blocksuite/blocks': import('@blocksuite/blocks'),
'@affine/sdk/entry': {
rootStore: rootStore,
currentWorkspaceAtom: currentWorkspaceAtom,
+3 -2
View File
@@ -85,12 +85,13 @@ export const pluginRegisterPromise = Promise.all(
if (assets.length > 0) {
await Promise.all(
assets.map(async (asset: string) => {
const loadedAssetName = `${pluginName}_${asset}`;
// todo(himself65): add assets into shadow dom
if (loadedAssets.has(asset)) {
if (loadedAssets.has(loadedAssetName)) {
return Promise.resolve();
}
if (asset.endsWith('.css')) {
loadedAssets.add(asset);
loadedAssets.add(loadedAssetName);
const res = await fetch(`${baseURL}/${asset}`);
if (res.ok) {
// todo: how to put css file into sandbox?
+7
View File
@@ -169,7 +169,14 @@ function createFirstAppData() {
rootStore.set(rootWorkspacesMetadataAtom, result);
}
let isSetup = false;
export async function setup() {
if (isSetup) {
console.warn('already setup');
return;
}
isSetup = true;
rootStore.set(
workspaceAdaptersAtom,
WorkspaceAdapters as Record<
@@ -81,11 +81,7 @@ const NameWorkspaceContent = ({
onChange={setWorkspaceName}
/>
<div className={style.buttonGroup}>
<Button
data-testid="create-workspace-close-button"
type="primary"
onClick={onClose}
>
<Button data-testid="create-workspace-close-button" onClick={onClose}>
{t.Cancel()}
</Button>
<Button
@@ -1,4 +1,10 @@
import { Menu, MenuItem, MenuTrigger, styled } from '@affine/component';
import {
Menu,
MenuItem,
type MenuProps,
MenuTrigger,
styled,
} from '@affine/component';
import { LOCALES } from '@affine/i18n';
import { useI18N } from '@affine/i18n';
import type { ButtonProps } from '@toeverything/components/button';
@@ -6,7 +12,6 @@ import type { ReactElement } from 'react';
import { useCallback } from 'react';
export const StyledListItem = styled(MenuItem)(() => ({
width: '132px',
height: '38px',
textTransform: 'capitalize',
}));
@@ -46,11 +51,14 @@ const LanguageMenuContent = ({ currentLanguage }: LanguageMenuContentProps) => {
);
};
interface LanguageMenuProps {
interface LanguageMenuProps extends Omit<MenuProps, 'children'> {
triggerProps?: ButtonProps;
}
export const LanguageMenu = ({ triggerProps }: LanguageMenuProps) => {
export const LanguageMenu = ({
triggerProps,
...menuProps
}: LanguageMenuProps) => {
const i18n = useI18N();
const currentLanguage = LOCALES.find(item => item.tag === i18n.language);
@@ -67,6 +75,7 @@ export const LanguageMenu = ({ triggerProps }: LanguageMenuProps) => {
placement="bottom-end"
trigger="click"
disablePortal={true}
{...menuProps}
>
<MenuTrigger
data-testid="language-menu-button"
@@ -30,11 +30,11 @@ export const DeleteLeaveWorkspace = ({
name={
<span style={{ color: 'var(--affine-error-color)' }}>
{isOwner
? t['com.affine.settings.workspace.remove']()
? t['com.affine.settings.remove-workspace']()
: t['Leave Workspace']()}
</span>
}
desc={t['com.affine.settings.workspace.remove.message']()}
desc={t['com.affine.settings.remove-workspace-description']()}
style={{ cursor: 'pointer' }}
onClick={() => {
setShowDelete(true);
@@ -38,9 +38,8 @@ export const ExportPanel = ({ workspace }: ExportPanelProps) => {
const t = useAFFiNEI18N();
const onExport = useCallback(async () => {
await syncBlobsToSqliteDb(workspace);
const result: SaveDBFileResult = await window.apis?.dialog.saveDBFileAs(
workspaceId
);
const result: SaveDBFileResult =
await window.apis?.dialog.saveDBFileAs(workspaceId);
if (result?.error) {
toast(t[result.error]());
} else if (!result?.canceled) {
@@ -59,14 +59,12 @@ export const WorkspaceSettingDetail = ({
<>
<SettingHeader
title={t[`Workspace Settings with name`]({ name })}
subtitle={t['You can customize your workspace here.']()}
subtitle={t['com.affine.settings.workspace.description']()}
/>
<SettingWrapper title={t['Info']()}>
<SettingRow
name={t['Workspace Profile']()}
desc={t[
'Only an owner can edit the the Workspace avatar and name.Changes will be shown for everyone.'
]()}
desc={t['com.affine.settings.workspace.not-owner']()}
spreadCol={false}
>
<ProfilePanel workspace={workspace} />
@@ -1,7 +1,7 @@
import { Input, toast } from '@affine/component';
import { FlexWrapper, Input, toast, Wrapper } from '@affine/component';
import { WorkspaceAvatar } from '@affine/component/workspace-avatar';
import { useAFFiNEI18N } from '@affine/i18n/hooks';
import { DoneIcon } from '@blocksuite/icons';
import { CameraIcon, DoneIcon } from '@blocksuite/icons';
import { IconButton } from '@toeverything/components/button';
import { useBlockSuiteWorkspaceAvatarUrl } from '@toeverything/hooks/use-block-suite-workspace-avatar-url';
import { useBlockSuiteWorkspaceName } from '@toeverything/hooks/use-block-suite-workspace-name';
@@ -11,25 +11,6 @@ import type { AffineOfficialWorkspace } from '../../../shared';
import { Upload } from '../../pure/file-upload';
import * as style from './style.css';
const CameraIcon = () => {
return (
<svg
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M10.6236 4.25001C10.635 4.25001 10.6467 4.25002 10.6584 4.25002H13.3416C13.3533 4.25002 13.365 4.25001 13.3764 4.25001C13.5609 4.24995 13.7105 4.2499 13.8543 4.26611C14.5981 4.34997 15.2693 4.75627 15.6826 5.38026C15.7624 5.50084 15.83 5.63398 15.9121 5.79586C15.9173 5.80613 15.9226 5.81652 15.9279 5.82703C15.9538 5.87792 15.9679 5.90562 15.9789 5.9261C15.9832 5.9341 15.9857 5.93861 15.9869 5.94065C16.0076 5.97069 16.0435 5.99406 16.0878 5.99905L16.0849 5.99877C16.0849 5.99877 16.0907 5.99918 16.1047 5.99947C16.1286 5.99998 16.1604 6.00002 16.2181 6.00002L17.185 6.00001C17.6577 6 18.0566 5.99999 18.3833 6.02627C18.7252 6.05377 19.0531 6.11364 19.3656 6.27035C19.8402 6.50842 20.2283 6.88944 20.4723 7.36077C20.6336 7.67233 20.6951 7.99944 20.7232 8.33858C20.75 8.66166 20.75 9.05554 20.75 9.51992V16.2301C20.75 16.6945 20.75 17.0884 20.7232 17.4114C20.6951 17.7506 20.6336 18.0777 20.4723 18.3893C20.2283 18.8606 19.8402 19.2416 19.3656 19.4797C19.0531 19.6364 18.7252 19.6963 18.3833 19.7238C18.0566 19.75 17.6578 19.75 17.185 19.75H6.81497C6.34225 19.75 5.9434 19.75 5.61668 19.7238C5.27477 19.6963 4.94688 19.6364 4.63444 19.4797C4.15978 19.2416 3.77167 18.8606 3.52771 18.3893C3.36644 18.0777 3.30494 17.7506 3.27679 17.4114C3.24998 17.0884 3.24999 16.6945 3.25 16.2302V9.51987C3.24999 9.05551 3.24998 8.66164 3.27679 8.33858C3.30494 7.99944 3.36644 7.67233 3.52771 7.36077C3.77167 6.88944 4.15978 6.50842 4.63444 6.27035C4.94688 6.11364 5.27477 6.05377 5.61668 6.02627C5.9434 5.99999 6.34225 6 6.81498 6.00001L7.78191 6.00002C7.83959 6.00002 7.87142 5.99998 7.8953 5.99947C7.90607 5.99924 7.91176 5.99897 7.91398 5.99884C7.95747 5.99343 7.99267 5.9703 8.01312 5.94066C8.01429 5.93863 8.01684 5.93412 8.02113 5.9261C8.0321 5.90561 8.04622 5.87791 8.07206 5.82703C8.07739 5.81653 8.08266 5.80615 8.08787 5.79588C8.17004 5.63397 8.23759 5.50086 8.31745 5.38026C8.73067 4.75627 9.40192 4.34997 10.1457 4.26611C10.2895 4.2499 10.4391 4.24995 10.6236 4.25001ZM10.6584 5.75002C10.422 5.75002 10.3627 5.75114 10.3138 5.75666C10.0055 5.79142 9.73316 5.95919 9.56809 6.20845C9.54218 6.24758 9.51544 6.29761 9.40943 6.50633C9.40611 6.51287 9.40274 6.5195 9.39934 6.52622C9.36115 6.60161 9.31758 6.68761 9.26505 6.76694C8.9964 7.17261 8.56105 7.4354 8.08026 7.48961C7.98625 7.50021 7.89021 7.50011 7.80434 7.50003C7.79678 7.50002 7.7893 7.50002 7.78191 7.50002H6.84445C6.33444 7.50002 5.99634 7.50058 5.73693 7.52144C5.48594 7.54163 5.37478 7.57713 5.30693 7.61115C5.11257 7.70864 4.95675 7.86306 4.85983 8.05029C4.82733 8.11308 4.79194 8.21816 4.77165 8.46266C4.7506 8.71626 4.75 9.0474 4.75 9.55001V16.2C4.75 16.7026 4.7506 17.0338 4.77165 17.2874C4.79194 17.5319 4.82733 17.6369 4.85983 17.6997C4.95675 17.887 5.11257 18.0414 5.30693 18.1389C5.37478 18.1729 5.48594 18.2084 5.73693 18.2286C5.99634 18.2494 6.33444 18.25 6.84445 18.25H17.1556C17.6656 18.25 18.0037 18.2494 18.2631 18.2286C18.5141 18.2084 18.6252 18.1729 18.6931 18.1389C18.8874 18.0414 19.0433 17.887 19.1402 17.6997C19.1727 17.6369 19.2081 17.5319 19.2283 17.2874C19.2494 17.0338 19.25 16.7026 19.25 16.2V9.55001C19.25 9.0474 19.2494 8.71626 19.2283 8.46266C19.2081 8.21816 19.1727 8.11308 19.1402 8.05029C19.0433 7.86306 18.8874 7.70864 18.6931 7.61115C18.6252 7.57713 18.5141 7.54163 18.2631 7.52144C18.0037 7.50058 17.6656 7.50002 17.1556 7.50002H16.2181C16.2107 7.50002 16.2032 7.50002 16.1957 7.50003C16.1098 7.50011 16.0138 7.50021 15.9197 7.48961C15.4389 7.4354 15.0036 7.17261 14.735 6.76694C14.6824 6.68761 14.6389 6.60163 14.6007 6.52622C14.5973 6.5195 14.5939 6.51287 14.5906 6.50633C14.4846 6.29763 14.4578 6.24758 14.4319 6.20846C14.2668 5.95919 13.9945 5.79142 13.6862 5.75666C13.6373 5.75114 13.578 5.75002 13.3416 5.75002H10.6584ZM12 11C10.9303 11 10.0833 11.8506 10.0833 12.875C10.0833 13.8995 10.9303 14.75 12 14.75C13.0697 14.75 13.9167 13.8995 13.9167 12.875C13.9167 11.8506 13.0697 11 12 11ZM8.58333 12.875C8.58333 11 10.1242 9.50002 12 9.50002C13.8758 9.50002 15.4167 11 15.4167 12.875C15.4167 14.7501 13.8758 16.25 12 16.25C10.1242 16.25 8.58333 14.7501 8.58333 12.875Z"
fill="white"
/>
</svg>
);
};
interface ProfilePanelProps {
workspace: AffineOfficialWorkspace;
}
@@ -74,32 +55,35 @@ export const ProfilePanel = ({ workspace }: ProfilePanelProps) => {
</>
</Upload>
</div>
<div className={style.profileHandlerWrapper}>
<Input
width={280}
height={32}
defaultValue={input}
data-testid="workspace-name-input"
placeholder={t['Workspace Name']()}
maxLength={64}
minLength={0}
onChange={setInput}
/>
{input === workspace.blockSuiteWorkspace.meta.name ? null : (
<IconButton
data-testid="save-workspace-name"
onClick={() => {
handleUpdateWorkspaceName(input);
}}
active={true}
style={{
marginLeft: '12px',
}}
>
<DoneIcon />
</IconButton>
)}
</div>
<Wrapper marginLeft={20}>
<div className={style.label}>{t['Workspace Name']()}</div>
<FlexWrapper alignItems="center" flexGrow="1">
<Input
width={280}
height={32}
defaultValue={input}
data-testid="workspace-name-input"
placeholder={t['Workspace Name']()}
maxLength={64}
minLength={0}
onChange={setInput}
/>
{input === workspace.blockSuiteWorkspace.meta.name ? null : (
<IconButton
data-testid="save-workspace-name"
onClick={() => {
handleUpdateWorkspaceName(input);
}}
active={true}
style={{
marginLeft: '12px',
}}
>
<DoneIcon />
</IconButton>
)}
</FlexWrapper>
</Wrapper>
</div>
);
};
@@ -94,7 +94,7 @@ const FakePublishPanelAffine = (_props: FakePublishPanelAffineProps) => {
return (
<Tooltip
content={t['com.affine.settings.workspace.publish.local-tooltip']()}
content={t['com.affine.settings.workspace.publish-tooltip']()}
placement="top"
>
<div className={style.fakeWrapper}>
@@ -5,12 +5,6 @@ export const profileWrapper = style({
alignItems: 'flex-end',
marginTop: '12px',
});
export const profileHandlerWrapper = style({
flexGrow: '1',
display: 'flex',
alignItems: 'center',
marginLeft: '20px',
});
export const avatarWrapper = style({
width: '56px',
@@ -39,6 +33,8 @@ globalStyle(`${avatarWrapper} .camera-icon-wrapper`, {
alignItems: 'center',
backgroundColor: 'rgba(60, 61, 63, 0.5)',
zIndex: '1',
color: 'var(--affine-white)',
fontSize: '24px',
});
export const urlButton = style({
@@ -71,3 +67,9 @@ export const fakeWrapper = style({
},
},
});
export const label = style({
fontSize: 'var(--affine-font-xs)',
color: 'var(--affine-text-secondary-color)',
marginBottom: '5px',
});
@@ -58,7 +58,7 @@ export const AboutAffine = () => {
</SettingRow>
<SettingRow
name={t[`Discover what's new`]()}
desc={t['View the AFFiNE Changelog.']()}
desc={t['Changelog description']()}
style={{ cursor: 'pointer' }}
onClick={() => {
window.open(runtimeConfig.changelogUrl, '_blank');
@@ -35,10 +35,10 @@ export const ThemeSettings = () => {
<RadioButton value="system" data-testid="system-theme-trigger">
{t['system']()}
</RadioButton>
<RadioButton bold={true} value="light" data-testid="light-theme-trigger">
<RadioButton value="light" data-testid="light-theme-trigger">
{t['light']()}
</RadioButton>
<RadioButton bold={true} value="dark" data-testid="dark-theme-trigger">
<RadioButton value="dark" data-testid="dark-theme-trigger">
{t['dark']()}
</RadioButton>
</RadioButtonGroup>
@@ -63,7 +63,6 @@ const FontFamilySettings = () => {
return (
<RadioButton
key={key}
bold={true}
value={key}
data-testid="system-font-style-trigger"
style={{
@@ -110,16 +109,28 @@ export const AppearanceSettings = () => {
</SettingRow>
<SettingRow
name={t['Display Language']()}
desc={t['Select the language for the interface.']()}
desc={t['com.affine.settings.appearance.language-description']()}
>
<div className={settingWrapper}>
<LanguageMenu />
<LanguageMenu
triggerContainerStyle={{ width: '100%' }}
triggerProps={{
style: {
width: '100%',
justifyContent: 'space-between',
fontWeight: 600,
padding: '0 10px',
},
}}
/>
</div>
</SettingRow>
{environment.isDesktop ? (
<SettingRow
name={t['Client Border Style']()}
desc={t['Customize the appearance of the client.']()}
desc={t[
'com.affine.settings.appearance.border-style-description'
]()}
>
<Switch
checked={appSettings.clientBorder}
@@ -130,7 +141,7 @@ export const AppearanceSettings = () => {
<SettingRow
name={t['Full width Layout']()}
desc={t['Maximum display of content within a page.']()}
desc={t['com.affine.settings.appearance.full-width-description']()}
>
<Switch
data-testid="full-width-layout-trigger"
@@ -141,7 +152,9 @@ export const AppearanceSettings = () => {
{runtimeConfig.enableNewSettingUnstableApi && environment.isDesktop ? (
<SettingRow
name={t['Window frame style']()}
desc={t['Customize appearance of Windows Client.']()}
desc={t[
'com.affine.settings.appearance.window-frame-description'
]()}
>
<RadioButtonGroup
className={settingWrapper}
@@ -166,7 +179,7 @@ export const AppearanceSettings = () => {
<SettingWrapper title={t['Date']()}>
<SettingRow
name={t['Date Format']()}
desc={t['Customize your date style.']()}
desc={t['com.affine.settings.appearance.date-format-description']()}
>
<div className={settingWrapper}>
<DateFormatSetting />
@@ -174,7 +187,7 @@ export const AppearanceSettings = () => {
</SettingRow>
<SettingRow
name={t['Start Week On Monday']()}
desc={t['By default, the week starts on Sunday.']()}
desc={t['com.affine.settings.appearance.start-week-description']()}
>
<Switch
checked={appSettings.startWeekOnMonday}
@@ -187,8 +200,8 @@ export const AppearanceSettings = () => {
{environment.isDesktop ? (
<SettingWrapper title={t['Sidebar']()}>
<SettingRow
name={t['com.affine.settings.appearance.sidebar.noise']()}
desc={t['com.affine.settings.appearance.sidebar.noise.message']()}
name={t['com.affine.settings.noise-style']()}
desc={t['com.affine.settings.noise-style-description']()}
>
<Switch
checked={appSettings.enableNoisyBackground}
@@ -198,10 +211,8 @@ export const AppearanceSettings = () => {
/>
</SettingRow>
<SettingRow
name={t['com.affine.settings.appearance.sidebar.translucent']()}
desc={t[
'com.affine.settings.appearance.sidebar.translucent.message'
]()}
name={t['com.affine.settings.translucent-style']()}
desc={t['com.affine.settings.translucent-style-description']()}
>
<Switch
checked={appSettings.enableBlurBackground}
@@ -3,20 +3,48 @@ import { SettingWrapper } from '@affine/component/setting-components';
import { useAFFiNEI18N } from '@affine/i18n/hooks';
import {
type ShortcutsInfo,
useEdgelessShortcuts,
useGeneralShortcuts,
useMarkdownShortcuts,
usePageShortcuts,
} from '../../../../../hooks/affine/use-shortcuts';
import { shortcutRow } from './style.css';
import { shortcutKey, shortcutKeyContainer, shortcutRow } from './style.css';
const ShortcutsPanel = ({
shortcutsInfo,
}: {
shortcutsInfo: ShortcutsInfo;
}) => {
return (
<SettingWrapper title={shortcutsInfo.title}>
{Object.entries(shortcutsInfo.shortcuts).map(([title, shortcuts]) => {
return (
<div key={title} className={shortcutRow}>
<span>{title}</span>
<div className={shortcutKeyContainer}>
{shortcuts.map(key => {
return (
<span className={shortcutKey} key={key}>
{key}
</span>
);
})}
</div>
</div>
);
})}
</SettingWrapper>
);
};
export const Shortcuts = () => {
const t = useAFFiNEI18N();
const markdownShortcuts = useMarkdownShortcuts();
const pageShortcuts = usePageShortcuts();
const edgelessShortcuts = useEdgelessShortcuts();
const generalShortcuts = useGeneralShortcuts();
const markdownShortcutsInfo = useMarkdownShortcuts();
const pageShortcutsInfo = usePageShortcuts();
const edgelessShortcutsInfo = useEdgelessShortcuts();
const generalShortcutsInfo = useGeneralShortcuts();
return (
<>
@@ -25,46 +53,10 @@ export const Shortcuts = () => {
subtitle={t['Check Keyboard Shortcuts quickly']()}
data-testid="keyboard-shortcuts-title"
/>
<SettingWrapper title={t['General']()}>
{Object.entries(generalShortcuts).map(([title, shortcuts]) => {
return (
<div key={title} className={shortcutRow}>
<span>{title}</span>
<span className="shortcut">{shortcuts}</span>
</div>
);
})}
</SettingWrapper>
<SettingWrapper title={t['Page']()}>
{Object.entries(pageShortcuts).map(([title, shortcuts]) => {
return (
<div key={title} className={shortcutRow}>
<span>{title}</span>
<span className="shortcut">{shortcuts}</span>
</div>
);
})}
</SettingWrapper>
<SettingWrapper title={t['Edgeless']()}>
{Object.entries(edgelessShortcuts).map(([title, shortcuts]) => {
return (
<div key={title} className={shortcutRow}>
<span>{title}</span>
<span className="shortcut">{shortcuts}</span>
</div>
);
})}
</SettingWrapper>
<SettingWrapper title={t['Markdown Syntax']()}>
{Object.entries(markdownShortcuts).map(([title, shortcuts]) => {
return (
<div key={title} className={shortcutRow}>
<span>{title}</span>
<span className="shortcut">{shortcuts}</span>
</div>
);
})}
</SettingWrapper>
<ShortcutsPanel shortcutsInfo={generalShortcutsInfo} />
<ShortcutsPanel shortcutsInfo={pageShortcutsInfo} />
<ShortcutsPanel shortcutsInfo={edgelessShortcutsInfo} />
<ShortcutsPanel shortcutsInfo={markdownShortcutsInfo} />
</>
);
};
@@ -1,4 +1,4 @@
import { globalStyle, style } from '@vanilla-extract/css';
import { style } from '@vanilla-extract/css';
export const shortcutRow = style({
height: '32px',
@@ -14,8 +14,25 @@ export const shortcutRow = style({
},
});
globalStyle(`${shortcutRow} .shortcut`, {
border: '1px solid var(--affine-border-color)',
borderRadius: '8px',
padding: '4px 18px',
export const shortcutKeyContainer = style({
display: 'flex',
});
export const shortcutKey = style({
minWidth: '24px',
height: '20px',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
padding: '0 6px',
border: '1px solid var(--affine-border-color)',
borderRadius: '4px',
background: 'var(--affine-background-tertiary-color)',
boxShadow:
'0px 6px 4px 0px rgba(255, 255, 255, 0.24) inset, 0px 0px 0px 0.5px rgba(0, 0, 0, 0.10) inset',
fontSize: 'var(--affine-font-xs)',
selectors: {
'&:not(:last-of-type)': {
marginRight: '2px',
},
},
});
@@ -1,10 +1,11 @@
import {
SettingModal as SettingModalBase,
type SettingModalProps as SettingModalBaseProps,
WorkspaceDetailSkeleton,
} from '@affine/component/setting-components';
import { useAFFiNEI18N } from '@affine/i18n/hooks';
import { ContactWithUsIcon } from '@blocksuite/icons';
import { useCallback } from 'react';
import { Suspense, useCallback } from 'react';
import { AccountSetting } from './account-setting';
import {
@@ -13,7 +14,7 @@ import {
useGeneralSettingList,
} from './general-setting';
import { SettingSidebar } from './setting-sidebar';
import { settingContent } from './style.css';
import { footerIconWrapper, settingContent } from './style.css';
import { WorkspaceSetting } from './workspace-setting';
type ActiveTab = GeneralSettingKeys | 'workspace' | 'account';
@@ -77,7 +78,9 @@ export const SettingModal = ({
<div className="wrapper">
<div className="content">
{activeTab === 'workspace' && workspaceId ? (
<WorkspaceSetting key={workspaceId} workspaceId={workspaceId} />
<Suspense fallback={<WorkspaceDetailSkeleton />}>
<WorkspaceSetting key={workspaceId} workspaceId={workspaceId} />
</Suspense>
) : null}
{generalSettingList.find(v => v.key === activeTab) ? (
<GeneralSetting generalKey={activeTab as GeneralSettingKeys} />
@@ -85,15 +88,15 @@ export const SettingModal = ({
{activeTab === 'account' ? <AccountSetting /> : null}
</div>
<div className="footer">
<ContactWithUsIcon />
<div className={footerIconWrapper}>
<ContactWithUsIcon />
</div>
<a
href="https://community.affine.pro/home"
target="_blank"
rel="noreferrer"
>
{t[
'Need more customization options? You can suggest them to us in the community.'
]()}
{t['com.affine.settings.suggestion']()}
</a>
</div>
</div>
@@ -28,16 +28,18 @@ globalStyle(`${settingContent} .footer`, {
marginTop: '-80px',
fontSize: 'var(--affine-font-sm)',
display: 'flex',
minHeight: '100px',
});
globalStyle(`${settingContent} .footer a`, {
color: 'var(--affine-text-primary-color)',
lineHeight: 'normal',
});
globalStyle(`${settingContent} .footer > svg`, {
export const footerIconWrapper = style({
fontSize: 'var(--affine-font-base)',
color: 'var(--affine-icon-color)',
marginRight: '12px',
marginTop: '1px',
height: '19px',
display: 'flex',
alignItems: 'center',
});
@@ -1,7 +1,6 @@
import { WorkspaceDetailSkeleton } from '@affine/component/setting-components';
import { usePassiveWorkspaceEffect } from '@toeverything/infra/__internal__/react';
import { useSetAtom } from 'jotai';
import { Suspense, useCallback } from 'react';
import { useCallback } from 'react';
import { getUIAdapter } from '../../../../adapters/workspace';
import { openSettingModalAtom } from '../../../../atoms';
@@ -33,12 +32,10 @@ export const WorkspaceSetting = ({ workspaceId }: { workspaceId: string }) => {
const onTransformWorkspace = useOnTransformWorkspace();
return (
<Suspense fallback={<WorkspaceDetailSkeleton />}>
<NewSettingsDetail
onTransformWorkspace={onTransformWorkspace}
onDeleteWorkspace={onDeleteWorkspace}
currentWorkspaceId={workspaceId}
/>
</Suspense>
<NewSettingsDetail
onTransformWorkspace={onTransformWorkspace}
onDeleteWorkspace={onDeleteWorkspace}
currentWorkspaceId={workspaceId}
/>
);
};
@@ -1,4 +1,4 @@
import { style } from '@vanilla-extract/css';
import { type ComplexStyleRule, style } from '@vanilla-extract/css';
export const headerTitleContainer = style({
display: 'flex',
@@ -10,11 +10,11 @@ export const headerTitleContainer = style({
});
export const titleEditButton = style({
flexGrow: 1,
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap',
});
WebkitAppRegion: 'no-drag',
} as ComplexStyleRule);
export const titleInput = style({
position: 'absolute',
@@ -3,6 +3,8 @@ import type { HTMLAttributes } from 'react';
import type React from 'react';
import { cloneElement, useState } from 'react';
import edgelessHover from './animation-data/edgeless-hover.json';
import pageHover from './animation-data/page-hover.json';
import { StyledSwitchItem } from './style';
type HoverAnimateControllerProps = {
@@ -52,7 +54,7 @@ export const PageSwitchItem = (
options={{
loop: false,
autoplay: false,
animationData: require('./animation-data/page-hover.json'),
animationData: pageHover,
rendererSettings: {
preserveAspectRatio: 'xMidYMid slice',
},
@@ -71,7 +73,7 @@ export const EdgelessSwitchItem = (
options={{
loop: false,
autoplay: false,
animationData: require('./animation-data/edgeless-hover.json'),
animationData: edgelessHover,
rendererSettings: {
preserveAspectRatio: 'xMidYMid slice',
},
+64 -26
View File
@@ -17,7 +17,15 @@ import { contentLayoutAtom, rootStore } from '@toeverything/infra/atom';
import clsx from 'clsx';
import { useAtomValue, useSetAtom } from 'jotai';
import type { CSSProperties, ReactElement } from 'react';
import { memo, Suspense, useCallback, useMemo } from 'react';
import {
memo,
startTransition,
Suspense,
useCallback,
useEffect,
useMemo,
useRef,
} from 'react';
import { Panel, PanelGroup, PanelResizeHandle } from 'react-resizable-panels';
import { pageSettingFamily } from '../atoms';
@@ -134,25 +142,42 @@ interface PluginContentAdapterProps {
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);
const rootRef = useRef<HTMLDivElement | null>(null);
useEffect(() => {
const abortController = new AbortController();
const root = rootRef.current;
if (root) {
startTransition(() => {
if (abortController.signal.aborted) {
return;
}
const div = document.createElement('div');
const cleanup = windowItem(div);
root.appendChild(div);
if (abortController.signal.aborted) {
cleanup();
root.removeChild(div);
} else {
const cl = () => {
cleanup();
root.removeChild(div);
};
const dispose = addCleanup(pluginName, cl);
abortController.signal.addEventListener('abort', () => {
setTimeout(() => {
dispose();
cl();
});
}
},
[pluginName, windowItem]
)}
/>
);
});
}
});
return () => {
abortController.abort();
};
}
return;
}, [pluginName, windowItem]);
return <div className={pluginContainer} ref={rootRef} />;
}
);
@@ -175,13 +200,13 @@ const LayoutPanel = memo(function LayoutPanel(
}
} else {
return (
<PanelGroup
style={{
height: 'calc(100% - 52px)',
}}
direction={node.direction}
>
<Panel defaultSize={node.splitPercentage}>
<PanelGroup direction={node.direction}>
<Panel
defaultSize={node.splitPercentage}
style={{
maxWidth: node.maxWidth?.[0],
}}
>
<Suspense>
<LayoutPanel node={node.first} editorProps={props.editorProps} />
</Suspense>
@@ -191,6 +216,7 @@ const LayoutPanel = memo(function LayoutPanel(
defaultSize={100 - node.splitPercentage}
style={{
overflow: 'scroll',
maxWidth: node.maxWidth?.[1],
}}
>
<Suspense>
@@ -211,6 +237,18 @@ export const PageDetailEditor = (props: PageDetailEditorProps) => {
const layout = useAtomValue(contentLayoutAtom);
if (layout === 'editor') {
return (
<Suspense>
<PanelGroup direction="horizontal">
<Panel>
<EditorWrapper {...props} />
</Panel>
</PanelGroup>
</Suspense>
);
}
return (
<>
<Suspense>
@@ -1,11 +1,12 @@
import { useAFFiNEI18N } from '@affine/i18n/hooks';
import { CloudWorkspaceIcon } from '@blocksuite/icons';
import { Button } from '@toeverything/components/button';
import { useSetAtom } from 'jotai';
import { type CSSProperties, forwardRef } from 'react';
import { openDisableCloudAlertModalAtom } from '../../../atoms';
import { stringToColour } from '../../../utils';
import { StyledFooter, StyledSignInButton } from './styles';
import { StyledFooter } from './styles';
export const Footer = () => {
const t = useAFFiNEI18N();
@@ -13,13 +14,13 @@ export const Footer = () => {
return (
<StyledFooter data-testid="workspace-list-modal-footer">
<StyledSignInButton
<Button
data-testid="sign-in-button"
type="plain"
icon={
<div className="circle">
<CloudWorkspaceIcon />
</div>
<CloudWorkspaceIcon
style={{ color: 'var(--affine-primary-color)' }}
/>
}
onClick={async () => {
if (!runtimeConfig.enableCloud) {
@@ -28,7 +29,7 @@ export const Footer = () => {
}}
>
{t['Sign in']()}
</StyledSignInButton>
</Button>
</StyledFooter>
);
};
+1 -25
View File
@@ -1,10 +1,4 @@
import {
displayFlex,
displayInlineFlex,
styled,
textEllipsis,
} from '@affine/component';
import { Button } from '@toeverything/components/button';
import { displayFlex, styled, textEllipsis } from '@affine/component';
export const StyleWorkspaceInfo = styled('div')(() => {
return {
marginLeft: '15px',
@@ -116,21 +110,3 @@ export const StyledModalHeader = styled('div')(() => {
...displayFlex('space-between', 'center'),
};
});
export const StyledSignInButton = styled(Button)(() => {
return {
fontWeight: 600,
paddingLeft: 0,
'.circle': {
width: '40px',
height: '40px',
borderRadius: '20px',
backgroundColor: 'var(--affine-hover-color)',
color: 'var(--affine-primary-color)',
fontSize: '24px',
flexShrink: 0,
marginRight: '16px',
...displayInlineFlex('center', 'center'),
},
};
});
+43 -15
View File
@@ -7,7 +7,7 @@ import {
import { isDesktop } from '@affine/env/constant';
import clsx from 'clsx';
import { useAtomValue } from 'jotai';
import throttle from 'lodash.throttle';
import debounce from 'lodash.debounce';
import type { MutableRefObject, ReactNode } from 'react';
import { useEffect, useRef, useState } from 'react';
@@ -22,37 +22,52 @@ interface HeaderPros {
const useIsTinyScreen = ({
mainContainer,
leftDoms,
leftStatic,
leftSlot,
centerDom,
rightDoms,
rightStatic,
rightSlot,
}: {
mainContainer: HTMLElement;
leftDoms: MutableRefObject<HTMLElement | null>[];
leftStatic: MutableRefObject<HTMLElement | null>;
leftSlot: MutableRefObject<HTMLElement | null>[];
centerDom: MutableRefObject<HTMLElement | null>;
rightDoms: MutableRefObject<HTMLElement | null>[];
rightStatic: MutableRefObject<HTMLElement | null>;
rightSlot: MutableRefObject<HTMLElement | null>[];
}) => {
const [isTinyScreen, setIsTinyScreen] = useState(false);
useEffect(() => {
const handleResize = throttle(() => {
const handleResize = debounce(() => {
if (!centerDom.current) {
return;
}
const leftTotalWidth = leftDoms.reduce((accWidth, dom) => {
const leftStaticWidth = leftStatic.current?.clientWidth || 0;
const leftSlotWidth = leftSlot.reduce((accWidth, dom) => {
return accWidth + (dom.current?.clientWidth || 0);
}, 0);
const rightTotalWidth = rightDoms.reduce((accWidth, dom) => {
const rightStaticWidth = rightStatic.current?.clientWidth || 0;
const rightSlotWidth = rightSlot.reduce((accWidth, dom) => {
return accWidth + (dom.current?.clientWidth || 0);
}, 0);
if (!leftSlotWidth && !rightSlotWidth) {
if (isTinyScreen) {
setIsTinyScreen(false);
}
return;
}
const containerRect = mainContainer.getBoundingClientRect();
const centerRect = centerDom.current.getBoundingClientRect();
const offset = isTinyScreen ? 50 : 0;
if (
leftTotalWidth + containerRect.left >= centerRect.left - offset ||
containerRect.right - centerRect.right <= rightTotalWidth + offset
leftStaticWidth + leftSlotWidth + containerRect.left >=
centerRect.left ||
containerRect.right - centerRect.right <=
rightSlotWidth + rightStaticWidth
) {
setIsTinyScreen(true);
} else {
@@ -67,7 +82,19 @@ const useIsTinyScreen = ({
});
resizeObserver.observe(mainContainer);
}, [centerDom, isTinyScreen, leftDoms, mainContainer, rightDoms]);
return () => {
resizeObserver.disconnect();
};
}, [
centerDom,
isTinyScreen,
leftSlot,
leftStatic,
mainContainer,
rightSlot,
rightStatic,
]);
return isTinyScreen;
};
@@ -84,9 +111,11 @@ export const Header = ({ left, center, right }: HeaderPros) => {
const isTinyScreen = useIsTinyScreen({
mainContainer: document.querySelector('.main-container') || document.body,
leftDoms: [sidebarSwitchRef, leftSlotRef],
leftStatic: sidebarSwitchRef,
leftSlot: [leftSlotRef],
centerDom: centerSlotRef,
rightDoms: [rightSlotRef, windowControlsRef],
rightSlot: [rightSlotRef],
rightStatic: windowControlsRef,
});
const isWindowsDesktop = globalThis.platform === 'win32' && isDesktop;
@@ -124,7 +153,6 @@ export const Header = ({ left, center, right }: HeaderPros) => {
className={clsx({
[style.headerCenter]: center,
'is-window': isWindowsDesktop,
'has-min-width': !isTinyScreen,
})}
ref={centerSlotRef}
>
@@ -8,6 +8,7 @@ export const header = style({
padding: '0 16px',
minHeight: '52px',
borderBottom: '1px solid var(--affine-border-color)',
zIndex: 2,
selectors: {
'&[data-sidebar-floating="false"]': {
WebkitAppRegion: 'drag',
@@ -48,6 +49,7 @@ export const headerCenter = style({
height: '52px',
flexShrink: 0,
maxWidth: '60%',
minWidth: '300px',
position: 'absolute',
transform: 'translateX(-50%)',
left: '50%',
@@ -56,9 +58,6 @@ export const headerCenter = style({
'&.is-window': {
maxWidth: '50%',
},
'&.is-window.has-min-width': {
minWidth: '400px',
},
'&.shadow': {
position: 'static',
visibility: 'hidden',
@@ -76,6 +75,7 @@ export const headerSideContainer = style({
},
'&.block': {
display: 'block',
paddingBottom: '10px',
},
},
});
@@ -83,6 +83,8 @@ export const headerSideContainer = style({
export const windowAppControlsWrapper = style({
display: 'flex',
marginLeft: '20px',
// header padding right
transform: 'translateX(16px)',
});
export const windowAppControl = style({
@@ -98,7 +100,6 @@ export const windowAppControl = style({
'&[data-type="close"]': {
width: '56px',
paddingRight: '5px',
marginRight: '-12px',
},
'&[data-type="close"]:hover': {
background: 'var(--affine-windows-close-button)',
@@ -44,7 +44,7 @@ const OSWarningMessage = () => {
return (
<span>
<Trans i18nKey="recommendBrowser">
We recommend the <strong>Chrome</strong> browser for optimal
We recommend the <strong>Chrome</strong> browser for an optimal
experience.
</Trans>
</span>
@@ -60,7 +60,11 @@ export const HelpIsland = ({
style={{ height: spread ? `${showList.length * 40 + 4}px` : 0 }}
>
{showList.includes('whatNew') && (
<Tooltip content={t["Discover what's new!"]()} placement="left-end">
<Tooltip
content={t["Discover what's new!"]()}
placement="left-end"
showArrow={true}
>
<StyledIconWrapper
data-testid="right-bottom-change-log-icon"
onClick={() => {
@@ -72,7 +76,11 @@ export const HelpIsland = ({
</Tooltip>
)}
{showList.includes('contact') && (
<Tooltip content={t['Contact Us']()} placement="left-end">
<Tooltip
content={t['Contact Us']()}
placement="left-end"
showArrow={true}
>
<StyledIconWrapper
data-testid="right-bottom-contact-us-icon"
onClick={openAbout}
@@ -82,7 +90,11 @@ export const HelpIsland = ({
</Tooltip>
)}
{showList.includes('shortcuts') && (
<Tooltip content={t['Keyboard Shortcuts']()} placement="left-end">
<Tooltip
content={t['Keyboard Shortcuts']()}
placement="left-end"
showArrow={true}
>
<StyledIconWrapper
data-testid="shortcuts-icon"
onClick={() => {
@@ -98,6 +110,7 @@ export const HelpIsland = ({
<Tooltip
content={t['com.affine.helpIsland.gettingStarted']()}
placement="left-end"
showArrow={true}
>
<StyledIconWrapper
data-testid="easy-guide"
@@ -112,7 +125,11 @@ export const HelpIsland = ({
)}
</StyledAnimateWrapper>
<Tooltip content={t['Help and Feedback']()} placement="left-end">
<Tooltip
content={t['Help and Feedback']()}
placement={'left-end'}
showArrow={true}
>
<MuiFade in={!spread} data-testid="faq-icon">
<StyledTriggerWrapper>
<HelpIcon />
@@ -6,46 +6,80 @@ import {
import { useAFFiNEI18N } from '@affine/i18n/hooks';
import {
type ShortcutsInfo,
useEdgelessShortcuts,
useGeneralShortcuts,
useMarkdownShortcuts,
usePageShortcuts,
} from '../../../hooks/affine/use-shortcuts';
import { KeyboardIcon } from './icons';
import {
StyledListItem,
StyledModalHeader,
StyledShortcutsModal,
StyledSubTitle,
StyledTitle,
} from './style';
import * as styles from './style.css';
// import {
// StyledListItem,
// StyledModalHeader,
// StyledShortcutsModal,
// StyledSubTitle,
// StyledTitle,
// } from './style';
type ModalProps = {
open: boolean;
onClose: () => void;
};
const ShortcutsPanel = ({
shortcutsInfo,
}: {
shortcutsInfo: ShortcutsInfo;
}) => {
return (
<>
<div className={styles.subtitle}>{shortcutsInfo.title}</div>
{Object.entries(shortcutsInfo.shortcuts).map(([title, shortcuts]) => {
return (
<div className={styles.listItem} key={title}>
<span>{title}</span>
<div className={styles.keyContainer}>
{shortcuts.map(key => {
return (
<span className={styles.key} key={key}>
{key}
</span>
);
})}
</div>
</div>
);
})}
</>
);
};
export const ShortcutsModal = ({ open, onClose }: ModalProps) => {
const t = useAFFiNEI18N();
const markdownShortcuts = useMarkdownShortcuts();
const pageShortcuts = usePageShortcuts();
const edgelessShortcuts = useEdgelessShortcuts();
const generalShortcuts = useGeneralShortcuts();
const markdownShortcutsInfo = useMarkdownShortcuts();
const pageShortcutsInfo = usePageShortcuts();
const edgelessShortcutsInfo = useEdgelessShortcuts();
const generalShortcutsInfo = useGeneralShortcuts();
return (
<MuiSlide direction="left" in={open} mountOnEnter unmountOnExit>
<StyledShortcutsModal data-testid="shortcuts-modal">
<div className={styles.shortcutsModal} data-testid="shortcuts-modal">
<MuiClickAwayListener
onClickAway={() => {
onClose();
}}
>
<div>
<StyledModalHeader>
<StyledTitle>
<div
className={styles.modalHeader}
style={{ marginBottom: '-28px' }}
>
<div className={styles.title}>
<KeyboardIcon />
{t['Shortcuts']()}
</StyledTitle>
</div>
<ModalCloseButton
top={6}
@@ -54,48 +88,14 @@ export const ShortcutsModal = ({ open, onClose }: ModalProps) => {
onClose();
}}
/>
</StyledModalHeader>
<StyledSubTitle style={{ marginTop: 0 }}>
{t['General']()}
</StyledSubTitle>
{Object.entries(generalShortcuts).map(([title, shortcuts]) => {
return (
<StyledListItem key={title}>
<span>{title}</span>
<span>{shortcuts}</span>
</StyledListItem>
);
})}
<StyledSubTitle>{t['Page']()}</StyledSubTitle>
{Object.entries(pageShortcuts).map(([title, shortcuts]) => {
return (
<StyledListItem key={title}>
<span>{title}</span>
<span>{shortcuts}</span>
</StyledListItem>
);
})}
<StyledSubTitle>{t['Edgeless']()}</StyledSubTitle>
{Object.entries(edgelessShortcuts).map(([title, shortcuts]) => {
return (
<StyledListItem key={title}>
<span>{title}</span>
<span>{shortcuts}</span>
</StyledListItem>
);
})}
<StyledSubTitle>{t['Markdown Syntax']()}</StyledSubTitle>
{Object.entries(markdownShortcuts).map(([title, shortcuts]) => {
return (
<StyledListItem key={title}>
<span>{title}</span>
<span>{shortcuts}</span>
</StyledListItem>
);
})}
</div>
<ShortcutsPanel shortcutsInfo={generalShortcutsInfo} />
<ShortcutsPanel shortcutsInfo={pageShortcutsInfo} />
<ShortcutsPanel shortcutsInfo={edgelessShortcutsInfo} />
<ShortcutsPanel shortcutsInfo={markdownShortcutsInfo} />
</div>
</MuiClickAwayListener>
</StyledShortcutsModal>
</div>
</MuiSlide>
);
};
@@ -0,0 +1,89 @@
import { globalStyle, style } from '@vanilla-extract/css';
export const shortcutsModal = style({
width: '288px',
height: '74vh',
paddingBottom: '28px',
backgroundColor: 'var(--affine-white)',
boxShadow: 'var(--affine-popover-shadow)',
borderRadius: `var(--affine-popover-radius)`,
overflow: 'auto',
position: 'fixed',
right: '12px',
top: '0',
bottom: '0',
margin: 'auto',
zIndex: 'var(--affine-z-index-modal)',
});
// export const shortcutsModal = style({
// color: 'var(--affine-text-primary-color)',
// fontWeight: '500',
// fontSize: 'var(--affine-font-sm)',
// height: '44px',
// display: 'flex',
// justifyContent: 'center',
// alignItems: 'center',
// svg: {
// width: '20px',
// marginRight: '14px',
// color: 'var(--affine-primary-color)',
// },
// });
export const title = style({
color: 'var(--affine-text-primary-color)',
fontWeight: '500',
fontSize: 'var(--affine-font-sm)',
height: '44px',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
});
globalStyle(`${title} svg`, {
width: '20px',
marginRight: '14px',
color: 'var(--affine-primary-color)',
});
export const subtitle = style({
fontWeight: '500',
fontSize: 'var(--affine-font-sm)',
height: '34px',
lineHeight: '36px',
marginTop: '28px',
padding: '0 16px',
});
export const modalHeader = style({
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
paddingTop: '8px 4px 0 4px',
width: '100%',
padding: '8px 16px 0 16px',
position: 'sticky',
left: '0',
top: '0',
background: 'var(--affine-white)',
transition: 'background-color 0.5s',
});
export const listItem = style({
height: '34px',
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
fontSize: 'var(--affine-font-sm)',
padding: '0 16px',
});
export const keyContainer = style({
display: 'flex',
});
export const key = style({
selectors: {
'&:not(:last-child)::after': {
content: '+',
margin: '0 4px',
},
},
});
@@ -1,56 +0,0 @@
import { displayFlex, styled } from '@affine/component';
export const StyledShortcutsModal = styled('div')(() => ({
width: '288px',
height: '74vh',
paddingBottom: '28px',
backgroundColor: 'var(--affine-white)',
boxShadow: 'var(--affine-popover-shadow)',
borderRadius: `var(--affine-popover-radius)`,
overflow: 'auto',
boxRadius: '10px',
position: 'fixed',
right: '12px',
top: '0',
bottom: '0',
margin: 'auto',
zIndex: 'var(--affine-z-index-modal)',
}));
export const StyledTitle = styled('div')(() => ({
color: 'var(--affine-text-primary-color)',
fontWeight: '500',
fontSize: 'var(--affine-font-sm)',
height: '44px',
...displayFlex('center', 'center'),
svg: {
width: '20px',
marginRight: '14px',
color: 'var(--affine-primary-color)',
},
}));
export const StyledSubTitle = styled('div')(() => ({
fontWeight: '500',
fontSize: 'var(--affine-font-sm)',
height: '34px',
lineHeight: '36px',
marginTop: '28px',
padding: '0 16px',
}));
export const StyledModalHeader = styled('div')(() => ({
...displayFlex('space-between', 'center'),
paddingTop: '8px 4px 0 4px',
width: '100%',
padding: '8px 16px 0 16px',
position: 'sticky',
left: '0',
top: '0',
background: 'var(--affine-white)',
transition: 'background-color 0.5s',
}));
export const StyledListItem = styled('div')(() => ({
height: '34px',
...displayFlex('space-between', 'center'),
fontSize: 'var(--affine-font-sm)',
padding: '0 16px',
}));
@@ -8,6 +8,7 @@ export const group = style({
display: 'flex',
gap: '24px',
justifyContent: 'center',
zIndex: 2,
});
export const buttonContainer = style({
boxShadow: 'var(--affine-float-button-shadow-2)',
@@ -1,12 +1,4 @@
import {
Menu,
MenuItem,
Modal,
ModalCloseButton,
ModalWrapper,
Tooltip,
} from '@affine/component';
import { ScrollableContainer } from '@affine/component';
import { Menu, MenuItem, Tooltip } from '@affine/component';
import { WorkspaceList } from '@affine/component/workspace-list';
import type {
AffineCloudWorkspace,
@@ -15,28 +7,40 @@ import type {
import { WorkspaceFlavour } from '@affine/env/workspace';
import { useAFFiNEI18N } from '@affine/i18n/hooks';
import type { RootWorkspaceMetadata } from '@affine/workspace/atom';
import { HelpIcon, ImportIcon, PlusIcon } from '@blocksuite/icons';
import type { DragEndEvent } from '@dnd-kit/core';
import { useCallback, useRef } from 'react';
import type { AllWorkspace } from '../../../shared';
import { Footer } from '../footer';
import {
StyledCreateWorkspaceCard,
CloudWorkspaceIcon,
HelpIcon,
ImportIcon,
MoreHorizontalIcon,
PlusIcon,
} from '@blocksuite/icons';
import type { DragEndEvent } from '@dnd-kit/core';
import { Popover } from '@mui/material';
import { IconButton } from '@toeverything/components/button';
import { Divider } from '@toeverything/components/divider';
import { useSetAtom } from 'jotai';
import { useCallback } from 'react';
import { openDisableCloudAlertModalAtom } from '../../../atoms';
import type { AllWorkspace } from '../../../shared';
import {
StyledCreateWorkspaceCardPill,
StyledCreateWorkspaceCardPillContainer,
StyledCreateWorkspaceCardPillContent,
StyledCreateWorkspaceCardPillIcon,
StyledCreateWorkspaceCardPillTextSecondary,
StyledHelperContainer,
StyledImportWorkspaceCardPill,
StyledModalBody,
StyledModalContent,
StyledModalFooterContent,
StyledModalHeader,
StyledModalHeaderContent,
StyledModalHeaderLeft,
StyledModalTitle,
StyledOperationWrapper,
StyleWorkspaceAdd,
StyleWorkspaceInfo,
StyleWorkspaceTitle,
StyledSignInCardPill,
StyledSignInCardPillTextCotainer,
StyledSignInCardPillTextPrimary,
StyledSignInCardPillTextSecondary,
} from './styles';
interface WorkspaceModalProps {
@@ -52,105 +56,94 @@ interface WorkspaceModalProps {
onMoveWorkspace: (activeId: string, overId: string) => void;
}
const CreateWorkspaceCard = ({
onNewWorkspace,
onAddWorkspace,
}: {
onNewWorkspace: () => void;
onAddWorkspace: () => void;
}) => {
const AccountMenu = () => {
const t = useAFFiNEI18N();
const anchorEL = useRef<HTMLDivElement>(null);
return (
<div>
<div>Unlimted</div>
<Divider></Divider>
<MenuItem icon={<ImportIcon />} data-testid="editor-option-menu-import">
{t['com.affine.workspace.cloud.join']()}
</MenuItem>
<MenuItem icon={<ImportIcon />} data-testid="editor-option-menu-import">
{t['com.affine.workspace.cloud.account.settings']()}
</MenuItem>
<Divider></Divider>
<MenuItem icon={<ImportIcon />} data-testid="editor-option-menu-import">
{t['com.affine.workspace.cloud.account.logout']()}
</MenuItem>
</div>
);
};
if (runtimeConfig.enableSQLiteProvider && environment.isDesktop) {
return (
<Menu
placement="auto"
trigger={['click']}
zIndex={1000}
content={
<StyledCreateWorkspaceCardPillContainer>
<StyledCreateWorkspaceCardPill>
<MenuItem
style={{
height: 'auto',
padding: '8px 12px',
}}
onClick={onNewWorkspace}
data-testid="new-workspace"
>
<StyledCreateWorkspaceCardPillContent>
<div>
<p>{t['New Workspace']()}</p>
<StyledCreateWorkspaceCardPillTextSecondary>
<p>{t['Create your own workspace']()}</p>
</StyledCreateWorkspaceCardPillTextSecondary>
</div>
<StyledCreateWorkspaceCardPillIcon>
<PlusIcon />
</StyledCreateWorkspaceCardPillIcon>
</StyledCreateWorkspaceCardPillContent>
</MenuItem>
</StyledCreateWorkspaceCardPill>
<StyledCreateWorkspaceCardPill>
<MenuItem
onClick={onAddWorkspace}
data-testid="add-workspace"
style={{
height: 'auto',
padding: '8px 12px',
}}
>
<StyledCreateWorkspaceCardPillContent>
<div>
<p>{t['Add Workspace']()}</p>
<StyledCreateWorkspaceCardPillTextSecondary>
<p>{t['Add Workspace Hint']()}</p>
</StyledCreateWorkspaceCardPillTextSecondary>
</div>
<StyledCreateWorkspaceCardPillIcon>
<ImportIcon />
</StyledCreateWorkspaceCardPillIcon>
</StyledCreateWorkspaceCardPillContent>
</MenuItem>
</StyledCreateWorkspaceCardPill>
</StyledCreateWorkspaceCardPillContainer>
}
>
<StyledCreateWorkspaceCard
ref={anchorEL}
data-testid="add-or-new-workspace"
>
<StyleWorkspaceAdd className="add-icon">
<PlusIcon />
</StyleWorkspaceAdd>
const CloudWorkSpaceList = ({
disabled,
workspaces,
onClickWorkspace,
onClickWorkspaceSetting,
currentWorkspaceId,
onMoveWorkspace,
}: WorkspaceModalProps) => {
const t = useAFFiNEI18N();
<StyleWorkspaceInfo>
<StyleWorkspaceTitle>{t['New Workspace']()}</StyleWorkspaceTitle>
<p>{t['Create Or Import']()}</p>
</StyleWorkspaceInfo>
</StyledCreateWorkspaceCard>
</Menu>
);
} else {
return (
<div>
<StyledCreateWorkspaceCard
onClick={onNewWorkspace}
data-testid="new-workspace"
>
<StyleWorkspaceAdd className="add-icon">
<PlusIcon />
</StyleWorkspaceAdd>
return (
<>
<StyledModalHeader>
<StyledModalHeaderLeft>
<StyledModalTitle>
{t['com.affine.workspace.cloud.sync']()}
</StyledModalTitle>
<Tooltip
content={t['Workspace description']()}
placement="top-start"
disablePortal={true}
>
<StyledHelperContainer>
<HelpIcon />
</StyledHelperContainer>
</Tooltip>
</StyledModalHeaderLeft>
<StyleWorkspaceInfo>
<StyleWorkspaceTitle>{t['New Workspace']()}</StyleWorkspaceTitle>
<p>{t['Create Or Import']()}</p>
</StyleWorkspaceInfo>
</StyledCreateWorkspaceCard>
</div>
);
}
<StyledOperationWrapper>
<Menu
placement="bottom-end"
trigger={['click']}
content={<AccountMenu />}
zIndex={1000}
>
<IconButton
data-testid="previous-image-button"
icon={<MoreHorizontalIcon />}
type="plain"
/>
</Menu>
</StyledOperationWrapper>
</StyledModalHeader>
<StyledModalContent>
<WorkspaceList
disabled={disabled}
items={
workspaces.filter(
({ flavour }) => flavour !== WorkspaceFlavour.PUBLIC
) as (AffineCloudWorkspace | LocalWorkspace)[]
}
currentWorkspaceId={currentWorkspaceId}
onClick={onClickWorkspace}
onSettingClick={onClickWorkspaceSetting}
onDragEnd={useCallback(
(event: DragEndEvent) => {
const { active, over } = event;
if (active.id !== over?.id) {
onMoveWorkspace(active.id as string, over?.id as string);
}
},
[onMoveWorkspace]
)}
/>
<Divider />
</StyledModalContent>
</>
);
};
export const WorkspaceListModal = ({
@@ -166,70 +159,152 @@ export const WorkspaceListModal = ({
onMoveWorkspace,
}: WorkspaceModalProps) => {
const t = useAFFiNEI18N();
const setOpen = useSetAtom(openDisableCloudAlertModalAtom);
// TODO: AFFiNE Cloud support
const isLoggedIn = false;
const anchorEl = document.getElementById('current-workspace');
return (
<Modal open={open} onClose={onClose}>
<ModalWrapper
width={720}
height={690}
style={{
<Popover
sx={{
color: 'success.main',
zIndex: 999,
'& .MuiPopover-paper': {
borderRadius: '8px',
display: 'flex',
flexDirection: 'column',
}}
>
<StyledModalHeader>
<StyledModalHeaderLeft>
<StyledModalTitle>{t['My Workspaces']()}</StyledModalTitle>
<Tooltip
content={t['Workspace description']()}
placement="top-start"
disablePortal={true}
>
<StyledHelperContainer>
<HelpIcon />
</StyledHelperContainer>
</Tooltip>
</StyledModalHeaderLeft>
<StyledOperationWrapper>
<ModalCloseButton
data-testid="close-workspace-modal"
onClick={() => {
onClose();
}}
absolute={false}
/>
</StyledOperationWrapper>
</StyledModalHeader>
<ScrollableContainer>
<StyledModalContent>
<WorkspaceList
disabled={disabled}
items={
workspaces.filter(
({ flavour }) => flavour !== WorkspaceFlavour.PUBLIC
) as (AffineCloudWorkspace | LocalWorkspace)[]
boxShadow: 'var(--affine-shadow-2)',
backgroundColor: 'var(--affine-background-overlay-panel-color)',
},
maxHeight: '90vh',
}}
open={open}
anchorEl={anchorEl}
onClose={onClose}
>
<StyledModalHeaderContent>
<StyledSignInCardPill>
<MenuItem
style={{
height: 'auto',
padding: '8px 12px',
}}
onClick={async () => {
if (!runtimeConfig.enableCloud) {
setOpen(true);
}
currentWorkspaceId={currentWorkspaceId}
onClick={onClickWorkspace}
onSettingClick={onClickWorkspaceSetting}
onDragEnd={useCallback(
(event: DragEndEvent) => {
const { active, over } = event;
if (active.id !== over?.id) {
onMoveWorkspace(active.id as string, over?.id as string);
}
},
[onMoveWorkspace]
)}
/>
<CreateWorkspaceCard
onNewWorkspace={onNewWorkspace}
onAddWorkspace={onAddWorkspace}
/>
</StyledModalContent>
</ScrollableContainer>
<Footer />
</ModalWrapper>
</Modal>
}}
data-testid="cloud-signin-button"
>
<StyledCreateWorkspaceCardPillContent>
<StyledCreateWorkspaceCardPillIcon>
<CloudWorkspaceIcon />
</StyledCreateWorkspaceCardPillIcon>
<StyledSignInCardPillTextCotainer>
<StyledSignInCardPillTextPrimary>
{t['com.affine.workspace.cloud.auth']()}
</StyledSignInCardPillTextPrimary>
<StyledSignInCardPillTextSecondary>
Sync with AFFiNE Cloud
</StyledSignInCardPillTextSecondary>
</StyledSignInCardPillTextCotainer>
</StyledCreateWorkspaceCardPillContent>
</MenuItem>
</StyledSignInCardPill>
<Divider />
</StyledModalHeaderContent>
<StyledModalBody>
{isLoggedIn ? (
<CloudWorkSpaceList
disabled={disabled}
open={open}
onClose={onClose}
workspaces={workspaces}
onClickWorkspace={onClickWorkspace}
onClickWorkspaceSetting={onClickWorkspaceSetting}
onNewWorkspace={onNewWorkspace}
onAddWorkspace={onAddWorkspace}
currentWorkspaceId={currentWorkspaceId}
onMoveWorkspace={onMoveWorkspace}
/>
) : null}
<StyledModalHeader>
<StyledModalTitle>{t['Local Workspace']()}</StyledModalTitle>
<Tooltip
content={t['Workspace description']()}
placement="top-start"
disablePortal={true}
>
<StyledHelperContainer>
<HelpIcon />
</StyledHelperContainer>
</Tooltip>
</StyledModalHeader>
<StyledModalContent>
<WorkspaceList
disabled={disabled}
items={
workspaces.filter(
({ flavour }) => flavour !== WorkspaceFlavour.PUBLIC
) as (AffineCloudWorkspace | LocalWorkspace)[]
}
currentWorkspaceId={currentWorkspaceId}
onClick={onClickWorkspace}
onSettingClick={onClickWorkspaceSetting}
onDragEnd={useCallback(
(event: DragEndEvent) => {
const { active, over } = event;
if (active.id !== over?.id) {
onMoveWorkspace(active.id as string, over?.id as string);
}
},
[onMoveWorkspace]
)}
/>
</StyledModalContent>
{runtimeConfig.enableSQLiteProvider && environment.isDesktop ? (
<StyledImportWorkspaceCardPill>
<MenuItem
onClick={onAddWorkspace}
data-testid="add-workspace"
style={{
height: 'auto',
padding: '8px 12px',
}}
>
<StyledCreateWorkspaceCardPillContent>
<StyledCreateWorkspaceCardPillIcon>
<ImportIcon />
</StyledCreateWorkspaceCardPillIcon>
<div>
<p>{t['com.affine.workspace.local.import']()}</p>
</div>
</StyledCreateWorkspaceCardPillContent>
</MenuItem>
</StyledImportWorkspaceCardPill>
) : null}
</StyledModalBody>
<StyledModalFooterContent>
<StyledCreateWorkspaceCardPill>
<MenuItem
style={{
height: 'auto',
padding: '8px 12px',
}}
onClick={onNewWorkspace}
data-testid="new-workspace"
>
<StyledCreateWorkspaceCardPillContent>
<StyledCreateWorkspaceCardPillIcon>
<PlusIcon />
</StyledCreateWorkspaceCardPillIcon>
<div>
<p>{t['New Workspace']()}</p>
</div>
</StyledCreateWorkspaceCardPillContent>
</MenuItem>
</StyledCreateWorkspaceCardPill>
</StyledModalFooterContent>
</Popover>
);
};
@@ -81,11 +81,29 @@ export const StyledCreateWorkspaceCardPillContainer = styled('div')(() => {
});
export const StyledCreateWorkspaceCardPill = styled('div')(() => {
return {
borderRadius: '8px',
display: 'flex',
width: '100%',
height: '58px',
border: `1px solid var(--affine-border-color)`,
};
});
export const StyledSignInCardPill = styled('div')(() => {
return {
borderRadius: '8px',
display: 'flex',
width: '100%',
height: '58px',
};
});
export const StyledImportWorkspaceCardPill = styled('div')(() => {
return {
borderRadius: '5px',
display: 'flex',
boxShadow: '0px 0px 6px 0px rgba(0, 0, 0, 0.1)',
background: 'var(--affine-background-primary-color)',
width: '100%',
};
});
@@ -94,7 +112,6 @@ export const StyledCreateWorkspaceCardPillContent = styled('div')(() => {
display: 'flex',
gap: '12px',
alignItems: 'center',
justifyContent: 'space-between',
};
});
@@ -106,13 +123,30 @@ export const StyledCreateWorkspaceCardPillIcon = styled('div')(() => {
};
});
export const StyledCreateWorkspaceCardPillTextSecondary = styled('div')(() => {
export const StyledSignInCardPillTextCotainer = styled('div')(() => {
return {
display: 'flex',
flexDirection: 'column',
};
});
export const StyledSignInCardPillTextSecondary = styled('div')(() => {
return {
fontSize: '12px',
color: 'var(--affine-text-secondary-color)',
};
});
export const StyledSignInCardPillTextPrimary = styled('div')(() => {
return {
fontSize: 'var(--affine-font-base)',
fontWeight: 600,
lineHeight: '24px',
maxWidth: '200px',
...textEllipsis(1),
};
});
export const StyledModalHeaderLeft = styled('div')(() => {
return { ...displayFlex('flex-start', 'center') };
});
@@ -120,6 +154,7 @@ export const StyledModalTitle = styled('div')(() => {
return {
fontWeight: 600,
fontSize: 'var(--affine-font-h6)',
color: 'var(--affine-text-primary-color)',
};
});
@@ -134,11 +169,30 @@ export const StyledHelperContainer = styled('div')(() => {
});
export const StyledModalContent = styled('div')({
height: '540px',
padding: '8px 40px',
...displayFlex('space-between', 'flex-start', 'flex-start'),
flexWrap: 'wrap',
flexDirection: 'column',
width: '100%',
});
export const StyledModalFooterContent = styled('div')({
...displayFlex('space-between', 'flex-start', 'flex-start'),
flexWrap: 'wrap',
flexDirection: 'column',
width: '100%',
padding: '12px',
backgroundColor: 'var(--affine-background-overlay-panel-color)',
});
export const StyledModalHeaderContent = styled('div')({
...displayFlex('space-between', 'flex-start', 'flex-start'),
flexWrap: 'wrap',
flexDirection: 'column',
width: '100%',
padding: '12px 12px 0px 12px',
backgroundColor: 'var(--affine-background-overlay-panel-color)',
});
export const StyledOperationWrapper = styled('div')(() => {
return {
...displayFlex('flex-end', 'center'),
@@ -150,23 +204,34 @@ export const StyleWorkspaceAdd = styled('div')(() => {
width: '58px',
height: '58px',
borderRadius: '100%',
background: 'var(--affine-white-80)',
background: 'var(--affine-background-overlay-panel-color)',
border: '1.5px dashed #f4f5fa',
transition: 'background .2s',
fontSize: '24px',
...displayFlex('center', 'center'),
borderColor: 'var(--affine-white)',
color: 'var(--affine-primary-color)',
color: 'var(--affine-background-overlay-panel-color)',
};
});
export const StyledModalHeader = styled('div')(() => {
return {
width: '100%',
marginTop: '10px',
left: 0,
top: 0,
borderRadius: '24px 24px 0 0',
padding: '10px 40px',
padding: '12px 14px',
...displayFlex('space-between', 'center'),
};
});
export const StyledModalBody = styled('div')(() => {
return {
padding: '0px 12px',
display: 'inline-flex',
flexDirection: 'column',
alignItems: 'flex-start',
gap: '12px',
flex: 1,
overflowY: 'auto',
};
});
@@ -6,7 +6,6 @@ export const StyledSelectorContainer = styled('div')(() => {
display: 'flex',
alignItems: 'center',
padding: '0 6px',
margin: '0 -6px',
borderRadius: '8px',
color: 'var(--affine-text-primary-color)',
':hover': {
@@ -32,6 +32,7 @@ export const WorkspaceSelector = ({
// Open dialog when `Enter` or `Space` pressed
// TODO-Doma Refactor with `@radix-ui/react-dialog` or other libraries that handle these out of the box and be accessible by default
// TODO: Delete this?
const handleKeyDown = useCallback(
(e: React.KeyboardEvent) => {
if (e.key === 'Enter' || e.key === ' ') {
@@ -50,6 +51,7 @@ export const WorkspaceSelector = ({
onClick={onClick}
onKeyDown={handleKeyDown}
data-testid="current-workspace"
id="current-workspace"
>
<WorkspaceAvatar
data-testid="workspace-avatar"
@@ -211,7 +211,7 @@ const CollectionRenderer = ({
}
>
<div data-testid="collection-options" className={styles.more}>
<MoreHorizontalIcon></MoreHorizontalIcon>
<MoreHorizontalIcon />
</div>
</Menu>
}
@@ -228,8 +228,8 @@ const CollectionRenderer = ({
<div>{collection.name}</div>
</div>
</MenuItem>
<Collapsible.Content>
<div style={{ marginLeft: 8 }}>
<Collapsible.Content className={styles.collapsibleContent}>
<div style={{ marginLeft: 20, marginTop: -4 }}>
{pagesToRender.map(page => {
return (
<Page
@@ -257,6 +257,7 @@ export const CollectionsList = ({ workspace }: CollectionsListProps) => {
() => savedCollections.filter(v => v.pinned),
[savedCollections]
);
const t = useAFFiNEI18N();
if (pinedCollections.length === 0) {
return (
<MenuItem
@@ -264,7 +265,7 @@ export const CollectionsList = ({ workspace }: CollectionsListProps) => {
icon={<InformationIcon />}
disabled
>
<span>Create a collection</span>
<span>{t['Create a collection']()}</span>
</MenuItem>
);
}
@@ -182,20 +182,18 @@ export const Page = ({
>
{page.title || t['Untitled']()}
</MenuItem>
<Collapsible.Content>
<div style={{ marginLeft: 8 }}>
{referencesToRender.map(id => {
return (
<ReferencePage
key={id}
workspace={workspace}
pageId={id}
metaMapping={allPageMeta}
parentIds={new Set([pageId])}
/>
);
})}
</div>
<Collapsible.Content className={styles.collapsibleContent}>
{referencesToRender.map(id => {
return (
<ReferencePage
key={id}
workspace={workspace}
pageId={id}
metaMapping={allPageMeta}
parentIds={new Set([pageId])}
/>
);
})}
</Collapsible.Content>
</Collapsible.Root>
);
@@ -1,4 +1,4 @@
import { globalStyle, style } from '@vanilla-extract/css';
import { globalStyle, keyframes, style } from '@vanilla-extract/css';
export const wrapper = style({
userSelect: 'none',
@@ -29,8 +29,9 @@ export const more = style({
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
borderRadius: 4,
padding: 4,
borderRadius: 2,
fontSize: 16,
color: 'var(--affine-icon-color)',
':hover': {
backgroundColor: 'var(--affine-hover-color)',
},
@@ -52,3 +53,34 @@ export const menuDividerStyle = style({
height: '1px',
background: 'var(--affine-border-color)',
});
const slideDown = keyframes({
'0%': {
height: '0px',
},
'100%': {
height: 'var(--radix-collapsible-content-height)',
},
});
const slideUp = keyframes({
'0%': {
height: 'var(--radix-collapsible-content-height)',
},
'100%': {
height: '0px',
},
});
export const collapsibleContent = style({
overflow: 'hidden',
marginTop: '4px',
selectors: {
'&[data-state="open"]': {
animation: `${slideDown} 0.2s ease-out`,
},
'&[data-state="closed"]': {
animation: `${slideUp} 0.2s ease-out`,
},
},
});
@@ -29,14 +29,8 @@ export const ReferencePage = ({
const icon = setting?.mode === 'edgeless' ? <EdgelessIcon /> : <PageIcon />;
const references = useBlockSuitePageReferences(workspace, pageId);
const referencesToShow = useMemo(() => {
return [
...new Set(
references.filter(
ref => !parentIds.has(ref) && !metaMapping[ref]?.trash
)
),
];
}, [references, parentIds, metaMapping]);
return [...new Set(references.filter(ref => !metaMapping[ref]?.trash))];
}, [references, metaMapping]);
const [collapsed, setCollapsed] = useState(true);
const collapsible = referencesToShow.length > 0;
const nestedItem = parentIds.size > 0;
@@ -11,11 +11,13 @@ export const label = style({
export const favItemWrapper = style({
display: 'flex',
flexDirection: 'column',
gap: '4px',
selectors: {
'&[data-nested="true"]': {
marginLeft: '12px',
width: 'calc(100% - 12px)',
marginLeft: '20px',
width: 'calc(100% - 20px)',
},
'&:not(:first-of-type)': {
marginTop: '4px',
},
},
});
@@ -40,6 +42,7 @@ const slideUp = keyframes({
export const collapsibleContent = style({
overflow: 'hidden',
marginTop: '4px',
selectors: {
'&[data-state="open"]': {
animation: `${slideDown} 0.2s ease-out`,
@@ -53,5 +56,4 @@ export const collapsibleContent = style({
export const collapsibleContentInner = style({
display: 'flex',
flexDirection: 'column',
gap: '4px',
});
@@ -51,14 +51,14 @@ export type RootAppSidebarProps = {
};
const RouteMenuLinkItem = React.forwardRef<
HTMLDivElement,
HTMLButtonElement,
{
currentPath: string; // todo: pass through useRouter?
path: string;
icon: ReactElement;
children?: ReactElement;
isDraggedOver?: boolean;
} & React.HTMLAttributes<HTMLDivElement>
} & React.HTMLAttributes<HTMLButtonElement>
>(({ currentPath, path, icon, children, isDraggedOver, ...props }, ref) => {
// Force active style when a page is dragged over
const active = isDraggedOver || currentPath === path;
@@ -196,6 +196,8 @@ export const RootAppSidebar = ({
</CategoryDivider>
<CollectionsList workspace={blockSuiteWorkspace} />
<CategoryDivider label={t['others']()} />
{/* fixme: remove the following spacer */}
<div style={{ height: '4px' }} />
<RouteMenuLinkItem
ref={trashDroppable.setNodeRef}
isDraggedOver={trashDroppable.isOver}
@@ -211,7 +213,7 @@ export const RootAppSidebar = ({
</SidebarScrollableContainer>
<SidebarContainer>
{isDesktop && <AppUpdaterButton />}
<div />
<div style={{ height: '4px' }} />
<AddPageButton onClick={onClickNewPage} />
</SidebarContainer>
</AppSidebar>
@@ -100,7 +100,6 @@ export function WorkspaceHeader({
/>
}
center={<WorkspaceModeFilterTab />}
right={<PluginHeader />}
/>
{<FilterContainer workspaceId={currentWorkspaceId} />}
</>
+151 -128
View File
@@ -1,20 +1,24 @@
import { useAFFiNEI18N } from '@affine/i18n/hooks';
import { useMemo } from 'react';
interface ShortcutTip {
[x: string]: string;
interface ShortcutMap {
[x: string]: string[];
}
export interface ShortcutsInfo {
title: string;
shortcuts: ShortcutMap;
}
export const useWinGeneralKeyboardShortcuts = (): ShortcutTip => {
export const useWinGeneralKeyboardShortcuts = (): ShortcutMap => {
const t = useAFFiNEI18N();
return useMemo(
() => ({
[t['Cancel']()]: 'ESC',
[t['Quick Search']()]: 'Ctrl + K',
[t['New Page']()]: 'Ctrl + N',
[t['Cancel']()]: ['ESC'],
[t['Quick Search']()]: ['Ctrl', 'K'],
[t['New Page']()]: ['Ctrl', 'N'],
// not implement yet
// [t['Append to Daily Note']()]: 'Ctrl + Alt + A',
[t['Expand/Collapse Sidebar']()]: 'Ctrl + /',
[t['Expand/Collapse Sidebar']()]: ['Ctrl', '/'],
// not implement yet
// [t['Go Back']()]: 'Ctrl + [',
// [t['Go Forward']()]: 'Ctrl + ]',
@@ -22,16 +26,16 @@ export const useWinGeneralKeyboardShortcuts = (): ShortcutTip => {
[t]
);
};
export const useMacGeneralKeyboardShortcuts = (): ShortcutTip => {
export const useMacGeneralKeyboardShortcuts = (): ShortcutMap => {
const t = useAFFiNEI18N();
return useMemo(
() => ({
[t['Cancel']()]: 'ESC',
[t['Quick Search']()]: '⌘ + K',
[t['New Page']()]: '⌘ + N',
[t['Cancel']()]: ['ESC'],
[t['Quick Search']()]: ['⌘', 'K'],
[t['New Page']()]: ['⌘', 'N'],
// not implement yet
// [t['Append to Daily Note']()]: '⌘ + ⌥ + A',
[t['Expand/Collapse Sidebar']()]: '⌘ + /',
[t['Expand/Collapse Sidebar']()]: ['⌘', '/'],
// not implement yet
// [t['Go Back']()]: '⌘ + [',
// [t['Go Forward']()]: '⌘ + ]',
@@ -40,28 +44,28 @@ export const useMacGeneralKeyboardShortcuts = (): ShortcutTip => {
);
};
export const useMacEdgelessKeyboardShortcuts = (): ShortcutTip => {
export const useMacEdgelessKeyboardShortcuts = (): ShortcutMap => {
const t = useAFFiNEI18N();
return useMemo(
() => ({
[t['Select All']()]: '⌘ + A',
[t['Undo']()]: '⌘ + Z',
[t['Redo']()]: '⌘ + ⇧ + Z',
[t['Zoom in']()]: '⌘ + +',
[t['Zoom out']()]: '⌘ + -',
[t['Zoom to 100%']()]: '⌘ + 0',
[t['Zoom to fit']()]: '⌘ + 1',
[t['Select']()]: 'V',
[t['Text']()]: 'T',
[t['Shape']()]: 'S',
[t['Image']()]: 'I',
[t['Straight Connector']()]: 'L',
[t['Elbowed Connector']()]: 'X',
[t['Select All']()]: ['⌘', 'A'],
[t['Undo']()]: ['⌘', 'Z'],
[t['Redo']()]: ['⌘', '⇧', 'Z'],
[t['Zoom in']()]: ['⌘', '+'],
[t['Zoom out']()]: ['⌘', '-'],
[t['Zoom to 100%']()]: ['⌘', '0'],
[t['Zoom to fit']()]: ['⌘', '1'],
[t['Select']()]: ['V'],
[t['Text']()]: ['T'],
[t['Shape']()]: ['S'],
[t['Image']()]: ['I'],
[t['Straight Connector']()]: ['L'],
[t['Elbowed Connector']()]: ['X'],
// not implement yet
// [t['Curve Connector']()]: 'C',
[t['Pen']()]: 'P',
[t['Hand']()]: 'H',
[t['Note']()]: 'N',
[t['Pen']()]: ['P'],
[t['Hand']()]: ['H'],
[t['Note']()]: ['N'],
// not implement yet
// [t['Group']()]: '⌘ + G',
// [t['Ungroup']()]: '⌘ + ⇧ + G',
@@ -69,29 +73,29 @@ export const useMacEdgelessKeyboardShortcuts = (): ShortcutTip => {
[t]
);
};
export const useWinEdgelessKeyboardShortcuts = (): ShortcutTip => {
export const useWinEdgelessKeyboardShortcuts = (): ShortcutMap => {
const t = useAFFiNEI18N();
return useMemo(
() => ({
[t['Select All']()]: 'Ctrl + A',
[t['Undo']()]: 'Ctrl + Z',
[t['Redo']()]: 'Ctrl + Y/Ctrl + Shift + Z',
[t['Zoom in']()]: 'Ctrl + +',
[t['Zoom out']()]: 'Ctrl + -',
[t['Zoom to 100%']()]: 'Ctrl + 0',
[t['Zoom to fit']()]: 'Ctrl + 1',
[t['Select']()]: 'V',
[t['Text']()]: 'T',
[t['Shape']()]: 'S',
[t['Image']()]: 'I',
[t['Straight Connector']()]: 'L',
[t['Elbowed Connector']()]: 'X',
[t['Select All']()]: ['Ctrl', 'A'],
[t['Undo']()]: ['Ctrl', 'Z'],
[t['Redo']()]: ['Ctrl', 'Y/Ctrl', 'Shift', 'Z'],
[t['Zoom in']()]: ['Ctrl', '+'],
[t['Zoom out']()]: ['Ctrl', '-'],
[t['Zoom to 100%']()]: ['Ctrl', '0'],
[t['Zoom to fit']()]: ['Ctrl', '1'],
[t['Select']()]: ['V'],
[t['Text']()]: ['T'],
[t['Shape']()]: ['S'],
[t['Image']()]: ['I'],
[t['Straight Connector']()]: ['L'],
[t['Elbowed Connector']()]: ['X'],
// not implement yet
// [t['Curve Connector']()]: 'C',
[t['Pen']()]: 'P',
[t['Hand']()]: 'H',
[t['Note']()]: 'N',
[t['Switch']()]: 'Alt + S',
[t['Pen']()]: ['P'],
[t['Hand']()]: ['H'],
[t['Note']()]: ['N'],
[t['Switch']()]: ['Alt ', ''],
// not implement yet
// [t['Group']()]: 'Ctrl + G',
// [t['Ungroup']()]: 'Ctrl + Shift + G',
@@ -99,31 +103,31 @@ export const useWinEdgelessKeyboardShortcuts = (): ShortcutTip => {
[t]
);
};
export const useMacPageKeyboardShortcuts = (): ShortcutTip => {
export const useMacPageKeyboardShortcuts = (): ShortcutMap => {
const t = useAFFiNEI18N();
return useMemo(
() => ({
[t['Undo']()]: '⌘+Z',
[t['Redo']()]: '⌘+⇧+Z',
[t['Bold']()]: '⌘+B',
[t['Italic']()]: '⌘+I',
[t['Underline']()]: '⌘+U',
[t['Strikethrough']()]: '⌘+⇧+S',
[t['Inline code']()]: ' ⌘+E',
[t['Code block']()]: '⌘+⌥+C',
[t['Link']()]: '⌘+K',
[t['Quick search']()]: '⌘+K',
[t['Body text']()]: '⌘+⌥+0',
[t['Heading']({ number: '1' })]: '⌘+⌥+1',
[t['Heading']({ number: '2' })]: '⌘+⌥+2',
[t['Heading']({ number: '3' })]: '⌘+⌥+3',
[t['Heading']({ number: '4' })]: '⌘+⌥+4',
[t['Heading']({ number: '5' })]: '⌘+⌥+5',
[t['Heading']({ number: '6' })]: '⌘+⌥+6',
[t['Increase indent']()]: 'Tab',
[t['Reduce indent']()]: '⇧+Tab',
[t['Group as Database']()]: '⌘ + G',
[t['Switch']()]: '⌥ + S',
[t['Undo']()]: ['⌘', 'Z'],
[t['Redo']()]: ['⌘', '⇧', 'Z'],
[t['Bold']()]: ['⌘', 'B'],
[t['Italic']()]: ['⌘', 'I'],
[t['Underline']()]: ['⌘', 'U'],
[t['Strikethrough']()]: ['⌘', '⇧', 'S'],
[t['Inline code']()]: ['⌘', 'E'],
[t['Code block']()]: ['⌘', '⌥', 'C'],
[t['Link']()]: ['⌘', 'K'],
[t['Quick search']()]: ['⌘', 'K'],
[t['Body text']()]: ['⌘', '⌥', '0'],
[t['Heading']({ number: '1' })]: ['⌘', '⌥', '1'],
[t['Heading']({ number: '2' })]: ['⌘', '⌥', '2'],
[t['Heading']({ number: '3' })]: ['⌘', '⌥', '3'],
[t['Heading']({ number: '4' })]: ['⌘', '⌥', '4'],
[t['Heading']({ number: '5' })]: ['⌘', '⌥', '5'],
[t['Heading']({ number: '6' })]: ['⌘', '⌥', '6'],
[t['Increase indent']()]: ['Tab'],
[t['Reduce indent']()]: ['⇧', 'Tab'],
[t['Group as Database']()]: ['⌘', 'G'],
[t['Switch']()]: ['⌥', 'S'],
// not implement yet
// [t['Move Up']()]: '⌘ + ⌥ + ↑',
// [t['Move Down']()]: '⌘ + ⌥ + ↓',
@@ -132,53 +136,53 @@ export const useMacPageKeyboardShortcuts = (): ShortcutTip => {
);
};
export const useMacMarkdownShortcuts = (): ShortcutTip => {
export const useMacMarkdownShortcuts = (): ShortcutMap => {
const t = useAFFiNEI18N();
return useMemo(
() => ({
[t['Bold']()]: '**Text** ',
[t['Italic']()]: '*Text* ',
[t['Underline']()]: '~Text~ ',
[t['Strikethrough']()]: '~~Text~~ ',
[t['Divider']()]: '***',
[t['Inline code']()]: '`Text` ',
[t['Code block']()]: '``` Space',
[t['Heading']({ number: '1' })]: '# Text',
[t['Heading']({ number: '2' })]: '## Text',
[t['Heading']({ number: '3' })]: '### Text',
[t['Heading']({ number: '4' })]: '#### Text',
[t['Heading']({ number: '5' })]: '##### Text',
[t['Heading']({ number: '6' })]: '###### Text',
[t['Bold']()]: ['**Text**'],
[t['Italic']()]: ['*Text*'],
[t['Underline']()]: ['~Text~'],
[t['Strikethrough']()]: ['~~Text~~'],
[t['Divider']()]: ['***'],
[t['Inline code']()]: ['`Text` '],
[t['Code block']()]: ['``` Space'],
[t['Heading']({ number: '1' })]: ['# Text'],
[t['Heading']({ number: '2' })]: ['## Text'],
[t['Heading']({ number: '3' })]: ['### Text'],
[t['Heading']({ number: '4' })]: ['#### Text'],
[t['Heading']({ number: '5' })]: ['##### Text'],
[t['Heading']({ number: '6' })]: ['###### Text'],
}),
[t]
);
};
export const useWinPageKeyboardShortcuts = (): ShortcutTip => {
export const useWinPageKeyboardShortcuts = (): ShortcutMap => {
const t = useAFFiNEI18N();
return useMemo(
() => ({
[t['Undo']()]: 'Ctrl+Z',
[t['Redo']()]: 'Ctrl+Y',
[t['Bold']()]: 'Ctrl+B',
[t['Italic']()]: 'Ctrl+I',
[t['Underline']()]: 'Ctrl+U',
[t['Strikethrough']()]: 'Ctrl+Shift+S',
[t['Inline code']()]: ' Ctrl+E',
[t['Code block']()]: 'Ctrl+Alt+C',
[t['Link']()]: 'Ctrl+K',
[t['Quick search']()]: 'Ctrl+K',
[t['Body text']()]: 'Ctrl+Shift+0',
[t['Heading']({ number: '1' })]: 'Ctrl+Shift+1',
[t['Heading']({ number: '2' })]: 'Ctrl+Shift+2',
[t['Heading']({ number: '3' })]: 'Ctrl+Shift+3',
[t['Heading']({ number: '4' })]: 'Ctrl+Shift+4',
[t['Heading']({ number: '5' })]: 'Ctrl+Shift+5',
[t['Heading']({ number: '6' })]: 'Ctrl+Shift+6',
[t['Increase indent']()]: 'Tab',
[t['Reduce indent']()]: 'Shift+Tab',
[t['Group as Database']()]: 'Ctrl + G',
['Switch']: 'Alt + S',
[t['Undo']()]: ['Ctrl', 'Z'],
[t['Redo']()]: ['Ctrl', 'Y'],
[t['Bold']()]: ['Ctrl', 'B'],
[t['Italic']()]: ['Ctrl', 'I'],
[t['Underline']()]: ['Ctrl', 'U'],
[t['Strikethrough']()]: ['Ctrl', 'Shift', 'S'],
[t['Inline code']()]: [' Ctrl', 'E'],
[t['Code block']()]: ['Ctrl', 'Alt', 'C'],
[t['Link']()]: ['Ctr', 'K'],
[t['Quick search']()]: ['Ctrl', 'K'],
[t['Body text']()]: ['Ctrl', 'Shift', '0'],
[t['Heading']({ number: '1' })]: ['Ctrl', 'Shift', '1'],
[t['Heading']({ number: '2' })]: ['Ctrl', 'Shift', '2'],
[t['Heading']({ number: '3' })]: ['Ctrl', 'Shift', '3'],
[t['Heading']({ number: '4' })]: ['Ctrl', 'Shift', '4'],
[t['Heading']({ number: '5' })]: ['Ctrl', 'Shift', '5'],
[t['Heading']({ number: '6' })]: ['Ctrl', 'Shift', '6'],
[t['Increase indent']()]: ['Tab'],
[t['Reduce indent']()]: ['Shift+Tab'],
[t['Group as Database']()]: ['Ctrl + G'],
['Switch']: ['Alt + S'],
// not implement yet
// [t['Move Up']()]: 'Ctrl + Alt + ↑',
// [t['Move Down']()]: 'Ctrl + Alt + ↓',
@@ -186,54 +190,73 @@ export const useWinPageKeyboardShortcuts = (): ShortcutTip => {
[t]
);
};
export const useWinMarkdownShortcuts = (): ShortcutTip => {
export const useWinMarkdownShortcuts = (): ShortcutMap => {
const t = useAFFiNEI18N();
return useMemo(
() => ({
[t['Bold']()]: '**Text** ',
[t['Italic']()]: '*Text* ',
[t['Underline']()]: '~Text~ ',
[t['Strikethrough']()]: '~~Text~~ ',
[t['Divider']()]: '***',
[t['Inline code']()]: '`Text` ',
[t['Code block']()]: '``` Text',
[t['Heading']({ number: '1' })]: '# Text',
[t['Heading']({ number: '2' })]: '## Text',
[t['Heading']({ number: '3' })]: '### Text',
[t['Heading']({ number: '4' })]: '#### Text',
[t['Heading']({ number: '5' })]: '##### Text',
[t['Heading']({ number: '6' })]: '###### Text',
[t['Bold']()]: ['**Text** '],
[t['Italic']()]: ['*Text* '],
[t['Underline']()]: ['~Text~ '],
[t['Strikethrough']()]: ['~~Text~~ '],
[t['Divider']()]: ['***'],
[t['Inline code']()]: ['`Text` '],
[t['Code block']()]: ['``` Text'],
[t['Heading']({ number: '1' })]: ['# Text'],
[t['Heading']({ number: '2' })]: ['## Text'],
[t['Heading']({ number: '3' })]: ['### Text'],
[t['Heading']({ number: '4' })]: ['#### Text'],
[t['Heading']({ number: '5' })]: ['##### Text'],
[t['Heading']({ number: '6' })]: ['###### Text'],
}),
[t]
);
};
export const useMarkdownShortcuts = (): ShortcutTip => {
export const useMarkdownShortcuts = (): ShortcutsInfo => {
const t = useAFFiNEI18N();
const macMarkdownShortcuts = useMacMarkdownShortcuts();
const winMarkdownShortcuts = useWinMarkdownShortcuts();
const isMac = environment.isBrowser && environment.isMacOs;
return isMac ? macMarkdownShortcuts : winMarkdownShortcuts;
return {
title: t['Markdown Syntax'](),
shortcuts: isMac ? macMarkdownShortcuts : winMarkdownShortcuts,
};
};
export const usePageShortcuts = (): ShortcutTip => {
export const usePageShortcuts = (): ShortcutsInfo => {
const t = useAFFiNEI18N();
const macPageShortcuts = useMacPageKeyboardShortcuts();
const winPageShortcuts = useWinPageKeyboardShortcuts();
const isMac = environment.isBrowser && environment.isMacOs;
return isMac ? macPageShortcuts : winPageShortcuts;
return {
title: t['Page'](),
shortcuts: isMac ? macPageShortcuts : winPageShortcuts,
};
};
export const useEdgelessShortcuts = (): ShortcutTip => {
export const useEdgelessShortcuts = (): ShortcutsInfo => {
const t = useAFFiNEI18N();
const macEdgelessShortcuts = useMacEdgelessKeyboardShortcuts();
const winEdgelessShortcuts = useWinEdgelessKeyboardShortcuts();
const isMac = environment.isBrowser && environment.isMacOs;
return isMac ? macEdgelessShortcuts : winEdgelessShortcuts;
return {
title: t['Edgeless'](),
shortcuts: isMac ? macEdgelessShortcuts : winEdgelessShortcuts,
};
};
export const useGeneralShortcuts = (): ShortcutTip => {
export const useGeneralShortcuts = (): ShortcutsInfo => {
const t = useAFFiNEI18N();
const macGeneralShortcuts = useMacGeneralKeyboardShortcuts();
const winGeneralShortcuts = useWinGeneralKeyboardShortcuts();
const isMac = environment.isBrowser && environment.isMacOs;
return isMac ? macGeneralShortcuts : winGeneralShortcuts;
return {
title: t['General'](),
shortcuts: isMac ? macGeneralShortcuts : winGeneralShortcuts,
};
};
+8 -1
View File
@@ -5,12 +5,14 @@ import { saveWorkspaceToLocalStorage } from '@affine/workspace/local/crud';
import { getOrCreateWorkspace } from '@affine/workspace/manager';
import { nanoid } from '@blocksuite/store';
import { getWorkspace } from '@toeverything/infra/__internal__/workspace';
import { rootStore } from '@toeverything/infra/atom';
import { buildShowcaseWorkspace } from '@toeverything/infra/blocksuite';
import { useAtomValue, useSetAtom } from 'jotai';
import { useCallback } from 'react';
import { LocalAdapter } from '../adapters/local';
import { WorkspaceAdapters } from '../adapters/workspace';
import { setPageModeAtom } from '../atoms';
const logger = new DebugLogger('use-workspaces');
@@ -52,7 +54,12 @@ export function useAppHelper() {
id,
WorkspaceFlavour.LOCAL
);
await buildShowcaseWorkspace(blockSuiteWorkspace);
await buildShowcaseWorkspace(blockSuiteWorkspace, {
store: rootStore,
atoms: {
pageMode: setPageModeAtom,
},
});
}
set(workspaces => [
...workspaces,
@@ -8,6 +8,7 @@ import type { EditorContainer } from '@blocksuite/editor';
import { assertExists } from '@blocksuite/global/utils';
import type { Page } from '@blocksuite/store';
import {
contentLayoutAtom,
currentPageIdAtom,
currentWorkspaceAtom,
currentWorkspaceIdAtom,
@@ -86,6 +87,7 @@ export const DetailPage = (): ReactElement => {
};
export const loader: LoaderFunction = async args => {
rootStore.set(contentLayoutAtom, 'editor');
if (args.params.workspaceId) {
localStorage.setItem('last_workspace_id', args.params.workspaceId);
rootStore.set(currentWorkspaceIdAtom, args.params.workspaceId);
+10 -5
View File
@@ -126,6 +126,7 @@ export const AllWorkspaceModals = (): ReactElement => {
);
const setCurrentPageId = useSetAtom(currentPageIdAtom);
const [isPending, startTransition] = useTransition();
const [, startCloseTransition] = useTransition();
const [, setOpenSettingModalAtom] = useAtom(openSettingModalAtom);
const handleOpenSettingModal = useCallback(
@@ -153,7 +154,9 @@ export const AllWorkspaceModals = (): ReactElement => {
isOpenCreateWorkspaceModal === false
}
onClose={useCallback(() => {
setOpenWorkspacesModal(false);
startCloseTransition(() => {
setOpenWorkspacesModal(false);
});
}, [setOpenWorkspacesModal])}
onMoveWorkspace={useCallback(
(activeId, overId) => {
@@ -169,10 +172,12 @@ export const AllWorkspaceModals = (): ReactElement => {
)}
onClickWorkspace={useCallback(
workspaceId => {
setOpenWorkspacesModal(false);
setCurrentWorkspaceId(workspaceId);
setCurrentPageId(null);
jumpToSubPath(workspaceId, WorkspaceSubPath.ALL);
startCloseTransition(() => {
setOpenWorkspacesModal(false);
setCurrentWorkspaceId(workspaceId);
setCurrentPageId(null);
jumpToSubPath(workspaceId, WorkspaceSubPath.ALL);
});
},
[
jumpToSubPath,
+38 -38
View File
@@ -1,41 +1,41 @@
import type { RouteObject } from 'react-router-dom';
import { createBrowserRouter } from 'react-router-dom';
export const router = createBrowserRouter(
[
{
path: '/',
lazy: () => import('./pages/index'),
},
{
path: '/workspace/:workspaceId',
lazy: () => import('./pages/workspace/index'),
children: [
{
path: 'all',
lazy: () => import('./pages/workspace/all-page'),
},
{
path: 'trash',
lazy: () => import('./pages/workspace/trash-page'),
},
{
path: ':pageId',
lazy: () => import('./pages/workspace/detail-page'),
},
],
},
{
path: '/404',
lazy: () => import('./pages/404'),
},
{
path: '*',
lazy: () => import('./pages/404'),
},
],
export const routes = [
{
future: {
v7_normalizeFormMethod: true,
},
}
);
path: '/',
lazy: () => import('./pages/index'),
},
{
path: '/workspace/:workspaceId',
lazy: () => import('./pages/workspace/index'),
children: [
{
path: 'all',
lazy: () => import('./pages/workspace/all-page'),
},
{
path: 'trash',
lazy: () => import('./pages/workspace/trash-page'),
},
{
path: ':pageId',
lazy: () => import('./pages/workspace/detail-page'),
},
],
},
{
path: '/404',
lazy: () => import('./pages/404'),
},
{
path: '*',
lazy: () => import('./pages/404'),
},
] satisfies [RouteObject, ...RouteObject[]];
export const router = createBrowserRouter(routes, {
future: {
v7_normalizeFormMethod: true,
},
});
+1 -1
View File
@@ -5,7 +5,7 @@
"typeRoots": ["../../node_modules", "../../node_modules/@types"],
"types": ["webpack-env", "ses", "affine__env"]
},
"include": ["src/**/*.ts", "src/**/*.tsx"],
"include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.json"],
"exclude": ["node_modules"],
"references": [
{
+8 -8
View File
@@ -1,6 +1,6 @@
{
"name": "@affine/docs",
"version": "0.8.0-canary.21",
"version": "0.8.0-beta.0",
"type": "module",
"private": true,
"scripts": {
@@ -10,12 +10,12 @@
},
"dependencies": {
"@affine/component": "workspace:*",
"@blocksuite/block-std": "0.0.0-20230811201552-f37162ea-nightly",
"@blocksuite/blocks": "0.0.0-20230811201552-f37162ea-nightly",
"@blocksuite/editor": "0.0.0-20230811201552-f37162ea-nightly",
"@blocksuite/global": "0.0.0-20230811201552-f37162ea-nightly",
"@blocksuite/lit": "0.0.0-20230811201552-f37162ea-nightly",
"@blocksuite/store": "0.0.0-20230811201552-f37162ea-nightly",
"@blocksuite/block-std": "0.0.0-20230818053411-bc330b35-nightly",
"@blocksuite/blocks": "0.0.0-20230818053411-bc330b35-nightly",
"@blocksuite/editor": "0.0.0-20230818053411-bc330b35-nightly",
"@blocksuite/global": "0.0.0-20230818053411-bc330b35-nightly",
"@blocksuite/lit": "0.0.0-20230818053411-bc330b35-nightly",
"@blocksuite/store": "0.0.0-20230818053411-bc330b35-nightly",
"express": "^4.18.2",
"jotai": "^2.3.1",
"react": "18.3.0-canary-7118f5dd7-20230705",
@@ -28,7 +28,7 @@
"@types/react-dom": "^18.2.7",
"@vanilla-extract/css": "^1.12.0",
"@vanilla-extract/vite-plugin": "^3.8.2",
"autoprefixer": "^10.4.14",
"autoprefixer": "^10.4.15",
"tailwindcss": "^3.3.3",
"typescript": "^5.1.6"
}
+17 -7
View File
@@ -117,7 +117,7 @@ test('affine cloud disabled', async ({ page }) => {
});
await page.waitForSelector('v-line');
await page.getByTestId('current-workspace').click();
await page.getByTestId('sign-in-button').click();
await page.getByTestId('cloud-signin-button').click();
await page.getByTestId('disable-affine-cloud-modal').waitFor({
state: 'visible',
});
@@ -153,13 +153,23 @@ test('windows only check', async ({ page }) => {
test('delete workspace', async ({ page }) => {
await page.getByTestId('current-workspace').click();
await page.getByTestId('add-or-new-workspace').click();
await page.getByTestId('new-workspace').click();
await page.getByTestId('create-workspace-default-location-button').click();
await page.getByTestId('create-workspace-input').type('Delete Me');
await page.getByTestId('create-workspace-create-button').click();
await page.getByTestId('create-workspace-continue-button').click();
await page.getByTestId('slider-bar-workspace-setting-button').click();
await page.getByTestId('create-workspace-default-location-button').click({
delay: 100,
});
await page.getByTestId('create-workspace-input').type('Delete Me', {
delay: 100,
});
await page.getByTestId('create-workspace-create-button').click({
delay: 100,
});
await page.getByTestId('create-workspace-continue-button').click({
delay: 100,
});
await page.waitForTimeout(1000);
await page.getByTestId('current-workspace').click();
await page.getByTestId('workspace-card').nth(1).hover();
await page.getByTestId('workspace-card-setting-button').nth(1).click();
await page.getByTestId('current-workspace-label').click();
expect(await page.getByTestId('workspace-name-input').inputValue()).toBe(
'Delete Me'
+2 -1
View File
@@ -82,7 +82,7 @@ const makers = [
!process.env.SKIP_BUNDLE && {
name: '@electron-forge/maker-squirrel',
config: {
name: 'AFFiNE',
name: productName,
setupIcon: icoPath,
iconUrl: windowsIconUrl,
loadingGif: './resources/icons/affine_installing.gif',
@@ -130,6 +130,7 @@ module.exports = {
: undefined,
// We need the following line for updater
extraResource: ['./resources/app-update.yml'],
ignore: ['e2e', 'tests'],
},
makers,
hooks: {
+8 -8
View File
@@ -1,7 +1,7 @@
{
"name": "@affine/electron",
"private": true,
"version": "0.8.0-canary.21",
"version": "0.8.0-beta.0",
"author": "affine",
"repository": {
"url": "https://github.com/toeverything/AFFiNE",
@@ -28,10 +28,10 @@
"@affine/env": "workspace:*",
"@affine/native": "workspace:*",
"@affine/sdk": "workspace:*",
"@blocksuite/blocks": "0.0.0-20230811201552-f37162ea-nightly",
"@blocksuite/editor": "0.0.0-20230811201552-f37162ea-nightly",
"@blocksuite/lit": "0.0.0-20230811201552-f37162ea-nightly",
"@blocksuite/store": "0.0.0-20230811201552-f37162ea-nightly",
"@blocksuite/blocks": "0.0.0-20230818053411-bc330b35-nightly",
"@blocksuite/editor": "0.0.0-20230818053411-bc330b35-nightly",
"@blocksuite/lit": "0.0.0-20230818053411-bc330b35-nightly",
"@blocksuite/store": "0.0.0-20230818053411-bc330b35-nightly",
"@electron-forge/cli": "^6.3.0",
"@electron-forge/core": "^6.3.0",
"@electron-forge/core-utils": "^6.3.0",
@@ -44,11 +44,11 @@
"@types/fs-extra": "^11.0.1",
"@types/uuid": "^9.0.2",
"cross-env": "7.0.3",
"electron": "^25.5.0",
"electron": "^26.0.0",
"electron-log": "^5.0.0-beta.25",
"electron-squirrel-startup": "1.0.0",
"electron-window-state": "^5.0.3",
"esbuild": "^0.18.20",
"esbuild": "^0.19.2",
"fs-extra": "^11.1.1",
"jotai": "^2.3.1",
"ts-node": "^10.9.1",
@@ -62,7 +62,7 @@
"@toeverything/infra": "workspace:*",
"async-call-rpc": "^6.3.1",
"electron-updater": "^6.1.4",
"link-preview-js": "^3.0.4",
"link-preview-js": "^3.0.5",
"lodash-es": "^4.17.21",
"nanoid": "^4.0.2",
"rxjs": "^7.8.1",
+1 -17
View File
@@ -1,12 +1,10 @@
/* eslint-disable no-async-promise-executor */
import { spawn } from 'node:child_process';
import { readFileSync } from 'node:fs';
import path from 'node:path';
import electronPath from 'electron';
import * as esbuild from 'esbuild';
import { config, electronDir } from './common.mjs';
import { config } from './common.mjs';
// this means we don't spawn electron windows, mainly for testing
const watchMode = process.argv.includes('--watch');
@@ -19,20 +17,6 @@ const stderrFilterPatterns = [
/ExtensionLoadWarning/,
];
// these are set before calling `config`, so we have a chance to override them
try {
const devJson = readFileSync(
path.resolve(electronDir, './dev.json'),
'utf-8'
);
const devEnv = JSON.parse(devJson);
Object.assign(process.env, devEnv);
} catch (err) {
console.warn(
`Could not read dev.json. Some functions may not work as expected.`
);
}
/** @type {ChildProcessWithoutNullStreams | null} */
let spawnProcess = null;
+3 -1
View File
@@ -37,12 +37,14 @@ if (process.platform === 'win32') {
$.prefix = '';
}
$.env.DISTRIBUTION = 'desktop';
cd(repoRootDir);
// step 1: build web (nextjs) dist
if (!process.env.SKIP_WEB_BUILD) {
await $`yarn -T run build:plugins`;
await $`DISTRIBUTION=desktop yarn nx build @affine/core`;
await $`yarn nx build @affine/core`;
await fs.move(affineCoreOutDir, publicAffineOutDir, { overwrite: true });
}
@@ -91,7 +91,8 @@ test('db should be removed in db$Map after destroyed', async () => {
expect(db$Map.has(workspaceId)).toBe(false);
});
test('if db has a secondary db path, we should also poll that', async () => {
// we have removed secondary db feature
test.skip('if db has a secondary db path, we should also poll that', async () => {
const { ensureSQLiteDB } = await import('../ensure-db');
const { storeWorkspaceMeta } = await import('../../workspace');
const workspaceId = v4();
@@ -44,10 +44,10 @@ export abstract class BaseSQLiteAdapter {
try {
if (!this.db) {
logger.warn(`${this.path} is not connected`);
return;
return null;
}
const blob = await this.db.getBlob(key);
return blob?.data;
return blob?.data ?? null;
} catch (error) {
logger.error('getBlob', error);
return null;
@@ -128,7 +128,7 @@ export class WorkspaceSQLiteDB extends BaseSQLiteAdapter {
if (doc) {
return encodeStateAsUpdate(doc);
}
return null;
return false;
};
// non-blocking and use yDoc to validate the update
+13 -1
View File
@@ -1,12 +1,24 @@
import type {
DBHandlers,
DialogHandlers,
WorkspaceHandlers,
} from '@toeverything/infra/type';
import { dbEvents, dbHandlers } from './db';
import { dialogHandlers } from './dialog';
import { workspaceEvents, workspaceHandlers } from './workspace';
type AllHandlers = {
db: DBHandlers;
workspace: WorkspaceHandlers;
dialog: DialogHandlers;
};
export const handlers = {
db: dbHandlers,
workspace: workspaceHandlers,
dialog: dialogHandlers,
};
} satisfies AllHandlers;
export const events = {
db: dbEvents,
+1 -1
View File
@@ -34,7 +34,7 @@ async function createWindow() {
: isWindows()
? 'hidden'
: 'default',
trafficLightPosition: { x: 24, y: 18 },
trafficLightPosition: { x: 20, y: 18 },
x: mainWindowState.x,
y: mainWindowState.y,
width: mainWindowState.width,
@@ -35,8 +35,8 @@ export const checkForUpdates = async (force = true) => {
};
export const registerUpdater = async () => {
// skip auto update in dev mode
if (isDev) {
// skip auto update in dev mode & internal
if (isDev || buildType === 'internal') {
return;
}
@@ -52,6 +52,7 @@ export const registerUpdater = async () => {
const feedUrl: Parameters<typeof autoUpdater.setFeedURL>[0] = {
channel: buildType,
provider: 'github',
// @ts-expect-error - just ignore for now
repo: buildType !== 'internal' ? 'AFFiNE' : 'AFFiNE-Releases',
owner: 'toeverything',
releaseType: buildType === 'stable' ? 'release' : 'prerelease',
+7 -7
View File
@@ -1,7 +1,7 @@
{
"name": "@affine/prototype",
"private": true,
"version": "0.8.0-canary.21",
"version": "0.8.0-beta.0",
"type": "module",
"scripts": {
"dev": "vite --host --port 3003",
@@ -18,13 +18,13 @@
"@affine/jotai": "workspace:*",
"@affine/templates": "workspace:*",
"@affine/workspace": "workspace:*",
"@blocksuite/block-std": "0.0.0-20230811201552-f37162ea-nightly",
"@blocksuite/blocks": "0.0.0-20230811201552-f37162ea-nightly",
"@blocksuite/editor": "0.0.0-20230811201552-f37162ea-nightly",
"@blocksuite/global": "0.0.0-20230811201552-f37162ea-nightly",
"@blocksuite/block-std": "0.0.0-20230818053411-bc330b35-nightly",
"@blocksuite/blocks": "0.0.0-20230818053411-bc330b35-nightly",
"@blocksuite/editor": "0.0.0-20230818053411-bc330b35-nightly",
"@blocksuite/global": "0.0.0-20230818053411-bc330b35-nightly",
"@blocksuite/icons": "^2.1.31",
"@blocksuite/lit": "0.0.0-20230811201552-f37162ea-nightly",
"@blocksuite/store": "0.0.0-20230811201552-f37162ea-nightly",
"@blocksuite/lit": "0.0.0-20230818053411-bc330b35-nightly",
"@blocksuite/store": "0.0.0-20230818053411-bc330b35-nightly",
"@toeverything/hooks": "workspace:*",
"@toeverything/y-indexeddb": "workspace:*",
"react": "^18.2.0",
+5 -5
View File
@@ -1,7 +1,7 @@
{
"name": "@affine/server",
"private": true,
"version": "0.8.0-canary.21",
"version": "0.8.0-beta.0",
"description": "Affine Node.js server",
"type": "module",
"bin": {
@@ -18,7 +18,7 @@
"dependencies": {
"@apollo/server": "^4.9.1",
"@auth/prisma-adapter": "^1.0.1",
"@aws-sdk/client-s3": "^3.388.0",
"@aws-sdk/client-s3": "^3.391.0",
"@nestjs/apollo": "^12.0.7",
"@nestjs/common": "^10.1.3",
"@nestjs/core": "^10.1.3",
@@ -31,11 +31,11 @@
"cookie-parser": "^1.4.6",
"dotenv": "^16.3.1",
"express": "^4.18.2",
"graphql": "^16.7.1",
"graphql": "^16.8.0",
"graphql-type-json": "^0.3.2",
"graphql-upload": "^16.0.2",
"lodash-es": "^4.17.21",
"next-auth": "4.22.1",
"next-auth": "4.22.5",
"nodemailer": "^6.9.4",
"parse-duration": "^1.1.0",
"prisma": "^5.1.1",
@@ -50,7 +50,7 @@
"@types/cookie-parser": "^1.4.3",
"@types/express": "^4.17.17",
"@types/lodash-es": "^4.17.8",
"@types/node": "^18.17.4",
"@types/node": "^18.17.5",
"@types/nodemailer": "^6.4.9",
"@types/supertest": "^2.0.12",
"c8": "^8.0.1",
+8 -1
View File
@@ -5,6 +5,7 @@ import { mergeConfig } from 'vite';
import tsconfigPaths from 'vite-tsconfig-paths';
import { vanillaExtractPlugin } from '@vanilla-extract/vite-plugin';
import { getRuntimeConfig } from '../../core/.webpack/runtime-config';
import turbosnap from 'vite-plugin-turbosnap';
runCli(
{
@@ -28,11 +29,12 @@ export default {
'@storybook/addon-interactions',
'@storybook/addon-storysource',
'storybook-dark-mode',
'storybook-addon-react-router-v6',
],
framework: {
name: '@storybook/react-vite',
},
async viteFinal(config, _) {
async viteFinal(config, { configType }) {
return mergeConfig(config, {
assetsInclude: ['**/*.md'],
plugins: [
@@ -40,6 +42,11 @@ export default {
tsconfigPaths({
root: fileURLToPath(new URL('../../../', import.meta.url)),
}),
configType === 'PRODUCTION'
? turbosnap({
rootDir: fileURLToPath(new URL('../../../', import.meta.url)),
})
: null,
],
define: {
'process.env': {},
+40 -45
View File
@@ -1,13 +1,17 @@
import 'ses';
import '@affine/component/theme/global.css';
import '@affine/component/theme/theme.css';
import { LOCALES, createI18n } from '@affine/i18n';
import '@toeverything/components/style.css';
import { createI18n } from '@affine/i18n';
import { ThemeProvider, useTheme } from 'next-themes';
import { setupGlobal } from '@affine/env/global';
import type { ComponentType } from 'react';
import { useEffect } from 'react';
import { useDarkMode } from 'storybook-dark-mode';
import { setup } from '@affine/core/bootstrap/setup';
import { AffineContext } from '@affine/component/context';
import { use } from 'foxact/use';
import useSWR from 'swr';
import type { Decorator } from '@storybook/react';
setupGlobal();
const setupPromise = setup();
export const parameters = {
backgrounds: { disable: true },
@@ -20,51 +24,42 @@ export const parameters = {
},
};
export const globalTypes = {
locale: {
name: 'Locale',
description: 'Internationalization locale',
defaultValue: 'en',
toolbar: {
icon: 'globe',
items: LOCALES.map(locale => ({
title: locale.originalName,
value: locale.tag,
right: locale.flagEmoji,
})),
const i18n = createI18n();
const withI18n: Decorator = (Story, context) => {
const locale = context.globals.locale;
useSWR(
locale,
async () => {
await i18n.changeLanguage(locale);
},
},
{
suspense: true,
}
);
return <Story {...context} />;
};
const createI18nDecorator = () => {
const i18n = createI18n();
const withI18n = (Story: any, context: any) => {
const locale = context.globals.locale;
useEffect(() => {
i18n.changeLanguage(locale);
}, [locale]);
return <Story {...context} />;
};
return withI18n;
};
const Component = () => {
const ThemeChange = () => {
const isDark = useDarkMode();
const theme = useTheme();
useEffect(() => {
theme.setTheme(isDark ? 'dark' : 'light');
}, [isDark]);
if (theme.resolvedTheme === 'dark' && !isDark) {
theme.setTheme('light');
} else if (theme.resolvedTheme === 'light' && isDark) {
theme.setTheme('dark');
}
return null;
};
export const decorators = [
(Story: ComponentType) => {
return (
<ThemeProvider>
<Component />
<Story />
</ThemeProvider>
);
},
createI18nDecorator(),
];
const withContextDecorator: Decorator = (Story, context) => {
use(setupPromise);
return (
<ThemeProvider>
<AffineContext>
<ThemeChange />
<Story {...context} />
</AffineContext>
</ThemeProvider>
);
};
export const decorators = [withContextDecorator, withI18n];
+1
View File
@@ -0,0 +1 @@
# Storybook
+23 -19
View File
@@ -3,42 +3,46 @@
"private": true,
"scripts": {
"dev": "storybook dev -p 6006",
"build": "NODE_OPTIONS=--max_old_space_size=4096 storybook build",
"build": "storybook build",
"test": "test-storybook"
},
"dependencies": {
"@affine/component": "workspace:*",
"@affine/i18n": "workspace:*",
"@storybook/addon-actions": "^7.2.3",
"@storybook/addon-essentials": "^7.2.3",
"@storybook/addon-interactions": "^7.2.3",
"@storybook/addon-links": "^7.2.3",
"@storybook/addon-storysource": "^7.2.3",
"@storybook/blocks": "^7.2.3",
"@storybook/builder-vite": "^7.2.3",
"@storybook/addon-actions": "^7.3.1",
"@storybook/addon-essentials": "^7.3.1",
"@storybook/addon-interactions": "^7.3.1",
"@storybook/addon-links": "^7.3.1",
"@storybook/addon-storysource": "^7.3.1",
"@storybook/blocks": "^7.3.1",
"@storybook/builder-vite": "^7.3.1",
"@storybook/jest": "^0.1.0",
"@storybook/react": "^7.2.3",
"@storybook/react-vite": "^7.2.3",
"@storybook/react": "^7.3.1",
"@storybook/react-vite": "^7.3.1",
"@storybook/test-runner": "^0.13.0",
"@storybook/testing-library": "^0.2.0",
"@vitejs/plugin-react": "^4.0.4",
"concurrently": "^8.2.0",
"jest-mock": "^29.6.2",
"serve": "^14.2.0",
"storybook": "^7.2.3",
"ses": "^0.18.7",
"storybook": "^7.3.1",
"storybook-dark-mode": "^3.0.1",
"wait-on": "^7.0.1"
},
"devDependencies": {
"@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/block-std": "0.0.0-20230818053411-bc330b35-nightly",
"@blocksuite/blocks": "0.0.0-20230818053411-bc330b35-nightly",
"@blocksuite/editor": "0.0.0-20230818053411-bc330b35-nightly",
"@blocksuite/global": "0.0.0-20230818053411-bc330b35-nightly",
"@blocksuite/icons": "^2.1.31",
"@blocksuite/lit": "0.0.0-20230811201552-f37162ea-nightly",
"@blocksuite/store": "0.0.0-20230811201552-f37162ea-nightly",
"@blocksuite/lit": "0.0.0-20230818053411-bc330b35-nightly",
"@blocksuite/store": "0.0.0-20230818053411-bc330b35-nightly",
"chromatic": "^6.22.0",
"react": "18.2.0",
"react-dom": "18.2.0"
"react-dom": "18.2.0",
"storybook-addon-react-router-v6": "^2.0.4",
"vite-plugin-turbosnap": "^1.0.2"
},
"peerDependencies": {
"@blocksuite/blocks": "*",
@@ -48,5 +52,5 @@
"@blocksuite/lit": "*",
"@blocksuite/store": "*"
},
"version": "0.8.0-canary.21"
"version": "0.8.0-beta.0"
}
@@ -1,92 +0,0 @@
/* deepscan-disable USELESS_ARROW_FUNC_BIND */
import { BlockHubWrapper } from '@affine/component/block-hub';
import type { EditorProps } from '@affine/component/block-suite-editor';
import { BlockSuiteEditor } from '@affine/component/block-suite-editor';
import { rootBlockHubAtom } from '@affine/workspace/atom';
import { __unstableSchemas, AffineSchemas } from '@blocksuite/blocks/models';
import type { EditorContainer } from '@blocksuite/editor';
import type { Page } from '@blocksuite/store';
import { createMemoryStorage, Schema, Workspace } from '@blocksuite/store';
import { expect } from '@storybook/jest';
import type { Meta, StoryFn } from '@storybook/react';
import { use } from 'foxact/use';
const schema = new Schema();
schema.register(AffineSchemas).register(__unstableSchemas);
const blockSuiteWorkspace = new Workspace({
id: 'test',
blobStorages: [createMemoryStorage],
schema,
});
async function initPage(page: Page) {
await page.waitForLoaded();
// Add page block and surface block at root level
const pageBlockId = page.addBlock('affine:page', {
title: new page.Text('Hello, world!'),
});
page.addBlock('affine:surface', {}, pageBlockId);
const frameId = page.addBlock('affine:note', {}, pageBlockId);
page.addBlock(
'affine:paragraph',
{
text: new page.Text('This is a paragraph.'),
},
frameId
);
page.resetHistory();
}
const page = blockSuiteWorkspace.createPage('page0');
type BlockSuiteMeta = Meta<typeof BlockSuiteEditor>;
export default {
title: 'BlockSuite/Editor',
component: BlockSuiteEditor,
} satisfies BlockSuiteMeta;
const Template: StoryFn<EditorProps> = (props: Partial<EditorProps>) => {
if (!page.loaded) {
use(initPage(page));
}
return (
<div
style={{
height: '100vh',
width: '100vw',
overflow: 'auto',
}}
>
<BlockSuiteEditor onInit={initPage} page={page} mode="page" {...props} />
<BlockHubWrapper
style={{
position: 'absolute',
right: 12,
bottom: 12,
}}
blockHubAtom={rootBlockHubAtom}
/>
</div>
);
};
export const Empty = Template.bind({});
Empty.play = async ({ canvasElement }) => {
await new Promise<void>(resolve => {
setTimeout(() => resolve(), 500);
});
const editorContainer = canvasElement.querySelector(
'[data-testid="editor-page0"]'
) as HTMLDivElement;
expect(editorContainer).not.toBeNull();
const editor = editorContainer.querySelector(
'editor-container'
) as EditorContainer;
expect(editor).not.toBeNull();
};
Empty.args = {
mode: 'page',
};
@@ -7,6 +7,9 @@ import { within } from '@storybook/testing-library';
export default {
title: 'AFFiNE/Breadcrumbs',
component: Breadcrumbs,
parameters: {
chromatic: { disableSnapshot: true },
},
} as Meta<typeof Breadcrumbs>;
const Template: StoryFn = args => <Breadcrumbs {...args} />;
+5 -1
View File
@@ -9,11 +9,15 @@ import {
HelpIcon,
PageIcon,
} from '@blocksuite/icons';
import type { Meta } from '@storybook/react';
export default {
title: 'AFFiNE/Card',
component: WorkspaceCard,
};
parameters: {
chromatic: { disableSnapshot: true },
},
} satisfies Meta;
const blockSuiteWorkspace = getOrCreateWorkspace(
'blocksuite-local',
+110
View File
@@ -0,0 +1,110 @@
import { pluginRegisterPromise } from '@affine/core/bootstrap/register-plugins';
import { routes } from '@affine/core/router';
import { assertExists } from '@blocksuite/global/utils';
import type { Decorator, StoryFn } from '@storybook/react';
import { userEvent, waitFor } from '@storybook/testing-library';
import { use } from 'foxact/use';
import { Outlet, useLocation } from 'react-router-dom';
import {
reactRouterOutlets,
reactRouterParameters,
withRouter,
} from 'storybook-addon-react-router-v6';
const withCleanLocalStorage: Decorator = (Story, context) => {
localStorage.clear();
return <Story {...context} />;
};
const FakeApp = () => {
const location = useLocation();
// fixme: `key` is a hack to force the storybook to re-render the outlet
return <Outlet key={location.pathname} />;
};
export default {
title: 'Preview/Core',
parameters: {
chromatic: { disableSnapshot: false },
},
};
export const Index: StoryFn = () => {
use(pluginRegisterPromise);
return <FakeApp />;
};
Index.decorators = [withRouter, withCleanLocalStorage];
Index.parameters = {
reactRouter: reactRouterParameters({
routing: reactRouterOutlets(routes),
}),
};
export const SettingPage: StoryFn = () => {
return <FakeApp />;
};
SettingPage.play = async ({ canvasElement }) => {
await waitFor(
() => {
assertExists(
canvasElement.querySelector('[data-testid="settings-modal-trigger"]')
);
},
{
timeout: 5000,
}
);
const settingModalBtn = canvasElement.querySelector(
'[data-testid="settings-modal-trigger"]'
) as Element;
await userEvent.click(settingModalBtn);
};
SettingPage.decorators = [withRouter, withCleanLocalStorage];
SettingPage.parameters = {
reactRouter: reactRouterParameters({
routing: reactRouterOutlets(routes),
}),
};
export const NotFoundPage: StoryFn = () => {
return <FakeApp />;
};
NotFoundPage.decorators = [withRouter, withCleanLocalStorage];
NotFoundPage.parameters = {
reactRouter: reactRouterParameters({
routing: reactRouterOutlets(routes),
location: {
path: '/404',
},
}),
};
export const WorkspaceList: StoryFn = () => {
return <FakeApp />;
};
WorkspaceList.play = async ({ canvasElement }) => {
// click current-workspace
await waitFor(
() => {
assertExists(
canvasElement.querySelector('[data-testid="current-workspace"]')
);
},
{
timeout: 5000,
}
);
const currentWorkspace = canvasElement.querySelector(
'[data-testid="current-workspace"]'
) as Element;
await userEvent.click(currentWorkspace);
};
WorkspaceList.decorators = [withRouter, withCleanLocalStorage];
WorkspaceList.parameters = {
reactRouter: reactRouterParameters({
routing: reactRouterOutlets(routes),
location: {
path: '/',
},
}),
};
@@ -1,11 +1,14 @@
import { AFFiNEDatePicker } from '@affine/component/date-picker';
import type { StoryFn } from '@storybook/react';
import type { Meta, StoryFn } from '@storybook/react';
import { useState } from 'react';
export default {
title: 'AFFiNE/AFFiNEDatePicker',
component: AFFiNEDatePicker,
};
parameters: {
chromatic: { disableSnapshot: true },
},
} satisfies Meta;
export const Default: StoryFn = () => {
const [value, setValue] = useState<string>(new Date().toString());
@@ -2,11 +2,15 @@
import { toast } from '@affine/component';
import { ImportPage } from '@affine/component/import-page';
import type { StoryFn } from '@storybook/react';
import type { Meta } from '@storybook/react';
export default {
title: 'AFFiNE/ImportPage',
component: ImportPage,
};
parameters: {
chromatic: { disableSnapshot: true },
},
} satisfies Meta;
const Template: StoryFn<typeof ImportPage> = args => <ImportPage {...args} />;
@@ -9,6 +9,9 @@ import { useAtomValue, useSetAtom } from 'jotai';
export default {
title: 'AFFiNE/NotificationCenter',
component: NotificationCenter,
parameters: {
chromatic: { disableSnapshot: true },
},
} satisfies Meta<typeof NotificationCenter>;
let id = 0;
@@ -1,11 +1,14 @@
/* deepscan-disable USELESS_ARROW_FUNC_BIND */
import { TourModal } from '@affine/component/tour-modal';
import type { StoryFn } from '@storybook/react';
import type { Meta, StoryFn } from '@storybook/react';
export default {
title: 'AFFiNE/TourModal',
component: TourModal,
};
parameters: {
chromatic: { disableSnapshot: true },
},
} satisfies Meta;
export const Basic: StoryFn = () => {
return <TourModal open={true} onClose={() => {}} />;
@@ -4,6 +4,9 @@ import type { Meta } from '@storybook/react';
export default {
title: 'AFFiNE/PageDetailSkeleton',
component: PageDetailSkeleton,
parameters: {
chromatic: { disableSnapshot: true },
},
} satisfies Meta<typeof PageDetailSkeleton>;
export const Basic = () => {
@@ -7,13 +7,16 @@ import { NewPageButton } from '@affine/component/page-list';
import { OperationCell } from '@affine/component/page-list';
import { PageIcon } from '@blocksuite/icons';
import { expect } from '@storybook/jest';
import type { StoryFn } from '@storybook/react';
import type { Meta, StoryFn } from '@storybook/react';
import { userEvent } from '@storybook/testing-library';
export default {
title: 'AFFiNE/PageList',
component: PageList,
};
parameters: {
chromatic: { disableSnapshot: true },
},
} satisfies Meta;
export const AffineOperationCell: StoryFn<OperationCellProps> = ({
...props
@@ -34,7 +37,7 @@ AffineOperationCell.play = async ({ canvasElement }) => {
'[data-testid="page-list-operation-button"]'
) as HTMLButtonElement;
expect(button).not.toBeNull();
userEvent.click(button);
await userEvent.click(button);
}
};
@@ -51,7 +54,7 @@ AffineNewPageButton.play = async ({ canvasElement }) => {
expect(button).not.toBeNull();
const dropdown = button.querySelector('svg') as SVGSVGElement;
expect(dropdown).not.toBeNull();
userEvent.click(dropdown);
await userEvent.click(dropdown);
};
export const AffineAllPageList: StoryFn<typeof PageList> = ({ ...props }) => (
@@ -69,11 +72,11 @@ AffineAllPageList.args = {
favorite: false,
icon: <PageIcon />,
isPublicPage: true,
title: 'Today Page',
title: 'Last Page',
tags: [],
preview: 'this is page preview',
createDate: new Date(),
updatedDate: new Date(),
createDate: new Date('2021-01-01'),
updatedDate: new Date('2023-08-15'),
bookmarkPage: () => toast('Bookmark page'),
onClickPage: () => toast('Click page'),
onDisablePublicSharing: () => toast('Disable public sharing'),
@@ -12,14 +12,17 @@ import { WorkspaceFlavour } from '@affine/env/workspace';
import { getOrCreateWorkspace } from '@affine/workspace/manager';
import type { Page } from '@blocksuite/store';
import { expect } from '@storybook/jest';
import type { StoryFn } from '@storybook/react';
import type { Meta, StoryFn } from '@storybook/react';
import { use } from 'foxact/use';
import { useState } from 'react';
export default {
title: 'AFFiNE/ShareMenu',
component: ShareMenu,
};
parameters: {
chromatic: { disableSnapshot: true },
},
} satisfies Meta;
async function initPage(page: Page) {
await page.waitForLoaded();
@@ -1,11 +1,14 @@
/* deepscan-disable USELESS_ARROW_FUNC_BIND */
import { Switch } from '@affine/component';
import type { StoryFn } from '@storybook/react';
import type { Meta, StoryFn } from '@storybook/react';
export default {
title: 'AFFiNE/Switch',
component: Switch,
};
parameters: {
chromatic: { disableSnapshot: true },
},
} satisfies Meta;
export const Basic: StoryFn = () => {
return <Switch>Switch</Switch>;
@@ -16,6 +16,9 @@ export default {
},
},
},
parameters: {
chromatic: { disableSnapshot: true },
},
} satisfies Meta<WorkspaceAvatarProps>;
const schema = new Schema();
@@ -9,6 +9,9 @@ import { useState } from 'react';
export default {
title: 'AFFiNE/WorkspaceList',
component: WorkspaceList,
parameters: {
chromatic: { disableSnapshot: true },
},
} satisfies Meta<WorkspaceListProps>;
export const Default = () => {
+3
View File
@@ -9,6 +9,9 @@
"outDir": "lib"
},
"references": [
{
"path": "../../apps/core"
},
{
"path": "../../packages/component"
},
+2 -1
View File
@@ -4,7 +4,7 @@
"composite": true,
"module": "ESNext",
"jsx": "react-jsx",
"moduleResolution": "Node",
"moduleResolution": "bundler",
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"noEmit": false,
@@ -13,6 +13,7 @@
"include": [".storybook/**/*"],
"exclude": ["lib"],
"references": [
{ "path": "../../apps/core" },
{ "path": "../../packages/i18n" },
{
"path": "../../packages/env"
+10 -10
View File
@@ -1,6 +1,6 @@
{
"name": "@affine/monorepo",
"version": "0.8.0-canary.21",
"version": "0.8.0-beta.0",
"private": true,
"author": "toeverything",
"license": "MPL-2.0",
@@ -66,7 +66,7 @@
"@faker-js/faker": "^8.0.2",
"@istanbuljs/schema": "^0.1.3",
"@magic-works/i18n-codegen": "^0.5.0",
"@nx/vite": "16.6.0",
"@nx/vite": "16.7.0",
"@perfsee/sdk": "^1.9.0",
"@playwright/test": "^1.37.0",
"@taplo/cli": "^0.5.2",
@@ -74,9 +74,9 @@
"@toeverything/infra": "workspace:*",
"@types/affine__env": "workspace:*",
"@types/eslint": "^8.44.2",
"@types/node": "^18.17.4",
"@typescript-eslint/eslint-plugin": "^6.3.0",
"@typescript-eslint/parser": "^6.3.0",
"@types/node": "^18.17.5",
"@typescript-eslint/eslint-plugin": "^6.4.0",
"@typescript-eslint/parser": "^6.4.0",
"@vanilla-extract/vite-plugin": "^3.8.2",
"@vanilla-extract/webpack-plugin": "^2.2.0",
"@vitejs/plugin-react-swc": "^3.3.2",
@@ -86,7 +86,7 @@
"eslint-config-prettier": "^9.0.0",
"eslint-plugin-i": "^2.28.0",
"eslint-plugin-prettier": "^5.0.0",
"eslint-plugin-react": "^7.33.1",
"eslint-plugin-react": "^7.33.2",
"eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-simple-import-sort": "^10.0.0",
"eslint-plugin-sonarjs": "^0.20.0",
@@ -94,16 +94,16 @@
"eslint-plugin-unused-imports": "^3.0.0",
"eslint-plugin-vue": "^9.17.0",
"fake-indexeddb": "4.0.2",
"happy-dom": "^10.9.0",
"happy-dom": "^10.10.0",
"husky": "^8.0.3",
"lint-staged": "^13.2.3",
"lint-staged": "^14.0.0",
"madge": "^6.1.0",
"msw": "^1.2.3",
"nanoid": "^4.0.2",
"nx": "16.6.0",
"nx": "16.7.0",
"nx-cloud": "latest",
"nyc": "^15.1.0",
"prettier": "^3.0.1",
"prettier": "^3.0.2",
"semver": "^7.5.4",
"serve": "^14.2.0",
"ts-node": "^10.9.1",
+1 -1
View File
@@ -7,5 +7,5 @@
"@affine/env": "workspace:*",
"@toeverything/infra": "workspace:*"
},
"version": "0.8.0-canary.21"
"version": "0.8.0-beta.0"
}
+1 -1
View File
@@ -20,5 +20,5 @@
"peerDependencies": {
"ts-node": "*"
},
"version": "0.8.0-canary.21"
"version": "0.8.0-beta.0"
}
+8 -8
View File
@@ -26,9 +26,9 @@
"@emotion/react": "^11.11.1",
"@emotion/server": "^11.11.0",
"@emotion/styled": "^11.11.0",
"@mui/base": "5.0.0-beta.10",
"@mui/base": "5.0.0-beta.11",
"@mui/icons-material": "^5.14.3",
"@mui/material": "^5.14.4",
"@mui/material": "^5.14.5",
"@popperjs/core": "^2.11.8",
"@radix-ui/react-avatar": "^1.0.3",
"@radix-ui/react-collapsible": "^1.0.3",
@@ -51,12 +51,12 @@
"rxjs": "^7.8.1"
},
"devDependencies": {
"@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/blocks": "0.0.0-20230818053411-bc330b35-nightly",
"@blocksuite/editor": "0.0.0-20230818053411-bc330b35-nightly",
"@blocksuite/global": "0.0.0-20230818053411-bc330b35-nightly",
"@blocksuite/icons": "^2.1.31",
"@blocksuite/lit": "0.0.0-20230811201552-f37162ea-nightly",
"@blocksuite/store": "0.0.0-20230811201552-f37162ea-nightly",
"@blocksuite/lit": "0.0.0-20230818053411-bc330b35-nightly",
"@blocksuite/store": "0.0.0-20230818053411-bc330b35-nightly",
"@types/react": "^18.2.20",
"@types/react-datepicker": "^4.15.0",
"@types/react-dnd": "^3.0.2",
@@ -66,5 +66,5 @@
"vite": "^4.4.9",
"yjs": "^13.6.7"
},
"version": "0.8.0-canary.21"
"version": "0.8.0-beta.0"
}
@@ -3,13 +3,16 @@ import { style } from '@vanilla-extract/css';
export const root = style({
fontSize: 'var(--affine-font-xs)',
minHeight: '16px',
width: 'calc(100% + 6px)',
userSelect: 'none',
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
marginBottom: '4px',
padding: '0 8px',
selectors: {
'&:not(:first-of-type)': {
marginTop: '10px',
marginTop: '16px',
},
},
});
@@ -52,7 +52,6 @@ export const navStyle = style({
display: 'flex',
flexDirection: 'column',
zIndex: parseInt(baseTheme.zIndexModal),
borderRight: '1px solid transparent',
});
export const navHeaderStyle = style({
@@ -76,6 +75,7 @@ export const navBodyStyle = style({
height: 'calc(100% - 52px)',
display: 'flex',
flexDirection: 'column',
rowGap: '4px',
});
export const sidebarFloatMaskStyle = style({
@@ -1,15 +1,23 @@
import { style } from '@vanilla-extract/css';
export const linkItemRoot = style({
color: 'inherit',
display: 'contents',
});
export const root = style({
display: 'inline-flex',
alignItems: 'center',
borderRadius: '4px',
textAlign: 'left',
color: 'inherit',
width: '100%',
minHeight: '30px',
userSelect: 'none',
cursor: 'pointer',
padding: '0 12px',
fontSize: 'var(--affine-font-sm)',
margin: '2px 0',
marginTop: '4px',
selectors: {
'&:hover': {
background: 'var(--affine-hover-color)',
@@ -29,10 +37,8 @@ export const root = style({
// 'linear-gradient(0deg, rgba(0, 0, 0, 0.04), rgba(0, 0, 0, 0.04)), rgba(0, 0, 0, 0.04)',
// },
'&[data-collapsible="true"]': {
width: 'calc(100% + 8px)',
transform: 'translateX(-8px)',
paddingLeft: '4px',
paddingRight: '12px',
paddingRight: '4px',
},
'&[data-type="collection-list-item"][data-collapsible="false"][data-active="true"],&[data-type="favorite-list-item"][data-collapsible="false"][data-active="true"], &[data-type="favorite-list-item"][data-collapsible="false"]:hover, &[data-type="collection-list-item"][data-collapsible="false"]:hover':
{
@@ -41,6 +47,9 @@ export const root = style({
paddingLeft: '20px',
paddingRight: '12px',
},
[`${linkItemRoot}:first-of-type &`]: {
marginTop: '0px',
},
},
});
@@ -53,6 +62,12 @@ export const content = style({
export const postfix = style({
justifySelf: 'flex-end',
display: 'none',
selectors: {
[`${root}:hover &`]: {
display: 'flex',
},
},
});
export const icon = style({
@@ -68,10 +83,15 @@ export const collapsedIconContainer = style({
justifyContent: 'center',
borderRadius: '2px',
transition: 'transform 0.2s',
color: 'inherit',
selectors: {
'&[data-collapsed="true"]': {
transform: 'rotate(-90deg)',
},
'&[data-disabled="true"]': {
opacity: 0.3,
pointerEvents: 'none',
},
'&:hover': {
background: 'var(--affine-hover-color)',
},
@@ -103,8 +123,3 @@ export const collapsedIcon = style({
export const spacer = style({
flex: 1,
});
export const linkItemRoot = style({
color: 'inherit',
display: 'contents',
});
@@ -6,11 +6,13 @@ import { Link } from 'react-router-dom';
import * as styles from './index.css';
export interface MenuItemProps extends React.HTMLAttributes<HTMLDivElement> {
export interface MenuItemProps extends React.HTMLAttributes<HTMLButtonElement> {
icon?: React.ReactElement;
active?: boolean;
disabled?: boolean;
collapsed?: boolean; // true, false, undefined. undefined means no collapse
// true, false, undefined. undefined means no collapse
collapsed?: boolean;
// if onCollapsedChange is given, but collapsed is undefined, then we will render the collapse button as disabled
onCollapsedChange?: (collapsed: boolean) => void;
postfix?: React.ReactElement;
}
@@ -23,7 +25,7 @@ const stopPropagation: React.MouseEventHandler = e => {
e.stopPropagation();
};
export const MenuItem = React.forwardRef<HTMLDivElement, MenuItemProps>(
export const MenuItem = React.forwardRef<HTMLButtonElement, MenuItemProps>(
(
{
onClick,
@@ -38,14 +40,9 @@ export const MenuItem = React.forwardRef<HTMLDivElement, MenuItemProps>(
},
ref
) => {
const collapsible = collapsed !== undefined;
if (collapsible && !onCollapsedChange) {
throw new Error(
'onCollapsedChange is required when collapsed is defined'
);
}
const collapsible = onCollapsedChange !== undefined;
return (
<div
<button
ref={ref}
{...props}
onClick={onClick}
@@ -58,6 +55,7 @@ export const MenuItem = React.forwardRef<HTMLDivElement, MenuItemProps>(
<div className={styles.iconsContainer} data-collapsible={collapsible}>
{collapsible && (
<div
data-disabled={collapsed === undefined ? true : undefined}
onClick={e => {
e.stopPropagation();
e.preventDefault(); // for links
@@ -68,7 +66,7 @@ export const MenuItem = React.forwardRef<HTMLDivElement, MenuItemProps>(
>
<ArrowDownSmallIcon
className={styles.collapsedIcon}
data-collapsed={collapsed}
data-collapsed={collapsed !== false}
/>
</div>
)}
@@ -84,21 +82,22 @@ export const MenuItem = React.forwardRef<HTMLDivElement, MenuItemProps>(
{postfix}
</div>
) : null}
</div>
</button>
);
}
);
MenuItem.displayName = 'MenuItem';
export const MenuLinkItem = React.forwardRef<HTMLDivElement, MenuLinkItemProps>(
({ to, ...props }, ref) => {
return (
<Link to={to} className={styles.linkItemRoot}>
{/* The <a> element rendered by Link does not generate display box due to `display: contents` style */}
{/* Thus ref is passed to MenuItem instead of Link */}
<MenuItem ref={ref} {...props}></MenuItem>
</Link>
);
}
);
export const MenuLinkItem = React.forwardRef<
HTMLButtonElement,
MenuLinkItemProps
>(({ to, ...props }, ref) => {
return (
<Link to={to} className={styles.linkItemRoot}>
{/* The <a> element rendered by Link does not generate display box due to `display: contents` style */}
{/* Thus ref is passed to MenuItem instead of Link */}
<MenuItem ref={ref} {...props}></MenuItem>
</Link>
);
});
MenuLinkItem.displayName = 'MenuLinkItem';
@@ -12,7 +12,7 @@ export const root = style({
userSelect: 'none',
cursor: 'pointer',
padding: '0 12px',
margin: '12px 0',
margin: '20px 0',
position: 'relative',
});
@@ -1,6 +1,7 @@
import { assertExists } from '@blocksuite/global/utils';
import { useAtom, useSetAtom } from 'jotai';
import type { ReactElement } from 'react';
import { useCallback, useLayoutEffect, useState } from 'react';
import { useCallback } from 'react';
import {
appSidebarOpenAtom,
@@ -18,16 +19,10 @@ export const ResizeIndicator = (props: ResizeIndicatorProps): ReactElement => {
const [sidebarOpen, setSidebarOpen] = useAtom(appSidebarOpenAtom);
const [isResizing, setIsResizing] = useAtom(appSidebarResizingAtom);
const [anchorLeft, setAnchorLeft] = useState(0);
useLayoutEffect(() => {
if (!props.targetElement) return;
const { left } = props.targetElement.getBoundingClientRect();
setAnchorLeft(left);
}, [props.targetElement]);
const onResizeStart = useCallback(() => {
let resized = false;
assertExists(props.targetElement);
const { left: anchorLeft } = props.targetElement.getBoundingClientRect();
function onMouseMove(e: MouseEvent) {
e.preventDefault();
@@ -51,13 +46,7 @@ export const ResizeIndicator = (props: ResizeIndicatorProps): ReactElement => {
},
{ once: true }
);
}, [
anchorLeft,
props.targetElement,
setIsResizing,
setSidebarOpen,
setWidth,
]);
}, [props.targetElement, setIsResizing, setSidebarOpen, setWidth]);
return (
<div

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