diff --git a/README.md b/README.md index 2d3528b..9e0f26b 100644 --- a/README.md +++ b/README.md @@ -24,6 +24,12 @@ Documentation is written as Markdown files in the `content` directory, and is or We care about the ease of writing documentation. Docbox comes with batteries included: after you `npm install` the project, you can run `npm start` and its development server, [budo](https://github.com/mattdesl/budo), will serve the website locally and update automatically. +### Requirements + +* Node v4 or higher +* NPM +* Git + To run the site locally: 1. Clone this repository diff --git a/content/example.md b/content/example.md index 087ccb6..f3d1163 100644 --- a/content/example.md +++ b/content/example.md @@ -55,7 +55,7 @@ wobbles.list() Creates a new, empty wobble. ```endpoint -POST /wobbles/v1/{username} wobbles:write +POST /wobbles/v1/{username} ``` #### Example request @@ -114,7 +114,7 @@ Property | Description Returns a single wobble. ```endpoint -GET /wobbles/v1/{username}/{wobble_id} wobbles:read +GET /wobbles/v1/{username}/{wobble_id} ``` Retrieve information about an existing wobble. @@ -156,7 +156,7 @@ client.readWobble('wobble-id', Updates the properties of a particular wobble. ```endpoint -PATCH /wobbles/v1/{username}/{wobble_id} wobbles:write +PATCH /wobbles/v1/{username}/{wobble_id} ``` #### Example request @@ -217,7 +217,7 @@ Property | Description Deletes a wobble, including all wibbles it contains. ```endpoint -DELETE /wobbles/v1/{username}/{wobble_id} wobbles:write +DELETE /wobbles/v1/{username}/{wobble_id} ``` #### Example request @@ -250,7 +250,7 @@ List all the wibbles in a wobble. The response body will be a WobbleCollection. ```endpoint -GET /wobbles/v1/{username}/{wobble_id}/wibbles wobbles:read +GET /wobbles/v1/{username}/{wobble_id}/wibbles ``` #### Example request @@ -304,7 +304,7 @@ with the given ID in the wobble, it will be replaced. If there isn't a wibble with that ID, a new wibble is created. ```endpoint -PUT /wobbles/v1/{username}/{wobble_id}/wibbles/{wibble_id} wobbles:write +PUT /wobbles/v1/{username}/{wobble_id}/wibbles/{wibble_id} ``` #### Example request @@ -362,7 +362,7 @@ Property | Description Retrieves a wibble in a wobble. ```endpoint -GET /wobbles/v1/{username}/{wobble_id}/wibbles/{wibble_id} wobbles:read +GET /wobbles/v1/{username}/{wobble_id}/wibbles/{wibble_id} ``` #### Example request @@ -403,7 +403,7 @@ wibble = wobbles.read_wibble(wobble_id, '2').json() Removes a wibble from a wobble. ```endpoint -DELETE /wobbles/v1/{username}/{wobble_id}/wibbles/{wibble_id} wobbles:write +DELETE /wobbles/v1/{username}/{wobble_id}/wibbles/{wibble_id} ``` #### Example request diff --git a/custom/content.js b/custom/content.js new file mode 100644 index 0000000..0e4f45b --- /dev/null +++ b/custom/content.js @@ -0,0 +1,16 @@ +var fs = require('fs'); + +/** + * This file exports the content of your website, as a bunch of concatenated + * Markdown files. By doing this explicitly, you can control the order + * of content without any level of abstraction. + * + * Using the brfs module, fs.readFileSync calls in this file are translated + * into strings of those files' content before the file is delivered to a + * browser: the content is read ahead-of-time and included in bundle.js. + */ +module.exports = + '# Introduction\n' + + fs.readFileSync('./content/introduction.md', 'utf8') + '\n' + + '# Example\n' + + fs.readFileSync('./content/example.md', 'utf8') + '\n'; diff --git a/custom/index.js b/custom/index.js new file mode 100644 index 0000000..46b3746 --- /dev/null +++ b/custom/index.js @@ -0,0 +1,61 @@ +'use strict'; + +/** + * Brand names, in order to decreasing length, for different + * media queries. + */ +module.exports.brandNames = { + desktop: 'Wobble API Documentation', + tablet: 'Wobble API Docs', + mobile: 'API Docs' +}; + +/** + * Classes that define the top-left brand box. + */ +module.exports.brandClasses = 'fill-red'; + + +/** + * Text for the link back to the linking website. + */ +module.exports.backLink = 'Back to wobbles.com'; + +/** + * Runs after highlighting code samples. You can use this + * hook to, for instance, highlight a token and link it + * to some canonical part of documentation. + */ +module.exports.postHighlight = function(html) { + return html; +}; + +/** + * Highlight tokens in endpoint URLs, optionally linking to documentation + * or adding detail. This is the equivalent of postHighlight but it + * operates on endpoint URLs only. + */ +function highlightTokens(str) { + return str.replace(/{[\w_]+}/g, + (str) => '' + str + ''); +} + +/** + * Transform endpoints given as strings in a highlighted block like + * + * ```endpoint + * GET /foo/bar + * ``` + * + * Into HTML nodes that format those endpoints in nice ways. + */ +module.exports.transformURL = function(value) { + let parts = value.split(/\s+/); + return { + type: 'html', + value: `
+
${parts[0]}
+
${highlightTokens(parts[1])}
+
` + }; +}; diff --git a/src/components/app.js b/src/components/app.js index 47b85ba..7ce4aa6 100644 --- a/src/components/app.js +++ b/src/components/app.js @@ -5,11 +5,12 @@ import RoundedToggle from './rounded_toggle'; import PureRenderMixin from 'react-pure-render/mixin'; import GithubSlugger from 'github-slugger'; import debounce from 'lodash.debounce'; +import { brandNames, brandClasses } from '../../custom'; let slugger = new GithubSlugger(); let slug = title => { slugger.reset(); return slugger.slug(title); }; -let languageOptions = ['curl', 'cli', 'python', 'javascript']; +let languageOptions = ['cURL', 'CLI', 'Python', 'JavaScript']; let debouncedReplaceState = debounce(hash => { window.history.replaceState('', '', hash); @@ -45,7 +46,7 @@ var App = React.createClass({ return { mqls: mqls, queries: {}, - language: 'curl', + language: 'cURL', activeSection: active, showNav: false }; @@ -57,7 +58,7 @@ var App = React.createClass({ queries: { desktop: true }, - language: 'curl', + language: 'cURL', activeSection: '', showNav: false }; @@ -126,11 +127,11 @@ var App = React.createClass({ {/* Content background */ } {!queries.mobile &&
-
+
} {/* Desktop nav */ } - {queries.desktop &&
+ {queries.desktop &&
+ language={this.state.language.toLowerCase()}/>
{/* Language toggle */ } -
-
+
+
+
+ Show examples in: +
{/* Header */ } -
- +
+
- {queries.desktop ? 'API Documentation' : - queries.mobile ? 'API Docs' : 'API Docs'} + {queries.desktop ? brandNames.desktop : + queries.mobile ? brandNames.mobile : brandNames.tablet}
{queries.tablet &&
{showNav &&
+ className='fixed-left keyline-top fill-dark pin-left col6 pad2 scroll-styled space-top5'> { slugger.reset(); return slugger.slug(title); }; -function highlightTokens(str) { - return str.replace(/{[\w_]+}/g, - (str) => '' + str + '') - .replace( - /{@2x}/g, - `{@2x}`); -} - -function transformURL(node) { - let { value } = node; - let parts = value.split(/\s+/); - if (parts.length === 3) { - return { - type: 'html', - value: `
-
${parts[0]}
-
${highlightTokens(parts[1])}
-
- ${parts[2]} - - Token scope - -
-
` - }; - } else { - return { - type: 'html', - value: `
-
${parts[0]}
-
${highlightTokens(parts[1])}
-
` - }; - } -} - function chunkifyAST(ast, language) { var preview = false; return ast.children.reduce((chunks, node) => { @@ -72,7 +37,9 @@ function chunkifyAST(ast, language) { right.push(node); } } else if (node.lang === 'endpoint') { - right.push(transformURL(node)); + right.push(transformURL(node.value)); + } else if (node.lang === null) { + left.push(node); } } else if (node.type === 'heading' && node.depth >= 4) { right.push(node); diff --git a/src/components/navigation.js b/src/components/navigation.js index abe4964..cf5e405 100644 --- a/src/components/navigation.js +++ b/src/components/navigation.js @@ -1,6 +1,7 @@ import React from 'react'; import PureRenderMixin from 'react-pure-render/mixin'; import NavigationItem from './navigation_item'; +import { backLink } from '../../custom'; function getAllInSectionFromChild(headings, idx) { for (var i = idx; i > 0; i--) { @@ -65,7 +66,7 @@ var Navigation = React.createClass({ if (child.depth === 1) { return (
{sectionName}
); + className='small pad0x quiet space-top1'>{sectionName}
); } else if (child.depth === 2) { return ({backLink}
); } }); diff --git a/src/components/navigation_item.js b/src/components/navigation_item.js index 2b9926c..489bf19 100644 --- a/src/components/navigation_item.js +++ b/src/components/navigation_item.js @@ -17,7 +17,7 @@ var NavigationItem = React.createClass({ return ( + className={`line-height15 pad0x pad00y quiet block ${active ? 'fill-lighten0 round' : ''}`}> {sectionName} ); } diff --git a/src/components/section.js b/src/components/section.js index 24dd70b..6dcf766 100644 --- a/src/components/section.js +++ b/src/components/section.js @@ -3,18 +3,16 @@ import remark from 'remark'; import remarkHTML from 'remark-html'; import remarkHighlight from 'remark-highlight.js'; import PureRenderMixin from 'react-pure-render/mixin'; +import { postHighlight } from '../../custom'; function renderHighlighted(nodes) { return { - __html: remark() - .use(remarkHTML) - .stringify(remark().use(remarkHighlight).run({ - type: 'root', - children: nodes - })) - .replace( - /"{timestamp}"<\/span>/g, - `"{timestamp}"`) + __html: postHighlight(remark() + .use(remarkHTML) + .stringify(remark().use(remarkHighlight).run({ + type: 'root', + children: nodes + }))) }; } @@ -28,12 +26,12 @@ var Section = React.createClass({ let { left, right, preview } = chunk; return (
+ className={`keyline-top section contain clearfix ${preview ? 'preview' : ''}`}>
{right.length > 0 &&
}
); } diff --git a/src/content.js b/src/content.js deleted file mode 100644 index 93f7886..0000000 --- a/src/content.js +++ /dev/null @@ -1,7 +0,0 @@ -var fs = require('fs'); - -module.exports = - '# Introduction\n' + - fs.readFileSync('./content/introduction.md', 'utf8') + '\n' + - '# Example\n' + - fs.readFileSync('./content/example.md', 'utf8') + '\n'; diff --git a/src/index.js b/src/index.js index f6816e6..b3018be 100644 --- a/src/index.js +++ b/src/index.js @@ -4,9 +4,11 @@ import ReactDOM from 'react-dom'; import App from './components/app'; import remark from 'remark'; import slug from 'remark-slug'; -import content from './content'; +import content from '../custom/content'; -var ast = remark().use(slug).run(remark().parse(content)); +var ast = remark() + .use(slug) + .run(remark().parse(content)); ReactDOM.render( , diff --git a/src/render.js b/src/render.js index 24ad6e8..029123e 100644 --- a/src/render.js +++ b/src/render.js @@ -3,10 +3,12 @@ import ReactDOMServer from 'react-dom/server'; import App from './components/app'; import remark from 'remark'; import slug from 'remark-slug'; -import content from './content'; +import content from '../custom/content'; import fs from 'fs'; -var ast = remark().use(slug).run(remark().parse(content)); +var ast = remark() + .use(slug) + .run(remark().parse(content)); var template = fs.readFileSync('./index.html', 'utf8');