Content pages
This commit is contained in:
@@ -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>
|
||||
);
|
||||
}
|
||||
18
server/pages/components/Markdown.js
Normal file
18
server/pages/components/Markdown.js
Normal 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;
|
||||
}
|
||||
}
|
||||
`;
|
||||
@@ -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;
|
||||
|
||||
45
server/pages/integrations/Integration.js
Normal file
45
server/pages/integrations/Integration.js
Normal 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>
|
||||
);
|
||||
}
|
||||
48
server/pages/integrations/Menu.js
Normal file
48
server/pages/integrations/Menu.js
Normal 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;
|
||||
`;
|
||||
@@ -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",
|
||||
5
server/pages/integrations/figma.md
Normal file
5
server/pages/integrations/figma.md
Normal 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.
|
||||
@@ -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(
|
||||
|
||||
Reference in New Issue
Block a user