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

@ -6,7 +6,7 @@ import OutlineLogo from 'shared/components/OutlineLogo';
function Branding() {
return (
<Link href={process.env.URL}>
<OutlineLogo />&nbsp;Outline
<OutlineLogo size={16} fill="#000" />&nbsp;Outline
</Link>
);
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 2.8 KiB

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(

View File

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

View File

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

View File

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