mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-12 04:18:54 +00:00
feat(core): open in app for self-hosted (#9231)
<div class='graphite__hidden'>
<div>🎥 Video uploaded on Graphite:</div>
<a href="https://app.graphite.dev/media/video/T2klNLEk0wxLh4NRDzhk/545994dd-6f7d-468d-a90c-45cb382fdb9d.mp4">
<img src="https://app.graphite.dev/api/v1/graphite/video/thumbnail/T2klNLEk0wxLh4NRDzhk/545994dd-6f7d-468d-a90c-45cb382fdb9d.mp4">
</a>
</div>
<video src="https://graphite-user-uploaded-assets-prod.s3.amazonaws.com/T2klNLEk0wxLh4NRDzhk/545994dd-6f7d-468d-a90c-45cb382fdb9d.mp4">20241222-1456-24.5006677.mp4</video>
fix AF-1815
This commit is contained in:
1
.github/workflows/release-desktop.yml
vendored
1
.github/workflows/release-desktop.yml
vendored
@@ -146,7 +146,6 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
SKIP_WEB_BUILD: 1
|
SKIP_WEB_BUILD: 1
|
||||||
HOIST_NODE_MODULES: 1
|
HOIST_NODE_MODULES: 1
|
||||||
DEBUG: '*'
|
|
||||||
|
|
||||||
- name: signing DMG
|
- name: signing DMG
|
||||||
if: ${{ matrix.spec.platform == 'darwin' }}
|
if: ${{ matrix.spec.platform == 'darwin' }}
|
||||||
|
|||||||
@@ -78,9 +78,11 @@ export const AddSelfhostedStep = ({
|
|||||||
...prev,
|
...prev,
|
||||||
initialServerBaseUrl: undefined,
|
initialServerBaseUrl: undefined,
|
||||||
}));
|
}));
|
||||||
|
if (serversService.getServerByBaseUrl(state.initialServerBaseUrl)) {
|
||||||
onConnect();
|
onConnect();
|
||||||
}
|
}
|
||||||
}, [changeState, onConnect, state]);
|
}
|
||||||
|
}, [changeState, onConnect, serversService, state]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { DefaultServerService, type Server } from '@affine/core/modules/cloud';
|
import { DefaultServerService, type Server } from '@affine/core/modules/cloud';
|
||||||
|
import type { AuthSessionStatus } from '@affine/core/modules/cloud/entities/session';
|
||||||
import { FrameworkScope, useService } from '@toeverything/infra';
|
import { FrameworkScope, useService } from '@toeverything/infra';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
|
|
||||||
@@ -22,11 +23,13 @@ export interface SignInState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const SignInPanel = ({
|
export const SignInPanel = ({
|
||||||
onClose,
|
onSkip,
|
||||||
server: initialServerBaseUrl,
|
server: initialServerBaseUrl,
|
||||||
initStep,
|
initStep,
|
||||||
|
onAuthenticated,
|
||||||
}: {
|
}: {
|
||||||
onClose: () => void;
|
onAuthenticated?: (status: AuthSessionStatus) => void;
|
||||||
|
onSkip: () => void;
|
||||||
server?: string;
|
server?: string;
|
||||||
initStep?: SignInStep | undefined;
|
initStep?: SignInStep | undefined;
|
||||||
}) => {
|
}) => {
|
||||||
@@ -47,18 +50,23 @@ export const SignInPanel = ({
|
|||||||
return (
|
return (
|
||||||
<FrameworkScope scope={server.scope}>
|
<FrameworkScope scope={server.scope}>
|
||||||
{step === 'signIn' ? (
|
{step === 'signIn' ? (
|
||||||
<SignInStep state={state} changeState={setState} close={onClose} />
|
<SignInStep
|
||||||
|
state={state}
|
||||||
|
changeState={setState}
|
||||||
|
onSkip={onSkip}
|
||||||
|
onAuthenticated={onAuthenticated}
|
||||||
|
/>
|
||||||
) : step === 'signInWithEmail' ? (
|
) : step === 'signInWithEmail' ? (
|
||||||
<SignInWithEmailStep
|
<SignInWithEmailStep
|
||||||
state={state}
|
state={state}
|
||||||
changeState={setState}
|
changeState={setState}
|
||||||
close={onClose}
|
onAuthenticated={onAuthenticated}
|
||||||
/>
|
/>
|
||||||
) : step === 'signInWithPassword' ? (
|
) : step === 'signInWithPassword' ? (
|
||||||
<SignInWithPasswordStep
|
<SignInWithPasswordStep
|
||||||
state={state}
|
state={state}
|
||||||
changeState={setState}
|
changeState={setState}
|
||||||
close={onClose}
|
onAuthenticated={onAuthenticated}
|
||||||
/>
|
/>
|
||||||
) : step === 'addSelfhosted' ? (
|
) : step === 'addSelfhosted' ? (
|
||||||
<AddSelfhostedStep state={state} changeState={setState} />
|
<AddSelfhostedStep state={state} changeState={setState} />
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import {
|
|||||||
import { Button } from '@affine/component/ui/button';
|
import { Button } from '@affine/component/ui/button';
|
||||||
import { useAsyncCallback } from '@affine/core/components/hooks/affine-async-hooks';
|
import { useAsyncCallback } from '@affine/core/components/hooks/affine-async-hooks';
|
||||||
import { AuthService, CaptchaService } from '@affine/core/modules/cloud';
|
import { AuthService, CaptchaService } from '@affine/core/modules/cloud';
|
||||||
|
import type { AuthSessionStatus } from '@affine/core/modules/cloud/entities/session';
|
||||||
import { Unreachable } from '@affine/env/constant';
|
import { Unreachable } from '@affine/env/constant';
|
||||||
import { Trans, useI18n } from '@affine/i18n';
|
import { Trans, useI18n } from '@affine/i18n';
|
||||||
import { useLiveData, useService } from '@toeverything/infra';
|
import { useLiveData, useService } from '@toeverything/infra';
|
||||||
@@ -27,11 +28,11 @@ import * as style from './style.css';
|
|||||||
export const SignInWithEmailStep = ({
|
export const SignInWithEmailStep = ({
|
||||||
state,
|
state,
|
||||||
changeState,
|
changeState,
|
||||||
close,
|
onAuthenticated,
|
||||||
}: {
|
}: {
|
||||||
state: SignInState;
|
state: SignInState;
|
||||||
changeState: Dispatch<SetStateAction<SignInState>>;
|
changeState: Dispatch<SetStateAction<SignInState>>;
|
||||||
close: () => void;
|
onAuthenticated?: (status: AuthSessionStatus) => void;
|
||||||
}) => {
|
}) => {
|
||||||
const initialSent = useRef(false);
|
const initialSent = useRef(false);
|
||||||
const [resendCountDown, setResendCountDown] = useState(0);
|
const [resendCountDown, setResendCountDown] = useState(0);
|
||||||
@@ -66,13 +67,13 @@ export const SignInWithEmailStep = ({
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (loginStatus === 'authenticated') {
|
if (loginStatus === 'authenticated') {
|
||||||
close();
|
|
||||||
notify.success({
|
notify.success({
|
||||||
title: t['com.affine.auth.toast.title.signed-in'](),
|
title: t['com.affine.auth.toast.title.signed-in'](),
|
||||||
message: t['com.affine.auth.toast.message.signed-in'](),
|
message: t['com.affine.auth.toast.message.signed-in'](),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, [close, loginStatus, t]);
|
onAuthenticated?.(loginStatus);
|
||||||
|
}, [loginStatus, onAuthenticated, t]);
|
||||||
|
|
||||||
const sendEmail = useAsyncCallback(async () => {
|
const sendEmail = useAsyncCallback(async () => {
|
||||||
if (isSending || (!verifyToken && needCaptcha)) return;
|
if (isSending || (!verifyToken && needCaptcha)) return;
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import {
|
|||||||
CaptchaService,
|
CaptchaService,
|
||||||
ServerService,
|
ServerService,
|
||||||
} from '@affine/core/modules/cloud';
|
} from '@affine/core/modules/cloud';
|
||||||
|
import type { AuthSessionStatus } from '@affine/core/modules/cloud/entities/session';
|
||||||
import { Unreachable } from '@affine/env/constant';
|
import { Unreachable } from '@affine/env/constant';
|
||||||
import { ServerDeploymentType } from '@affine/graphql';
|
import { ServerDeploymentType } from '@affine/graphql';
|
||||||
import { useI18n } from '@affine/i18n';
|
import { useI18n } from '@affine/i18n';
|
||||||
@@ -25,11 +26,11 @@ import * as styles from './style.css';
|
|||||||
export const SignInWithPasswordStep = ({
|
export const SignInWithPasswordStep = ({
|
||||||
state,
|
state,
|
||||||
changeState,
|
changeState,
|
||||||
close,
|
onAuthenticated,
|
||||||
}: {
|
}: {
|
||||||
state: SignInState;
|
state: SignInState;
|
||||||
changeState: Dispatch<SetStateAction<SignInState>>;
|
changeState: Dispatch<SetStateAction<SignInState>>;
|
||||||
close: () => void;
|
onAuthenticated?: (status: AuthSessionStatus) => void;
|
||||||
}) => {
|
}) => {
|
||||||
const t = useI18n();
|
const t = useI18n();
|
||||||
const authService = useService(AuthService);
|
const authService = useService(AuthService);
|
||||||
@@ -62,13 +63,13 @@ export const SignInWithPasswordStep = ({
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (loginStatus === 'authenticated') {
|
if (loginStatus === 'authenticated') {
|
||||||
close();
|
|
||||||
notify.success({
|
notify.success({
|
||||||
title: t['com.affine.auth.toast.title.signed-in'](),
|
title: t['com.affine.auth.toast.title.signed-in'](),
|
||||||
message: t['com.affine.auth.toast.message.signed-in'](),
|
message: t['com.affine.auth.toast.message.signed-in'](),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, [close, loginStatus, t]);
|
onAuthenticated?.(loginStatus);
|
||||||
|
}, [loginStatus, onAuthenticated, t]);
|
||||||
|
|
||||||
const onSignIn = useAsyncCallback(async () => {
|
const onSignIn = useAsyncCallback(async () => {
|
||||||
if (isLoading || (!verifyToken && needCaptcha)) return;
|
if (isLoading || (!verifyToken && needCaptcha)) return;
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { AuthInput, ModalHeader } from '@affine/component/auth-components';
|
|||||||
import { OAuth } from '@affine/core/components/affine/auth/oauth';
|
import { OAuth } from '@affine/core/components/affine/auth/oauth';
|
||||||
import { useAsyncCallback } from '@affine/core/components/hooks/affine-async-hooks';
|
import { useAsyncCallback } from '@affine/core/components/hooks/affine-async-hooks';
|
||||||
import { AuthService, ServerService } from '@affine/core/modules/cloud';
|
import { AuthService, ServerService } from '@affine/core/modules/cloud';
|
||||||
|
import type { AuthSessionStatus } from '@affine/core/modules/cloud/entities/session';
|
||||||
import { FeatureFlagService } from '@affine/core/modules/feature-flag';
|
import { FeatureFlagService } from '@affine/core/modules/feature-flag';
|
||||||
import { ServerDeploymentType } from '@affine/graphql';
|
import { ServerDeploymentType } from '@affine/graphql';
|
||||||
import { Trans, useI18n } from '@affine/i18n';
|
import { Trans, useI18n } from '@affine/i18n';
|
||||||
@@ -30,11 +31,13 @@ function validateEmail(email: string) {
|
|||||||
export const SignInStep = ({
|
export const SignInStep = ({
|
||||||
state,
|
state,
|
||||||
changeState,
|
changeState,
|
||||||
close,
|
onSkip,
|
||||||
|
onAuthenticated,
|
||||||
}: {
|
}: {
|
||||||
state: SignInState;
|
state: SignInState;
|
||||||
changeState: Dispatch<SetStateAction<SignInState>>;
|
changeState: Dispatch<SetStateAction<SignInState>>;
|
||||||
close: () => void;
|
onSkip: () => void;
|
||||||
|
onAuthenticated?: (status: AuthSessionStatus) => void;
|
||||||
}) => {
|
}) => {
|
||||||
const t = useI18n();
|
const t = useI18n();
|
||||||
const serverService = useService(ServerService);
|
const serverService = useService(ServerService);
|
||||||
@@ -61,13 +64,13 @@ export const SignInStep = ({
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (loginStatus === 'authenticated') {
|
if (loginStatus === 'authenticated') {
|
||||||
close();
|
|
||||||
notify.success({
|
notify.success({
|
||||||
title: t['com.affine.auth.toast.title.signed-in'](),
|
title: t['com.affine.auth.toast.title.signed-in'](),
|
||||||
message: t['com.affine.auth.toast.message.signed-in'](),
|
message: t['com.affine.auth.toast.message.signed-in'](),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, [close, loginStatus, t]);
|
onAuthenticated?.(loginStatus);
|
||||||
|
}, [loginStatus, onAuthenticated, t]);
|
||||||
|
|
||||||
const onContinue = useAsyncCallback(async () => {
|
const onContinue = useAsyncCallback(async () => {
|
||||||
if (!validateEmail(email)) {
|
if (!validateEmail(email)) {
|
||||||
@@ -205,7 +208,7 @@ export const SignInStep = ({
|
|||||||
</div>
|
</div>
|
||||||
<Button
|
<Button
|
||||||
variant="plain"
|
variant="plain"
|
||||||
onClick={() => close()}
|
onClick={onSkip}
|
||||||
className={style.skipLink}
|
className={style.skipLink}
|
||||||
suffix={<ArrowRightBigIcon className={style.skipLinkIcon} />}
|
suffix={<ArrowRightBigIcon className={style.skipLinkIcon} />}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -1,14 +1,24 @@
|
|||||||
import { Modal } from '@affine/component';
|
import { Modal } from '@affine/component';
|
||||||
import { SignInPanel, type SignInStep } from '@affine/core/components/sign-in';
|
import { SignInPanel, type SignInStep } from '@affine/core/components/sign-in';
|
||||||
|
import type { AuthSessionStatus } from '@affine/core/modules/cloud/entities/session';
|
||||||
import type {
|
import type {
|
||||||
DialogComponentProps,
|
DialogComponentProps,
|
||||||
GLOBAL_DIALOG_SCHEMA,
|
GLOBAL_DIALOG_SCHEMA,
|
||||||
} from '@affine/core/modules/dialogs';
|
} from '@affine/core/modules/dialogs';
|
||||||
|
import { useCallback } from 'react';
|
||||||
export const SignInDialog = ({
|
export const SignInDialog = ({
|
||||||
close,
|
close,
|
||||||
server: initialServerBaseUrl,
|
server: initialServerBaseUrl,
|
||||||
step,
|
step,
|
||||||
}: DialogComponentProps<GLOBAL_DIALOG_SCHEMA['sign-in']>) => {
|
}: DialogComponentProps<GLOBAL_DIALOG_SCHEMA['sign-in']>) => {
|
||||||
|
const onAuthenticated = useCallback(
|
||||||
|
(status: AuthSessionStatus) => {
|
||||||
|
if (status === 'authenticated') {
|
||||||
|
close();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[close]
|
||||||
|
);
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
open
|
open
|
||||||
@@ -21,7 +31,8 @@ export const SignInDialog = ({
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<SignInPanel
|
<SignInPanel
|
||||||
onClose={close}
|
onSkip={close}
|
||||||
|
onAuthenticated={onAuthenticated}
|
||||||
server={initialServerBaseUrl}
|
server={initialServerBaseUrl}
|
||||||
initStep={step as SignInStep}
|
initStep={step as SignInStep}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -2,11 +2,9 @@ import { notify } from '@affine/component';
|
|||||||
import { AffineOtherPageLayout } from '@affine/component/affine-other-page-layout';
|
import { AffineOtherPageLayout } from '@affine/component/affine-other-page-layout';
|
||||||
import { SignInPageContainer } from '@affine/component/auth-components';
|
import { SignInPageContainer } from '@affine/component/auth-components';
|
||||||
import { SignInPanel } from '@affine/core/components/sign-in';
|
import { SignInPanel } from '@affine/core/components/sign-in';
|
||||||
import { AuthService } from '@affine/core/modules/cloud';
|
import type { AuthSessionStatus } from '@affine/core/modules/cloud/entities/session';
|
||||||
import { useI18n } from '@affine/i18n';
|
import { useI18n } from '@affine/i18n';
|
||||||
import { useService } from '@toeverything/infra';
|
import { useCallback, useEffect } from 'react';
|
||||||
import { useEffect } from 'react';
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-restricted-imports
|
|
||||||
import { useNavigate, useSearchParams } from 'react-router-dom';
|
import { useNavigate, useSearchParams } from 'react-router-dom';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@@ -20,11 +18,12 @@ export const SignIn = ({
|
|||||||
redirectUrl?: string;
|
redirectUrl?: string;
|
||||||
}) => {
|
}) => {
|
||||||
const t = useI18n();
|
const t = useI18n();
|
||||||
const session = useService(AuthService).session;
|
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
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');
|
||||||
|
|
||||||
|
const server = searchParams.get('server') ?? undefined;
|
||||||
const error = searchParams.get('error');
|
const error = searchParams.get('error');
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -36,22 +35,38 @@ export const SignIn = ({
|
|||||||
}
|
}
|
||||||
}, [error, t]);
|
}, [error, t]);
|
||||||
|
|
||||||
const handleClose = () => {
|
const handleClose = useCallback(() => {
|
||||||
if (session.status$.value === 'authenticated' && redirectUrl) {
|
jumpToIndex(RouteLogic.REPLACE, {
|
||||||
|
search: searchParams.toString(),
|
||||||
|
});
|
||||||
|
}, [jumpToIndex, searchParams]);
|
||||||
|
|
||||||
|
const handleAuthenticated = useCallback(
|
||||||
|
(status: AuthSessionStatus) => {
|
||||||
|
if (status === 'authenticated') {
|
||||||
|
if (redirectUrl) {
|
||||||
navigate(redirectUrl, {
|
navigate(redirectUrl, {
|
||||||
replace: true,
|
replace: true,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
jumpToIndex(RouteLogic.REPLACE, {
|
handleClose();
|
||||||
search: searchParams.toString(),
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
},
|
||||||
|
[handleClose, navigate, redirectUrl]
|
||||||
|
);
|
||||||
|
|
||||||
|
const initStep = server ? 'addSelfhosted' : 'signIn';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SignInPageContainer>
|
<SignInPageContainer>
|
||||||
<div style={{ maxWidth: '400px', width: '100%' }}>
|
<div style={{ maxWidth: '400px', width: '100%' }}>
|
||||||
<SignInPanel onClose={handleClose} />
|
<SignInPanel
|
||||||
|
onSkip={handleClose}
|
||||||
|
onAuthenticated={handleAuthenticated}
|
||||||
|
initStep={initStep}
|
||||||
|
server={server}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</SignInPageContainer>
|
</SignInPageContainer>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { AffineOtherPageLayout } from '@affine/component/affine-other-page-layou
|
|||||||
import { workbenchRoutes } from '@affine/core/desktop/workbench-router';
|
import { workbenchRoutes } from '@affine/core/desktop/workbench-router';
|
||||||
import {
|
import {
|
||||||
DefaultServerService,
|
DefaultServerService,
|
||||||
|
ServersService,
|
||||||
WorkspaceServerService,
|
WorkspaceServerService,
|
||||||
} from '@affine/core/modules/cloud';
|
} from '@affine/core/modules/cloud';
|
||||||
import { DndService } from '@affine/core/modules/dnd/services';
|
import { DndService } from '@affine/core/modules/dnd/services';
|
||||||
@@ -21,12 +22,18 @@ import {
|
|||||||
} from '@toeverything/infra';
|
} from '@toeverything/infra';
|
||||||
import type { PropsWithChildren, ReactElement } from 'react';
|
import type { PropsWithChildren, ReactElement } from 'react';
|
||||||
import { useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react';
|
import { useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react';
|
||||||
import { matchPath, useLocation, useParams } from 'react-router-dom';
|
import {
|
||||||
|
matchPath,
|
||||||
|
useLocation,
|
||||||
|
useParams,
|
||||||
|
useSearchParams,
|
||||||
|
} from 'react-router-dom';
|
||||||
|
|
||||||
import { AffineErrorBoundary } from '../../../components/affine/affine-error-boundary';
|
import { AffineErrorBoundary } from '../../../components/affine/affine-error-boundary';
|
||||||
import { WorkbenchRoot } from '../../../modules/workbench';
|
import { WorkbenchRoot } from '../../../modules/workbench';
|
||||||
import { AppContainer } from '../../components/app-container';
|
import { AppContainer } from '../../components/app-container';
|
||||||
import { PageNotFound } from '../404';
|
import { PageNotFound } from '../404';
|
||||||
|
import { SignIn } from '../auth/sign-in';
|
||||||
import { WorkspaceLayout } from './layouts/workspace-layout';
|
import { WorkspaceLayout } from './layouts/workspace-layout';
|
||||||
import { SharePage } from './share/share-page';
|
import { SharePage } from './share/share-page';
|
||||||
|
|
||||||
@@ -52,6 +59,7 @@ export const Component = (): ReactElement => {
|
|||||||
|
|
||||||
const params = useParams();
|
const params = useParams();
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
|
const [searchParams] = useSearchParams();
|
||||||
|
|
||||||
// check if we are in detail doc route, if so, maybe render share page
|
// check if we are in detail doc route, if so, maybe render share page
|
||||||
const detailDocRoute = useMemo(() => {
|
const detailDocRoute = useMemo(() => {
|
||||||
@@ -80,6 +88,11 @@ export const Component = (): ReactElement => {
|
|||||||
const [workspaceNotFound, setWorkspaceNotFound] = useState(false);
|
const [workspaceNotFound, setWorkspaceNotFound] = useState(false);
|
||||||
const listLoading = useLiveData(workspacesService.list.isRevalidating$);
|
const listLoading = useLiveData(workspacesService.list.isRevalidating$);
|
||||||
const workspaces = useLiveData(workspacesService.list.workspaces$);
|
const workspaces = useLiveData(workspacesService.list.workspaces$);
|
||||||
|
const serversService = useService(ServersService);
|
||||||
|
const serverFromSearchParams = searchParams.get('server');
|
||||||
|
const serverNotFound = serverFromSearchParams
|
||||||
|
? serversService.getServerByBaseUrl(serverFromSearchParams)
|
||||||
|
: null;
|
||||||
const meta = useMemo(() => {
|
const meta = useMemo(() => {
|
||||||
return workspaces.find(({ id }) => id === params.workspaceId);
|
return workspaces.find(({ id }) => id === params.workspaceId);
|
||||||
}, [workspaces, params.workspaceId]);
|
}, [workspaces, params.workspaceId]);
|
||||||
@@ -113,6 +126,16 @@ export const Component = (): ReactElement => {
|
|||||||
}, [listLoading, meta, workspaceNotFound, workspacesService]);
|
}, [listLoading, meta, workspaceNotFound, workspacesService]);
|
||||||
|
|
||||||
if (workspaceNotFound) {
|
if (workspaceNotFound) {
|
||||||
|
if (BUILD_CONFIG.isElectron && serverNotFound) {
|
||||||
|
const url = new URL(window.location.href);
|
||||||
|
url.searchParams.delete('server');
|
||||||
|
const redirectUrl = url.toString().replace(window.location.origin, '');
|
||||||
|
return (
|
||||||
|
<AffineOtherPageLayout>
|
||||||
|
<SignIn redirectUrl={redirectUrl} />
|
||||||
|
</AffineOtherPageLayout>
|
||||||
|
);
|
||||||
|
}
|
||||||
if (detailDocRoute) {
|
if (detailDocRoute) {
|
||||||
return (
|
return (
|
||||||
<SharePage
|
<SharePage
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
import { SignInPanel } from '@affine/core/components/sign-in';
|
import { SignInPanel } from '@affine/core/components/sign-in';
|
||||||
|
import type { AuthSessionStatus } from '@affine/core/modules/cloud/entities/session';
|
||||||
|
import { useCallback } from 'react';
|
||||||
|
|
||||||
import { MobileSignInLayout } from './layout';
|
import { MobileSignInLayout } from './layout';
|
||||||
|
|
||||||
@@ -9,9 +11,22 @@ export const MobileSignInPanel = ({
|
|||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
server?: string;
|
server?: string;
|
||||||
}) => {
|
}) => {
|
||||||
|
const onAuthenticated = useCallback(
|
||||||
|
(status: AuthSessionStatus) => {
|
||||||
|
if (status === 'authenticated') {
|
||||||
|
onClose();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[onClose]
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MobileSignInLayout>
|
<MobileSignInLayout>
|
||||||
<SignInPanel onClose={onClose} server={server} />
|
<SignInPanel
|
||||||
|
onSkip={onClose}
|
||||||
|
onAuthenticated={onAuthenticated}
|
||||||
|
server={server}
|
||||||
|
/>
|
||||||
</MobileSignInLayout>
|
</MobileSignInLayout>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -35,6 +35,11 @@ export interface AuthSessionAuthenticated {
|
|||||||
session: AuthSessionInfo;
|
session: AuthSessionInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type AuthSessionStatus = (
|
||||||
|
| AuthSessionUnauthenticated
|
||||||
|
| AuthSessionAuthenticated
|
||||||
|
)['status'];
|
||||||
|
|
||||||
export class AuthSession extends Entity {
|
export class AuthSession extends Entity {
|
||||||
session$: LiveData<AuthSessionUnauthenticated | AuthSessionAuthenticated> =
|
session$: LiveData<AuthSessionUnauthenticated | AuthSessionAuthenticated> =
|
||||||
LiveData.from(this.store.watchCachedAuthSession(), null).map(session =>
|
LiveData.from(this.store.watchCachedAuthSession(), null).map(session =>
|
||||||
|
|||||||
@@ -20,6 +20,10 @@ export const getOpenUrlInDesktopAppLink = (
|
|||||||
if (newTab) {
|
if (newTab) {
|
||||||
params.set('new-tab', '1');
|
params.set('new-tab', '1');
|
||||||
}
|
}
|
||||||
|
if (environment.isSelfHosted) {
|
||||||
|
// assume self-hosted server is the current origin
|
||||||
|
params.set('server', location.origin);
|
||||||
|
}
|
||||||
return new URL(
|
return new URL(
|
||||||
`${scheme}://${urlObject.host}${urlObject.pathname}?${params.toString()}#${urlObject.hash}`
|
`${scheme}://${urlObject.host}${urlObject.pathname}?${params.toString()}#${urlObject.hash}`
|
||||||
).toString();
|
).toString();
|
||||||
|
|||||||
@@ -360,7 +360,18 @@ export const createConfiguration: (
|
|||||||
client: {
|
client: {
|
||||||
overlay: process.env.DISABLE_DEV_OVERLAY === 'true' ? false : undefined,
|
overlay: process.env.DISABLE_DEV_OVERLAY === 'true' ? false : undefined,
|
||||||
},
|
},
|
||||||
historyApiFallback: true,
|
historyApiFallback: {
|
||||||
|
rewrites: [
|
||||||
|
{
|
||||||
|
from: /.*/,
|
||||||
|
to: () => {
|
||||||
|
return process.env.SELF_HOSTED === 'true'
|
||||||
|
? '/selfhost.html'
|
||||||
|
: '/index.html';
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
static: [
|
static: [
|
||||||
{
|
{
|
||||||
directory: join(
|
directory: join(
|
||||||
|
|||||||
@@ -22,7 +22,6 @@ export function getBuildConfig(buildFlags: BuildFlags): BUILD_CONFIG_TYPE {
|
|||||||
isAndroid: buildFlags.distribution === 'android',
|
isAndroid: buildFlags.distribution === 'android',
|
||||||
isAdmin: buildFlags.distribution === 'admin',
|
isAdmin: buildFlags.distribution === 'admin',
|
||||||
|
|
||||||
isSelfHosted: process.env.SELF_HOSTED === 'true',
|
|
||||||
appBuildType: 'stable' as const,
|
appBuildType: 'stable' as const,
|
||||||
serverUrlPrefix: 'https://app.affine.pro',
|
serverUrlPrefix: 'https://app.affine.pro',
|
||||||
appVersion: packageJson.version,
|
appVersion: packageJson.version,
|
||||||
|
|||||||
Reference in New Issue
Block a user