Content pages

This commit is contained in:
Tom Moor
2018-12-20 20:00:58 -08:00
parent d1b352963f
commit b7bea4941e
18 changed files with 195 additions and 88 deletions

View File

@@ -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';
}

View File

@@ -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>

View File

@@ -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>
);
}

View File

@@ -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>
);
}

View File

@@ -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;
}
}
`;

View File

@@ -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" />&nbsp;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;

View File

@@ -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>
);
}

View File

@@ -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;
`;

View File

@@ -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",

View File

@@ -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.

View File

@@ -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);

View File

@@ -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(