├── .babelrc ├── .circleci └── config.yml ├── .eslintrc.json ├── .flowconfig ├── .github └── ISSUE_TEMPLATE.md ├── .gitignore ├── README.md ├── TODO.md ├── bin └── cli.js ├── components ├── chap.js ├── layout.js └── nav.js ├── dist └── bin │ └── cli.js ├── docs ├── CNAME ├── advanced │ ├── github-CNAME.md │ ├── github-pages.md │ ├── production.md │ └── styling.md ├── basic │ ├── configuration.md │ ├── getting-started.md │ └── images │ │ └── logo-250x250.png ├── examples │ └── syntax-highlight.md ├── media │ ├── flybook-flow-detail.png │ ├── flybook-flow.png │ └── logo.png ├── readme.md └── toc.yml ├── flow-typed └── flybook.js.flow ├── flyfile.js ├── index.js ├── libs ├── _document.js ├── main.js ├── md-loader.js ├── repository.js ├── routes.js ├── toc.js └── unslug.js ├── package.json ├── pages └── App.js ├── static ├── css │ ├── dark.css │ ├── flexboxgrid.min.css │ ├── github-flavored-markdown.css │ ├── light.css │ └── normalize.css └── main.js └── yarn.lock /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015", "react"] 3 | } 4 | -------------------------------------------------------------------------------- /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | jobs: 3 | build: 4 | docker: 5 | - image: circleci/node:8.1 6 | working_directory: ~/flybook 7 | steps: 8 | - checkout 9 | - restore_cache: 10 | key: dependency-cache-{{ checksum "package.json" }} 11 | - run: 12 | name: install-npm-wee 13 | command: yarn install 14 | - run: 15 | name: generate docs 16 | command: yarn docs 17 | - save_cache: 18 | key: dependency-cache-{{ checksum "package.json" }} 19 | paths: 20 | - node_modules 21 | test: 22 | steps: 23 | - checkout 24 | - run: 25 | name: lint 26 | command: yarn lint 27 | - run: 28 | name: coverage 29 | command: yarn flow:coverage 30 | 31 | workflows: 32 | version: 2 33 | build_and_test: 34 | jobs: 35 | - build 36 | - test: 37 | requires: 38 | - build 39 | filters: 40 | branches: 41 | only: feature/ci -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "babel-eslint", 3 | "env": { 4 | "node": true, 5 | "es6": true 6 | }, 7 | "parserOptions": { 8 | "ecmaVersion": 6, 9 | "sourceType": "module", 10 | "ecmaFeatures": { 11 | "jsx": true 12 | } 13 | }, 14 | "extends": [ 15 | "standard", 16 | "standard-flow", 17 | "react-app" 18 | ], 19 | "plugins": [ 20 | "flowtype", 21 | "react" 22 | ], 23 | "settings": { 24 | "flowtype": { 25 | "onlyFilesWithFlowAnnotation": false 26 | } 27 | }, 28 | "rules": { 29 | "import/no-webpack-loader-syntax": "off", 30 | "import/first": "off", 31 | "standard/no-callback-literal": "off" 32 | } 33 | } -------------------------------------------------------------------------------- /.flowconfig: -------------------------------------------------------------------------------- 1 | [ignore] 2 | .*/node_modules/* 3 | .*/__test__/.* 4 | 5 | [include] 6 | ./libs/ 7 | ./components/ 8 | ./pages/ 9 | 10 | [libs] 11 | ./flow-typed/ 12 | 13 | [options] 14 | suppress_comment= \\(.\\|\n\\)*\\$FlowFixMe -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | _Detailed Description_ 2 | 3 | ### Environment 4 | - Node Version: 5 | - OS/Arch: 6 | - Flybook Version: 7 | 8 | ### Structure of Document Root 9 | ```bash 10 | - docs 11 | \_ basic 12 | \__ a.md 13 | \_ advance 14 | \__ b.md 15 | ``` 16 | 17 | ### Error 18 | ```bash 19 | at ChildProcess.exithandler (child_process.js:205:12) 20 | at emitTwo (events.js:106:13) 21 | at ChildProcess.emit (events.js:194:7) 22 | at maybeClose (internal/child_process.js:899:16) 23 | at Process.ChildProcess._handle.onexit (internal/child_process.js:226:5) 24 | ``` 25 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | node_modules 5 | 6 | # testing 7 | coverage 8 | 9 | # production 10 | build 11 | dist 12 | 13 | # misc 14 | .DS_Store 15 | .env 16 | npm-debug.log 17 | v8.log 18 | .vscode 19 | yarn-*.log 20 | 21 | # flowtype 22 | flow-typed/npm 23 | 24 | # 25 | out 26 | out_flybook -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![](./docs/basic/images/logo-250x250.png)](https://rhiokim.github.io/flybook) 2 | 3 | Just write markdown, Flybook will create your book. 4 | 5 | `FlyBook` is a simple utility to generate static website. This is inspired by [funbook](https://funbook.js.org/) and [next.js export functionality](https://zeit.co/blog/next) 6 | 7 | Rewrited using by **React** and **React DOM Server** 8 | 9 | ## Goals 10 | Already we know that we can have a number of document tools to publish markdown docs. 11 | So flybook will keep in simplest way to generate static web site for writing the manual of project 12 | 13 | ## How flybook works 14 | ![](./docs/media/flybook-flow-detail.png) 15 | 16 | ## Features 17 | 18 | * Custom Styling (Google Fonts, Highlight.js) 19 | * Theme (only support `light` and `dark` now) 20 | * Responsive 21 | 22 | ## How to use flybook 23 | 24 | For example, Flybook documentation structure look like below 25 | ``` 26 | $ ls /path/to/project/docs 27 | docs 28 | |____advanced 29 | | \____theme.md 30 | |____basic 31 | | \____getting-started.md 32 | | \____install.md 33 | |____examples 34 | | \____syntax-highlight.md 35 | |____readme.md 36 | ``` 37 | 38 | **globally** 39 | ``` 40 | $ npm i -g flybook 41 | $ cd /path/to/project 42 | 43 | $ flybook docs 44 | > FlyBook was generated at /Users/rhio/Works/my/fly-book/out 45 | ``` 46 | 47 | **with NPM Project** 48 | ``` 49 | $ cd /path/to/project 50 | $ npm install flybook --save-dev 51 | $ 52 | $ vi package.json 53 | 54 | , 55 | "scripts": { 56 | ..., 57 | "docs": "flybook docs --outdir=out" 58 | }, 59 | ... 60 | // after save 61 | 62 | $ npm run docs 63 | ``` 64 | 65 | ## Development 66 | 67 | Turn on the auto build mode after `npm install` 68 | ``` 69 | $ git clone git@github.com:rhiokim/flybook 70 | $ cd flybook 71 | $ npm install 72 | $ npm run build 73 | ``` 74 | 75 | After that you are able to see the notification with your code changes automatically 76 | 77 | * npm run release // build 78 | * npm run docs // generate a book with newest code 79 | 80 | ## License 81 | MIT -------------------------------------------------------------------------------- /TODO.md: -------------------------------------------------------------------------------- 1 | 2 | ## TODO 3 | - [ ] Add style widget to change font style 4 | - [ ] Add style widget to change syntax highlight 5 | - using highlight.js provided cdnjs.com 6 | - [ ] Add `next` and `prev` link 7 | - [ ] Theme 8 | - [ ] PDF, EPUB 9 | - [ ] i18n -------------------------------------------------------------------------------- /bin/cli.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import { resolve, join, normalize, sep } from 'path' 3 | import { existsSync } from 'fs' 4 | import inquirer from 'inquirer' 5 | import parseArgs from 'minimist' 6 | import updateNotifier from 'update-notifier' 7 | import exportApp from '../libs/main' 8 | import { writeTOC, overwriteTOC, updateTOC } from '../libs/toc' 9 | const pkg = require(normalize('../../package.json')) 10 | 11 | var questions = [ 12 | { 13 | type: 'input', 14 | name: 'toc', 15 | message: 16 | 'There is no `toc.yml` which is table of contents file to generate static book\nPlease create table of content [Y/n]' 17 | } 18 | ] 19 | 20 | const argv = parseArgs(process.argv.slice(2), { 21 | alias: { 22 | h: 'help', 23 | s: 'silent', 24 | o: 'outdir', 25 | t: 'theme', 26 | v: 'version', 27 | p: 'prod' 28 | }, 29 | boolean: ['h', 'p', 's'], 30 | default: { 31 | s: false, 32 | o: null 33 | } 34 | }) 35 | 36 | if (argv.version) { 37 | console.log(`Flybook v${pkg.version}`) 38 | process.exit(0) 39 | } 40 | 41 | if (argv.help || !argv._[0]) { 42 | console.log( 43 | ` 44 | Description 45 | FlyBook is a simple utility to generate static website that look like online manual. 46 | 47 | Usage 48 | $ flybook [options] 49 | represents where the compiled dist folder should go. 50 | 51 | If no directory is provided, the 'out' folder will be created in the current directory. 52 | You can set a custom folder in config https://rhiokim.github.io/flybook 53 | 54 | Options 55 | -h, --help - list this help 56 | -v, --version - version of FlyBook 57 | -o, --outdir - set the output dir (defaults to 'out') 58 | -s, --silent - do not print any messages to console 59 | -t, --theme - set the theme 60 | --font - font family (default to 'Rubik|Unica+One') google fonts 61 | --codeStyle - code syntax highlight style (default to 'solarized-dark') hightlight.js 62 | ` 63 | ) 64 | process.exit(0) 65 | } 66 | 67 | if (argv._[0] === sep || argv._[0] === '.' || argv._[0] === '..') { 68 | console.log( 69 | "> FlyBook doesn't support as root directory (/), current working directory (./), and parent directory (../)" 70 | ) 71 | process.exit(1) 72 | } 73 | 74 | const dir = resolve(argv._[0] || '.') 75 | 76 | const gen = () => { 77 | const options = { 78 | docDir: dir, 79 | silent: argv.silent, 80 | prod: argv.prod, 81 | theme: argv.theme, 82 | font: argv.font, 83 | codeStyle: argv.codeStyle, 84 | outDir: normalize(argv.outdir ? resolve(argv.outdir) : resolve(dir, '..', 'out_flybook')) 85 | } 86 | 87 | updateTOC(dir) 88 | exportApp(options) 89 | } 90 | 91 | // Check if pages dir exists and warn if not 92 | if (!existsSync(dir)) { 93 | console.log(`> No such directory exists as the documentation root: ${dir}`) 94 | process.exit(1) 95 | } 96 | 97 | // No table of contents file found 98 | if (!existsSync(join(dir, 'toc.yml'))) { 99 | inquirer.prompt(questions).then(answer => { 100 | if (answer.toc === '' || answer.toc.toLowerCase() === 'y') { 101 | writeTOC(dir) 102 | gen() 103 | } else { 104 | console.log( 105 | '> No `toc.yml` file found. Did you mean to run `flybook` in the parent (`../`) directory?' 106 | ) 107 | process.exit(1) 108 | } 109 | }) 110 | } else { 111 | // `toc.yml` file found, but wannt renew 112 | if (argv.toc) { 113 | inquirer 114 | .prompt([ 115 | { 116 | type: 'input', 117 | name: 'toc', 118 | message: '`toc.yml` file found. Overwrite? [y/N]' 119 | } 120 | ]) 121 | .then(answer => { 122 | if (answer.toc === 'y') { 123 | overwriteTOC(dir) 124 | } 125 | gen() 126 | }) 127 | } else { 128 | gen() 129 | } 130 | } 131 | 132 | const notifier = updateNotifier({ pkg }) 133 | notifier.notify() 134 | 135 | // console.log(notifier.update); 136 | -------------------------------------------------------------------------------- /components/chap.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import React from 'react' 3 | import type { Children } from 'react' 4 | 5 | type Props = { 6 | children?: Children, 7 | title: string 8 | } 9 | 10 | export default ({ children, title }: Props) => 11 |
12 | {title} 13 |
    14 | {children} 15 |
16 |
17 | -------------------------------------------------------------------------------- /components/layout.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import React from 'react' 3 | import type { Children } from 'react' 4 | import classnames from 'classnames' 5 | 6 | import Nav from './nav' 7 | import Chap from './chap' 8 | 9 | type NavItem = Object & { 10 | [key: string]: string 11 | } 12 | 13 | type Author = { 14 | url?: string, 15 | name?: string 16 | } 17 | 18 | type RepositoryType = 'git' | 'svn' 19 | 20 | type Repository = 21 | | string 22 | | { 23 | type: RepositoryType, 24 | url: string 25 | } 26 | 27 | type Pkg = { 28 | name: string, 29 | homepage?: string, 30 | author?: Author, 31 | repository: Repository 32 | } 33 | 34 | type Props = { 35 | children?: Children, 36 | title: string, 37 | className?: string, 38 | toc: NavItem, 39 | pkg: Pkg, 40 | root: string 41 | } 42 | 43 | // @TODO need to improve performance 44 | const fixURI = (url: string = '') => { 45 | let arr = url.split('/') 46 | 47 | let res = arr.map(dir => { 48 | return encodeURIComponent(dir) 49 | }) 50 | 51 | return res.join('/') 52 | } 53 | 54 | export default ({ 55 | children, 56 | title = '', 57 | className, 58 | toc = {}, 59 | pkg, 60 | root = '' 61 | }: Props) => { 62 | return ( 63 |
64 | 65 |
66 |

{pkg.name}

67 |
69 | 70 |
71 | 93 |
94 | {children} 95 |
96 |
97 | 98 | {pkg.author 99 | ? 103 | : null} 104 |
105 | ) 106 | } 107 | -------------------------------------------------------------------------------- /components/nav.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import React from 'react' 3 | import repo from '../libs/repository' 4 | 5 | type ILink = { 6 | href: string, 7 | key: string, 8 | label: string 9 | } 10 | 11 | type RepositoryType = 'git' | 'svn' 12 | type Repository = 13 | | string 14 | | { 15 | type: RepositoryType, 16 | url: string 17 | } 18 | 19 | type Props = { 20 | homepage?: string, 21 | repository: Repository 22 | } 23 | 24 | const links: ILink[] = [].map((link: ILink, i: number) => { 25 | link.key = `nav-link-${i}` 26 | return link 27 | }) 28 | 29 | const Nav = ({ homepage, repository }: Props) => { 30 | const repoUrl = repo(repository) // @TODO need to split this logic from here 31 | return ( 32 | 54 | ) 55 | } 56 | 57 | export default Nav 58 | -------------------------------------------------------------------------------- /dist/bin/cli.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 'use strict'; 3 | 4 | var _path = require('path'); 5 | 6 | var _fs = require('fs'); 7 | 8 | var _inquirer = require('inquirer'); 9 | 10 | var _inquirer2 = _interopRequireDefault(_inquirer); 11 | 12 | var _minimist = require('minimist'); 13 | 14 | var _minimist2 = _interopRequireDefault(_minimist); 15 | 16 | var _updateNotifier = require('update-notifier'); 17 | 18 | var _updateNotifier2 = _interopRequireDefault(_updateNotifier); 19 | 20 | var _main = require('../libs/main'); 21 | 22 | var _main2 = _interopRequireDefault(_main); 23 | 24 | var _toc = require('../libs/toc'); 25 | 26 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 27 | 28 | var pkg = require((0, _path.normalize)('../../package.json')); 29 | 30 | var questions = [{ 31 | type: 'input', 32 | name: 'toc', 33 | message: 'There is no `toc.yml` which is table of contents file to generate static book\nPlease create table of content [Y/n]' 34 | }]; 35 | 36 | var argv = (0, _minimist2.default)(process.argv.slice(2), { 37 | alias: { 38 | h: 'help', 39 | s: 'silent', 40 | o: 'outdir', 41 | t: 'theme', 42 | v: 'version', 43 | p: 'prod' 44 | }, 45 | boolean: ['h', 'p', 's'], 46 | default: { 47 | s: false, 48 | o: null 49 | } 50 | }); 51 | 52 | if (argv.version) { 53 | console.log('Flybook v' + pkg.version); 54 | process.exit(0); 55 | } 56 | 57 | if (argv.help || !argv._[0]) { 58 | console.log('\n Description\n FlyBook is a simple utility to generate static website that look like online manual.\n\n Usage\n $ flybook [options]\n represents where the compiled dist folder should go.\n\n If no directory is provided, the \'out\' folder will be created in the current directory.\n You can set a custom folder in config https://rhiokim.github.io/flybook\n\n Options\n -h, --help - list this help\n -v, --version - version of FlyBook\n -o, --outdir - set the output dir (defaults to \'out\')\n -s, --silent - do not print any messages to console\n -t, --theme - set the theme\n --font - font family (default to \'Rubik|Unica+One\') google fonts\n --codeStyle - code syntax highlight style (default to \'solarized-dark\') hightlight.js\n '); 59 | process.exit(0); 60 | } 61 | 62 | if (argv._[0] === _path.sep || argv._[0] === '.' || argv._[0] === '..') { 63 | console.log("> FlyBook doesn't support as root directory (/), current working directory (./), and parent directory (../)"); 64 | process.exit(1); 65 | } 66 | 67 | var dir = (0, _path.resolve)(argv._[0] || '.'); 68 | 69 | var gen = function gen() { 70 | var options = { 71 | docDir: dir, 72 | silent: argv.silent, 73 | prod: argv.prod, 74 | theme: argv.theme, 75 | font: argv.font, 76 | codeStyle: argv.codeStyle, 77 | outDir: (0, _path.normalize)(argv.outdir ? (0, _path.resolve)(argv.outdir) : (0, _path.resolve)(dir, '..', 'out_flybook')) 78 | }; 79 | 80 | (0, _toc.updateTOC)(dir); 81 | (0, _main2.default)(options); 82 | }; 83 | 84 | // Check if pages dir exists and warn if not 85 | if (!(0, _fs.existsSync)(dir)) { 86 | console.log('> No such directory exists as the documentation root: ' + dir); 87 | process.exit(1); 88 | } 89 | 90 | // No table of contents file found 91 | if (!(0, _fs.existsSync)((0, _path.join)(dir, 'toc.yml'))) { 92 | _inquirer2.default.prompt(questions).then(function (answer) { 93 | if (answer.toc === '' || answer.toc.toLowerCase() === 'y') { 94 | (0, _toc.writeTOC)(dir); 95 | gen(); 96 | } else { 97 | console.log('> No `toc.yml` file found. Did you mean to run `flybook` in the parent (`../`) directory?'); 98 | process.exit(1); 99 | } 100 | }); 101 | } else { 102 | // `toc.yml` file found, but wannt renew 103 | if (argv.toc) { 104 | _inquirer2.default.prompt([{ 105 | type: 'input', 106 | name: 'toc', 107 | message: '`toc.yml` file found. Overwrite? [y/N]' 108 | }]).then(function (answer) { 109 | if (answer.toc === 'y') { 110 | (0, _toc.overwriteTOC)(dir); 111 | } 112 | gen(); 113 | }); 114 | } else { 115 | gen(); 116 | } 117 | } 118 | 119 | var notifier = (0, _updateNotifier2.default)({ pkg: pkg }); 120 | notifier.notify(); 121 | 122 | // console.log(notifier.update); -------------------------------------------------------------------------------- /docs/CNAME: -------------------------------------------------------------------------------- 1 | flybook.js.org -------------------------------------------------------------------------------- /docs/advanced/github-CNAME.md: -------------------------------------------------------------------------------- 1 | You can customize the domain name of your GitHub Pages site with Flybook. 2 | 3 | It's quite simple just add the `CNAME` file into your document root directory look like below 4 | 5 | ```shell 6 | $ ls /path/to/project/docs 7 | docs 8 | |____advanced 9 | | \____theme.md 10 | |____basic 11 | | \____getting-started.md 12 | | \____install.md 13 | |____examples 14 | | \____syntax-highlight.md 15 | |____CNAME <---- add this file 16 | |____readme.md 17 | ``` 18 | 19 | And you want to know about `CNAME` in detail. Please see the references link 20 | 21 | ### References 22 | - [Using a custom domain with GitHub Pages](https://help.github.com/articles/using-a-custom-domain-with-github-pages/) -------------------------------------------------------------------------------- /docs/advanced/github-pages.md: -------------------------------------------------------------------------------- 1 | To deploy 2 | 3 | ### Prerequsite 4 | 5 | Install two modules, `npm install flybook gh-pages --save-dev` 6 | 7 | ### Setting 8 | 9 | First add two kind of `npm script` into `package.json` file 10 | 11 | _package.json_ 12 | ```js 13 | { 14 | ..., 15 | "scripts": { 16 | "docs": "flybook docs --outdir=out", 17 | "pages": "npm run docs && gh-pages -d out" 18 | } 19 | } 20 | ``` 21 | 22 | And then run `npm run pages` after finish that open `https://[account].github.io/[project-name]` 23 | 24 | It's done. Quite simple :) -------------------------------------------------------------------------------- /docs/advanced/production.md: -------------------------------------------------------------------------------- 1 | ## Generating production pages 2 | 3 | ``` 4 | ... 5 | "scripts": { 6 | /* local */ 7 | "docs": "flybook docs --outdir=out", 8 | /* production */ 9 | "docs:prod": "flybook docs --outdir=out --prod" 10 | ... 11 | }, 12 | ``` 13 | 14 | Flybook generates static web pages on your local environment and other places such as `gh-pages` branch of your github repository or your own server. 15 | 16 | In local (= dev) environment, Flybook doesn't need any configuration. But when you deploy static files—you publish them in production (= real) level—you must set some variables in `package.json` file as well as command line option `--prod` into your command, just like the example above. 17 | 18 | See [BASIC > Configuration](../../basic/configuration) for more detailed information. 19 | -------------------------------------------------------------------------------- /docs/advanced/styling.md: -------------------------------------------------------------------------------- 1 | ## Styling 2 | 3 | ### Font 4 | [Google Fonts](https://fonts.google.com) is great service to use the web font. 5 | 6 | Basically **Flybook** work with Google's Web Fonts. So if you want to change the font for static site, please use `--font` option when run flybook commend 7 | 8 | ```js 9 | , 10 | "scripts": { 11 | "docs": "flybook docs --outdir=out --font=[GOOGLE_FONT_NAME_HERE]" 12 | }, 13 | ... 14 | ``` 15 | 16 | ### Syntax Highlighting 17 | [Highlight.js](https://highlightjs.org) is awesome open source project for code syntax highligting 18 | As you know, it being used by lots of project. Likewise **Flybook** 19 | 20 | To use this. please use `--codeStyle` option with [highlight styles](https://highlightjs.org/static/demo/) 21 | 22 | ```js 23 | , 24 | "scripts": { 25 | "docs": "flybook docs --outdir=out --codeStyle=[HIGHLIGHT_STYLE_NAME]" 26 | }, 27 | ... 28 | ``` 29 | 30 | ### Theme 31 | 32 | Only support `light` and `dark` theme 33 | 34 | ```js 35 | , 36 | "scripts": { 37 | "docs": "flybook docs --outdir=out --theme=dark" 38 | }, 39 | ... 40 | ``` 41 | 42 | ### Tutorial video 43 | 44 | -------------------------------------------------------------------------------- /docs/basic/configuration.md: -------------------------------------------------------------------------------- 1 | ## Basic Configuration 2 | 3 | Please fill out follow information into `package.json` file 4 | 5 | package.json 6 | ```js 7 | { 8 | "name": "PROJECT", 9 | "description": "PROJECT Description", 10 | "homepage": "https://rhiokim.github.com/flybook", 11 | "repository": { 12 | "url": "https://github.com/rhiokim/fly-book", 13 | "type": "git" 14 | }, 15 | "author": { 16 | "name": "Rhio Kim", 17 | "email": "rhio.kim@gmail.com", 18 | "url": "https://fly-book.github.io/" 19 | }, 20 | ... 21 | } 22 | ``` -------------------------------------------------------------------------------- /docs/basic/getting-started.md: -------------------------------------------------------------------------------- 1 | ![](images/logo-250x250.png) 2 | 3 | ## getting started 4 | 5 | 1. Wirte markdown docs 6 | 2. Run `flybook` 7 | 8 | ### Write contents 9 | 10 | Create `docs` directory and then write `basic.md` and `advanced.md` 11 | 12 | * docs/basic.md 13 | 14 | ``` 15 | ## Basic 16 | 17 | Basic Content 18 | ``` 19 | 20 | * docs/advanced.md 21 | 22 | ``` 23 | ## Advanced 24 | 25 | Advanced Content 26 | ``` 27 | 28 | ### Add npm scripts 29 | 30 | package.json 31 | 32 | ```js 33 | , 34 | "scripts": { 35 | "docs": "flybook docs" 36 | }, 37 | ... 38 | ``` 39 | 40 | and then run `npm run docs` 41 | 42 | ### Tutorial Video 43 | 44 | -------------------------------------------------------------------------------- /docs/basic/images/logo-250x250.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rhiokim/flybook/ab887213c573bdd3b0f8163a2efee39977b951ce/docs/basic/images/logo-250x250.png -------------------------------------------------------------------------------- /docs/examples/syntax-highlight.md: -------------------------------------------------------------------------------- 1 | **apache** 2 | ```apache 3 | # rewrite`s rules for wordpress pretty url 4 | LoadModule rewrite_module modules/mod_rewrite.so 5 | RewriteCond %{REQUEST_FILENAME} !-f 6 | RewriteCond %{REQUEST_FILENAME} !-d 7 | RewriteRule . index.php [NC,L] 8 | 9 | ExpiresActive On 10 | ExpiresByType application/x-javascript "access plus 1 days" 11 | 12 | Order Deny,Allow 13 | Allow from All 14 | 15 | 16 | RewriteMap map txt:map.txt 17 | RewriteMap lower int:tolower 18 | RewriteCond %{REQUEST_URI} ^/([^/.]+)\.html$ [NC] 19 | RewriteCond ${map:${lower:%1}|NOT_FOUND} !NOT_FOUND 20 | RewriteRule .? /index.php?q=${map:${lower:%1}} [NC,L] 21 | 22 | 23 | ``` 24 | 25 | **bash** 26 | ```bash 27 | #!/bin/bash 28 | 29 | ###### CONFIG 30 | ACCEPTED_HOSTS="/root/.hag_accepted.conf" 31 | BE_VERBOSE=false 32 | 33 | if [ "$UID" -ne 0 ] 34 | then 35 | echo "Superuser rights required" 36 | exit 2 37 | fi 38 | 39 | genApacheConf(){ 40 | echo -e "# Host ${HOME_DIR}$1/$2 :" 41 | } 42 | 43 | ``` 44 | 45 | **coffeescript** 46 | ```coffeescript 47 | grade = (student, period=(if b? then 7 else 6)) -> 48 | if student.excellentWork 49 | "A+" 50 | else if student.okayStuff 51 | if student.triedHard then "B" else "B-" 52 | else 53 | "C" 54 | 55 | class Animal extends Being 56 | constructor: (@name) -> 57 | 58 | move: (meters) -> 59 | alert @name + " moved #{meters}m." 60 | 61 | ``` 62 | 63 | **cpp** 64 | ```cpp 65 | #include 66 | 67 | int main(int argc, char *argv[]) { 68 | 69 | /* An annoying "Hello World" example */ 70 | for (auto i = 0; i < 0xFFFF; i++) 71 | cout << "Hello, World!" << endl; 72 | 73 | char c = '\n'; 74 | unordered_map > m; 75 | m["key"] = "\\\\"; // this is an error 76 | 77 | return -2e3 + 12l; 78 | } 79 | 80 | ``` 81 | 82 | **cs** 83 | ```cs 84 | using System.IO.Compression; 85 | 86 | #pragma warning disable 414, 3021 87 | 88 | namespace MyApplication 89 | { 90 | [Obsolete("...")] 91 | class Program : IInterface 92 | { 93 | public static List JustDoIt(int count) 94 | { 95 | Console.WriteLine($"Hello {Name}!"); 96 | return new List(new int[] { 1, 2, 3 }) 97 | } 98 | } 99 | } 100 | 101 | ``` 102 | 103 | **css** 104 | ```css 105 | @font-face { 106 | font-family: Chunkfive; src: url('Chunkfive.otf'); 107 | } 108 | 109 | body, .usertext { 110 | color: #F0F0F0; background: #600; 111 | font-family: Chunkfive, sans; 112 | } 113 | 114 | @import url(print.css); 115 | @media print { 116 | a[href^=http]::after { 117 | content: attr(href) 118 | } 119 | } 120 | 121 | ``` 122 | 123 | **diff** 124 | ```diff 125 | Index: languages/ini.js 126 | =================================================================== 127 | --- languages/ini.js (revision 199) 128 | +++ languages/ini.js (revision 200) 129 | @@ -1,8 +1,7 @@ 130 | .LANGUAGES.ini = 131 | { 132 | case_insensitive: true, 133 | - defaultMode: 134 | - { 135 | + defaultMode: { 136 | contains: ['comment', 'title', 'setting'], 137 | illegal: '[^\\s]' 138 | }, 139 | 140 | *** /path/to/original timestamp 141 | --- /path/to/new timestamp 142 | *************** 143 | *** 1,3 **** 144 | --- 1,9 ---- 145 | + This is an important 146 | + notice! It should 147 | + therefore be located at 148 | + the beginning of this 149 | + document! 150 | 151 | ! compress the size of the 152 | ! changes. 153 | 154 | It is important to spell 155 | 156 | ``` 157 | 158 | **http** 159 | ```http 160 | POST /task?id=1 HTTP/1.1 161 | Host: example.org 162 | Content-Type: application/json; charset=utf-8 163 | Content-Length: 137 164 | 165 | { 166 | "status": "ok", 167 | "extended": true, 168 | "results": [ 169 | {"value": 0, "type": "int64"}, 170 | {"value": 1.0e+3, "type": "decimal"} 171 | ] 172 | } 173 | 174 | ``` 175 | 176 | **ini** 177 | ```ini 178 | ; boilerplate 179 | [package] 180 | name = "some_name" 181 | authors = ["Author"] 182 | description = "This is \ 183 | a description" 184 | 185 | [[lib]] 186 | name = ${NAME} 187 | default = True 188 | auto = no 189 | counter = 1_000 190 | 191 | ``` 192 | 193 | **java** 194 | ```java 195 | /** 196 | * @author John Smith 197 | */ 198 | package l2f.gameserver.model; 199 | 200 | public abstract class L2Char extends L2Object { 201 | public static final Short ERROR = 0x0001; 202 | 203 | public void moveTo(int x, int y, int z) { 204 | _ai = null; 205 | log("Should not be called"); 206 | if (1 > 5) { // wtf!? 207 | return; 208 | } 209 | } 210 | } 211 | 212 | ``` 213 | 214 | **javascript** 215 | ```javascript 216 | function $initHighlight(block, cls) { 217 | try { 218 | if (cls.search(/\bno\-highlight\b/) != -1) 219 | return process(block, true, 0x0F) + 220 | ` class="${cls}"`; 221 | } catch (e) { 222 | /* handle exception */ 223 | } 224 | for (var i = 0 / 2; i < classes.length; i++) { 225 | if (checkCondition(classes[i]) === undefined) 226 | console.log('undefined'); 227 | } 228 | } 229 | 230 | export $initHighlight; 231 | 232 | ``` 233 | 234 | **json** 235 | ```json 236 | [ 237 | { 238 | "title": "apples", 239 | "count": [12000, 20000], 240 | "description": {"text": "...", "sensitive": false} 241 | }, 242 | { 243 | "title": "oranges", 244 | "count": [17500, null], 245 | "description": {"text": "...", "sensitive": false} 246 | } 247 | ] 248 | 249 | ``` 250 | 251 | **makefile** 252 | ```makefile 253 | # Makefile 254 | 255 | BUILDDIR = _build 256 | EXTRAS ?= $(BUILDDIR)/extras 257 | 258 | .PHONY: main clean 259 | 260 | main: 261 | @echo "Building main facility..." 262 | build_main $(BUILDDIR) 263 | 264 | clean: 265 | rm -rf $(BUILDDIR)/* 266 | 267 | ``` 268 | 269 | **markdown** 270 | ```markdown 271 | # hello world 272 | 273 | you can write text [with links](http://example.com) inline or [link references][1]. 274 | 275 | * one _thing_ has *em*phasis 276 | * two __things__ are **bold** 277 | 278 | [1]: http://example.com 279 | 280 | --- 281 | 282 | hello world 283 | =========== 284 | 285 | 286 | 287 | > markdown is so cool 288 | 289 | so are code segments 290 | 291 | 1. one thing (yeah!) 292 | 2. two thing `i can write code`, and `more` wipee! 293 | 294 | 295 | ``` 296 | 297 | **nginx** 298 | ```nginx 299 | user www www; 300 | worker_processes 2; 301 | pid /var/run/nginx.pid; 302 | error_log /var/log/nginx.error_log debug | info | notice | warn | error | crit; 303 | 304 | events { 305 | connections 2000; 306 | use kqueue | rtsig | epoll | /dev/poll | select | poll; 307 | } 308 | 309 | http { 310 | log_format main '$remote_addr - $remote_user [$time_local] ' 311 | '"$request" $status $bytes_sent ' 312 | '"$http_referer" "$http_user_agent" ' 313 | '"$gzip_ratio"'; 314 | 315 | send_timeout 3m; 316 | client_header_buffer_size 1k; 317 | 318 | gzip on; 319 | gzip_min_length 1100; 320 | 321 | #lingering_time 30; 322 | 323 | server { 324 | server_name one.example.com www.one.example.com; 325 | access_log /var/log/nginx.access_log main; 326 | 327 | rewrite (.*) /index.php?page=$1 break; 328 | 329 | location / { 330 | proxy_pass http://127.0.0.1/; 331 | proxy_redirect off; 332 | proxy_set_header Host $host; 333 | proxy_set_header X-Real-IP $remote_addr; 334 | charset koi8-r; 335 | } 336 | 337 | location /api/ { 338 | fastcgi_pass 127.0.0.1:9000; 339 | } 340 | 341 | location ~* \.(jpg|jpeg|gif)$ { 342 | root /spool/www; 343 | } 344 | } 345 | } 346 | 347 | ``` 348 | 349 | **objectivec** 350 | ```objectivec 351 | #import 352 | #import "Dependency.h" 353 | 354 | @protocol WorldDataSource 355 | @optional 356 | - (NSString*)worldName; 357 | @required 358 | - (BOOL)allowsToLive; 359 | @end 360 | 361 | @property (nonatomic, readonly) NSString *title; 362 | - (IBAction) show; 363 | @end 364 | 365 | ``` 366 | 367 | **perl** 368 | ```perl 369 | # loads object 370 | sub load 371 | { 372 | my $flds = $c->db_load($id,@_) || do { 373 | Carp::carp "Can`t load (class: $c, id: $id): '$!'"; return undef 374 | }; 375 | my $o = $c->_perl_new(); 376 | $id12 = $id / 24 / 3600; 377 | $o->{'ID'} = $id12 + 123; 378 | #$o->{'SHCUT'} = $flds->{'SHCUT'}; 379 | my $p = $o->props; 380 | my $vt; 381 | $string =~ m/^sought_text$/; 382 | $items = split //, 'abc'; 383 | $string //= "bar"; 384 | for my $key (keys %$p) 385 | { 386 | if(${$vt.'::property'}) { 387 | $o->{$key . '_real'} = $flds->{$key}; 388 | tie $o->{$key}, 'CMSBuilder::Property', $o, $key; 389 | } 390 | } 391 | $o->save if delete $o->{'_save_after_load'}; 392 | 393 | # GH-117 394 | my $g = glob("/usr/bin/*"); 395 | 396 | return $o; 397 | } 398 | 399 | __DATA__ 400 | @@ layouts/default.html.ep 401 | 402 | 403 | <%= title %> 404 | <%= content %> 405 | 406 | __END__ 407 | 408 | =head1 NAME 409 | POD till the end of file 410 | 411 | ``` 412 | 413 | **php** 414 | ```php 415 | require_once 'Zend/Uri/Http.php'; 416 | 417 | namespace Location\Web; 418 | 419 | interface Factory 420 | { 421 | static function _factory(); 422 | } 423 | 424 | abstract class URI extends BaseURI implements Factory 425 | { 426 | abstract function test(); 427 | 428 | public static $st1 = 1; 429 | const ME = "Yo"; 430 | var $list = NULL; 431 | private $var; 432 | 433 | /** 434 | * Returns a URI 435 | * 436 | * @return URI 437 | */ 438 | static public function _factory($stats = array(), $uri = 'http') 439 | { 440 | echo __METHOD__; 441 | $uri = explode(':', $uri, 0b10); 442 | $schemeSpecific = isset($uri[1]) ? $uri[1] : ''; 443 | $desc = 'Multi 444 | line description'; 445 | 446 | // Security check 447 | if (!ctype_alnum($scheme)) { 448 | throw new Zend_Uri_Exception('Illegal scheme'); 449 | } 450 | 451 | $this->var = 0 - self::$st; 452 | $this->list = list(Array("1"=> 2, 2=>self::ME, 3 => \Location\Web\URI::class)); 453 | 454 | return [ 455 | 'uri' => $uri, 456 | 'value' => null, 457 | ]; 458 | } 459 | } 460 | 461 | echo URI::ME . URI::$st1; 462 | 463 | __halt_compiler () ; datahere 464 | datahere 465 | datahere */ 466 | datahere 467 | 468 | ``` 469 | 470 | **python** 471 | ```python 472 | @requires_authorization 473 | def somefunc(param1='', param2=0): 474 | r'''A docstring''' 475 | if param1 > param2: # interesting 476 | print 'Gre\'ater' 477 | return (param2 - param1 + 1 + 0b10l) or None 478 | 479 | class SomeClass: 480 | pass 481 | 482 | >>> message = '''interpreter 483 | ... prompt''' 484 | 485 | ``` 486 | 487 | **ruby** 488 | ```ruby 489 | # The Greeter class 490 | class Greeter 491 | def initialize(name) 492 | @name = name.capitalize 493 | end 494 | 495 | def salute 496 | puts "Hello #{@name}!" 497 | end 498 | end 499 | 500 | g = Greeter.new("world") 501 | g.salute 502 | 503 | ``` 504 | 505 | **shell** 506 | ```shell 507 | $ echo $EDITOR 508 | vim 509 | $ git checkout master 510 | Switched to branch 'master' 511 | Your branch is up-to-date with 'origin/master'. 512 | $ git push 513 | Everything up-to-date 514 | $ echo 'All 515 | > done!' 516 | All 517 | done! 518 | 519 | ``` 520 | 521 | **sql** 522 | ```sql 523 | CREATE TABLE "topic" ( 524 | "id" serial NOT NULL PRIMARY KEY, 525 | "forum_id" integer NOT NULL, 526 | "subject" varchar(255) NOT NULL 527 | ); 528 | ALTER TABLE "topic" 529 | ADD CONSTRAINT forum_id FOREIGN KEY ("forum_id") 530 | REFERENCES "forum" ("id"); 531 | 532 | -- Initials 533 | insert into "topic" ("forum_id", "subject") 534 | values (2, 'D''artagnian'); 535 | 536 | ``` 537 | 538 | **xml** 539 | ```xml 540 | 541 | Title 542 | 543 | 544 | 545 | 548 | 549 | 550 |

Title

551 | 552 | 553 | 554 | ``` -------------------------------------------------------------------------------- /docs/media/flybook-flow-detail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rhiokim/flybook/ab887213c573bdd3b0f8163a2efee39977b951ce/docs/media/flybook-flow-detail.png -------------------------------------------------------------------------------- /docs/media/flybook-flow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rhiokim/flybook/ab887213c573bdd3b0f8163a2efee39977b951ce/docs/media/flybook-flow.png -------------------------------------------------------------------------------- /docs/media/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rhiokim/flybook/ab887213c573bdd3b0f8163a2efee39977b951ce/docs/media/logo.png -------------------------------------------------------------------------------- /docs/readme.md: -------------------------------------------------------------------------------- 1 | ![](./basic/images/logo-250x250.png) 2 | 3 | Just write markdown, Flybook will create your book. 4 | 5 | `FlyBook` is a simple utility to generate static website. This is inspired by [funbook](https://funbook.js.org/) and [next.js export functionality](https://zeit.co/blog/next) 6 | 7 | Rewrited using by **React** and **React DOM Server** 8 | 9 | ## Goals 10 | Already we know that we can have a number of document tools to publish markdown docs. 11 | So flybook will keep in simplest way to generate static web site for writing the manual of project 12 | 13 | ## How to use flybook 14 | 15 | For example, Flybook documentation structure look like below 16 | ```shell 17 | $ ls /path/to/project/docs 18 | docs 19 | |____advanced 20 | | \____theme.md 21 | |____basic 22 | | \____getting-started.md 23 | | \____install.md 24 | |____examples 25 | | \____syntax-highlight.md 26 | |____readme.md 27 | ``` 28 | 29 | **globally** 30 | ```shell 31 | $ npm i -g flybook 32 | $ cd /path/to/project 33 | 34 | $ flybook docs 35 | > FlyBook was generated at /Users/rhio/Works/my/fly-book/out 36 | ``` 37 | 38 | **with NPM Project** 39 | ```shell 40 | $ cd /path/to/project 41 | $ npm install flybook --save-dev 42 | $ 43 | $ vi package.json 44 | 45 | , 46 | "scripts": { 47 | ..., 48 | "docs": "flybook docs --outdir=out" 49 | }, 50 | ... 51 | // after save 52 | 53 | $ npm run docs 54 | ``` 55 | 56 | ## Development 57 | 58 | Turn on the auto build mode after `npm install` 59 | ``` 60 | $ git clone git@github.com:rhiokim/flybook 61 | $ cd flybook 62 | $ npm install 63 | $ npm run build 64 | ``` 65 | 66 | After that you are able to see the notification with your code changes automatically 67 | 68 | ```shell 69 | $ npm run release # build 70 | $ npm run docs # generate a book with newest code 71 | ``` 72 | 73 | ## License 74 | MIT -------------------------------------------------------------------------------- /docs/toc.yml: -------------------------------------------------------------------------------- 1 | --- 2 | Basic: 3 | Getting Started: "basic/getting-started.md" 4 | Configuration: "basic/configuration.md" 5 | Advanced: 6 | Styling: "advanced/styling.md" 7 | Github Pages: "advanced/github-pages.md" 8 | Production: "advanced/production.md" 9 | Github Cname: "advanced/github-CNAME.md" 10 | Examples: 11 | Syntax Highlight: "examples/syntax-highlight.md" 12 | -------------------------------------------------------------------------------- /flow-typed/flybook.js.flow: -------------------------------------------------------------------------------- 1 | declare class RegExpMatchArray extends Array { 2 | index: number; 3 | input: string; 4 | } 5 | -------------------------------------------------------------------------------- /flyfile.js: -------------------------------------------------------------------------------- 1 | const notifier = require('node-notifier') 2 | 3 | export async function compile (fly) { 4 | await fly.parallel(['bin', 'libs', 'components']) 5 | } 6 | 7 | export async function bin (fly, opts) { 8 | await fly 9 | .source(opts.src || 'bin/*') 10 | .babel() 11 | .target('dist/bin', { mode: '0755' }) 12 | notify('Compiled binaries') 13 | } 14 | 15 | export async function components (fly, opts) { 16 | await fly 17 | .source(opts.src || 'components/*') 18 | .babel({ babelrc: true }) 19 | .target('dist/components') 20 | notify('Compiled components') 21 | } 22 | 23 | export async function libs (fly, opts) { 24 | await fly.source(opts.src || 'libs/**/*.js').babel().target('dist/libs') 25 | notify('Compiled lib files') 26 | } 27 | 28 | export async function copy (fly) { 29 | await fly 30 | .source('pages/**/*.js') 31 | .babel({ babelrc: true }) 32 | .target('dist/pages') 33 | notify('Compiled page files and Copied package.json') 34 | } 35 | 36 | export async function build (fly) { 37 | await fly.serial(['copy', 'compile']) 38 | } 39 | 40 | export default async function (fly) { 41 | await fly.start('build') 42 | await fly.watch('bin/*', 'bin') 43 | await fly.watch('components/*', 'components') 44 | await fly.watch('libs/**/*.js', ['libs']) 45 | await fly.watch('pages/**/*.js', 'copy') 46 | } 47 | 48 | export async function release (fly) { 49 | await fly.clear('dist').start('build') 50 | } 51 | 52 | // notification helper 53 | function notify (msg) { 54 | return notifier.notify({ 55 | title: 'Next-Book', 56 | message: msg, 57 | icon: false 58 | }) 59 | } 60 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | require('babel-register') 2 | module.exports = require('./lib/main') 3 | -------------------------------------------------------------------------------- /libs/_document.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import crypto from 'crypto' 3 | 4 | const rand: string = crypto.randomBytes(8).toString('hex') 5 | const defaultDescription = '' 6 | // const defaultOGURL = '' 7 | // const defaultOGImage = '' 8 | 9 | type Props = { 10 | body: string, 11 | title: string, 12 | root: string, 13 | theme?: string, 14 | font?: string, 15 | codeStyle?: string 16 | } 17 | 18 | export default ({ 19 | body, 20 | title, 21 | root = '', 22 | theme = 'light', 23 | font = 'Rubik|Unica+One', 24 | codeStyle = 'solarized-dark' 25 | }: Props) => { 26 | return ` 27 | 28 | 29 | 30 | ${title} 31 | 35 | 36 | 37 | 41 | 42 | 43 | 44 | 45 | 46 | 51 | 52 | 53 | 54 |
${body}
55 | 56 | 57 | ` 58 | } 59 | -------------------------------------------------------------------------------- /libs/main.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import { writeFileSync, existsSync, copySync } from 'fs-extra' 3 | import { join, relative, dirname, normalize, sep } from 'path' 4 | import glob from 'glob' 5 | import copy from 'recursive-copy' 6 | import del from 'del' 7 | import mkdirp from 'mkdirp' 8 | import deepAssign from 'deep-assign' 9 | import { createElement } from 'react' 10 | import { renderToStaticMarkup } from 'react-dom/server' 11 | 12 | import routeTable from './routes' 13 | import mdLoader from './md-loader' 14 | import _document from './_document' 15 | import App from '../pages/App' 16 | 17 | type Props = { 18 | /* document root */ 19 | docDir: string, 20 | /* out directory which is generated static files */ 21 | outDir: string, 22 | /* disable log */ 23 | silent: boolean, 24 | /* when you deploy on not local env */ 25 | prod: boolean, 26 | /* theme - now `dark` and `light` only */ 27 | theme: string, 28 | /* font - provided by google font only */ 29 | font?: string, 30 | /* syntax highlight - provided by highlight.js */ 31 | codeStyle?: string 32 | } 33 | 34 | let pkg = { 35 | name: 'Flybook', 36 | description: 'Flybook', 37 | homepage: '', 38 | author: {}, 39 | repository: { 40 | type: 'git', 41 | url: '' 42 | } 43 | } 44 | 45 | try { 46 | // https://wietse.loves.engineering/ignore-a-flowtype-error-on-a-specific-line-14cdfa70a739 47 | // $FlowFixMe 48 | pkg = deepAssign(pkg, require(join(process.cwd(), 'package.json').toString())) 49 | } catch (e) {} 50 | 51 | const Html = ( 52 | title: string, 53 | contents: string = '', 54 | root: string, 55 | routes: any, 56 | theme?: string, 57 | font?: string, 58 | codeStyle?: string 59 | ) => { 60 | return _document({ 61 | title, 62 | root, 63 | theme, 64 | font, 65 | codeStyle, 66 | body: renderToStaticMarkup(createElement(App, { title, contents, toc: routes, pkg, root })) 67 | }) 68 | } 69 | 70 | const makeIndexPage = ( 71 | docDir: string, 72 | outDir: string, 73 | routes: any, 74 | theme?: string, 75 | font?: string, 76 | codeStyle?: string 77 | ) => { 78 | let contents: string 79 | let html: string 80 | 81 | const loadIndex = (docDir: string) => { 82 | contents = mdLoader(join(docDir, 'readme.md')) 83 | return contents 84 | } 85 | 86 | if (!existsSync(join(docDir, 'readme.md'))) { 87 | if (!existsSync(join(process.cwd(), 'readme.md'))) { 88 | console.log(`> No 'readme.md' file found in ${docDir} and project root`) 89 | process.exit(1) 90 | } else { 91 | contents = loadIndex(process.cwd()) 92 | } 93 | } else { 94 | contents = loadIndex(docDir) 95 | } 96 | 97 | html = Html(pkg.name, contents, '', routes, theme, font, codeStyle) 98 | 99 | /* gen new files */ 100 | writeFileSync(join(outDir, 'index.html'), html, { encoding: 'utf8' }) 101 | } 102 | 103 | export default ({ docDir, outDir, silent, prod, theme, font, codeStyle }: Props) => { 104 | const routes: { [key: string]: any } = routeTable(docDir) || {} 105 | 106 | // if not production mode, homepage is '/' path 107 | if (!prod) { 108 | pkg.homepage = '/' 109 | } 110 | 111 | /* clean previous files */ 112 | del.sync([join(outDir, '**', '*')]) 113 | 114 | // create output directory 115 | mkdirp.sync(outDir) 116 | 117 | // make index page 118 | makeIndexPage(docDir, outDir, routes, theme, font, codeStyle) 119 | 120 | Object.keys(routes).forEach(key => { 121 | const subRoutes = routes[key] || {} 122 | 123 | for (let title in subRoutes) { 124 | let file = subRoutes[title] 125 | 126 | let outputDir = join(outDir, dirname(file)) 127 | let outputFile = join(outDir, file.replace(/\.md$/, '.html')) 128 | let contents = mdLoader(join(docDir, file)) 129 | let relativePath = relative(outputDir, outDir) 130 | relativePath = relativePath === '' ? '.' : relativePath 131 | 132 | let html = Html(title, contents, relativePath + sep, routes, theme, font, codeStyle) 133 | 134 | /* mkdir output dir */ 135 | mkdirp.sync(outputDir) 136 | 137 | /* gen new files */ 138 | writeFileSync(outputFile, html, { encoding: 'utf8' }) 139 | 140 | // log 141 | if (!silent) { 142 | console.log('>', key, '-', title, '\n', outputFile) 143 | } 144 | } 145 | }) 146 | 147 | /* copy static assets */ 148 | copy(join(__dirname, '..', '..', 'static'), join(outDir, 'static')).then(result => { 149 | console.log(`> FlyBook was generated at ${outDir}`) 150 | }) 151 | 152 | /* copy assets which is used in docs */ 153 | const files: string[] = glob.sync( 154 | join(docDir, '**', '!(*.md|*.markdown|*.mdown|*.mkdn|*.mkd|*.mdwn|*.mkdown|toc.yml)'), 155 | { 156 | ignore: [join(docDir, 'node_modules', '**')], 157 | nodir: true 158 | } 159 | ) 160 | files.map(file => normalize(file)).forEach(file => { 161 | copySync(file, file.replace(docDir, outDir)) 162 | }) 163 | } 164 | -------------------------------------------------------------------------------- /libs/md-loader.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import fs from 'fs' 3 | import remark from 'remark' 4 | import html from 'remark-html' 5 | import hljs from 'remark-highlight.js' 6 | import slug from 'remark-slug' 7 | import headding from 'remark-autolink-headings' 8 | 9 | const app = remark() 10 | app.use([slug, headding, html]) 11 | 12 | export default (file: string): string => { 13 | let markdown: string = fs.readFileSync(file).toString('utf8') 14 | const { contents } = app.processSync(markdown) 15 | 16 | return contents 17 | } 18 | -------------------------------------------------------------------------------- /libs/repository.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | type RepositoryType = 'git' | 'svn' 4 | 5 | type Repository = 6 | | string 7 | | { 8 | type: RepositoryType, 9 | url: string 10 | } 11 | 12 | const isShortcut = (url: string): string => { 13 | let repo: ?RegExpMatchArray | ?Array 14 | const re = /(gist|bitbucket|gitlab):([\w]+)+(\/[\w]+)*/ 15 | repo = url.match(re) 16 | 17 | if (!repo) { 18 | return `https://github.com/${url}` 19 | } else { 20 | switch (repo[1]) { 21 | case 'bitbucket': 22 | return `https://bitbucket.org/${repo[2]}${repo[3]}` 23 | case 'gitlab': 24 | return `https://gitlab.com/${repo[2]}${repo[3]}` 25 | default: 26 | return `https://gist.github.com/${repo[2]}` 27 | } 28 | } 29 | } 30 | 31 | const isObject = (obj: { type: RepositoryType, url: string }): string => { 32 | let repo: ?RegExpMatchArray | ?Array 33 | const re: RegExp = /((git|ssh|http(s)?)|(git@[\w\.]+))(:(\/\/)?)([\w\.@\:/\-~]+)(\.git)(\/)?/ 34 | 35 | repo = obj.url.match(re) 36 | 37 | if (!repo) { 38 | return '' 39 | } else { 40 | return `https://${repo[7]}` 41 | } 42 | } 43 | 44 | export default (repo: Repository) => { 45 | // let url = typeof repo === 'object' ? repo.url : repo 46 | // let _repo: ?RegExpMatchArray | ?Array 47 | 48 | // if (typeof repo === 'object') { 49 | // url = repo.url 50 | // } 51 | if (typeof repo === 'string') { 52 | return isShortcut(repo) 53 | } else { 54 | return isObject(repo) 55 | // _repo = repo.url.match(re) 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /libs/routes.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import { readFileSync } from 'fs' 3 | import { join } from 'path' 4 | import { safeLoad } from 'js-yaml' 5 | 6 | type Routes = { 7 | [key: string]: string 8 | } 9 | 10 | export default (targetDir: string) => { 11 | let routes 12 | let routesObj: ?Routes 13 | const file = join(targetDir, 'toc.yml') 14 | 15 | try { 16 | routes = readFileSync(file, 'utf8') 17 | routesObj = (safeLoad(routes): ?Routes) 18 | } catch (e) { 19 | throw new Error(`> No such toc.yml file exists as the document root: ${file}`) 20 | } 21 | 22 | return routesObj 23 | } 24 | -------------------------------------------------------------------------------- /libs/toc.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import fs from 'fs' 3 | import { join, normalize, dirname, basename } from 'path' 4 | import glob from 'glob' 5 | import del from 'del' 6 | import titleize from 'titleize' 7 | import j2y from 'json2yaml' 8 | import yaml from 'js-yaml' 9 | import deepAssign from 'deep-assign' 10 | import unslug from './unslug' 11 | 12 | type Obj = { 13 | [key: string]: any 14 | } 15 | 16 | // simple object deep compare 17 | const compare = (obj1: Obj, obj2: Obj) => { 18 | return JSON.stringify(obj1) === JSON.stringify(obj2) 19 | } 20 | 21 | // remove properties of source object that dest object have not attribute 22 | const clean = (source: Obj, dest: Obj) => { 23 | for (let attr in source) { 24 | if (typeof source[attr] !== 'object') { 25 | if (!dest.hasOwnProperty(attr)) { 26 | delete source[attr] 27 | } 28 | } else { 29 | clean(source[attr], dest[attr]) 30 | } 31 | } 32 | 33 | return source 34 | } 35 | 36 | // clean merge after deep assign 37 | const cleanMerge = (source: Obj, dest: Obj) => { 38 | let deep = deepAssign({}, source, dest) 39 | deep = clean(deep, dest) 40 | 41 | return deep 42 | } 43 | 44 | const gen = (docDir: string) => { 45 | let toc: Obj = {} 46 | // const files: string[] = glob.sync(join(docDir, '**', '*.md'), { 47 | const files: string[] = glob.sync(join(docDir, '**', '*.*'), { 48 | ignore: [join(docDir, 'node_modules', '**')], 49 | nodir: true 50 | }) 51 | 52 | files 53 | .filter(file => /\.(md|markdown|mdown|mkdn|mkd|mdwn|mkdown)$/.test(file)) 54 | .map(file => normalize(file)) 55 | .filter(file => { 56 | return file !== join(docDir, 'readme.md') // basically readme.md is index file of directory 57 | }) 58 | .forEach(file => { 59 | file = file.replace(docDir, '').substr(1, file.length) 60 | const dir: string = unslug(dirname(file)) 61 | const name: string = unslug(basename(file).replace(/\.md$/, '')) 62 | 63 | /* 64 | * Parent Directory: 65 | * Child Directory: File Path 66 | * 67 | * Basic: 68 | * Getting Started: 'basic/getting-started.md' 69 | * 70 | */ 71 | if (toc.hasOwnProperty(dir)) { 72 | toc[dir] = Object.assign(toc[dir], { 73 | [titleize(name)]: file 74 | }) 75 | } else { 76 | toc = Object.assign(toc, { 77 | [titleize(dir)]: { 78 | [titleize(name)]: file 79 | } 80 | }) 81 | } 82 | }) 83 | 84 | return toc 85 | } 86 | 87 | export const updateTOC = (docDir: string) => { 88 | const tocFile = join(docDir, 'toc.yml') 89 | const has = fs.existsSync(tocFile) 90 | const json = gen(docDir) 91 | 92 | if (has) { 93 | let toc = fs.readFileSync(tocFile, 'utf8') 94 | let tocJson: Obj = yaml.safeLoad(toc) 95 | 96 | if (!compare(tocJson, json)) { 97 | save(tocFile, cleanMerge(tocJson, json)) 98 | } 99 | } 100 | } 101 | 102 | const save = (file: string, json: Obj) => { 103 | fs.writeFileSync(file, j2y.stringify(json), { 104 | encoding: 'utf8' 105 | }) 106 | } 107 | 108 | export const writeTOC = (docDir: string) => { 109 | const json = gen(docDir) 110 | const out = join(docDir, 'toc.yml') 111 | 112 | save(out, json) 113 | } 114 | 115 | export const overwriteTOC = (docDir: string) => { 116 | const json = gen(docDir) 117 | const out = join(docDir, 'toc.yml') 118 | 119 | del.sync([out]) 120 | save(out, json) 121 | } 122 | -------------------------------------------------------------------------------- /libs/unslug.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | export default (slugs: string) => { 3 | slugs = slugs.replace(/_/g, '-') 4 | slugs = slugs.replace(/--/g, '-') 5 | 6 | var list: string[] = [] 7 | slugs.split('-').forEach((slug: string) => { 8 | list.push(slug.substr(0, 1).toUpperCase() + slug.substr(1)) 9 | }) 10 | 11 | return list.join(' ') 12 | } 13 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "flybook", 3 | "version": "1.4.4", 4 | "main": "./dist/app.js", 5 | "license": "MIT", 6 | "homepage": "https://rhiokim.github.io/flybook", 7 | "repository": { 8 | "url": "https://github.com/rhiokim/flybook.git", 9 | "type": "git" 10 | }, 11 | "author": { 12 | "name": "Rhio Kim", 13 | "email": "rhio.kim@gmail.com", 14 | "url": "https://flybook.github.io/" 15 | }, 16 | "engines": { 17 | "node": ">=4.0.0" 18 | }, 19 | "scripts": { 20 | "build": "fly", 21 | "release": "fly release", 22 | "serve": "serve out -s -p 5002", 23 | "docs": "./dist/bin/cli.js docs --outdir=out --font=Overpass --codeStyle=tomorrow-night", 24 | "docs:prod": "./dist/bin/cli.js docs --outdir=out --prod --font=Overpass --codeStyle=solarized-light --theme=light", 25 | "pages": "npm run docs:prod && gh-pages -d out", 26 | "lint": "eslint --format=node_modules/eslint-formatter-pretty 'libs/**/*.@(js|jsx)'", 27 | "lint:msg": "validate-commit-msg", 28 | "format": "prettier-eslint --write --print-width 120 'libs/**/*.@(js|jsx)'", 29 | "flow": "flow", 30 | "flow:coverage": "flow-coverage-report", 31 | "cz": "git cz", 32 | "precommit": "flow && lint-staged", 33 | "commitmsg": "npm run lint:msg", 34 | "changelog": "conventional-changelog -p angular -i CHANGELOG.md -s -r 1" 35 | }, 36 | "dependencies": { 37 | "babel-register": "^6.24.1", 38 | "classnames": "^2.2.5", 39 | "deep-assign": "^2.0.0", 40 | "del": "^3.0.0", 41 | "eslint-plugin-node": "^5.0.0", 42 | "fs-extra": "^3.0.1", 43 | "glob": "^7.1.2", 44 | "inquirer": "^3.1.0", 45 | "js-yaml": "^3.8.4", 46 | "json2yaml": "^1.1.0", 47 | "minimist": "^1.2.0", 48 | "mkdirp": "^0.5.1", 49 | "react": "^15.6.0", 50 | "react-dom": "^15.6.0", 51 | "recursive-copy": "^2.0.6", 52 | "remark": "^7.0.1", 53 | "remark-autolink-headings": "^5.0.0", 54 | "remark-highlight.js": "^5.0.0", 55 | "remark-html": "^6.0.0", 56 | "remark-slug": "^4.2.2", 57 | "titleize": "^1.0.0", 58 | "update-notifier": "^2.2.0" 59 | }, 60 | "devDependencies": { 61 | "babel-eslint": "^7.2.3", 62 | "babel-plugin-transform-flow-strip-types": "^6.22.0", 63 | "babel-preset-es2015": "^6.24.1", 64 | "babel-preset-react": "^6.24.1", 65 | "commitizen": "^2.9.6", 66 | "conventional-changelog-cli": "^1.3.1", 67 | "cz-conventional-changelog": "^2.0.0", 68 | "eslint": "^4.0.0", 69 | "eslint-config-react-app": "^1.0.4", 70 | "eslint-config-standard": "^10.2.1", 71 | "eslint-config-standard-flow": "^1.0.1", 72 | "eslint-formatter-pretty": "^1.1.0", 73 | "eslint-plugin-flowtype": "^2.34.0", 74 | "eslint-plugin-import": "^2.3.0", 75 | "eslint-plugin-jsx-a11y": "^5.0.3", 76 | "eslint-plugin-promise": "^3.5.0", 77 | "eslint-plugin-react": "^7.1.0", 78 | "flow-bin": "^0.48.0", 79 | "flow-coverage-report": "^0.3.0", 80 | "fly": "^2.0.6", 81 | "fly-babel": "^2.1.1", 82 | "fly-clear": "^1.0.1", 83 | "fly-esnext": "^2.0.1", 84 | "fly-watch": "^1.1.1", 85 | "gh-pages": "^1.0.0", 86 | "husky": "^0.13.4", 87 | "lint-staged": "^3.6.1", 88 | "node-notifier": "^5.1.2", 89 | "prettier-eslint": "^6.3.0", 90 | "prettier-eslint-cli": "^4.1.1", 91 | "replace": "^0.3.0", 92 | "validate-commit-msg": "^2.12.2" 93 | }, 94 | "bin": { 95 | "flybook": "./dist/bin/cli.js" 96 | }, 97 | "files": [ 98 | "dist", 99 | "static", 100 | "index.js" 101 | ], 102 | "flow-coverage-report": { 103 | "includeGlob": [ 104 | "libs/**/*.js", 105 | "components/**/*.js", 106 | "pages/**/*.js" 107 | ], 108 | "type": [ 109 | "text" 110 | ] 111 | }, 112 | "config": { 113 | "validate-commit-msg": { 114 | "preset": "angular", 115 | "warnOnFail": false, 116 | "maxSubjectLength": 100 117 | }, 118 | "commitizen": { 119 | "path": "node_modules/cz-conventional-changelog" 120 | } 121 | }, 122 | "lint-staged": { 123 | "libs/**/*.{js,jsx}": [ 124 | "format", 125 | "lint", 126 | "git add" 127 | ], 128 | "components/**/*.{js,jsx}": [ 129 | "format", 130 | "lint", 131 | "git add" 132 | ], 133 | "pages/**/*.{js,jsx}": [ 134 | "format", 135 | "lint", 136 | "git add" 137 | ] 138 | }, 139 | "contributors": [{ 140 | "name": "Uno Kim", 141 | "email": "djkehh@gmail.com", 142 | "url": "https://djkeh.github.io/" 143 | }] 144 | } 145 | -------------------------------------------------------------------------------- /pages/App.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import React from 'react' 3 | import Layout from '../components/layout' 4 | 5 | type NavItem = { 6 | [key: string]: string 7 | } 8 | 9 | type Author = { 10 | url?: string, 11 | name?: string 12 | } 13 | 14 | type RepositoryType = 'git' | 'svn' 15 | 16 | type Repository = 17 | | string 18 | | { 19 | type: RepositoryType, 20 | url: string 21 | } 22 | 23 | type Pkg = { 24 | name: string, 25 | homepage?: string, 26 | author?: Author, 27 | repository: Repository 28 | } 29 | 30 | type Props = { 31 | title: string, 32 | toc: NavItem, 33 | contents: string, 34 | pkg: Pkg, 35 | root: string 36 | } 37 | 38 | const App = ({ title, toc, contents, pkg, root }: Props) => 39 | 46 |
50 | 51 | 52 | export default App 53 | -------------------------------------------------------------------------------- /static/css/dark.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-size: 1em; 3 | line-height: 1.6; 4 | font-weight: 400; 5 | color: rgba(255,255,255,0.75); 6 | background-color: #24292e 7 | } 8 | 9 | a { 10 | text-decoration: none; 11 | } 12 | 13 | .site-header { 14 | padding: 50px 10px; 15 | margin: 0 auto; 16 | margin-bottom: 30px; 17 | max-width: 940px; 18 | border-bottom: 1px solid rgba(80,80,80,0.50); 19 | display: flex; 20 | justify-content: space-between; 21 | align-items: center; 22 | } 23 | 24 | .site-header a { 25 | /*color: #333;*/ 26 | color: rgba(255,255,255,0.50); 27 | } 28 | 29 | .site-header h1>a { 30 | letter-spacing: 3px; 31 | } 32 | 33 | .nav-link { 34 | display: flex; 35 | list-style: none; 36 | padding-left: 0; 37 | } 38 | 39 | .nav-link a:not(:first-child) { 40 | margin-left: 15px; 41 | } 42 | 43 | .main { 44 | max-width: 940px; 45 | margin: 0 auto; 46 | padding: 30px 10px; 47 | } 48 | 49 | aside { 50 | padding: 0 15px 0 0; 51 | border-right: 1px solid rgba(56, 61, 66, .5) 52 | } 53 | 54 | aside ul { 55 | list-style: none; 56 | padding-left: 0; 57 | } 58 | 59 | .main article { 60 | padding-left: 30px; 61 | } 62 | 63 | footer { 64 | text-align: center; 65 | max-width: 940px; 66 | margin: 0 auto; 67 | padding: 30px 10px; 68 | color: rgba(255,255,255,0.50); 69 | } 70 | 71 | footer a { 72 | color: #ccc 73 | } 74 | 75 | .chap { 76 | } 77 | 78 | .chap ul { 79 | margin-top: 0; 80 | } 81 | 82 | .chap li > a { 83 | color: #ccc; 84 | } 85 | 86 | .chap li.active a { 87 | color: #63a35c; 88 | } 89 | 90 | .chap-title { 91 | font-size: 18px; 92 | color: #777; 93 | font-weight: 700; 94 | text-transform: uppercase; 95 | font-size: .7rem; 96 | letter-spacing: 3px; 97 | } 98 | 99 | /* 100 | * icon 101 | */ 102 | h1:hover span.icon-link, 103 | h2:hover span.icon-link, 104 | h3:hover span.icon-link, 105 | h4:hover span.icon-link, 106 | h5:hover span.icon-link, 107 | h6:hover span.icon-link { 108 | visibility: visible; 109 | } 110 | 111 | /* 112 | * markdown reset 113 | */ 114 | 115 | .markdown-body { 116 | /*color: #24292e;*/ 117 | } 118 | 119 | .markdown-body h2 { 120 | border-bottom: 0; 121 | } 122 | 123 | .markdown-body code:not(.hljs)::before, 124 | .markdown-body code:not(.hljs)::after { 125 | letter-spacing: -0.2em; 126 | content: "\00a0"; 127 | } 128 | 129 | .markdown-body code:not(.hljs) { 130 | padding: 0; 131 | padding-top: 0.2em; 132 | padding-bottom: 0.2em; 133 | margin: 0; 134 | background-color: rgba(244, 244, 244, 0.1); 135 | border-radius: 3px; 136 | /*font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, Courier, monospace;*/ 137 | font-size: 85%; 138 | } 139 | 140 | .markdown-body pre > code { 141 | display: block; 142 | overflow-x: auto; 143 | border-radius: 3px; 144 | padding: 0.7em; 145 | } 146 | 147 | /* 148 | * mobile support 149 | */ 150 | .more { 151 | display: none; 152 | height: 25px; 153 | } 154 | 155 | @media only screen and (max-width: 768px) { 156 | section.main { 157 | padding: 5rem 0px; 158 | } 159 | header.site-header { 160 | padding: 0; 161 | position: fixed; 162 | left: 0; 163 | width: 100%; 164 | background-color: rgba(41,41,41,1); 165 | box-shadow: 0px 5px 10px rgba(0,0,0,.1) 166 | } 167 | header h1 { 168 | margin: 0.3em 0; 169 | padding-left: 2rem; 170 | font-size: 1.5em; 171 | } 172 | header nav { 173 | padding-right: 2rem; 174 | } 175 | aside { 176 | position: fixed; 177 | display: none; 178 | right: 0; 179 | top: 52px; 180 | overflow-y: auto; 181 | background-color: rgba(37, 37, 37, 1); 182 | max-height: calc(100% - 53px); 183 | } 184 | .main article { 185 | padding: 0; 186 | } 187 | .main aside { 188 | padding-left: 20px; 189 | padding-right: 20px; 190 | padding-top: 10px; 191 | border: 1px solid rgba(80,80,80,0.50); 192 | box-shadow: 0px 5px 10px rgba(0,0,0,.1); 193 | } 194 | .more { 195 | display: inline-block; 196 | } 197 | .octicon { 198 | fill: currentColor; 199 | } 200 | } -------------------------------------------------------------------------------- /static/css/flexboxgrid.min.css: -------------------------------------------------------------------------------- 1 | .container,.container-fluid{margin-right:auto;margin-left:auto}.container-fluid{padding-right:2rem;padding-left:2rem}.row{box-sizing:border-box;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-flex:0;-ms-flex:0 1 auto;flex:0 1 auto;-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-direction:row;flex-direction:row;-ms-flex-wrap:wrap;flex-wrap:wrap;margin-right:-.5rem;margin-left:-.5rem}.row.reverse{-webkit-box-orient:horizontal;-webkit-box-direction:reverse;-ms-flex-direction:row-reverse;flex-direction:row-reverse}.col.reverse{-webkit-box-orient:vertical;-webkit-box-direction:reverse;-ms-flex-direction:column-reverse;flex-direction:column-reverse}.col-xs,.col-xs-1,.col-xs-10,.col-xs-11,.col-xs-12,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9,.col-xs-offset-0,.col-xs-offset-1,.col-xs-offset-10,.col-xs-offset-11,.col-xs-offset-12,.col-xs-offset-2,.col-xs-offset-3,.col-xs-offset-4,.col-xs-offset-5,.col-xs-offset-6,.col-xs-offset-7,.col-xs-offset-8,.col-xs-offset-9{box-sizing:border-box;-webkit-box-flex:0;-ms-flex:0 0 auto;flex:0 0 auto;padding-right:.5rem;padding-left:.5rem}.col-xs{-webkit-box-flex:1;-ms-flex-positive:1;flex-grow:1;-ms-flex-preferred-size:0;flex-basis:0;max-width:100%}.col-xs-1{-ms-flex-preferred-size:8.33333333%;flex-basis:8.33333333%;max-width:8.33333333%}.col-xs-2{-ms-flex-preferred-size:16.66666667%;flex-basis:16.66666667%;max-width:16.66666667%}.col-xs-3{-ms-flex-preferred-size:25%;flex-basis:25%;max-width:25%}.col-xs-4{-ms-flex-preferred-size:33.33333333%;flex-basis:33.33333333%;max-width:33.33333333%}.col-xs-5{-ms-flex-preferred-size:41.66666667%;flex-basis:41.66666667%;max-width:41.66666667%}.col-xs-6{-ms-flex-preferred-size:50%;flex-basis:50%;max-width:50%}.col-xs-7{-ms-flex-preferred-size:58.33333333%;flex-basis:58.33333333%;max-width:58.33333333%}.col-xs-8{-ms-flex-preferred-size:66.66666667%;flex-basis:66.66666667%;max-width:66.66666667%}.col-xs-9{-ms-flex-preferred-size:75%;flex-basis:75%;max-width:75%}.col-xs-10{-ms-flex-preferred-size:83.33333333%;flex-basis:83.33333333%;max-width:83.33333333%}.col-xs-11{-ms-flex-preferred-size:91.66666667%;flex-basis:91.66666667%;max-width:91.66666667%}.col-xs-12{-ms-flex-preferred-size:100%;flex-basis:100%;max-width:100%}.col-xs-offset-0{margin-left:0}.col-xs-offset-1{margin-left:8.33333333%}.col-xs-offset-2{margin-left:16.66666667%}.col-xs-offset-3{margin-left:25%}.col-xs-offset-4{margin-left:33.33333333%}.col-xs-offset-5{margin-left:41.66666667%}.col-xs-offset-6{margin-left:50%}.col-xs-offset-7{margin-left:58.33333333%}.col-xs-offset-8{margin-left:66.66666667%}.col-xs-offset-9{margin-left:75%}.col-xs-offset-10{margin-left:83.33333333%}.col-xs-offset-11{margin-left:91.66666667%}.start-xs{-webkit-box-pack:start;-ms-flex-pack:start;justify-content:flex-start;text-align:start}.center-xs{-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;text-align:center}.end-xs{-webkit-box-pack:end;-ms-flex-pack:end;justify-content:flex-end;text-align:end}.top-xs{-webkit-box-align:start;-ms-flex-align:start;align-items:flex-start}.middle-xs{-webkit-box-align:center;-ms-flex-align:center;align-items:center}.bottom-xs{-webkit-box-align:end;-ms-flex-align:end;align-items:flex-end}.around-xs{-ms-flex-pack:distribute;justify-content:space-around}.between-xs{-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between}.first-xs{-webkit-box-ordinal-group:0;-ms-flex-order:-1;order:-1}.last-xs{-webkit-box-ordinal-group:2;-ms-flex-order:1;order:1}@media only screen and (min-width:48em){.container{width:49rem}.col-sm,.col-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-sm-offset-0,.col-sm-offset-1,.col-sm-offset-10,.col-sm-offset-11,.col-sm-offset-12,.col-sm-offset-2,.col-sm-offset-3,.col-sm-offset-4,.col-sm-offset-5,.col-sm-offset-6,.col-sm-offset-7,.col-sm-offset-8,.col-sm-offset-9{box-sizing:border-box;-webkit-box-flex:0;-ms-flex:0 0 auto;flex:0 0 auto;padding-right:.5rem;padding-left:.5rem}.col-sm{-webkit-box-flex:1;-ms-flex-positive:1;flex-grow:1;-ms-flex-preferred-size:0;flex-basis:0;max-width:100%}.col-sm-1{-ms-flex-preferred-size:8.33333333%;flex-basis:8.33333333%;max-width:8.33333333%}.col-sm-2{-ms-flex-preferred-size:16.66666667%;flex-basis:16.66666667%;max-width:16.66666667%}.col-sm-3{-ms-flex-preferred-size:25%;flex-basis:25%;max-width:25%}.col-sm-4{-ms-flex-preferred-size:33.33333333%;flex-basis:33.33333333%;max-width:33.33333333%}.col-sm-5{-ms-flex-preferred-size:41.66666667%;flex-basis:41.66666667%;max-width:41.66666667%}.col-sm-6{-ms-flex-preferred-size:50%;flex-basis:50%;max-width:50%}.col-sm-7{-ms-flex-preferred-size:58.33333333%;flex-basis:58.33333333%;max-width:58.33333333%}.col-sm-8{-ms-flex-preferred-size:66.66666667%;flex-basis:66.66666667%;max-width:66.66666667%}.col-sm-9{-ms-flex-preferred-size:75%;flex-basis:75%;max-width:75%}.col-sm-10{-ms-flex-preferred-size:83.33333333%;flex-basis:83.33333333%;max-width:83.33333333%}.col-sm-11{-ms-flex-preferred-size:91.66666667%;flex-basis:91.66666667%;max-width:91.66666667%}.col-sm-12{-ms-flex-preferred-size:100%;flex-basis:100%;max-width:100%}.col-sm-offset-0{margin-left:0}.col-sm-offset-1{margin-left:8.33333333%}.col-sm-offset-2{margin-left:16.66666667%}.col-sm-offset-3{margin-left:25%}.col-sm-offset-4{margin-left:33.33333333%}.col-sm-offset-5{margin-left:41.66666667%}.col-sm-offset-6{margin-left:50%}.col-sm-offset-7{margin-left:58.33333333%}.col-sm-offset-8{margin-left:66.66666667%}.col-sm-offset-9{margin-left:75%}.col-sm-offset-10{margin-left:83.33333333%}.col-sm-offset-11{margin-left:91.66666667%}.start-sm{-webkit-box-pack:start;-ms-flex-pack:start;justify-content:flex-start;text-align:start}.center-sm{-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;text-align:center}.end-sm{-webkit-box-pack:end;-ms-flex-pack:end;justify-content:flex-end;text-align:end}.top-sm{-webkit-box-align:start;-ms-flex-align:start;align-items:flex-start}.middle-sm{-webkit-box-align:center;-ms-flex-align:center;align-items:center}.bottom-sm{-webkit-box-align:end;-ms-flex-align:end;align-items:flex-end}.around-sm{-ms-flex-pack:distribute;justify-content:space-around}.between-sm{-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between}.first-sm{-webkit-box-ordinal-group:0;-ms-flex-order:-1;order:-1}.last-sm{-webkit-box-ordinal-group:2;-ms-flex-order:1;order:1}}@media only screen and (min-width:64em){.container{width:65rem}.col-md,.col-md-1,.col-md-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-md-offset-0,.col-md-offset-1,.col-md-offset-10,.col-md-offset-11,.col-md-offset-12,.col-md-offset-2,.col-md-offset-3,.col-md-offset-4,.col-md-offset-5,.col-md-offset-6,.col-md-offset-7,.col-md-offset-8,.col-md-offset-9{box-sizing:border-box;-webkit-box-flex:0;-ms-flex:0 0 auto;flex:0 0 auto;padding-right:.5rem;padding-left:.5rem}.col-md{-webkit-box-flex:1;-ms-flex-positive:1;flex-grow:1;-ms-flex-preferred-size:0;flex-basis:0;max-width:100%}.col-md-1{-ms-flex-preferred-size:8.33333333%;flex-basis:8.33333333%;max-width:8.33333333%}.col-md-2{-ms-flex-preferred-size:16.66666667%;flex-basis:16.66666667%;max-width:16.66666667%}.col-md-3{-ms-flex-preferred-size:25%;flex-basis:25%;max-width:25%}.col-md-4{-ms-flex-preferred-size:33.33333333%;flex-basis:33.33333333%;max-width:33.33333333%}.col-md-5{-ms-flex-preferred-size:41.66666667%;flex-basis:41.66666667%;max-width:41.66666667%}.col-md-6{-ms-flex-preferred-size:50%;flex-basis:50%;max-width:50%}.col-md-7{-ms-flex-preferred-size:58.33333333%;flex-basis:58.33333333%;max-width:58.33333333%}.col-md-8{-ms-flex-preferred-size:66.66666667%;flex-basis:66.66666667%;max-width:66.66666667%}.col-md-9{-ms-flex-preferred-size:75%;flex-basis:75%;max-width:75%}.col-md-10{-ms-flex-preferred-size:83.33333333%;flex-basis:83.33333333%;max-width:83.33333333%}.col-md-11{-ms-flex-preferred-size:91.66666667%;flex-basis:91.66666667%;max-width:91.66666667%}.col-md-12{-ms-flex-preferred-size:100%;flex-basis:100%;max-width:100%}.col-md-offset-0{margin-left:0}.col-md-offset-1{margin-left:8.33333333%}.col-md-offset-2{margin-left:16.66666667%}.col-md-offset-3{margin-left:25%}.col-md-offset-4{margin-left:33.33333333%}.col-md-offset-5{margin-left:41.66666667%}.col-md-offset-6{margin-left:50%}.col-md-offset-7{margin-left:58.33333333%}.col-md-offset-8{margin-left:66.66666667%}.col-md-offset-9{margin-left:75%}.col-md-offset-10{margin-left:83.33333333%}.col-md-offset-11{margin-left:91.66666667%}.start-md{-webkit-box-pack:start;-ms-flex-pack:start;justify-content:flex-start;text-align:start}.center-md{-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;text-align:center}.end-md{-webkit-box-pack:end;-ms-flex-pack:end;justify-content:flex-end;text-align:end}.top-md{-webkit-box-align:start;-ms-flex-align:start;align-items:flex-start}.middle-md{-webkit-box-align:center;-ms-flex-align:center;align-items:center}.bottom-md{-webkit-box-align:end;-ms-flex-align:end;align-items:flex-end}.around-md{-ms-flex-pack:distribute;justify-content:space-around}.between-md{-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between}.first-md{-webkit-box-ordinal-group:0;-ms-flex-order:-1;order:-1}.last-md{-webkit-box-ordinal-group:2;-ms-flex-order:1;order:1}}@media only screen and (min-width:75em){.container{width:76rem}.col-lg,.col-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9,.col-lg-offset-0,.col-lg-offset-1,.col-lg-offset-10,.col-lg-offset-11,.col-lg-offset-12,.col-lg-offset-2,.col-lg-offset-3,.col-lg-offset-4,.col-lg-offset-5,.col-lg-offset-6,.col-lg-offset-7,.col-lg-offset-8,.col-lg-offset-9{box-sizing:border-box;-webkit-box-flex:0;-ms-flex:0 0 auto;flex:0 0 auto;padding-right:.5rem;padding-left:.5rem}.col-lg{-webkit-box-flex:1;-ms-flex-positive:1;flex-grow:1;-ms-flex-preferred-size:0;flex-basis:0;max-width:100%}.col-lg-1{-ms-flex-preferred-size:8.33333333%;flex-basis:8.33333333%;max-width:8.33333333%}.col-lg-2{-ms-flex-preferred-size:16.66666667%;flex-basis:16.66666667%;max-width:16.66666667%}.col-lg-3{-ms-flex-preferred-size:25%;flex-basis:25%;max-width:25%}.col-lg-4{-ms-flex-preferred-size:33.33333333%;flex-basis:33.33333333%;max-width:33.33333333%}.col-lg-5{-ms-flex-preferred-size:41.66666667%;flex-basis:41.66666667%;max-width:41.66666667%}.col-lg-6{-ms-flex-preferred-size:50%;flex-basis:50%;max-width:50%}.col-lg-7{-ms-flex-preferred-size:58.33333333%;flex-basis:58.33333333%;max-width:58.33333333%}.col-lg-8{-ms-flex-preferred-size:66.66666667%;flex-basis:66.66666667%;max-width:66.66666667%}.col-lg-9{-ms-flex-preferred-size:75%;flex-basis:75%;max-width:75%}.col-lg-10{-ms-flex-preferred-size:83.33333333%;flex-basis:83.33333333%;max-width:83.33333333%}.col-lg-11{-ms-flex-preferred-size:91.66666667%;flex-basis:91.66666667%;max-width:91.66666667%}.col-lg-12{-ms-flex-preferred-size:100%;flex-basis:100%;max-width:100%}.col-lg-offset-0{margin-left:0}.col-lg-offset-1{margin-left:8.33333333%}.col-lg-offset-2{margin-left:16.66666667%}.col-lg-offset-3{margin-left:25%}.col-lg-offset-4{margin-left:33.33333333%}.col-lg-offset-5{margin-left:41.66666667%}.col-lg-offset-6{margin-left:50%}.col-lg-offset-7{margin-left:58.33333333%}.col-lg-offset-8{margin-left:66.66666667%}.col-lg-offset-9{margin-left:75%}.col-lg-offset-10{margin-left:83.33333333%}.col-lg-offset-11{margin-left:91.66666667%}.start-lg{-webkit-box-pack:start;-ms-flex-pack:start;justify-content:flex-start;text-align:start}.center-lg{-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;text-align:center}.end-lg{-webkit-box-pack:end;-ms-flex-pack:end;justify-content:flex-end;text-align:end}.top-lg{-webkit-box-align:start;-ms-flex-align:start;align-items:flex-start}.middle-lg{-webkit-box-align:center;-ms-flex-align:center;align-items:center}.bottom-lg{-webkit-box-align:end;-ms-flex-align:end;align-items:flex-end}.around-lg{-ms-flex-pack:distribute;justify-content:space-around}.between-lg{-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between}.first-lg{-webkit-box-ordinal-group:0;-ms-flex-order:-1;order:-1}.last-lg{-webkit-box-ordinal-group:2;-ms-flex-order:1;order:1}} -------------------------------------------------------------------------------- /static/css/github-flavored-markdown.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: octicons-link; 3 | src: url(data:font/woff;charset=utf-8;base64,d09GRgABAAAAAAZwABAAAAAACFQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABEU0lHAAAGaAAAAAgAAAAIAAAAAUdTVUIAAAZcAAAACgAAAAoAAQAAT1MvMgAAAyQAAABJAAAAYFYEU3RjbWFwAAADcAAAAEUAAACAAJThvmN2dCAAAATkAAAABAAAAAQAAAAAZnBnbQAAA7gAAACyAAABCUM+8IhnYXNwAAAGTAAAABAAAAAQABoAI2dseWYAAAFsAAABPAAAAZwcEq9taGVhZAAAAsgAAAA0AAAANgh4a91oaGVhAAADCAAAABoAAAAkCA8DRGhtdHgAAAL8AAAADAAAAAwGAACfbG9jYQAAAsAAAAAIAAAACABiATBtYXhwAAACqAAAABgAAAAgAA8ASm5hbWUAAAToAAABQgAAAlXu73sOcG9zdAAABiwAAAAeAAAAME3QpOBwcmVwAAAEbAAAAHYAAAB/aFGpk3jaTY6xa8JAGMW/O62BDi0tJLYQincXEypYIiGJjSgHniQ6umTsUEyLm5BV6NDBP8Tpts6F0v+k/0an2i+itHDw3v2+9+DBKTzsJNnWJNTgHEy4BgG3EMI9DCEDOGEXzDADU5hBKMIgNPZqoD3SilVaXZCER3/I7AtxEJLtzzuZfI+VVkprxTlXShWKb3TBecG11rwoNlmmn1P2WYcJczl32etSpKnziC7lQyWe1smVPy/Lt7Kc+0vWY/gAgIIEqAN9we0pwKXreiMasxvabDQMM4riO+qxM2ogwDGOZTXxwxDiycQIcoYFBLj5K3EIaSctAq2kTYiw+ymhce7vwM9jSqO8JyVd5RH9gyTt2+J/yUmYlIR0s04n6+7Vm1ozezUeLEaUjhaDSuXHwVRgvLJn1tQ7xiuVv/ocTRF42mNgZGBgYGbwZOBiAAFGJBIMAAizAFoAAABiAGIAznjaY2BkYGAA4in8zwXi+W2+MjCzMIDApSwvXzC97Z4Ig8N/BxYGZgcgl52BCSQKAA3jCV8CAABfAAAAAAQAAEB42mNgZGBg4f3vACQZQABIMjKgAmYAKEgBXgAAeNpjYGY6wTiBgZWBg2kmUxoDA4MPhGZMYzBi1AHygVLYQUCaawqDA4PChxhmh/8ODDEsvAwHgMKMIDnGL0x7gJQCAwMAJd4MFwAAAHjaY2BgYGaA4DAGRgYQkAHyGMF8NgYrIM3JIAGVYYDT+AEjAwuDFpBmA9KMDEwMCh9i/v8H8sH0/4dQc1iAmAkALaUKLgAAAHjaTY9LDsIgEIbtgqHUPpDi3gPoBVyRTmTddOmqTXThEXqrob2gQ1FjwpDvfwCBdmdXC5AVKFu3e5MfNFJ29KTQT48Ob9/lqYwOGZxeUelN2U2R6+cArgtCJpauW7UQBqnFkUsjAY/kOU1cP+DAgvxwn1chZDwUbd6CFimGXwzwF6tPbFIcjEl+vvmM/byA48e6tWrKArm4ZJlCbdsrxksL1AwWn/yBSJKpYbq8AXaaTb8AAHja28jAwOC00ZrBeQNDQOWO//sdBBgYGRiYWYAEELEwMTE4uzo5Zzo5b2BxdnFOcALxNjA6b2ByTswC8jYwg0VlNuoCTWAMqNzMzsoK1rEhNqByEyerg5PMJlYuVueETKcd/89uBpnpvIEVomeHLoMsAAe1Id4AAAAAAAB42oWQT07CQBTGv0JBhagk7HQzKxca2sJCE1hDt4QF+9JOS0nbaaYDCQfwCJ7Au3AHj+LO13FMmm6cl7785vven0kBjHCBhfpYuNa5Ph1c0e2Xu3jEvWG7UdPDLZ4N92nOm+EBXuAbHmIMSRMs+4aUEd4Nd3CHD8NdvOLTsA2GL8M9PODbcL+hD7C1xoaHeLJSEao0FEW14ckxC+TU8TxvsY6X0eLPmRhry2WVioLpkrbp84LLQPGI7c6sOiUzpWIWS5GzlSgUzzLBSikOPFTOXqly7rqx0Z1Q5BAIoZBSFihQYQOOBEdkCOgXTOHA07HAGjGWiIjaPZNW13/+lm6S9FT7rLHFJ6fQbkATOG1j2OFMucKJJsxIVfQORl+9Jyda6Sl1dUYhSCm1dyClfoeDve4qMYdLEbfqHf3O/AdDumsjAAB42mNgYoAAZQYjBmyAGYQZmdhL8zLdDEydARfoAqIAAAABAAMABwAKABMAB///AA8AAQAAAAAAAAAAAAAAAAABAAAAAA==) format('woff'); 4 | } 5 | 6 | .markdown-body { 7 | -ms-text-size-adjust: 100%; 8 | -webkit-text-size-adjust: 100%; 9 | line-height: 1.5; 10 | /*color: #24292e;*/ 11 | /*font-family: -apple-system, system-ui, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";*/ 12 | font-size: 16px; 13 | line-height: 1.5; 14 | word-wrap: break-word; 15 | } 16 | 17 | .markdown-body .pl-c { 18 | color: #969896; 19 | } 20 | 21 | .markdown-body .pl-c1, 22 | .markdown-body .pl-s .pl-v { 23 | color: #0086b3; 24 | } 25 | 26 | .markdown-body .pl-e, 27 | .markdown-body .pl-en { 28 | color: #795da3; 29 | } 30 | 31 | .markdown-body .pl-smi, 32 | .markdown-body .pl-s .pl-s1 { 33 | color: #333; 34 | } 35 | 36 | .markdown-body .pl-ent { 37 | color: #63a35c; 38 | } 39 | 40 | .markdown-body .pl-k { 41 | color: #a71d5d; 42 | } 43 | 44 | .markdown-body .pl-s, 45 | .markdown-body .pl-pds, 46 | .markdown-body .pl-s .pl-pse .pl-s1, 47 | .markdown-body .pl-sr, 48 | .markdown-body .pl-sr .pl-cce, 49 | .markdown-body .pl-sr .pl-sre, 50 | .markdown-body .pl-sr .pl-sra { 51 | color: #183691; 52 | } 53 | 54 | .markdown-body .pl-v, 55 | .markdown-body .pl-smw { 56 | color: #ed6a43; 57 | } 58 | 59 | .markdown-body .pl-bu { 60 | color: #b52a1d; 61 | } 62 | 63 | .markdown-body .pl-ii { 64 | color: #f8f8f8; 65 | background-color: #b52a1d; 66 | } 67 | 68 | .markdown-body .pl-c2 { 69 | color: #f8f8f8; 70 | background-color: #b52a1d; 71 | } 72 | 73 | .markdown-body .pl-c2::before { 74 | content: "^M"; 75 | } 76 | 77 | .markdown-body .pl-sr .pl-cce { 78 | font-weight: bold; 79 | color: #63a35c; 80 | } 81 | 82 | .markdown-body .pl-ml { 83 | color: #693a17; 84 | } 85 | 86 | .markdown-body .pl-mh, 87 | .markdown-body .pl-mh .pl-en, 88 | .markdown-body .pl-ms { 89 | font-weight: bold; 90 | color: #1d3e81; 91 | } 92 | 93 | .markdown-body .pl-mq { 94 | color: #008080; 95 | } 96 | 97 | .markdown-body .pl-mi { 98 | font-style: italic; 99 | color: #333; 100 | } 101 | 102 | .markdown-body .pl-mb { 103 | font-weight: bold; 104 | color: #333; 105 | } 106 | 107 | .markdown-body .pl-md { 108 | color: #bd2c00; 109 | background-color: #ffecec; 110 | } 111 | 112 | .markdown-body .pl-mi1 { 113 | color: #55a532; 114 | background-color: #eaffea; 115 | } 116 | 117 | .markdown-body .pl-mc { 118 | color: #ef9700; 119 | background-color: #ffe3b4; 120 | } 121 | 122 | .markdown-body .pl-mi2 { 123 | color: #d8d8d8; 124 | background-color: #808080; 125 | } 126 | 127 | .markdown-body .pl-mdr { 128 | font-weight: bold; 129 | color: #795da3; 130 | } 131 | 132 | .markdown-body .pl-mo { 133 | color: #1d3e81; 134 | } 135 | 136 | .markdown-body .pl-ba { 137 | color: #595e62; 138 | } 139 | 140 | .markdown-body .pl-sg { 141 | color: #c0c0c0; 142 | } 143 | 144 | .markdown-body .pl-corl { 145 | text-decoration: underline; 146 | color: #183691; 147 | } 148 | 149 | .markdown-body .octicon { 150 | display: inline-block; 151 | vertical-align: text-top; 152 | fill: currentColor; 153 | } 154 | 155 | .markdown-body a { 156 | background-color: transparent; 157 | -webkit-text-decoration-skip: objects; 158 | } 159 | 160 | .markdown-body a:active, 161 | .markdown-body a:hover { 162 | outline-width: 0; 163 | } 164 | 165 | .markdown-body strong { 166 | font-weight: inherit; 167 | } 168 | 169 | .markdown-body strong { 170 | font-weight: bolder; 171 | } 172 | 173 | .markdown-body h1 { 174 | font-size: 2em; 175 | margin: 0.67em 0; 176 | } 177 | 178 | .markdown-body img { 179 | border-style: none; 180 | } 181 | 182 | .markdown-body svg:not(:root) { 183 | overflow: hidden; 184 | } 185 | 186 | .markdown-body code, 187 | .markdown-body kbd, 188 | .markdown-body pre { 189 | /*font-family: monospace, monospace;*/ 190 | font-size: 1em; 191 | } 192 | 193 | .markdown-body hr { 194 | box-sizing: content-box; 195 | height: 0; 196 | overflow: visible; 197 | } 198 | 199 | .markdown-body input { 200 | font: inherit; 201 | margin: 0; 202 | } 203 | 204 | .markdown-body input { 205 | overflow: visible; 206 | } 207 | 208 | .markdown-body [type="checkbox"] { 209 | box-sizing: border-box; 210 | padding: 0; 211 | } 212 | 213 | .markdown-body * { 214 | box-sizing: border-box; 215 | } 216 | 217 | .markdown-body input { 218 | font-family: inherit; 219 | font-size: inherit; 220 | line-height: inherit; 221 | } 222 | 223 | .markdown-body a { 224 | color: #0366d6; 225 | text-decoration: none; 226 | } 227 | 228 | .markdown-body a:hover { 229 | text-decoration: underline; 230 | } 231 | 232 | .markdown-body strong { 233 | font-weight: 600; 234 | } 235 | 236 | .markdown-body hr { 237 | height: 0; 238 | margin: 15px 0; 239 | overflow: hidden; 240 | background: transparent; 241 | border: 0; 242 | border-bottom: 1px solid #dfe2e5; 243 | } 244 | 245 | .markdown-body hr::before { 246 | display: table; 247 | content: ""; 248 | } 249 | 250 | .markdown-body hr::after { 251 | display: table; 252 | clear: both; 253 | content: ""; 254 | } 255 | 256 | .markdown-body table { 257 | border-spacing: 0; 258 | border-collapse: collapse; 259 | } 260 | 261 | .markdown-body td, 262 | .markdown-body th { 263 | padding: 0; 264 | } 265 | 266 | .markdown-body h1, 267 | .markdown-body h2, 268 | .markdown-body h3, 269 | .markdown-body h4, 270 | .markdown-body h5, 271 | .markdown-body h6 { 272 | margin-top: 0; 273 | margin-bottom: 0; 274 | } 275 | 276 | .markdown-body h1 { 277 | font-size: 32px; 278 | font-weight: 600; 279 | } 280 | 281 | .markdown-body h2 { 282 | font-size: 24px; 283 | font-weight: 600; 284 | } 285 | 286 | .markdown-body h3 { 287 | font-size: 20px; 288 | font-weight: 600; 289 | } 290 | 291 | .markdown-body h4 { 292 | font-size: 16px; 293 | font-weight: 600; 294 | } 295 | 296 | .markdown-body h5 { 297 | font-size: 14px; 298 | font-weight: 600; 299 | } 300 | 301 | .markdown-body h6 { 302 | font-size: 12px; 303 | font-weight: 600; 304 | } 305 | 306 | .markdown-body p { 307 | margin-top: 0; 308 | margin-bottom: 10px; 309 | } 310 | 311 | .markdown-body blockquote { 312 | margin: 0; 313 | } 314 | 315 | .markdown-body ul, 316 | .markdown-body ol { 317 | padding-left: 0; 318 | margin-top: 0; 319 | margin-bottom: 0; 320 | } 321 | 322 | .markdown-body ol ol, 323 | .markdown-body ul ol { 324 | list-style-type: lower-roman; 325 | } 326 | 327 | .markdown-body ul ul ol, 328 | .markdown-body ul ol ol, 329 | .markdown-body ol ul ol, 330 | .markdown-body ol ol ol { 331 | list-style-type: lower-alpha; 332 | } 333 | 334 | .markdown-body dd { 335 | margin-left: 0; 336 | } 337 | 338 | .markdown-body .octicon { 339 | vertical-align: text-bottom; 340 | } 341 | 342 | .markdown-body .pl-0 { 343 | padding-left: 0 !important; 344 | } 345 | 346 | .markdown-body .pl-1 { 347 | padding-left: 4px !important; 348 | } 349 | 350 | .markdown-body .pl-2 { 351 | padding-left: 8px !important; 352 | } 353 | 354 | .markdown-body .pl-3 { 355 | padding-left: 16px !important; 356 | } 357 | 358 | .markdown-body .pl-4 { 359 | padding-left: 24px !important; 360 | } 361 | 362 | .markdown-body .pl-5 { 363 | padding-left: 32px !important; 364 | } 365 | 366 | .markdown-body .pl-6 { 367 | padding-left: 40px !important; 368 | } 369 | 370 | .markdown-body::before { 371 | display: table; 372 | content: ""; 373 | } 374 | 375 | .markdown-body::after { 376 | display: table; 377 | clear: both; 378 | content: ""; 379 | } 380 | 381 | .markdown-body>*:first-child { 382 | margin-top: 0 !important; 383 | } 384 | 385 | .markdown-body>*:last-child { 386 | margin-bottom: 0 !important; 387 | } 388 | 389 | .markdown-body a:not([href]) { 390 | color: inherit; 391 | text-decoration: none; 392 | } 393 | 394 | .markdown-body .anchor { 395 | float: left; 396 | padding-right: 4px; 397 | margin-left: -20px; 398 | line-height: 1; 399 | } 400 | 401 | .markdown-body .anchor:focus { 402 | outline: none; 403 | } 404 | 405 | .markdown-body p, 406 | .markdown-body blockquote, 407 | .markdown-body ul, 408 | .markdown-body ol, 409 | .markdown-body dl, 410 | .markdown-body table, 411 | .markdown-body pre { 412 | margin-top: 0; 413 | margin-bottom: 16px; 414 | } 415 | 416 | .markdown-body hr { 417 | height: 0.25em; 418 | padding: 0; 419 | margin: 24px 0; 420 | background-color: #e1e4e8; 421 | border: 0; 422 | } 423 | 424 | .markdown-body blockquote { 425 | padding: 0 1em; 426 | color: #6a737d; 427 | border-left: 0.25em solid #dfe2e5; 428 | } 429 | 430 | .markdown-body blockquote>:first-child { 431 | margin-top: 0; 432 | } 433 | 434 | .markdown-body blockquote>:last-child { 435 | margin-bottom: 0; 436 | } 437 | 438 | .markdown-body kbd { 439 | display: inline-block; 440 | padding: 3px 5px; 441 | font-size: 11px; 442 | line-height: 10px; 443 | color: #444d56; 444 | vertical-align: middle; 445 | background-color: #fafbfc; 446 | border: solid 1px #c6cbd1; 447 | border-bottom-color: #959da5; 448 | border-radius: 3px; 449 | box-shadow: inset 0 -1px 0 #959da5; 450 | } 451 | 452 | .markdown-body h1, 453 | .markdown-body h2, 454 | .markdown-body h3, 455 | .markdown-body h4, 456 | .markdown-body h5, 457 | .markdown-body h6 { 458 | margin-top: 24px; 459 | margin-bottom: 16px; 460 | font-weight: 600; 461 | line-height: 1.25; 462 | } 463 | 464 | .markdown-body h1 .octicon-link, 465 | .markdown-body h2 .octicon-link, 466 | .markdown-body h3 .octicon-link, 467 | .markdown-body h4 .octicon-link, 468 | .markdown-body h5 .octicon-link, 469 | .markdown-body h6 .octicon-link { 470 | color: #1b1f23; 471 | vertical-align: middle; 472 | visibility: hidden; 473 | } 474 | 475 | .markdown-body h1:hover .anchor, 476 | .markdown-body h2:hover .anchor, 477 | .markdown-body h3:hover .anchor, 478 | .markdown-body h4:hover .anchor, 479 | .markdown-body h5:hover .anchor, 480 | .markdown-body h6:hover .anchor { 481 | text-decoration: none; 482 | } 483 | 484 | .markdown-body h1:hover .anchor .octicon-link, 485 | .markdown-body h2:hover .anchor .octicon-link, 486 | .markdown-body h3:hover .anchor .octicon-link, 487 | .markdown-body h4:hover .anchor .octicon-link, 488 | .markdown-body h5:hover .anchor .octicon-link, 489 | .markdown-body h6:hover .anchor .octicon-link { 490 | visibility: visible; 491 | } 492 | 493 | .markdown-body h1 { 494 | padding-bottom: 0.3em; 495 | font-size: 2em; 496 | border-bottom: 1px solid #eaecef; 497 | } 498 | 499 | .markdown-body h2 { 500 | padding-bottom: 0.3em; 501 | font-size: 1.5em; 502 | border-bottom: 1px solid #eaecef; 503 | } 504 | 505 | .markdown-body h3 { 506 | font-size: 1.25em; 507 | } 508 | 509 | .markdown-body h4 { 510 | font-size: 1em; 511 | } 512 | 513 | .markdown-body h5 { 514 | font-size: 0.875em; 515 | } 516 | 517 | .markdown-body h6 { 518 | font-size: 0.85em; 519 | color: #6a737d; 520 | } 521 | 522 | .markdown-body ul, 523 | .markdown-body ol { 524 | padding-left: 2em; 525 | } 526 | 527 | .markdown-body ul ul, 528 | .markdown-body ul ol, 529 | .markdown-body ol ol, 530 | .markdown-body ol ul { 531 | margin-top: 0; 532 | margin-bottom: 0; 533 | } 534 | 535 | .markdown-body li>p { 536 | margin-top: 16px; 537 | } 538 | 539 | .markdown-body li+li { 540 | margin-top: 0.25em; 541 | } 542 | 543 | .markdown-body dl { 544 | padding: 0; 545 | } 546 | 547 | .markdown-body dl dt { 548 | padding: 0; 549 | margin-top: 16px; 550 | font-size: 1em; 551 | font-style: italic; 552 | font-weight: 600; 553 | } 554 | 555 | .markdown-body dl dd { 556 | padding: 0 16px; 557 | margin-bottom: 16px; 558 | } 559 | 560 | .markdown-body table { 561 | display: block; 562 | width: 100%; 563 | overflow: auto; 564 | } 565 | 566 | .markdown-body table th { 567 | font-weight: 600; 568 | } 569 | 570 | .markdown-body table th, 571 | .markdown-body table td { 572 | padding: 6px 13px; 573 | border: 1px solid #dfe2e5; 574 | } 575 | 576 | .markdown-body table tr { 577 | background-color: #fff; 578 | border-top: 1px solid #c6cbd1; 579 | } 580 | 581 | .markdown-body table tr:nth-child(2n) { 582 | background-color: #f6f8fa; 583 | } 584 | 585 | .markdown-body img { 586 | max-width: 100%; 587 | box-sizing: content-box; 588 | background-color: #fff; 589 | } 590 | 591 | .markdown-body .full-commit .btn-outline:not(:disabled):hover { 592 | color: #005cc5; 593 | border-color: #005cc5; 594 | } 595 | 596 | .markdown-body kbd { 597 | display: inline-block; 598 | padding: 3px 5px; 599 | font: 11px "SFMono-Regular", Consolas, "Liberation Mono", Menlo, Courier, monospace; 600 | line-height: 10px; 601 | color: #444d56; 602 | vertical-align: middle; 603 | background-color: #fcfcfc; 604 | border: solid 1px #c6cbd1; 605 | border-bottom-color: #959da5; 606 | border-radius: 3px; 607 | box-shadow: inset 0 -1px 0 #959da5; 608 | } 609 | 610 | .markdown-body :checked+.radio-label { 611 | position: relative; 612 | z-index: 1; 613 | border-color: #0366d6; 614 | } 615 | 616 | .markdown-body .task-list-item { 617 | list-style-type: none; 618 | } 619 | 620 | .markdown-body .task-list-item+.task-list-item { 621 | margin-top: 3px; 622 | } 623 | 624 | .markdown-body .task-list-item input { 625 | margin: 0 0.2em 0.25em -1.6em; 626 | vertical-align: middle; 627 | } 628 | 629 | .markdown-body hr { 630 | border-bottom-color: #eee; 631 | } -------------------------------------------------------------------------------- /static/css/light.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-size: 1em; 3 | line-height: 1.6; 4 | font-weight: 400; 5 | color: #222; 6 | background-color: #fff; 7 | } 8 | 9 | a { 10 | text-decoration: none; 11 | } 12 | 13 | .site-header { 14 | padding: 50px 10px; 15 | margin: 0 auto; 16 | margin-bottom: 30px; 17 | max-width: 940px; 18 | border-bottom: 1px solid rgba(200,200,200,0.50); 19 | display: flex; 20 | justify-content: space-between; 21 | align-items: center; 22 | } 23 | 24 | .site-header a { 25 | color: #333; 26 | } 27 | 28 | .site-header h1>a { 29 | letter-spacing: 3px; 30 | } 31 | 32 | .nav-link { 33 | display: flex; 34 | list-style: none; 35 | padding-left: 0; 36 | } 37 | 38 | .nav-link a:not(:first-child) { 39 | margin-left: 15px; 40 | } 41 | 42 | .main { 43 | max-width: 940px; 44 | margin: 0 auto; 45 | padding: 30px 10px; 46 | } 47 | 48 | aside { 49 | padding: 0 15px 0 0; 50 | border-right: 1px solid rgba(206, 211, 216, .5) 51 | } 52 | 53 | aside ul { 54 | list-style: none; 55 | padding-left: 0; 56 | } 57 | 58 | .main article { 59 | padding-left: 30px; 60 | } 61 | 62 | footer { 63 | text-align: center; 64 | max-width: 940px; 65 | margin: 0 auto; 66 | padding: 30px 10px; 67 | color: rgba(100,100,100,0.50); 68 | } 69 | 70 | footer a { 71 | color: #6a737d 72 | } 73 | 74 | .chap { 75 | } 76 | 77 | .chap ul { 78 | margin-top: 0; 79 | } 80 | 81 | .chap li > a { 82 | color: #6a737d; 83 | } 84 | 85 | .chap li.active a { 86 | color: #63a35c; 87 | } 88 | 89 | .chap-title { 90 | font-size: 18px; 91 | color: #aaa; 92 | font-weight: 700; 93 | text-transform: uppercase; 94 | font-size: .7rem; 95 | letter-spacing: 3px; 96 | } 97 | 98 | /* 99 | * icon 100 | */ 101 | h1:hover span.icon-link, 102 | h2:hover span.icon-link, 103 | h3:hover span.icon-link, 104 | h4:hover span.icon-link, 105 | h5:hover span.icon-link, 106 | h6:hover span.icon-link { 107 | visibility: visible; 108 | } 109 | 110 | /* 111 | * markdown reset 112 | */ 113 | 114 | .markdown-body { 115 | color: #24292e; 116 | } 117 | 118 | .markdown-body h2 { 119 | border-bottom: 0; 120 | } 121 | 122 | .markdown-body code:not(.hljs)::before, 123 | .markdown-body code:not(.hljs)::after { 124 | letter-spacing: -0.2em; 125 | content: "\00a0"; 126 | } 127 | 128 | .markdown-body code:not(.hljs) { 129 | padding: 0; 130 | padding-top: 0.2em; 131 | padding-bottom: 0.2em; 132 | margin: 0; 133 | background-color: rgba(27,31,35,0.05); 134 | border-radius: 3px; 135 | /*font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, Courier, monospace;*/ 136 | font-size: 85%; 137 | } 138 | 139 | .markdown-body pre > code { 140 | display: block; 141 | overflow-x: auto; 142 | border-radius: 3px; 143 | padding: 0.7em; 144 | } 145 | 146 | 147 | /* 148 | * mobile support 149 | */ 150 | .more { 151 | display: none; 152 | height: 25px; 153 | } 154 | 155 | @media only screen and (max-width: 768px) { 156 | section.main { 157 | padding: 5rem 0px; 158 | } 159 | header.site-header { 160 | padding: 0; 161 | position: fixed; 162 | left: 0; 163 | width: 100%; 164 | background-color: rgba(244,244,244,1); 165 | box-shadow: 0px 5px 10px rgba(100,100,100,.1); 166 | } 167 | header h1 { 168 | margin: 0.3em 0; 169 | padding-left: 2rem; 170 | font-size: 1.5em; 171 | } 172 | header nav { 173 | padding-right: 2rem; 174 | } 175 | aside { 176 | position: fixed; 177 | display: none; 178 | right: 0; 179 | top: 52px; 180 | overflow-y: auto; 181 | background-color: rgba(250,250,250,1); 182 | max-height: calc(100% - 53px); 183 | } 184 | .main article { 185 | padding: 0; 186 | } 187 | .main aside { 188 | padding-left: 20px; 189 | padding-right: 20px; 190 | padding-top: 10px; 191 | border: 1px solid #ddd; 192 | box-shadow: 0px 5px 10px rgba(100,100,100,.1); 193 | 194 | } 195 | .more { 196 | display: inline-block; 197 | } 198 | .octicon { 199 | fill: currentColor; 200 | } 201 | } -------------------------------------------------------------------------------- /static/css/normalize.css: -------------------------------------------------------------------------------- 1 | /*! normalize.css v7.0.0 | MIT License | github.com/necolas/normalize.css */ 2 | 3 | /* Document 4 | ========================================================================== */ 5 | 6 | /** 7 | * 1. Correct the line height in all browsers. 8 | * 2. Prevent adjustments of font size after orientation changes in 9 | * IE on Windows Phone and in iOS. 10 | */ 11 | 12 | html { 13 | line-height: 1.15; /* 1 */ 14 | -ms-text-size-adjust: 100%; /* 2 */ 15 | -webkit-text-size-adjust: 100%; /* 2 */ 16 | } 17 | 18 | /* Sections 19 | ========================================================================== */ 20 | 21 | /** 22 | * Remove the margin in all browsers (opinionated). 23 | */ 24 | 25 | body { 26 | margin: 0; 27 | } 28 | 29 | /** 30 | * Add the correct display in IE 9-. 31 | */ 32 | 33 | article, 34 | aside, 35 | footer, 36 | header, 37 | nav, 38 | section { 39 | display: block; 40 | } 41 | 42 | /** 43 | * Correct the font size and margin on `h1` elements within `section` and 44 | * `article` contexts in Chrome, Firefox, and Safari. 45 | */ 46 | 47 | h1 { 48 | font-size: 2em; 49 | margin: 0.67em 0; 50 | } 51 | 52 | /* Grouping content 53 | ========================================================================== */ 54 | 55 | /** 56 | * Add the correct display in IE 9-. 57 | * 1. Add the correct display in IE. 58 | */ 59 | 60 | figcaption, 61 | figure, 62 | main { /* 1 */ 63 | display: block; 64 | } 65 | 66 | /** 67 | * Add the correct margin in IE 8. 68 | */ 69 | 70 | figure { 71 | margin: 1em 40px; 72 | } 73 | 74 | /** 75 | * 1. Add the correct box sizing in Firefox. 76 | * 2. Show the overflow in Edge and IE. 77 | */ 78 | 79 | hr { 80 | box-sizing: content-box; /* 1 */ 81 | height: 0; /* 1 */ 82 | overflow: visible; /* 2 */ 83 | } 84 | 85 | /** 86 | * 1. Correct the inheritance and scaling of font size in all browsers. 87 | * 2. Correct the odd `em` font sizing in all browsers. 88 | */ 89 | 90 | pre { 91 | font-family: monospace, monospace; /* 1 */ 92 | font-size: 1em; /* 2 */ 93 | } 94 | 95 | /* Text-level semantics 96 | ========================================================================== */ 97 | 98 | /** 99 | * 1. Remove the gray background on active links in IE 10. 100 | * 2. Remove gaps in links underline in iOS 8+ and Safari 8+. 101 | */ 102 | 103 | a { 104 | background-color: transparent; /* 1 */ 105 | -webkit-text-decoration-skip: objects; /* 2 */ 106 | } 107 | 108 | /** 109 | * 1. Remove the bottom border in Chrome 57- and Firefox 39-. 110 | * 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari. 111 | */ 112 | 113 | abbr[title] { 114 | border-bottom: none; /* 1 */ 115 | text-decoration: underline; /* 2 */ 116 | text-decoration: underline dotted; /* 2 */ 117 | } 118 | 119 | /** 120 | * Prevent the duplicate application of `bolder` by the next rule in Safari 6. 121 | */ 122 | 123 | b, 124 | strong { 125 | font-weight: inherit; 126 | } 127 | 128 | /** 129 | * Add the correct font weight in Chrome, Edge, and Safari. 130 | */ 131 | 132 | b, 133 | strong { 134 | font-weight: bolder; 135 | } 136 | 137 | /** 138 | * 1. Correct the inheritance and scaling of font size in all browsers. 139 | * 2. Correct the odd `em` font sizing in all browsers. 140 | */ 141 | 142 | code, 143 | kbd, 144 | samp { 145 | font-family: monospace, monospace; /* 1 */ 146 | font-size: 1em; /* 2 */ 147 | } 148 | 149 | /** 150 | * Add the correct font style in Android 4.3-. 151 | */ 152 | 153 | dfn { 154 | font-style: italic; 155 | } 156 | 157 | /** 158 | * Add the correct background and color in IE 9-. 159 | */ 160 | 161 | mark { 162 | background-color: #ff0; 163 | color: #000; 164 | } 165 | 166 | /** 167 | * Add the correct font size in all browsers. 168 | */ 169 | 170 | small { 171 | font-size: 80%; 172 | } 173 | 174 | /** 175 | * Prevent `sub` and `sup` elements from affecting the line height in 176 | * all browsers. 177 | */ 178 | 179 | sub, 180 | sup { 181 | font-size: 75%; 182 | line-height: 0; 183 | position: relative; 184 | vertical-align: baseline; 185 | } 186 | 187 | sub { 188 | bottom: -0.25em; 189 | } 190 | 191 | sup { 192 | top: -0.5em; 193 | } 194 | 195 | /* Embedded content 196 | ========================================================================== */ 197 | 198 | /** 199 | * Add the correct display in IE 9-. 200 | */ 201 | 202 | audio, 203 | video { 204 | display: inline-block; 205 | } 206 | 207 | /** 208 | * Add the correct display in iOS 4-7. 209 | */ 210 | 211 | audio:not([controls]) { 212 | display: none; 213 | height: 0; 214 | } 215 | 216 | /** 217 | * Remove the border on images inside links in IE 10-. 218 | */ 219 | 220 | img { 221 | border-style: none; 222 | } 223 | 224 | /** 225 | * Hide the overflow in IE. 226 | */ 227 | 228 | svg:not(:root) { 229 | overflow: hidden; 230 | } 231 | 232 | /* Forms 233 | ========================================================================== */ 234 | 235 | /** 236 | * 1. Change the font styles in all browsers (opinionated). 237 | * 2. Remove the margin in Firefox and Safari. 238 | */ 239 | 240 | button, 241 | input, 242 | optgroup, 243 | select, 244 | textarea { 245 | font-family: sans-serif; /* 1 */ 246 | font-size: 100%; /* 1 */ 247 | line-height: 1.15; /* 1 */ 248 | margin: 0; /* 2 */ 249 | } 250 | 251 | /** 252 | * Show the overflow in IE. 253 | * 1. Show the overflow in Edge. 254 | */ 255 | 256 | button, 257 | input { /* 1 */ 258 | overflow: visible; 259 | } 260 | 261 | /** 262 | * Remove the inheritance of text transform in Edge, Firefox, and IE. 263 | * 1. Remove the inheritance of text transform in Firefox. 264 | */ 265 | 266 | button, 267 | select { /* 1 */ 268 | text-transform: none; 269 | } 270 | 271 | /** 272 | * 1. Prevent a WebKit bug where (2) destroys native `audio` and `video` 273 | * controls in Android 4. 274 | * 2. Correct the inability to style clickable types in iOS and Safari. 275 | */ 276 | 277 | button, 278 | html [type="button"], /* 1 */ 279 | [type="reset"], 280 | [type="submit"] { 281 | -webkit-appearance: button; /* 2 */ 282 | } 283 | 284 | /** 285 | * Remove the inner border and padding in Firefox. 286 | */ 287 | 288 | button::-moz-focus-inner, 289 | [type="button"]::-moz-focus-inner, 290 | [type="reset"]::-moz-focus-inner, 291 | [type="submit"]::-moz-focus-inner { 292 | border-style: none; 293 | padding: 0; 294 | } 295 | 296 | /** 297 | * Restore the focus styles unset by the previous rule. 298 | */ 299 | 300 | button:-moz-focusring, 301 | [type="button"]:-moz-focusring, 302 | [type="reset"]:-moz-focusring, 303 | [type="submit"]:-moz-focusring { 304 | outline: 1px dotted ButtonText; 305 | } 306 | 307 | /** 308 | * Correct the padding in Firefox. 309 | */ 310 | 311 | fieldset { 312 | padding: 0.35em 0.75em 0.625em; 313 | } 314 | 315 | /** 316 | * 1. Correct the text wrapping in Edge and IE. 317 | * 2. Correct the color inheritance from `fieldset` elements in IE. 318 | * 3. Remove the padding so developers are not caught out when they zero out 319 | * `fieldset` elements in all browsers. 320 | */ 321 | 322 | legend { 323 | box-sizing: border-box; /* 1 */ 324 | color: inherit; /* 2 */ 325 | display: table; /* 1 */ 326 | max-width: 100%; /* 1 */ 327 | padding: 0; /* 3 */ 328 | white-space: normal; /* 1 */ 329 | } 330 | 331 | /** 332 | * 1. Add the correct display in IE 9-. 333 | * 2. Add the correct vertical alignment in Chrome, Firefox, and Opera. 334 | */ 335 | 336 | progress { 337 | display: inline-block; /* 1 */ 338 | vertical-align: baseline; /* 2 */ 339 | } 340 | 341 | /** 342 | * Remove the default vertical scrollbar in IE. 343 | */ 344 | 345 | textarea { 346 | overflow: auto; 347 | } 348 | 349 | /** 350 | * 1. Add the correct box sizing in IE 10-. 351 | * 2. Remove the padding in IE 10-. 352 | */ 353 | 354 | [type="checkbox"], 355 | [type="radio"] { 356 | box-sizing: border-box; /* 1 */ 357 | padding: 0; /* 2 */ 358 | } 359 | 360 | /** 361 | * Correct the cursor style of increment and decrement buttons in Chrome. 362 | */ 363 | 364 | [type="number"]::-webkit-inner-spin-button, 365 | [type="number"]::-webkit-outer-spin-button { 366 | height: auto; 367 | } 368 | 369 | /** 370 | * 1. Correct the odd appearance in Chrome and Safari. 371 | * 2. Correct the outline style in Safari. 372 | */ 373 | 374 | [type="search"] { 375 | -webkit-appearance: textfield; /* 1 */ 376 | outline-offset: -2px; /* 2 */ 377 | } 378 | 379 | /** 380 | * Remove the inner padding and cancel buttons in Chrome and Safari on macOS. 381 | */ 382 | 383 | [type="search"]::-webkit-search-cancel-button, 384 | [type="search"]::-webkit-search-decoration { 385 | -webkit-appearance: none; 386 | } 387 | 388 | /** 389 | * 1. Correct the inability to style clickable types in iOS and Safari. 390 | * 2. Change font properties to `inherit` in Safari. 391 | */ 392 | 393 | ::-webkit-file-upload-button { 394 | -webkit-appearance: button; /* 1 */ 395 | font: inherit; /* 2 */ 396 | } 397 | 398 | /* Interactive 399 | ========================================================================== */ 400 | 401 | /* 402 | * Add the correct display in IE 9-. 403 | * 1. Add the correct display in Edge, IE, and Firefox. 404 | */ 405 | 406 | details, /* 1 */ 407 | menu { 408 | display: block; 409 | } 410 | 411 | /* 412 | * Add the correct display in all browsers. 413 | */ 414 | 415 | summary { 416 | display: list-item; 417 | } 418 | 419 | /* Scripting 420 | ========================================================================== */ 421 | 422 | /** 423 | * Add the correct display in IE 9-. 424 | */ 425 | 426 | canvas { 427 | display: inline-block; 428 | } 429 | 430 | /** 431 | * Add the correct display in IE. 432 | */ 433 | 434 | template { 435 | display: none; 436 | } 437 | 438 | /* Hidden 439 | ========================================================================== */ 440 | 441 | /** 442 | * Add the correct display in IE 10-. 443 | */ 444 | 445 | [hidden] { 446 | display: none; 447 | } -------------------------------------------------------------------------------- /static/main.js: -------------------------------------------------------------------------------- 1 | /* global hljs */ 2 | window.addEventListener('load', () => { 3 | document.querySelectorAll('pre>code').forEach(block => { 4 | hljs.highlightBlock(block) 5 | }) 6 | 7 | const aside = document.querySelector('aside') 8 | document.querySelector('.more').addEventListener('click', () => { 9 | const display = aside.style.display 10 | 11 | aside.style.display = display !== 'inline-block' ? 'inline-block' : 'none' 12 | }) 13 | }) 14 | --------------------------------------------------------------------------------