mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-14 21:27:20 +00:00
feat(core): open app in electron app entry (#8637)
fix PD-208 fix PD-210 fix PD-209 fix AF-1495
This commit is contained in:
@@ -33,7 +33,12 @@ export const topNavLink = style({
|
||||
textDecoration: 'none',
|
||||
padding: '4px 18px',
|
||||
});
|
||||
export const tryAgainLink = style({
|
||||
|
||||
export const promptLinks = style({
|
||||
display: 'flex',
|
||||
columnGap: 16,
|
||||
});
|
||||
export const promptLink = style({
|
||||
color: cssVar('linkColor'),
|
||||
fontWeight: 500,
|
||||
textDecoration: 'none',
|
||||
@@ -49,3 +54,11 @@ export const prompt = style({
|
||||
marginTop: 20,
|
||||
marginBottom: 12,
|
||||
});
|
||||
export const editSettingsLink = style({
|
||||
fontWeight: 500,
|
||||
textDecoration: 'none',
|
||||
color: cssVar('linkColor'),
|
||||
fontSize: cssVar('fontSm'),
|
||||
position: 'absolute',
|
||||
bottom: 48,
|
||||
});
|
||||
|
||||
@@ -1,134 +1,32 @@
|
||||
import { Button } from '@affine/component/ui/button';
|
||||
import {
|
||||
appIconMap,
|
||||
appNames,
|
||||
appSchemes,
|
||||
type Channel,
|
||||
schemeToChannel,
|
||||
} from '@affine/core/modules/open-in-app/constant';
|
||||
import { OpenInAppPage } from '@affine/core/modules/open-in-app/views/open-in-app-page';
|
||||
import { appSchemes } from '@affine/core/utils';
|
||||
import type { GetCurrentUserQuery } from '@affine/graphql';
|
||||
import { fetcher, getCurrentUserQuery } from '@affine/graphql';
|
||||
import { Trans, useI18n } from '@affine/i18n';
|
||||
import { Logo1Icon } from '@blocksuite/icons/rc';
|
||||
import { useCallback } from 'react';
|
||||
import type { LoaderFunction } from 'react-router-dom';
|
||||
import { useLoaderData, useSearchParams } from 'react-router-dom';
|
||||
|
||||
import * as styles from './open-app.css';
|
||||
|
||||
let lastOpened = '';
|
||||
interface OpenAppProps {
|
||||
urlToOpen?: string | null;
|
||||
channel: Channel;
|
||||
}
|
||||
|
||||
interface LoaderData {
|
||||
action: 'url' | 'signin-redirect';
|
||||
currentUser?: GetCurrentUserQuery['currentUser'];
|
||||
}
|
||||
|
||||
const OpenAppImpl = ({ urlToOpen, channel }: OpenAppProps) => {
|
||||
const t = useI18n();
|
||||
const openDownloadLink = useCallback(() => {
|
||||
const url = `https://affine.pro/download?channel=${channel}`;
|
||||
open(url, '_blank');
|
||||
}, [channel]);
|
||||
const appIcon = appIconMap[channel];
|
||||
const appName = appNames[channel];
|
||||
|
||||
if (urlToOpen && lastOpened !== urlToOpen) {
|
||||
lastOpened = urlToOpen;
|
||||
location.href = urlToOpen;
|
||||
}
|
||||
const OpenUrl = () => {
|
||||
const [params] = useSearchParams();
|
||||
const urlToOpen = params.get('url');
|
||||
|
||||
if (!urlToOpen) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={styles.root}>
|
||||
<div className={styles.topNav}>
|
||||
<a href="/" rel="noreferrer" className={styles.affineLogo}>
|
||||
<Logo1Icon width={24} height={24} />
|
||||
</a>
|
||||
|
||||
<div className={styles.topNavLinks}>
|
||||
<a
|
||||
href="https://affine.pro"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className={styles.topNavLink}
|
||||
>
|
||||
Official Website
|
||||
</a>
|
||||
<a
|
||||
href="https://community.affine.pro/home"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className={styles.topNavLink}
|
||||
>
|
||||
AFFiNE Community
|
||||
</a>
|
||||
<a
|
||||
href="https://affine.pro/blog"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className={styles.topNavLink}
|
||||
>
|
||||
Blog
|
||||
</a>
|
||||
<a
|
||||
href="https://affine.pro/about-us"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className={styles.topNavLink}
|
||||
>
|
||||
Contact us
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<Button onClick={openDownloadLink}>
|
||||
{t['com.affine.auth.open.affine.download-app']()}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className={styles.centerContent}>
|
||||
<img src={appIcon} alt={appName} width={120} height={120} />
|
||||
|
||||
<div className={styles.prompt}>
|
||||
<Trans i18nKey="com.affine.auth.open.affine.prompt">
|
||||
Open {appName} app now
|
||||
</Trans>
|
||||
</div>
|
||||
|
||||
<a
|
||||
className={styles.tryAgainLink}
|
||||
href={urlToOpen}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
{t['com.affine.auth.open.affine.try-again']()}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const OpenUrl = () => {
|
||||
const [params] = useSearchParams();
|
||||
const urlToOpen = params.get('url');
|
||||
params.delete('url');
|
||||
|
||||
const urlObj = new URL(urlToOpen || '');
|
||||
const maybeScheme = appSchemes.safeParse(urlObj.protocol.replace(':', ''));
|
||||
const channel =
|
||||
schemeToChannel[maybeScheme.success ? maybeScheme.data : 'affine'];
|
||||
|
||||
params.forEach((v, k) => {
|
||||
urlObj.searchParams.set(k, v);
|
||||
});
|
||||
|
||||
return <OpenAppImpl urlToOpen={urlObj.toString()} channel={channel} />;
|
||||
return <OpenInAppPage urlToOpen={urlObj.toString()} />;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -141,7 +39,6 @@ const OpenOAuthJwt = () => {
|
||||
const maybeScheme = appSchemes.safeParse(params.get('scheme'));
|
||||
const scheme = maybeScheme.success ? maybeScheme.data : 'affine';
|
||||
const next = params.get('next');
|
||||
const channel = schemeToChannel[scheme];
|
||||
|
||||
if (!currentUser || !currentUser?.token?.sessionToken) {
|
||||
return null;
|
||||
@@ -151,7 +48,7 @@ const OpenOAuthJwt = () => {
|
||||
currentUser.token.sessionToken
|
||||
}&next=${next || ''}`;
|
||||
|
||||
return <OpenAppImpl urlToOpen={urlToOpen} channel={channel} />;
|
||||
return <OpenInAppPage urlToOpen={urlToOpen} />;
|
||||
};
|
||||
|
||||
export const Component = () => {
|
||||
|
||||
@@ -97,10 +97,7 @@ export const Component = (): ReactElement => {
|
||||
}, [listLoading, meta, workspaceNotFound, workspacesService]);
|
||||
|
||||
if (workspaceNotFound) {
|
||||
if (
|
||||
!BUILD_CONFIG.isElectron /* only browser has share page */ &&
|
||||
detailDocRoute
|
||||
) {
|
||||
if (detailDocRoute) {
|
||||
return (
|
||||
<SharePage
|
||||
docId={detailDocRoute.docId}
|
||||
|
||||
@@ -6,6 +6,7 @@ export const root = style({
|
||||
height: '100%',
|
||||
overflow: 'hidden',
|
||||
width: '100%',
|
||||
flexDirection: 'column',
|
||||
});
|
||||
|
||||
export const mainContainer = style({
|
||||
@@ -56,3 +57,9 @@ export const linkText = style({
|
||||
fontWeight: 700,
|
||||
whiteSpace: 'nowrap',
|
||||
});
|
||||
|
||||
export const shareCard = style({
|
||||
position: 'fixed',
|
||||
bottom: '16px',
|
||||
left: '16px',
|
||||
});
|
||||
|
||||
@@ -7,6 +7,8 @@ import { useNavigateHelper } from '@affine/core/components/hooks/use-navigate-he
|
||||
import { PageDetailEditor } from '@affine/core/components/page-detail-editor';
|
||||
import { SharePageNotFoundError } from '@affine/core/components/share-page-not-found-error';
|
||||
import { AppContainer, MainContainer } from '@affine/core/components/workspace';
|
||||
import { OpenInAppCard } from '@affine/core/modules/app-sidebar/views';
|
||||
import { AppTabsHeader } from '@affine/core/modules/app-tabs-header';
|
||||
import { AuthService, FetchService } from '@affine/core/modules/cloud';
|
||||
import {
|
||||
type Editor,
|
||||
@@ -16,6 +18,8 @@ import {
|
||||
} from '@affine/core/modules/editor';
|
||||
import { PeekViewManagerModal } from '@affine/core/modules/peek-view';
|
||||
import { ShareReaderService } from '@affine/core/modules/share-doc';
|
||||
import { ViewIcon, ViewTitle } from '@affine/core/modules/workbench';
|
||||
import { DesktopStateSynchronizer } from '@affine/core/modules/workbench/services/desktop-state-synchronizer';
|
||||
import { CloudBlobStorage } from '@affine/core/modules/workspace-engine';
|
||||
import { WorkspaceFlavour } from '@affine/env/workspace';
|
||||
import { useI18n } from '@affine/i18n';
|
||||
@@ -35,11 +39,18 @@ import {
|
||||
ReadonlyDocStorage,
|
||||
useLiveData,
|
||||
useService,
|
||||
useServiceOptional,
|
||||
useServices,
|
||||
WorkspacesService,
|
||||
} from '@toeverything/infra';
|
||||
import clsx from 'clsx';
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import {
|
||||
type PropsWithChildren,
|
||||
useCallback,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useState,
|
||||
} from 'react';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
|
||||
import { PageNotFound } from '../../404';
|
||||
@@ -125,6 +136,75 @@ export const SharePage = ({
|
||||
}
|
||||
};
|
||||
|
||||
interface SharePageContainerProps {
|
||||
pageId: string;
|
||||
pageTitle?: string;
|
||||
publishMode: DocMode;
|
||||
isTemplate?: boolean;
|
||||
templateName?: string;
|
||||
templateSnapshotUrl?: string;
|
||||
}
|
||||
|
||||
const SharePageWebContainer = ({
|
||||
children,
|
||||
pageId,
|
||||
publishMode,
|
||||
isTemplate,
|
||||
templateName,
|
||||
templateSnapshotUrl,
|
||||
}: PropsWithChildren<SharePageContainerProps>) => {
|
||||
return (
|
||||
<MainContainer>
|
||||
<div className={styles.root}>
|
||||
<div className={styles.mainContainer}>
|
||||
<ShareHeader
|
||||
pageId={pageId}
|
||||
publishMode={publishMode}
|
||||
isTemplate={isTemplate}
|
||||
templateName={templateName}
|
||||
snapshotUrl={templateSnapshotUrl}
|
||||
/>
|
||||
{children}
|
||||
<SharePageFooter />
|
||||
</div>
|
||||
<OpenInAppCard className={styles.shareCard} />
|
||||
</div>
|
||||
</MainContainer>
|
||||
);
|
||||
};
|
||||
|
||||
const SharePageDesktopContainer = ({
|
||||
children,
|
||||
pageId,
|
||||
pageTitle,
|
||||
publishMode,
|
||||
isTemplate,
|
||||
templateName,
|
||||
templateSnapshotUrl,
|
||||
}: PropsWithChildren<SharePageContainerProps>) => {
|
||||
useServiceOptional(DesktopStateSynchronizer);
|
||||
return (
|
||||
<MainContainer>
|
||||
{/* share page does not have ViewRoot so the following does not work yet */}
|
||||
<ViewTitle title={pageTitle || ''} />
|
||||
<ViewIcon icon="doc" />
|
||||
<div className={styles.root}>
|
||||
<AppTabsHeader />
|
||||
<div className={styles.mainContainer}>
|
||||
<ShareHeader
|
||||
pageId={pageId}
|
||||
publishMode={publishMode}
|
||||
isTemplate={isTemplate}
|
||||
templateName={templateName}
|
||||
snapshotUrl={templateSnapshotUrl}
|
||||
/>
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
</MainContainer>
|
||||
);
|
||||
};
|
||||
|
||||
const SharePageInner = ({
|
||||
workspaceId,
|
||||
docId,
|
||||
@@ -274,41 +354,40 @@ const SharePageInner = ({
|
||||
return;
|
||||
}
|
||||
|
||||
const Container = BUILD_CONFIG.isElectron
|
||||
? SharePageDesktopContainer
|
||||
: SharePageWebContainer;
|
||||
|
||||
return (
|
||||
<FrameworkScope scope={workspace.scope}>
|
||||
<FrameworkScope scope={page.scope}>
|
||||
<FrameworkScope scope={editor.scope}>
|
||||
<AppContainer>
|
||||
<MainContainer>
|
||||
<div className={styles.root}>
|
||||
<div className={styles.mainContainer}>
|
||||
<ShareHeader
|
||||
pageId={page.id}
|
||||
publishMode={publishMode}
|
||||
isTemplate={isTemplate}
|
||||
templateName={templateName}
|
||||
snapshotUrl={templateSnapshotUrl}
|
||||
/>
|
||||
<Scrollable.Root>
|
||||
<Scrollable.Viewport
|
||||
className={clsx(
|
||||
'affine-page-viewport',
|
||||
styles.editorContainer
|
||||
)}
|
||||
>
|
||||
<PageDetailEditor onLoad={onEditorLoad} />
|
||||
{publishMode === 'page' ? <ShareFooter /> : null}
|
||||
</Scrollable.Viewport>
|
||||
<Scrollable.Scrollbar />
|
||||
</Scrollable.Root>
|
||||
<EditorOutlineViewer
|
||||
editor={editorContainer}
|
||||
show={publishMode === 'page'}
|
||||
/>
|
||||
<SharePageFooter />
|
||||
</div>
|
||||
</div>
|
||||
</MainContainer>
|
||||
<Container
|
||||
pageTitle={pageTitle}
|
||||
pageId={page.id}
|
||||
publishMode={publishMode}
|
||||
isTemplate={isTemplate}
|
||||
templateName={templateName}
|
||||
templateSnapshotUrl={templateSnapshotUrl}
|
||||
>
|
||||
<Scrollable.Root>
|
||||
<Scrollable.Viewport
|
||||
className={clsx(
|
||||
'affine-page-viewport',
|
||||
styles.editorContainer
|
||||
)}
|
||||
>
|
||||
<PageDetailEditor onLoad={onEditorLoad} />
|
||||
{publishMode === 'page' ? <ShareFooter /> : null}
|
||||
</Scrollable.Viewport>
|
||||
<Scrollable.Scrollbar />
|
||||
</Scrollable.Root>
|
||||
<EditorOutlineViewer
|
||||
editor={editorContainer}
|
||||
show={publishMode === 'page'}
|
||||
/>
|
||||
</Container>
|
||||
<PeekViewManagerModal />
|
||||
</AppContainer>
|
||||
</FrameworkScope>
|
||||
|
||||
Reference in New Issue
Block a user