mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-20 15:57:06 +08:00
Compare commits
2 Commits
v0.26.3-be
...
jimmfly/05
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f4afbb7c68 | ||
|
|
565f61456f |
@@ -51,7 +51,7 @@
|
|||||||
"react-dom": "^19.0.0",
|
"react-dom": "^19.0.0",
|
||||||
"react-hook-form": "^7.54.1",
|
"react-hook-form": "^7.54.1",
|
||||||
"react-resizable-panels": "^3.0.0",
|
"react-resizable-panels": "^3.0.0",
|
||||||
"react-router-dom": "^7.5.1",
|
"react-router": "^7.6.0",
|
||||||
"sonner": "^2.0.0",
|
"sonner": "^2.0.0",
|
||||||
"swr": "^2.2.5",
|
"swr": "^2.2.5",
|
||||||
"vaul": "^1.1.1",
|
"vaul": "^1.1.1",
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { Toaster } from '@affine/admin/components/ui/sonner';
|
import { Toaster } from '@affine/admin/components/ui/sonner';
|
||||||
import { lazy, ROUTES } from '@affine/routes';
|
import { FACTORIES, lazy, RELATIVE_ROUTES } from '@affine/routes';
|
||||||
import { withSentryReactRouterV7Routing } from '@sentry/react';
|
import { withSentryReactRouterV7Routing } from '@sentry/react';
|
||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
import {
|
import {
|
||||||
@@ -9,7 +9,7 @@ import {
|
|||||||
Route,
|
Route,
|
||||||
Routes as ReactRouterRoutes,
|
Routes as ReactRouterRoutes,
|
||||||
useLocation,
|
useLocation,
|
||||||
} from 'react-router-dom';
|
} from 'react-router';
|
||||||
import { toast } from 'sonner';
|
import { toast } from 'sonner';
|
||||||
import { SWRConfig } from 'swr';
|
import { SWRConfig } from 'swr';
|
||||||
|
|
||||||
@@ -18,22 +18,24 @@ import { isAdmin, useCurrentUser, useServerConfig } from './modules/common';
|
|||||||
import { Layout } from './modules/layout';
|
import { Layout } from './modules/layout';
|
||||||
|
|
||||||
export const Setup = lazy(
|
export const Setup = lazy(
|
||||||
() => import(/* webpackChunkName: "setup" */ './modules/setup')
|
async () => await import(/* webpackChunkName: "setup" */ './modules/setup')
|
||||||
);
|
);
|
||||||
export const Accounts = lazy(
|
export const Accounts = lazy(
|
||||||
() => import(/* webpackChunkName: "accounts" */ './modules/accounts')
|
async () =>
|
||||||
|
await import(/* webpackChunkName: "accounts" */ './modules/accounts')
|
||||||
);
|
);
|
||||||
export const AI = lazy(
|
export const AI = lazy(
|
||||||
() => import(/* webpackChunkName: "ai" */ './modules/ai')
|
async () => await import(/* webpackChunkName: "ai" */ './modules/ai')
|
||||||
);
|
);
|
||||||
export const About = lazy(
|
export const About = lazy(
|
||||||
() => import(/* webpackChunkName: "about" */ './modules/about')
|
async () => await import(/* webpackChunkName: "about" */ './modules/about')
|
||||||
);
|
);
|
||||||
export const Settings = lazy(
|
export const Settings = lazy(
|
||||||
() => import(/* webpackChunkName: "settings" */ './modules/settings')
|
async () =>
|
||||||
|
await import(/* webpackChunkName: "settings" */ './modules/settings')
|
||||||
);
|
);
|
||||||
export const Auth = lazy(
|
export const Auth = lazy(
|
||||||
() => import(/* webpackChunkName: "auth" */ './modules/auth')
|
async () => await import(/* webpackChunkName: "auth" */ './modules/auth')
|
||||||
);
|
);
|
||||||
|
|
||||||
const Routes = window.SENTRY_RELEASE
|
const Routes = window.SENTRY_RELEASE
|
||||||
@@ -50,7 +52,7 @@ function AuthenticatedRoutes() {
|
|||||||
}, [user]);
|
}, [user]);
|
||||||
|
|
||||||
if (!user || !isAdmin(user)) {
|
if (!user || !isAdmin(user)) {
|
||||||
return <Navigate to="/admin/auth" />;
|
return <Navigate to={FACTORIES.admin.auth()} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -86,19 +88,21 @@ export const App = () => {
|
|||||||
>
|
>
|
||||||
<BrowserRouter basename={environment.subPath}>
|
<BrowserRouter basename={environment.subPath}>
|
||||||
<Routes>
|
<Routes>
|
||||||
<Route path={ROUTES.admin.index} element={<RootRoutes />}>
|
<Route path={RELATIVE_ROUTES.admin.index}>
|
||||||
<Route path={ROUTES.admin.auth} element={<Auth />} />
|
<Route index element={<RootRoutes />} />
|
||||||
<Route path={ROUTES.admin.setup} element={<Setup />} />
|
<Route path={RELATIVE_ROUTES.admin.auth} element={<Auth />} />
|
||||||
|
<Route path={RELATIVE_ROUTES.admin.setup} element={<Setup />} />
|
||||||
<Route element={<AuthenticatedRoutes />}>
|
<Route element={<AuthenticatedRoutes />}>
|
||||||
<Route path={ROUTES.admin.accounts} element={<Accounts />} />
|
|
||||||
<Route path={ROUTES.admin.ai} element={<AI />} />
|
|
||||||
<Route path={ROUTES.admin.about} element={<About />} />
|
|
||||||
<Route
|
<Route
|
||||||
path={ROUTES.admin.settings.index}
|
path={RELATIVE_ROUTES.admin.accounts}
|
||||||
element={<Settings />}
|
element={<Accounts />}
|
||||||
>
|
/>
|
||||||
|
<Route path={RELATIVE_ROUTES.admin.ai} element={<AI />} />
|
||||||
|
<Route path={RELATIVE_ROUTES.admin.about} element={<About />} />
|
||||||
|
<Route path={RELATIVE_ROUTES.admin.settings.index}>
|
||||||
|
<Route index element={<Settings />} />
|
||||||
<Route
|
<Route
|
||||||
path={ROUTES.admin.settings.module}
|
path={RELATIVE_ROUTES.admin.settings.module}
|
||||||
element={<Settings />}
|
element={<Settings />}
|
||||||
/>
|
/>
|
||||||
</Route>
|
</Route>
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { Label } from '@affine/admin/components/ui/label';
|
|||||||
import { FeatureType, getUserFeaturesQuery } from '@affine/graphql';
|
import { FeatureType, getUserFeaturesQuery } from '@affine/graphql';
|
||||||
import type { FormEvent } from 'react';
|
import type { FormEvent } from 'react';
|
||||||
import { useCallback, useRef } from 'react';
|
import { useCallback, useRef } from 'react';
|
||||||
import { Navigate } from 'react-router-dom';
|
import { Navigate } from 'react-router';
|
||||||
import { toast } from 'sonner';
|
import { toast } from 'sonner';
|
||||||
|
|
||||||
import { affineFetch } from '../../fetch-utils';
|
import { affineFetch } from '../../fetch-utils';
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { useCallback } from 'react';
|
import { useCallback } from 'react';
|
||||||
import { NavLink } from 'react-router-dom';
|
import { NavLink } from 'react-router';
|
||||||
|
|
||||||
import { buttonVariants } from '../../components/ui/button';
|
import { buttonVariants } from '../../components/ui/button';
|
||||||
import { cn } from '../../utils';
|
import { cn } from '../../utils';
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { buttonVariants } from '@affine/admin/components/ui/button';
|
import { buttonVariants } from '@affine/admin/components/ui/button';
|
||||||
import { cn } from '@affine/admin/utils';
|
import { cn } from '@affine/admin/utils';
|
||||||
import { cssVarV2 } from '@toeverything/theme/v2';
|
import { cssVarV2 } from '@toeverything/theme/v2';
|
||||||
import { NavLink } from 'react-router-dom';
|
import { NavLink } from 'react-router';
|
||||||
|
|
||||||
interface NavItemProps {
|
interface NavItemProps {
|
||||||
icon: React.ReactNode;
|
icon: React.ReactNode;
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { buttonVariants } from '@affine/admin/components/ui/button';
|
|||||||
import { cn } from '@affine/admin/utils';
|
import { cn } from '@affine/admin/utils';
|
||||||
import { AccountIcon, AiOutlineIcon, SelfhostIcon } from '@blocksuite/icons/rc';
|
import { AccountIcon, AiOutlineIcon, SelfhostIcon } from '@blocksuite/icons/rc';
|
||||||
import { cssVarV2 } from '@toeverything/theme/v2';
|
import { cssVarV2 } from '@toeverything/theme/v2';
|
||||||
import { NavLink } from 'react-router-dom';
|
import { NavLink } from 'react-router';
|
||||||
|
|
||||||
import { ServerVersion } from './server-version';
|
import { ServerVersion } from './server-version';
|
||||||
import { SettingsItem } from './settings-item';
|
import { SettingsItem } from './settings-item';
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import { SettingsIcon } from '@blocksuite/icons/rc';
|
|||||||
import * as NavigationMenuPrimitive from '@radix-ui/react-navigation-menu';
|
import * as NavigationMenuPrimitive from '@radix-ui/react-navigation-menu';
|
||||||
import * as ScrollAreaPrimitive from '@radix-ui/react-scroll-area';
|
import * as ScrollAreaPrimitive from '@radix-ui/react-scroll-area';
|
||||||
import { cssVarV2 } from '@toeverything/theme/v2';
|
import { cssVarV2 } from '@toeverything/theme/v2';
|
||||||
import { NavLink } from 'react-router-dom';
|
import { NavLink } from 'react-router';
|
||||||
|
|
||||||
import { KNOWN_CONFIG_GROUPS, UNKNOWN_CONFIG_GROUPS } from '../settings/config';
|
import { KNOWN_CONFIG_GROUPS, UNKNOWN_CONFIG_GROUPS } from '../settings/config';
|
||||||
import { NormalSubItem } from './collapsible-item';
|
import { NormalSubItem } from './collapsible-item';
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import {
|
|||||||
import { validateEmailAndPassword } from '@affine/admin/utils';
|
import { validateEmailAndPassword } from '@affine/admin/utils';
|
||||||
import { useAsyncCallback } from '@affine/core/components/hooks/affine-async-hooks';
|
import { useAsyncCallback } from '@affine/core/components/hooks/affine-async-hooks';
|
||||||
import { useCallback, useEffect, useState } from 'react';
|
import { useCallback, useEffect, useState } from 'react';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router';
|
||||||
import { toast } from 'sonner';
|
import { toast } from 'sonner';
|
||||||
|
|
||||||
import { affineFetch } from '../../fetch-utils';
|
import { affineFetch } from '../../fetch-utils';
|
||||||
@@ -149,7 +149,7 @@ export const Form = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (current === count) {
|
if (current === count) {
|
||||||
return navigate('/', { replace: true });
|
return await navigate('/', { replace: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
api?.scrollNext();
|
api?.scrollNext();
|
||||||
@@ -168,7 +168,7 @@ export const Form = () => {
|
|||||||
const onPrevious = useAsyncCallback(async () => {
|
const onPrevious = useAsyncCallback(async () => {
|
||||||
if (current === count) {
|
if (current === count) {
|
||||||
if (serverConfig.initialized === true) {
|
if (serverConfig.initialized === true) {
|
||||||
return navigate('/admin', { replace: true });
|
return await navigate('/admin', { replace: true });
|
||||||
}
|
}
|
||||||
toast.error('Goto Admin Panel failed, please try again.');
|
toast.error('Goto Admin Panel failed, please try again.');
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Navigate } from 'react-router-dom';
|
import { Navigate } from 'react-router';
|
||||||
|
|
||||||
import { useServerConfig } from '../common';
|
import { useServerConfig } from '../common';
|
||||||
import { Form } from './form';
|
import { Form } from './form';
|
||||||
|
|||||||
@@ -31,7 +31,7 @@
|
|||||||
"next-themes": "^0.4.4",
|
"next-themes": "^0.4.4",
|
||||||
"react": "^19.0.0",
|
"react": "^19.0.0",
|
||||||
"react-dom": "^19.0.0",
|
"react-dom": "^19.0.0",
|
||||||
"react-router-dom": "^6.28.0"
|
"react-router": "^7.6.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@capacitor/cli": "^7.0.0",
|
"@capacitor/cli": "^7.0.0",
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { getStoreManager } from '@affine/core/blocksuite/manager/store';
|
import { getStoreManager } from '@affine/core/blocksuite/manager/store';
|
||||||
import { AffineContext } from '@affine/core/components/context';
|
import { AffineContext } from '@affine/core/components/context';
|
||||||
import { AppFallback } from '@affine/core/mobile/components/app-fallback';
|
|
||||||
import { configureMobileModules } from '@affine/core/mobile/modules';
|
import { configureMobileModules } from '@affine/core/mobile/modules';
|
||||||
import { VirtualKeyboardProvider } from '@affine/core/mobile/modules/virtual-keyboard';
|
import { VirtualKeyboardProvider } from '@affine/core/mobile/modules/virtual-keyboard';
|
||||||
import { router } from '@affine/core/mobile/router';
|
import { router } from '@affine/core/mobile/router';
|
||||||
@@ -46,7 +45,7 @@ import { OpClient } from '@toeverything/infra/op';
|
|||||||
import { AsyncCall } from 'async-call-rpc';
|
import { AsyncCall } from 'async-call-rpc';
|
||||||
import { useTheme } from 'next-themes';
|
import { useTheme } from 'next-themes';
|
||||||
import { Suspense, useEffect } from 'react';
|
import { Suspense, useEffect } from 'react';
|
||||||
import { RouterProvider } from 'react-router-dom';
|
import { RouterProvider } from 'react-router/dom';
|
||||||
|
|
||||||
import { AffineTheme } from './plugins/affine-theme';
|
import { AffineTheme } from './plugins/affine-theme';
|
||||||
import { AIButton } from './plugins/ai-button';
|
import { AIButton } from './plugins/ai-button';
|
||||||
@@ -60,10 +59,6 @@ window.addEventListener('beforeunload', () => {
|
|||||||
storeManagerClient.dispose();
|
storeManagerClient.dispose();
|
||||||
});
|
});
|
||||||
|
|
||||||
const future = {
|
|
||||||
v7_startTransition: true,
|
|
||||||
} as const;
|
|
||||||
|
|
||||||
const framework = new Framework();
|
const framework = new Framework();
|
||||||
configureCommonModules(framework);
|
configureCommonModules(framework);
|
||||||
configureBrowserWorkbenchModule(framework);
|
configureBrowserWorkbenchModule(framework);
|
||||||
@@ -342,11 +337,7 @@ export function App() {
|
|||||||
<I18nProvider>
|
<I18nProvider>
|
||||||
<AffineContext store={getCurrentStore()}>
|
<AffineContext store={getCurrentStore()}>
|
||||||
<ThemeProvider />
|
<ThemeProvider />
|
||||||
<RouterProvider
|
<RouterProvider router={router} />
|
||||||
fallbackElement={<AppFallback />}
|
|
||||||
router={router}
|
|
||||||
future={future}
|
|
||||||
/>
|
|
||||||
</AffineContext>
|
</AffineContext>
|
||||||
</I18nProvider>
|
</I18nProvider>
|
||||||
</FrameworkRoot>
|
</FrameworkRoot>
|
||||||
|
|||||||
@@ -25,7 +25,7 @@
|
|||||||
"next-themes": "^0.4.4",
|
"next-themes": "^0.4.4",
|
||||||
"react": "^19.0.0",
|
"react": "^19.0.0",
|
||||||
"react-dom": "^19.0.0",
|
"react-dom": "^19.0.0",
|
||||||
"react-router-dom": "^6.28.0",
|
"react-router": "^7.6.0",
|
||||||
"uuid": "^11.0.3",
|
"uuid": "^11.0.3",
|
||||||
"webm-muxer": "^5.0.3"
|
"webm-muxer": "^5.0.3"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,13 +1,12 @@
|
|||||||
import { AffineContext } from '@affine/core/components/context';
|
import { AffineContext } from '@affine/core/components/context';
|
||||||
import { WindowsAppControls } from '@affine/core/components/pure/header/windows-app-controls';
|
import { WindowsAppControls } from '@affine/core/components/pure/header/windows-app-controls';
|
||||||
import { AppContainer } from '@affine/core/desktop/components/app-container';
|
import { Router } from '@affine/core/desktop/router';
|
||||||
import { router } from '@affine/core/desktop/router';
|
|
||||||
import { I18nProvider } from '@affine/core/modules/i18n';
|
import { I18nProvider } from '@affine/core/modules/i18n';
|
||||||
import createEmotionCache from '@affine/core/utils/create-emotion-cache';
|
import createEmotionCache from '@affine/core/utils/create-emotion-cache';
|
||||||
import { CacheProvider } from '@emotion/react';
|
import { CacheProvider } from '@emotion/react';
|
||||||
import { FrameworkRoot, getCurrentStore } from '@toeverything/infra';
|
import { FrameworkRoot, getCurrentStore } from '@toeverything/infra';
|
||||||
import { Suspense } from 'react';
|
import { Suspense } from 'react';
|
||||||
import { RouterProvider } from 'react-router-dom';
|
import { BrowserRouter } from 'react-router';
|
||||||
|
|
||||||
import { setupEffects } from './effects';
|
import { setupEffects } from './effects';
|
||||||
import { DesktopThemeSync } from './theme-sync';
|
import { DesktopThemeSync } from './theme-sync';
|
||||||
@@ -34,10 +33,6 @@ if (
|
|||||||
|
|
||||||
const cache = createEmotionCache();
|
const cache = createEmotionCache();
|
||||||
|
|
||||||
const future = {
|
|
||||||
v7_startTransition: true,
|
|
||||||
} as const;
|
|
||||||
|
|
||||||
export function App() {
|
export function App() {
|
||||||
return (
|
return (
|
||||||
<Suspense>
|
<Suspense>
|
||||||
@@ -46,11 +41,9 @@ export function App() {
|
|||||||
<I18nProvider>
|
<I18nProvider>
|
||||||
<AffineContext store={getCurrentStore()}>
|
<AffineContext store={getCurrentStore()}>
|
||||||
<DesktopThemeSync />
|
<DesktopThemeSync />
|
||||||
<RouterProvider
|
<BrowserRouter basename={environment.subPath}>
|
||||||
fallbackElement={<AppContainer fallback />}
|
<Router />
|
||||||
router={router}
|
</BrowserRouter>
|
||||||
future={future}
|
|
||||||
/>
|
|
||||||
{environment.isWindows && (
|
{environment.isWindows && (
|
||||||
<div style={{ position: 'fixed', right: 0, top: 0, zIndex: 5 }}>
|
<div style={{ position: 'fixed', right: 0, top: 0, zIndex: 5 }}>
|
||||||
<WindowsAppControls />
|
<WindowsAppControls />
|
||||||
|
|||||||
@@ -35,7 +35,7 @@
|
|||||||
"next-themes": "^0.4.4",
|
"next-themes": "^0.4.4",
|
||||||
"react": "^19.0.0",
|
"react": "^19.0.0",
|
||||||
"react-dom": "^19.0.0",
|
"react-dom": "^19.0.0",
|
||||||
"react-router-dom": "^6.28.0",
|
"react-router": "^7.6.0",
|
||||||
"yjs": "^13.6.21"
|
"yjs": "^13.6.21"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { getStoreManager } from '@affine/core/blocksuite/manager/store';
|
import { getStoreManager } from '@affine/core/blocksuite/manager/store';
|
||||||
import { AffineContext } from '@affine/core/components/context';
|
import { AffineContext } from '@affine/core/components/context';
|
||||||
import { AppFallback } from '@affine/core/mobile/components/app-fallback';
|
|
||||||
import { configureMobileModules } from '@affine/core/mobile/modules';
|
import { configureMobileModules } from '@affine/core/mobile/modules';
|
||||||
import { HapticProvider } from '@affine/core/mobile/modules/haptics';
|
import { HapticProvider } from '@affine/core/mobile/modules/haptics';
|
||||||
import { NavigationGestureProvider } from '@affine/core/mobile/modules/navigation-gesture';
|
import { NavigationGestureProvider } from '@affine/core/mobile/modules/navigation-gesture';
|
||||||
@@ -56,7 +55,7 @@ import { AsyncCall } from 'async-call-rpc';
|
|||||||
import { AppTrackingTransparency } from 'capacitor-plugin-app-tracking-transparency';
|
import { AppTrackingTransparency } from 'capacitor-plugin-app-tracking-transparency';
|
||||||
import { useTheme } from 'next-themes';
|
import { useTheme } from 'next-themes';
|
||||||
import { Suspense, useEffect } from 'react';
|
import { Suspense, useEffect } from 'react';
|
||||||
import { RouterProvider } from 'react-router-dom';
|
import { RouterProvider } from 'react-router/dom';
|
||||||
|
|
||||||
import { BlocksuiteMenuConfigProvider } from './bs-menu-config';
|
import { BlocksuiteMenuConfigProvider } from './bs-menu-config';
|
||||||
import { ModalConfigProvider } from './modal-config';
|
import { ModalConfigProvider } from './modal-config';
|
||||||
@@ -72,10 +71,6 @@ window.addEventListener('beforeunload', () => {
|
|||||||
storeManagerClient.dispose();
|
storeManagerClient.dispose();
|
||||||
});
|
});
|
||||||
|
|
||||||
const future = {
|
|
||||||
v7_startTransition: true,
|
|
||||||
} as const;
|
|
||||||
|
|
||||||
const framework = new Framework();
|
const framework = new Framework();
|
||||||
configureCommonModules(framework);
|
configureCommonModules(framework);
|
||||||
configureBrowserWorkbenchModule(framework);
|
configureBrowserWorkbenchModule(framework);
|
||||||
@@ -411,11 +406,7 @@ export function App() {
|
|||||||
<KeyboardThemeProvider />
|
<KeyboardThemeProvider />
|
||||||
<ModalConfigProvider>
|
<ModalConfigProvider>
|
||||||
<BlocksuiteMenuConfigProvider>
|
<BlocksuiteMenuConfigProvider>
|
||||||
<RouterProvider
|
<RouterProvider router={router} />
|
||||||
fallbackElement={<AppFallback />}
|
|
||||||
router={router}
|
|
||||||
future={future}
|
|
||||||
/>
|
|
||||||
</BlocksuiteMenuConfigProvider>
|
</BlocksuiteMenuConfigProvider>
|
||||||
</ModalConfigProvider>
|
</ModalConfigProvider>
|
||||||
</AffineContext>
|
</AffineContext>
|
||||||
|
|||||||
@@ -20,7 +20,7 @@
|
|||||||
"@toeverything/infra": "workspace:*",
|
"@toeverything/infra": "workspace:*",
|
||||||
"react": "^19.0.0",
|
"react": "^19.0.0",
|
||||||
"react-dom": "^19.0.0",
|
"react-dom": "^19.0.0",
|
||||||
"react-router-dom": "^6.28.0"
|
"react-router": "^7.6.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/react": "^19.0.1",
|
"@types/react": "^19.0.1",
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { AffineContext } from '@affine/core/components/context';
|
import { AffineContext } from '@affine/core/components/context';
|
||||||
import { AppFallback } from '@affine/core/mobile/components/app-fallback';
|
|
||||||
import { configureMobileModules } from '@affine/core/mobile/modules';
|
import { configureMobileModules } from '@affine/core/mobile/modules';
|
||||||
import { HapticProvider } from '@affine/core/mobile/modules/haptics';
|
import { HapticProvider } from '@affine/core/mobile/modules/haptics';
|
||||||
import { VirtualKeyboardProvider } from '@affine/core/mobile/modules/virtual-keyboard';
|
import { VirtualKeyboardProvider } from '@affine/core/mobile/modules/virtual-keyboard';
|
||||||
@@ -19,7 +18,7 @@ import { StoreManagerClient } from '@affine/nbstore/worker/client';
|
|||||||
import { Framework, FrameworkRoot, getCurrentStore } from '@toeverything/infra';
|
import { Framework, FrameworkRoot, getCurrentStore } from '@toeverything/infra';
|
||||||
import { OpClient } from '@toeverything/infra/op';
|
import { OpClient } from '@toeverything/infra/op';
|
||||||
import { Suspense } from 'react';
|
import { Suspense } from 'react';
|
||||||
import { RouterProvider } from 'react-router-dom';
|
import { RouterProvider } from 'react-router/dom';
|
||||||
|
|
||||||
let storeManagerClient: StoreManagerClient;
|
let storeManagerClient: StoreManagerClient;
|
||||||
|
|
||||||
@@ -35,10 +34,6 @@ window.addEventListener('beforeunload', () => {
|
|||||||
storeManagerClient.dispose();
|
storeManagerClient.dispose();
|
||||||
});
|
});
|
||||||
|
|
||||||
const future = {
|
|
||||||
v7_startTransition: true,
|
|
||||||
} as const;
|
|
||||||
|
|
||||||
const framework = new Framework();
|
const framework = new Framework();
|
||||||
configureCommonModules(framework);
|
configureCommonModules(framework);
|
||||||
configureBrowserWorkbenchModule(framework);
|
configureBrowserWorkbenchModule(framework);
|
||||||
@@ -149,11 +144,7 @@ export function App() {
|
|||||||
<FrameworkRoot framework={frameworkProvider}>
|
<FrameworkRoot framework={frameworkProvider}>
|
||||||
<I18nProvider>
|
<I18nProvider>
|
||||||
<AffineContext store={getCurrentStore()}>
|
<AffineContext store={getCurrentStore()}>
|
||||||
<RouterProvider
|
<RouterProvider router={router} />
|
||||||
fallbackElement={<AppFallback />}
|
|
||||||
router={router}
|
|
||||||
future={future}
|
|
||||||
/>
|
|
||||||
</AffineContext>
|
</AffineContext>
|
||||||
</I18nProvider>
|
</I18nProvider>
|
||||||
</FrameworkRoot>
|
</FrameworkRoot>
|
||||||
|
|||||||
@@ -20,7 +20,7 @@
|
|||||||
"@toeverything/infra": "workspace:*",
|
"@toeverything/infra": "workspace:*",
|
||||||
"react": "^19.0.0",
|
"react": "^19.0.0",
|
||||||
"react-dom": "^19.0.0",
|
"react-dom": "^19.0.0",
|
||||||
"react-router-dom": "^6.28.0"
|
"react-router": "^7.6.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/react": "^19.0.1",
|
"@types/react": "^19.0.1",
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { AffineContext } from '@affine/core/components/context';
|
import { AffineContext } from '@affine/core/components/context';
|
||||||
import { AppContainer } from '@affine/core/desktop/components/app-container';
|
import { Router } from '@affine/core/desktop/router';
|
||||||
import { router } from '@affine/core/desktop/router';
|
|
||||||
import { configureCommonModules } from '@affine/core/modules';
|
import { configureCommonModules } from '@affine/core/modules';
|
||||||
import { I18nProvider } from '@affine/core/modules/i18n';
|
import { I18nProvider } from '@affine/core/modules/i18n';
|
||||||
import { LifecycleService } from '@affine/core/modules/lifecycle';
|
import { LifecycleService } from '@affine/core/modules/lifecycle';
|
||||||
@@ -18,7 +17,7 @@ import { CacheProvider } from '@emotion/react';
|
|||||||
import { Framework, FrameworkRoot, getCurrentStore } from '@toeverything/infra';
|
import { Framework, FrameworkRoot, getCurrentStore } from '@toeverything/infra';
|
||||||
import { OpClient } from '@toeverything/infra/op';
|
import { OpClient } from '@toeverything/infra/op';
|
||||||
import { Suspense } from 'react';
|
import { Suspense } from 'react';
|
||||||
import { RouterProvider } from 'react-router-dom';
|
import { BrowserRouter } from 'react-router';
|
||||||
|
|
||||||
const cache = createEmotionCache();
|
const cache = createEmotionCache();
|
||||||
|
|
||||||
@@ -42,10 +41,6 @@ window.addEventListener('beforeunload', () => {
|
|||||||
storeManagerClient.dispose();
|
storeManagerClient.dispose();
|
||||||
});
|
});
|
||||||
|
|
||||||
const future = {
|
|
||||||
v7_startTransition: true,
|
|
||||||
} as const;
|
|
||||||
|
|
||||||
const framework = new Framework();
|
const framework = new Framework();
|
||||||
configureCommonModules(framework);
|
configureCommonModules(framework);
|
||||||
configureBrowserWorkbenchModule(framework);
|
configureBrowserWorkbenchModule(framework);
|
||||||
@@ -90,11 +85,9 @@ export function App() {
|
|||||||
<CacheProvider value={cache}>
|
<CacheProvider value={cache}>
|
||||||
<I18nProvider>
|
<I18nProvider>
|
||||||
<AffineContext store={getCurrentStore()}>
|
<AffineContext store={getCurrentStore()}>
|
||||||
<RouterProvider
|
<BrowserRouter basename={environment.subPath}>
|
||||||
fallbackElement={<AppContainer fallback />}
|
<Router />
|
||||||
router={router}
|
</BrowserRouter>
|
||||||
future={future}
|
|
||||||
/>
|
|
||||||
</AffineContext>
|
</AffineContext>
|
||||||
</I18nProvider>
|
</I18nProvider>
|
||||||
</CacheProvider>
|
</CacheProvider>
|
||||||
|
|||||||
@@ -61,7 +61,7 @@
|
|||||||
"react": "19.1.0",
|
"react": "19.1.0",
|
||||||
"react-dom": "19.1.0",
|
"react-dom": "19.1.0",
|
||||||
"react-paginate": "^8.2.0",
|
"react-paginate": "^8.2.0",
|
||||||
"react-router-dom": "^6.28.0",
|
"react-router": "^7.6.0",
|
||||||
"react-transition-state": "^2.2.0",
|
"react-transition-state": "^2.2.0",
|
||||||
"sonner": "^2.0.0",
|
"sonner": "^2.0.0",
|
||||||
"swr": "^2.2.5",
|
"swr": "^2.2.5",
|
||||||
|
|||||||
@@ -1,14 +1,16 @@
|
|||||||
|
import { UserFriendlyError } from '@affine/error';
|
||||||
import { ArrowRightSmallIcon } from '@blocksuite/icons/rc';
|
import { ArrowRightSmallIcon } from '@blocksuite/icons/rc';
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
import { useMemo, useState } from 'react';
|
import { useCallback, useMemo, useState } from 'react';
|
||||||
import type { Location } from 'react-router-dom';
|
import type { Location } from 'react-router';
|
||||||
import { useLocation, useNavigate } from 'react-router-dom';
|
import { useLocation, useNavigate } from 'react-router';
|
||||||
import useSWR from 'swr';
|
import useSWR from 'swr';
|
||||||
|
|
||||||
import { Button } from '../../ui/button';
|
import { Button } from '../../ui/button';
|
||||||
import { Checkbox } from '../../ui/checkbox';
|
import { Checkbox } from '../../ui/checkbox';
|
||||||
import { Divider } from '../../ui/divider';
|
import { Divider } from '../../ui/divider';
|
||||||
import Input from '../../ui/input';
|
import Input from '../../ui/input';
|
||||||
|
import { notify } from '../../ui/notification';
|
||||||
import { ScrollableContainer } from '../../ui/scrollbar';
|
import { ScrollableContainer } from '../../ui/scrollbar';
|
||||||
import * as styles from './onboarding-page.css';
|
import * as styles from './onboarding-page.css';
|
||||||
import type { User } from './type';
|
import type { User } from './type';
|
||||||
@@ -118,6 +120,21 @@ export const OnboardingPage = ({
|
|||||||
() => questions?.[questionIdx],
|
() => questions?.[questionIdx],
|
||||||
[questionIdx, questions]
|
[questionIdx, questions]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const onClick = useCallback(() => {
|
||||||
|
if (callbackUrl) {
|
||||||
|
const result = navigate(callbackUrl);
|
||||||
|
if (result instanceof Promise) {
|
||||||
|
result.catch((err: Error) => {
|
||||||
|
const error = UserFriendlyError.fromAny(err);
|
||||||
|
console.error(error);
|
||||||
|
notify.error(error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
onOpenAffine();
|
||||||
|
}
|
||||||
|
}, [callbackUrl, navigate, onOpenAffine]);
|
||||||
const isMacosDesktop = BUILD_CONFIG.isElectron && environment.isMacOs;
|
const isMacosDesktop = BUILD_CONFIG.isElectron && environment.isMacOs;
|
||||||
const isWindowsDesktop = BUILD_CONFIG.isElectron && environment.isWindows;
|
const isWindowsDesktop = BUILD_CONFIG.isElectron && environment.isWindows;
|
||||||
|
|
||||||
@@ -263,13 +280,7 @@ export const OnboardingPage = ({
|
|||||||
className={clsx(styles.button, styles.openAFFiNEButton)}
|
className={clsx(styles.button, styles.openAFFiNEButton)}
|
||||||
variant="primary"
|
variant="primary"
|
||||||
size="extraLarge"
|
size="extraLarge"
|
||||||
onClick={() => {
|
onClick={onClick}
|
||||||
if (callbackUrl) {
|
|
||||||
navigate(callbackUrl);
|
|
||||||
} else {
|
|
||||||
onOpenAffine();
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
suffix={<ArrowRightSmallIcon />}
|
suffix={<ArrowRightSmallIcon />}
|
||||||
>
|
>
|
||||||
Get Started
|
Get Started
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { DialogTrigger } from '@radix-ui/react-dialog';
|
import { DialogTrigger } from '@radix-ui/react-dialog';
|
||||||
import { cssVar } from '@toeverything/theme';
|
import { cssVar } from '@toeverything/theme';
|
||||||
import { memo, useCallback } from 'react';
|
import { memo, useCallback } from 'react';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router';
|
||||||
|
|
||||||
import type { ButtonProps } from '../button';
|
import type { ButtonProps } from '../button';
|
||||||
import { Button } from '../button';
|
import { Button } from '../button';
|
||||||
|
|||||||
@@ -16,6 +16,7 @@
|
|||||||
"@affine/graphql": "workspace:*",
|
"@affine/graphql": "workspace:*",
|
||||||
"@affine/i18n": "workspace:*",
|
"@affine/i18n": "workspace:*",
|
||||||
"@affine/nbstore": "workspace:*",
|
"@affine/nbstore": "workspace:*",
|
||||||
|
"@affine/routes": "workspace:*",
|
||||||
"@affine/templates": "workspace:*",
|
"@affine/templates": "workspace:*",
|
||||||
"@affine/track": "workspace:*",
|
"@affine/track": "workspace:*",
|
||||||
"@blocksuite/affine": "workspace:*",
|
"@blocksuite/affine": "workspace:*",
|
||||||
@@ -74,7 +75,7 @@
|
|||||||
"react": "19.1.0",
|
"react": "19.1.0",
|
||||||
"react-dom": "19.1.0",
|
"react-dom": "19.1.0",
|
||||||
"react-error-boundary": "^6.0.0",
|
"react-error-boundary": "^6.0.0",
|
||||||
"react-router-dom": "^6.28.0",
|
"react-router": "^7.6.0",
|
||||||
"react-transition-state": "^2.2.0",
|
"react-transition-state": "^2.2.0",
|
||||||
"react-virtuoso": "^4.12.3",
|
"react-virtuoso": "^4.12.3",
|
||||||
"rxjs": "^7.8.1",
|
"rxjs": "^7.8.1",
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import clsx from 'clsx';
|
|||||||
import { Provider } from 'jotai/react';
|
import { Provider } from 'jotai/react';
|
||||||
import type { FC } from 'react';
|
import type { FC } from 'react';
|
||||||
import { useCallback, useMemo } from 'react';
|
import { useCallback, useMemo } from 'react';
|
||||||
import { useRouteError } from 'react-router-dom';
|
import { useRouteError } from 'react-router';
|
||||||
|
|
||||||
import * as styles from './affine-error-fallback.css';
|
import * as styles from './affine-error-fallback.css';
|
||||||
import { ErrorDetail } from './error-basic/error-detail';
|
import { ErrorDetail } from './error-basic/error-detail';
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { GlobalContextService } from '@affine/core/modules/global-context';
|
import { GlobalContextService } from '@affine/core/modules/global-context';
|
||||||
import { useLiveData, useServices } from '@toeverything/infra';
|
import { useLiveData, useServices } from '@toeverything/infra';
|
||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
import { useLocation, useParams } from 'react-router-dom';
|
import { useLocation, useParams } from 'react-router';
|
||||||
|
|
||||||
export interface DumpInfoProps {
|
export interface DumpInfoProps {
|
||||||
error: any;
|
error: any;
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ import {
|
|||||||
useRef,
|
useRef,
|
||||||
useState,
|
useState,
|
||||||
} from 'react';
|
} from 'react';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router';
|
||||||
|
|
||||||
import * as styles from './styles.css';
|
import * as styles from './styles.css';
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import { useNavigateHelper } from '../use-navigate-helper';
|
|||||||
|
|
||||||
export function useBlockSuiteMetaHelper() {
|
export function useBlockSuiteMetaHelper() {
|
||||||
const workspace = useService(WorkspaceService).workspace;
|
const workspace = useService(WorkspaceService).workspace;
|
||||||
const { openPage } = useNavigateHelper();
|
const { jumpToPage } = useNavigateHelper();
|
||||||
const docsService = useService(DocsService);
|
const docsService = useService(DocsService);
|
||||||
const docRecordList = useService(DocsService).list;
|
const docRecordList = useService(DocsService).list;
|
||||||
|
|
||||||
@@ -45,9 +45,9 @@ export function useBlockSuiteMetaHelper() {
|
|||||||
async (pageId: string, openPageAfterDuplication: boolean = true) => {
|
async (pageId: string, openPageAfterDuplication: boolean = true) => {
|
||||||
const newPageId = await docsService.duplicate(pageId);
|
const newPageId = await docsService.duplicate(pageId);
|
||||||
openPageAfterDuplication &&
|
openPageAfterDuplication &&
|
||||||
openPage(workspace.docCollection.id, newPageId);
|
jumpToPage(workspace.docCollection.id, newPageId);
|
||||||
},
|
},
|
||||||
[docsService, openPage, workspace.docCollection.id]
|
[docsService, jumpToPage, workspace.docCollection.id]
|
||||||
);
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ export const useSignOut = ({
|
|||||||
}: ConfirmModalProps = {}) => {
|
}: ConfirmModalProps = {}) => {
|
||||||
const t = useI18n();
|
const t = useI18n();
|
||||||
const { openConfirmModal } = useConfirmModal();
|
const { openConfirmModal } = useConfirmModal();
|
||||||
const { openPage } = useNavigateHelper();
|
const { jumpToAll } = useNavigateHelper();
|
||||||
|
|
||||||
const serverService = useService(ServerService);
|
const serverService = useService(ServerService);
|
||||||
const authService = useService(AuthService);
|
const authService = useService(AuthService);
|
||||||
@@ -56,14 +56,14 @@ export const useSignOut = ({
|
|||||||
w => w.flavour !== serverService.server.id
|
w => w.flavour !== serverService.server.id
|
||||||
);
|
);
|
||||||
if (localWorkspace) {
|
if (localWorkspace) {
|
||||||
openPage(localWorkspace.id, 'all');
|
jumpToAll(localWorkspace.id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [
|
}, [
|
||||||
authService,
|
authService,
|
||||||
currentWorkspaceFlavour,
|
currentWorkspaceFlavour,
|
||||||
|
jumpToAll,
|
||||||
onConfirm,
|
onConfirm,
|
||||||
openPage,
|
|
||||||
serverService.server.id,
|
serverService.server.id,
|
||||||
workspaces,
|
workspaces,
|
||||||
]);
|
]);
|
||||||
|
|||||||
@@ -1,10 +1,13 @@
|
|||||||
|
import { notify } from '@affine/component';
|
||||||
import type { SettingTab } from '@affine/core/modules/dialogs/constant';
|
import type { SettingTab } from '@affine/core/modules/dialogs/constant';
|
||||||
import { toDocSearchParams } from '@affine/core/modules/navigation';
|
import { toDocSearchParams } from '@affine/core/modules/navigation';
|
||||||
import { getOpenUrlInDesktopAppLink } from '@affine/core/modules/open-in-app';
|
import { getOpenUrlInDesktopAppLink } from '@affine/core/modules/open-in-app';
|
||||||
|
import { UserFriendlyError } from '@affine/error';
|
||||||
|
import { FACTORIES } from '@affine/routes';
|
||||||
import type { DocMode } from '@blocksuite/affine/model';
|
import type { DocMode } from '@blocksuite/affine/model';
|
||||||
import { nanoid } from 'nanoid';
|
import { nanoid } from 'nanoid';
|
||||||
import { createContext, useCallback, useContext, useMemo } from 'react';
|
import { createContext, useCallback, useContext, useMemo } from 'react';
|
||||||
import type { NavigateFunction, NavigateOptions } from 'react-router-dom';
|
import type { NavigateFunction, NavigateOptions, To } from 'react-router';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* In workbench, we use nested react-router, so default `useNavigate` can't get correct navigate function in workbench.
|
* In workbench, we use nested react-router, so default `useNavigate` can't get correct navigate function in workbench.
|
||||||
@@ -22,11 +25,27 @@ export enum RouteLogic {
|
|||||||
* Use this for over workbench navigate, for navigate in workbench, use `WorkbenchService`.
|
* Use this for over workbench navigate, for navigate in workbench, use `WorkbenchService`.
|
||||||
*/
|
*/
|
||||||
export function useNavigateHelper() {
|
export function useNavigateHelper() {
|
||||||
const navigate = useContext(NavigateContext);
|
const navigateFunction = useContext(NavigateContext);
|
||||||
|
|
||||||
if (!navigate) {
|
const navigate = useCallback(
|
||||||
throw new Error('useNavigateHelper must be used within a NavigateProvider');
|
(to: To, options?: NavigateOptions) => {
|
||||||
}
|
if (!navigateFunction) {
|
||||||
|
throw new Error(
|
||||||
|
'useNavigateHelper must be used within a NavigateProvider'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const result = navigateFunction(to, options);
|
||||||
|
if (result instanceof Promise) {
|
||||||
|
result.catch((err: Error) => {
|
||||||
|
const error = UserFriendlyError.fromAny(err);
|
||||||
|
console.error(error);
|
||||||
|
notify.error(error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
},
|
||||||
|
[navigateFunction]
|
||||||
|
);
|
||||||
|
|
||||||
const jumpToPage = useCallback(
|
const jumpToPage = useCallback(
|
||||||
(
|
(
|
||||||
@@ -34,7 +53,7 @@ export function useNavigateHelper() {
|
|||||||
pageId: string,
|
pageId: string,
|
||||||
logic: RouteLogic = RouteLogic.PUSH
|
logic: RouteLogic = RouteLogic.PUSH
|
||||||
) => {
|
) => {
|
||||||
return navigate(`/workspace/${workspaceId}/${pageId}`, {
|
return navigate(FACTORIES.workspace.doc({ workspaceId, docId: pageId }), {
|
||||||
replace: logic === RouteLogic.REPLACE,
|
replace: logic === RouteLogic.REPLACE,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@@ -56,15 +75,18 @@ export function useNavigateHelper() {
|
|||||||
refreshKey: nanoid(),
|
refreshKey: nanoid(),
|
||||||
});
|
});
|
||||||
const query = search?.size ? `?${search.toString()}` : '';
|
const query = search?.size ? `?${search.toString()}` : '';
|
||||||
return navigate(`/workspace/${workspaceId}/${pageId}${query}`, {
|
return navigate(
|
||||||
replace: logic === RouteLogic.REPLACE,
|
FACTORIES.workspace.doc({ workspaceId, docId: pageId }) + query,
|
||||||
});
|
{
|
||||||
|
replace: logic === RouteLogic.REPLACE,
|
||||||
|
}
|
||||||
|
);
|
||||||
},
|
},
|
||||||
[navigate]
|
[navigate]
|
||||||
);
|
);
|
||||||
const jumpToCollections = useCallback(
|
const jumpToCollections = useCallback(
|
||||||
(workspaceId: string, logic: RouteLogic = RouteLogic.PUSH) => {
|
(workspaceId: string, logic: RouteLogic = RouteLogic.PUSH) => {
|
||||||
return navigate(`/workspace/${workspaceId}/collection`, {
|
return navigate(FACTORIES.workspace.collections({ workspaceId }), {
|
||||||
replace: logic === RouteLogic.REPLACE,
|
replace: logic === RouteLogic.REPLACE,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@@ -72,7 +94,7 @@ export function useNavigateHelper() {
|
|||||||
);
|
);
|
||||||
const jumpToTags = useCallback(
|
const jumpToTags = useCallback(
|
||||||
(workspaceId: string, logic: RouteLogic = RouteLogic.PUSH) => {
|
(workspaceId: string, logic: RouteLogic = RouteLogic.PUSH) => {
|
||||||
return navigate(`/workspace/${workspaceId}/tag`, {
|
return navigate(FACTORIES.workspace.tags({ workspaceId }), {
|
||||||
replace: logic === RouteLogic.REPLACE,
|
replace: logic === RouteLogic.REPLACE,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@@ -84,7 +106,7 @@ export function useNavigateHelper() {
|
|||||||
tagId: string,
|
tagId: string,
|
||||||
logic: RouteLogic = RouteLogic.PUSH
|
logic: RouteLogic = RouteLogic.PUSH
|
||||||
) => {
|
) => {
|
||||||
return navigate(`/workspace/${workspaceId}/tag/${tagId}`, {
|
return navigate(FACTORIES.workspace.tags.tag({ workspaceId, tagId }), {
|
||||||
replace: logic === RouteLogic.REPLACE,
|
replace: logic === RouteLogic.REPLACE,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@@ -96,18 +118,26 @@ export function useNavigateHelper() {
|
|||||||
collectionId: string,
|
collectionId: string,
|
||||||
logic: RouteLogic = RouteLogic.PUSH
|
logic: RouteLogic = RouteLogic.PUSH
|
||||||
) => {
|
) => {
|
||||||
return navigate(`/workspace/${workspaceId}/collection/${collectionId}`, {
|
return navigate(
|
||||||
replace: logic === RouteLogic.REPLACE,
|
FACTORIES.workspace.collections.collection({
|
||||||
});
|
workspaceId,
|
||||||
|
collectionId,
|
||||||
|
}),
|
||||||
|
{
|
||||||
|
replace: logic === RouteLogic.REPLACE,
|
||||||
|
}
|
||||||
|
);
|
||||||
},
|
},
|
||||||
[navigate]
|
[navigate]
|
||||||
);
|
);
|
||||||
|
|
||||||
const openPage = useCallback(
|
const jumpToAll = useCallback(
|
||||||
(workspaceId: string, pageId: string, logic?: RouteLogic) => {
|
(workspaceId: string, logic?: RouteLogic) => {
|
||||||
return jumpToPage(workspaceId, pageId, logic);
|
return navigate(FACTORIES.workspace.all({ workspaceId }), {
|
||||||
|
replace: logic === RouteLogic.REPLACE,
|
||||||
|
});
|
||||||
},
|
},
|
||||||
[jumpToPage]
|
[navigate]
|
||||||
);
|
);
|
||||||
|
|
||||||
const jumpToIndex = useCallback(
|
const jumpToIndex = useCallback(
|
||||||
@@ -124,7 +154,7 @@ export function useNavigateHelper() {
|
|||||||
|
|
||||||
const jumpTo404 = useCallback(
|
const jumpTo404 = useCallback(
|
||||||
(logic: RouteLogic = RouteLogic.PUSH) => {
|
(logic: RouteLogic = RouteLogic.PUSH) => {
|
||||||
return navigate('/404', {
|
return navigate(FACTORIES.notFound(), {
|
||||||
replace: logic === RouteLogic.REPLACE,
|
replace: logic === RouteLogic.REPLACE,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@@ -132,7 +162,7 @@ export function useNavigateHelper() {
|
|||||||
);
|
);
|
||||||
const jumpToExpired = useCallback(
|
const jumpToExpired = useCallback(
|
||||||
(logic: RouteLogic = RouteLogic.PUSH) => {
|
(logic: RouteLogic = RouteLogic.PUSH) => {
|
||||||
return navigate('/expired', {
|
return navigate(FACTORIES.expired(), {
|
||||||
replace: logic === RouteLogic.REPLACE,
|
replace: logic === RouteLogic.REPLACE,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@@ -156,7 +186,7 @@ export function useNavigateHelper() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return navigate(
|
return navigate(
|
||||||
'/sign-in' +
|
FACTORIES.signIn() +
|
||||||
(searchParams.toString() ? '?' + searchParams.toString() : ''),
|
(searchParams.toString() ? '?' + searchParams.toString() : ''),
|
||||||
{
|
{
|
||||||
replace: logic === RouteLogic.REPLACE,
|
replace: logic === RouteLogic.REPLACE,
|
||||||
@@ -176,7 +206,7 @@ export function useNavigateHelper() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const encodedUrl = encodeURIComponent(deeplink);
|
const encodedUrl = encodeURIComponent(deeplink);
|
||||||
return navigate(`/open-app/url?url=${encodedUrl}`);
|
return navigate(FACTORIES.openApp({ action: `url?url=${encodedUrl}` }));
|
||||||
},
|
},
|
||||||
[navigate]
|
[navigate]
|
||||||
);
|
);
|
||||||
@@ -184,7 +214,8 @@ export function useNavigateHelper() {
|
|||||||
const jumpToImportTemplate = useCallback(
|
const jumpToImportTemplate = useCallback(
|
||||||
(name: string, snapshotUrl: string) => {
|
(name: string, snapshotUrl: string) => {
|
||||||
return navigate(
|
return navigate(
|
||||||
`/template/import?name=${encodeURIComponent(name)}&snapshotUrl=${encodeURIComponent(snapshotUrl)}`
|
FACTORIES.template.import() +
|
||||||
|
`?name=${encodeURIComponent(name)}&snapshotUrl=${encodeURIComponent(snapshotUrl)}`
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
[navigate]
|
[navigate]
|
||||||
@@ -201,7 +232,8 @@ export function useNavigateHelper() {
|
|||||||
searchParams.set('tab', tab);
|
searchParams.set('tab', tab);
|
||||||
}
|
}
|
||||||
return navigate(
|
return navigate(
|
||||||
`/workspace/${workspaceId}/settings?${searchParams.toString()}`,
|
FACTORIES.workspace.settings({ workspaceId }) +
|
||||||
|
(searchParams.toString() ? `?${searchParams.toString()}` : ''),
|
||||||
{
|
{
|
||||||
replace: logic === RouteLogic.REPLACE,
|
replace: logic === RouteLogic.REPLACE,
|
||||||
}
|
}
|
||||||
@@ -215,7 +247,7 @@ export function useNavigateHelper() {
|
|||||||
jumpToPageBlock,
|
jumpToPageBlock,
|
||||||
jumpToIndex,
|
jumpToIndex,
|
||||||
jumpTo404,
|
jumpTo404,
|
||||||
openPage,
|
jumpToAll,
|
||||||
jumpToExpired,
|
jumpToExpired,
|
||||||
jumpToSignIn,
|
jumpToSignIn,
|
||||||
jumpToCollection,
|
jumpToCollection,
|
||||||
@@ -231,7 +263,7 @@ export function useNavigateHelper() {
|
|||||||
jumpToPageBlock,
|
jumpToPageBlock,
|
||||||
jumpToIndex,
|
jumpToIndex,
|
||||||
jumpTo404,
|
jumpTo404,
|
||||||
openPage,
|
jumpToAll,
|
||||||
jumpToExpired,
|
jumpToExpired,
|
||||||
jumpToSignIn,
|
jumpToSignIn,
|
||||||
jumpToCollection,
|
jumpToCollection,
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ import {
|
|||||||
import { useLiveData, useService, useServices } from '@toeverything/infra';
|
import { useLiveData, useService, useServices } from '@toeverything/infra';
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
import { useCallback, useMemo, useState } from 'react';
|
import { useCallback, useMemo, useState } from 'react';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router';
|
||||||
|
|
||||||
import { usePageHelper } from '../../../blocksuite/block-suite-page-list/utils';
|
import { usePageHelper } from '../../../blocksuite/block-suite-page-list/utils';
|
||||||
import {
|
import {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import type { CollectionMeta } from '@affine/core/modules/collection';
|
import type { CollectionMeta } from '@affine/core/modules/collection';
|
||||||
import type { DocMeta, Workspace } from '@blocksuite/affine/store';
|
import type { DocMeta, Workspace } from '@blocksuite/affine/store';
|
||||||
import type { JSX, PropsWithChildren, ReactNode } from 'react';
|
import type { JSX, PropsWithChildren, ReactNode } from 'react';
|
||||||
import type { To } from 'react-router-dom';
|
import type { To } from 'react-router';
|
||||||
|
|
||||||
export type ListItem =
|
export type ListItem =
|
||||||
| DocMeta
|
| DocMeta
|
||||||
|
|||||||
@@ -160,7 +160,7 @@ const CloudWorkSpaceList = ({
|
|||||||
if (currentWorkspaceFlavour === server.id) {
|
if (currentWorkspaceFlavour === server.id) {
|
||||||
const otherWorkspace = workspaces.find(w => w.flavour !== server.id);
|
const otherWorkspace = workspaces.find(w => w.flavour !== server.id);
|
||||||
if (otherWorkspace) {
|
if (otherWorkspace) {
|
||||||
navigateHelper.openPage(otherWorkspace.id, 'all');
|
navigateHelper.jumpToAll(otherWorkspace.id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [
|
}, [
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ const Dialog = ({
|
|||||||
? workspacesService.open({ metadata: workspaceMeta })
|
? workspacesService.open({ metadata: workspaceMeta })
|
||||||
: { workspace: undefined };
|
: { workspace: undefined };
|
||||||
|
|
||||||
const { jumpToPage } = useNavigateHelper();
|
const { jumpToPage, jumpToAll } = useNavigateHelper();
|
||||||
|
|
||||||
const enableCloud = useCallback(async () => {
|
const enableCloud = useCallback(async () => {
|
||||||
try {
|
try {
|
||||||
@@ -61,7 +61,11 @@ const Dialog = ({
|
|||||||
account.id,
|
account.id,
|
||||||
selectedServer.id
|
selectedServer.id
|
||||||
);
|
);
|
||||||
jumpToPage(newId, openPageId || 'all');
|
if (openPageId) {
|
||||||
|
jumpToPage(newId, openPageId);
|
||||||
|
} else {
|
||||||
|
jumpToAll(newId);
|
||||||
|
}
|
||||||
close?.();
|
close?.();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
@@ -74,9 +78,10 @@ const Dialog = ({
|
|||||||
account,
|
account,
|
||||||
workspacesService,
|
workspacesService,
|
||||||
selectedServer.id,
|
selectedServer.id,
|
||||||
jumpToPage,
|
|
||||||
openPageId,
|
openPageId,
|
||||||
close,
|
close,
|
||||||
|
jumpToPage,
|
||||||
|
jumpToAll,
|
||||||
t,
|
t,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ const Dialog = ({
|
|||||||
workspaces.find(w => w.flavour !== 'local') ??
|
workspaces.find(w => w.flavour !== 'local') ??
|
||||||
workspaces.at(0);
|
workspaces.at(0);
|
||||||
const selectedWorkspaceName = useWorkspaceName(selectedWorkspace);
|
const selectedWorkspaceName = useWorkspaceName(selectedWorkspace);
|
||||||
const { openPage, jumpToSignIn } = useNavigateHelper();
|
const { jumpToPage, jumpToSignIn } = useNavigateHelper();
|
||||||
|
|
||||||
const noWorkspace = workspaces.length === 0;
|
const noWorkspace = workspaces.length === 0;
|
||||||
|
|
||||||
@@ -119,7 +119,7 @@ const Dialog = ({
|
|||||||
templateDownloader.data$.value,
|
templateDownloader.data$.value,
|
||||||
templateMode
|
templateMode
|
||||||
);
|
);
|
||||||
openPage(selectedWorkspace.id, docId);
|
jumpToPage(selectedWorkspace.id, docId);
|
||||||
onClose?.();
|
onClose?.();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setImportingError(err);
|
setImportingError(err);
|
||||||
@@ -129,8 +129,8 @@ const Dialog = ({
|
|||||||
}
|
}
|
||||||
}, [
|
}, [
|
||||||
importTemplateService,
|
importTemplateService,
|
||||||
|
jumpToPage,
|
||||||
onClose,
|
onClose,
|
||||||
openPage,
|
|
||||||
selectedWorkspace,
|
selectedWorkspace,
|
||||||
templateDownloader.data$.value,
|
templateDownloader.data$.value,
|
||||||
templateMode,
|
templateMode,
|
||||||
@@ -149,7 +149,7 @@ const Dialog = ({
|
|||||||
'Workspace',
|
'Workspace',
|
||||||
templateDownloader.data$.value
|
templateDownloader.data$.value
|
||||||
);
|
);
|
||||||
openPage(workspaceId, docId);
|
jumpToPage(workspaceId, docId);
|
||||||
onClose?.();
|
onClose?.();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setImportingError(err);
|
setImportingError(err);
|
||||||
@@ -158,8 +158,8 @@ const Dialog = ({
|
|||||||
}
|
}
|
||||||
}, [
|
}, [
|
||||||
importTemplateService,
|
importTemplateService,
|
||||||
|
jumpToPage,
|
||||||
onClose,
|
onClose,
|
||||||
openPage,
|
|
||||||
templateDownloader.data$.value,
|
templateDownloader.data$.value,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { AuthPageContainer } from '@affine/component/auth-components';
|
|||||||
import { useNavigateHelper } from '@affine/core/components/hooks/use-navigate-helper';
|
import { useNavigateHelper } from '@affine/core/components/hooks/use-navigate-helper';
|
||||||
import { Trans, useI18n } from '@affine/i18n';
|
import { Trans, useI18n } from '@affine/i18n';
|
||||||
import { useCallback } from 'react';
|
import { useCallback } from 'react';
|
||||||
import { useSearchParams } from 'react-router-dom';
|
import { useSearchParams } from 'react-router';
|
||||||
|
|
||||||
import * as styles from './styles.css';
|
import * as styles from './styles.css';
|
||||||
|
|
||||||
|
|||||||
@@ -14,9 +14,7 @@ import {
|
|||||||
import { useI18n } from '@affine/i18n';
|
import { useI18n } from '@affine/i18n';
|
||||||
import { useLiveData, useService } from '@toeverything/infra';
|
import { useLiveData, useService } from '@toeverything/infra';
|
||||||
import { useCallback } from 'react';
|
import { useCallback } from 'react';
|
||||||
import type { LoaderFunction } from 'react-router-dom';
|
import { useParams, useSearchParams } from 'react-router';
|
||||||
import { redirect, useParams, useSearchParams } from 'react-router-dom';
|
|
||||||
import { z } from 'zod';
|
|
||||||
|
|
||||||
import { useMutation } from '../../../components/hooks/use-mutation';
|
import { useMutation } from '../../../components/hooks/use-mutation';
|
||||||
import {
|
import {
|
||||||
@@ -28,18 +26,6 @@ import { AppContainer } from '../../components/app-container';
|
|||||||
import { ConfirmChangeEmail } from './confirm-change-email';
|
import { ConfirmChangeEmail } from './confirm-change-email';
|
||||||
import { ConfirmVerifiedEmail } from './email-verified-email';
|
import { ConfirmVerifiedEmail } from './email-verified-email';
|
||||||
|
|
||||||
const authTypeSchema = z.enum([
|
|
||||||
'onboarding',
|
|
||||||
'setPassword',
|
|
||||||
'signIn',
|
|
||||||
'changePassword',
|
|
||||||
'signUp',
|
|
||||||
'changeEmail',
|
|
||||||
'confirm-change-email',
|
|
||||||
'subscription-redirect',
|
|
||||||
'verify-email',
|
|
||||||
]);
|
|
||||||
|
|
||||||
export const Component = () => {
|
export const Component = () => {
|
||||||
const authService = useService(AuthService);
|
const authService = useService(AuthService);
|
||||||
const account = useLiveData(authService.session.account$);
|
const account = useLiveData(authService.session.account$);
|
||||||
@@ -159,14 +145,3 @@ export const Component = () => {
|
|||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const loader: LoaderFunction = async args => {
|
|
||||||
if (!args.params.authType) {
|
|
||||||
return redirect('/404');
|
|
||||||
}
|
|
||||||
if (!authTypeSchema.safeParse(args.params.authType).success) {
|
|
||||||
return redirect('/404');
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
};
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import { changeEmailMutation } from '@affine/graphql';
|
|||||||
import { useI18n } from '@affine/i18n';
|
import { useI18n } from '@affine/i18n';
|
||||||
import { useService } from '@toeverything/infra';
|
import { useService } from '@toeverything/infra';
|
||||||
import { type FC, useEffect, useState } from 'react';
|
import { type FC, useEffect, useState } from 'react';
|
||||||
import { useSearchParams } from 'react-router-dom';
|
import { useSearchParams } from 'react-router';
|
||||||
|
|
||||||
import { AppContainer } from '../../components/app-container';
|
import { AppContainer } from '../../components/app-container';
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import { verifyEmailMutation } from '@affine/graphql';
|
|||||||
import { useI18n } from '@affine/i18n';
|
import { useI18n } from '@affine/i18n';
|
||||||
import { useService } from '@toeverything/infra';
|
import { useService } from '@toeverything/infra';
|
||||||
import { type FC, useEffect, useState } from 'react';
|
import { type FC, useEffect, useState } from 'react';
|
||||||
import { useSearchParams } from 'react-router-dom';
|
import { useSearchParams } from 'react-router';
|
||||||
|
|
||||||
import { AppContainer } from '../../components/app-container';
|
import { AppContainer } from '../../components/app-container';
|
||||||
|
|
||||||
|
|||||||
@@ -1,64 +1,22 @@
|
|||||||
|
import { useAsyncNavigate } from '@affine/core/utils/use-async-navigate';
|
||||||
import { useService } from '@toeverything/infra';
|
import { useService } from '@toeverything/infra';
|
||||||
import { useEffect, useRef } from 'react';
|
import { useEffect, useRef } from 'react';
|
||||||
import {
|
import { useLoaderData } from 'react-router';
|
||||||
type LoaderFunction,
|
|
||||||
redirect,
|
|
||||||
useLoaderData,
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-restricted-imports
|
|
||||||
useNavigate,
|
|
||||||
} from 'react-router-dom';
|
|
||||||
|
|
||||||
import { AuthService } from '../../../modules/cloud';
|
import { AuthService } from '../../../modules/cloud';
|
||||||
import { supportedClient } from './common';
|
export interface MagicLinkLoaderData {
|
||||||
|
|
||||||
interface LoaderData {
|
|
||||||
token: string;
|
token: string;
|
||||||
email: string;
|
email: string;
|
||||||
redirectUri: string | null;
|
redirectUri: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const loader: LoaderFunction = ({ request }) => {
|
|
||||||
const url = new URL(request.url);
|
|
||||||
const params = url.searchParams;
|
|
||||||
const client = params.get('client');
|
|
||||||
const email = params.get('email');
|
|
||||||
const token = params.get('token');
|
|
||||||
const redirectUri = params.get('redirect_uri');
|
|
||||||
|
|
||||||
if (!email || !token) {
|
|
||||||
return redirect('/sign-in?error=Invalid magic link');
|
|
||||||
}
|
|
||||||
|
|
||||||
const payload: LoaderData = {
|
|
||||||
email,
|
|
||||||
token,
|
|
||||||
redirectUri,
|
|
||||||
};
|
|
||||||
|
|
||||||
if (!client || client === 'web') {
|
|
||||||
return payload;
|
|
||||||
}
|
|
||||||
|
|
||||||
const clientCheckResult = supportedClient.safeParse(client);
|
|
||||||
if (!clientCheckResult.success) {
|
|
||||||
return redirect('/sign-in?error=Invalid callback parameters');
|
|
||||||
}
|
|
||||||
|
|
||||||
const authParams = new URLSearchParams();
|
|
||||||
authParams.set('method', 'magic-link');
|
|
||||||
authParams.set('payload', JSON.stringify(payload));
|
|
||||||
|
|
||||||
return redirect(
|
|
||||||
`/open-app/url?url=${encodeURIComponent(`${client}://authentication?${authParams.toString()}`)}`
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const Component = () => {
|
export const Component = () => {
|
||||||
// TODO(@eyhn): loading ui
|
// TODO(@eyhn): loading ui
|
||||||
const auth = useService(AuthService);
|
const auth = useService(AuthService);
|
||||||
const data = useLoaderData() as LoaderData;
|
const data = useLoaderData() as MagicLinkLoaderData;
|
||||||
|
|
||||||
|
const nav = useAsyncNavigate();
|
||||||
|
|
||||||
const nav = useNavigate();
|
|
||||||
// loader data from useLoaderData is not reactive, so that we can safely
|
// loader data from useLoaderData is not reactive, so that we can safely
|
||||||
// assume the effect below is only triggered once
|
// assume the effect below is only triggered once
|
||||||
const triggeredRef = useRef(false);
|
const triggeredRef = useRef(false);
|
||||||
|
|||||||
@@ -1,72 +1,25 @@
|
|||||||
|
import { useAsyncNavigate } from '@affine/core/utils/use-async-navigate';
|
||||||
import { useService } from '@toeverything/infra';
|
import { useService } from '@toeverything/infra';
|
||||||
import { useEffect, useRef } from 'react';
|
import { useEffect, useRef } from 'react';
|
||||||
import {
|
import { useLoaderData } from 'react-router';
|
||||||
type LoaderFunction,
|
|
||||||
redirect,
|
|
||||||
useLoaderData,
|
|
||||||
useNavigate,
|
|
||||||
} from 'react-router-dom';
|
|
||||||
|
|
||||||
import { AuthService } from '../../../modules/cloud';
|
import { AuthService } from '../../../modules/cloud';
|
||||||
import { supportedClient } from './common';
|
|
||||||
|
|
||||||
interface LoaderData {
|
export interface OAuthCallbackLoaderData {
|
||||||
state: string;
|
state: string;
|
||||||
code: string;
|
code: string;
|
||||||
provider: string;
|
provider: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const loader: LoaderFunction = async ({ request }) => {
|
|
||||||
const url = new URL(request.url);
|
|
||||||
const queries = url.searchParams;
|
|
||||||
const code = queries.get('code');
|
|
||||||
let stateStr = queries.get('state') ?? '{}';
|
|
||||||
|
|
||||||
if (!code || !stateStr) {
|
|
||||||
return redirect('/sign-in?error=Invalid oauth callback parameters');
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const { state, client, provider } = JSON.parse(stateStr);
|
|
||||||
stateStr = state;
|
|
||||||
|
|
||||||
const payload: LoaderData = {
|
|
||||||
state,
|
|
||||||
code,
|
|
||||||
provider,
|
|
||||||
};
|
|
||||||
|
|
||||||
if (!client || client === 'web') {
|
|
||||||
return payload;
|
|
||||||
}
|
|
||||||
|
|
||||||
const clientCheckResult = supportedClient.safeParse(client);
|
|
||||||
if (!clientCheckResult.success) {
|
|
||||||
return redirect('/sign-in?error=Invalid oauth callback parameters');
|
|
||||||
}
|
|
||||||
|
|
||||||
const authParams = new URLSearchParams();
|
|
||||||
authParams.set('method', 'oauth');
|
|
||||||
authParams.set('payload', JSON.stringify(payload));
|
|
||||||
authParams.set('server', location.origin);
|
|
||||||
|
|
||||||
return redirect(
|
|
||||||
`/open-app/url?url=${encodeURIComponent(`${client}://authentication?${authParams.toString()}`)}`
|
|
||||||
);
|
|
||||||
} catch {
|
|
||||||
return redirect('/sign-in?error=Invalid oauth callback parameters');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export const Component = () => {
|
export const Component = () => {
|
||||||
const auth = useService(AuthService);
|
const auth = useService(AuthService);
|
||||||
const data = useLoaderData() as LoaderData;
|
const data = useLoaderData() as OAuthCallbackLoaderData;
|
||||||
|
|
||||||
// loader data from useLoaderData is not reactive, so that we can safely
|
// loader data from useLoaderData is not reactive, so that we can safely
|
||||||
// assume the effect below is only triggered once
|
// assume the effect below is only triggered once
|
||||||
const triggeredRef = useRef(false);
|
const triggeredRef = useRef(false);
|
||||||
|
|
||||||
const nav = useNavigate();
|
const nav = useAsyncNavigate();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (triggeredRef.current) {
|
if (triggeredRef.current) {
|
||||||
|
|||||||
@@ -1,68 +1,21 @@
|
|||||||
import { AuthService } from '@affine/core/modules/cloud';
|
import { AuthService } from '@affine/core/modules/cloud';
|
||||||
import { OAuthProviderType } from '@affine/graphql';
|
import { useAsyncNavigate } from '@affine/core/utils';
|
||||||
|
import type { OAuthProviderType } from '@affine/graphql';
|
||||||
import { useService } from '@toeverything/infra';
|
import { useService } from '@toeverything/infra';
|
||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
import {
|
import { useLoaderData } from 'react-router';
|
||||||
type LoaderFunction,
|
|
||||||
redirect,
|
|
||||||
useLoaderData,
|
|
||||||
// oxlint-disable-next-line @typescript-eslint/no-restricted-imports
|
|
||||||
useNavigate,
|
|
||||||
} from 'react-router-dom';
|
|
||||||
import { z } from 'zod';
|
|
||||||
|
|
||||||
import { supportedClient } from './common';
|
interface OAuthLoginLoaderData {
|
||||||
|
|
||||||
const supportedProvider = z.nativeEnum(OAuthProviderType);
|
|
||||||
|
|
||||||
const oauthParameters = z.object({
|
|
||||||
provider: supportedProvider,
|
|
||||||
client: supportedClient,
|
|
||||||
redirectUri: z.string().optional().nullable(),
|
|
||||||
});
|
|
||||||
|
|
||||||
interface LoaderData {
|
|
||||||
provider: OAuthProviderType;
|
provider: OAuthProviderType;
|
||||||
client: string;
|
client: string;
|
||||||
redirectUri?: string;
|
redirectUri?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const loader: LoaderFunction = async ({ request }) => {
|
|
||||||
const url = new URL(request.url);
|
|
||||||
const searchParams = url.searchParams;
|
|
||||||
const provider = searchParams.get('provider');
|
|
||||||
const client = searchParams.get('client') ?? 'web';
|
|
||||||
const redirectUri = searchParams.get('redirect_uri');
|
|
||||||
|
|
||||||
// sign out first, web only
|
|
||||||
if (client === 'web') {
|
|
||||||
await fetch('/api/auth/sign-out');
|
|
||||||
}
|
|
||||||
|
|
||||||
const paramsParseResult = oauthParameters.safeParse({
|
|
||||||
provider,
|
|
||||||
client,
|
|
||||||
redirectUri,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (paramsParseResult.success) {
|
|
||||||
return {
|
|
||||||
provider,
|
|
||||||
client,
|
|
||||||
redirectUri,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return redirect(
|
|
||||||
`/sign-in?error=${encodeURIComponent(`Invalid oauth parameters`)}`
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const Component = () => {
|
export const Component = () => {
|
||||||
const auth = useService(AuthService);
|
const auth = useService(AuthService);
|
||||||
const data = useLoaderData() as LoaderData;
|
const data = useLoaderData() as OAuthLoginLoaderData;
|
||||||
|
|
||||||
const nav = useNavigate();
|
const nav = useAsyncNavigate();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
auth
|
auth
|
||||||
|
|||||||
@@ -4,9 +4,10 @@ import { SignInPageContainer } from '@affine/component/auth-components';
|
|||||||
import { SignInPanel } from '@affine/core/components/sign-in';
|
import { SignInPanel } from '@affine/core/components/sign-in';
|
||||||
import { SignInBackgroundArts } from '@affine/core/components/sign-in/background-arts';
|
import { SignInBackgroundArts } from '@affine/core/components/sign-in/background-arts';
|
||||||
import type { AuthSessionStatus } from '@affine/core/modules/cloud/entities/session';
|
import type { AuthSessionStatus } from '@affine/core/modules/cloud/entities/session';
|
||||||
|
import { useAsyncNavigate } from '@affine/core/utils';
|
||||||
import { useI18n } from '@affine/i18n';
|
import { useI18n } from '@affine/i18n';
|
||||||
import { useCallback, useEffect } from 'react';
|
import { useCallback, useEffect } from 'react';
|
||||||
import { useNavigate, useSearchParams } from 'react-router-dom';
|
import { useSearchParams } from 'react-router';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
RouteLogic,
|
RouteLogic,
|
||||||
@@ -19,7 +20,7 @@ export const SignIn = ({
|
|||||||
redirectUrl?: string;
|
redirectUrl?: string;
|
||||||
}) => {
|
}) => {
|
||||||
const t = useI18n();
|
const t = useI18n();
|
||||||
const navigate = useNavigate();
|
const navigate = useAsyncNavigate();
|
||||||
const { jumpToIndex } = useNavigateHelper();
|
const { jumpToIndex } = useNavigateHelper();
|
||||||
const [searchParams] = useSearchParams();
|
const [searchParams] = useSearchParams();
|
||||||
const redirectUrl = redirectUrlFromProps ?? searchParams.get('redirect_uri');
|
const redirectUrl = redirectUrlFromProps ?? searchParams.get('redirect_uri');
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { GlobalDialogService } from '@affine/core/modules/dialogs';
|
|||||||
import type { DocMode } from '@blocksuite/affine/model';
|
import type { DocMode } from '@blocksuite/affine/model';
|
||||||
import { useService } from '@toeverything/infra';
|
import { useService } from '@toeverything/infra';
|
||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
import { useSearchParams } from 'react-router-dom';
|
import { useSearchParams } from 'react-router';
|
||||||
|
|
||||||
import { useNavigateHelper } from '../../../components/hooks/use-navigate-helper';
|
import { useNavigateHelper } from '../../../components/hooks/use-navigate-helper';
|
||||||
|
|
||||||
|
|||||||
@@ -17,12 +17,9 @@ import {
|
|||||||
useRef,
|
useRef,
|
||||||
useState,
|
useState,
|
||||||
} from 'react';
|
} from 'react';
|
||||||
import { useSearchParams } from 'react-router-dom';
|
import { useSearchParams } from 'react-router';
|
||||||
|
|
||||||
import {
|
import { useNavigateHelper } from '../../../components/hooks/use-navigate-helper';
|
||||||
RouteLogic,
|
|
||||||
useNavigateHelper,
|
|
||||||
} from '../../../components/hooks/use-navigate-helper';
|
|
||||||
import { WorkspaceNavigator } from '../../../components/workspace-selector';
|
import { WorkspaceNavigator } from '../../../components/workspace-selector';
|
||||||
import { AuthService } from '../../../modules/cloud';
|
import { AuthService } from '../../../modules/cloud';
|
||||||
import { AppContainer } from '../../components/app-container';
|
import { AppContainer } from '../../components/app-container';
|
||||||
@@ -55,7 +52,7 @@ export const Component = ({
|
|||||||
const list = useLiveData(workspacesService.list.workspaces$);
|
const list = useLiveData(workspacesService.list.workspaces$);
|
||||||
const listIsLoading = useLiveData(workspacesService.list.isRevalidating$);
|
const listIsLoading = useLiveData(workspacesService.list.isRevalidating$);
|
||||||
|
|
||||||
const { openPage, jumpToPage } = useNavigateHelper();
|
const { jumpToAll, jumpToPage } = useNavigateHelper();
|
||||||
const [searchParams] = useSearchParams();
|
const [searchParams] = useSearchParams();
|
||||||
|
|
||||||
const createOnceRef = useRef(false);
|
const createOnceRef = useRef(false);
|
||||||
@@ -68,12 +65,14 @@ export const Component = ({
|
|||||||
.then(({ meta, defaultDocId }) => {
|
.then(({ meta, defaultDocId }) => {
|
||||||
if (defaultDocId) {
|
if (defaultDocId) {
|
||||||
jumpToPage(meta.id, defaultDocId);
|
jumpToPage(meta.id, defaultDocId);
|
||||||
|
} else if (defaultIndexRoute === 'all') {
|
||||||
|
jumpToAll(meta.id);
|
||||||
} else {
|
} else {
|
||||||
openPage(meta.id, defaultIndexRoute);
|
jumpToPage(meta.id, defaultIndexRoute);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(err => console.error('Failed to create cloud workspace', err));
|
.catch(err => console.error('Failed to create cloud workspace', err));
|
||||||
}, [defaultIndexRoute, jumpToPage, openPage, workspacesService]);
|
}, [defaultIndexRoute, jumpToPage, jumpToAll, workspacesService]);
|
||||||
|
|
||||||
useLayoutEffect(() => {
|
useLayoutEffect(() => {
|
||||||
if (!navigating) {
|
if (!navigating) {
|
||||||
@@ -95,7 +94,11 @@ export const Component = ({
|
|||||||
// open first cloud workspace
|
// open first cloud workspace
|
||||||
const openWorkspace =
|
const openWorkspace =
|
||||||
list.find(w => w.flavour === 'affine-cloud') ?? list[0];
|
list.find(w => w.flavour === 'affine-cloud') ?? list[0];
|
||||||
openPage(openWorkspace.id, defaultIndexRoute);
|
if (defaultIndexRoute === 'all') {
|
||||||
|
jumpToAll(openWorkspace.id);
|
||||||
|
} else {
|
||||||
|
jumpToPage(openWorkspace.id, defaultIndexRoute);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -108,17 +111,22 @@ export const Component = ({
|
|||||||
const lastId = localStorage.getItem('last_workspace_id');
|
const lastId = localStorage.getItem('last_workspace_id');
|
||||||
|
|
||||||
const openWorkspace = list.find(w => w.id === lastId) ?? list[0];
|
const openWorkspace = list.find(w => w.id === lastId) ?? list[0];
|
||||||
openPage(openWorkspace.id, defaultIndexRoute, RouteLogic.REPLACE);
|
if (defaultIndexRoute === 'all') {
|
||||||
|
jumpToAll(openWorkspace.id);
|
||||||
|
} else {
|
||||||
|
jumpToPage(openWorkspace.id, defaultIndexRoute);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}, [
|
}, [
|
||||||
createCloudWorkspace,
|
createCloudWorkspace,
|
||||||
list,
|
list,
|
||||||
openPage,
|
|
||||||
searchParams,
|
searchParams,
|
||||||
listIsLoading,
|
listIsLoading,
|
||||||
loggedIn,
|
loggedIn,
|
||||||
navigating,
|
navigating,
|
||||||
defaultIndexRoute,
|
defaultIndexRoute,
|
||||||
|
jumpToAll,
|
||||||
|
jumpToPage,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const desktopApi = useServiceOptional(DesktopApiService);
|
const desktopApi = useServiceOptional(DesktopApiService);
|
||||||
@@ -138,7 +146,7 @@ export const Component = ({
|
|||||||
createdWorkspace.defaultPageId
|
createdWorkspace.defaultPageId
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
openPage(createdWorkspace.meta.id, 'all');
|
jumpToAll(createdWorkspace.meta.id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -148,7 +156,7 @@ export const Component = ({
|
|||||||
.finally(() => {
|
.finally(() => {
|
||||||
setCreating(false);
|
setCreating(false);
|
||||||
});
|
});
|
||||||
}, [jumpToPage, openPage, workspacesService]);
|
}, [jumpToAll, jumpToPage, workspacesService]);
|
||||||
|
|
||||||
if (navigating || creating) {
|
if (navigating || creating) {
|
||||||
return fallback ?? <AppContainer fallback />;
|
return fallback ?? <AppContainer fallback />;
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import { UserFriendlyError } from '@affine/error';
|
|||||||
import { WorkspaceMemberStatus } from '@affine/graphql';
|
import { WorkspaceMemberStatus } from '@affine/graphql';
|
||||||
import { useLiveData, useService } from '@toeverything/infra';
|
import { useLiveData, useService } from '@toeverything/infra';
|
||||||
import { useCallback, useEffect, useState } from 'react';
|
import { useCallback, useEffect, useState } from 'react';
|
||||||
import { Navigate, useParams } from 'react-router-dom';
|
import { Navigate, useParams } from 'react-router';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
RouteLogic,
|
RouteLogic,
|
||||||
|
|||||||
@@ -1,24 +1,8 @@
|
|||||||
import { DesktopApiService } from '@affine/core/modules/desktop-api';
|
import { DesktopApiService } from '@affine/core/modules/desktop-api';
|
||||||
import { useServiceOptional } from '@toeverything/infra';
|
import { useServiceOptional } from '@toeverything/infra';
|
||||||
import { useCallback } from 'react';
|
import { useCallback } from 'react';
|
||||||
import { redirect } from 'react-router-dom';
|
|
||||||
|
|
||||||
import { Onboarding } from '../../../components/affine/onboarding/onboarding';
|
import { Onboarding } from '../../../components/affine/onboarding/onboarding';
|
||||||
import { appConfigStorage } from '../../../components/hooks/use-app-config-storage';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* /onboarding page
|
|
||||||
*
|
|
||||||
* only for electron
|
|
||||||
*/
|
|
||||||
export const loader = () => {
|
|
||||||
if (!BUILD_CONFIG.isElectron && !appConfigStorage.get('onBoarding')) {
|
|
||||||
// onboarding is off, redirect to index
|
|
||||||
return redirect('/');
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const Component = () => {
|
export const Component = () => {
|
||||||
const desktopApi = useServiceOptional(DesktopApiService);
|
const desktopApi = useServiceOptional(DesktopApiService);
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import type { GetCurrentUserQuery } from '@affine/graphql';
|
|||||||
import { getCurrentUserQuery } from '@affine/graphql';
|
import { getCurrentUserQuery } from '@affine/graphql';
|
||||||
import { useService } from '@toeverything/infra';
|
import { useService } from '@toeverything/infra';
|
||||||
import { useCallback, useEffect, useState } from 'react';
|
import { useCallback, useEffect, useState } from 'react';
|
||||||
import { useParams, useSearchParams } from 'react-router-dom';
|
import { useParams, useSearchParams } from 'react-router';
|
||||||
|
|
||||||
import { AppContainer } from '../../components/app-container';
|
import { AppContainer } from '../../components/app-container';
|
||||||
|
|
||||||
|
|||||||
@@ -1,53 +1,4 @@
|
|||||||
import { DebugLogger } from '@affine/debug';
|
import { Navigate, useLoaderData } from 'react-router';
|
||||||
import { type LoaderFunction, Navigate, useLoaderData } from 'react-router-dom';
|
|
||||||
|
|
||||||
const trustedDomain = [
|
|
||||||
'google.com',
|
|
||||||
'stripe.com',
|
|
||||||
'github.com',
|
|
||||||
'twitter.com',
|
|
||||||
'discord.gg',
|
|
||||||
'youtube.com',
|
|
||||||
't.me',
|
|
||||||
'reddit.com',
|
|
||||||
'affine.pro',
|
|
||||||
];
|
|
||||||
|
|
||||||
const logger = new DebugLogger('redirect_proxy');
|
|
||||||
|
|
||||||
/**
|
|
||||||
* /redirect-proxy page
|
|
||||||
*
|
|
||||||
* only for web
|
|
||||||
*/
|
|
||||||
export const loader: LoaderFunction = async ({ request }) => {
|
|
||||||
const url = new URL(request.url);
|
|
||||||
const searchParams = url.searchParams;
|
|
||||||
const redirectUri = searchParams.get('redirect_uri');
|
|
||||||
|
|
||||||
if (!redirectUri) {
|
|
||||||
return { allow: false };
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const target = new URL(redirectUri);
|
|
||||||
|
|
||||||
if (
|
|
||||||
target.hostname === window.location.hostname ||
|
|
||||||
trustedDomain.some(domain =>
|
|
||||||
new RegExp(`.?${domain}$`).test(target.hostname)
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
location.href = redirectUri;
|
|
||||||
return { allow: true };
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
logger.error('Failed to parse redirect uri', e);
|
|
||||||
return { allow: false };
|
|
||||||
}
|
|
||||||
|
|
||||||
return { allow: true };
|
|
||||||
};
|
|
||||||
|
|
||||||
export const Component = () => {
|
export const Component = () => {
|
||||||
const { allow } = useLoaderData() as { allow: boolean };
|
const { allow } = useLoaderData() as { allow: boolean };
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { NotificationCenter } from '@affine/component';
|
|||||||
import { DefaultServerService } from '@affine/core/modules/cloud';
|
import { DefaultServerService } from '@affine/core/modules/cloud';
|
||||||
import { FrameworkScope, useService } from '@toeverything/infra';
|
import { FrameworkScope, useService } from '@toeverything/infra';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { Outlet } from 'react-router-dom';
|
import { Outlet } from 'react-router';
|
||||||
|
|
||||||
import { GlobalDialogs } from '../../dialogs';
|
import { GlobalDialogs } from '../../dialogs';
|
||||||
import { CustomThemeModifier } from './custom-theme';
|
import { CustomThemeModifier } from './custom-theme';
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import { track } from '@affine/track';
|
|||||||
import { effect, fromPromise, useServices } from '@toeverything/infra';
|
import { effect, fromPromise, useServices } from '@toeverything/infra';
|
||||||
import { nanoid } from 'nanoid';
|
import { nanoid } from 'nanoid';
|
||||||
import { useEffect, useMemo, useState } from 'react';
|
import { useEffect, useMemo, useState } from 'react';
|
||||||
import { useSearchParams } from 'react-router-dom';
|
import { useSearchParams } from 'react-router';
|
||||||
import { switchMap } from 'rxjs';
|
import { switchMap } from 'rxjs';
|
||||||
|
|
||||||
import { generateSubscriptionCallbackLink } from '../../../components/hooks/affine/use-subscription-notify';
|
import { generateSubscriptionCallbackLink } from '../../../components/hooks/affine/use-subscription-notify';
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { AuthPageContainer } from '@affine/component/auth-components';
|
|||||||
import { useNavigateHelper } from '@affine/core/components/hooks/use-navigate-helper';
|
import { useNavigateHelper } from '@affine/core/components/hooks/use-navigate-helper';
|
||||||
import { Trans, useI18n } from '@affine/i18n';
|
import { Trans, useI18n } from '@affine/i18n';
|
||||||
import { useCallback } from 'react';
|
import { useCallback } from 'react';
|
||||||
import { useSearchParams } from 'react-router-dom';
|
import { useSearchParams } from 'react-router';
|
||||||
|
|
||||||
import * as styles from './styles.css';
|
import * as styles from './styles.css';
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import { Trans, useI18n } from '@affine/i18n';
|
|||||||
import { CopyIcon } from '@blocksuite/icons/rc';
|
import { CopyIcon } from '@blocksuite/icons/rc';
|
||||||
import { useLiveData, useService } from '@toeverything/infra';
|
import { useLiveData, useService } from '@toeverything/infra';
|
||||||
import { useCallback, useEffect } from 'react';
|
import { useCallback, useEffect } from 'react';
|
||||||
import { useSearchParams } from 'react-router-dom';
|
import { useSearchParams } from 'react-router';
|
||||||
|
|
||||||
import { PageNotFound } from '../../404';
|
import { PageNotFound } from '../../404';
|
||||||
import * as styles from './styles.css';
|
import * as styles from './styles.css';
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ import { type I18nString, Trans, useI18n } from '@affine/i18n';
|
|||||||
import { DoneIcon, NewPageIcon, SignOutIcon } from '@blocksuite/icons/rc';
|
import { DoneIcon, NewPageIcon, SignOutIcon } from '@blocksuite/icons/rc';
|
||||||
import { useLiveData, useService } from '@toeverything/infra';
|
import { useLiveData, useService } from '@toeverything/infra';
|
||||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||||
import { useSearchParams } from 'react-router-dom';
|
import { useSearchParams } from 'react-router';
|
||||||
|
|
||||||
import { Upgrade } from '../../dialogs/setting/general-setting/plans/plan-card';
|
import { Upgrade } from '../../dialogs/setting/general-setting/plans/plan-card';
|
||||||
import { PageNotFound } from '../404';
|
import { PageNotFound } from '../404';
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { type Doc, DocsService } from '@affine/core/modules/doc';
|
|||||||
import { type AttachmentBlockModel } from '@blocksuite/affine/model';
|
import { type AttachmentBlockModel } from '@blocksuite/affine/model';
|
||||||
import { FrameworkScope, useLiveData, useService } from '@toeverything/infra';
|
import { FrameworkScope, useLiveData, useService } from '@toeverything/infra';
|
||||||
import { type ReactElement, useLayoutEffect, useState } from 'react';
|
import { type ReactElement, useLayoutEffect, useState } from 'react';
|
||||||
import { useParams } from 'react-router-dom';
|
import { useParams } from 'react-router';
|
||||||
|
|
||||||
import { ViewIcon, ViewTitle } from '../../../../modules/workbench';
|
import { ViewIcon, ViewTitle } from '../../../../modules/workbench';
|
||||||
import { PageNotFound } from '../../404';
|
import { PageNotFound } from '../../404';
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ import { useI18n } from '@affine/i18n';
|
|||||||
import { ViewLayersIcon } from '@blocksuite/icons/rc';
|
import { ViewLayersIcon } from '@blocksuite/icons/rc';
|
||||||
import { useLiveData, useService, useServices } from '@toeverything/infra';
|
import { useLiveData, useService, useServices } from '@toeverything/infra';
|
||||||
import { useCallback, useEffect, useState } from 'react';
|
import { useCallback, useEffect, useState } from 'react';
|
||||||
import { useParams } from 'react-router-dom';
|
import { useParams } from 'react-router';
|
||||||
|
|
||||||
import { useNavigateHelper } from '../../../../components/hooks/use-navigate-helper';
|
import { useNavigateHelper } from '../../../../components/hooks/use-navigate-helper';
|
||||||
import {
|
import {
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ import {
|
|||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
import { nanoid } from 'nanoid';
|
import { nanoid } from 'nanoid';
|
||||||
import { memo, useCallback, useEffect, useRef, useState } from 'react';
|
import { memo, useCallback, useEffect, useRef, useState } from 'react';
|
||||||
import { useParams } from 'react-router-dom';
|
import { useParams } from 'react-router';
|
||||||
import type { Subscription } from 'rxjs';
|
import type { Subscription } from 'rxjs';
|
||||||
|
|
||||||
import { PageNotFound } from '../../404';
|
import { PageNotFound } from '../../404';
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ import {
|
|||||||
useLocation,
|
useLocation,
|
||||||
useParams,
|
useParams,
|
||||||
useSearchParams,
|
useSearchParams,
|
||||||
} from 'react-router-dom';
|
} from 'react-router';
|
||||||
import { map } from 'rxjs';
|
import { map } from 'rxjs';
|
||||||
import * as _Y from 'yjs';
|
import * as _Y from 'yjs';
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import type { SettingTab } from '@affine/core/modules/dialogs/constant';
|
|||||||
import { WorkbenchService } from '@affine/core/modules/workbench';
|
import { WorkbenchService } from '@affine/core/modules/workbench';
|
||||||
import { useService } from '@toeverything/infra';
|
import { useService } from '@toeverything/infra';
|
||||||
import { useEffect, useRef } from 'react';
|
import { useEffect, useRef } from 'react';
|
||||||
import { useSearchParams } from 'react-router-dom';
|
import { useSearchParams } from 'react-router';
|
||||||
|
|
||||||
export const Component = () => {
|
export const Component = () => {
|
||||||
const workbenchService = useService(WorkbenchService);
|
const workbenchService = useService(WorkbenchService);
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ import { Logo1Icon } from '@blocksuite/icons/rc';
|
|||||||
import { FrameworkScope, useLiveData, useService } from '@toeverything/infra';
|
import { FrameworkScope, useLiveData, useService } from '@toeverything/infra';
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||||
import { useLocation } from 'react-router-dom';
|
import { useLocation } from 'react-router';
|
||||||
|
|
||||||
import { PageNotFound } from '../../404';
|
import { PageNotFound } from '../../404';
|
||||||
import { ShareFooter } from './share-footer';
|
import { ShareFooter } from './share-footer';
|
||||||
@@ -186,7 +186,7 @@ const SharePageInner = ({
|
|||||||
|
|
||||||
const t = useI18n();
|
const t = useI18n();
|
||||||
const pageTitle = useLiveData(page?.title$);
|
const pageTitle = useLiveData(page?.title$);
|
||||||
const { jumpToPageBlock, openPage } = useNavigateHelper();
|
const { jumpToPageBlock, jumpToPage } = useNavigateHelper();
|
||||||
|
|
||||||
const onEditorLoad = useCallback(
|
const onEditorLoad = useCallback(
|
||||||
(editorContainer: AffineEditorContainer) => {
|
(editorContainer: AffineEditorContainer) => {
|
||||||
@@ -212,7 +212,7 @@ const SharePageInner = ({
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
return openPage(workspaceId, pageId);
|
return jumpToPage(workspaceId, pageId);
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -221,7 +221,13 @@ const SharePageInner = ({
|
|||||||
unbind();
|
unbind();
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
[editor, setActiveBlocksuiteEditor, jumpToPageBlock, openPage, workspaceId]
|
[
|
||||||
|
setActiveBlocksuiteEditor,
|
||||||
|
editor,
|
||||||
|
jumpToPage,
|
||||||
|
workspaceId,
|
||||||
|
jumpToPageBlock,
|
||||||
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
if (noPermission) {
|
if (noPermission) {
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ import {
|
|||||||
} from '@affine/core/modules/workbench';
|
} from '@affine/core/modules/workbench';
|
||||||
import { useLiveData, useService } from '@toeverything/infra';
|
import { useLiveData, useService } from '@toeverything/infra';
|
||||||
import { useCallback, useEffect, useState } from 'react';
|
import { useCallback, useEffect, useState } from 'react';
|
||||||
import { useParams } from 'react-router-dom';
|
import { useParams } from 'react-router';
|
||||||
|
|
||||||
import { PageNotFound } from '../../404';
|
import { PageNotFound } from '../../404';
|
||||||
import { AllDocSidebarTabs } from '../layouts/all-doc-sidebar-tabs';
|
import { AllDocSidebarTabs } from '../layouts/all-doc-sidebar-tabs';
|
||||||
|
|||||||
219
packages/frontend/core/src/desktop/router-loader.tsx
Normal file
219
packages/frontend/core/src/desktop/router-loader.tsx
Normal file
@@ -0,0 +1,219 @@
|
|||||||
|
import { DebugLogger } from '@affine/debug';
|
||||||
|
import { OAuthProviderType } from '@affine/graphql';
|
||||||
|
import { FACTORIES } from '@affine/routes';
|
||||||
|
import type { LoaderFunction } from 'react-router';
|
||||||
|
import { redirect } from 'react-router';
|
||||||
|
import { z } from 'zod';
|
||||||
|
|
||||||
|
import { appConfigStorage } from '../components/hooks/use-app-config-storage';
|
||||||
|
import { supportedClient } from './pages/auth/common';
|
||||||
|
import type { MagicLinkLoaderData } from './pages/auth/magic-link';
|
||||||
|
import type { OAuthCallbackLoaderData } from './pages/auth/oauth-callback';
|
||||||
|
|
||||||
|
const trustedDomain = [
|
||||||
|
'google.com',
|
||||||
|
'stripe.com',
|
||||||
|
'github.com',
|
||||||
|
'twitter.com',
|
||||||
|
'discord.gg',
|
||||||
|
'youtube.com',
|
||||||
|
't.me',
|
||||||
|
'reddit.com',
|
||||||
|
'affine.pro',
|
||||||
|
];
|
||||||
|
|
||||||
|
const authTypeSchema = z.enum([
|
||||||
|
'onboarding',
|
||||||
|
'setPassword',
|
||||||
|
'signIn',
|
||||||
|
'changePassword',
|
||||||
|
'signUp',
|
||||||
|
'changeEmail',
|
||||||
|
'confirm-change-email',
|
||||||
|
'subscription-redirect',
|
||||||
|
'verify-email',
|
||||||
|
]);
|
||||||
|
|
||||||
|
const supportedProvider = z.nativeEnum(OAuthProviderType);
|
||||||
|
|
||||||
|
const oauthParameters = z.object({
|
||||||
|
provider: supportedProvider,
|
||||||
|
client: supportedClient,
|
||||||
|
redirectUri: z.string().optional().nullable(),
|
||||||
|
});
|
||||||
|
|
||||||
|
const redirectLogger = new DebugLogger('redirect_proxy');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* /onboarding page
|
||||||
|
*
|
||||||
|
* only for electron
|
||||||
|
*/
|
||||||
|
export const onboardingLoader = async () => {
|
||||||
|
if (!BUILD_CONFIG.isElectron && !appConfigStorage.get('onBoarding')) {
|
||||||
|
// onboarding is off, redirect to index
|
||||||
|
return redirect('/');
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* /redirect-proxy page
|
||||||
|
*
|
||||||
|
* only for web
|
||||||
|
*/
|
||||||
|
export const redirectLoader: LoaderFunction = async ({ request }) => {
|
||||||
|
const url = new URL(request.url);
|
||||||
|
const searchParams = url.searchParams;
|
||||||
|
const redirectUri = searchParams.get('redirect_uri');
|
||||||
|
|
||||||
|
if (!redirectUri) {
|
||||||
|
return { allow: false };
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const target = new URL(redirectUri);
|
||||||
|
|
||||||
|
if (
|
||||||
|
target.hostname === window.location.hostname ||
|
||||||
|
trustedDomain.some(domain =>
|
||||||
|
new RegExp(`.?${domain}$`).test(target.hostname)
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
location.href = redirectUri;
|
||||||
|
return { allow: true };
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
redirectLogger.error('Failed to parse redirect uri', e);
|
||||||
|
return { allow: false };
|
||||||
|
}
|
||||||
|
|
||||||
|
return { allow: true };
|
||||||
|
};
|
||||||
|
|
||||||
|
export const authLoader: LoaderFunction = async args => {
|
||||||
|
if (!args.params.authType) {
|
||||||
|
return redirect(FACTORIES.notFound());
|
||||||
|
}
|
||||||
|
if (!authTypeSchema.safeParse(args.params.authType).success) {
|
||||||
|
return redirect(FACTORIES.notFound());
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const magicLinkLoader: LoaderFunction = ({ request }) => {
|
||||||
|
const url = new URL(request.url);
|
||||||
|
const params = url.searchParams;
|
||||||
|
const client = params.get('client');
|
||||||
|
const email = params.get('email');
|
||||||
|
const token = params.get('token');
|
||||||
|
const redirectUri = params.get('redirect_uri');
|
||||||
|
|
||||||
|
if (!email || !token) {
|
||||||
|
return redirect(FACTORIES.signIn() + '?error=Invalid magic link');
|
||||||
|
}
|
||||||
|
|
||||||
|
const payload: MagicLinkLoaderData = {
|
||||||
|
email,
|
||||||
|
token,
|
||||||
|
redirectUri,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!client || client === 'web') {
|
||||||
|
return payload;
|
||||||
|
}
|
||||||
|
|
||||||
|
const clientCheckResult = supportedClient.safeParse(client);
|
||||||
|
if (!clientCheckResult.success) {
|
||||||
|
return redirect(FACTORIES.signIn() + '?error=Invalid callback parameters');
|
||||||
|
}
|
||||||
|
|
||||||
|
const authParams = new URLSearchParams();
|
||||||
|
authParams.set('method', 'magic-link');
|
||||||
|
authParams.set('payload', JSON.stringify(payload));
|
||||||
|
|
||||||
|
return redirect(
|
||||||
|
`${FACTORIES.openApp({ action: 'url' })}?url=${encodeURIComponent(`${client}://authentication?${authParams.toString()}`)}`
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const oauthLoginLoader: LoaderFunction = async ({ request }) => {
|
||||||
|
const url = new URL(request.url);
|
||||||
|
const searchParams = url.searchParams;
|
||||||
|
const provider = searchParams.get('provider');
|
||||||
|
const client = searchParams.get('client') ?? 'web';
|
||||||
|
const redirectUri = searchParams.get('redirect_uri');
|
||||||
|
|
||||||
|
// sign out first, web only
|
||||||
|
if (client === 'web') {
|
||||||
|
await fetch('/api/auth/sign-out');
|
||||||
|
}
|
||||||
|
|
||||||
|
const paramsParseResult = oauthParameters.safeParse({
|
||||||
|
provider,
|
||||||
|
client,
|
||||||
|
redirectUri,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (paramsParseResult.success) {
|
||||||
|
return {
|
||||||
|
provider,
|
||||||
|
client,
|
||||||
|
redirectUri,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return redirect(
|
||||||
|
`${FACTORIES.signIn()}?error=${encodeURIComponent(`Invalid oauth parameters`)}`
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const oauthCallbackLoader: LoaderFunction = async ({ request }) => {
|
||||||
|
const url = new URL(request.url);
|
||||||
|
const queries = url.searchParams;
|
||||||
|
const code = queries.get('code');
|
||||||
|
let stateStr = queries.get('state') ?? '{}';
|
||||||
|
|
||||||
|
if (!code || !stateStr) {
|
||||||
|
return redirect(
|
||||||
|
`${FACTORIES.signIn()}?error=${encodeURIComponent(`Invalid oauth callback parameters`)}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const { state, client, provider } = JSON.parse(stateStr);
|
||||||
|
stateStr = state;
|
||||||
|
|
||||||
|
const payload: OAuthCallbackLoaderData = {
|
||||||
|
state,
|
||||||
|
code,
|
||||||
|
provider,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!client || client === 'web') {
|
||||||
|
return payload;
|
||||||
|
}
|
||||||
|
|
||||||
|
const clientCheckResult = supportedClient.safeParse(client);
|
||||||
|
if (!clientCheckResult.success) {
|
||||||
|
return redirect(
|
||||||
|
`${FACTORIES.signIn()}?error=${encodeURIComponent(`Invalid oauth callback parameters`)}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const authParams = new URLSearchParams();
|
||||||
|
authParams.set('method', 'oauth');
|
||||||
|
authParams.set('payload', JSON.stringify(payload));
|
||||||
|
authParams.set('server', location.origin);
|
||||||
|
|
||||||
|
return redirect(
|
||||||
|
`${FACTORIES.openApp({ action: 'url' })}?url=${encodeURIComponent(`${client}://authentication?${authParams.toString()}`)}`
|
||||||
|
);
|
||||||
|
} catch {
|
||||||
|
return redirect(
|
||||||
|
`${FACTORIES.signIn()}?error=${encodeURIComponent(`Invalid oauth callback parameters`)}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -1,16 +1,26 @@
|
|||||||
import { wrapCreateBrowserRouterV6 } from '@sentry/react';
|
import { FACTORIES, lazy, RELATIVE_ROUTES } from '@affine/routes';
|
||||||
|
import { withSentryReactRouterV7Routing } from '@sentry/react';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import type { RouteObject } from 'react-router-dom';
|
import type { Params } from 'react-router';
|
||||||
import {
|
import {
|
||||||
createBrowserRouter as reactRouterCreateBrowserRouter,
|
|
||||||
redirect,
|
redirect,
|
||||||
|
Route,
|
||||||
|
Routes as ReactRouterRoutes,
|
||||||
useNavigate,
|
useNavigate,
|
||||||
} from 'react-router-dom';
|
} from 'react-router';
|
||||||
|
|
||||||
import { AffineErrorComponent } from '../components/affine/affine-error-boundary/affine-error-fallback';
|
import { AffineErrorComponent } from '../components/affine/affine-error-boundary/affine-error-fallback';
|
||||||
import { NavigateContext } from '../components/hooks/use-navigate-helper';
|
import { NavigateContext } from '../components/hooks/use-navigate-helper';
|
||||||
|
import { AppContainer } from './components/app-container';
|
||||||
import { RootWrapper } from './pages/root';
|
import { RootWrapper } from './pages/root';
|
||||||
|
import {
|
||||||
|
authLoader,
|
||||||
|
magicLinkLoader,
|
||||||
|
oauthCallbackLoader,
|
||||||
|
oauthLoginLoader,
|
||||||
|
onboardingLoader,
|
||||||
|
redirectLoader,
|
||||||
|
} from './router-loader';
|
||||||
export function RootRouter() {
|
export function RootRouter() {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const [ready, setReady] = useState(false);
|
const [ready, setReady] = useState(false);
|
||||||
@@ -27,169 +37,194 @@ export function RootRouter() {
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
export const Index = lazy(async () => await import('./pages/index'));
|
||||||
export const topLevelRoutes = [
|
export const Workspace = lazy(
|
||||||
{
|
async () => await import('./pages/workspace/index')
|
||||||
element: <RootRouter />,
|
|
||||||
errorElement: <AffineErrorComponent />,
|
|
||||||
children: [
|
|
||||||
{
|
|
||||||
path: '/',
|
|
||||||
lazy: () => import('./pages/index'),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: '/workspace/:workspaceId/*',
|
|
||||||
lazy: () => import('./pages/workspace/index'),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: '/share/:workspaceId/:pageId',
|
|
||||||
loader: ({ params }) => {
|
|
||||||
return redirect(`/workspace/${params.workspaceId}/${params.pageId}`);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: '/404',
|
|
||||||
lazy: () => import('./pages/404'),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: '/expired',
|
|
||||||
lazy: () => import('./pages/expired'),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: '/invite/:inviteId',
|
|
||||||
lazy: () => import('./pages/invite'),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: '/upgrade-success',
|
|
||||||
lazy: () => import('./pages/upgrade-success'),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: '/upgrade-success/team',
|
|
||||||
lazy: () => import('./pages/upgrade-success/team'),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: '/upgrade-success/self-hosted-team',
|
|
||||||
lazy: () => import('./pages/upgrade-success/self-host-team'),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: '/ai-upgrade-success',
|
|
||||||
lazy: () => import('./pages/ai-upgrade-success'),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: '/onboarding',
|
|
||||||
lazy: () => import('./pages/onboarding'),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: '/redirect-proxy',
|
|
||||||
lazy: () => import('./pages/redirect'),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: '/subscribe',
|
|
||||||
lazy: () => import('./pages/subscribe'),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: '/upgrade-to-team',
|
|
||||||
lazy: () => import('./pages/upgrade-to-team'),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: '/try-cloud',
|
|
||||||
loader: () => {
|
|
||||||
return redirect(
|
|
||||||
`/sign-in?redirect_uri=${encodeURIComponent('/?initCloud=true')}`
|
|
||||||
);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: '/theme-editor',
|
|
||||||
lazy: () => import('./pages/theme-editor'),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: '/clipper/import',
|
|
||||||
lazy: () => import('./pages/import-clipper'),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: '/template/import',
|
|
||||||
lazy: () => import('./pages/import-template'),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: '/template/preview',
|
|
||||||
loader: ({ request }) => {
|
|
||||||
const url = new URL(request.url);
|
|
||||||
const workspaceId = url.searchParams.get('workspaceId');
|
|
||||||
const docId = url.searchParams.get('docId');
|
|
||||||
const templateName = url.searchParams.get('name');
|
|
||||||
const templateMode = url.searchParams.get('mode');
|
|
||||||
const snapshotUrl = url.searchParams.get('snapshotUrl');
|
|
||||||
|
|
||||||
return redirect(
|
|
||||||
`/workspace/${workspaceId}/${docId}?${new URLSearchParams({
|
|
||||||
isTemplate: 'true',
|
|
||||||
templateName: templateName ?? '',
|
|
||||||
snapshotUrl: snapshotUrl ?? '',
|
|
||||||
mode: templateMode ?? 'page',
|
|
||||||
}).toString()}`
|
|
||||||
);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: '/auth/:authType',
|
|
||||||
lazy: () => import(/* webpackChunkName: "auth" */ './pages/auth/auth'),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: '/sign-In',
|
|
||||||
lazy: () =>
|
|
||||||
import(/* webpackChunkName: "auth" */ './pages/auth/sign-in'),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: '/magic-link',
|
|
||||||
lazy: () =>
|
|
||||||
import(/* webpackChunkName: "auth" */ './pages/auth/magic-link'),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: '/oauth/login',
|
|
||||||
lazy: () =>
|
|
||||||
import(/* webpackChunkName: "auth" */ './pages/auth/oauth-login'),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: '/oauth/callback',
|
|
||||||
lazy: () =>
|
|
||||||
import(/* webpackChunkName: "auth" */ './pages/auth/oauth-callback'),
|
|
||||||
},
|
|
||||||
// deprecated, keep for old client compatibility
|
|
||||||
// TODO(@forehalo): remove
|
|
||||||
{
|
|
||||||
path: '/desktop-signin',
|
|
||||||
lazy: () =>
|
|
||||||
import(/* webpackChunkName: "auth" */ './pages/auth/oauth-login'),
|
|
||||||
},
|
|
||||||
// deprecated, keep for old client compatibility
|
|
||||||
// use '/sign-in'
|
|
||||||
// TODO(@forehalo): remove
|
|
||||||
{
|
|
||||||
path: '/signIn',
|
|
||||||
lazy: () =>
|
|
||||||
import(/* webpackChunkName: "auth" */ './pages/auth/sign-in'),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: '/open-app/:action',
|
|
||||||
lazy: () => import('./pages/open-app'),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: '*',
|
|
||||||
lazy: () => import('./pages/404'),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
] satisfies [RouteObject, ...RouteObject[]];
|
|
||||||
|
|
||||||
const createBrowserRouter = wrapCreateBrowserRouterV6(
|
|
||||||
reactRouterCreateBrowserRouter
|
|
||||||
);
|
);
|
||||||
export const router = (
|
export const NotFound = lazy(async () => await import('./pages/404'));
|
||||||
window.SENTRY_RELEASE ? createBrowserRouter : reactRouterCreateBrowserRouter
|
export const Expired = lazy(async () => await import('./pages/expired'));
|
||||||
)(topLevelRoutes, {
|
export const Invite = lazy(async () => await import('./pages/invite'));
|
||||||
basename: environment.subPath,
|
export const UpgradeSuccess = lazy(
|
||||||
future: {
|
async () => await import('./pages/upgrade-success')
|
||||||
v7_normalizeFormMethod: true,
|
);
|
||||||
},
|
export const UpgradeSuccessTeam = lazy(
|
||||||
});
|
async () => await import('./pages/upgrade-success/team')
|
||||||
|
);
|
||||||
|
export const UpgradeSuccessSelfHostedTeam = lazy(
|
||||||
|
async () => await import('./pages/upgrade-success/self-host-team')
|
||||||
|
);
|
||||||
|
export const AIUpgradeSuccess = lazy(
|
||||||
|
async () => await import('./pages/ai-upgrade-success')
|
||||||
|
);
|
||||||
|
export const Subscribe = lazy(async () => await import('./pages/subscribe'));
|
||||||
|
export const UpgradeToTeam = lazy(
|
||||||
|
async () => await import('./pages/upgrade-to-team')
|
||||||
|
);
|
||||||
|
export const ThemeEditor = lazy(
|
||||||
|
async () => await import('./pages/theme-editor')
|
||||||
|
);
|
||||||
|
export const ImportClipper = lazy(
|
||||||
|
async () => await import('./pages/import-clipper')
|
||||||
|
);
|
||||||
|
export const ImportTemplate = lazy(
|
||||||
|
async () => await import('./pages/import-template')
|
||||||
|
);
|
||||||
|
export const OpenApp = lazy(async () => await import('./pages/open-app'));
|
||||||
|
export const Onboarding = lazy(async () => await import('./pages/onboarding'));
|
||||||
|
export const Redirect = lazy(async () => await import('./pages/redirect'));
|
||||||
|
export const Auth = lazy(
|
||||||
|
async () => await import(/* webpackChunkName: "auth" */ './pages/auth/auth')
|
||||||
|
);
|
||||||
|
export const SignIn = lazy(
|
||||||
|
async () =>
|
||||||
|
await import(/* webpackChunkName: "auth" */ './pages/auth/sign-in')
|
||||||
|
);
|
||||||
|
export const MagicLink = lazy(
|
||||||
|
async () =>
|
||||||
|
await import(/* webpackChunkName: "auth" */ './pages/auth/magic-link')
|
||||||
|
);
|
||||||
|
export const OAuthLogin = lazy(
|
||||||
|
async () =>
|
||||||
|
await import(/* webpackChunkName: "auth" */ './pages/auth/oauth-login')
|
||||||
|
);
|
||||||
|
export const OAuthCallback = lazy(
|
||||||
|
async () =>
|
||||||
|
await import(/* webpackChunkName: "auth" */ './pages/auth/oauth-callback')
|
||||||
|
);
|
||||||
|
|
||||||
|
// Define routes using JSX syntax for better type checking
|
||||||
|
export const routes = (
|
||||||
|
<Route
|
||||||
|
element={<RootRouter />}
|
||||||
|
errorElement={<AffineErrorComponent />}
|
||||||
|
hydrateFallbackElement={<AppContainer fallback />}
|
||||||
|
>
|
||||||
|
<Route path={RELATIVE_ROUTES.index}>
|
||||||
|
<Route index element={<Index />} />
|
||||||
|
<Route path={RELATIVE_ROUTES.workspace.index} element={<Workspace />} />
|
||||||
|
<Route
|
||||||
|
path={RELATIVE_ROUTES.share}
|
||||||
|
loader={async ({ params }: { params: Params<string> }) => {
|
||||||
|
return redirect(
|
||||||
|
FACTORIES.workspace.doc({
|
||||||
|
workspaceId: params.workspaceId ?? '',
|
||||||
|
docId: params.pageId ?? '',
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Route path={RELATIVE_ROUTES.notFound} element={<NotFound />} />
|
||||||
|
<Route path={RELATIVE_ROUTES.expired} element={<Expired />} />
|
||||||
|
<Route path={RELATIVE_ROUTES.invite} element={<Invite />} />
|
||||||
|
<Route path={RELATIVE_ROUTES.upgradeSuccess.index}>
|
||||||
|
<Route index element={<UpgradeSuccess />} />
|
||||||
|
<Route
|
||||||
|
path={RELATIVE_ROUTES.upgradeSuccess.team}
|
||||||
|
element={<UpgradeSuccessTeam />}
|
||||||
|
/>
|
||||||
|
<Route
|
||||||
|
path={RELATIVE_ROUTES.upgradeSuccess.selfHostTeam}
|
||||||
|
element={<UpgradeSuccessSelfHostedTeam />}
|
||||||
|
/>
|
||||||
|
</Route>
|
||||||
|
<Route
|
||||||
|
path={RELATIVE_ROUTES.aiUpgradeSuccess}
|
||||||
|
element={<AIUpgradeSuccess />}
|
||||||
|
/>
|
||||||
|
<Route
|
||||||
|
path={RELATIVE_ROUTES.onboarding}
|
||||||
|
element={<Onboarding />}
|
||||||
|
loader={onboardingLoader}
|
||||||
|
/>
|
||||||
|
<Route
|
||||||
|
path={RELATIVE_ROUTES.redirect}
|
||||||
|
element={<Redirect />}
|
||||||
|
loader={redirectLoader}
|
||||||
|
/>
|
||||||
|
<Route path={RELATIVE_ROUTES.subscribe} element={<Subscribe />} />
|
||||||
|
<Route path={RELATIVE_ROUTES.upgradeToTeam} element={<UpgradeToTeam />} />
|
||||||
|
<Route
|
||||||
|
path={RELATIVE_ROUTES.tryCloud}
|
||||||
|
loader={async () => {
|
||||||
|
return redirect(
|
||||||
|
FACTORIES.signIn() +
|
||||||
|
`?redirect_uri=${encodeURIComponent('/?initCloud=true')}`
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Route path={RELATIVE_ROUTES.themeEditor} element={<ThemeEditor />} />
|
||||||
|
<Route path="clipper/import" element={<ImportClipper />} />
|
||||||
|
<Route path={RELATIVE_ROUTES.template.index} element={<ImportTemplate />}>
|
||||||
|
<Route
|
||||||
|
path={RELATIVE_ROUTES.template.import}
|
||||||
|
element={<ImportTemplate />}
|
||||||
|
/>
|
||||||
|
<Route
|
||||||
|
path={RELATIVE_ROUTES.template.preview}
|
||||||
|
loader={async ({ request }: { request: Request }) => {
|
||||||
|
const url = new URL(request.url);
|
||||||
|
const workspaceId = url.searchParams.get('workspaceId');
|
||||||
|
const docId = url.searchParams.get('docId');
|
||||||
|
const templateName = url.searchParams.get('name');
|
||||||
|
const templateMode = url.searchParams.get('mode');
|
||||||
|
const snapshotUrl = url.searchParams.get('snapshotUrl');
|
||||||
|
|
||||||
|
return redirect(
|
||||||
|
FACTORIES.workspace.doc({
|
||||||
|
workspaceId: workspaceId ?? '',
|
||||||
|
docId: docId ?? '',
|
||||||
|
}) +
|
||||||
|
`?${new URLSearchParams({
|
||||||
|
isTemplate: 'true',
|
||||||
|
templateName: templateName ?? '',
|
||||||
|
snapshotUrl: snapshotUrl ?? '',
|
||||||
|
mode: templateMode ?? 'page',
|
||||||
|
}).toString()}`
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Route>
|
||||||
|
|
||||||
|
<Route
|
||||||
|
path={RELATIVE_ROUTES.auth}
|
||||||
|
element={<Auth />}
|
||||||
|
loader={authLoader}
|
||||||
|
/>
|
||||||
|
<Route path={RELATIVE_ROUTES.signIn} element={<SignIn />} />
|
||||||
|
<Route
|
||||||
|
path={RELATIVE_ROUTES.magicLink}
|
||||||
|
element={<MagicLink />}
|
||||||
|
loader={magicLinkLoader}
|
||||||
|
/>
|
||||||
|
<Route path={RELATIVE_ROUTES.oauth.index} element={<NotFound />}>
|
||||||
|
<Route
|
||||||
|
path={RELATIVE_ROUTES.oauth.login}
|
||||||
|
element={<OAuthLogin />}
|
||||||
|
loader={oauthLoginLoader}
|
||||||
|
/>
|
||||||
|
<Route
|
||||||
|
path={RELATIVE_ROUTES.oauth.callback}
|
||||||
|
element={<OAuthCallback />}
|
||||||
|
loader={oauthCallbackLoader}
|
||||||
|
/>
|
||||||
|
</Route>
|
||||||
|
<Route path={RELATIVE_ROUTES.openApp} element={<OpenApp />} />
|
||||||
|
{/* deprecated, keep for old client compatibility */}
|
||||||
|
{/* TODO(@forehalo): remove */}
|
||||||
|
<Route path="desktop-signin" element={<OAuthLogin />} />
|
||||||
|
{/* deprecated, keep for old client compatibility */}
|
||||||
|
{/* use '/sign-in' */}
|
||||||
|
{/* TODO(@forehalo): remove */}
|
||||||
|
<Route path="signIn" element={<SignIn />} />
|
||||||
|
<Route path="*" element={<NotFound />} />
|
||||||
|
</Route>
|
||||||
|
</Route>
|
||||||
|
);
|
||||||
|
|
||||||
|
// Apply Sentry wrapper to ReactRouterRoutes if needed
|
||||||
|
const Routes = window.SENTRY_RELEASE
|
||||||
|
? withSentryReactRouterV7Routing(ReactRouterRoutes)
|
||||||
|
: ReactRouterRoutes;
|
||||||
|
|
||||||
|
// Export Router component - will be wrapped by BrowserRouter in app.tsx
|
||||||
|
export const Router = () => <Routes>{routes}</Routes>;
|
||||||
|
|||||||
@@ -1,52 +1,52 @@
|
|||||||
import type { RouteObject } from 'react-router-dom';
|
import type { RouteObject } from 'react-router';
|
||||||
|
|
||||||
export const workbenchRoutes = [
|
export const workbenchRoutes = [
|
||||||
{
|
{
|
||||||
path: '/all',
|
path: '/all',
|
||||||
lazy: () => import('./pages/workspace/all-page/all-page'),
|
lazy: async () => await import('./pages/workspace/all-page/all-page'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/all-old',
|
path: '/all-old',
|
||||||
lazy: () => import('./pages/workspace/all-page-old/all-page'),
|
lazy: async () => await import('./pages/workspace/all-page-old/all-page'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/collection',
|
path: '/collection',
|
||||||
lazy: () => import('./pages/workspace/all-collection'),
|
lazy: async () => await import('./pages/workspace/all-collection'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/collection/:collectionId',
|
path: '/collection/:collectionId',
|
||||||
lazy: () => import('./pages/workspace/collection/index'),
|
lazy: async () => await import('./pages/workspace/collection/index'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/tag',
|
path: '/tag',
|
||||||
lazy: () => import('./pages/workspace/all-tag'),
|
lazy: async () => await import('./pages/workspace/all-tag'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/tag/:tagId',
|
path: '/tag/:tagId',
|
||||||
lazy: () => import('./pages/workspace/tag'),
|
lazy: async () => await import('./pages/workspace/tag'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/trash',
|
path: '/trash',
|
||||||
lazy: () => import('./pages/workspace/trash-page'),
|
lazy: async () => await import('./pages/workspace/trash-page'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/:pageId',
|
path: '/:pageId',
|
||||||
lazy: () => import('./pages/workspace/detail-page/detail-page'),
|
lazy: async () => await import('./pages/workspace/detail-page/detail-page'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/:pageId/attachments/:attachmentId',
|
path: '/:pageId/attachments/:attachmentId',
|
||||||
lazy: () => import('./pages/workspace/attachment/index'),
|
lazy: async () => await import('./pages/workspace/attachment/index'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/journals',
|
path: '/journals',
|
||||||
lazy: () => import('./pages/journals'),
|
lazy: async () => await import('./pages/journals'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/settings',
|
path: '/settings',
|
||||||
lazy: () => import('./pages/workspace/settings'),
|
lazy: async () => await import('./pages/workspace/settings'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '*',
|
path: '*',
|
||||||
lazy: () => import('./pages/404'),
|
lazy: async () => await import('./pages/404'),
|
||||||
},
|
},
|
||||||
] satisfies RouteObject[];
|
] satisfies RouteObject[];
|
||||||
|
|||||||
@@ -216,7 +216,7 @@ const CloudWorkSpaceList = ({
|
|||||||
if (currentWorkspaceFlavour === server.id) {
|
if (currentWorkspaceFlavour === server.id) {
|
||||||
const otherWorkspace = workspaces.find(w => w.flavour !== server.id);
|
const otherWorkspace = workspaces.find(w => w.flavour !== server.id);
|
||||||
if (otherWorkspace) {
|
if (otherWorkspace) {
|
||||||
navigateHelper.openPage(otherWorkspace.id, 'all');
|
navigateHelper.jumpToAll(otherWorkspace.id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [
|
}, [
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { NotificationCenter } from '@affine/component';
|
|||||||
import { DefaultServerService } from '@affine/core/modules/cloud';
|
import { DefaultServerService } from '@affine/core/modules/cloud';
|
||||||
import { FrameworkScope, useService } from '@toeverything/infra';
|
import { FrameworkScope, useService } from '@toeverything/infra';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { Outlet } from 'react-router-dom';
|
import { Outlet } from 'react-router';
|
||||||
|
|
||||||
import { GlobalDialogs } from '../../dialogs';
|
import { GlobalDialogs } from '../../dialogs';
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,13 @@
|
|||||||
// eslint-disable-next-line @typescript-eslint/no-restricted-imports
|
import { useAsyncNavigate } from '@affine/core/utils';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useCallback } from 'react';
|
||||||
|
|
||||||
import { MobileSignInPanel } from '../components/sign-in';
|
import { MobileSignInPanel } from '../components/sign-in';
|
||||||
|
|
||||||
export const Component = () => {
|
export const Component = () => {
|
||||||
const navigate = useNavigate();
|
const navigate = useAsyncNavigate();
|
||||||
|
const onClose = useCallback(() => {
|
||||||
|
navigate('/');
|
||||||
|
}, [navigate]);
|
||||||
|
|
||||||
return <MobileSignInPanel onClose={() => navigate('/')} />;
|
return <MobileSignInPanel onClose={onClose} />;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { CollectionService } from '@affine/core/modules/collection';
|
|||||||
import { GlobalContextService } from '@affine/core/modules/global-context';
|
import { GlobalContextService } from '@affine/core/modules/global-context';
|
||||||
import { useLiveData, useServices } from '@toeverything/infra';
|
import { useLiveData, useServices } from '@toeverything/infra';
|
||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
import { useParams } from 'react-router-dom';
|
import { useParams } from 'react-router';
|
||||||
|
|
||||||
import { CollectionDetail } from '../../../views';
|
import { CollectionDetail } from '../../../views';
|
||||||
|
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ import { cssVarV2 } from '@toeverything/theme/v2';
|
|||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||||
import { useParams } from 'react-router-dom';
|
import { useParams } from 'react-router';
|
||||||
|
|
||||||
import { AppTabs } from '../../../components';
|
import { AppTabs } from '../../../components';
|
||||||
import { JournalConflictBlock } from './journal-conflict-block';
|
import { JournalConflictBlock } from './journal-conflict-block';
|
||||||
@@ -73,7 +73,7 @@ const DetailPageImpl = () => {
|
|||||||
const mode = useLiveData(editor.mode$);
|
const mode = useLiveData(editor.mode$);
|
||||||
|
|
||||||
const isInTrash = useLiveData(doc.meta$.map(meta => meta.trash));
|
const isInTrash = useLiveData(doc.meta$.map(meta => meta.trash));
|
||||||
const { openPage, jumpToPageBlock } = useNavigateHelper();
|
const { jumpToPage, jumpToPageBlock } = useNavigateHelper();
|
||||||
const scrollViewportRef = useRef<HTMLDivElement | null>(null);
|
const scrollViewportRef = useRef<HTMLDivElement | null>(null);
|
||||||
|
|
||||||
const editorContainer = useLiveData(editor.editorContainer$);
|
const editorContainer = useLiveData(editor.editorContainer$);
|
||||||
@@ -163,7 +163,7 @@ const DetailPageImpl = () => {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return openPage(docCollection.id, pageId);
|
return jumpToPage(docCollection.id, pageId);
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -178,7 +178,7 @@ const DetailPageImpl = () => {
|
|||||||
disposable.dispose();
|
disposable.dispose();
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
[docCollection.id, editor, jumpToPageBlock, openPage, server]
|
[docCollection.id, editor, jumpToPage, jumpToPageBlock, server.baseUrl]
|
||||||
);
|
);
|
||||||
|
|
||||||
const canEdit = useGuard('Doc_Update', doc.id);
|
const canEdit = useGuard('Doc_Update', doc.id);
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ import {
|
|||||||
type RouteObject,
|
type RouteObject,
|
||||||
useLocation,
|
useLocation,
|
||||||
useParams,
|
useParams,
|
||||||
} from 'react-router-dom';
|
} from 'react-router';
|
||||||
|
|
||||||
import { WorkspaceLayout } from './layout';
|
import { WorkspaceLayout } from './layout';
|
||||||
import { MobileWorkbenchRoot } from './workbench-root';
|
import { MobileWorkbenchRoot } from './workbench-root';
|
||||||
@@ -37,7 +37,11 @@ const MobileRouteContainer = ({ route }: { route: Route }) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const warpedRoutes = workbenchRoutes.map((originalRoute: RouteObject) => {
|
const warpedRoutes = workbenchRoutes.map((originalRoute: RouteObject) => {
|
||||||
if (originalRoute.Component || !originalRoute.lazy) {
|
if (
|
||||||
|
originalRoute.Component ||
|
||||||
|
!originalRoute.lazy ||
|
||||||
|
typeof originalRoute.lazy !== 'function'
|
||||||
|
) {
|
||||||
return originalRoute;
|
return originalRoute;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { GlobalContextService } from '@affine/core/modules/global-context';
|
|||||||
import { TagService } from '@affine/core/modules/tag';
|
import { TagService } from '@affine/core/modules/tag';
|
||||||
import { useLiveData, useService } from '@toeverything/infra';
|
import { useLiveData, useService } from '@toeverything/infra';
|
||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
import { useParams } from 'react-router-dom';
|
import { useParams } from 'react-router';
|
||||||
|
|
||||||
import { TagDetail } from '../../../views';
|
import { TagDetail } from '../../../views';
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import {
|
|||||||
import { ViewRoot } from '@affine/core/modules/workbench/view/view-root';
|
import { ViewRoot } from '@affine/core/modules/workbench/view/view-root';
|
||||||
import { useLiveData, useService } from '@toeverything/infra';
|
import { useLiveData, useService } from '@toeverything/infra';
|
||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
import { type RouteObject, useLocation } from 'react-router-dom';
|
import { type RouteObject, useLocation } from 'react-router';
|
||||||
|
|
||||||
export const MobileWorkbenchRoot = ({ routes }: { routes: RouteObject[] }) => {
|
export const MobileWorkbenchRoot = ({ routes }: { routes: RouteObject[] }) => {
|
||||||
const workbench = useService(WorkbenchService).workbench;
|
const workbench = useService(WorkbenchService).workbench;
|
||||||
|
|||||||
@@ -1,13 +1,15 @@
|
|||||||
import { NavigateContext } from '@affine/core/components/hooks/use-navigate-helper';
|
import { NavigateContext } from '@affine/core/components/hooks/use-navigate-helper';
|
||||||
import { wrapCreateBrowserRouterV6 } from '@sentry/react';
|
import { ROUTES } from '@affine/routes';
|
||||||
|
import { wrapCreateBrowserRouterV7 } from '@sentry/react';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import type { RouteObject } from 'react-router-dom';
|
import type { RouteObject } from 'react-router';
|
||||||
import {
|
import {
|
||||||
createBrowserRouter as reactRouterCreateBrowserRouter,
|
createBrowserRouter as reactRouterCreateBrowserRouter,
|
||||||
redirect,
|
redirect,
|
||||||
useNavigate,
|
useNavigate,
|
||||||
} from 'react-router-dom';
|
} from 'react-router';
|
||||||
|
|
||||||
|
import { AppFallback } from './components/app-fallback';
|
||||||
import { RootWrapper } from './pages/root';
|
import { RootWrapper } from './pages/root';
|
||||||
|
|
||||||
function RootRouter() {
|
function RootRouter() {
|
||||||
@@ -30,77 +32,76 @@ function RootRouter() {
|
|||||||
export const topLevelRoutes = [
|
export const topLevelRoutes = [
|
||||||
{
|
{
|
||||||
element: <RootRouter />,
|
element: <RootRouter />,
|
||||||
|
hydrateFallbackElement: <AppFallback />,
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
path: '/',
|
path: ROUTES.index,
|
||||||
lazy: () => import('./pages/index'),
|
lazy: async () => await import('./pages/index'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/workspace/:workspaceId/*',
|
path: `${ROUTES.workspace.index}/*`,
|
||||||
lazy: () => import('./pages/workspace/index'),
|
lazy: async () => await import('./pages/workspace/index'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/share/:workspaceId/:pageId',
|
path: ROUTES.share,
|
||||||
loader: ({ params }) => {
|
loader: async ({ params }) => {
|
||||||
return redirect(`/workspace/${params.workspaceId}/${params.pageId}`);
|
return redirect(
|
||||||
|
`/workspaces/${params.workspaceId}/docs/${params.pageId}`
|
||||||
|
);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/404',
|
path: ROUTES.notFound,
|
||||||
lazy: () => import('./pages/404'),
|
lazy: async () => await import('./pages/404'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/auth/:authType',
|
path: ROUTES.auth,
|
||||||
lazy: () => import('./pages/auth'),
|
lazy: async () => await import('./pages/auth'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/sign-in',
|
path: ROUTES.signIn,
|
||||||
lazy: () => import('./pages/sign-in'),
|
lazy: async () => await import('./pages/sign-in'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/magic-link',
|
path: ROUTES.magicLink,
|
||||||
lazy: () =>
|
lazy: async () =>
|
||||||
import(
|
await import(
|
||||||
/* webpackChunkName: "auth" */ '@affine/core/desktop/pages/auth/magic-link'
|
/* webpackChunkName: "auth" */ '@affine/core/desktop/pages/auth/magic-link'
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/oauth/login',
|
path: ROUTES.oauth.login,
|
||||||
lazy: () =>
|
lazy: async () =>
|
||||||
import(
|
await import(
|
||||||
/* webpackChunkName: "auth" */ '@affine/core/desktop/pages/auth/oauth-login'
|
/* webpackChunkName: "auth" */ '@affine/core/desktop/pages/auth/oauth-login'
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/oauth/callback',
|
path: ROUTES.oauth.callback,
|
||||||
lazy: () =>
|
lazy: async () =>
|
||||||
import(
|
await import(
|
||||||
/* webpackChunkName: "auth" */ '@affine/core/desktop/pages/auth/oauth-callback'
|
/* webpackChunkName: "auth" */ '@affine/core/desktop/pages/auth/oauth-callback'
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/redirect-proxy',
|
path: ROUTES.redirect,
|
||||||
lazy: () => import('@affine/core/desktop/pages/redirect'),
|
lazy: async () => await import('@affine/core/desktop/pages/redirect'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/open-app/:action',
|
path: ROUTES.openApp,
|
||||||
lazy: () => import('@affine/core/desktop/pages/open-app'),
|
lazy: async () => await import('@affine/core/desktop/pages/open-app'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '*',
|
path: '*',
|
||||||
lazy: () => import('./pages/404'),
|
lazy: async () => await import('./pages/404'),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
] satisfies [RouteObject, ...RouteObject[]];
|
] satisfies [RouteObject, ...RouteObject[]];
|
||||||
|
|
||||||
const createBrowserRouter = wrapCreateBrowserRouterV6(
|
const createBrowserRouter = wrapCreateBrowserRouterV7(
|
||||||
reactRouterCreateBrowserRouter
|
reactRouterCreateBrowserRouter
|
||||||
);
|
);
|
||||||
export const router = (
|
export const router = (
|
||||||
window.SENTRY_RELEASE ? createBrowserRouter : reactRouterCreateBrowserRouter
|
window.SENTRY_RELEASE ? createBrowserRouter : reactRouterCreateBrowserRouter
|
||||||
)(topLevelRoutes, {
|
)(topLevelRoutes);
|
||||||
future: {
|
|
||||||
v7_normalizeFormMethod: true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import type { RouteObject } from 'react-router-dom';
|
import type { RouteObject } from 'react-router';
|
||||||
|
|
||||||
import { Component as All } from './pages/workspace/all';
|
import { Component as All } from './pages/workspace/all';
|
||||||
import { Component as Collection } from './pages/workspace/collection';
|
import { Component as Collection } from './pages/workspace/collection';
|
||||||
@@ -43,14 +43,15 @@ export const workbenchRoutes = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/trash',
|
path: '/trash',
|
||||||
lazy: () => import('./pages/workspace/trash'),
|
lazy: async () => await import('./pages/workspace/trash'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/:pageId',
|
path: '/:pageId',
|
||||||
lazy: () => import('./pages/workspace/detail/mobile-detail-page'),
|
lazy: async () =>
|
||||||
|
await import('./pages/workspace/detail/mobile-detail-page'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '*',
|
path: '*',
|
||||||
lazy: () => import('./pages/404'),
|
lazy: async () => await import('./pages/404'),
|
||||||
},
|
},
|
||||||
] satisfies [RouteObject, ...RouteObject[]];
|
] satisfies [RouteObject, ...RouteObject[]];
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { DualLinkIcon } from '@blocksuite/icons/rc';
|
import { DualLinkIcon } from '@blocksuite/icons/rc';
|
||||||
import { cssVarV2 } from '@toeverything/theme/v2';
|
import { cssVarV2 } from '@toeverything/theme/v2';
|
||||||
import type { ReactElement, SVGAttributes } from 'react';
|
import type { ReactElement, SVGAttributes } from 'react';
|
||||||
import type { To } from 'react-router-dom';
|
import type { To } from 'react-router';
|
||||||
|
|
||||||
import { MenuLinkItem } from './index';
|
import { MenuLinkItem } from './index';
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { WorkbenchLink } from '@affine/core/modules/workbench';
|
|||||||
import { ArrowDownSmallIcon } from '@blocksuite/icons/rc';
|
import { ArrowDownSmallIcon } from '@blocksuite/icons/rc';
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
import React, { type SVGAttributes } from 'react';
|
import React, { type SVGAttributes } from 'react';
|
||||||
import type { To } from 'react-router-dom';
|
import type { To } from 'react-router';
|
||||||
|
|
||||||
import * as styles from './index.css';
|
import * as styles from './index.css';
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
|
import { useAsyncNavigate } from '@affine/core/utils';
|
||||||
import { useLiveData } from '@toeverything/infra';
|
import { useLiveData } from '@toeverything/infra';
|
||||||
import type { Location } from 'history';
|
import type { Location } from 'history';
|
||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
// eslint-disable-next-line @typescript-eslint/no-restricted-imports
|
import { useLocation } from 'react-router';
|
||||||
import { useLocation, useNavigate } from 'react-router-dom';
|
|
||||||
|
|
||||||
import type { Workbench } from '../entities/workbench';
|
import type { Workbench } from '../entities/workbench';
|
||||||
|
|
||||||
@@ -26,7 +26,7 @@ export function useBindWorkbenchToBrowserRouter(
|
|||||||
workbench: Workbench,
|
workbench: Workbench,
|
||||||
basename: string
|
basename: string
|
||||||
) {
|
) {
|
||||||
const navigate = useNavigate();
|
const navigate = useAsyncNavigate();
|
||||||
const browserLocation = useLocation();
|
const browserLocation = useLocation();
|
||||||
|
|
||||||
const view = useLiveData(workbench.activeView$);
|
const view = useLiveData(workbench.activeView$);
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import type { Location } from 'history';
|
import type { Location } from 'history';
|
||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
// eslint-disable-next-line @typescript-eslint/no-restricted-imports
|
import { useLocation } from 'react-router';
|
||||||
import { useLocation } from 'react-router-dom';
|
|
||||||
|
|
||||||
import type { Workbench } from '../entities/workbench';
|
import type { Workbench } from '../entities/workbench';
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { AffineErrorBoundary } from '@affine/core/components/affine/affine-error
|
|||||||
import { RightSidebarIcon } from '@blocksuite/icons/rc';
|
import { RightSidebarIcon } from '@blocksuite/icons/rc';
|
||||||
import { useLiveData, useService } from '@toeverything/infra';
|
import { useLiveData, useService } from '@toeverything/infra';
|
||||||
import { Suspense, useCallback } from 'react';
|
import { Suspense, useCallback } from 'react';
|
||||||
import { Outlet } from 'react-router-dom';
|
import { Outlet } from 'react-router';
|
||||||
|
|
||||||
import { AppSidebarService } from '../../app-sidebar';
|
import { AppSidebarService } from '../../app-sidebar';
|
||||||
import { SidebarSwitch } from '../../app-sidebar/views/sidebar-header';
|
import { SidebarSwitch } from '../../app-sidebar/views/sidebar-header';
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
import { FrameworkScope, useLiveData } from '@toeverything/infra';
|
import { FrameworkScope, useLiveData } from '@toeverything/infra';
|
||||||
import { useLayoutEffect, useMemo } from 'react';
|
import { useLayoutEffect, useMemo } from 'react';
|
||||||
import type { RouteObject } from 'react-router-dom';
|
import type { RouteObject } from 'react-router';
|
||||||
import {
|
import {
|
||||||
createMemoryRouter,
|
createMemoryRouter,
|
||||||
RouterProvider,
|
RouterProvider,
|
||||||
UNSAFE_LocationContext,
|
UNSAFE_LocationContext,
|
||||||
UNSAFE_RouteContext,
|
UNSAFE_RouteContext,
|
||||||
} from 'react-router-dom';
|
} from 'react-router';
|
||||||
|
|
||||||
import type { View } from '../entities/view';
|
import type { View } from '../entities/view';
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import {
|
|||||||
} from '@toeverything/infra';
|
} from '@toeverything/infra';
|
||||||
import { useAtomValue } from 'jotai';
|
import { useAtomValue } from 'jotai';
|
||||||
import { memo, useCallback, useEffect, useRef, useState } from 'react';
|
import { memo, useCallback, useEffect, useRef, useState } from 'react';
|
||||||
import { type RouteObject, useLocation } from 'react-router-dom';
|
import { type RouteObject, useLocation } from 'react-router';
|
||||||
|
|
||||||
import type { View } from '../entities/view';
|
import type { View } from '../entities/view';
|
||||||
import { WorkbenchService } from '../services/workbench';
|
import { WorkbenchService } from '../services/workbench';
|
||||||
|
|||||||
@@ -5,3 +5,4 @@ export * from './extract-emoji-icon';
|
|||||||
export * from './string2color';
|
export * from './string2color';
|
||||||
export * from './toast';
|
export * from './toast';
|
||||||
export * from './unflatten-object';
|
export * from './unflatten-object';
|
||||||
|
export * from './use-async-navigate';
|
||||||
|
|||||||
25
packages/frontend/core/src/utils/use-async-navigate.ts
Normal file
25
packages/frontend/core/src/utils/use-async-navigate.ts
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
import { notify } from '@affine/component';
|
||||||
|
import { UserFriendlyError } from '@affine/error';
|
||||||
|
import { useCallback } from 'react';
|
||||||
|
import { type NavigateOptions, type To, useNavigate } from 'react-router';
|
||||||
|
|
||||||
|
export const useAsyncNavigate = () => {
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
const nav = useCallback(
|
||||||
|
(to: To, options?: NavigateOptions) => {
|
||||||
|
const result = navigate(to, options);
|
||||||
|
if (result instanceof Promise) {
|
||||||
|
result.catch((err: Error) => {
|
||||||
|
const error = UserFriendlyError.fromAny(err);
|
||||||
|
console.error(error);
|
||||||
|
notify.error(error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
},
|
||||||
|
[navigate]
|
||||||
|
);
|
||||||
|
|
||||||
|
return nav;
|
||||||
|
};
|
||||||
@@ -18,6 +18,6 @@
|
|||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"react": "^19.1.0",
|
"react": "^19.1.0",
|
||||||
"react-router-dom": "^7.5.1"
|
"react-router": "^7.5.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,70 @@
|
|||||||
"$schema": "./schema.json",
|
"$schema": "./schema.json",
|
||||||
"route": "/",
|
"route": "/",
|
||||||
"children": {
|
"children": {
|
||||||
|
"workspace": {
|
||||||
|
"route": "workspaces/:workspaceId",
|
||||||
|
"children": {
|
||||||
|
"all": "all",
|
||||||
|
"trash": "trash",
|
||||||
|
"doc": {
|
||||||
|
"route": "docs/:docId",
|
||||||
|
"children": {
|
||||||
|
"attachment": "attachment/:attachmentId"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"journals": "journals",
|
||||||
|
"collections": {
|
||||||
|
"route": "collections",
|
||||||
|
"children": {
|
||||||
|
"collection": ":collectionId"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tags": {
|
||||||
|
"route": "tags",
|
||||||
|
"children": {
|
||||||
|
"tag": ":tagId"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"settings": "settings"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"share": "share/:workspaceId/:pageId",
|
||||||
|
"expired": "expired",
|
||||||
|
"invite": "invite/:inviteId",
|
||||||
|
"payment": "payment/:plan/success",
|
||||||
|
"onboarding": "onboarding",
|
||||||
|
"redirect": "redirect",
|
||||||
|
"subscribe": "subscribe",
|
||||||
|
"upgradeToTeam": "upgrade-to-team",
|
||||||
|
"upgradeSuccess": {
|
||||||
|
"route": "upgrade-success",
|
||||||
|
"children": {
|
||||||
|
"team": "team",
|
||||||
|
"selfHostTeam": "self-host-team"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"aiUpgradeSuccess": "ai-upgrade-success",
|
||||||
|
"tryCloud": "try-cloud",
|
||||||
|
"themeEditor": "theme-editor",
|
||||||
|
"template": {
|
||||||
|
"route": "template",
|
||||||
|
"children": {
|
||||||
|
"import": "import",
|
||||||
|
"preview": "preview"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"auth": "auth/:authType",
|
||||||
|
"signIn": "sign-in",
|
||||||
|
"magicLink": "magic-link",
|
||||||
|
"oauth": {
|
||||||
|
"route": "oauth",
|
||||||
|
"children": {
|
||||||
|
"login": "login",
|
||||||
|
"callback": "callback"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"openApp": "open-app/:action",
|
||||||
|
"notFound": "404",
|
||||||
"admin": {
|
"admin": {
|
||||||
"route": "admin",
|
"route": "admin",
|
||||||
"children": {
|
"children": {
|
||||||
|
|||||||
@@ -1,5 +1,29 @@
|
|||||||
// #region Path Parameter Types
|
// #region Path Parameter Types
|
||||||
export interface RouteParamsTypes {
|
export interface RouteParamsTypes {
|
||||||
|
workspace: {
|
||||||
|
index: { workspaceId: string };
|
||||||
|
all: { workspaceId: string };
|
||||||
|
trash: { workspaceId: string };
|
||||||
|
doc: {
|
||||||
|
index: { workspaceId: string; docId: string };
|
||||||
|
attachment: { workspaceId: string; docId: string; attachmentId: string };
|
||||||
|
};
|
||||||
|
journals: { workspaceId: string };
|
||||||
|
collections: {
|
||||||
|
index: { workspaceId: string };
|
||||||
|
collection: { workspaceId: string; collectionId: string };
|
||||||
|
};
|
||||||
|
tags: {
|
||||||
|
index: { workspaceId: string };
|
||||||
|
tag: { workspaceId: string; tagId: string };
|
||||||
|
};
|
||||||
|
settings: { workspaceId: string };
|
||||||
|
};
|
||||||
|
share: { workspaceId: string; pageId: string };
|
||||||
|
invite: { inviteId: string };
|
||||||
|
payment: { plan: string };
|
||||||
|
auth: { authType: string };
|
||||||
|
openApp: { action: string };
|
||||||
admin: { settings: { module: { module: string } } };
|
admin: { settings: { module: { module: string } } };
|
||||||
}
|
}
|
||||||
// #endregion
|
// #endregion
|
||||||
@@ -7,6 +31,57 @@ export interface RouteParamsTypes {
|
|||||||
// #region Absolute Paths
|
// #region Absolute Paths
|
||||||
export const ROUTES = {
|
export const ROUTES = {
|
||||||
index: '/',
|
index: '/',
|
||||||
|
workspace: {
|
||||||
|
index: '/workspaces/:workspaceId',
|
||||||
|
all: '/workspaces/:workspaceId/all',
|
||||||
|
trash: '/workspaces/:workspaceId/trash',
|
||||||
|
doc: {
|
||||||
|
index: '/workspaces/:workspaceId/docs/:docId',
|
||||||
|
attachment:
|
||||||
|
'/workspaces/:workspaceId/docs/:docId/attachment/:attachmentId',
|
||||||
|
},
|
||||||
|
journals: '/workspaces/:workspaceId/journals',
|
||||||
|
collections: {
|
||||||
|
index: '/workspaces/:workspaceId/collections',
|
||||||
|
collection: '/workspaces/:workspaceId/collections/:collectionId',
|
||||||
|
},
|
||||||
|
tags: {
|
||||||
|
index: '/workspaces/:workspaceId/tags',
|
||||||
|
tag: '/workspaces/:workspaceId/tags/:tagId',
|
||||||
|
},
|
||||||
|
settings: '/workspaces/:workspaceId/settings',
|
||||||
|
},
|
||||||
|
share: '/share/:workspaceId/:pageId',
|
||||||
|
expired: '/expired',
|
||||||
|
invite: '/invite/:inviteId',
|
||||||
|
payment: '/payment/:plan/success',
|
||||||
|
onboarding: '/onboarding',
|
||||||
|
redirect: '/redirect',
|
||||||
|
subscribe: '/subscribe',
|
||||||
|
upgradeToTeam: '/upgrade-to-team',
|
||||||
|
upgradeSuccess: {
|
||||||
|
index: '/upgrade-success',
|
||||||
|
team: '/upgrade-success/team',
|
||||||
|
selfHostTeam: '/upgrade-success/self-host-team',
|
||||||
|
},
|
||||||
|
aiUpgradeSuccess: '/ai-upgrade-success',
|
||||||
|
tryCloud: '/try-cloud',
|
||||||
|
themeEditor: '/theme-editor',
|
||||||
|
template: {
|
||||||
|
index: '/template',
|
||||||
|
import: '/template/import',
|
||||||
|
preview: '/template/preview',
|
||||||
|
},
|
||||||
|
auth: '/auth/:authType',
|
||||||
|
signIn: '/sign-in',
|
||||||
|
magicLink: '/magic-link',
|
||||||
|
oauth: {
|
||||||
|
index: '/oauth',
|
||||||
|
login: '/oauth/login',
|
||||||
|
callback: '/oauth/callback',
|
||||||
|
},
|
||||||
|
openApp: '/open-app/:action',
|
||||||
|
notFound: '/404',
|
||||||
admin: {
|
admin: {
|
||||||
index: '/admin',
|
index: '/admin',
|
||||||
auth: '/admin/auth',
|
auth: '/admin/auth',
|
||||||
@@ -23,6 +98,39 @@ export const ROUTES = {
|
|||||||
// #region Relative Paths
|
// #region Relative Paths
|
||||||
export const RELATIVE_ROUTES = {
|
export const RELATIVE_ROUTES = {
|
||||||
index: '/',
|
index: '/',
|
||||||
|
workspace: {
|
||||||
|
index: 'workspaces/:workspaceId',
|
||||||
|
all: 'all',
|
||||||
|
trash: 'trash',
|
||||||
|
doc: { index: 'docs/:docId', attachment: 'attachment/:attachmentId' },
|
||||||
|
journals: 'journals',
|
||||||
|
collections: { index: 'collections', collection: ':collectionId' },
|
||||||
|
tags: { index: 'tags', tag: ':tagId' },
|
||||||
|
settings: 'settings',
|
||||||
|
},
|
||||||
|
share: 'share/:workspaceId/:pageId',
|
||||||
|
expired: 'expired',
|
||||||
|
invite: 'invite/:inviteId',
|
||||||
|
payment: 'payment/:plan/success',
|
||||||
|
onboarding: 'onboarding',
|
||||||
|
redirect: 'redirect',
|
||||||
|
subscribe: 'subscribe',
|
||||||
|
upgradeToTeam: 'upgrade-to-team',
|
||||||
|
upgradeSuccess: {
|
||||||
|
index: 'upgrade-success',
|
||||||
|
team: 'team',
|
||||||
|
selfHostTeam: 'self-host-team',
|
||||||
|
},
|
||||||
|
aiUpgradeSuccess: 'ai-upgrade-success',
|
||||||
|
tryCloud: 'try-cloud',
|
||||||
|
themeEditor: 'theme-editor',
|
||||||
|
template: { index: 'template', import: 'import', preview: 'preview' },
|
||||||
|
auth: 'auth/:authType',
|
||||||
|
signIn: 'sign-in',
|
||||||
|
magicLink: 'magic-link',
|
||||||
|
oauth: { index: 'oauth', login: 'login', callback: 'callback' },
|
||||||
|
openApp: 'open-app/:action',
|
||||||
|
notFound: '404',
|
||||||
admin: {
|
admin: {
|
||||||
index: 'admin',
|
index: 'admin',
|
||||||
auth: 'auth',
|
auth: 'auth',
|
||||||
@@ -38,6 +146,63 @@ export const RELATIVE_ROUTES = {
|
|||||||
|
|
||||||
// #region Path Factories
|
// #region Path Factories
|
||||||
const home = () => '/';
|
const home = () => '/';
|
||||||
|
const workspace = (params: { workspaceId: string }) =>
|
||||||
|
`/workspaces/${params.workspaceId}`;
|
||||||
|
workspace.all = (params: { workspaceId: string }) =>
|
||||||
|
`/workspaces/${params.workspaceId}/all`;
|
||||||
|
workspace.trash = (params: { workspaceId: string }) =>
|
||||||
|
`/workspaces/${params.workspaceId}/trash`;
|
||||||
|
const workspace_doc = (params: { workspaceId: string; docId: string }) =>
|
||||||
|
`/workspaces/${params.workspaceId}/docs/${params.docId}`;
|
||||||
|
workspace_doc.attachment = (params: {
|
||||||
|
workspaceId: string;
|
||||||
|
docId: string;
|
||||||
|
attachmentId: string;
|
||||||
|
}) =>
|
||||||
|
`/workspaces/${params.workspaceId}/docs/${params.docId}/attachment/${params.attachmentId}`;
|
||||||
|
workspace.doc = workspace_doc;
|
||||||
|
workspace.journals = (params: { workspaceId: string }) =>
|
||||||
|
`/workspaces/${params.workspaceId}/journals`;
|
||||||
|
const workspace_collections = (params: { workspaceId: string }) =>
|
||||||
|
`/workspaces/${params.workspaceId}/collections`;
|
||||||
|
workspace_collections.collection = (params: {
|
||||||
|
workspaceId: string;
|
||||||
|
collectionId: string;
|
||||||
|
}) => `/workspaces/${params.workspaceId}/collections/${params.collectionId}`;
|
||||||
|
workspace.collections = workspace_collections;
|
||||||
|
const workspace_tags = (params: { workspaceId: string }) =>
|
||||||
|
`/workspaces/${params.workspaceId}/tags`;
|
||||||
|
workspace_tags.tag = (params: { workspaceId: string; tagId: string }) =>
|
||||||
|
`/workspaces/${params.workspaceId}/tags/${params.tagId}`;
|
||||||
|
workspace.tags = workspace_tags;
|
||||||
|
workspace.settings = (params: { workspaceId: string }) =>
|
||||||
|
`/workspaces/${params.workspaceId}/settings`;
|
||||||
|
const share = (params: { workspaceId: string; pageId: string }) =>
|
||||||
|
`/share/${params.workspaceId}/${params.pageId}`;
|
||||||
|
const expired = () => '/expired';
|
||||||
|
const invite = (params: { inviteId: string }) => `/invite/${params.inviteId}`;
|
||||||
|
const payment = (params: { plan: string }) => `/payment/${params.plan}/success`;
|
||||||
|
const onboarding = () => '/onboarding';
|
||||||
|
const redirect = () => '/redirect';
|
||||||
|
const subscribe = () => '/subscribe';
|
||||||
|
const upgradeToTeam = () => '/upgrade-to-team';
|
||||||
|
const upgradeSuccess = () => '/upgrade-success';
|
||||||
|
upgradeSuccess.team = () => '/upgrade-success/team';
|
||||||
|
upgradeSuccess.selfHostTeam = () => '/upgrade-success/self-host-team';
|
||||||
|
const aiUpgradeSuccess = () => '/ai-upgrade-success';
|
||||||
|
const tryCloud = () => '/try-cloud';
|
||||||
|
const themeEditor = () => '/theme-editor';
|
||||||
|
const template = () => '/template';
|
||||||
|
template.import = () => '/template/import';
|
||||||
|
template.preview = () => '/template/preview';
|
||||||
|
const auth = (params: { authType: string }) => `/auth/${params.authType}`;
|
||||||
|
const signIn = () => '/sign-in';
|
||||||
|
const magicLink = () => '/magic-link';
|
||||||
|
const oauth = () => '/oauth';
|
||||||
|
oauth.login = () => '/oauth/login';
|
||||||
|
oauth.callback = () => '/oauth/callback';
|
||||||
|
const openApp = (params: { action: string }) => `/open-app/${params.action}`;
|
||||||
|
const notFound = () => '/404';
|
||||||
const admin = () => '/admin';
|
const admin = () => '/admin';
|
||||||
admin.auth = () => '/admin/auth';
|
admin.auth = () => '/admin/auth';
|
||||||
admin.setup = () => '/admin/setup';
|
admin.setup = () => '/admin/setup';
|
||||||
@@ -49,5 +214,28 @@ admin_settings.module = (params: { module: string }) =>
|
|||||||
admin.settings = admin_settings;
|
admin.settings = admin_settings;
|
||||||
admin.about = () => '/admin/about';
|
admin.about = () => '/admin/about';
|
||||||
admin.notFound = () => '/admin/404';
|
admin.notFound = () => '/admin/404';
|
||||||
export const FACTORIES = { admin, home };
|
export const FACTORIES = {
|
||||||
|
workspace,
|
||||||
|
share,
|
||||||
|
expired,
|
||||||
|
invite,
|
||||||
|
payment,
|
||||||
|
onboarding,
|
||||||
|
redirect,
|
||||||
|
subscribe,
|
||||||
|
upgradeToTeam,
|
||||||
|
upgradeSuccess,
|
||||||
|
aiUpgradeSuccess,
|
||||||
|
tryCloud,
|
||||||
|
themeEditor,
|
||||||
|
template,
|
||||||
|
auth,
|
||||||
|
signIn,
|
||||||
|
magicLink,
|
||||||
|
oauth,
|
||||||
|
openApp,
|
||||||
|
notFound,
|
||||||
|
admin,
|
||||||
|
home,
|
||||||
|
};
|
||||||
// #endregion
|
// #endregion
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
"@affine/debug": "workspace:*",
|
"@affine/debug": "workspace:*",
|
||||||
"@sentry/react": "^9.2.0",
|
"@sentry/react": "^9.2.0",
|
||||||
"mixpanel-browser": "^2.56.0",
|
"mixpanel-browser": "^2.56.0",
|
||||||
"react-router-dom": "6.30.0"
|
"react-router": "^7.6.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/mixpanel-browser": "^2.50.2",
|
"@types/mixpanel-browser": "^2.50.2",
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import {
|
|||||||
matchRoutes,
|
matchRoutes,
|
||||||
useLocation,
|
useLocation,
|
||||||
useNavigationType,
|
useNavigationType,
|
||||||
} from 'react-router-dom';
|
} from 'react-router';
|
||||||
|
|
||||||
function createSentry() {
|
function createSentry() {
|
||||||
let client: Sentry.BrowserClient | undefined;
|
let client: Sentry.BrowserClient | undefined;
|
||||||
@@ -18,7 +18,7 @@ function createSentry() {
|
|||||||
debug: BUILD_CONFIG.debug ?? false,
|
debug: BUILD_CONFIG.debug ?? false,
|
||||||
environment: BUILD_CONFIG.appBuildType,
|
environment: BUILD_CONFIG.appBuildType,
|
||||||
integrations: [
|
integrations: [
|
||||||
Sentry.reactRouterV6BrowserTracingIntegration({
|
Sentry.reactRouterV7BrowserTracingIntegration({
|
||||||
useEffect,
|
useEffect,
|
||||||
useLocation,
|
useLocation,
|
||||||
useNavigationType,
|
useNavigationType,
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ test('should not show hidden note in embed view page mode', async ({
|
|||||||
await createLinkedPage(page, 'Test Page');
|
await createLinkedPage(page, 'Test Page');
|
||||||
const inlineLink = page.locator('affine-reference');
|
const inlineLink = page.locator('affine-reference');
|
||||||
await inlineLink.dblclick();
|
await inlineLink.dblclick();
|
||||||
|
await waitForEditorLoad(page);
|
||||||
|
|
||||||
// reference the previous page
|
// reference the previous page
|
||||||
await page.keyboard.press('Enter');
|
await page.keyboard.press('Enter');
|
||||||
|
|||||||
@@ -255,6 +255,7 @@ test('create template doc from sidebar template entrance', async ({ page }) => {
|
|||||||
test('should show starter-bar when doc is empty', async ({ page }) => {
|
test('should show starter-bar when doc is empty', async ({ page }) => {
|
||||||
await openHomePage(page);
|
await openHomePage(page);
|
||||||
await page.getByTestId('sidebar-new-page-button').click();
|
await page.getByTestId('sidebar-new-page-button').click();
|
||||||
|
await waitForEditorLoad(page);
|
||||||
await page.keyboard.press('ArrowDown');
|
await page.keyboard.press('ArrowDown');
|
||||||
const starterBar = page.getByTestId('starter-bar');
|
const starterBar = page.getByTestId('starter-bar');
|
||||||
await expect(starterBar).toBeVisible();
|
await expect(starterBar).toBeVisible();
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ test('all tab', async ({ page }) => {
|
|||||||
await expect(docsTab).toBeVisible();
|
await expect(docsTab).toBeVisible();
|
||||||
|
|
||||||
await docsTab.click();
|
await docsTab.click();
|
||||||
|
await page.waitForTimeout(300);
|
||||||
|
|
||||||
const todayDocs = page.getByTestId('doc-list-item');
|
const todayDocs = page.getByTestId('doc-list-item');
|
||||||
expect(await todayDocs.count()).toBeGreaterThan(0);
|
expect(await todayDocs.count()).toBeGreaterThan(0);
|
||||||
|
|||||||
Reference in New Issue
Block a user