Update landing page (#387)

* feat: update landing page

* feat(code): improve code editing experience

* feat: update landing page

* fix: react warning

* fix: update logo and i18n

Co-authored-by: tzhangchi <terry.zhangchi@outlook.com>
Co-authored-by: Yifeng Wang <doodlewind@qq.com>
This commit is contained in:
zuomeng wang 2022-09-08 16:24:49 +08:00 committed by GitHub
parent d5878d60c0
commit 715b235fea
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 883 additions and 669 deletions

View File

@ -10,6 +10,7 @@
"keywords": [],
"author": "AFFiNE <developer@affine.pro>",
"dependencies": {
"@emotion/babel-plugin": "^11.10.2",
"@mui/icons-material": "^5.8.4"
},
"devDependencies": {

View File

@ -7,6 +7,7 @@
"keywords": [],
"author": "DarkSky <darksky2048@gmail.com>",
"dependencies": {
"@emotion/babel-plugin": "^11.10.2",
"@emotion/react": "^11.10.0",
"@emotion/styled": "^11.10.0",
"@mui/joy": "^5.0.0-alpha.42",

View File

@ -10,7 +10,7 @@ export const AboutUs = () => {
return (
<>
<AFFiNEHeader />
<Grid xs={12} sx={{ display: 'flex', marginTop: '4vh!important' }}>
<Grid xs={12} sx={{ display: 'flex', marginTop: '12vh!important' }}>
<Box
sx={{
display: 'inline-flex',

View File

@ -1,600 +0,0 @@
/* eslint-disable max-lines */
/* eslint-disable @typescript-eslint/naming-convention */
import clsx from 'clsx';
import { useEffect, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useNavigate } from 'react-router-dom';
import { Box, Button, Grid, Typography } from '@mui/joy';
import { styled } from '@mui/joy/styles';
import { LogoIcon } from '@toeverything/components/icons';
// eslint-disable-next-line no-restricted-imports
import { useMediaQuery } from '@mui/material';
import CollaborationImage from './collaboration.png';
import { AFFiNEFooter, AFFiNEHeader, AFFiNEImage } from './common';
import { GitHub } from './Icons';
import PageImage from './page.png';
import ShapeImage from './shape.png';
import TaskImage from './task.png';
const Alternatives = styled(Box)<{ width: string }>(({ width }) => ({
position: 'relative',
width: '24em',
height: '128px',
transform: 'translateY(-8px)',
overflowY: 'hidden',
'@media (max-width: 1024px)': {
width,
height: '48px',
transform: 'translateY(0)',
},
'& .scroll-element': {
width: 'inherit',
height: 'inherit',
position: 'absolute',
left: '0%',
top: '0%',
lineHeight: '96px',
'@media (max-width: 1024px)': {
lineHeight: '32px',
},
},
'& .scroll-element.active': {
animation: 'primary 500ms linear infinite',
},
'.primary.active': {
animation: 'primary 500ms linear infinite',
},
'.secondary.active': {
animation: 'secondary 500ms linear infinite',
},
'@keyframes primary': {
from: {
top: '0%',
},
to: {
top: '-100%',
},
},
'@keyframes secondary': {
from: {
top: '100%',
},
to: {
top: '0%',
},
},
}));
const _alternatives = ['Notion', 'Miro', 'Monday'];
const _alternativesSize = [8, 6, 10];
const Product = () => {
const [idx, setIdx] = useState(0);
const [last, current] = useMemo(
() => [
_alternatives[idx],
_alternatives[idx + 1] ? _alternatives[idx + 1] : _alternatives[0],
],
[idx]
);
const maxWidth = useMemo(() => _alternativesSize[idx], [idx]);
const [active, setActive] = useState(false);
const matches = useMediaQuery('(max-width: 1024px)');
useEffect(() => {
const handle = setInterval(() => {
setActive(true);
setTimeout(
() => {
setIdx(idx => (_alternatives[idx + 1] ? idx + 1 : 0));
setActive(false);
},
matches ? 450 : 380
);
}, 2000);
return () => clearInterval(handle);
}, [matches]);
return (
<Alternatives
width={`${maxWidth}em`}
sx={{
margin: 'auto',
marginRight: '1em',
transition: 'width .5s',
'@media (max-width: 1024px)': {
width: '8em',
},
}}
>
<Box
className={clsx(
'scroll-element',
'primary',
active && 'active'
)}
>
<Typography
fontSize="96px"
fontWeight={900}
sx={{
color: '#06449d',
textAlign: 'right',
overflow: 'hidden',
'@media (max-width: 1024px)': {
fontSize: '32px',
},
}}
>
{last}
</Typography>
</Box>
<Box
className={clsx(
'scroll-element',
'primary',
active && 'active'
)}
sx={{
marginTop: '96px',
textAlign: 'right',
overflow: 'hidden',
'@media (max-width: 1024px)': {
marginTop: '48px',
},
}}
>
<Typography
fontSize="96px"
fontWeight={900}
sx={{
color: '#06449d',
overflow: 'hidden',
'@media (max-width: 1024px)': {
fontSize: '32px',
},
}}
>
{current}
</Typography>
</Box>
</Alternatives>
);
};
const AFFiNEOnline = (props: { center?: boolean; flat?: boolean }) => {
const matches = useMediaQuery('(max-width: 1024px)');
const { t } = useTranslation();
return (
<Button
onClick={() => {
window.open('https://livedemo.affine.pro/');
}}
{...(props.flat ? { variant: 'plain' } : {})}
{...{
sx: {
margin: 'auto 1em',
fontSize: '24px',
'@media (max-width: 1024px)': {
fontSize: '16px',
},
...(props.flat
? {
padding: matches ? '0' : '0 0.5em',
':hover': { backgroundColor: 'unset' },
}
: {}),
...(props.center
? {
padding: '0.5em 1em',
fontSize: '2em',
backgroundColor: '#000',
':hover': {
backgroundColor: '#0c60d9',
boxShadow: '2px 2px 20px #08f4',
},
}
: {}),
},
}}
startIcon={<LogoIcon />}
size="lg"
>
{t('Try it Online')}
</Button>
);
};
export function App() {
const matches = useMediaQuery('(max-width: 1024px)');
const navigate = useNavigate();
const { t, i18n } = useTranslation();
const changeLanguage = (event: any) => {
i18n.changeLanguage(event);
};
return (
<>
<AFFiNEHeader />
<Grid xs={12} sx={{ display: 'flex', marginTop: '12vh!important' }}>
<Box
sx={{
display: 'inline-flex',
flexWrap: 'wrap',
justifyContent: 'center',
margin: 'auto',
fontWeight: 'bold',
textAlign: 'center',
}}
>
<Typography
fontSize="96px"
fontWeight={900}
sx={{
marginRight: '0.25em',
'@media (max-width: 1024px)': {
fontSize: '32px',
marginRight: 0,
},
}}
>
{t('Open Source')},
</Typography>
<Typography
fontSize="96px"
fontWeight={900}
sx={{
'@media (max-width: 1024px)': {
fontSize: '32px',
},
}}
>
{t('Privacy First')}
</Typography>
</Box>
</Grid>
<Grid
xs={12}
sx={{
display: 'flex',
flexFlow: 'wrap',
overflow: 'auto',
}}
>
<Box
sx={{
display: 'inline-flex',
flexFlow: 'wrap',
margin: 'auto',
fontWeight: 'bold',
textAlign: 'center',
}}
>
<Product />
<Typography
fontSize="96px"
fontWeight={900}
sx={{
color: '#06449d',
margin: 'auto',
'@media (max-width: 1024px)': {
fontSize: '32px',
},
}}
>
{t('Alternative')}
</Typography>
</Box>
</Grid>
<Grid xs={12} sx={{ display: 'flex' }}>
<Box
sx={{
display: 'flex',
flexWrap: 'wrap',
justifyContent: 'center',
margin: 'auto',
textAlign: 'center',
}}
>
<Typography
level="h3"
fontWeight={'400'}
sx={{ color: '#888' }}
>
{t('description1.part1')}
</Typography>
</Box>
</Grid>
<Grid xs={12} sx={{ display: 'flex' }}>
<Box
sx={{
display: 'flex',
flexWrap: 'wrap',
justifyContent: 'center',
margin: 'auto',
textAlign: 'center',
marginTop: '1.5em',
marginBottom: '12vh!important',
rawGap: '1em',
}}
>
<GitHub center />
<AFFiNEOnline center />
</Box>
</Grid>
<Grid
xs={12}
sx={{ display: 'flex', maxWidth: '1200px', margin: 'auto' }}
>
<Box
sx={{
display: 'flex',
justifyContent: 'center',
margin: 'auto',
transition: 'all .5s',
transform: 'scale(0.98)',
boxShadow: '2px 2px 40px #0002',
':hover': {
transform: 'scale(1)',
boxShadow: '2px 2px 40px #0004',
},
}}
>
<AFFiNEImage src={PageImage} alt="AFFiNE main ui" />
</Box>
</Grid>
<Grid xs={12} sx={{ display: 'flex' }}>
<Box
sx={{
display: 'flex',
flexWrap: 'wrap',
margin: 'auto',
marginTop: '12em',
}}
>
<Typography
level={matches ? 'h2' : 'h1'}
fontWeight={'bold'}
>
{t('description1.part2')}
</Typography>
</Box>
</Grid>
<Grid xs={12} sx={{ display: 'flex' }}>
<Box
sx={{
display: 'flex',
flexDirection: 'column',
flexWrap: 'wrap',
margin: 'auto',
justifyContent: 'center',
textAlign: 'center',
marginBottom: '12em',
}}
>
<Typography fontSize="1.2em">
{t('description1.part3')}
</Typography>
<Typography fontSize="1.2em">
{t('description1.part4')}
</Typography>
</Box>
</Grid>
<Grid
xs={12}
sx={{
display: 'flex',
flexDirection: matches ? 'column' : 'row',
marginBottom: '12em',
}}
>
<Grid
xs={matches ? 12 : 3}
sx={{
display: 'flex',
...(matches
? {}
: { marginLeft: '4em', marginRight: '2em' }),
}}
>
<Box
sx={{
display: 'flex',
flexDirection: 'column',
flexWrap: 'wrap',
justifyContent: 'left',
alignSelf: 'center',
textAlign: 'left',
width: '100%',
}}
>
<Typography
level="h2"
fontWeight={'bold'}
style={{ marginBottom: '0.5em' }}
>
{t('description2.part1')}
</Typography>
<Typography
fontSize="1.2em"
style={{ marginBottom: '0.25em' }}
>
{t('description2.part2')}
</Typography>
<Typography
fontSize="1.2em"
style={{ marginBottom: '0.25em' }}
>
{t('description2.part3')}
</Typography>
</Box>
</Grid>
<Grid
xs={matches ? 12 : 9}
sx={{ display: 'flex', width: '100%' }}
>
<Box
sx={{
display: 'flex',
flexDirection: 'column',
flexWrap: 'wrap',
justifyContent: 'left',
textAlign: 'left',
transition: 'all .5s',
transform: 'scale(0.98)',
boxShadow: '2px 2px 40px #0002',
':hover': {
transform: 'scale(1)',
boxShadow: '2px 2px 40px #0004',
},
}}
>
<AFFiNEImage
src={ShapeImage}
alt="AFFiNE Shape Your Page"
/>
</Box>
</Grid>
</Grid>
<Grid
xs={12}
sx={{
display: 'flex',
flexDirection: matches ? 'column' : 'row-reverse',
marginBottom: '12em',
}}
>
<Grid
xs={matches ? 12 : 6}
sx={{
display: 'flex',
...(matches ? {} : { marginLeft: '4em' }),
}}
>
<Box
sx={{
display: 'flex',
flexDirection: 'column',
flexWrap: 'wrap',
justifyContent: 'left',
alignSelf: 'center',
textAlign: 'left',
width: '100%',
}}
>
<Typography
level="h2"
fontWeight={'bold'}
style={{ marginBottom: '0.5em' }}
>
{t('description3.part1')}
</Typography>
<Typography
fontSize="1.2em"
style={{ marginBottom: '0.25em' }}
>
{t('description3.part2')}
</Typography>
<Typography
fontSize="1.2em"
style={{ marginBottom: '0.25em' }}
>
{t('description3.part3')}
</Typography>
<Typography
fontSize="1.2em"
style={{ marginBottom: '0.25em' }}
>
{t('description3.part4')}
</Typography>
</Box>
</Grid>
<Grid
xs={matches ? 12 : 6}
sx={{ display: 'flex', width: '100%' }}
>
<Box
sx={{
display: 'flex',
flexDirection: 'column',
flexWrap: 'wrap',
justifyContent: 'left',
textAlign: 'left',
transition: 'all .5s',
transform: 'scale(0.98)',
boxShadow: '2px 2px 40px #0002',
':hover': {
transform: 'scale(1)',
boxShadow: '2px 2px 40px #0004',
},
}}
>
<AFFiNEImage
src={TaskImage}
alt="AFFiNE Plan Your Task"
/>
</Box>
</Grid>
</Grid>
<Grid
xs={12}
sx={{
display: 'flex',
}}
>
<Box
sx={{
display: 'flex',
flexDirection: 'column',
flexWrap: 'wrap',
margin: 'auto',
textAlign: 'center',
marginBottom: '4em',
}}
>
<Typography
level="h2"
fontWeight={'bold'}
style={{ marginBottom: '0.5em' }}
>
{t('description4.part1')}
</Typography>
<Typography
fontSize="1.2em"
style={{ marginBottom: '0.25em' }}
>
{t('description4.part2')}
</Typography>
<Typography
fontSize="1.2em"
style={{ marginBottom: '0.25em' }}
>
{t('description4.part3')}
</Typography>
</Box>
</Grid>
<Grid xs={12} sx={{ display: 'flex', marginBottom: '12em' }}>
<Box
sx={{
display: 'flex',
justifyContent: 'center',
margin: 'auto',
transition: 'all .5s',
transform: 'scale(0.98)',
':hover': {
transform: 'scale(1)',
},
}}
>
<AFFiNEImage
src={CollaborationImage}
alt="AFFiNE Privacy-first, and collaborative"
/>
</Box>
</Grid>
<AFFiNEFooter />
</>
);
}

View File

@ -0,0 +1,153 @@
import { Box, Typography } from '@mui/joy';
import { styled } from '@mui/joy/styles';
import clsx from 'clsx';
import { useEffect, useMemo, useState } from 'react';
// eslint-disable-next-line no-restricted-imports
import { useMediaQuery } from '@mui/material';
const Alternatives = styled(Box)<{ width: string }>(({ width }) => ({
position: 'relative',
width: '20em',
height: '128px',
transform: 'translateY(-8px)',
overflowY: 'hidden',
'@media (max-width: 1024px)': {
width,
height: '48px',
transform: 'translateY(0)',
},
'& .scroll-element': {
width: 'inherit',
height: 'inherit',
position: 'absolute',
left: '0',
top: '0',
paddingTop: '22px',
lineHeight: '96px',
'@media (max-width: 1024px)': {
lineHeight: '32px',
},
},
'& .scroll-element.active': {
animation: 'primary 500ms linear infinite',
},
'.primary.active': {
animation: 'primary 500ms linear infinite',
},
'.secondary.active': {
animation: 'secondary 500ms linear infinite',
},
'@keyframes primary': {
from: {
top: '0%',
},
to: {
top: '-100%',
},
},
'@keyframes secondary': {
from: {
top: '100%',
},
to: {
top: '0%',
},
},
}));
const _alternatives = ['Notion', 'Miro', 'Monday'];
const _alternativesSize = [8, 6, 10];
export const AlternativesProduct = () => {
const [idx, setIdx] = useState(0);
const [last, current] = useMemo(
() => [
_alternatives[idx],
_alternatives[idx + 1] ? _alternatives[idx + 1] : _alternatives[0],
],
[idx]
);
const maxWidth = useMemo(() => _alternativesSize[idx], [idx]);
const [active, setActive] = useState(false);
const matches = useMediaQuery('(max-width: 1024px)');
useEffect(() => {
const handle = setInterval(() => {
setActive(true);
setTimeout(
() => {
setIdx(idx => (_alternatives[idx + 1] ? idx + 1 : 0));
setActive(false);
},
matches ? 450 : 380
);
}, 2000);
return () => clearInterval(handle);
}, [matches]);
return (
<Alternatives
width={`${maxWidth}em`}
sx={{
margin: 'auto',
marginRight: '1em',
transition: 'width .5s',
'@media (max-width: 1024px)': {
width: '8em',
},
}}
>
<Box
className={clsx(
'scroll-element',
'primary',
active && 'active'
)}
>
<Typography
fontSize="64px"
fontWeight={900}
sx={{
color: '#06449d',
textAlign: 'right',
overflow: 'hidden',
'@media (max-width: 1024px)': {
fontSize: '32px',
},
}}
>
{last}
</Typography>
</Box>
<Box
className={clsx(
'scroll-element',
'primary',
active && 'active'
)}
sx={{
marginTop: '96px',
textAlign: 'right',
overflow: 'hidden',
'@media (max-width: 1024px)': {
marginTop: '48px',
},
}}
>
<Typography
fontSize="64px"
fontWeight={900}
sx={{
color: '#06449d',
overflow: 'hidden',
'@media (max-width: 1024px)': {
fontSize: '32px',
},
}}
>
{current}
</Typography>
</Box>
</Alternatives>
);
};

View File

@ -0,0 +1,200 @@
import { Box, Grid, Typography } from '@mui/joy';
import { useState } from 'react';
// eslint-disable-next-line no-restricted-imports
import { Paper, Slide, Tab, Tabs, useMediaQuery } from '@mui/material';
import { useTranslation } from 'react-i18next';
import { AFFiNEImage } from '../common';
import ShapeImage from './shape.png';
import TaskImage from './task.png';
export const FunctionTabs = () => {
const matches = useMediaQuery('(max-width: 1024px)');
const { t } = useTranslation();
const [tab, selectTab] = useState(0);
return (
<Paper
sx={{
// backgroundColor: 'rgba(54, 100, 214, 0.05)',
padding: '10px',
position: 'relative',
height: '700px',
}}
elevation={0}
>
<Tabs
value={tab}
onChange={(_, value) => selectTab(value)}
centered
>
<Tab label={t('description2.part1')} value={0} />
<Tab label={t('description3.part1')} value={1} />
</Tabs>
<Slide direction="left" in={tab === 0} mountOnEnter unmountOnExit>
<Grid
xs={12}
sx={{
display: 'flex',
flexDirection: matches ? 'column' : 'row',
marginBottom: '12em',
position: 'absolute',
top: '100px',
left: 0,
width: '100%',
}}
>
<Grid
xs={matches ? 12 : 3}
sx={{
display: 'flex',
paddingLeft: '20px',
flex: 1,
}}
>
<Box
sx={{
display: 'flex',
flexDirection: 'column',
flexWrap: 'wrap',
justifyContent: 'left',
alignSelf: 'center',
textAlign: 'left',
width: '100%',
}}
>
<Typography
level="h2"
fontWeight={'bold'}
style={{ marginBottom: '0.5em' }}
>
{t('description2.part1')}
</Typography>
<Typography
fontSize="1.2em"
style={{ marginBottom: '0.5em' }}
>
{t('description2.part2')}
</Typography>
<Typography
fontSize="1.2em"
style={{ marginBottom: '0.25em' }}
>
{t('description2.part3')}
</Typography>
</Box>
</Grid>
<Grid
xs={matches ? 12 : 9}
sx={{ display: 'flex', width: '100%', flex: 2 }}
>
<Box
sx={{
display: 'flex',
flexDirection: 'column',
flexWrap: 'wrap',
justifyContent: 'left',
textAlign: 'left',
transition: 'all .5s',
transform: 'scale(0.98)',
boxShadow: '2px 2px 40px #0002',
':hover': {
transform: 'scale(1)',
boxShadow: '2px 2px 40px #0004',
},
}}
>
<AFFiNEImage
src={ShapeImage}
alt="AFFiNE Shape Your Page"
/>
</Box>
</Grid>
</Grid>
</Slide>
<Slide direction="left" in={tab === 1} mountOnEnter unmountOnExit>
<Grid
xs={12}
sx={{
display: 'flex',
flexDirection: matches ? 'column' : 'row-reverse',
position: 'absolute',
top: '100px',
left: 0,
width: '100%',
}}
>
<Grid
xs={matches ? 12 : 6}
sx={{
display: 'flex',
...(matches ? {} : { marginLeft: '4em' }),
}}
>
<Box
sx={{
display: 'flex',
flexDirection: 'column',
flexWrap: 'wrap',
justifyContent: 'left',
alignSelf: 'center',
textAlign: 'left',
width: '100%',
}}
>
<Typography
level="h2"
fontWeight={'bold'}
style={{ marginBottom: '0.5em' }}
>
{t('description3.part1')}
</Typography>
<Typography
fontSize="1.2em"
style={{ marginBottom: '0.25em' }}
>
{t('description3.part2')}
</Typography>
<Typography
fontSize="1.2em"
style={{ marginBottom: '0.25em' }}
>
{t('description3.part3')}
</Typography>
<Typography
fontSize="1.2em"
style={{ marginBottom: '0.25em' }}
>
{t('description3.part4')}
</Typography>
</Box>
</Grid>
<Grid
xs={matches ? 12 : 6}
sx={{ display: 'flex', width: '100%' }}
>
<Box
sx={{
display: 'flex',
flexDirection: 'column',
flexWrap: 'wrap',
justifyContent: 'left',
textAlign: 'left',
transition: 'all .5s',
transform: 'scale(0.98)',
boxShadow: '2px 2px 40px #0002',
':hover': {
transform: 'scale(1)',
boxShadow: '2px 2px 40px #0004',
},
}}
>
<AFFiNEImage
src={TaskImage}
alt="AFFiNE Plan Your Task"
/>
</Box>
</Grid>
</Grid>
</Slide>
</Paper>
);
};

View File

@ -0,0 +1,292 @@
/* eslint-disable max-lines */
/* eslint-disable @typescript-eslint/naming-convention */
import { useTranslation } from 'react-i18next';
import { Box, Button, Grid, Typography } from '@mui/joy';
import { LogoIcon } from '@toeverything/components/icons';
// eslint-disable-next-line no-restricted-imports
import { useMediaQuery } from '@mui/material';
import { AFFiNEFooter, AFFiNEHeader, AFFiNEImage } from '../common';
import { GitHub } from '../Icons';
import { AlternativesProduct } from './Alternatives';
import CollaborationImage from './collaboration.png';
import { FunctionTabs } from './FunctionTabs';
import PageImage from './page.png';
const AFFiNEOnline = (props: { center?: boolean; flat?: boolean }) => {
const matches = useMediaQuery('(max-width: 1024px)');
const { t } = useTranslation();
return (
<Button
onClick={() => {
window.open('https://livedemo.affine.pro/');
}}
{...(props.flat ? { variant: 'plain' } : {})}
{...{
sx: {
margin: 'auto 1em',
fontSize: '24px',
'@media (max-width: 1024px)': {
fontSize: '16px',
},
...(props.flat
? {
padding: matches ? '0' : '0 0.5em',
':hover': { backgroundColor: 'unset' },
}
: {}),
...(props.center
? {
padding: '0.5em 1em',
fontSize: '2em',
backgroundColor: '#000',
':hover': {
backgroundColor: '#0c60d9',
boxShadow: '2px 2px 20px #08f4',
},
}
: {}),
},
}}
startIcon={<LogoIcon />}
size="lg"
>
{t('Try it Online')}
</Button>
);
};
export function IndexPage() {
const matches = useMediaQuery('(max-width: 1024px)');
const { t } = useTranslation();
return (
<>
<AFFiNEHeader />
<Grid xs={12} sx={{ display: 'flex', marginTop: '12vh!important' }}>
<Box
sx={{
display: 'inline-flex',
flexWrap: 'wrap',
justifyContent: 'center',
margin: 'auto',
fontWeight: 'bold',
textAlign: 'center',
}}
>
<Typography
fontSize="64px"
fontWeight={900}
sx={{
marginRight: '0.25em',
'@media (max-width: 1024px)': {
fontSize: '32px',
marginRight: 0,
},
}}
>
{t('Open Source')},
</Typography>
<Typography
fontSize="64px"
fontWeight={900}
sx={{
'@media (max-width: 1024px)': {
fontSize: '32px',
},
}}
>
{t('Privacy First')}
</Typography>
</Box>
</Grid>
<Grid
xs={12}
sx={{
display: 'flex',
flexFlow: 'wrap',
overflow: 'auto',
}}
>
<Box
sx={{
display: 'inline-flex',
flexFlow: 'wrap',
margin: 'auto',
fontWeight: 'bold',
textAlign: 'center',
}}
>
<AlternativesProduct />
<Typography
fontSize="64px"
fontWeight={900}
sx={{
color: '#06449d',
margin: 'auto',
'@media (max-width: 1024px)': {
fontSize: '32px',
},
}}
>
{t('Alternative')}
</Typography>
</Box>
</Grid>
<Grid xs={12} sx={{ display: 'flex' }}>
<Box
sx={{
display: 'flex',
flexWrap: 'wrap',
justifyContent: 'center',
margin: 'auto',
textAlign: 'center',
}}
>
<Typography
level="h3"
fontWeight={'400'}
sx={{ color: '#888' }}
>
{t('description1.part1')}
</Typography>
</Box>
</Grid>
<Grid xs={12} sx={{ display: 'flex' }}>
<Box
sx={{
display: 'flex',
flexWrap: 'wrap',
justifyContent: 'center',
margin: 'auto',
textAlign: 'center',
marginTop: '1.5em',
marginBottom: '12vh!important',
rawGap: '1em',
}}
>
<GitHub center />
<AFFiNEOnline center />
</Box>
</Grid>
<Grid
xs={12}
sx={{ display: 'flex', maxWidth: '1200px', margin: 'auto' }}
>
<Box
sx={{
display: 'flex',
justifyContent: 'center',
margin: 'auto',
transition: 'all .5s',
transform: 'scale(0.98)',
boxShadow: '2px 2px 40px #0002',
':hover': {
transform: 'scale(1)',
boxShadow: '2px 2px 40px #0004',
},
}}
>
<AFFiNEImage src={PageImage} alt="AFFiNE main ui" />
</Box>
</Grid>
<Grid xs={12} sx={{ display: 'flex' }}>
<Box
sx={{
display: 'flex',
flexWrap: 'wrap',
margin: 'auto',
marginTop: '12em',
}}
>
<Typography
level={matches ? 'h2' : 'h1'}
fontWeight={'bold'}
>
{t('description1.part2')}
</Typography>
</Box>
</Grid>
<Grid xs={12} sx={{ display: 'flex' }}>
<Box
sx={{
display: 'flex',
flexDirection: 'column',
flexWrap: 'wrap',
margin: 'auto',
justifyContent: 'center',
textAlign: 'center',
marginBottom: '12em',
}}
>
<Typography fontSize="1.2em">
{t('description1.part3')}
</Typography>
<Typography fontSize="1.2em">
{t('description1.part4')}
</Typography>
</Box>
</Grid>
<FunctionTabs />
<Grid
xs={12}
sx={{
display: 'flex',
marginTop: '12em !important',
}}
>
<Box
sx={{
display: 'flex',
flexDirection: 'column',
flexWrap: 'wrap',
margin: 'auto',
textAlign: 'center',
marginBottom: '4em',
}}
>
<Typography
level="h2"
fontWeight={'bold'}
style={{ marginBottom: '0.5em' }}
>
{t('description4.part1')}
</Typography>
<Typography
fontSize="1.2em"
style={{ marginBottom: '0.25em' }}
>
{t('description4.part2')}
</Typography>
<Typography
fontSize="1.2em"
style={{ marginBottom: '0.25em' }}
>
{t('description4.part3')}
</Typography>
</Box>
</Grid>
<Grid xs={12} sx={{ display: 'flex', marginBottom: '12em' }}>
<Box
sx={{
display: 'flex',
justifyContent: 'center',
margin: 'auto',
transition: 'all .5s',
transform: 'scale(0.98)',
':hover': {
transform: 'scale(1)',
},
}}
>
<AFFiNEImage
src={CollaborationImage}
alt="AFFiNE Privacy-first, and collaborative"
/>
</Box>
</Grid>
<AFFiNEFooter />
</>
);
}

View File

Before

Width:  |  Height:  |  Size: 1.5 MiB

After

Width:  |  Height:  |  Size: 1.5 MiB

View File

@ -0,0 +1 @@
export { IndexPage } from './IndexPage';

View File

Before

Width:  |  Height:  |  Size: 1.0 MiB

After

Width:  |  Height:  |  Size: 1.0 MiB

View File

Before

Width:  |  Height:  |  Size: 1.2 MiB

After

Width:  |  Height:  |  Size: 1.2 MiB

View File

Before

Width:  |  Height:  |  Size: 1.0 MiB

After

Width:  |  Height:  |  Size: 1.0 MiB

View File

@ -6,82 +6,141 @@ import { Button, Grid } from '@mui/joy';
import Option from '@mui/joy/Option';
import Select from '@mui/joy/Select';
// eslint-disable-next-line no-restricted-imports
import { useMediaQuery } from '@mui/material';
import GitHubIcon from '@mui/icons-material/GitHub';
// eslint-disable-next-line no-restricted-imports
import { styled, useMediaQuery } from '@mui/material';
import AFFiNETextLogo from './affine-text-logo.png';
import { HoverMenu } from './HoverMenu';
import { options } from '../i18n';
import { GitHub } from '../Icons';
export const AFFiNEHeader = () => {
const matches = useMediaQuery('(max-width: 1024px)');
const navigate = useNavigate();
const { i18n } = useTranslation();
const { i18n, t } = useTranslation();
const changeLanguage = (event: any) => {
i18n.changeLanguage(event);
};
const matchesIPAD = useMediaQuery('(max-width: 768px)');
return (
<Grid
container
spacing={2}
sx={{
maxWidth: '1280px',
margin: 'auto',
}}
>
<Grid xs={6}>
<Button
size="lg"
variant="plain"
sx={{
padding: matches ? '0' : '0 0.5em',
':hover': { backgroundColor: 'unset' },
fontSize: '24px',
'@media (max-width: 1024px)': {
fontSize: '16px',
},
}}
<Container container spacing={2}>
<Grid xs={6} sx={{ display: 'flex', alignItems: 'center' }}>
<StyledImage
src={AFFiNETextLogo}
alt="affine"
onClick={() => navigate('/')}
>
AFFiNE
</Button>
</Grid>
<Grid xs={6} sx={{ display: 'flex', justifyContent: 'right' }}>
<GitHub flat />
<Button
onClick={() => window.open('https://blog.affine.pro')}
variant="plain"
sx={{
padding: matches ? '0' : '0 0.5em',
':hover': { backgroundColor: 'unset' },
fontSize: '24px',
'@media (max-width: 1024px)': {
fontSize: '16px',
},
}}
size="lg"
>
Blog
</Button>
/>
<Button
onClick={() => navigate('/aboutus')}
variant="plain"
color="neutral"
sx={{
padding: matches ? '0' : '0 0.5em',
':hover': { backgroundColor: 'unset' },
fontSize: '24px',
'@media (max-width: 1024px)': {
fontSize: '16px',
},
fontSize: '16px',
}}
size="lg"
size="md"
>
About Us
{t('AboutUs')}
</Button>
<Button
onClick={() => window.open('https://blog.affine.pro')}
variant="plain"
color="neutral"
sx={{
padding: matches ? '0' : '0 0.5em',
fontSize: '16px',
}}
size="md"
>
{t('Blog')}
</Button>
<Button
onClick={() => window.open('https://docs.affine.pro/')}
variant="plain"
color="neutral"
sx={{
padding: matches ? '0' : '0 0.5em',
fontSize: '16px',
}}
size="md"
>
{t('Docs')}
</Button>
<Button
onClick={() => window.open('https://feedback.affine.pro/')}
variant="plain"
color="neutral"
sx={{
padding: matches ? '0' : '0 0.5em',
fontSize: '16px',
}}
size="md"
>
{t('Feedback')}
</Button>
</Grid>
<Grid xs={6} sx={{ display: 'flex', justifyContent: 'right' }}>
<Button
variant="plain"
color="neutral"
onClick={() =>
window.open('https://github.com/toeverything/AFFiNE')
}
sx={{
padding: matches ? '0' : '0 0.5em',
fontSize: '16px',
}}
size="md"
>
<GitHubIcon />
</Button>
<Button
onClick={() => window.open('https://livedemo.affine.pro/')}
variant="plain"
color="neutral"
sx={{
padding: matches ? '0' : '0 0.5em',
fontSize: '16px',
}}
size="md"
>
{t('Try it Online')}
</Button>
<HoverMenu
title={t('ContactUs')}
options={[
{
title: 'Discord',
value: 'https://discord.gg/Arn7TqJBvG',
},
{
title: 'Telegram',
value: 'https://t.me/affineworkos',
},
{
title: 'Reddit',
value: 'https://www.reddit.com/r/Affine/',
},
{
title: 'Medium',
value: 'https://medium.com/@affineworkos',
},
{
title: 'Email',
value: 'mailto:contact@toeverything.info',
},
]}
onSelect={href => {
window.open(href);
}}
/>
<Select
defaultValue="en"
sx={{ display: matchesIPAD ? 'none' : 'intial' }}
onChange={changeLanguage}
size="md"
variant="plain"
>
{options.map(option => (
<Option key={option.value} value={option.value}>
@ -90,6 +149,26 @@ export const AFFiNEHeader = () => {
))}
</Select>
</Grid>
</Grid>
</Container>
);
};
const Container = styled(Grid)({
position: 'fixed',
top: 0,
left: '50%',
transform: 'translateX(-50%)',
width: '100%',
paddingTop: '1em',
backgroundColor: '#fff',
zIndex: 1500,
maxWidth: '1440px',
margin: 'auto',
marginTop: '0 !important',
});
const StyledImage = styled('img')({
height: '24px',
marginRight: '16px',
cursor: 'pointer',
});

View File

@ -0,0 +1,73 @@
import Button from '@mui/joy/Button';
// eslint-disable-next-line no-restricted-imports
import { styled, Tooltip, type TooltipProps } from '@mui/material';
interface HoverMenuProps {
title: string;
options: Array<{ title: string; value: string }>;
onSelect: (value: string) => void;
}
export function HoverMenu({ title, options, onSelect }: HoverMenuProps) {
return (
<StyledTooltip
title={
<>
{options.map(option => {
return (
<ListItem
key={option.value}
href={option.value}
target="_blank"
title={option.value}
onClick={() => {
onSelect(option.value);
}}
>
{option.title}
</ListItem>
);
})}
</>
}
>
<Button
variant="plain"
color="neutral"
sx={{
fontSize: '16px',
}}
size="md"
>
{title}
</Button>
</StyledTooltip>
);
}
const StyledTooltip = styled(({ className, ...props }: TooltipProps) => (
<Tooltip {...props} classes={{ popper: className }} />
))(({ theme }) => ({
'& .MuiTooltip-tooltip': {
backgroundColor: 'white',
boxShadow: theme.shadows[4],
color: '#272930',
zIndex: '1500',
},
}));
const ListItem = styled('a')({
display: 'block',
fontSize: '16px',
lineHeight: '32px',
padding: '5px',
cursor: 'pointer',
borderRadius: '4px',
color: '#272930',
textDecoration: 'none',
'&:hover': {
color: '#131418',
backgroundColor: '#eeeff0',
},
});

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

View File

@ -1,8 +1,11 @@
{
"translation": {
"Blog": "Blog",
"AboutUs": "About AboutUs",
"AboutUs": "About Us",
"Open Source": "Open Source",
"Docs": "Docs",
"Feedback": "Feedback",
"ContactUs": "Contact Us",
"Privacy First": "Privacy First",
"Alternative": "Alternative",
"Check GitHub": "Check GitHub",

View File

@ -4,9 +4,12 @@
"AboutUs": "关于我们",
"Open Source": "开源",
"Privacy First": "隐私第一",
"Docs": "文档",
"Feedback": "反馈",
"ContactUs": "联系我们",
"Alternative": "的另一种选择",
"Check GitHub": "GitHub中查看",
"Try it Online": "在线上试试",
"Try it Online": "在线试用",
"description1": {
"part1": "Affine是面向专业人士的下一代协同知识库",
"part2": "它不仅仅是一个文档、白板和表格的集合。",

View File

@ -4,7 +4,7 @@ import { Container } from '@mui/joy';
import { CssVarsProvider } from '@mui/joy/styles';
import { AboutUs } from './AboutUs';
import { App } from './App';
import { IndexPage } from './IndexPage';
const VenusContainer = () => {
return (
@ -30,7 +30,7 @@ export function VenusRoutes() {
<Routes>
<Route element={<VenusContainer />}>
<Route path="/aboutus" element={<AboutUs />} />
<Route path="/" element={<App />} />
<Route path="/" element={<IndexPage />} />
</Route>
</Routes>
);

View File

@ -25,9 +25,9 @@ const GoogleIcon = () => (
<g
id="Page-1"
stroke="none"
stroke-width="1"
strokeWidth="1"
fill="none"
fill-rule="evenodd"
fillRule="evenodd"
>
<g id="Artboard-1" transform="translate(-332.000000, -639.000000)">
<g

View File

@ -99,7 +99,7 @@ const langs: Record<string, any> = {
dockerfile: () => StreamLanguage.define(dockerFile),
r: () => StreamLanguage.define(r),
};
const DEFAULT_LANG = 'javascript';
const DEFAULT_LANG = 'markdown';
const CodeBlock = styled('div')(({ theme }) => ({
backgroundColor: '#F2F5F9',
padding: '8px 24px',
@ -142,10 +142,13 @@ export const CodeView = ({ block, editor }: CreateCodeView) => {
const langType: string = block.getProperty('lang');
const [extensions, setExtensions] = useState<Extension[]>();
const codeMirror = useRef<ReactCodeMirrorRef>();
useOnSelect(block.id, (_is_select: boolean) => {
const focusCode = () => {
if (codeMirror.current) {
codeMirror?.current?.view?.focus();
}
};
useOnSelect(block.id, (_is_select: boolean) => {
focusCode();
});
const onChange = (value: string) => {
block.setProperty('text', {
@ -158,6 +161,9 @@ export const CodeView = ({ block, editor }: CreateCodeView) => {
};
useEffect(() => {
handleLangChange(langType ? langType : DEFAULT_LANG);
setTimeout(() => {
focusCode();
}, 100);
}, []);
const copyCode = () => {

18
pnpm-lock.yaml generated
View File

@ -194,11 +194,13 @@ importers:
apps/ligo-virgo:
specifiers:
'@emotion/babel-plugin': ^11.10.2
'@mui/icons-material': ^5.8.4
firebase: ^9.9.3
mini-css-extract-plugin: ^2.6.1
webpack: ^5.74.0
dependencies:
'@emotion/babel-plugin': 11.10.2
'@mui/icons-material': 5.8.4
devDependencies:
firebase: 9.9.3
@ -213,6 +215,7 @@ importers:
apps/venus:
specifiers:
'@emotion/babel-plugin': ^11.10.2
'@emotion/react': ^11.10.0
'@emotion/styled': ^11.10.0
'@mdx-js/loader': ^2.1.3
@ -227,6 +230,7 @@ importers:
react-i18next: ^11.18.4
webpack: ^5.74.0
dependencies:
'@emotion/babel-plugin': 11.10.2
'@emotion/react': 11.10.0
'@emotion/styled': 11.10.0_@emotion+react@11.10.0
'@mui/joy': 5.0.0-alpha.42_72v32ofbtgpmxm7mhvtx474vfu
@ -2681,8 +2685,8 @@ packages:
tslib: 2.4.0
dev: false
/@emotion/babel-plugin/11.10.0:
resolution: {integrity: sha512-xVnpDAAbtxL1dsuSelU5A7BnY/lftws0wUexNJZTPsvX/1tM4GZJbclgODhvW4E+NH7E5VFcH0bBn30NvniPJA==}
/@emotion/babel-plugin/11.10.2:
resolution: {integrity: sha512-xNQ57njWTFVfPAc3cjfuaPdsgLp5QOSuRsj9MA6ndEhH/AzuZM86qIQzt6rq+aGBwj3n5/TkLmU5lhAfdRmogA==}
peerDependencies:
'@babel/core': ^7.0.0
peerDependenciesMeta:
@ -2811,7 +2815,7 @@ packages:
optional: true
dependencies:
'@babel/runtime': 7.18.6
'@emotion/babel-plugin': 11.10.0
'@emotion/babel-plugin': 11.10.2
'@emotion/cache': 11.10.1
'@emotion/serialize': 1.1.0
'@emotion/utils': 1.2.0
@ -2938,7 +2942,7 @@ packages:
optional: true
dependencies:
'@babel/runtime': 7.18.6
'@emotion/babel-plugin': 11.10.0
'@emotion/babel-plugin': 11.10.2
'@emotion/is-prop-valid': 1.2.0
'@emotion/react': 11.10.0
'@emotion/serialize': 1.1.0
@ -7417,10 +7421,8 @@ packages:
indent-string: 4.0.0
dev: true
/ajv-formats/2.1.1_ajv@8.11.0:
/ajv-formats/2.1.1:
resolution: {integrity: sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==}
peerDependencies:
ajv: ^8.0.0
peerDependenciesMeta:
ajv:
optional: true
@ -16987,7 +16989,7 @@ packages:
dependencies:
'@types/json-schema': 7.0.11
ajv: 8.11.0
ajv-formats: 2.1.1_ajv@8.11.0
ajv-formats: 2.1.1
ajv-keywords: 5.1.0_ajv@8.11.0
dev: true