diff --git a/docs/API.md b/docs/API.md deleted file mode 100644 index 336666c..0000000 --- a/docs/API.md +++ /dev/null @@ -1,99 +0,0 @@ -Docs -========= - - -### Table of Contents - -- [TuyaDevice](#tuyadevice) - - [resolveIds](#resolveids) - - [get](#get) - - [set](#set) - -## TuyaDevice - -Represents a Tuya device. - -**Parameters** - -- `options` **[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)** options for constructing a TuyaDevice - - `options.type` **[String](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String)** type of device (optional, default `'outlet'`) - - `options.ip` **[String](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String)?** IP of device - - `options.port` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)** port of device (optional, default `6668`) - - `options.id` **[String](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String)** ID of device - - `options.uid` **[String](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String)** UID of device (optional, default `''`) - - `options.key` **[String](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String)** encryption key of device - - `options.version` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)** protocol version (optional, default `3.1`) - -**Examples** - -```javascript -const tuya = new TuyaDevice({id: 'xxxxxxxxxxxxxxxxxxxx', key: 'xxxxxxxxxxxxxxxx'}) -``` - -```javascript -const tuya = new TuyaDevice([ -{id: 'xxxxxxxxxxxxxxxxxxxx', key: 'xxxxxxxxxxxxxxxx'}, -{id: 'xxxxxxxxxxxxxxxxxxxx', key: 'xxxxxxxxxxxxxxxx'}]) -``` - -### resolveIds - -Resolves IDs stored in class to IPs. If you didn't pass IPs to the constructor, -you must call this before doing anything else. - -Returns **[Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise)<[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)>** true if IPs were found and devices are ready to be used - -### get - -Gets a device's current status. Defaults to returning only the value of the first result, -but by setting {schema: true} you can get everything. - -**Parameters** - -- `options` **[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)?** optional options for getting data - - `options.id` **[String](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String)?** ID of device - - `options.schema` **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)?** true to return entire schema, not just the first result - -**Examples** - -```javascript -// get status for device with one property -tuya.get().then(status => console.log(status)) -``` - -```javascript -// get status for specific device with one property -tuya.get({id: 'xxxxxxxxxxxxxxxxxxxx'}).then(status => console.log(status)) -``` - -```javascript -// get all available data from device -tuya.get({schema: true}).then(data => console.log(data)) -``` - -Returns **[Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise)<[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)>** returns boolean if no options are provided, otherwise returns object of results - -### set - -Sets a property on a device. - -**Parameters** - -- `options` **[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)** options for setting properties - - `options.id` **[String](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String)?** ID of device - - `options.set` **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)** `true` for on, `false` for off - - `options.dps` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)?** dps index to change - -**Examples** - -```javascript -// set default property on default device -tuya.set({set: true}).then(() => console.log('device was changed')) -``` - -```javascript -// set custom property on non-default device -tuya.set({id: 'xxxxxxxxxxxxxxxxxxxx', 'dps': 2, set: true}).then(() => console.log('device was changed')) -``` - -Returns **[Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise)<[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)>** returns `true` if the command succeeded diff --git a/docs/assets/anchor.js b/docs/assets/anchor.js new file mode 100644 index 0000000..5c29527 --- /dev/null +++ b/docs/assets/anchor.js @@ -0,0 +1,350 @@ +/*! + * AnchorJS - v4.0.0 - 2017-06-02 + * https://github.com/bryanbraun/anchorjs + * Copyright (c) 2017 Bryan Braun; Licensed MIT + */ +/* eslint-env amd, node */ + +// https://github.com/umdjs/umd/blob/master/templates/returnExports.js +(function(root, factory) { + 'use strict'; + if (typeof define === 'function' && define.amd) { + // AMD. Register as an anonymous module. + define([], factory); + } else if (typeof module === 'object' && module.exports) { + // Node. Does not work with strict CommonJS, but + // only CommonJS-like environments that support module.exports, + // like Node. + module.exports = factory(); + } else { + // Browser globals (root is window) + root.AnchorJS = factory(); + root.anchors = new root.AnchorJS(); + } +})(this, function() { + 'use strict'; + function AnchorJS(options) { + this.options = options || {}; + this.elements = []; + + /** + * Assigns options to the internal options object, and provides defaults. + * @param {Object} opts - Options object + */ + function _applyRemainingDefaultOptions(opts) { + opts.icon = opts.hasOwnProperty('icon') ? opts.icon : '\ue9cb'; // Accepts characters (and also URLs?), like '#', '¶', '❡', or '§'. + opts.visible = opts.hasOwnProperty('visible') ? opts.visible : 'hover'; // Also accepts 'always' & 'touch' + opts.placement = opts.hasOwnProperty('placement') + ? opts.placement + : 'right'; // Also accepts 'left' + opts.class = opts.hasOwnProperty('class') ? opts.class : ''; // Accepts any class name. + // Using Math.floor here will ensure the value is Number-cast and an integer. + opts.truncate = opts.hasOwnProperty('truncate') + ? Math.floor(opts.truncate) + : 64; // Accepts any value that can be typecast to a number. + } + + _applyRemainingDefaultOptions(this.options); + + /** + * Checks to see if this device supports touch. Uses criteria pulled from Modernizr: + * https://github.com/Modernizr/Modernizr/blob/da22eb27631fc4957f67607fe6042e85c0a84656/feature-detects/touchevents.js#L40 + * @returns {Boolean} - true if the current device supports touch. + */ + this.isTouchDevice = function() { + return !!( + 'ontouchstart' in window || + (window.DocumentTouch && document instanceof DocumentTouch) + ); + }; + + /** + * Add anchor links to page elements. + * @param {String|Array|Nodelist} selector - A CSS selector for targeting the elements you wish to add anchor links + * to. Also accepts an array or nodeList containing the relavant elements. + * @returns {this} - The AnchorJS object + */ + this.add = function(selector) { + var elements, + elsWithIds, + idList, + elementID, + i, + index, + count, + tidyText, + newTidyText, + readableID, + anchor, + visibleOptionToUse, + indexesToDrop = []; + + // We reapply options here because somebody may have overwritten the default options object when setting options. + // For example, this overwrites all options but visible: + // + // anchors.options = { visible: 'always'; } + _applyRemainingDefaultOptions(this.options); + + visibleOptionToUse = this.options.visible; + if (visibleOptionToUse === 'touch') { + visibleOptionToUse = this.isTouchDevice() ? 'always' : 'hover'; + } + + // Provide a sensible default selector, if none is given. + if (!selector) { + selector = 'h2, h3, h4, h5, h6'; + } + + elements = _getElements(selector); + + if (elements.length === 0) { + return this; + } + + _addBaselineStyles(); + + // We produce a list of existing IDs so we don't generate a duplicate. + elsWithIds = document.querySelectorAll('[id]'); + idList = [].map.call(elsWithIds, function assign(el) { + return el.id; + }); + + for (i = 0; i < elements.length; i++) { + if (this.hasAnchorJSLink(elements[i])) { + indexesToDrop.push(i); + continue; + } + + if (elements[i].hasAttribute('id')) { + elementID = elements[i].getAttribute('id'); + } else if (elements[i].hasAttribute('data-anchor-id')) { + elementID = elements[i].getAttribute('data-anchor-id'); + } else { + tidyText = this.urlify(elements[i].textContent); + + // Compare our generated ID to existing IDs (and increment it if needed) + // before we add it to the page. + newTidyText = tidyText; + count = 0; + do { + if (index !== undefined) { + newTidyText = tidyText + '-' + count; + } + + index = idList.indexOf(newTidyText); + count += 1; + } while (index !== -1); + index = undefined; + idList.push(newTidyText); + + elements[i].setAttribute('id', newTidyText); + elementID = newTidyText; + } + + readableID = elementID.replace(/-/g, ' '); + + // The following code builds the following DOM structure in a more effiecient (albeit opaque) way. + // ''; + anchor = document.createElement('a'); + anchor.className = 'anchorjs-link ' + this.options.class; + anchor.href = '#' + elementID; + anchor.setAttribute('aria-label', 'Anchor link for: ' + readableID); + anchor.setAttribute('data-anchorjs-icon', this.options.icon); + + if (visibleOptionToUse === 'always') { + anchor.style.opacity = '1'; + } + + if (this.options.icon === '\ue9cb') { + anchor.style.font = '1em/1 anchorjs-icons'; + + // We set lineHeight = 1 here because the `anchorjs-icons` font family could otherwise affect the + // height of the heading. This isn't the case for icons with `placement: left`, so we restore + // line-height: inherit in that case, ensuring they remain positioned correctly. For more info, + // see https://github.com/bryanbraun/anchorjs/issues/39. + if (this.options.placement === 'left') { + anchor.style.lineHeight = 'inherit'; + } + } + + if (this.options.placement === 'left') { + anchor.style.position = 'absolute'; + anchor.style.marginLeft = '-1em'; + anchor.style.paddingRight = '0.5em'; + elements[i].insertBefore(anchor, elements[i].firstChild); + } else { + // if the option provided is `right` (or anything else). + anchor.style.paddingLeft = '0.375em'; + elements[i].appendChild(anchor); + } + } + + for (i = 0; i < indexesToDrop.length; i++) { + elements.splice(indexesToDrop[i] - i, 1); + } + this.elements = this.elements.concat(elements); + + return this; + }; + + /** + * Removes all anchorjs-links from elements targed by the selector. + * @param {String|Array|Nodelist} selector - A CSS selector string targeting elements with anchor links, + * OR a nodeList / array containing the DOM elements. + * @returns {this} - The AnchorJS object + */ + this.remove = function(selector) { + var index, + domAnchor, + elements = _getElements(selector); + + for (var i = 0; i < elements.length; i++) { + domAnchor = elements[i].querySelector('.anchorjs-link'); + if (domAnchor) { + // Drop the element from our main list, if it's in there. + index = this.elements.indexOf(elements[i]); + if (index !== -1) { + this.elements.splice(index, 1); + } + // Remove the anchor from the DOM. + elements[i].removeChild(domAnchor); + } + } + return this; + }; + + /** + * Removes all anchorjs links. Mostly used for tests. + */ + this.removeAll = function() { + this.remove(this.elements); + }; + + /** + * Urlify - Refine text so it makes a good ID. + * + * To do this, we remove apostrophes, replace nonsafe characters with hyphens, + * remove extra hyphens, truncate, trim hyphens, and make lowercase. + * + * @param {String} text - Any text. Usually pulled from the webpage element we are linking to. + * @returns {String} - hyphen-delimited text for use in IDs and URLs. + */ + this.urlify = function(text) { + // Regex for finding the nonsafe URL characters (many need escaping): & +$,:;=?@"#{}|^~[`%!'<>]./()*\ + var nonsafeChars = /[& +$,:;=?@"#{}|^~[`%!'<>\]\.\/\(\)\*\\]/g, + urlText; + + // The reason we include this _applyRemainingDefaultOptions is so urlify can be called independently, + // even after setting options. This can be useful for tests or other applications. + if (!this.options.truncate) { + _applyRemainingDefaultOptions(this.options); + } + + // Note: we trim hyphens after truncating because truncating can cause dangling hyphens. + // Example string: // " ⚡⚡ Don't forget: URL fragments should be i18n-friendly, hyphenated, short, and clean." + urlText = text + .trim() // "⚡⚡ Don't forget: URL fragments should be i18n-friendly, hyphenated, short, and clean." + .replace(/\'/gi, '') // "⚡⚡ Dont forget: URL fragments should be i18n-friendly, hyphenated, short, and clean." + .replace(nonsafeChars, '-') // "⚡⚡-Dont-forget--URL-fragments-should-be-i18n-friendly--hyphenated--short--and-clean-" + .replace(/-{2,}/g, '-') // "⚡⚡-Dont-forget-URL-fragments-should-be-i18n-friendly-hyphenated-short-and-clean-" + .substring(0, this.options.truncate) // "⚡⚡-Dont-forget-URL-fragments-should-be-i18n-friendly-hyphenated-" + .replace(/^-+|-+$/gm, '') // "⚡⚡-Dont-forget-URL-fragments-should-be-i18n-friendly-hyphenated" + .toLowerCase(); // "⚡⚡-dont-forget-url-fragments-should-be-i18n-friendly-hyphenated" + + return urlText; + }; + + /** + * Determines if this element already has an AnchorJS link on it. + * Uses this technique: http://stackoverflow.com/a/5898748/1154642 + * @param {HTMLElemnt} el - a DOM node + * @returns {Boolean} true/false + */ + this.hasAnchorJSLink = function(el) { + var hasLeftAnchor = + el.firstChild && + (' ' + el.firstChild.className + ' ').indexOf(' anchorjs-link ') > -1, + hasRightAnchor = + el.lastChild && + (' ' + el.lastChild.className + ' ').indexOf(' anchorjs-link ') > -1; + + return hasLeftAnchor || hasRightAnchor || false; + }; + + /** + * Turns a selector, nodeList, or array of elements into an array of elements (so we can use array methods). + * It also throws errors on any other inputs. Used to handle inputs to .add and .remove. + * @param {String|Array|Nodelist} input - A CSS selector string targeting elements with anchor links, + * OR a nodeList / array containing the DOM elements. + * @returns {Array} - An array containing the elements we want. + */ + function _getElements(input) { + var elements; + if (typeof input === 'string' || input instanceof String) { + // See https://davidwalsh.name/nodelist-array for the technique transforming nodeList -> Array. + elements = [].slice.call(document.querySelectorAll(input)); + // I checked the 'input instanceof NodeList' test in IE9 and modern browsers and it worked for me. + } else if (Array.isArray(input) || input instanceof NodeList) { + elements = [].slice.call(input); + } else { + throw new Error('The selector provided to AnchorJS was invalid.'); + } + return elements; + } + + /** + * _addBaselineStyles + * Adds baseline styles to the page, used by all AnchorJS links irregardless of configuration. + */ + function _addBaselineStyles() { + // We don't want to add global baseline styles if they've been added before. + if (document.head.querySelector('style.anchorjs') !== null) { + return; + } + + var style = document.createElement('style'), + linkRule = + ' .anchorjs-link {' + + ' opacity: 0;' + + ' text-decoration: none;' + + ' -webkit-font-smoothing: antialiased;' + + ' -moz-osx-font-smoothing: grayscale;' + + ' }', + hoverRule = + ' *:hover > .anchorjs-link,' + + ' .anchorjs-link:focus {' + + ' opacity: 1;' + + ' }', + anchorjsLinkFontFace = + ' @font-face {' + + ' font-family: "anchorjs-icons";' + // Icon from icomoon; 10px wide & 10px tall; 2 empty below & 4 above + ' src: url(data:n/a;base64,AAEAAAALAIAAAwAwT1MvMg8yG2cAAAE4AAAAYGNtYXDp3gC3AAABpAAAAExnYXNwAAAAEAAAA9wAAAAIZ2x5ZlQCcfwAAAH4AAABCGhlYWQHFvHyAAAAvAAAADZoaGVhBnACFwAAAPQAAAAkaG10eASAADEAAAGYAAAADGxvY2EACACEAAAB8AAAAAhtYXhwAAYAVwAAARgAAAAgbmFtZQGOH9cAAAMAAAAAunBvc3QAAwAAAAADvAAAACAAAQAAAAEAAHzE2p9fDzz1AAkEAAAAAADRecUWAAAAANQA6R8AAAAAAoACwAAAAAgAAgAAAAAAAAABAAADwP/AAAACgAAA/9MCrQABAAAAAAAAAAAAAAAAAAAAAwABAAAAAwBVAAIAAAAAAAIAAAAAAAAAAAAAAAAAAAAAAAMCQAGQAAUAAAKZAswAAACPApkCzAAAAesAMwEJAAAAAAAAAAAAAAAAAAAAARAAAAAAAAAAAAAAAAAAAAAAQAAg//0DwP/AAEADwABAAAAAAQAAAAAAAAAAAAAAIAAAAAAAAAIAAAACgAAxAAAAAwAAAAMAAAAcAAEAAwAAABwAAwABAAAAHAAEADAAAAAIAAgAAgAAACDpy//9//8AAAAg6cv//f///+EWNwADAAEAAAAAAAAAAAAAAAAACACEAAEAAAAAAAAAAAAAAAAxAAACAAQARAKAAsAAKwBUAAABIiYnJjQ3NzY2MzIWFxYUBwcGIicmNDc3NjQnJiYjIgYHBwYUFxYUBwYGIwciJicmNDc3NjIXFhQHBwYUFxYWMzI2Nzc2NCcmNDc2MhcWFAcHBgYjARQGDAUtLXoWOR8fORYtLTgKGwoKCjgaGg0gEhIgDXoaGgkJBQwHdR85Fi0tOAobCgoKOBoaDSASEiANehoaCQkKGwotLXoWOR8BMwUFLYEuehYXFxYugC44CQkKGwo4GkoaDQ0NDXoaShoKGwoFBe8XFi6ALjgJCQobCjgaShoNDQ0NehpKGgobCgoKLYEuehYXAAAADACWAAEAAAAAAAEACAAAAAEAAAAAAAIAAwAIAAEAAAAAAAMACAAAAAEAAAAAAAQACAAAAAEAAAAAAAUAAQALAAEAAAAAAAYACAAAAAMAAQQJAAEAEAAMAAMAAQQJAAIABgAcAAMAAQQJAAMAEAAMAAMAAQQJAAQAEAAMAAMAAQQJAAUAAgAiAAMAAQQJAAYAEAAMYW5jaG9yanM0MDBAAGEAbgBjAGgAbwByAGoAcwA0ADAAMABAAAAAAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAH//wAP) format("truetype");' + + ' }', + pseudoElContent = + ' [data-anchorjs-icon]::after {' + + ' content: attr(data-anchorjs-icon);' + + ' }', + firstStyleEl; + + style.className = 'anchorjs'; + style.appendChild(document.createTextNode('')); // Necessary for Webkit. + + // We place it in the head with the other style tags, if possible, so as to + // not look out of place. We insert before the others so these styles can be + // overridden if necessary. + firstStyleEl = document.head.querySelector('[rel="stylesheet"], style'); + if (firstStyleEl === undefined) { + document.head.appendChild(style); + } else { + document.head.insertBefore(style, firstStyleEl); + } + + style.sheet.insertRule(linkRule, style.sheet.cssRules.length); + style.sheet.insertRule(hoverRule, style.sheet.cssRules.length); + style.sheet.insertRule(pseudoElContent, style.sheet.cssRules.length); + style.sheet.insertRule(anchorjsLinkFontFace, style.sheet.cssRules.length); + } + } + + return AnchorJS; +}); diff --git a/docs/assets/bass-addons.css b/docs/assets/bass-addons.css new file mode 100644 index 0000000..c27e96d --- /dev/null +++ b/docs/assets/bass-addons.css @@ -0,0 +1,12 @@ +.input { + font-family: inherit; + display: block; + width: 100%; + height: 2rem; + padding: .5rem; + margin-bottom: 1rem; + border: 1px solid #ccc; + font-size: .875rem; + border-radius: 3px; + box-sizing: border-box; +} diff --git a/docs/assets/bass.css b/docs/assets/bass.css new file mode 100644 index 0000000..2d860c5 --- /dev/null +++ b/docs/assets/bass.css @@ -0,0 +1,544 @@ +/*! Basscss | http://basscss.com | MIT License */ + +.h1{ font-size: 2rem } +.h2{ font-size: 1.5rem } +.h3{ font-size: 1.25rem } +.h4{ font-size: 1rem } +.h5{ font-size: .875rem } +.h6{ font-size: .75rem } + +.font-family-inherit{ font-family:inherit } +.font-size-inherit{ font-size:inherit } +.text-decoration-none{ text-decoration:none } + +.bold{ font-weight: bold; font-weight: bold } +.regular{ font-weight:normal } +.italic{ font-style:italic } +.caps{ text-transform:uppercase; letter-spacing: .2em; } + +.left-align{ text-align:left } +.center{ text-align:center } +.right-align{ text-align:right } +.justify{ text-align:justify } + +.nowrap{ white-space:nowrap } +.break-word{ word-wrap:break-word } + +.line-height-1{ line-height: 1 } +.line-height-2{ line-height: 1.125 } +.line-height-3{ line-height: 1.25 } +.line-height-4{ line-height: 1.5 } + +.list-style-none{ list-style:none } +.underline{ text-decoration:underline } + +.truncate{ + max-width:100%; + overflow:hidden; + text-overflow:ellipsis; + white-space:nowrap; +} + +.list-reset{ + list-style:none; + padding-left:0; +} + +.inline{ display:inline } +.block{ display:block } +.inline-block{ display:inline-block } +.table{ display:table } +.table-cell{ display:table-cell } + +.overflow-hidden{ overflow:hidden } +.overflow-scroll{ overflow:scroll } +.overflow-auto{ overflow:auto } + +.clearfix:before, +.clearfix:after{ + content:" "; + display:table +} +.clearfix:after{ clear:both } + +.left{ float:left } +.right{ float:right } + +.fit{ max-width:100% } + +.max-width-1{ max-width: 24rem } +.max-width-2{ max-width: 32rem } +.max-width-3{ max-width: 48rem } +.max-width-4{ max-width: 64rem } + +.border-box{ box-sizing:border-box } + +.align-baseline{ vertical-align:baseline } +.align-top{ vertical-align:top } +.align-middle{ vertical-align:middle } +.align-bottom{ vertical-align:bottom } + +.m0{ margin:0 } +.mt0{ margin-top:0 } +.mr0{ margin-right:0 } +.mb0{ margin-bottom:0 } +.ml0{ margin-left:0 } +.mx0{ margin-left:0; margin-right:0 } +.my0{ margin-top:0; margin-bottom:0 } + +.m1{ margin: .5rem } +.mt1{ margin-top: .5rem } +.mr1{ margin-right: .5rem } +.mb1{ margin-bottom: .5rem } +.ml1{ margin-left: .5rem } +.mx1{ margin-left: .5rem; margin-right: .5rem } +.my1{ margin-top: .5rem; margin-bottom: .5rem } + +.m2{ margin: 1rem } +.mt2{ margin-top: 1rem } +.mr2{ margin-right: 1rem } +.mb2{ margin-bottom: 1rem } +.ml2{ margin-left: 1rem } +.mx2{ margin-left: 1rem; margin-right: 1rem } +.my2{ margin-top: 1rem; margin-bottom: 1rem } + +.m3{ margin: 2rem } +.mt3{ margin-top: 2rem } +.mr3{ margin-right: 2rem } +.mb3{ margin-bottom: 2rem } +.ml3{ margin-left: 2rem } +.mx3{ margin-left: 2rem; margin-right: 2rem } +.my3{ margin-top: 2rem; margin-bottom: 2rem } + +.m4{ margin: 4rem } +.mt4{ margin-top: 4rem } +.mr4{ margin-right: 4rem } +.mb4{ margin-bottom: 4rem } +.ml4{ margin-left: 4rem } +.mx4{ margin-left: 4rem; margin-right: 4rem } +.my4{ margin-top: 4rem; margin-bottom: 4rem } + +.mxn1{ margin-left: -.5rem; margin-right: -.5rem; } +.mxn2{ margin-left: -1rem; margin-right: -1rem; } +.mxn3{ margin-left: -2rem; margin-right: -2rem; } +.mxn4{ margin-left: -4rem; margin-right: -4rem; } + +.ml-auto{ margin-left:auto } +.mr-auto{ margin-right:auto } +.mx-auto{ margin-left:auto; margin-right:auto; } + +.p0{ padding:0 } +.pt0{ padding-top:0 } +.pr0{ padding-right:0 } +.pb0{ padding-bottom:0 } +.pl0{ padding-left:0 } +.px0{ padding-left:0; padding-right:0 } +.py0{ padding-top:0; padding-bottom:0 } + +.p1{ padding: .5rem } +.pt1{ padding-top: .5rem } +.pr1{ padding-right: .5rem } +.pb1{ padding-bottom: .5rem } +.pl1{ padding-left: .5rem } +.py1{ padding-top: .5rem; padding-bottom: .5rem } +.px1{ padding-left: .5rem; padding-right: .5rem } + +.p2{ padding: 1rem } +.pt2{ padding-top: 1rem } +.pr2{ padding-right: 1rem } +.pb2{ padding-bottom: 1rem } +.pl2{ padding-left: 1rem } +.py2{ padding-top: 1rem; padding-bottom: 1rem } +.px2{ padding-left: 1rem; padding-right: 1rem } + +.p3{ padding: 2rem } +.pt3{ padding-top: 2rem } +.pr3{ padding-right: 2rem } +.pb3{ padding-bottom: 2rem } +.pl3{ padding-left: 2rem } +.py3{ padding-top: 2rem; padding-bottom: 2rem } +.px3{ padding-left: 2rem; padding-right: 2rem } + +.p4{ padding: 4rem } +.pt4{ padding-top: 4rem } +.pr4{ padding-right: 4rem } +.pb4{ padding-bottom: 4rem } +.pl4{ padding-left: 4rem } +.py4{ padding-top: 4rem; padding-bottom: 4rem } +.px4{ padding-left: 4rem; padding-right: 4rem } + +.col{ + float:left; + box-sizing:border-box; +} + +.col-right{ + float:right; + box-sizing:border-box; +} + +.col-1{ + width:8.33333%; +} + +.col-2{ + width:16.66667%; +} + +.col-3{ + width:25%; +} + +.col-4{ + width:33.33333%; +} + +.col-5{ + width:41.66667%; +} + +.col-6{ + width:50%; +} + +.col-7{ + width:58.33333%; +} + +.col-8{ + width:66.66667%; +} + +.col-9{ + width:75%; +} + +.col-10{ + width:83.33333%; +} + +.col-11{ + width:91.66667%; +} + +.col-12{ + width:100%; +} +@media (min-width: 40em){ + + .sm-col{ + float:left; + box-sizing:border-box; + } + + .sm-col-right{ + float:right; + box-sizing:border-box; + } + + .sm-col-1{ + width:8.33333%; + } + + .sm-col-2{ + width:16.66667%; + } + + .sm-col-3{ + width:25%; + } + + .sm-col-4{ + width:33.33333%; + } + + .sm-col-5{ + width:41.66667%; + } + + .sm-col-6{ + width:50%; + } + + .sm-col-7{ + width:58.33333%; + } + + .sm-col-8{ + width:66.66667%; + } + + .sm-col-9{ + width:75%; + } + + .sm-col-10{ + width:83.33333%; + } + + .sm-col-11{ + width:91.66667%; + } + + .sm-col-12{ + width:100%; + } + +} +@media (min-width: 52em){ + + .md-col{ + float:left; + box-sizing:border-box; + } + + .md-col-right{ + float:right; + box-sizing:border-box; + } + + .md-col-1{ + width:8.33333%; + } + + .md-col-2{ + width:16.66667%; + } + + .md-col-3{ + width:25%; + } + + .md-col-4{ + width:33.33333%; + } + + .md-col-5{ + width:41.66667%; + } + + .md-col-6{ + width:50%; + } + + .md-col-7{ + width:58.33333%; + } + + .md-col-8{ + width:66.66667%; + } + + .md-col-9{ + width:75%; + } + + .md-col-10{ + width:83.33333%; + } + + .md-col-11{ + width:91.66667%; + } + + .md-col-12{ + width:100%; + } + +} +@media (min-width: 64em){ + + .lg-col{ + float:left; + box-sizing:border-box; + } + + .lg-col-right{ + float:right; + box-sizing:border-box; + } + + .lg-col-1{ + width:8.33333%; + } + + .lg-col-2{ + width:16.66667%; + } + + .lg-col-3{ + width:25%; + } + + .lg-col-4{ + width:33.33333%; + } + + .lg-col-5{ + width:41.66667%; + } + + .lg-col-6{ + width:50%; + } + + .lg-col-7{ + width:58.33333%; + } + + .lg-col-8{ + width:66.66667%; + } + + .lg-col-9{ + width:75%; + } + + .lg-col-10{ + width:83.33333%; + } + + .lg-col-11{ + width:91.66667%; + } + + .lg-col-12{ + width:100%; + } + +} +.flex{ display:-webkit-box; display:-webkit-flex; display:-ms-flexbox; display:flex } + +@media (min-width: 40em){ + .sm-flex{ display:-webkit-box; display:-webkit-flex; display:-ms-flexbox; display:flex } +} + +@media (min-width: 52em){ + .md-flex{ display:-webkit-box; display:-webkit-flex; display:-ms-flexbox; display:flex } +} + +@media (min-width: 64em){ + .lg-flex{ display:-webkit-box; display:-webkit-flex; display:-ms-flexbox; display:flex } +} + +.flex-column{ -webkit-box-orient:vertical; -webkit-box-direction:normal; -webkit-flex-direction:column; -ms-flex-direction:column; flex-direction:column } +.flex-wrap{ -webkit-flex-wrap:wrap; -ms-flex-wrap:wrap; flex-wrap:wrap } + +.items-start{ -webkit-box-align:start; -webkit-align-items:flex-start; -ms-flex-align:start; -ms-grid-row-align:flex-start; align-items:flex-start } +.items-end{ -webkit-box-align:end; -webkit-align-items:flex-end; -ms-flex-align:end; -ms-grid-row-align:flex-end; align-items:flex-end } +.items-center{ -webkit-box-align:center; -webkit-align-items:center; -ms-flex-align:center; -ms-grid-row-align:center; align-items:center } +.items-baseline{ -webkit-box-align:baseline; -webkit-align-items:baseline; -ms-flex-align:baseline; -ms-grid-row-align:baseline; align-items:baseline } +.items-stretch{ -webkit-box-align:stretch; -webkit-align-items:stretch; -ms-flex-align:stretch; -ms-grid-row-align:stretch; align-items:stretch } + +.self-start{ -webkit-align-self:flex-start; -ms-flex-item-align:start; align-self:flex-start } +.self-end{ -webkit-align-self:flex-end; -ms-flex-item-align:end; align-self:flex-end } +.self-center{ -webkit-align-self:center; -ms-flex-item-align:center; align-self:center } +.self-baseline{ -webkit-align-self:baseline; -ms-flex-item-align:baseline; align-self:baseline } +.self-stretch{ -webkit-align-self:stretch; -ms-flex-item-align:stretch; align-self:stretch } + +.justify-start{ -webkit-box-pack:start; -webkit-justify-content:flex-start; -ms-flex-pack:start; justify-content:flex-start } +.justify-end{ -webkit-box-pack:end; -webkit-justify-content:flex-end; -ms-flex-pack:end; justify-content:flex-end } +.justify-center{ -webkit-box-pack:center; -webkit-justify-content:center; -ms-flex-pack:center; justify-content:center } +.justify-between{ -webkit-box-pack:justify; -webkit-justify-content:space-between; -ms-flex-pack:justify; justify-content:space-between } +.justify-around{ -webkit-justify-content:space-around; -ms-flex-pack:distribute; justify-content:space-around } + +.content-start{ -webkit-align-content:flex-start; -ms-flex-line-pack:start; align-content:flex-start } +.content-end{ -webkit-align-content:flex-end; -ms-flex-line-pack:end; align-content:flex-end } +.content-center{ -webkit-align-content:center; -ms-flex-line-pack:center; align-content:center } +.content-between{ -webkit-align-content:space-between; -ms-flex-line-pack:justify; align-content:space-between } +.content-around{ -webkit-align-content:space-around; -ms-flex-line-pack:distribute; align-content:space-around } +.content-stretch{ -webkit-align-content:stretch; -ms-flex-line-pack:stretch; align-content:stretch } +.flex-auto{ + -webkit-box-flex:1; + -webkit-flex:1 1 auto; + -ms-flex:1 1 auto; + flex:1 1 auto; + min-width:0; + min-height:0; +} +.flex-none{ -webkit-box-flex:0; -webkit-flex:none; -ms-flex:none; flex:none } +.fs0{ flex-shrink: 0 } + +.order-0{ -webkit-box-ordinal-group:1; -webkit-order:0; -ms-flex-order:0; order:0 } +.order-1{ -webkit-box-ordinal-group:2; -webkit-order:1; -ms-flex-order:1; order:1 } +.order-2{ -webkit-box-ordinal-group:3; -webkit-order:2; -ms-flex-order:2; order:2 } +.order-3{ -webkit-box-ordinal-group:4; -webkit-order:3; -ms-flex-order:3; order:3 } +.order-last{ -webkit-box-ordinal-group:100000; -webkit-order:99999; -ms-flex-order:99999; order:99999 } + +.relative{ position:relative } +.absolute{ position:absolute } +.fixed{ position:fixed } + +.top-0{ top:0 } +.right-0{ right:0 } +.bottom-0{ bottom:0 } +.left-0{ left:0 } + +.z1{ z-index: 1 } +.z2{ z-index: 2 } +.z3{ z-index: 3 } +.z4{ z-index: 4 } + +.border{ + border-style:solid; + border-width: 1px; +} + +.border-top{ + border-top-style:solid; + border-top-width: 1px; +} + +.border-right{ + border-right-style:solid; + border-right-width: 1px; +} + +.border-bottom{ + border-bottom-style:solid; + border-bottom-width: 1px; +} + +.border-left{ + border-left-style:solid; + border-left-width: 1px; +} + +.border-none{ border:0 } + +.rounded{ border-radius: 3px } +.circle{ border-radius:50% } + +.rounded-top{ border-radius: 3px 3px 0 0 } +.rounded-right{ border-radius: 0 3px 3px 0 } +.rounded-bottom{ border-radius: 0 0 3px 3px } +.rounded-left{ border-radius: 3px 0 0 3px } + +.not-rounded{ border-radius:0 } + +.hide{ + position:absolute !important; + height:1px; + width:1px; + overflow:hidden; + clip:rect(1px, 1px, 1px, 1px); +} + +@media (max-width: 40em){ + .xs-hide{ display:none !important } +} + +@media (min-width: 40em) and (max-width: 52em){ + .sm-hide{ display:none !important } +} + +@media (min-width: 52em) and (max-width: 64em){ + .md-hide{ display:none !important } +} + +@media (min-width: 64em){ + .lg-hide{ display:none !important } +} + +.display-none{ display:none !important } + diff --git a/docs/assets/fonts/EOT/SourceCodePro-Bold.eot b/docs/assets/fonts/EOT/SourceCodePro-Bold.eot new file mode 100755 index 0000000..d24cc39 Binary files /dev/null and b/docs/assets/fonts/EOT/SourceCodePro-Bold.eot differ diff --git a/docs/assets/fonts/EOT/SourceCodePro-Regular.eot b/docs/assets/fonts/EOT/SourceCodePro-Regular.eot new file mode 100755 index 0000000..09e9473 Binary files /dev/null and b/docs/assets/fonts/EOT/SourceCodePro-Regular.eot differ diff --git a/docs/assets/fonts/LICENSE.txt b/docs/assets/fonts/LICENSE.txt new file mode 100755 index 0000000..d154618 --- /dev/null +++ b/docs/assets/fonts/LICENSE.txt @@ -0,0 +1,93 @@ +Copyright 2010, 2012 Adobe Systems Incorporated (http://www.adobe.com/), with Reserved Font Name 'Source'. All Rights Reserved. Source is a trademark of Adobe Systems Incorporated in the United States and/or other countries. + +This Font Software is licensed under the SIL Open Font License, Version 1.1. + +This license is copied below, and is also available with a FAQ at: http://scripts.sil.org/OFL + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/docs/assets/fonts/OTF/SourceCodePro-Bold.otf b/docs/assets/fonts/OTF/SourceCodePro-Bold.otf new file mode 100755 index 0000000..f4e576c Binary files /dev/null and b/docs/assets/fonts/OTF/SourceCodePro-Bold.otf differ diff --git a/docs/assets/fonts/OTF/SourceCodePro-Regular.otf b/docs/assets/fonts/OTF/SourceCodePro-Regular.otf new file mode 100755 index 0000000..4e3b9d0 Binary files /dev/null and b/docs/assets/fonts/OTF/SourceCodePro-Regular.otf differ diff --git a/docs/assets/fonts/TTF/SourceCodePro-Bold.ttf b/docs/assets/fonts/TTF/SourceCodePro-Bold.ttf new file mode 100755 index 0000000..e0c576f Binary files /dev/null and b/docs/assets/fonts/TTF/SourceCodePro-Bold.ttf differ diff --git a/docs/assets/fonts/TTF/SourceCodePro-Regular.ttf b/docs/assets/fonts/TTF/SourceCodePro-Regular.ttf new file mode 100755 index 0000000..437f472 Binary files /dev/null and b/docs/assets/fonts/TTF/SourceCodePro-Regular.ttf differ diff --git a/docs/assets/fonts/WOFF/OTF/SourceCodePro-Bold.otf.woff b/docs/assets/fonts/WOFF/OTF/SourceCodePro-Bold.otf.woff new file mode 100755 index 0000000..cf96099 Binary files /dev/null and b/docs/assets/fonts/WOFF/OTF/SourceCodePro-Bold.otf.woff differ diff --git a/docs/assets/fonts/WOFF/OTF/SourceCodePro-Regular.otf.woff b/docs/assets/fonts/WOFF/OTF/SourceCodePro-Regular.otf.woff new file mode 100755 index 0000000..395436e Binary files /dev/null and b/docs/assets/fonts/WOFF/OTF/SourceCodePro-Regular.otf.woff differ diff --git a/docs/assets/fonts/WOFF/TTF/SourceCodePro-Bold.ttf.woff b/docs/assets/fonts/WOFF/TTF/SourceCodePro-Bold.ttf.woff new file mode 100755 index 0000000..c65ba84 Binary files /dev/null and b/docs/assets/fonts/WOFF/TTF/SourceCodePro-Bold.ttf.woff differ diff --git a/docs/assets/fonts/WOFF/TTF/SourceCodePro-Regular.ttf.woff b/docs/assets/fonts/WOFF/TTF/SourceCodePro-Regular.ttf.woff new file mode 100755 index 0000000..0af792a Binary files /dev/null and b/docs/assets/fonts/WOFF/TTF/SourceCodePro-Regular.ttf.woff differ diff --git a/docs/assets/fonts/WOFF2/OTF/SourceCodePro-Bold.otf.woff2 b/docs/assets/fonts/WOFF2/OTF/SourceCodePro-Bold.otf.woff2 new file mode 100755 index 0000000..cbe3835 Binary files /dev/null and b/docs/assets/fonts/WOFF2/OTF/SourceCodePro-Bold.otf.woff2 differ diff --git a/docs/assets/fonts/WOFF2/OTF/SourceCodePro-Regular.otf.woff2 b/docs/assets/fonts/WOFF2/OTF/SourceCodePro-Regular.otf.woff2 new file mode 100755 index 0000000..65cd591 Binary files /dev/null and b/docs/assets/fonts/WOFF2/OTF/SourceCodePro-Regular.otf.woff2 differ diff --git a/docs/assets/fonts/WOFF2/TTF/SourceCodePro-Bold.ttf.woff2 b/docs/assets/fonts/WOFF2/TTF/SourceCodePro-Bold.ttf.woff2 new file mode 100755 index 0000000..b78d523 Binary files /dev/null and b/docs/assets/fonts/WOFF2/TTF/SourceCodePro-Bold.ttf.woff2 differ diff --git a/docs/assets/fonts/WOFF2/TTF/SourceCodePro-Regular.ttf.woff2 b/docs/assets/fonts/WOFF2/TTF/SourceCodePro-Regular.ttf.woff2 new file mode 100755 index 0000000..18d2199 Binary files /dev/null and b/docs/assets/fonts/WOFF2/TTF/SourceCodePro-Regular.ttf.woff2 differ diff --git a/docs/assets/fonts/source-code-pro.css b/docs/assets/fonts/source-code-pro.css new file mode 100755 index 0000000..3abb4f0 --- /dev/null +++ b/docs/assets/fonts/source-code-pro.css @@ -0,0 +1,23 @@ +@font-face{ + font-family: 'Source Code Pro'; + font-weight: 400; + font-style: normal; + font-stretch: normal; + src: url('EOT/SourceCodePro-Regular.eot') format('embedded-opentype'), + url('WOFF2/TTF/SourceCodePro-Regular.ttf.woff2') format('woff2'), + url('WOFF/OTF/SourceCodePro-Regular.otf.woff') format('woff'), + url('OTF/SourceCodePro-Regular.otf') format('opentype'), + url('TTF/SourceCodePro-Regular.ttf') format('truetype'); +} + +@font-face{ + font-family: 'Source Code Pro'; + font-weight: 700; + font-style: normal; + font-stretch: normal; + src: url('EOT/SourceCodePro-Bold.eot') format('embedded-opentype'), + url('WOFF2/TTF/SourceCodePro-Bold.ttf.woff2') format('woff2'), + url('WOFF/OTF/SourceCodePro-Bold.otf.woff') format('woff'), + url('OTF/SourceCodePro-Bold.otf') format('opentype'), + url('TTF/SourceCodePro-Bold.ttf') format('truetype'); +} diff --git a/docs/assets/github.css b/docs/assets/github.css new file mode 100644 index 0000000..8852abb --- /dev/null +++ b/docs/assets/github.css @@ -0,0 +1,123 @@ +/* + +github.com style (c) Vasily Polovnyov + +*/ + +.hljs { + display: block; + overflow-x: auto; + padding: 0.5em; + color: #333; + background: #f8f8f8; + -webkit-text-size-adjust: none; +} + +.hljs-comment, +.diff .hljs-header, +.hljs-javadoc { + color: #998; + font-style: italic; +} + +.hljs-keyword, +.css .rule .hljs-keyword, +.hljs-winutils, +.nginx .hljs-title, +.hljs-subst, +.hljs-request, +.hljs-status { + color: #1184CE; +} + +.hljs-number, +.hljs-hexcolor, +.ruby .hljs-constant { + color: #ed225d; +} + +.hljs-string, +.hljs-tag .hljs-value, +.hljs-phpdoc, +.hljs-dartdoc, +.tex .hljs-formula { + color: #ed225d; +} + +.hljs-title, +.hljs-id, +.scss .hljs-preprocessor { + color: #900; + font-weight: bold; +} + +.hljs-list .hljs-keyword, +.hljs-subst { + font-weight: normal; +} + +.hljs-class .hljs-title, +.hljs-type, +.vhdl .hljs-literal, +.tex .hljs-command { + color: #458; + font-weight: bold; +} + +.hljs-tag, +.hljs-tag .hljs-title, +.hljs-rules .hljs-property, +.django .hljs-tag .hljs-keyword { + color: #000080; + font-weight: normal; +} + +.hljs-attribute, +.hljs-variable, +.lisp .hljs-body { + color: #008080; +} + +.hljs-regexp { + color: #009926; +} + +.hljs-symbol, +.ruby .hljs-symbol .hljs-string, +.lisp .hljs-keyword, +.clojure .hljs-keyword, +.scheme .hljs-keyword, +.tex .hljs-special, +.hljs-prompt { + color: #990073; +} + +.hljs-built_in { + color: #0086b3; +} + +.hljs-preprocessor, +.hljs-pragma, +.hljs-pi, +.hljs-doctype, +.hljs-shebang, +.hljs-cdata { + color: #999; + font-weight: bold; +} + +.hljs-deletion { + background: #fdd; +} + +.hljs-addition { + background: #dfd; +} + +.diff .hljs-change { + background: #0086b3; +} + +.hljs-chunk { + color: #aaa; +} diff --git a/docs/assets/site.js b/docs/assets/site.js new file mode 100644 index 0000000..f86584f --- /dev/null +++ b/docs/assets/site.js @@ -0,0 +1,168 @@ +/* global anchors */ + +// add anchor links to headers +anchors.options.placement = 'left'; +anchors.add('h3'); + +// Filter UI +var tocElements = document.getElementById('toc').getElementsByTagName('li'); + +document.getElementById('filter-input').addEventListener('keyup', function(e) { + var i, element, children; + + // enter key + if (e.keyCode === 13) { + // go to the first displayed item in the toc + for (i = 0; i < tocElements.length; i++) { + element = tocElements[i]; + if (!element.classList.contains('display-none')) { + location.replace(element.firstChild.href); + return e.preventDefault(); + } + } + } + + var match = function() { + return true; + }; + + var value = this.value.toLowerCase(); + + if (!value.match(/^\s*$/)) { + match = function(element) { + var html = element.firstChild.innerHTML; + return html && html.toLowerCase().indexOf(value) !== -1; + }; + } + + for (i = 0; i < tocElements.length; i++) { + element = tocElements[i]; + children = Array.from(element.getElementsByTagName('li')); + if (match(element) || children.some(match)) { + element.classList.remove('display-none'); + } else { + element.classList.add('display-none'); + } + } +}); + +var items = document.getElementsByClassName('toggle-sibling'); +for (var j = 0; j < items.length; j++) { + items[j].addEventListener('click', toggleSibling); +} + +function toggleSibling() { + var stepSibling = this.parentNode.getElementsByClassName('toggle-target')[0]; + var icon = this.getElementsByClassName('icon')[0]; + var klass = 'display-none'; + if (stepSibling.classList.contains(klass)) { + stepSibling.classList.remove(klass); + icon.innerHTML = '▾'; + } else { + stepSibling.classList.add(klass); + icon.innerHTML = '▸'; + } +} + +function showHashTarget(targetId) { + if (targetId) { + var hashTarget = document.getElementById(targetId); + // new target is hidden + if ( + hashTarget && + hashTarget.offsetHeight === 0 && + hashTarget.parentNode.parentNode.classList.contains('display-none') + ) { + hashTarget.parentNode.parentNode.classList.remove('display-none'); + } + } +} + +function scrollIntoView(targetId) { + // Only scroll to element if we don't have a stored scroll position. + if (targetId && !history.state) { + var hashTarget = document.getElementById(targetId); + if (hashTarget) { + hashTarget.scrollIntoView(); + } + } +} + +function gotoCurrentTarget() { + showHashTarget(location.hash.substring(1)); + scrollIntoView(location.hash.substring(1)); +} + +window.addEventListener('hashchange', gotoCurrentTarget); +gotoCurrentTarget(); + +var toclinks = document.getElementsByClassName('pre-open'); +for (var k = 0; k < toclinks.length; k++) { + toclinks[k].addEventListener('mousedown', preOpen, false); +} + +function preOpen() { + showHashTarget(this.hash.substring(1)); +} + +var split_left = document.querySelector('#split-left'); +var split_right = document.querySelector('#split-right'); +var split_parent = split_left.parentNode; +var cw_with_sb = split_left.clientWidth; +split_left.style.overflow = 'hidden'; +var cw_without_sb = split_left.clientWidth; +split_left.style.overflow = ''; + +Split(['#split-left', '#split-right'], { + elementStyle: function(dimension, size, gutterSize) { + return { + 'flex-basis': 'calc(' + size + '% - ' + gutterSize + 'px)' + }; + }, + gutterStyle: function(dimension, gutterSize) { + return { + 'flex-basis': gutterSize + 'px' + }; + }, + gutterSize: 20, + sizes: [33, 67] +}); + +// Chrome doesn't remember scroll position properly so do it ourselves. +// Also works on Firefox and Edge. + +function updateState() { + history.replaceState( + { + left_top: split_left.scrollTop, + right_top: split_right.scrollTop + }, + document.title + ); +} + +function loadState(ev) { + if (ev) { + // Edge doesn't replace change history.state on popstate. + history.replaceState(ev.state, document.title); + } + if (history.state) { + split_left.scrollTop = history.state.left_top; + split_right.scrollTop = history.state.right_top; + } +} + +window.addEventListener('load', function() { + // Restore after Firefox scrolls to hash. + setTimeout(function() { + loadState(); + // Update with initial scroll position. + updateState(); + // Update scroll positions only after we've loaded because Firefox + // emits an initial scroll event with 0. + split_left.addEventListener('scroll', updateState); + split_right.addEventListener('scroll', updateState); + }, 1); +}); + +window.addEventListener('popstate', loadState); diff --git a/docs/assets/split.css b/docs/assets/split.css new file mode 100644 index 0000000..2d7779e --- /dev/null +++ b/docs/assets/split.css @@ -0,0 +1,15 @@ +.gutter { + background-color: #f5f5f5; + background-repeat: no-repeat; + background-position: 50%; +} + +.gutter.gutter-vertical { + background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB4AAAAFAQMAAABo7865AAAABlBMVEVHcEzMzMzyAv2sAAAAAXRSTlMAQObYZgAAABBJREFUeF5jOAMEEAIEEFwAn3kMwcB6I2AAAAAASUVORK5CYII='); + cursor: ns-resize; +} + +.gutter.gutter-horizontal { + background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAeCAYAAADkftS9AAAAIklEQVQoU2M4c+bMfxAGAgYYmwGrIIiDjrELjpo5aiZeMwF+yNnOs5KSvgAAAABJRU5ErkJggg=='); + cursor: ew-resize; +} diff --git a/docs/assets/split.js b/docs/assets/split.js new file mode 100644 index 0000000..2c52481 --- /dev/null +++ b/docs/assets/split.js @@ -0,0 +1,586 @@ +/*! Split.js - v1.3.5 */ +// https://github.com/nathancahill/Split.js +// Copyright (c) 2017 Nathan Cahill; Licensed MIT + +(function(global, factory) { + typeof exports === 'object' && typeof module !== 'undefined' + ? (module.exports = factory()) + : typeof define === 'function' && define.amd + ? define(factory) + : (global.Split = factory()); +})(this, function() { + 'use strict'; + // The programming goals of Split.js are to deliver readable, understandable and + // maintainable code, while at the same time manually optimizing for tiny minified file size, + // browser compatibility without additional requirements, graceful fallback (IE8 is supported) + // and very few assumptions about the user's page layout. + var global = window; + var document = global.document; + + // Save a couple long function names that are used frequently. + // This optimization saves around 400 bytes. + var addEventListener = 'addEventListener'; + var removeEventListener = 'removeEventListener'; + var getBoundingClientRect = 'getBoundingClientRect'; + var NOOP = function() { + return false; + }; + + // Figure out if we're in IE8 or not. IE8 will still render correctly, + // but will be static instead of draggable. + var isIE8 = global.attachEvent && !global[addEventListener]; + + // This library only needs two helper functions: + // + // The first determines which prefixes of CSS calc we need. + // We only need to do this once on startup, when this anonymous function is called. + // + // Tests -webkit, -moz and -o prefixes. Modified from StackOverflow: + // http://stackoverflow.com/questions/16625140/js-feature-detection-to-detect-the-usage-of-webkit-calc-over-calc/16625167#16625167 + var calc = + ['', '-webkit-', '-moz-', '-o-'] + .filter(function(prefix) { + var el = document.createElement('div'); + el.style.cssText = 'width:' + prefix + 'calc(9px)'; + + return !!el.style.length; + }) + .shift() + 'calc'; + + // The second helper function allows elements and string selectors to be used + // interchangeably. In either case an element is returned. This allows us to + // do `Split([elem1, elem2])` as well as `Split(['#id1', '#id2'])`. + var elementOrSelector = function(el) { + if (typeof el === 'string' || el instanceof String) { + return document.querySelector(el); + } + + return el; + }; + + // The main function to initialize a split. Split.js thinks about each pair + // of elements as an independant pair. Dragging the gutter between two elements + // only changes the dimensions of elements in that pair. This is key to understanding + // how the following functions operate, since each function is bound to a pair. + // + // A pair object is shaped like this: + // + // { + // a: DOM element, + // b: DOM element, + // aMin: Number, + // bMin: Number, + // dragging: Boolean, + // parent: DOM element, + // isFirst: Boolean, + // isLast: Boolean, + // direction: 'horizontal' | 'vertical' + // } + // + // The basic sequence: + // + // 1. Set defaults to something sane. `options` doesn't have to be passed at all. + // 2. Initialize a bunch of strings based on the direction we're splitting. + // A lot of the behavior in the rest of the library is paramatized down to + // rely on CSS strings and classes. + // 3. Define the dragging helper functions, and a few helpers to go with them. + // 4. Loop through the elements while pairing them off. Every pair gets an + // `pair` object, a gutter, and special isFirst/isLast properties. + // 5. Actually size the pair elements, insert gutters and attach event listeners. + var Split = function(ids, options) { + if (options === void 0) options = {}; + + var dimension; + var clientDimension; + var clientAxis; + var position; + var paddingA; + var paddingB; + var elements; + + // All DOM elements in the split should have a common parent. We can grab + // the first elements parent and hope users read the docs because the + // behavior will be whacky otherwise. + var parent = elementOrSelector(ids[0]).parentNode; + var parentFlexDirection = global.getComputedStyle(parent).flexDirection; + + // Set default options.sizes to equal percentages of the parent element. + var sizes = + options.sizes || + ids.map(function() { + return 100 / ids.length; + }); + + // Standardize minSize to an array if it isn't already. This allows minSize + // to be passed as a number. + var minSize = options.minSize !== undefined ? options.minSize : 100; + var minSizes = Array.isArray(minSize) + ? minSize + : ids.map(function() { + return minSize; + }); + var gutterSize = options.gutterSize !== undefined ? options.gutterSize : 10; + var snapOffset = options.snapOffset !== undefined ? options.snapOffset : 30; + var direction = options.direction || 'horizontal'; + var cursor = + options.cursor || + (direction === 'horizontal' ? 'ew-resize' : 'ns-resize'); + var gutter = + options.gutter || + function(i, gutterDirection) { + var gut = document.createElement('div'); + gut.className = 'gutter gutter-' + gutterDirection; + return gut; + }; + var elementStyle = + options.elementStyle || + function(dim, size, gutSize) { + var style = {}; + + if (typeof size !== 'string' && !(size instanceof String)) { + if (!isIE8) { + style[dim] = calc + '(' + size + '% - ' + gutSize + 'px)'; + } else { + style[dim] = size + '%'; + } + } else { + style[dim] = size; + } + + return style; + }; + var gutterStyle = + options.gutterStyle || + function(dim, gutSize) { + return (obj = {}), (obj[dim] = gutSize + 'px'), obj; + var obj; + }; + + // 2. Initialize a bunch of strings based on the direction we're splitting. + // A lot of the behavior in the rest of the library is paramatized down to + // rely on CSS strings and classes. + if (direction === 'horizontal') { + dimension = 'width'; + clientDimension = 'clientWidth'; + clientAxis = 'clientX'; + position = 'left'; + paddingA = 'paddingLeft'; + paddingB = 'paddingRight'; + } else if (direction === 'vertical') { + dimension = 'height'; + clientDimension = 'clientHeight'; + clientAxis = 'clientY'; + position = 'top'; + paddingA = 'paddingTop'; + paddingB = 'paddingBottom'; + } + + // 3. Define the dragging helper functions, and a few helpers to go with them. + // Each helper is bound to a pair object that contains it's metadata. This + // also makes it easy to store references to listeners that that will be + // added and removed. + // + // Even though there are no other functions contained in them, aliasing + // this to self saves 50 bytes or so since it's used so frequently. + // + // The pair object saves metadata like dragging state, position and + // event listener references. + + function setElementSize(el, size, gutSize) { + // Split.js allows setting sizes via numbers (ideally), or if you must, + // by string, like '300px'. This is less than ideal, because it breaks + // the fluid layout that `calc(% - px)` provides. You're on your own if you do that, + // make sure you calculate the gutter size by hand. + var style = elementStyle(dimension, size, gutSize); + + // eslint-disable-next-line no-param-reassign + Object.keys(style).forEach(function(prop) { + return (el.style[prop] = style[prop]); + }); + } + + function setGutterSize(gutterElement, gutSize) { + var style = gutterStyle(dimension, gutSize); + + // eslint-disable-next-line no-param-reassign + Object.keys(style).forEach(function(prop) { + return (gutterElement.style[prop] = style[prop]); + }); + } + + // Actually adjust the size of elements `a` and `b` to `offset` while dragging. + // calc is used to allow calc(percentage + gutterpx) on the whole split instance, + // which allows the viewport to be resized without additional logic. + // Element a's size is the same as offset. b's size is total size - a size. + // Both sizes are calculated from the initial parent percentage, + // then the gutter size is subtracted. + function adjust(offset) { + var a = elements[this.a]; + var b = elements[this.b]; + var percentage = a.size + b.size; + + a.size = offset / this.size * percentage; + b.size = percentage - offset / this.size * percentage; + + setElementSize(a.element, a.size, this.aGutterSize); + setElementSize(b.element, b.size, this.bGutterSize); + } + + // drag, where all the magic happens. The logic is really quite simple: + // + // 1. Ignore if the pair is not dragging. + // 2. Get the offset of the event. + // 3. Snap offset to min if within snappable range (within min + snapOffset). + // 4. Actually adjust each element in the pair to offset. + // + // --------------------------------------------------------------------- + // | | <- a.minSize || b.minSize -> | | + // | | | <- this.snapOffset || this.snapOffset -> | | | + // | | | || | | | + // | | | || | | | + // --------------------------------------------------------------------- + // | <- this.start this.size -> | + function drag(e) { + var offset; + + if (!this.dragging) { + return; + } + + // Get the offset of the event from the first side of the + // pair `this.start`. Supports touch events, but not multitouch, so only the first + // finger `touches[0]` is counted. + if ('touches' in e) { + offset = e.touches[0][clientAxis] - this.start; + } else { + offset = e[clientAxis] - this.start; + } + + // If within snapOffset of min or max, set offset to min or max. + // snapOffset buffers a.minSize and b.minSize, so logic is opposite for both. + // Include the appropriate gutter sizes to prevent overflows. + if (offset <= elements[this.a].minSize + snapOffset + this.aGutterSize) { + offset = elements[this.a].minSize + this.aGutterSize; + } else if ( + offset >= + this.size - (elements[this.b].minSize + snapOffset + this.bGutterSize) + ) { + offset = this.size - (elements[this.b].minSize + this.bGutterSize); + } + + // Actually adjust the size. + adjust.call(this, offset); + + // Call the drag callback continously. Don't do anything too intensive + // in this callback. + if (options.onDrag) { + options.onDrag(); + } + } + + // Cache some important sizes when drag starts, so we don't have to do that + // continously: + // + // `size`: The total size of the pair. First + second + first gutter + second gutter. + // `start`: The leading side of the first element. + // + // ------------------------------------------------ + // | aGutterSize -> ||| | + // | ||| | + // | ||| | + // | ||| <- bGutterSize | + // ------------------------------------------------ + // | <- start size -> | + function calculateSizes() { + // Figure out the parent size minus padding. + var a = elements[this.a].element; + var b = elements[this.b].element; + + this.size = + a[getBoundingClientRect]()[dimension] + + b[getBoundingClientRect]()[dimension] + + this.aGutterSize + + this.bGutterSize; + this.start = a[getBoundingClientRect]()[position]; + } + + // stopDragging is very similar to startDragging in reverse. + function stopDragging() { + var self = this; + var a = elements[self.a].element; + var b = elements[self.b].element; + + if (self.dragging && options.onDragEnd) { + options.onDragEnd(); + } + + self.dragging = false; + + // Remove the stored event listeners. This is why we store them. + global[removeEventListener]('mouseup', self.stop); + global[removeEventListener]('touchend', self.stop); + global[removeEventListener]('touchcancel', self.stop); + + self.parent[removeEventListener]('mousemove', self.move); + self.parent[removeEventListener]('touchmove', self.move); + + // Delete them once they are removed. I think this makes a difference + // in memory usage with a lot of splits on one page. But I don't know for sure. + delete self.stop; + delete self.move; + + a[removeEventListener]('selectstart', NOOP); + a[removeEventListener]('dragstart', NOOP); + b[removeEventListener]('selectstart', NOOP); + b[removeEventListener]('dragstart', NOOP); + + a.style.userSelect = ''; + a.style.webkitUserSelect = ''; + a.style.MozUserSelect = ''; + a.style.pointerEvents = ''; + + b.style.userSelect = ''; + b.style.webkitUserSelect = ''; + b.style.MozUserSelect = ''; + b.style.pointerEvents = ''; + + self.gutter.style.cursor = ''; + self.parent.style.cursor = ''; + } + + // startDragging calls `calculateSizes` to store the inital size in the pair object. + // It also adds event listeners for mouse/touch events, + // and prevents selection while dragging so avoid the selecting text. + function startDragging(e) { + // Alias frequently used variables to save space. 200 bytes. + var self = this; + var a = elements[self.a].element; + var b = elements[self.b].element; + + // Call the onDragStart callback. + if (!self.dragging && options.onDragStart) { + options.onDragStart(); + } + + // Don't actually drag the element. We emulate that in the drag function. + e.preventDefault(); + + // Set the dragging property of the pair object. + self.dragging = true; + + // Create two event listeners bound to the same pair object and store + // them in the pair object. + self.move = drag.bind(self); + self.stop = stopDragging.bind(self); + + // All the binding. `window` gets the stop events in case we drag out of the elements. + global[addEventListener]('mouseup', self.stop); + global[addEventListener]('touchend', self.stop); + global[addEventListener]('touchcancel', self.stop); + + self.parent[addEventListener]('mousemove', self.move); + self.parent[addEventListener]('touchmove', self.move); + + // Disable selection. Disable! + a[addEventListener]('selectstart', NOOP); + a[addEventListener]('dragstart', NOOP); + b[addEventListener]('selectstart', NOOP); + b[addEventListener]('dragstart', NOOP); + + a.style.userSelect = 'none'; + a.style.webkitUserSelect = 'none'; + a.style.MozUserSelect = 'none'; + a.style.pointerEvents = 'none'; + + b.style.userSelect = 'none'; + b.style.webkitUserSelect = 'none'; + b.style.MozUserSelect = 'none'; + b.style.pointerEvents = 'none'; + + // Set the cursor, both on the gutter and the parent element. + // Doing only a, b and gutter causes flickering. + self.gutter.style.cursor = cursor; + self.parent.style.cursor = cursor; + + // Cache the initial sizes of the pair. + calculateSizes.call(self); + } + + // 5. Create pair and element objects. Each pair has an index reference to + // elements `a` and `b` of the pair (first and second elements). + // Loop through the elements while pairing them off. Every pair gets a + // `pair` object, a gutter, and isFirst/isLast properties. + // + // Basic logic: + // + // - Starting with the second element `i > 0`, create `pair` objects with + // `a = i - 1` and `b = i` + // - Set gutter sizes based on the _pair_ being first/last. The first and last + // pair have gutterSize / 2, since they only have one half gutter, and not two. + // - Create gutter elements and add event listeners. + // - Set the size of the elements, minus the gutter sizes. + // + // ----------------------------------------------------------------------- + // | i=0 | i=1 | i=2 | i=3 | + // | | isFirst | | isLast | + // | pair 0 pair 1 pair 2 | + // | | | | | + // ----------------------------------------------------------------------- + var pairs = []; + elements = ids.map(function(id, i) { + // Create the element object. + var element = { + element: elementOrSelector(id), + size: sizes[i], + minSize: minSizes[i] + }; + + var pair; + + if (i > 0) { + // Create the pair object with it's metadata. + pair = { + a: i - 1, + b: i, + dragging: false, + isFirst: i === 1, + isLast: i === ids.length - 1, + direction: direction, + parent: parent + }; + + // For first and last pairs, first and last gutter width is half. + pair.aGutterSize = gutterSize; + pair.bGutterSize = gutterSize; + + if (pair.isFirst) { + pair.aGutterSize = gutterSize / 2; + } + + if (pair.isLast) { + pair.bGutterSize = gutterSize / 2; + } + + // if the parent has a reverse flex-direction, switch the pair elements. + if ( + parentFlexDirection === 'row-reverse' || + parentFlexDirection === 'column-reverse' + ) { + var temp = pair.a; + pair.a = pair.b; + pair.b = temp; + } + } + + // Determine the size of the current element. IE8 is supported by + // staticly assigning sizes without draggable gutters. Assigns a string + // to `size`. + // + // IE9 and above + if (!isIE8) { + // Create gutter elements for each pair. + if (i > 0) { + var gutterElement = gutter(i, direction); + setGutterSize(gutterElement, gutterSize); + + gutterElement[addEventListener]( + 'mousedown', + startDragging.bind(pair) + ); + gutterElement[addEventListener]( + 'touchstart', + startDragging.bind(pair) + ); + + parent.insertBefore(gutterElement, element.element); + + pair.gutter = gutterElement; + } + } + + // Set the element size to our determined size. + // Half-size gutters for first and last elements. + if (i === 0 || i === ids.length - 1) { + setElementSize(element.element, element.size, gutterSize / 2); + } else { + setElementSize(element.element, element.size, gutterSize); + } + + var computedSize = element.element[getBoundingClientRect]()[dimension]; + + if (computedSize < element.minSize) { + element.minSize = computedSize; + } + + // After the first iteration, and we have a pair object, append it to the + // list of pairs. + if (i > 0) { + pairs.push(pair); + } + + return element; + }); + + function setSizes(newSizes) { + newSizes.forEach(function(newSize, i) { + if (i > 0) { + var pair = pairs[i - 1]; + var a = elements[pair.a]; + var b = elements[pair.b]; + + a.size = newSizes[i - 1]; + b.size = newSize; + + setElementSize(a.element, a.size, pair.aGutterSize); + setElementSize(b.element, b.size, pair.bGutterSize); + } + }); + } + + function destroy() { + pairs.forEach(function(pair) { + pair.parent.removeChild(pair.gutter); + elements[pair.a].element.style[dimension] = ''; + elements[pair.b].element.style[dimension] = ''; + }); + } + + if (isIE8) { + return { + setSizes: setSizes, + destroy: destroy + }; + } + + return { + setSizes: setSizes, + getSizes: function getSizes() { + return elements.map(function(element) { + return element.size; + }); + }, + collapse: function collapse(i) { + if (i === pairs.length) { + var pair = pairs[i - 1]; + + calculateSizes.call(pair); + + if (!isIE8) { + adjust.call(pair, pair.size - pair.bGutterSize); + } + } else { + var pair$1 = pairs[i]; + + calculateSizes.call(pair$1); + + if (!isIE8) { + adjust.call(pair$1, pair$1.aGutterSize); + } + } + }, + destroy: destroy + }; + }; + + return Split; +}); diff --git a/docs/assets/style.css b/docs/assets/style.css new file mode 100644 index 0000000..5265ea1 --- /dev/null +++ b/docs/assets/style.css @@ -0,0 +1,140 @@ +.documentation { + font-family: Helvetica, sans-serif; + color: #666; + line-height: 1.5; + background: #f5f5f5; +} + +.black { + color: #666; +} + +.bg-white { + background-color: #fff; +} + +h4 { + margin: 20px 0 10px 0; +} + +.documentation h3 { + color: #000; +} + +.border-bottom { + border-color: #ddd; +} + +a { + color: #1184CE; + text-decoration: none; +} + +.documentation a[href]:hover { + text-decoration: underline; +} + +a:hover { + cursor: pointer; +} + +.py1-ul li { + padding: 5px 0; +} + +.max-height-100 { + max-height: 100%; +} + +.height-viewport-100 { + height: 100vh; +} + +section:target h3 { + font-weight:700; +} + +.documentation td, +.documentation th { + padding: .25rem .25rem; +} + +h1:hover .anchorjs-link, +h2:hover .anchorjs-link, +h3:hover .anchorjs-link, +h4:hover .anchorjs-link { + opacity: 1; +} + +.fix-3 { + width: 25%; + max-width: 244px; +} + +.fix-3 { + width: 25%; + max-width: 244px; +} + +@media (min-width: 52em) { + .fix-margin-3 { + margin-left: 25%; + } +} + +.pre, pre, code, .code { + font-family: Source Code Pro,Menlo,Consolas,Liberation Mono,monospace; + font-size: 14px; +} + +.fill-light { + background: #F9F9F9; +} + +.width2 { + width: 1rem; +} + +.input { + font-family: inherit; + display: block; + width: 100%; + height: 2rem; + padding: .5rem; + margin-bottom: 1rem; + border: 1px solid #ccc; + font-size: .875rem; + border-radius: 3px; + box-sizing: border-box; +} + +table { + border-collapse: collapse; +} + +.prose table th, +.prose table td { + text-align: left; + padding:8px; + border:1px solid #ddd; +} + +.prose table th:nth-child(1) { border-right: none; } +.prose table th:nth-child(2) { border-left: none; } + +.prose table { + border:1px solid #ddd; +} + +.prose-big { + font-size: 18px; + line-height: 30px; +} + +.quiet { + opacity: 0.7; +} + +.minishadow { + box-shadow: 2px 2px 10px #f3f3f3; +} diff --git a/docs/index.html b/docs/index.html new file mode 100644 index 0000000..42155e3 --- /dev/null +++ b/docs/index.html @@ -0,0 +1,1207 @@ + + + + + tuyapi 2.0.3 | Documentation + + + + + + + +
+
+
+

tuyapi

+
2.0.3
+ +
+ +
+ +
+
+
+ + +
+ + +
+ +

+ TuyaDevice +

+ + +
+ + +

Represents a Tuya device.

+ + +
new TuyaDevice(options: Object)
+ + + + + + + + + + + +
Parameters
+
+ +
+
+ options (Object) + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameDescription
options.ip String? + IP of device +
options.port Number + + (default 6668) + port of device +
options.id String + ID of device +
options.key String + encryption key of device +
options.version Number + + (default 3.1) + protocol version +
+ +
+ +
+ + + + + + + + + +
Example
+ + +
const tuya = new TuyaDevice({id: 'xxxxxxxxxxxxxxxxxxxx', key: 'xxxxxxxxxxxxxxxx'})
+ + + + + + +
Instance Members
+
+ +
+
+
+ + resolveIds(options?) +
+
+ +
+ +
+
+
+ + get(options?) +
+
+ +
+ +
+
+
+ + set(options) +
+
+ +
+ +
+ + + + +
+ + + + +
+ + +
+ +

+ TuyaCipher +

+ + +
+ + +

Class for encrypting and decrypting payloads.

+ + +
new TuyaCipher(options: Object)
+ + + + + + + + + + + +
Parameters
+
+ +
+
+ options (Object) + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameDescription
options.key String + localKey of cipher +
options.version Number + protocol version +
+ +
+ +
+ + + + + + + + + +
Example
+ + +
const cipher = new TuyaCipher({key: 'xxxxxxxxxxxxxxxx', version: 3.1})
+ + + + + + +
Instance Members
+
+ +
+
+
+ + encrypt(options) +
+
+ +
+ +
+
+
+ + decrypt(data) +
+
+ +
+ +
+
+
+ + md5(data) +
+
+ +
+ +
+ + + + +
+ + + + +
+ + +
+ +

+ MessageParser +

+ + +
+ + +

Class for decoding and encoding payloads.

+ + +
new MessageParser()
+ + + + + + + + + + + + + + + + + + + + + +
Static Members
+
+ +
+
+
+ + parse(data) +
+
+ +
+ +
+
+
+ + encode(options) +
+
+ +
+ +
+ + + + + + +
+ + + +
+
+ + + + + diff --git a/index.js b/index.js index 06d4947..2377966 100644 --- a/index.js +++ b/index.js @@ -1,5 +1,3 @@ -'use strict'; - // Import packages const dgram = require('dgram'); const net = require('net'); @@ -20,25 +18,19 @@ const Parser = require('./lib/message-parser'); * Update docs on setup * Update docs for DPS * Add comments in code -* Add coverage */ /** * Represents a Tuya device. * @class -* @param {Object} options - options for constructing a TuyaDevice -* @param {String} [options.ip] - IP of device -* @param {Number} [options.port=6668] - port of device -* @param {String} options.id - ID of device -* @param {String} options.key - encryption key of device -* @param {Number} [options.version=3.1] - protocol version +* @param {Object} options +* @param {String} [options.ip] IP of device +* @param {Number} [options.port=6668] port of device +* @param {String} options.id ID of device +* @param {String} options.key encryption key of device +* @param {Number} [options.version=3.1] protocol version * @example * const tuya = new TuyaDevice({id: 'xxxxxxxxxxxxxxxxxxxx', key: 'xxxxxxxxxxxxxxxx'}) -* @example -* const tuya = new TuyaDevice({ -* id: 'xxxxxxxxxxxxxxxxxxxx', -* key: 'xxxxxxxxxxxxxxxx', -* ip: 'xxx.xxx.xxx.xxx') */ function TuyaDevice(options) { this.device = options; @@ -72,10 +64,14 @@ function TuyaDevice(options) { /** * Resolves IDs stored in class to IPs. If you didn't pass IPs to the constructor, * you must call this before doing anything else. -* @param {Object} [options] - options -* @param {Number} [options.timeout=10] - how long, in seconds, to wait for device +* @param {Object} [options] +* @param {Number} [options.timeout=10] +* how long, in seconds, to wait for device * to be resolved before timeout error is thrown -* @returns {Promise} - true if IPs were found and devices are ready to be used +* @example +* tuya.resolveIds().then(() => console.log('ready!')) +* @returns {Promise} +* true if IP was found and device is ready to be used */ TuyaDevice.prototype.resolveIds = function (options) { // Set default options @@ -133,17 +129,18 @@ TuyaDevice.prototype.resolveIds = function (options) { /** * Gets a device's current status. -* Defaults to returning only the value of the first result, -* but by setting {schema: true} you can get everything. -* @param {Object} [options] - options for getting data +* Defaults to returning only the value of the first DPS index. +* @param {Object} [options] * @param {Boolean} [options.schema] -* true to return entire schema, not just the first result +* true to return entire schema of device +* @param {Number} [options.dps=1] +* DPS index to return * @example -* // get status for device with one property +* // get first, default property from device * tuya.get().then(status => console.log(status)) * @example -* // get status for specific device with one property -* tuya.get({id: 'xxxxxxxxxxxxxxxxxxxx'}).then(status => console.log(status)) +* // get second property from device +* tuya.get({dps: 2}).then(status => console.log(status)) * @example * // get all available data from device * tuya.get({schema: true}).then(data => console.log(data)) @@ -151,6 +148,9 @@ TuyaDevice.prototype.resolveIds = function (options) { * returns boolean if no options are provided, otherwise returns object of results */ TuyaDevice.prototype.get = function (options) { + // Set empty object as default + options = options ? options : {}; + const payload = {gwId: this.device.id, devId: this.device.id}; debug('Payload: ', payload); @@ -160,8 +160,10 @@ TuyaDevice.prototype.get = function (options) { return new Promise((resolve, reject) => { this._send(this.device.ip, buffer).then(data => { - if (options !== undefined && options.schema === true) { + if (options.schema === true) { resolve(data); + } else if (options.dps) { + resolve(data.dps[options.dps]); } else { resolve(data.dps['1']); } @@ -173,15 +175,15 @@ TuyaDevice.prototype.get = function (options) { /** * Sets a property on a device. -* @param {Object} options - options for setting properties -* @param {Boolean} options.set - `true` for on, `false` for off -* @param {Number} [options.dps] - dps index to change +* @param {Object} options +* @param {Number} [options.dps=1] DPS index to set +* @param {*} options.set value to set * @example * // set default property * tuya.set({set: true}).then(() => console.log('device was changed')) * @example * // set custom property -* tuya.set({'dps': 2, set: true}).then(() => console.log('device was changed')) +* tuya.set({dps: 2, set: true}).then(() => console.log('device was changed')) * @returns {Promise} - returns `true` if the command succeeded */ TuyaDevice.prototype.set = function (options) { @@ -231,9 +233,9 @@ TuyaDevice.prototype.set = function (options) { /** * Sends a query to a device. * @private -* @param {String} ip - IP of device -* @param {Buffer} buffer - buffer of data -* @returns {Promise} - returned data +* @param {String} ip IP of device +* @param {Buffer} buffer buffer of data +* @returns {Promise} returned data */ TuyaDevice.prototype._send = function (ip, buffer) { debug('Sending this data: ', buffer.toString('hex')); diff --git a/lib/cipher.js b/lib/cipher.js index a395111..10298c3 100644 --- a/lib/cipher.js +++ b/lib/cipher.js @@ -3,11 +3,11 @@ const forge = require('node-forge'); /** * Class for encrypting and decrypting payloads. * @class -* @param {Object} options - options for constructing a TuyaCipher -* @param {String} options.key - localKey of cipher -* @param {Number} options.version - version of protocol +* @param {Object} options +* @param {String} options.key localKey of cipher +* @param {Number} options.version protocol version * @example -* const cipher = new TuyaCipher({key: 'xxxxxxxxxxxxxxxx'}) +* const cipher = new TuyaCipher({key: 'xxxxxxxxxxxxxxxx', version: 3.1}) */ function TuyaCipher(options) { this.cipher = forge.cipher.createCipher('AES-ECB', options.key); @@ -17,12 +17,12 @@ function TuyaCipher(options) { /** * Encrypts data. -* @param {Object} options - options for encryption -* @param {String} options.data - data to encrypt -* @param {Boolean} [options.base64=true] - `true` to return result in Base64 +* @param {Object} options +* @param {String} options.data data to encrypt +* @param {Boolean} [options.base64=true] `true` to return result in Base64 * @example * TuyaCipher.encrypt({data: 'hello world'}) -* @returns {Buffer} - returns buffer unless options.base64 is true +* @returns {Buffer|String} returns Buffer unless options.base64 is true */ TuyaCipher.prototype.encrypt = function (options) { this.cipher.start({iv: ''}); @@ -36,6 +36,12 @@ TuyaCipher.prototype.encrypt = function (options) { return this.cipher.output; }; +/** +* Decrypts data. +* @param {String} data to decrypt +* @returns {Object|String} +* returns object if data is JSON, else returns string +*/ TuyaCipher.prototype.decrypt = function (data) { if (data.indexOf(this.version.toString()) !== -1) { // Data has version number and is encoded in base64 @@ -58,6 +64,11 @@ TuyaCipher.prototype.decrypt = function (data) { } }; +/** +* Calculates a MD5 hash. +* @param {String} data to hash +* @returns {String} last 8 characters of hash of data +*/ TuyaCipher.prototype.md5 = function (data) { const md5hash = forge.md.md5.create().update(data).digest().toHex(); return md5hash.toString().toLowerCase().substr(8, 16); diff --git a/lib/message-parser.js b/lib/message-parser.js index c108efa..cf38211 100644 --- a/lib/message-parser.js +++ b/lib/message-parser.js @@ -2,6 +2,10 @@ const debug = require('debug')('TuyAPI:MessageParser'); const crc = require('crc'); +/** +* Class for decoding and encoding payloads. +* @class +*/ function MessageParser() { this._parsed = false; this._buff = Buffer.alloc(0); @@ -11,11 +15,21 @@ function MessageParser() { this._leftOver = undefined; } -MessageParser.prototype.append = function (buff) { +/** +* Append data to current buffer. +* @param {Buffer} buff data to append +* @private +*/ +MessageParser.prototype._append = function (buff) { this._buff = Buffer.concat([this._buff, buff]); }; -MessageParser.prototype.parse = function () { +/** +* Parse current buffer stored in instance. +* @returns {Boolean} true if successfully parsed +* @private +*/ +MessageParser.prototype._parse = function () { if (this._parsed) { return true; } @@ -79,7 +93,12 @@ MessageParser.prototype.parse = function () { return true; }; -MessageParser.prototype.decode = function () { +/** +* Attempt to parse data to JSON. +* @returns {Undefined|Object|String} +* @private +*/ +MessageParser.prototype._decode = function () { if (this._data.length === 0) { return undefined; } @@ -91,13 +110,17 @@ MessageParser.prototype.decode = function () { } }; -MessageParser.prototype.leftOver = function () { - return this._leftOver; -}; - -// Options.data -// options.commandByte -MessageParser.prototype.encode = function (options) { +/** +* Encode data (usually an object) into +* a protocol-compliant form that a device +* can understand. +* @param {Object} options +* @param {String|Buffer|Object} options.data data to encode +* @param {String} options.commandByte command byte +* @returns {Buffer} binary payload +* @private +*/ +MessageParser.prototype._encode = function (options) { // Ensure data is a Buffer let payload; if (options.data instanceof Buffer) { @@ -126,16 +149,33 @@ MessageParser.prototype.encode = function (options) { return concatBuffer; }; -function parse(data) { +/** +* Static wrapper for lower-level MessageParser +* functions to easily parse packets. +* @param {Buffer} data packet to parse +* @returns {Undefined|Object|String} +* An object or string, depending on whether +* data contains an object. Undefined if +* there is no data in packet. +*/ +MessageParser.parse = function (data) { const p = new MessageParser(); - p.append(data); - p.parse(); - return p.decode(); -} + p._append(data); + p._parse(); + return p._decode(); +}; -function encode(options) { +/** +* Static wrapper for lower-level MessageParser +* functions to easily encode packets +* @param {Object} options +* @param {String|Buffer|Object} options.data data to encode +* @param {String} options.commandByte command byte +* @returns {Buffer} binary payload +*/ +MessageParser.encode = function (options) { const p = new MessageParser(); - return p.encode({data: options.data, commandByte: options.commandByte}); -} + return p._encode({data: options.data, commandByte: options.commandByte}); +}; -module.exports = {parse, encode}; +module.exports = {parse: MessageParser.parse, encode: MessageParser.encode}; diff --git a/package.json b/package.json index 69ad330..33dead1 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,7 @@ "scripts": { "test": "xo && ava", "cover": "nyc npm test && nyc report --reporter=text-lcov | coveralls", - "document": "documentation build index.js -f md | (echo 'Docs \n=========' && cat) > docs/API.md", + "document": "documentation build index.js -f html -o docs", "pushtags": "git push origin master --tags" }, "repository": { diff --git a/requests.json b/requests.json deleted file mode 100644 index 527db0a..0000000 --- a/requests.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "outlet": { - "status": { - "hexByte": "0a", - "command": {"gwId": "", "devId": ""} - }, - "set": { - "hexByte": "07", - "command": {"devId": "", "uid": "", "t": ""} - }, - "prefix": "000055aa00000000000000", - "suffix": "000000000000aa55" - } -}