Content pages
This commit is contained in:
parent
d1b352963f
commit
b7bea4941e
|
@ -6,7 +6,7 @@ import OutlineLogo from 'shared/components/OutlineLogo';
|
|||
function Branding() {
|
||||
return (
|
||||
<Link href={process.env.URL}>
|
||||
<OutlineLogo /> Outline
|
||||
<OutlineLogo size={16} fill="#000" /> Outline
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 2.8 KiB |
|
@ -23,7 +23,12 @@ export default function errorHandling() {
|
|||
}
|
||||
}
|
||||
|
||||
if (message.match('Authorization error')) {
|
||||
if (message.match(/Not found/i)) {
|
||||
ctx.status = 404;
|
||||
error = 'not_found';
|
||||
}
|
||||
|
||||
if (message.match(/Authorization error/i)) {
|
||||
ctx.status = 403;
|
||||
error = 'authorization_error';
|
||||
}
|
||||
|
|
|
@ -3,8 +3,8 @@ import * as React from 'react';
|
|||
import format from 'date-fns/format';
|
||||
import styled from 'styled-components';
|
||||
import Grid from 'styled-components-grid';
|
||||
import ReactMarkdown from 'react-markdown';
|
||||
import { Helmet } from 'react-helmet';
|
||||
import Markdown from './components/Markdown';
|
||||
import Header from './components/Header';
|
||||
import Content from './components/Content';
|
||||
|
||||
|
@ -36,7 +36,7 @@ function Changelog({ releases }: { releases: Release[] }) {
|
|||
<Time dateTime={release.created_at}>
|
||||
{format(new Date(release.created_at), 'MMMM Do, YYYY')}
|
||||
</Time>
|
||||
<ReactMarkdown source={release.body} />
|
||||
<Markdown source={release.body} />
|
||||
</Article>
|
||||
))}
|
||||
</Content>
|
||||
|
|
|
@ -1,29 +0,0 @@
|
|||
// @flow
|
||||
import * as React from 'react';
|
||||
import { find } from 'lodash';
|
||||
import Grid from 'styled-components-grid';
|
||||
import { Helmet } from 'react-helmet';
|
||||
import Header from './components/Header';
|
||||
import Content from './components/Content';
|
||||
import IntegrationMenu from './components/IntegrationMenu';
|
||||
import integrations from '../config/integrations';
|
||||
|
||||
export default function Integration({ slug }: { slug: string }) {
|
||||
const integation = find(integrations, i => i.slug === slug);
|
||||
|
||||
return (
|
||||
<Grid>
|
||||
<Helmet>
|
||||
<title>{integation.name} Integration</title>
|
||||
</Helmet>
|
||||
<Header>
|
||||
<h1>{integation.name} Integration</h1>
|
||||
<p>{integation.description}</p>
|
||||
</Header>
|
||||
<Content>
|
||||
<IntegrationMenu integrations={integrations} />
|
||||
<div />
|
||||
</Content>
|
||||
</Grid>
|
||||
);
|
||||
}
|
|
@ -1,24 +0,0 @@
|
|||
// @flow
|
||||
import { map, groupBy } from 'lodash';
|
||||
import * as React from 'react';
|
||||
|
||||
export default function IntegrationMenu({ integrations }) {
|
||||
const categories = groupBy(integrations, i => i.category);
|
||||
|
||||
return (
|
||||
<nav>
|
||||
{map(categories, (integrations, category) => (
|
||||
<React.Fragment>
|
||||
<h3>{category}</h3>
|
||||
<ul>
|
||||
{integrations.map(i => (
|
||||
<li>
|
||||
<a href={`/integrations/${i.slug}`}>{i.name}</a>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</React.Fragment>
|
||||
))}
|
||||
</nav>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
// @flow
|
||||
import ReactMarkdown from 'react-markdown';
|
||||
import styled from 'styled-components';
|
||||
|
||||
export default styled(ReactMarkdown)`
|
||||
blockquote {
|
||||
margin-left: 0;
|
||||
background-color: ${props => props.theme.smoke};
|
||||
border-left: 6px solid ${props => props.theme.smokeDark};
|
||||
padding: 15px 30px 15px 15px;
|
||||
font-style: italic;
|
||||
font-size: 16px;
|
||||
|
||||
p {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
`;
|
|
@ -4,6 +4,7 @@ import { sortBy } from 'lodash';
|
|||
import styled from 'styled-components';
|
||||
import breakpoint from 'styled-components-breakpoint';
|
||||
import Centered from './Centered';
|
||||
import OutlineLogo from '../../../shared/components/OutlineLogo';
|
||||
import TeamLogo from '../../../shared/components/TeamLogo';
|
||||
import { fadeAndScaleIn } from '../../../shared/styles/animations';
|
||||
import {
|
||||
|
@ -37,7 +38,9 @@ function TopNavigation({ sessions, loggedIn }: Props) {
|
|||
|
||||
return (
|
||||
<Nav>
|
||||
<Brand href={process.env.URL}>Outline</Brand>
|
||||
<Brand href={process.env.URL}>
|
||||
<OutlineLogo size={18} fill="#000" /> Outline
|
||||
</Brand>
|
||||
<Menu>
|
||||
<MenuItemDesktop>
|
||||
<a href={features()}>Features</a>
|
||||
|
@ -247,6 +250,8 @@ const BottomNav = styled.nav`
|
|||
`;
|
||||
|
||||
const Brand = styled.a`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-weight: 600;
|
||||
font-size: 20px;
|
||||
text-decoration: none;
|
||||
|
|
|
@ -0,0 +1,45 @@
|
|||
// @flow
|
||||
import * as React from 'react';
|
||||
import Grid from 'styled-components-grid';
|
||||
import { Helmet } from 'react-helmet';
|
||||
import Markdown from '../components/Markdown';
|
||||
import Header from '../components/Header';
|
||||
import Content from '../components/Content';
|
||||
import Menu from './Menu';
|
||||
import integrations from './content';
|
||||
|
||||
type TIntegration = {
|
||||
slug: string,
|
||||
name: string,
|
||||
url: string,
|
||||
description: string,
|
||||
};
|
||||
|
||||
type Props = {
|
||||
integration: TIntegration,
|
||||
content: string,
|
||||
};
|
||||
|
||||
export default function Integration({ integration, content }: Props) {
|
||||
return (
|
||||
<Grid>
|
||||
<Helmet>
|
||||
<title>{integration.name} Integration</title>
|
||||
</Helmet>
|
||||
<Header background="#F4F7FA">
|
||||
<h1>{integration.name} Integration</h1>
|
||||
<p>{integration.description}</p>
|
||||
</Header>
|
||||
<Content>
|
||||
<Grid>
|
||||
<Grid.Unit size={{ desktop: 1 / 4 }}>
|
||||
<Menu integrations={integrations} />
|
||||
</Grid.Unit>
|
||||
<Grid.Unit size={{ desktop: 3 / 4 }}>
|
||||
<Markdown source={content} />
|
||||
</Grid.Unit>
|
||||
</Grid>
|
||||
</Content>
|
||||
</Grid>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
// @flow
|
||||
import * as React from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { map, groupBy } from 'lodash';
|
||||
|
||||
export default function IntegrationMenu({ integrations }: { integrations: * }) {
|
||||
const categories = groupBy(integrations, i => i.category);
|
||||
|
||||
return (
|
||||
<nav>
|
||||
{map(categories, (integrations, category) => (
|
||||
<React.Fragment key={category}>
|
||||
<h3>{category}</h3>
|
||||
<List>
|
||||
{integrations.map(i => (
|
||||
<li key={i.slug}>
|
||||
<MenuItem href={`/integrations/${i.slug}`}>
|
||||
<Logo src={`/images/${i.slug}.png`} alt={i.name} />
|
||||
<span>{i.name}</span>
|
||||
</MenuItem>
|
||||
</li>
|
||||
))}
|
||||
</List>
|
||||
</React.Fragment>
|
||||
))}
|
||||
</nav>
|
||||
);
|
||||
}
|
||||
|
||||
const MenuItem = styled.a`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 16px;
|
||||
color: ${props => props.theme.text};
|
||||
`;
|
||||
|
||||
const Logo = styled.img`
|
||||
user-select: none;
|
||||
height: 18px;
|
||||
border-radius: 2px;
|
||||
margin-right: 8px;
|
||||
`;
|
||||
|
||||
const List = styled.ul`
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
`;
|
|
@ -95,14 +95,6 @@
|
|||
"description": "Sharable code snippets, hosted by GitHub",
|
||||
"content": ""
|
||||
},
|
||||
{
|
||||
"slug": "numeracy",
|
||||
"name": "Numeracy",
|
||||
"url": "https://numeracy.io",
|
||||
"category": "Developers",
|
||||
"description": "A SQL pad for writing, iterating, and exploring data",
|
||||
"content": ""
|
||||
},
|
||||
{
|
||||
"slug": "mode-analytics",
|
||||
"name": "Mode Analytics",
|
||||
|
@ -111,6 +103,14 @@
|
|||
"description": "Connect and analyze data from anywhere",
|
||||
"content": ""
|
||||
},
|
||||
{
|
||||
"slug": "numeracy",
|
||||
"name": "Numeracy",
|
||||
"url": "https://numeracy.io",
|
||||
"category": "Developers",
|
||||
"description": "A SQL pad for writing, iterating, and exploring data",
|
||||
"content": ""
|
||||
},
|
||||
{
|
||||
"slug": "loom",
|
||||
"name": "Loom",
|
|
@ -0,0 +1,5 @@
|
|||
In an Outline document, paste a link to a [Figma](https://figma.com) design and we will instantly convert it to an interactive, live preview.
|
||||
|
||||
Because Figma is an online design tool you can see design work happening in realtime, right within Outline. Embed design specs, product designs, or marketing materials easily.
|
||||
|
||||
> This integration works without any additional settings or authentication.
|
|
@ -4,9 +4,9 @@ import { map, groupBy } from 'lodash';
|
|||
import styled from 'styled-components';
|
||||
import Grid from 'styled-components-grid';
|
||||
import { Helmet } from 'react-helmet';
|
||||
import Header from './components/Header';
|
||||
import Content from './components/Content';
|
||||
import integrations from '../config/integrations';
|
||||
import Header from '../components/Header';
|
||||
import Content from '../components/Content';
|
||||
import integrations from './content';
|
||||
|
||||
const categories = groupBy(integrations, i => i.category);
|
||||
|
|
@ -1,5 +1,7 @@
|
|||
// @flow
|
||||
import * as React from 'react';
|
||||
import fs from 'fs-extra';
|
||||
import { find } from 'lodash';
|
||||
import path from 'path';
|
||||
import Koa from 'koa';
|
||||
import Router from 'koa-router';
|
||||
|
@ -18,8 +20,9 @@ import About from './pages/About';
|
|||
import Changelog from './pages/Changelog';
|
||||
import Privacy from './pages/Privacy';
|
||||
import Pricing from './pages/Pricing';
|
||||
import Integrations from './pages/Integrations';
|
||||
import Integration from './pages/Integration';
|
||||
import Integrations from './pages/integrations';
|
||||
import integrations from './pages/integrations/content';
|
||||
import Integration from './pages/integrations/Integration';
|
||||
import Api from './pages/Api';
|
||||
import SubdomainSignin from './pages/SubdomainSignin';
|
||||
|
||||
|
@ -36,7 +39,11 @@ const renderapp = async ctx => {
|
|||
};
|
||||
|
||||
// serve static assets
|
||||
koa.use(serve(path.resolve(__dirname, '../public')));
|
||||
koa.use(
|
||||
serve(path.resolve(__dirname, '../public'), {
|
||||
maxage: 60 * 60 * 24 * 30 * 1000,
|
||||
})
|
||||
);
|
||||
|
||||
router.get('/_health', ctx => (ctx.body = 'OK'));
|
||||
|
||||
|
@ -58,9 +65,20 @@ router.get('/about', ctx => renderpage(ctx, <About />));
|
|||
router.get('/pricing', ctx => renderpage(ctx, <Pricing />));
|
||||
router.get('/developers', ctx => renderpage(ctx, <Api />));
|
||||
router.get('/privacy', ctx => renderpage(ctx, <Privacy />));
|
||||
router.get('/integrations/:slug', ctx =>
|
||||
renderpage(ctx, <Integration slug={ctx.params.slug} />)
|
||||
);
|
||||
router.get('/integrations/:slug', async ctx => {
|
||||
const slug = ctx.params.slug;
|
||||
const integration = find(integrations, i => i.slug === slug);
|
||||
if (!integration) throw new Error('Not found');
|
||||
|
||||
const content = await fs.readFile(
|
||||
path.resolve(__dirname, `pages/integrations/${slug}.md`)
|
||||
);
|
||||
|
||||
return renderpage(
|
||||
ctx,
|
||||
<Integration integration={integration} content={content} />
|
||||
);
|
||||
});
|
||||
router.get('/integrations', ctx => renderpage(ctx, <Integrations />));
|
||||
router.get('/changelog', async ctx => {
|
||||
const data = await fetch(
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
// @flow
|
||||
import * as React from 'react';
|
||||
|
||||
type Props = {
|
||||
size?: number,
|
||||
fill?: string,
|
||||
className?: string,
|
||||
};
|
||||
|
||||
function OutlineLogo({ size = 32, fill = '#333', className }: Props) {
|
||||
return (
|
||||
<svg
|
||||
fill={fill}
|
||||
width={size}
|
||||
height={size}
|
||||
viewBox="0 0 64 64"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className={className}
|
||||
>
|
||||
<path d="M32,57.6 L32,59.1606101 C32,61.3697491 30.209139,63.1606101 28,63.1606101 C27.3130526,63.1606101 26.6376816,62.9836959 26.038955,62.6469122 L2.03895504,49.1469122 C0.779447116,48.438439 -4.3614532e-15,47.1057033 -7.10542736e-15,45.6606101 L-7.10542736e-15,18.3393899 C-7.28240024e-15,16.8942967 0.779447116,15.561561 2.03895504,14.8530878 L26.038955,1.35308779 C27.9643866,0.270032565 30.4032469,0.952913469 31.4863021,2.87834498 C31.8230858,3.47707155 32,4.15244252 32,4.83938994 L32,6.4 L34.8506085,5.54481746 C36.9665799,4.91002604 39.1965137,6.11075966 39.8313051,8.22673106 C39.9431692,8.59961116 40,8.98682435 40,9.3761226 L40,11 L43.5038611,10.5620174 C45.6959408,10.2880074 47.6951015,11.8429102 47.9691115,14.0349899 C47.9896839,14.1995692 48,14.3652688 48,14.5311289 L48,49.4688711 C48,51.6780101 46.209139,53.4688711 44,53.4688711 C43.8341399,53.4688711 43.6684404,53.458555 43.5038611,53.4379826 L40,53 L40,54.6238774 C40,56.8330164 38.209139,58.6238774 36,58.6238774 C35.6107017,58.6238774 35.2234886,58.5670466 34.8506085,58.4551825 L32,57.6 Z M32,53.4238774 L36,54.6238774 L36,9.3761226 L32,10.5761226 L32,53.4238774 Z M40,15.0311289 L40,48.9688711 L44,49.4688711 L44,14.5311289 L40,15.0311289 Z M5.32907052e-15,44.4688711 L5.32907052e-15,19.5311289 L3.55271368e-15,44.4688711 Z M4,18.3393899 L4,45.6606101 L28,59.1606101 L28,4.83938994 L4,18.3393899 Z M8,21 L12,19 L12,45 L8,43 L8,21 Z" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export default OutlineLogo;
|
|
@ -1,9 +0,0 @@
|
|||
// @flow
|
||||
import * as React from 'react';
|
||||
import logo from './logo.png';
|
||||
|
||||
function OutlineLogo() {
|
||||
return <img src={logo} width={16} height={16} alt="Outline Logo" />;
|
||||
}
|
||||
|
||||
export default OutlineLogo;
|
Binary file not shown.
Before Width: | Height: | Size: 569 B |
|
@ -77,8 +77,8 @@ export default `
|
|||
|
||||
hr {
|
||||
border: 0;
|
||||
height: 0;
|
||||
border-top: 1px solid rgba(0, 0, 0, 0.1);
|
||||
border - bottom: 1px solid rgba(255, 255, 255, 0.3);
|
||||
}
|
||||
height: 0;
|
||||
border-top: 1px solid rgba(0, 0, 0, 0.1);
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.3);
|
||||
}
|
||||
`;
|
||||
|
|
Reference in New Issue