├── .circleci └── config.yml ├── .gitignore ├── lib ├── attribute-parser │ ├── attribute-parser.js │ ├── attribute-parser.spec.js │ └── index.js ├── example-formatter │ ├── example-formatter.js │ ├── example-formatter.spec.js │ └── index.js ├── helpers │ ├── .gitkeep │ ├── debug.js │ ├── idify.js │ └── stringify.js ├── payload-example │ ├── index.js │ └── payload-example.js ├── plugin.js ├── public │ ├── css │ │ ├── ending.css │ │ └── screen.css │ ├── fonts │ │ ├── slate.ttf │ │ └── slate.woff │ ├── img │ │ ├── hapi-logo.svg │ │ └── navbar.png │ └── js │ │ └── all.js ├── route-flattener │ ├── index.js │ ├── route-flattener.js │ └── route-flattener.spec.js ├── route-settings-parser │ ├── index.js │ ├── route-settings-parser.js │ └── route-settings-parser.spec.js └── views │ └── root.handlebars ├── package-lock.json ├── package.json ├── plugin.js ├── readme.md ├── screenshot.png └── test ├── demo.js ├── hapi-ending.functional.spec.js └── routes └── example.js /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | jobs: 3 | build: 4 | docker: 5 | - image: circleci/node:10 6 | 7 | working_directory: ~/workspace 8 | 9 | steps: 10 | - checkout 11 | 12 | - restore_cache: 13 | keys: 14 | - v1-dependencies-{{ checksum "package.json" }} 15 | 16 | - run: npm install 17 | 18 | - run: npm test 19 | 20 | - save_cache: 21 | paths: 22 | - node_modules 23 | key: v1-dependencies-{{ checksum "package.json" }} 24 | 25 | - run: 26 | name: Eslint 27 | command: npm run lint 28 | 29 | - store_test_results: 30 | path: reports/junit 31 | 32 | publish: 33 | docker: 34 | - image: circleci/node:10 35 | 36 | steps: 37 | - add_ssh_keys 38 | - checkout 39 | 40 | - run: 41 | name: Authorize NPM 42 | command: echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" > ~/.npmrc 43 | 44 | - restore_cache: 45 | keys: 46 | - v1-dependencies-{{ checksum "package.json" }} 47 | 48 | - run: npm install 49 | 50 | - run: npm test 51 | 52 | - save_cache: 53 | paths: 54 | - node_modules 55 | key: v1-dependencies-{{ checksum "package.json" }} 56 | 57 | - run: 58 | name: Publish to NPM 59 | command: npm publish 60 | 61 | workflows: 62 | version: 2 63 | main: 64 | jobs: 65 | - build: 66 | context: org-global 67 | filters: 68 | branches: 69 | only: master 70 | tags: 71 | only: /v.*/ 72 | - publish: 73 | context: org-global 74 | requires: 75 | - build 76 | filters: 77 | branches: 78 | ignore: /.*/ 79 | tags: 80 | only: /v.*/ -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | *.log 3 | -------------------------------------------------------------------------------- /lib/attribute-parser/attribute-parser.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { transform } = require('reorient') 4 | const examples = require('../payload-example') 5 | 6 | function isIterable (obj) { 7 | if (obj == null) { 8 | return false 9 | } 10 | return obj[Symbol.iterator] !== undefined 11 | } 12 | 13 | function extractValidParams (data) { 14 | const valids = data._valids 15 | const isSet = valids && valids._set 16 | const items = isSet ? Array.from(valids._set) : [] 17 | return items.length > 0 ? items : undefined 18 | } 19 | 20 | function parse (children, parentKey, master) { 21 | if (!isIterable(children)) { return } 22 | 23 | for (let param of children) { 24 | const key = `${parentKey ? parentKey + '.' : ''}${param.key}` 25 | 26 | const schema = { 27 | type: '_type', 28 | valid: extractValidParams, 29 | description: '_description' 30 | } 31 | 32 | master[key] = transform(param.schema, schema) 33 | 34 | if (param.schema._type === 'object') { 35 | parse(param.schema._inner.children, key, master) 36 | } else { 37 | const { type } = master[key] 38 | master[key].example = examples[type] || `<${type}>` 39 | } 40 | } 41 | } 42 | 43 | exports.parse = parse 44 | -------------------------------------------------------------------------------- /lib/attribute-parser/attribute-parser.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { expect } = require('code') 4 | const { parse } = require('.') 5 | 6 | describe('attribute-parser', () => { 7 | context('Not iterable', () => { 8 | it('returns', () => { 9 | expect( 10 | parse(null) 11 | ).to.equal() 12 | }) 13 | }) 14 | 15 | context('parses validation schema', () => { 16 | const children = [ 17 | { 18 | key: 'key', 19 | schema: { 20 | _type: 'string', 21 | _valids: {}, 22 | _description: 'description' 23 | } 24 | }, 25 | { 26 | key: 'key2', 27 | schema: { 28 | _type: 'randomtype', 29 | _valids: {}, 30 | _description: 'description2' 31 | } 32 | }, 33 | { 34 | key: 'key3', 35 | schema: { 36 | _type: 'string', 37 | _valids: { 38 | _set: new Set(['foo', 'bar']) 39 | }, 40 | _description: 'description3' 41 | } 42 | } 43 | ] 44 | const parentKey = 'parent' 45 | const master = {} 46 | const expectedKey = 'parent.key' 47 | 48 | beforeEach(() => { 49 | parse(children, parentKey, master) 50 | }) 51 | 52 | it('augments master', () => { 53 | expect(master).to.include(expectedKey) 54 | }) 55 | 56 | it('maps type', () => { 57 | expect(master[expectedKey].type).to.equal('string') 58 | }) 59 | 60 | it('maps description', () => { 61 | expect(master[expectedKey].description).to.equal('description') 62 | }) 63 | 64 | it('maps example', () => { 65 | expect(master[expectedKey].example).to.equal('qux') 66 | }) 67 | 68 | it('maps example', () => { 69 | expect(master[`${expectedKey}2`].example).to.equal('') 70 | }) 71 | 72 | it('maps valids', () => { 73 | expect( 74 | master[`${expectedKey}3`].valid 75 | ).to.equal([ 'foo', 'bar' ]) 76 | }) 77 | }) 78 | 79 | context('no parent key', () => { 80 | const children = [ 81 | { 82 | key: 'key', 83 | schema: { 84 | _type: 'string', 85 | _valids: {}, 86 | _description: 'description' 87 | } 88 | } 89 | ] 90 | const parentKey = null 91 | const master = {} 92 | const expectedKey = 'key' 93 | 94 | beforeEach(() => { 95 | parse(children, parentKey, master) 96 | }) 97 | 98 | it('augments master', () => { 99 | expect(master).to.include(expectedKey) 100 | }) 101 | }) 102 | 103 | context('nested validation', () => { 104 | const children = [ 105 | { 106 | key: 'key', 107 | schema: { 108 | _type: 'object', 109 | _valids: {}, 110 | _description: 'description', 111 | _inner: { 112 | children: [ 113 | { 114 | key: 'nested-key', 115 | schema: { 116 | _type: 'number', 117 | _valids: {}, 118 | _description: 'nested-description' 119 | } 120 | } 121 | ] 122 | } 123 | } 124 | } 125 | ] 126 | const parentKey = 'parent' 127 | const master = {} 128 | const expectedKey = 'parent.key.nested-key' 129 | 130 | beforeEach(() => { 131 | parse(children, parentKey, master) 132 | }) 133 | 134 | it('augments master', () => { 135 | expect(master).to.include(expectedKey) 136 | }) 137 | 138 | it('maps type', () => { 139 | expect(master[expectedKey].type).to.equal('number') 140 | }) 141 | 142 | it('maps description', () => { 143 | expect(master[expectedKey].description).to.equal('nested-description') 144 | }) 145 | 146 | it('maps example', () => { 147 | expect(master[expectedKey].example).to.equal(123) 148 | }) 149 | }) 150 | }) 151 | -------------------------------------------------------------------------------- /lib/attribute-parser/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | module.exports = require('./attribute-parser') 4 | -------------------------------------------------------------------------------- /lib/example-formatter/example-formatter.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const dot = require('dot-object') 4 | 5 | function getExample (items, key) { 6 | return (items[key].valid && items[key].valid.length > 1) ? items[key].valid[0] : items[key].example 7 | } 8 | 9 | const exampleFormats = { 10 | query: function (items) { 11 | return Object.keys(items).map((key) => { 12 | return `${key}=${getExample(items, key)}` 13 | }).join('&') 14 | }, 15 | payload: function (items) { 16 | const mapping = Object.keys(items).reduce((acc, key) => { 17 | acc[key] = getExample(items, key) 18 | return acc 19 | }, {}) 20 | return dot.object(mapping) 21 | }, 22 | params: function (items, entry) { 23 | return Object.keys(items).reduce((acc, key) => { 24 | return acc.replace('{' + key + '}', getExample(items, key)) 25 | }, entry.path) 26 | } 27 | } 28 | 29 | module.exports = function (type, items, entry) { 30 | return exampleFormats[type](items, entry) 31 | } 32 | -------------------------------------------------------------------------------- /lib/example-formatter/example-formatter.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { expect } = require('code') 4 | const format = require('.') 5 | 6 | describe('example-formatter', () => { 7 | const items = { 8 | foo: { 9 | example: 'bar' 10 | }, 11 | baz: { 12 | example: 'qux' 13 | }, 14 | quux: { 15 | valid: ['zug', 'zif'], 16 | example: 'fee' 17 | } 18 | } 19 | 20 | context('query', () => { 21 | it('creates example', () => { 22 | expect( 23 | format('query', items) 24 | ).to.equal( 25 | 'foo=bar&baz=qux&quux=zug' 26 | ) 27 | }) 28 | }) 29 | 30 | context('params', () => { 31 | it('creates example', () => { 32 | const entry = { 33 | path: '/some/{foo}/path/{baz}/{quux}' 34 | } 35 | 36 | expect( 37 | format('params', items, entry) 38 | ).to.equal( 39 | '/some/bar/path/qux/zug' 40 | ) 41 | }) 42 | }) 43 | 44 | context('payload', () => { 45 | it('creates example', () => { 46 | expect( 47 | format('payload', items) 48 | ).to.equal({ 49 | foo: 'bar', 50 | baz: 'qux', 51 | quux: 'zug' 52 | }) 53 | }) 54 | 55 | it('creates nested example', () => { 56 | const nested = { 57 | 'foo.whee': { 58 | example: 'lux' 59 | } 60 | } 61 | 62 | expect( 63 | format('payload', nested) 64 | ).to.equal({ 65 | foo: { 66 | whee: 'lux' 67 | } 68 | }) 69 | }) 70 | }) 71 | }) 72 | -------------------------------------------------------------------------------- /lib/example-formatter/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | module.exports = require('./example-formatter') 4 | -------------------------------------------------------------------------------- /lib/helpers/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/desirable-objects/hapi-ending/3e3e29c35dc71cdab1b7755318f66219d4da5b3e/lib/helpers/.gitkeep -------------------------------------------------------------------------------- /lib/helpers/debug.js: -------------------------------------------------------------------------------- 1 | module.exports = function (optionalValue) { 2 | console.log('Current Context') 3 | console.log('====================') 4 | console.log(this) 5 | 6 | if (optionalValue) { 7 | console.log('Value') 8 | console.log('====================') 9 | console.log(optionalValue) 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /lib/helpers/idify.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | module.exports = function (part1, part2) { 4 | let ep = `${part2.toString()}__${part1.toString()}` 5 | return Buffer.from(ep).toString('base64') 6 | } 7 | -------------------------------------------------------------------------------- /lib/helpers/stringify.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | module.exports = function (json) { 4 | return JSON.stringify(json, null, '\t') 5 | } 6 | -------------------------------------------------------------------------------- /lib/payload-example/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | module.exports = require('./payload-example') 4 | -------------------------------------------------------------------------------- /lib/payload-example/payload-example.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | module.exports = { 4 | string: 'qux', 5 | number: 123, 6 | array: ['foo', 'bar', 'baz'], 7 | object: { foo: 'bar' }, 8 | boolean: true, 9 | date: new Date().toISOString() 10 | } 11 | -------------------------------------------------------------------------------- /lib/plugin.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { join } = require('path') 4 | const Inert = require('inert') 5 | const Vision = require('vision') 6 | const handlebars = require('handlebars') 7 | const pkg = require('../package.json') 8 | const { flatten } = require('./route-flattener') 9 | 10 | exports.register = async function (server, options) { 11 | if (!options.enabled) { 12 | return 13 | } 14 | 15 | server.path(join(__dirname, '.')) 16 | 17 | try { 18 | await server.register([ 19 | Inert, 20 | Vision 21 | ]) 22 | } catch (err) { 23 | if (err) { 24 | console.error('Failed to register plugin dependencies:', err) 25 | } 26 | } 27 | 28 | const views = { 29 | engines: { 30 | handlebars 31 | }, 32 | path: './views', 33 | helpersPath: './helpers' 34 | } 35 | 36 | server.views(views) 37 | 38 | function routing (svr, options) { 39 | let assetsPath = options.assetsPath || 'assets' 40 | 41 | let allRoutes = server.table() 42 | 43 | return { 44 | routes: flatten(allRoutes), 45 | baseUrl: options.baseUrl, 46 | assetsPath, 47 | logoUrl: options.logoUrl || `/${assetsPath}/img/hapi-logo.svg`, 48 | documentationUrl: options.path || '' 49 | } 50 | } 51 | 52 | server.method('routing', routing, { 53 | generateKey: () => { 54 | return 'routing' 55 | }, 56 | cache: { expiresIn: 60000, generateTimeout: 30000 } 57 | }) 58 | 59 | server.route({ 60 | method: 'GET', 61 | path: `${options.path || ''}/${options.assetsPath || 'assets'}/{param*}`, 62 | config: { 63 | auth: false, 64 | description: 'Asset delivery url for hapi-ending plugin', 65 | tags: ['metadata', 'private', 'api', 'assets'] 66 | }, 67 | handler: { 68 | directory: { 69 | path: './public', 70 | listing: true 71 | } 72 | } 73 | }) 74 | 75 | server.route({ 76 | method: 'GET', 77 | path: options.path || '/', 78 | config: { 79 | auth: false, 80 | description: 'Describes endpoints in your application', 81 | tags: ['metadata', 'private', 'api'] 82 | }, 83 | handler: async function (request, h) { 84 | let model = await request.server.methods.routing(request.server, options) 85 | return h.view('root', model) 86 | } 87 | }) 88 | } 89 | 90 | exports.name = 'hapi-ending' 91 | exports.pkg = pkg 92 | -------------------------------------------------------------------------------- /lib/public/css/ending.css: -------------------------------------------------------------------------------- 1 | .tags, .valids { 2 | list-style-type: none; 3 | } 4 | .tag, .valid { 5 | display: inline; 6 | padding: 4px 7px; 7 | margin-left: 4px; 8 | border-radius: 2px; 9 | } 10 | .tag { 11 | background-color: #c6dde9; 12 | } 13 | .valids { 14 | margin: 0; 15 | padding: 0; 16 | } 17 | .valid { 18 | background-color: #6ac174; 19 | } 20 | .method { 21 | text-transform: uppercase; 22 | background-color: #c6dde9; 23 | } 24 | -------------------------------------------------------------------------------- /lib/public/css/screen.css: -------------------------------------------------------------------------------- 1 | /*! normalize.css v2.0.1 | MIT License | git.io/normalize */article,aside,details,figcaption,figure,footer,header,hgroup,nav,section,summary{display:block}audio,canvas,video{display:inline-block}audio:not([controls]){display:none;height:0}[hidden]{display:none}html{font-family:sans-serif;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}body{margin:0}a:focus{outline:thin dotted}a:active,a:hover{outline:0}h1{font-size:2em}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:bold}dfn{font-style:italic}mark{background:#ff0;color:#000}code,kbd,pre,samp{font-family:monospace, serif;font-size:1em}pre{white-space:pre;white-space:pre-wrap;word-wrap:break-word}q{quotes:"\201C" "\201D" "\2018" "\2019"}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-0.5em}sub{bottom:-0.25em}img{border:0}svg:not(:root){overflow:hidden}figure{margin:0}fieldset{border:1px solid #c0c0c0;margin:0 2px;padding:0.35em 0.625em 0.75em}legend{border:0;padding:0}button,input,select,textarea{font-family:inherit;font-size:100%;margin:0}button,input{line-height:normal}button,html input[type="button"],input[type="reset"],input[type="submit"]{-webkit-appearance:button;cursor:pointer}button[disabled],input[disabled]{cursor:default}input[type="checkbox"],input[type="radio"]{box-sizing:border-box;padding:0}input[type="search"]{-webkit-appearance:textfield;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box}input[type="search"]::-webkit-search-cancel-button,input[type="search"]::-webkit-search-decoration{-webkit-appearance:none}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}textarea{overflow:auto;vertical-align:top}table{border-collapse:collapse;border-spacing:0}.content h1,.content h2,.content h3,.content h4,.content h5,.content h6,html,body{font-family:"Helvetica Neue", Helvetica, Arial, sans-serif;font-size:13px}.content h1,.content h2,.content h3,.content h4,.content h5,.content h6{font-weight:bold}.content code,.content pre{font-family:Consolas, Menlo, Monaco, "Lucida Console", "Liberation Mono", "DejaVu Sans Mono", "Bitstream Vera Sans Mono", "Courier New", monospace, serif;font-size:12px;line-height:1.5}.content code{-ms-word-break:break-all;word-break:break-all;word-break:break-word;-webkit-hyphens:auto;-moz-hyphens:auto;hyphens:auto}.highlight table td{padding:5px}.highlight table pre{margin:0}.highlight,.highlight .w{color:#f8f8f2;background-color:#272822}.highlight .err{color:#151515;background-color:#ac4142}.highlight .c,.highlight .cd,.highlight .cm,.highlight .c1,.highlight .cs{color:#505050}.highlight .cp{color:#f4bf75}.highlight .nt{color:#f4bf75}.highlight .o,.highlight .ow{color:#d0d0d0}.highlight .p,.highlight .pi{color:#d0d0d0}.highlight .gi{color:#90a959}.highlight .gd{color:#ac4142}.highlight .gh{color:#6a9fb5;background-color:#151515;font-weight:bold}.highlight .k,.highlight .kn,.highlight .kp,.highlight .kr,.highlight .kv{color:#aa759f}.highlight .kc{color:#d28445}.highlight .kt{color:#d28445}.highlight .kd{color:#d28445}.highlight .s,.highlight .sb,.highlight .sc,.highlight .sd,.highlight .s2,.highlight .sh,.highlight .sx,.highlight .s1{color:#90a959}.highlight .sr{color:#75b5aa}.highlight .si{color:#8f5536}.highlight .se{color:#8f5536}.highlight .nn{color:#f4bf75}.highlight .nc{color:#f4bf75}.highlight .no{color:#f4bf75}.highlight .na{color:#6a9fb5}.highlight .m,.highlight .mf,.highlight .mh,.highlight .mi,.highlight .il,.highlight .mo,.highlight .mb,.highlight .mx{color:#90a959}.highlight .ss{color:#90a959}.highlight .c,.highlight .cm,.highlight .c1,.highlight .cs{color:#909090}.highlight,.highlight .w{background-color:#292929}@font-face{font-family:'slate';src:url("../fonts/slate.eot");src:url("../fonts/slate.eot?#iefix") format("embedded-opentype"),url("../fonts/slate.ttf") format("truetype"),url("../fonts/slate.woff") format("woff"),url("../fonts/slate.svg#slate") format("svg");font-weight:normal;font-style:normal}.content aside.warning:before,.content aside.notice:before,.content aside.success:before,.tocify-wrapper>.search:before{font-family:'slate';speak:none;font-style:normal;font-weight:normal;font-variant:normal;text-transform:none;line-height:1}.content aside.warning:before{content:"\e600"}.content aside.notice:before{content:"\e602"}.content aside.success:before{content:"\e606"}.tocify-wrapper>.search:before{content:"\e607"}html,body{color:#333;padding:0;margin:0;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;background-color:#eaf2f6;height:100%;-webkit-text-size-adjust:none}#toc>ul>li>a>span{float:right;background-color:#2484FF;border-radius:40px;width:20px}.tocify-wrapper{-moz-transition:left 0.3s ease-in-out;-o-transition:left 0.3s ease-in-out;-webkit-transition:left 0.3s ease-in-out;transition:left 0.3s ease-in-out;overflow-y:auto;overflow-x:hidden;position:fixed;z-index:30;top:0;left:0;bottom:0;width:230px;background-color:#393939;font-size:13px;font-weight:bold}.tocify-wrapper .lang-selector{display:none}.tocify-wrapper .lang-selector a{padding-top:0.5em;padding-bottom:0.5em}.tocify-wrapper>img{display:block}.tocify-wrapper>.search{position:relative}.tocify-wrapper>.search input{background:#393939;border-width:0 0 1px 0;border-color:#666;padding:6px 0 6px 20px;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box;margin:10px 15px;width:200px;outline:none;color:#fff;border-radius:0}.tocify-wrapper>.search:before{position:absolute;top:17px;left:15px;color:#fff}.tocify-wrapper img+.tocify{margin-top:20px}.tocify-wrapper .search-results{margin-top:0;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box;height:0;overflow-y:auto;overflow-x:hidden;-moz-transition-property:height, margin;-o-transition-property:height, margin;-webkit-transition-property:height, margin;transition-property:height margin;-moz-transition-duration:180ms;-o-transition-duration:180ms;-webkit-transition-duration:180ms;transition-duration:180ms;-moz-transition-timing-function:ease-in-out;-o-transition-timing-function:ease-in-out;-webkit-transition-timing-function:ease-in-out;transition-timing-function:ease-in-out;background:url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4gPHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJncmFkIiBncmFkaWVudFVuaXRzPSJvYmplY3RCb3VuZGluZ0JveCIgeDE9IjAuNSIgeTE9IjAuMCIgeDI9IjAuNSIgeTI9IjEuMCI+PHN0b3Agb2Zmc2V0PSIwJSIgc3RvcC1jb2xvcj0iIzAwMDAwMCIgc3RvcC1vcGFjaXR5PSIwLjIiLz48c3RvcCBvZmZzZXQ9IjEwMCUiIHN0b3AtY29sb3I9IiMwMDAwMDAiIHN0b3Atb3BhY2l0eT0iMC4wIi8+PC9saW5lYXJHcmFkaWVudD48L2RlZnM+PHJlY3QgeD0iMCIgeT0iMCIgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0idXJsKCNncmFkKSIgLz48L3N2Zz4g'),url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4gPHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJncmFkIiBncmFkaWVudFVuaXRzPSJvYmplY3RCb3VuZGluZ0JveCIgeDE9IjAuNSIgeTE9IjEuMCIgeDI9IjAuNSIgeTI9IjAuMCI+PHN0b3Agb2Zmc2V0PSIwJSIgc3RvcC1jb2xvcj0iIzAwMDAwMCIgc3RvcC1vcGFjaXR5PSIwLjIiLz48c3RvcCBvZmZzZXQ9IjEwMCUiIHN0b3AtY29sb3I9IiMwMDAwMDAiIHN0b3Atb3BhY2l0eT0iMC4wIi8+PC9saW5lYXJHcmFkaWVudD48L2RlZnM+PHJlY3QgeD0iMCIgeT0iMCIgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0idXJsKCNncmFkKSIgLz48L3N2Zz4g'),url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4gPHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJncmFkIiBncmFkaWVudFVuaXRzPSJvYmplY3RCb3VuZGluZ0JveCIgeDE9IjAuNSIgeTE9IjAuMCIgeDI9IjAuNSIgeTI9IjEuMCI+PHN0b3Agb2Zmc2V0PSIwJSIgc3RvcC1jb2xvcj0iIzAwMDAwMCIvPjxzdG9wIG9mZnNldD0iMTAwJSIgc3RvcC1jb2xvcj0iIzAwMDAwMCIgc3RvcC1vcGFjaXR5PSIwLjAiLz48L2xpbmVhckdyYWRpZW50PjwvZGVmcz48cmVjdCB4PSIwIiB5PSIwIiB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSJ1cmwoI2dyYWQpIiAvPjwvc3ZnPiA='),url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4gPHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJncmFkIiBncmFkaWVudFVuaXRzPSJvYmplY3RCb3VuZGluZ0JveCIgeDE9IjAuNSIgeTE9IjEuMCIgeDI9IjAuNSIgeTI9IjAuMCI+PHN0b3Agb2Zmc2V0PSIwJSIgc3RvcC1jb2xvcj0iIzkzOTM5MyIvPjxzdG9wIG9mZnNldD0iMTAwJSIgc3RvcC1jb2xvcj0iIzkzOTM5MyIgc3RvcC1vcGFjaXR5PSIwLjAiLz48L2xpbmVhckdyYWRpZW50PjwvZGVmcz48cmVjdCB4PSIwIiB5PSIwIiB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSJ1cmwoI2dyYWQpIiAvPjwvc3ZnPiA='),#262626;background:-webkit-gradient(linear, 50% 0%, 50% 8, color-stop(0%, rgba(0,0,0,0.2)), color-stop(100%, transparent)),-webkit-gradient(linear, 50% 100%, 50% 0%, color-stop(0%, rgba(0,0,0,0.2)), color-stop(100%, transparent)),-webkit-gradient(linear, 50% 0%, 50% 1.5, color-stop(0%, #000), color-stop(100%, transparent)),-webkit-gradient(linear, 50% 100%, 50% 0%, color-stop(0%, #939393), color-stop(100%, rgba(147,147,147,0))),#262626;background:-moz-linear-gradient(top, rgba(0,0,0,0.2), transparent 8px),-moz-linear-gradient(bottom, rgba(0,0,0,0.2), transparent 8px),-moz-linear-gradient(top, #000, transparent 1.5px),-moz-linear-gradient(bottom, #939393, rgba(147,147,147,0) 1.5px),#262626;background:-webkit-linear-gradient(top, rgba(0,0,0,0.2), transparent 8px),-webkit-linear-gradient(bottom, rgba(0,0,0,0.2), transparent 8px),-webkit-linear-gradient(top, #000, transparent 1.5px),-webkit-linear-gradient(bottom, #939393, rgba(147,147,147,0) 1.5px),#262626;background:linear-gradient(to bottom, rgba(0,0,0,0.2), transparent 8px),linear-gradient(to top, rgba(0,0,0,0.2), transparent 8px),linear-gradient(to bottom, #000, transparent 1.5px),linear-gradient(to top, #939393, rgba(147,147,147,0) 1.5px),#262626}.tocify-wrapper .search-results.visible{height:30%;margin-bottom:1em}.tocify-wrapper .search-results li{margin:1em 15px;line-height:1}.tocify-wrapper .search-results a{color:#fff;text-decoration:none}.tocify-wrapper .search-results a:hover{text-decoration:underline}.tocify-wrapper .tocify-item>a,.tocify-wrapper .toc-footer li{padding:0 15px 0 15px;display:block;overflow-x:hidden;white-space:nowrap;text-overflow:ellipsis}.tocify-wrapper ul,.tocify-wrapper li{list-style:none;margin:0;padding:0;line-height:28px}.tocify-wrapper li{color:#fff;-moz-transition-property:"background";-o-transition-property:"background";-webkit-transition-property:"background";transition-property:"background";-moz-transition-timing-function:"linear";-o-transition-timing-function:"linear";-webkit-transition-timing-function:"linear";transition-timing-function:"linear";-moz-transition-duration:230ms;-o-transition-duration:230ms;-webkit-transition-duration:230ms;transition-duration:230ms}.tocify-wrapper .tocify-focus{-moz-box-shadow:0px 1px 0px #000;-webkit-box-shadow:0px 1px 0px #000;box-shadow:0px 1px 0px #000;background-color:#2467af;color:#fff}.tocify-wrapper .tocify-subheader{display:none;background-color:#262626;font-weight:500;background:url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4gPHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJncmFkIiBncmFkaWVudFVuaXRzPSJvYmplY3RCb3VuZGluZ0JveCIgeDE9IjAuNSIgeTE9IjAuMCIgeDI9IjAuNSIgeTI9IjEuMCI+PHN0b3Agb2Zmc2V0PSIwJSIgc3RvcC1jb2xvcj0iIzAwMDAwMCIgc3RvcC1vcGFjaXR5PSIwLjIiLz48c3RvcCBvZmZzZXQ9IjEwMCUiIHN0b3AtY29sb3I9IiMwMDAwMDAiIHN0b3Atb3BhY2l0eT0iMC4wIi8+PC9saW5lYXJHcmFkaWVudD48L2RlZnM+PHJlY3QgeD0iMCIgeT0iMCIgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0idXJsKCNncmFkKSIgLz48L3N2Zz4g'),url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4gPHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJncmFkIiBncmFkaWVudFVuaXRzPSJvYmplY3RCb3VuZGluZ0JveCIgeDE9IjAuNSIgeTE9IjEuMCIgeDI9IjAuNSIgeTI9IjAuMCI+PHN0b3Agb2Zmc2V0PSIwJSIgc3RvcC1jb2xvcj0iIzAwMDAwMCIgc3RvcC1vcGFjaXR5PSIwLjIiLz48c3RvcCBvZmZzZXQ9IjEwMCUiIHN0b3AtY29sb3I9IiMwMDAwMDAiIHN0b3Atb3BhY2l0eT0iMC4wIi8+PC9saW5lYXJHcmFkaWVudD48L2RlZnM+PHJlY3QgeD0iMCIgeT0iMCIgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0idXJsKCNncmFkKSIgLz48L3N2Zz4g'),url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4gPHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJncmFkIiBncmFkaWVudFVuaXRzPSJvYmplY3RCb3VuZGluZ0JveCIgeDE9IjAuNSIgeTE9IjAuMCIgeDI9IjAuNSIgeTI9IjEuMCI+PHN0b3Agb2Zmc2V0PSIwJSIgc3RvcC1jb2xvcj0iIzAwMDAwMCIvPjxzdG9wIG9mZnNldD0iMTAwJSIgc3RvcC1jb2xvcj0iIzAwMDAwMCIgc3RvcC1vcGFjaXR5PSIwLjAiLz48L2xpbmVhckdyYWRpZW50PjwvZGVmcz48cmVjdCB4PSIwIiB5PSIwIiB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSJ1cmwoI2dyYWQpIiAvPjwvc3ZnPiA='),url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4gPHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJncmFkIiBncmFkaWVudFVuaXRzPSJvYmplY3RCb3VuZGluZ0JveCIgeDE9IjAuNSIgeTE9IjEuMCIgeDI9IjAuNSIgeTI9IjAuMCI+PHN0b3Agb2Zmc2V0PSIwJSIgc3RvcC1jb2xvcj0iIzkzOTM5MyIvPjxzdG9wIG9mZnNldD0iMTAwJSIgc3RvcC1jb2xvcj0iIzkzOTM5MyIgc3RvcC1vcGFjaXR5PSIwLjAiLz48L2xpbmVhckdyYWRpZW50PjwvZGVmcz48cmVjdCB4PSIwIiB5PSIwIiB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSJ1cmwoI2dyYWQpIiAvPjwvc3ZnPiA='),#262626;background:-webkit-gradient(linear, 50% 0%, 50% 8, color-stop(0%, rgba(0,0,0,0.2)), color-stop(100%, transparent)),-webkit-gradient(linear, 50% 100%, 50% 0%, color-stop(0%, rgba(0,0,0,0.2)), color-stop(100%, transparent)),-webkit-gradient(linear, 50% 0%, 50% 1.5, color-stop(0%, #000), color-stop(100%, transparent)),-webkit-gradient(linear, 50% 100%, 50% 0%, color-stop(0%, #939393), color-stop(100%, rgba(147,147,147,0))),#262626;background:-moz-linear-gradient(top, rgba(0,0,0,0.2), transparent 8px),-moz-linear-gradient(bottom, rgba(0,0,0,0.2), transparent 8px),-moz-linear-gradient(top, #000, transparent 1.5px),-moz-linear-gradient(bottom, #939393, rgba(147,147,147,0) 1.5px),#262626;background:-webkit-linear-gradient(top, rgba(0,0,0,0.2), transparent 8px),-webkit-linear-gradient(bottom, rgba(0,0,0,0.2), transparent 8px),-webkit-linear-gradient(top, #000, transparent 1.5px),-webkit-linear-gradient(bottom, #939393, rgba(147,147,147,0) 1.5px),#262626;background:linear-gradient(to bottom, rgba(0,0,0,0.2), transparent 8px),linear-gradient(to top, rgba(0,0,0,0.2), transparent 8px),linear-gradient(to bottom, #000, transparent 1.5px),linear-gradient(to top, #939393, rgba(147,147,147,0) 1.5px),#262626}.tocify-wrapper .tocify-subheader .tocify-item>a{padding-left:25px;font-size:12px}.tocify-wrapper .tocify-subheader>li:last-child{box-shadow:none}.tocify-wrapper .toc-footer{padding:1em 0;margin-top:1em;border-top:1px dashed #666}.tocify-wrapper .toc-footer li,.tocify-wrapper .toc-footer a{color:#fff;text-decoration:none}.tocify-wrapper .toc-footer a:hover{text-decoration:underline}.tocify-wrapper .toc-footer li{font-size:0.8em;line-height:1.7;text-decoration:none}#nav-button{padding:0 1.5em 5em 0;display:none;position:fixed;top:0;left:0;z-index:100;color:#000;text-decoration:none;font-weight:bold;opacity:0.7;line-height:16px;-moz-transition:left 0.3s ease-in-out;-o-transition:left 0.3s ease-in-out;-webkit-transition:left 0.3s ease-in-out;transition:left 0.3s ease-in-out}#nav-button span{display:block;padding:6px 6px 6px;background-color:rgba(234,242,246,0.7);-moz-transform-origin:0 0;-ms-transform-origin:0 0;-webkit-transform-origin:0 0;transform-origin:0 0;-moz-transform:rotate(-90deg) translate(-100%, 0);-ms-transform:rotate(-90deg) translate(-100%, 0);-webkit-transform:rotate(-90deg) translate(-100%, 0);transform:rotate(-90deg) translate(-100%, 0);border-radius:0 0 0 5px}#nav-button img{height:16px;vertical-align:bottom}#nav-button:hover{opacity:1}#nav-button.open{left:230px}.page-wrapper{margin-left:230px;position:relative;z-index:10;background-color:#eaf2f6;min-height:100%;padding-bottom:1px}.page-wrapper .dark-box{width:50%;background-color:#393939;position:absolute;right:0;top:0;bottom:0}.page-wrapper .lang-selector{position:fixed;z-index:50;border-bottom:5px solid #393939}.lang-selector{background-color:#222;width:100%;font-weight:bold}.lang-selector a{display:block;float:left;color:#fff;text-decoration:none;padding:0 10px;line-height:30px}.lang-selector a:active{background-color:#111;color:#fff}.lang-selector a.active{background-color:#393939;color:#fff}.lang-selector:after{content:'';clear:both;display:block}.content{position:relative;z-index:30}.content:after{content:'';display:block;clear:both}.content>h1,.content>h2,.content>h3,.content>h4,.content>h5,.content>h6,.content>p,.content>table,.content>ul,.content>ol,.content>aside,.content>dl{margin-right:50%;padding:0 28px;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box;display:block;text-shadow:0px 1px 0px #fff}.content>ul,.content>ol{padding-left:43px}.content>h1,.content>h2,.content>div{clear:both}.content h1{font-size:30px;padding-top:0.5em;padding-bottom:0.5em;border-bottom:1px solid #ccc;margin-bottom:21px;margin-top:2em;border-top:1px solid #ddd;background-image:url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4gPHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJncmFkIiBncmFkaWVudFVuaXRzPSJvYmplY3RCb3VuZGluZ0JveCIgeDE9IjAuNSIgeTE9IjAuMCIgeDI9IjAuNSIgeTI9IjEuMCI+PHN0b3Agb2Zmc2V0PSIwJSIgc3RvcC1jb2xvcj0iI2ZmZmZmZiIvPjxzdG9wIG9mZnNldD0iMTAwJSIgc3RvcC1jb2xvcj0iI2Y5ZjlmOSIvPjwvbGluZWFyR3JhZGllbnQ+PC9kZWZzPjxyZWN0IHg9IjAiIHk9IjAiIHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9InVybCgjZ3JhZCkiIC8+PC9zdmc+IA==');background-size:100%;background-image:-webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #fff), color-stop(100%, #f9f9f9));background-image:-moz-linear-gradient(top, #fff, #f9f9f9);background-image:-webkit-linear-gradient(top, #fff, #f9f9f9);background-image:linear-gradient(to bottom, #fff, #f9f9f9)}.content h1:first-child,.content div:first-child+h1{border-top-width:0;margin-top:0}.content h2{font-size:20px;margin-top:4em;margin-bottom:0;border-top:1px solid #ccc;padding-top:1.2em;padding-bottom:1.2em;background-image:url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4gPHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJncmFkIiBncmFkaWVudFVuaXRzPSJvYmplY3RCb3VuZGluZ0JveCIgeDE9IjAuNSIgeTE9IjAuMCIgeDI9IjAuNSIgeTI9IjEuMCI+PHN0b3Agb2Zmc2V0PSIwJSIgc3RvcC1jb2xvcj0iI2ZmZmZmZiIgc3RvcC1vcGFjaXR5PSIwLjQiLz48c3RvcCBvZmZzZXQ9IjEwMCUiIHN0b3AtY29sb3I9IiNmZmZmZmYiIHN0b3Atb3BhY2l0eT0iMC4wIi8+PC9saW5lYXJHcmFkaWVudD48L2RlZnM+PHJlY3QgeD0iMCIgeT0iMCIgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0idXJsKCNncmFkKSIgLz48L3N2Zz4g');background-size:100%;background-image:-webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, rgba(255,255,255,0.4)), color-stop(100%, rgba(255,255,255,0)));background-image:-moz-linear-gradient(top, rgba(255,255,255,0.4), rgba(255,255,255,0));background-image:-webkit-linear-gradient(top, rgba(255,255,255,0.4), rgba(255,255,255,0));background-image:linear-gradient(to bottom, rgba(255,255,255,0.4), rgba(255,255,255,0))}.content h1+h2,.content h1+div+h2{margin-top:-21px;border-top:none}.content h3,.content h4,.content h5,.content h6{font-size:12px;margin-top:2.5em;margin-bottom:0.8em;text-transform:uppercase}.content h4,.content h5,.content h6{font-size:10px}.content hr{margin:2em 0;border-top:2px solid #393939;border-bottom:2px solid #eaf2f6}.content table{margin-bottom:1em;overflow:auto}.content table th,.content table td{text-align:left;vertical-align:top;line-height:1.6}.content table th{padding:5px 10px;border-bottom:1px solid #ccc;vertical-align:bottom}.content table td{padding:10px}.content table tr:last-child{border-bottom:1px solid #ccc}.content table tr:nth-child(odd)>td{background-color:#f9fbfc}.content table tr:nth-child(even)>td{background-color:#f3f7fa}.content dt{font-weight:bold}.content dd{margin-left:15px}.content p,.content li,.content dt,.content dd{line-height:1.6;margin-top:0}.content img{max-width:100%}.content code{background-color:rgba(0,0,0,0.05);padding:3px;border-radius:3px}.content pre>code{background-color:transparent;padding:0}.content aside{padding-top:1em;padding-bottom:1em;text-shadow:0 1px 0 #c6dde9;margin-top:1.5em;margin-bottom:1.5em;background:#8fbcd4;line-height:1.6}.content aside.warning{background-color:#c97a7e;text-shadow:0 1px 0 #dfb0b3}.content aside.success{background-color:#6ac174;text-shadow:0 1px 0 #a0d7a6}.content aside:before{vertical-align:middle;padding-right:0.5em;font-size:14px}.content .search-highlight{padding:2px;margin:-2px;border-radius:4px;border:1px solid #F7E633;text-shadow:1px 1px 0 #666;background:url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4gPHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJncmFkIiBncmFkaWVudFVuaXRzPSJvYmplY3RCb3VuZGluZ0JveCIgeDE9IjEuMCIgeTE9IjEuMCIgeDI9IjAuMCIgeTI9IjAuMCI+PHN0b3Agb2Zmc2V0PSIwJSIgc3RvcC1jb2xvcj0iI2Y3ZTYzMyIvPjxzdG9wIG9mZnNldD0iMTAwJSIgc3RvcC1jb2xvcj0iI2YxZDMyZiIvPjwvbGluZWFyR3JhZGllbnQ+PC9kZWZzPjxyZWN0IHg9IjAiIHk9IjAiIHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9InVybCgjZ3JhZCkiIC8+PC9zdmc+IA==');background:-webkit-gradient(linear, 100% 100%, 0% 0%, color-stop(0%, #f7e633), color-stop(100%, #f1d32f));background:-moz-linear-gradient(bottom right, #f7e633 0%, #f1d32f 100%);background:-webkit-linear-gradient(bottom right, #f7e633 0%, #f1d32f 100%);background:linear-gradient(to top left, #f7e633 0%, #f1d32f 100%)}.content pre,.content blockquote{background-color:#292929;color:#fff;padding:2em 28px;margin:0;width:50%;float:right;clear:right;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box;text-shadow:0px 1px 2px rgba(0,0,0,0.4)}.content pre>p,.content blockquote>p{margin:0}.content pre a,.content blockquote a{color:#fff;text-decoration:none;border-bottom:dashed 1px #ccc}.content blockquote>p{background-color:#1c1c1c;border-radius:5px;padding:13px;color:#ccc;border-top:1px solid #000;border-bottom:1px solid #404040}@media (max-width: 930px){.tocify-wrapper{left:-230px}.tocify-wrapper.open{left:0}.page-wrapper{margin-left:0}#nav-button{display:block}.tocify-wrapper .tocify-item>a{padding-top:0.3em;padding-bottom:0.3em}}@media (max-width: 700px){.dark-box{display:none}.content>h1,.content>h2,.content>h3,.content>h4,.content>h5,.content>h6,.content>p,.content>table,.content>ul,.content>ol,.content>aside,.content>dl{margin-right:0}.tocify-wrapper .lang-selector{display:block}.page-wrapper .lang-selector{display:none}.content pre,.content blockquote{width:auto;float:none}.content>pre+h1,.content>blockquote+h1,.content>pre+h2,.content>blockquote+h2,.content>pre+h3,.content>blockquote+h3,.content>pre+h4,.content>blockquote+h4,.content>pre+h5,.content>blockquote+h5,.content>pre+h6,.content>blockquote+h6,.content>pre+p,.content>blockquote+p,.content>pre+table,.content>blockquote+table,.content>pre+ul,.content>blockquote+ul,.content>pre+ol,.content>blockquote+ol,.content>pre+aside,.content>blockquote+aside,.content>pre+dl,.content>blockquote+dl{margin-top:28px}} 2 | -------------------------------------------------------------------------------- /lib/public/fonts/slate.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/desirable-objects/hapi-ending/3e3e29c35dc71cdab1b7755318f66219d4da5b3e/lib/public/fonts/slate.ttf -------------------------------------------------------------------------------- /lib/public/fonts/slate.woff: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | slate/slate.woff at master · tripit/slate 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | Skip to content 74 |
75 | 76 | 77 | 78 | 79 | 80 | 81 | 214 | 215 | 216 | 217 | 218 | 219 | 220 |
221 |
222 |
223 | 224 |
225 |
226 |
227 | 228 |
229 | 230 |
    231 | 232 |
  • 233 |
    234 | 235 |
    236 | 239 | 240 | 241 | Watch 242 | 243 | 244 | 247 | 248 |
    249 | 299 |
    300 |
    301 |
    302 |
  • 303 | 304 |
  • 305 | 306 |
    307 | 308 |
    309 | 316 | 319 |
    320 |
    321 | 328 | 331 |
    332 | 333 |
  • 334 | 335 |
  • 336 | 341 | 342 | Fork 343 | 344 | 345 | 346 | 354 |
  • 355 | 356 |
357 | 358 |

359 | 360 | /slate 363 | 364 | 365 | 366 | 367 | 368 |

369 |
370 | 371 |
372 |
373 | 374 |
375 |
376 |
377 | 378 | 379 | 428 | 429 |
430 | 431 |
433 |

HTTPS clone URL

434 |
435 | 437 | 438 | 439 | 440 |
441 |
442 | 443 | 444 |
446 |

SSH clone URL

447 |
448 | 450 | 451 | 452 | 453 |
454 |
455 | 456 | 457 |
459 |

Subversion checkout URL

460 |
461 | 463 | 464 | 465 | 466 |
467 |
468 | 469 | 470 | 471 |
You can clone with 472 |
,
, or
. 473 | 474 | 475 | 476 |
477 | 478 | 483 | 484 | Download ZIP 485 | 486 |
487 |
488 |
489 | 490 | 491 | 492 | 493 | 494 | 495 | 496 |
497 | 498 |
499 | 503 | Branch: 504 | master 505 | 506 | 507 | 619 |
620 | 621 |
622 | 627 | 628 | 629 | 630 |
631 | 632 | 635 |
636 | 637 | 638 |
639 | Fetching contributors… 640 |
641 | 642 |
643 |

644 |

Cannot retrieve contributors at this time

645 |
646 |
647 |
648 |
649 |
650 | 651 |
652 | Raw 653 | History 654 |
655 | 656 | 657 | 658 |
659 | 662 |
663 | 664 |
665 | executable file 666 | 667 | 1.796 kB 668 |
669 |
670 | 671 | 672 |
673 |
674 | View Raw 675 |
676 |
677 | 678 |
679 | 680 | Jump to Line 681 | 686 | 687 |
688 |
689 | 690 |
691 |
692 | 693 | 694 |
695 | 696 |
697 | 720 |
721 | 722 | 723 |
724 |
725 |
726 | 727 |
728 |
729 |
730 |
731 |
732 | 741 |
742 | 743 | 744 | 745 | 746 | 747 | 748 |
749 | 750 | 751 | Something went wrong with that request. Please try again. 752 |
753 | 754 | 755 | 756 | 757 | 758 | 759 | 764 | 765 | 766 | 767 | -------------------------------------------------------------------------------- /lib/public/img/hapi-logo.svg: -------------------------------------------------------------------------------- 1 | Untitled 2 -------------------------------------------------------------------------------- /lib/public/img/navbar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/desirable-objects/hapi-ending/3e3e29c35dc71cdab1b7755318f66219d4da5b3e/lib/public/img/navbar.png -------------------------------------------------------------------------------- /lib/public/js/all.js: -------------------------------------------------------------------------------- 1 | !(function () { if ('ontouchstart' in window) { var t; var e; var i; var n; var o; var s; var r = {}; t = function (t, e) { return Math.abs(t[0] - e[0]) > 5 || Math.abs(t[1] - e[1]) > 5 }, e = function (t) { this.startXY = [t.touches[0].clientX, t.touches[0].clientY], this.threshold = !1 }, i = function (e) { return this.threshold ? !1 : void (this.threshold = t(this.startXY, [e.touches[0].clientX, e.touches[0].clientY])) }, n = function (e) { if (!this.threshold && !t(this.startXY, [e.changedTouches[0].clientX, e.changedTouches[0].clientY])) { var i = e.changedTouches[0]; var n = document.createEvent('MouseEvents'); n.initMouseEvent('click', !0, !0, window, 0, i.screenX, i.screenY, i.clientX, i.clientY, !1, !1, !1, !1, 0, null), n.simulated = !0, e.target.dispatchEvent(n) } }, o = function (t) { var e = Date.now(); var i = e - r.time; var n = t.clientX; var o = t.clientY; var a = [Math.abs(r.x - n), Math.abs(r.y - o)]; var l = s(t.target, 'A') || t.target; var c = l.nodeName; var h = c === 'A'; var u = window.navigator.standalone && h && t.target.getAttribute('href'); return r.time = e, r.x = n, r.y = o, (!t.simulated && (i < 500 || i < 1500 && a[0] < 50 && a[1] < 50) || u) && (t.preventDefault(), t.stopPropagation(), !u) ? !1 : (u && (window.location = l.getAttribute('href')), void (l && l.classList && (l.classList.add('energize-focus'), window.setTimeout(function () { l.classList.remove('energize-focus') }, 150)))) }, s = function (t, e) { for (var i = t; i !== document.body;) { if (!i || i.nodeName === e) return i; i = i.parentNode } return null }, document.addEventListener('touchstart', e, !1), document.addEventListener('touchmove', i, !1), document.addEventListener('touchend', n, !1), document.addEventListener('click', o, !0) } }()), /* 2 | * jQuery Highlight plugin 3 | * 4 | * Based on highlight v3 by Johann Burkard 5 | * http://johannburkard.de/blog/programming/javascript/highlight-javascript-text-higlighting-jquery-plugin.html 6 | * 7 | * Code a little bit refactored and cleaned (in my humble opinion). 8 | * Most important changes: 9 | * - has an option to highlight only entire words (wordsOnly - false by default), 10 | * - has an option to be case sensitive (caseSensitive - false by default) 11 | * - highlight element tag and class names can be specified in options 12 | * 13 | * Usage: 14 | * // wrap every occurrance of text 'lorem' in content 15 | * // with (default options) 16 | * $('#content').highlight('lorem'); 17 | * 18 | * // search for and highlight more terms at once 19 | * // so you can save some time on traversing DOM 20 | * $('#content').highlight(['lorem', 'ipsum']); 21 | * $('#content').highlight('lorem ipsum'); 22 | * 23 | * // search only for entire word 'lorem' 24 | * $('#content').highlight('lorem', { wordsOnly: true }); 25 | * 26 | * // don't ignore case during search of term 'lorem' 27 | * $('#content').highlight('lorem', { caseSensitive: true }); 28 | * 29 | * // wrap every occurrance of term 'ipsum' in content 30 | * // with 31 | * $('#content').highlight('ipsum', { element: 'em', className: 'important' }); 32 | * 33 | * // remove default highlight 34 | * $('#content').unhighlight(); 35 | * 36 | * // remove custom highlight 37 | * $('#content').unhighlight({ element: 'em', className: 'important' }); 38 | * 39 | * 40 | * Copyright (c) 2009 Bartek Szopka 41 | * 42 | * Licensed under MIT license. 43 | * 44 | */ 45 | jQuery.extend({ highlight: function (t, e, i, n) { if (t.nodeType === 3) { var o = t.data.match(e); if (o) { var s = document.createElement(i || 'span'); s.className = n || 'highlight'; var r = t.splitText(o.index); r.splitText(o[0].length); var a = r.cloneNode(!0); return s.appendChild(a), r.parentNode.replaceChild(s, r), 1 } } else if (t.nodeType === 1 && t.childNodes && !/(script|style)/i.test(t.tagName) && (t.tagName !== i.toUpperCase() || t.className !== n)) for (var l = 0; l < t.childNodes.length; l++)l += jQuery.highlight(t.childNodes[l], e, i, n); return 0 } }), jQuery.fn.unhighlight = function (t) { var e = { className: 'highlight', element: 'span' }; return jQuery.extend(e, t), this.find(e.element + '.' + e.className).each(function () { var t = this.parentNode; t.replaceChild(this.firstChild, this), t.normalize() }).end() }, jQuery.fn.highlight = function (t, e) { var i = { className: 'highlight', element: 'span', caseSensitive: !1, wordsOnly: !1 }; if (jQuery.extend(i, e), t.constructor === String && (t = [t]), t = jQuery.grep(t, function (t) { return t != '' }), t = jQuery.map(t, function (t) { return t.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&') }), t.length == 0) return this; var n = i.caseSensitive ? '' : 'i'; var o = '(' + t.join('|') + ')'; i.wordsOnly && (o = '\\b' + o + '\\b'); var s = new RegExp(o, n); return this.each(function () { jQuery.highlight(this, s, i.element, i.className) }) }, /*! jQuery UI - v1.10.3 - 2013-09-16 46 | * http://jqueryui.com 47 | * Includes: jquery.ui.widget.js 48 | * Copyright 2013 jQuery Foundation and other contributors; Licensed MIT */ 49 | (function (t, e) { var i = 0; var n = Array.prototype.slice; var o = t.cleanData; t.cleanData = function (e) { for (var i, n = 0; (i = e[n]) != null; n++) try { t(i).triggerHandler('remove') } catch (s) {}o(e) }, t.widget = function (i, n, o) { var s; var r; var a; var l; var c = {}; var h = i.split('.')[0]; i = i.split('.')[1], s = h + '-' + i, o || (o = n, n = t.Widget), t.expr[':'][s.toLowerCase()] = function (e) { return !!t.data(e, s) }, t[h] = t[h] || {}, r = t[h][i], a = t[h][i] = function (t, i) { return this._createWidget ? (arguments.length && this._createWidget(t, i), e) : new a(t, i) }, t.extend(a, r, { version: o.version, _proto: t.extend({}, o), _childConstructors: [] }), l = new n(), l.options = t.widget.extend({}, l.options), t.each(o, function (i, o) { return t.isFunction(o) ? (c[i] = (function () { var t = function () { return n.prototype[i].apply(this, arguments) }; var e = function (t) { return n.prototype[i].apply(this, t) }; return function () { var i; var n = this._super; var s = this._superApply; return this._super = t, this._superApply = e, i = o.apply(this, arguments), this._super = n, this._superApply = s, i } }()), e) : (c[i] = o, e) }), a.prototype = t.widget.extend(l, { widgetEventPrefix: r ? l.widgetEventPrefix : i }, c, { constructor: a, namespace: h, widgetName: i, widgetFullName: s }), r ? (t.each(r._childConstructors, function (e, i) { var n = i.prototype; t.widget(n.namespace + '.' + n.widgetName, a, i._proto) }), delete r._childConstructors) : n._childConstructors.push(a), t.widget.bridge(i, a) }, t.widget.extend = function (i) { for (var o, s, r = n.call(arguments, 1), a = 0, l = r.length; l > a; a++) for (o in r[a])s = r[a][o], r[a].hasOwnProperty(o) && s !== e && (i[o] = t.isPlainObject(s) ? t.isPlainObject(i[o]) ? t.widget.extend({}, i[o], s) : t.widget.extend({}, s) : s); return i }, t.widget.bridge = function (i, o) { var s = o.prototype.widgetFullName || i; t.fn[i] = function (r) { var a = typeof r === 'string'; var l = n.call(arguments, 1); var c = this; return r = !a && l.length ? t.widget.extend.apply(null, [r].concat(l)) : r, this.each(a ? function () { var n; var o = t.data(this, s); return o ? t.isFunction(o[r]) && r.charAt(0) !== '_' ? (n = o[r].apply(o, l), n !== o && n !== e ? (c = n && n.jquery ? c.pushStack(n.get()) : n, !1) : e) : t.error("no such method '" + r + "' for " + i + ' widget instance') : t.error('cannot call methods on ' + i + " prior to initialization; attempted to call method '" + r + "'") } : function () { var e = t.data(this, s); e ? e.option(r || {})._init() : t.data(this, s, new o(r, this)) }), c } }, t.Widget = function () {}, t.Widget._childConstructors = [], t.Widget.prototype = { widgetName: 'widget', widgetEventPrefix: '', defaultElement: '
', options: { disabled: !1, create: null }, _createWidget: function (e, n) { n = t(n || this.defaultElement || this)[0], this.element = t(n), this.uuid = i++, this.eventNamespace = '.' + this.widgetName + this.uuid, this.options = t.widget.extend({}, this.options, this._getCreateOptions(), e), this.bindings = t(), this.hoverable = t(), this.focusable = t(), n !== this && (t.data(n, this.widgetFullName, this), this._on(!0, this.element, { remove: function (t) { t.target === n && this.destroy() } }), this.document = t(n.style ? n.ownerDocument : n.document || n), this.window = t(this.document[0].defaultView || this.document[0].parentWindow)), this._create(), this._trigger('create', null, this._getCreateEventData()), this._init() }, _getCreateOptions: t.noop, _getCreateEventData: t.noop, _create: t.noop, _init: t.noop, destroy: function () { this._destroy(), this.element.unbind(this.eventNamespace).removeData(this.widgetName).removeData(this.widgetFullName).removeData(t.camelCase(this.widgetFullName)), this.widget().unbind(this.eventNamespace).removeAttr('aria-disabled').removeClass(this.widgetFullName + '-disabled ui-state-disabled'), this.bindings.unbind(this.eventNamespace), this.hoverable.removeClass('ui-state-hover'), this.focusable.removeClass('ui-state-focus') }, _destroy: t.noop, widget: function () { return this.element }, option: function (i, n) { var o; var s; var r; var a = i; if (arguments.length === 0) return t.widget.extend({}, this.options); if (typeof i === 'string') if (a = {}, o = i.split('.'), i = o.shift(), o.length) { for (s = a[i] = t.widget.extend({}, this.options[i]), r = 0; o.length - 1 > r; r++)s[o[r]] = s[o[r]] || {}, s = s[o[r]]; if (i = o.pop(), n === e) return s[i] === e ? null : s[i]; s[i] = n } else { if (n === e) return this.options[i] === e ? null : this.options[i]; a[i] = n } return this._setOptions(a), this }, _setOptions: function (t) { var e; for (e in t) this._setOption(e, t[e]); return this }, _setOption: function (t, e) { return this.options[t] = e, t === 'disabled' && (this.widget().toggleClass(this.widgetFullName + '-disabled ui-state-disabled', !!e).attr('aria-disabled', e), this.hoverable.removeClass('ui-state-hover'), this.focusable.removeClass('ui-state-focus')), this }, enable: function () { return this._setOption('disabled', !1) }, disable: function () { return this._setOption('disabled', !0) }, _on: function (i, n, o) { var s; var r = this; typeof i !== 'boolean' && (o = n, n = i, i = !1), o ? (n = s = t(n), this.bindings = this.bindings.add(n)) : (o = n, n = this.element, s = this.widget()), t.each(o, function (o, a) { function l () { return i || r.options.disabled !== !0 && !t(this).hasClass('ui-state-disabled') ? (typeof a === 'string' ? r[a] : a).apply(r, arguments) : e } typeof a !== 'string' && (l.guid = a.guid = a.guid || l.guid || t.guid++); var c = o.match(/^(\w+)\s*(.*)$/); var h = c[1] + r.eventNamespace; var u = c[2]; u ? s.delegate(u, h, l) : n.bind(h, l) }) }, _off: function (t, e) { e = (e || '').split(' ').join(this.eventNamespace + ' ') + this.eventNamespace, t.unbind(e).undelegate(e) }, _delay: function (t, e) { function i () { return (typeof t === 'string' ? n[t] : t).apply(n, arguments) } var n = this; return setTimeout(i, e || 0) }, _hoverable: function (e) { this.hoverable = this.hoverable.add(e), this._on(e, { mouseenter: function (e) { t(e.currentTarget).addClass('ui-state-hover') }, mouseleave: function (e) { t(e.currentTarget).removeClass('ui-state-hover') } }) }, _focusable: function (e) { this.focusable = this.focusable.add(e), this._on(e, { focusin: function (e) { t(e.currentTarget).addClass('ui-state-focus') }, focusout: function (e) { t(e.currentTarget).removeClass('ui-state-focus') } }) }, _trigger: function (e, i, n) { var o; var s; var r = this.options[e]; if (n = n || {}, i = t.Event(i), i.type = (e === this.widgetEventPrefix ? e : this.widgetEventPrefix + e).toLowerCase(), i.target = this.element[0], s = i.originalEvent) for (o in s)o in i || (i[o] = s[o]); return this.element.trigger(i, n), !(t.isFunction(r) && r.apply(this.element[0], [i].concat(n)) === !1 || i.isDefaultPrevented()) } }, t.each({ show: 'fadeIn', hide: 'fadeOut' }, function (e, i) { t.Widget.prototype['_' + e] = function (n, o, s) { typeof o === 'string' && (o = { effect: o }); var r; var a = o ? o === !0 || typeof o === 'number' ? i : o.effect || i : e; o = o || {}, typeof o === 'number' && (o = { duration: o }), r = !t.isEmptyObject(o), o.complete = s, o.delay && n.delay(o.delay), r && t.effects && t.effects.effect[a] ? n[e](o) : a !== e && n[a] ? n[a](o.duration, o.easing, s) : n.queue(function (i) { t(this)[e](), s && s.call(n[0]), i() }) } }) }(jQuery)), /* jquery Tocify - v1.8.0 - 2013-09-16 50 | * http://www.gregfranko.com/jquery.tocify.js/ 51 | * Copyright (c) 2013 Greg Franko; Licensed MIT 52 | * Modified lightly by Robert Lord to fix a bug I found, 53 | * and also so it adds ids to headers 54 | * also because I want height caching, since the 55 | * height lookup for h1s and h2s was causing serious 56 | * lag spikes below 30 fps */ 57 | (function (t) { 'use strict'; t(window.jQuery, window, document) }(function (t, e, i, n) { 'use strict'; var o = 'tocify'; var s = 'tocify-focus'; var r = 'tocify-hover'; var a = 'tocify-hide'; var l = 'tocify-header'; var c = '.' + l; var h = 'tocify-subheader'; var u = '.' + h; var d = 'tocify-item'; var f = '.' + d; var p = 'tocify-extend-page'; var g = '.' + p; t.widget('toc.tocify', { version: '1.8.0', options: { context: 'body', ignoreSelector: null, selectors: 'h1, h2, h3', showAndHide: !0, showEffect: 'slideDown', showEffectSpeed: 'medium', hideEffect: 'slideUp', hideEffectSpeed: 'medium', smoothScroll: !0, smoothScrollSpeed: 'medium', scrollTo: 0, showAndHideOnScroll: !0, highlightOnScroll: !0, highlightOffset: 40, theme: 'bootstrap', extendPage: !0, extendPageOffset: 100, history: !0, scrollHistory: !1, hashGenerator: 'compact', highlightDefault: !0 }, _create: function () { var i = this; i.tocifyWrapper = t('.tocify-wrapper'), i.extendPageScroll = !0, i.items = [], i._generateToc(), i.cachedHeights = [], i.cachedAnchors = [], i._addCSSClasses(), i.webkit = (function () { for (var t in e) if (t && t.toLowerCase().indexOf('webkit') !== -1) return !0; return !1 }()), i._setEventHandlers(), t(e).load(function () { i._setActiveElement(!0), t('html, body').promise().done(function () { setTimeout(function () { i.extendPageScroll = !1 }, 0) }) }) }, _generateToc: function () { var e; var i; var n = this; var s = n.options.ignoreSelector; return e = t(this.options.context).find(this.options.selectors.indexOf(',') !== -1 ? this.options.selectors.replace(/ /g, '').substr(0, this.options.selectors.indexOf(',')) : this.options.selectors.replace(/ /g, '')), e.length ? (n.element.addClass(o), void e.each(function (e) { t(this).is(s) || (i = t('