From 2a1ce667965d4a29b950762f635def22c62582a4 Mon Sep 17 00:00:00 2001 From: dirz88 Date: Tue, 29 Dec 2015 15:46:46 +0100 Subject: [PATCH 1/2] Added area and location - Added menu bar for improved user experience (pushy.js) - Added area: Each macro does have now a location This is my first project with Node.js or generally in programming. I hope it meets the requirements and tell me what I need to adjust, it is adopted. --- app.js | 7 +- static/css/app.less | 47 ++++++++++++- static/css/compiled/app.css | 36 +++++++++- static/css/pushy.css | 120 ++++++++++++++++++++++++++++++++++ static/js/compiled/app.js | 2 +- static/js/vendor/pushy.js | 107 ++++++++++++++++++++++++++++++ static/js/vendor/pushy.min.js | 1 + templates/index.swig | 105 ++++++++++++++++++++--------- test/fixtures/config.json | 40 ++++++++---- test/lirc_web.js | 4 +- 10 files changed, 416 insertions(+), 53 deletions(-) create mode 100644 static/css/pushy.css create mode 100644 static/js/vendor/pushy.js create mode 100644 static/js/vendor/pushy.min.js diff --git a/app.js b/app.js index 912aa54..17493af 100644 --- a/app.js +++ b/app.js @@ -99,7 +99,6 @@ app.get('/macros/:macro.json', function(req, res) { } }); - // Send :remote/:command one time app.post('/remotes/:remote/:command', function(req, res) { lirc_node.irsend.send_once(req.params.remote, req.params.command, function() {}); @@ -122,15 +121,15 @@ app.post('/remotes/:remote/:command/send_stop', function(req, res) { }); // Execute a macro (a collection of commands to one or more remotes) -app.post('/macros/:macro', function(req, res) { +app.post('/macros/:area/:macro', function(req, res) { // If the macro exists, execute each command in the macro with 100msec // delay between each command. - if (config.macros && config.macros[req.params.macro]) { + if (config.macros && config.macros[req.params.area]) { var i = 0; var nextCommand = function() { - var command = config.macros[req.params.macro][i]; + var command = config.macros[req.params.area][req.params.macro][i]; if (!command) { return true; } diff --git a/static/css/app.less b/static/css/app.less index 0c541bf..5da70c8 100644 --- a/static/css/app.less +++ b/static/css/app.less @@ -28,9 +28,15 @@ ul { padding: 0; } +.macrocommands li { + margin: 20px 0; + padding: 0; +} .commands li { margin: 20px 0; padding: 0; + width: 120px; + display: inline-block; } h1 { @@ -52,6 +58,25 @@ h1 { } } +h2 { + background: @colorWetAsphalt; + color: #fff; + font-family: LatoBold; + margin: 0 0 30px; + padding: 10px 0; + text-align: center; + z-index: 6; + + left: 5px; + position: fixed; + top: 0; +// width: 20%; + + &.is-remote { + background: @colorPeterRiver; + } +} + .command { margin: 20px 0; } @@ -82,12 +107,22 @@ h1 { width: 100%; } +.macro { + margin: 0; + padding: 0; + position: relative; +} + .remote { margin: 0; padding: 0; position: relative; } +.remotes-navmenu, +.macros-navmenu { +} + .remotes-nav, .macros-nav { } @@ -98,9 +133,9 @@ h1 { } .back { - position: absolute; + position: fixed; top: 7px; - left: 8px; + left: 30px; cursor: pointer; } @@ -121,6 +156,14 @@ a:active, margin: 0 40px; } +.macro { + display: none; +} + +.macro.active { + display: block; +} + .remote { display: none; } diff --git a/static/css/compiled/app.css b/static/css/compiled/app.css index f6aedae..a63e80b 100644 --- a/static/css/compiled/app.css +++ b/static/css/compiled/app.css @@ -16,9 +16,15 @@ ul { margin: 0; padding: 0; } +.macrocommands li { + margin: 20px 0; + padding: 0; +} .commands li { margin: 20px 0; padding: 0; + width: 120px; + display: inline-block; } h1 { background: #34495e; @@ -36,6 +42,21 @@ h1 { h1.is-remote { background: #3498db; } +h2 { + background: #34495e; + color: #fff; + font-family: LatoBold; + margin: 0 0 30px; + padding: 10px 0; + text-align: center; + z-index: 6; + left: 5px; + position: fixed; + top: 0; +} +h2.is-remote { + background: #3498db; +} .command { margin: 20px 0; } @@ -59,6 +80,11 @@ h1.is-remote { padding-right: 0; width: 100%; } +.macro { + margin: 0; + padding: 0; + position: relative; +} .remote { margin: 0; padding: 0; @@ -69,9 +95,9 @@ h1.is-remote { margin: 20px 0; } .back { - position: absolute; + position: fixed; top: 7px; - left: 8px; + left: 30px; cursor: pointer; } a, @@ -89,6 +115,12 @@ a:active, #container { margin: 0 40px; } +.macro { + display: none; +} +.macro.active { + display: block; +} .remote { display: none; } diff --git a/static/css/pushy.css b/static/css/pushy.css new file mode 100644 index 0000000..53813f8 --- /dev/null +++ b/static/css/pushy.css @@ -0,0 +1,120 @@ +/*! Pushy - v0.9.2 - 2014-9-13 +* Pushy is a responsive off-canvas navigation menu using CSS transforms & transitions. +* https://github.com/christophery/pushy/ +* by Christopher Yee */ + +/* Menu Appearance */ + +.pushy{ + position: fixed; + width: 200px; + height: 100%; + top: 0; + z-index: 9999; + background: #333332; + font-size: 0.9em; + font-weight: bold; + -webkit-box-shadow: inset -10px 0 6px -9px rgba(0, 0, 0, .7); + -moz-box-shadow: inset -10px 0 6px -9px rgba(0, 0, 0, .7); + box-shadow: inset -10px 0 6px -9px rgba(0, 0, 0, .7); + overflow: auto; + -webkit-overflow-scrolling: touch; /* enables momentum scrolling in iOS overflow elements */ +} + +.pushy a{ + display: block; + color: #b3b3b1; + padding: 15px 30px; + border-bottom: 1px solid rgba(0, 0, 0, .1); + border-top: 1px solid rgba(255, 255, 255, .1); + text-decoration: none; +} + +.pushy a:hover{ + background: #00b4ff; + color: #FFF; +} + +/* Menu Movement */ + +.pushy-left{ + -webkit-transform: translate3d(-200px,0,0); + -moz-transform: translate3d(-200px,0,0); + -ms-transform: translate3d(-200px,0,0); + -o-transform: translate3d(-200px,0,0); + transform: translate3d(-200px,0,0); +} + +.pushy-open{ + -webkit-transform: translate3d(0,0,0); + -moz-transform: translate3d(0,0,0); + -ms-transform: translate3d(0,0,0); + -o-transform: translate3d(0,0,0); + transform: translate3d(0,0,0); +} + +.container-push, .push-push{ + -webkit-transform: translate3d(200px,0,0); + -moz-transform: translate3d(200px,0,0); + -ms-transform: translate3d(200px,0,0); + -o-transform: translate3d(200px,0,0); + transform: translate3d(200px,0,0); +} + +/* Menu Transitions */ + +.pushy, #container, .push{ + -webkit-transition: -webkit-transform .2s cubic-bezier(.16, .68, .43, .99); + -moz-transition: -moz-transform .2s cubic-bezier(.16, .68, .43, .99); + -o-transition: -o-transform .2s cubic-bezier(.16, .68, .43, .99); + transition: transform .2s cubic-bezier(.16, .68, .43, .99); +} + +/* Site Overlay */ + +.site-overlay{ + display: none; +} + +.pushy-active .site-overlay{ + display: block; + position: fixed; + top: 0; + right: 0; + bottom: 0; + left: 0; + z-index: 9998; + background-color: rgba(0,0,0,0.5); + -webkit-animation: fade 500ms; + -moz-animation: fade 500ms; + -o-animation: fade 500ms; + animation: fade 500ms; +} + +@keyframes fade{ + 0% { opacity: 0; } + 100% { opacity: 1; } +} + +@-moz-keyframes fade{ + 0% { opacity: 0; } + 100% { opacity: 1; } +} + +@-webkit-keyframes fade{ + 0% { opacity: 0; } + 100% { opacity: 1; } +} + +@-o-keyframes fade{ + 0% { opacity: 0; } + 100% { opacity: 1; } +} + +/* Example Media Query */ + +@media screen and (max-width: 768px){ + .pushy{ + font-size: 1.0em; + } +} diff --git a/static/js/compiled/app.js b/static/js/compiled/app.js index d312aff..72a3c18 100644 --- a/static/js/compiled/app.js +++ b/static/js/compiled/app.js @@ -1,2 +1,2 @@ function FastClick(a){"use strict";var b,c=this;if(this.trackingClick=!1,this.trackingClickStart=0,this.targetElement=null,this.touchStartX=0,this.touchStartY=0,this.lastTouchIdentifier=0,this.layer=a,!a||!a.nodeType)throw new TypeError("Layer must be a document node");this.onClick=function(){return FastClick.prototype.onClick.apply(c,arguments)},this.onMouse=function(){return FastClick.prototype.onMouse.apply(c,arguments)},this.onTouchStart=function(){return FastClick.prototype.onTouchStart.apply(c,arguments)},this.onTouchEnd=function(){return FastClick.prototype.onTouchEnd.apply(c,arguments)},this.onTouchCancel=function(){return FastClick.prototype.onTouchCancel.apply(c,arguments)},"undefined"!=typeof window.ontouchstart&&(this.deviceIsAndroid&&(a.addEventListener("mouseover",this.onMouse,!0),a.addEventListener("mousedown",this.onMouse,!0),a.addEventListener("mouseup",this.onMouse,!0)),a.addEventListener("click",this.onClick,!0),a.addEventListener("touchstart",this.onTouchStart,!1),a.addEventListener("touchend",this.onTouchEnd,!1),a.addEventListener("touchcancel",this.onTouchCancel,!1),Event.prototype.stopImmediatePropagation||(a.removeEventListener=function(b,c,d){var e=Node.prototype.removeEventListener;"click"===b?e.call(a,b,c.hijacked||c,d):e.call(a,b,c,d)},a.addEventListener=function(b,c,d){var e=Node.prototype.addEventListener;"click"===b?e.call(a,b,c.hijacked||(c.hijacked=function(a){a.propagationStopped||c(a)}),d):e.call(a,b,c,d)}),"function"==typeof a.onclick&&(b=a.onclick,a.addEventListener("click",function(a){b(a)},!1),a.onclick=null))}!function(a){String.prototype.trim===a&&(String.prototype.trim=function(){return this.replace(/^\s+|\s+$/g,"")}),Array.prototype.reduce===a&&(Array.prototype.reduce=function(b){if(void 0===this||null===this)throw new TypeError;var c,d=Object(this),e=d.length>>>0,f=0;if("function"!=typeof b)throw new TypeError;if(0==e&&1==arguments.length)throw new TypeError;if(arguments.length>=2)c=arguments[1];else for(;;){if(f in d){c=d[f++];break}if(++f>=e)throw new TypeError}for(;e>f;)f in d&&(c=b.call(a,c,d[f],f,d)),f++;return c})}();var Zepto=function(){function a(a){return null==a?String(a):W[X.call(a)]||"object"}function b(b){return"function"==a(b)}function c(a){return null!=a&&a==a.window}function d(a){return null!=a&&a.nodeType==a.DOCUMENT_NODE}function e(b){return"object"==a(b)}function f(a){return e(a)&&!c(a)&&a.__proto__==Object.prototype}function g(a){return a instanceof Array}function h(a){return"number"==typeof a.length}function i(a){return E.call(a,function(a){return null!=a})}function j(a){return a.length>0?y.fn.concat.apply([],a):a}function k(a){return a.replace(/::/g,"/").replace(/([A-Z]+)([A-Z][a-z])/g,"$1_$2").replace(/([a-z\d])([A-Z])/g,"$1_$2").replace(/_/g,"-").toLowerCase()}function l(a){return a in H?H[a]:H[a]=new RegExp("(^|\\s)"+a+"(\\s|$)")}function m(a,b){return"number"!=typeof b||J[k(a)]?b:b+"px"}function n(a){var b,c;return G[a]||(b=F.createElement(a),F.body.appendChild(b),c=I(b,"").getPropertyValue("display"),b.parentNode.removeChild(b),"none"==c&&(c="block"),G[a]=c),G[a]}function o(a){return"children"in a?D.call(a.children):y.map(a.childNodes,function(a){return 1==a.nodeType?a:void 0})}function p(a,b,c){for(x in b)c&&(f(b[x])||g(b[x]))?(f(b[x])&&!f(a[x])&&(a[x]={}),g(b[x])&&!g(a[x])&&(a[x]=[]),p(a[x],b[x],c)):b[x]!==w&&(a[x]=b[x])}function q(a,b){return b===w?y(a):y(a).filter(b)}function r(a,c,d,e){return b(c)?c.call(a,d,e):c}function s(a,b,c){null==c?a.removeAttribute(b):a.setAttribute(b,c)}function t(a,b){var c=a.className,d=c&&c.baseVal!==w;return b===w?d?c.baseVal:c:void(d?c.baseVal=b:a.className=b)}function u(a){var b;try{return a?"true"==a||("false"==a?!1:"null"==a?null:isNaN(b=Number(a))?/^[\[\{]/.test(a)?y.parseJSON(a):a:b):a}catch(c){return a}}function v(a,b){b(a);for(var c in a.childNodes)v(a.childNodes[c],b)}var w,x,y,z,A,B,C=[],D=C.slice,E=C.filter,F=window.document,G={},H={},I=F.defaultView.getComputedStyle,J={"column-count":1,columns:1,"font-weight":1,"line-height":1,opacity:1,"z-index":1,zoom:1},K=/^\s*<(\w+|!)[^>]*>/,L=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi,M=/^(?:body|html)$/i,N=["val","css","html","text","data","width","height","offset"],O=["after","prepend","before","append"],P=F.createElement("table"),Q=F.createElement("tr"),R={tr:F.createElement("tbody"),tbody:P,thead:P,tfoot:P,td:Q,th:Q,"*":F.createElement("div")},S=/complete|loaded|interactive/,T=/^\.([\w-]+)$/,U=/^#([\w-]*)$/,V=/^[\w-]+$/,W={},X=W.toString,Y={},Z=F.createElement("div");return Y.matches=function(a,b){if(!a||1!==a.nodeType)return!1;var c=a.webkitMatchesSelector||a.mozMatchesSelector||a.oMatchesSelector||a.matchesSelector;if(c)return c.call(a,b);var d,e=a.parentNode,f=!e;return f&&(e=Z).appendChild(a),d=~Y.qsa(e,b).indexOf(a),f&&Z.removeChild(a),d},A=function(a){return a.replace(/-+(.)?/g,function(a,b){return b?b.toUpperCase():""})},B=function(a){return E.call(a,function(b,c){return a.indexOf(b)==c})},Y.fragment=function(a,b,c){a.replace&&(a=a.replace(L,"<$1>")),b===w&&(b=K.test(a)&&RegExp.$1),b in R||(b="*");var d,e,g=R[b];return g.innerHTML=""+a,e=y.each(D.call(g.childNodes),function(){g.removeChild(this)}),f(c)&&(d=y(e),y.each(c,function(a,b){N.indexOf(a)>-1?d[a](b):d.attr(a,b)})),e},Y.Z=function(a,b){return a=a||[],a.__proto__=y.fn,a.selector=b||"",a},Y.isZ=function(a){return a instanceof Y.Z},Y.init=function(a,c){if(!a)return Y.Z();if(b(a))return y(F).ready(a);if(Y.isZ(a))return a;var d;if(g(a))d=i(a);else if(e(a))d=[f(a)?y.extend({},a):a],a=null;else if(K.test(a))d=Y.fragment(a.trim(),RegExp.$1,c),a=null;else{if(c!==w)return y(c).find(a);d=Y.qsa(F,a)}return Y.Z(d,a)},y=function(a,b){return Y.init(a,b)},y.extend=function(a){var b,c=D.call(arguments,1);return"boolean"==typeof a&&(b=a,a=c.shift()),c.forEach(function(c){p(a,c,b)}),a},Y.qsa=function(a,b){var c;return d(a)&&U.test(b)?(c=a.getElementById(RegExp.$1))?[c]:[]:1!==a.nodeType&&9!==a.nodeType?[]:D.call(T.test(b)?a.getElementsByClassName(RegExp.$1):V.test(b)?a.getElementsByTagName(b):a.querySelectorAll(b))},y.contains=function(a,b){return a!==b&&a.contains(b)},y.type=a,y.isFunction=b,y.isWindow=c,y.isArray=g,y.isPlainObject=f,y.isEmptyObject=function(a){var b;for(b in a)return!1;return!0},y.inArray=function(a,b,c){return C.indexOf.call(b,a,c)},y.camelCase=A,y.trim=function(a){return a.trim()},y.uuid=0,y.support={},y.expr={},y.map=function(a,b){var c,d,e,f=[];if(h(a))for(d=0;d=0?a:a+this.length]},toArray:function(){return this.get()},size:function(){return this.length},remove:function(){return this.each(function(){null!=this.parentNode&&this.parentNode.removeChild(this)})},each:function(a){return C.every.call(this,function(b,c){return a.call(b,c,b)!==!1}),this},filter:function(a){return b(a)?this.not(this.not(a)):y(E.call(this,function(b){return Y.matches(b,a)}))},add:function(a,b){return y(B(this.concat(y(a,b))))},is:function(a){return this.length>0&&Y.matches(this[0],a)},not:function(a){var c=[];if(b(a)&&a.call!==w)this.each(function(b){a.call(this,b)||c.push(this)});else{var d="string"==typeof a?this.filter(a):h(a)&&b(a.item)?D.call(a):y(a);this.forEach(function(a){d.indexOf(a)<0&&c.push(a)})}return y(c)},has:function(a){return this.filter(function(){return e(a)?y.contains(this,a):y(this).find(a).size()})},eq:function(a){return-1===a?this.slice(a):this.slice(a,+a+1)},first:function(){var a=this[0];return a&&!e(a)?a:y(a)},last:function(){var a=this[this.length-1];return a&&!e(a)?a:y(a)},find:function(a){var b,c=this;return b="object"==typeof a?y(a).filter(function(){var a=this;return C.some.call(c,function(b){return y.contains(b,a)})}):1==this.length?y(Y.qsa(this[0],a)):this.map(function(){return Y.qsa(this,a)})},closest:function(a,b){var c=this[0],e=!1;for("object"==typeof a&&(e=y(a));c&&!(e?e.indexOf(c)>=0:Y.matches(c,a));)c=c!==b&&!d(c)&&c.parentNode;return y(c)},parents:function(a){for(var b=[],c=this;c.length>0;)c=y.map(c,function(a){return(a=a.parentNode)&&!d(a)&&b.indexOf(a)<0?(b.push(a),a):void 0});return q(b,a)},parent:function(a){return q(B(this.pluck("parentNode")),a)},children:function(a){return q(this.map(function(){return o(this)}),a)},contents:function(){return this.map(function(){return D.call(this.childNodes)})},siblings:function(a){return q(this.map(function(a,b){return E.call(o(b.parentNode),function(a){return a!==b})}),a)},empty:function(){return this.each(function(){this.innerHTML=""})},pluck:function(a){return y.map(this,function(b){return b[a]})},show:function(){return this.each(function(){"none"==this.style.display&&(this.style.display=null),"none"==I(this,"").getPropertyValue("display")&&(this.style.display=n(this.nodeName))})},replaceWith:function(a){return this.before(a).remove()},wrap:function(a){var c=b(a);if(this[0]&&!c)var d=y(a).get(0),e=d.parentNode||this.length>1;return this.each(function(b){y(this).wrapAll(c?a.call(this,b):e?d.cloneNode(!0):d)})},wrapAll:function(a){if(this[0]){y(this[0]).before(a=y(a));for(var b;(b=a.children()).length;)a=b.first();y(a).append(this)}return this},wrapInner:function(a){var c=b(a);return this.each(function(b){var d=y(this),e=d.contents(),f=c?a.call(this,b):a;e.length?e.wrapAll(f):d.append(f)})},unwrap:function(){return this.parent().each(function(){y(this).replaceWith(y(this).children())}),this},clone:function(){return this.map(function(){return this.cloneNode(!0)})},hide:function(){return this.css("display","none")},toggle:function(a){return this.each(function(){var b=y(this);(a===w?"none"==b.css("display"):a)?b.show():b.hide()})},prev:function(a){return y(this.pluck("previousElementSibling")).filter(a||"*")},next:function(a){return y(this.pluck("nextElementSibling")).filter(a||"*")},html:function(a){return a===w?this.length>0?this[0].innerHTML:null:this.each(function(b){var c=this.innerHTML;y(this).empty().append(r(this,a,b,c))})},text:function(a){return a===w?this.length>0?this[0].textContent:null:this.each(function(){this.textContent=a})},attr:function(a,b){var c;return"string"==typeof a&&b===w?0==this.length||1!==this[0].nodeType?w:"value"==a&&"INPUT"==this[0].nodeName?this.val():!(c=this[0].getAttribute(a))&&a in this[0]?this[0][a]:c:this.each(function(c){if(1===this.nodeType)if(e(a))for(x in a)s(this,x,a[x]);else s(this,a,r(this,b,c,this.getAttribute(a)))})},removeAttr:function(a){return this.each(function(){1===this.nodeType&&s(this,a)})},prop:function(a,b){return b===w?this[0]&&this[0][a]:this.each(function(c){this[a]=r(this,b,c,this[a])})},data:function(a,b){var c=this.attr("data-"+k(a),b);return null!==c?u(c):w},val:function(a){return a===w?this[0]&&(this[0].multiple?y(this[0]).find("option").filter(function(a){return this.selected}).pluck("value"):this[0].value):this.each(function(b){this.value=r(this,a,b,this.value)})},offset:function(a){if(a)return this.each(function(b){var c=y(this),d=r(this,a,b,c.offset()),e=c.offsetParent().offset(),f={top:d.top-e.top,left:d.left-e.left};"static"==c.css("position")&&(f.position="relative"),c.css(f)});if(0==this.length)return null;var b=this[0].getBoundingClientRect();return{left:b.left+window.pageXOffset,top:b.top+window.pageYOffset,width:Math.round(b.width),height:Math.round(b.height)}},css:function(b,c){if(arguments.length<2&&"string"==typeof b)return this[0]&&(this[0].style[A(b)]||I(this[0],"").getPropertyValue(b));var d="";if("string"==a(b))c||0===c?d=k(b)+":"+m(b,c):this.each(function(){this.style.removeProperty(k(b))});else for(x in b)b[x]||0===b[x]?d+=k(x)+":"+m(x,b[x])+";":this.each(function(){this.style.removeProperty(k(x))});return this.each(function(){this.style.cssText+=";"+d})},index:function(a){return a?this.indexOf(y(a)[0]):this.parent().children().indexOf(this[0])},hasClass:function(a){return C.some.call(this,function(a){return this.test(t(a))},l(a))},addClass:function(a){return this.each(function(b){z=[];var c=t(this),d=r(this,a,b,c);d.split(/\s+/g).forEach(function(a){y(this).hasClass(a)||z.push(a)},this),z.length&&t(this,c+(c?" ":"")+z.join(" "))})},removeClass:function(a){return this.each(function(b){return a===w?t(this,""):(z=t(this),r(this,a,b,z).split(/\s+/g).forEach(function(a){z=z.replace(l(a)," ")}),t(this,z.trim()),void 0)})},toggleClass:function(a,b){return this.each(function(c){var d=y(this),e=r(this,a,c,t(this));e.split(/\s+/g).forEach(function(a){(b===w?!d.hasClass(a):b)?d.addClass(a):d.removeClass(a)})})},scrollTop:function(){return this.length?"scrollTop"in this[0]?this[0].scrollTop:this[0].scrollY:void 0},position:function(){if(this.length){var a=this[0],b=this.offsetParent(),c=this.offset(),d=M.test(b[0].nodeName)?{top:0,left:0}:b.offset();return c.top-=parseFloat(y(a).css("margin-top"))||0,c.left-=parseFloat(y(a).css("margin-left"))||0,d.top+=parseFloat(y(b[0]).css("border-top-width"))||0,d.left+=parseFloat(y(b[0]).css("border-left-width"))||0,{top:c.top-d.top,left:c.left-d.left}}},offsetParent:function(){return this.map(function(){for(var a=this.offsetParent||F.body;a&&!M.test(a.nodeName)&&"static"==y(a).css("position");)a=a.offsetParent;return a})}},y.fn.detach=y.fn.remove,["width","height"].forEach(function(a){y.fn[a]=function(b){var e,f=this[0],g=a.replace(/./,function(a){return a[0].toUpperCase()});return b===w?c(f)?f["inner"+g]:d(f)?f.documentElement["offset"+g]:(e=this.offset())&&e[a]:this.each(function(c){f=y(this),f.css(a,r(this,b,c,f[a]()))})}}),O.forEach(function(b,c){var d=c%2;y.fn[b]=function(){var b,e,f=y.map(arguments,function(c){return b=a(c),"object"==b||"array"==b||null==c?c:Y.fragment(c)}),g=this.length>1;return f.length<1?this:this.each(function(a,b){e=d?b:b.parentNode,b=0==c?b.nextSibling:1==c?b.firstChild:2==c?b:null,f.forEach(function(a){if(g)a=a.cloneNode(!0);else if(!e)return y(a).remove();v(e.insertBefore(a,b),function(a){null!=a.nodeName&&"SCRIPT"===a.nodeName.toUpperCase()&&(!a.type||"text/javascript"===a.type)&&!a.src&&window.eval.call(window,a.innerHTML)})})})},y.fn[d?b+"To":"insert"+(c?"Before":"After")]=function(a){return y(a)[b](this),this}}),Y.Z.prototype=y.fn,Y.uniq=B,Y.deserializeValue=u,y.zepto=Y,y}();window.Zepto=Zepto,"$"in window||(window.$=Zepto),function(a){function b(a){var b=this.os={},c=this.browser={},d=a.match(/WebKit\/([\d.]+)/),e=a.match(/(Android)\s+([\d.]+)/),f=a.match(/(iPad).*OS\s([\d_]+)/),g=!f&&a.match(/(iPhone\sOS)\s([\d_]+)/),h=a.match(/(webOS|hpwOS)[\s\/]([\d.]+)/),i=h&&a.match(/TouchPad/),j=a.match(/Kindle\/([\d.]+)/),k=a.match(/Silk\/([\d._]+)/),l=a.match(/(BlackBerry).*Version\/([\d.]+)/),m=a.match(/(BB10).*Version\/([\d.]+)/),n=a.match(/(RIM\sTablet\sOS)\s([\d.]+)/),o=a.match(/PlayBook/),p=a.match(/Chrome\/([\d.]+)/)||a.match(/CriOS\/([\d.]+)/),q=a.match(/Firefox\/([\d.]+)/);(c.webkit=!!d)&&(c.version=d[1]),e&&(b.android=!0,b.version=e[2]),g&&(b.ios=b.iphone=!0,b.version=g[2].replace(/_/g,".")),f&&(b.ios=b.ipad=!0,b.version=f[2].replace(/_/g,".")),h&&(b.webos=!0,b.version=h[2]),i&&(b.touchpad=!0),l&&(b.blackberry=!0,b.version=l[2]),m&&(b.bb10=!0,b.version=m[2]),n&&(b.rimtabletos=!0,b.version=n[2]),o&&(c.playbook=!0),j&&(b.kindle=!0,b.version=j[1]),k&&(c.silk=!0,c.version=k[1]),!k&&b.android&&a.match(/Kindle Fire/)&&(c.silk=!0),p&&(c.chrome=!0,c.version=p[1]),q&&(c.firefox=!0,c.version=q[1]),b.tablet=!!(f||o||e&&!a.match(/Mobile/)||q&&a.match(/Tablet/)),b.phone=!b.tablet&&!!(e||g||h||l||m||p&&a.match(/Android/)||p&&a.match(/CriOS\/([\d.]+)/)||q&&a.match(/Mobile/))}b.call(a,navigator.userAgent),a.__detect=b}(Zepto),function(a){function b(a){return a._zid||(a._zid=n++)}function c(a,c,f,g){if(c=d(c),c.ns)var h=e(c.ns);return(m[b(a)]||[]).filter(function(a){return a&&(!c.e||a.e==c.e)&&(!c.ns||h.test(a.ns))&&(!f||b(a.fn)===b(f))&&(!g||a.sel==g)})}function d(a){var b=(""+a).split(".");return{e:b[0],ns:b.slice(1).sort().join(" ")}}function e(a){return new RegExp("(?:^| )"+a.replace(" "," .* ?")+"(?: |$)")}function f(b,c,d){"string"!=a.type(b)?a.each(b,d):b.split(/\s/).forEach(function(a){d(a,c)})}function g(a,b){return a.del&&("focus"==a.e||"blur"==a.e)||!!b}function h(a){return p[a]||a}function i(c,e,i,j,k,l){var n=b(c),o=m[n]||(m[n]=[]);f(e,i,function(b,e){var f=d(b);f.fn=e,f.sel=j,f.e in p&&(e=function(b){var c=b.relatedTarget;return!c||c!==this&&!a.contains(this,c)?f.fn.apply(this,arguments):void 0}),f.del=k&&k(e,b);var i=f.del||e;f.proxy=function(a){var b=i.apply(c,[a].concat(a.data));return b===!1&&(a.preventDefault(),a.stopPropagation()),b},f.i=o.length,o.push(f),c.addEventListener(h(f.e),f.proxy,g(f,l))})}function j(a,d,e,i,j){var k=b(a);f(d||"",e,function(b,d){c(a,b,d,i).forEach(function(b){delete m[k][b.i],a.removeEventListener(h(b.e),b.proxy,g(b,j))})})}function k(b){var c,d={originalEvent:b};for(c in b)!s.test(c)&&void 0!==b[c]&&(d[c]=b[c]);return a.each(t,function(a,c){d[a]=function(){return this[c]=q,b[a].apply(b,arguments)},d[c]=r}),d}function l(a){if(!("defaultPrevented"in a)){a.defaultPrevented=!1;var b=a.preventDefault;a.preventDefault=function(){this.defaultPrevented=!0,b.call(this)}}}var m=(a.zepto.qsa,{}),n=1,o={},p={mouseenter:"mouseover",mouseleave:"mouseout"};o.click=o.mousedown=o.mouseup=o.mousemove="MouseEvents",a.event={add:i,remove:j},a.proxy=function(c,d){if(a.isFunction(c)){var e=function(){return c.apply(d,arguments)};return e._zid=b(c),e}if("string"==typeof d)return a.proxy(c[d],c);throw new TypeError("expected function")},a.fn.bind=function(a,b){return this.each(function(){i(this,a,b)})},a.fn.unbind=function(a,b){return this.each(function(){j(this,a,b)})},a.fn.one=function(a,b){return this.each(function(c,d){i(this,a,b,null,function(a,b){return function(){var c=a.apply(d,arguments);return j(d,b,a),c}})})};var q=function(){return!0},r=function(){return!1},s=/^([A-Z]|layer[XY]$)/,t={preventDefault:"isDefaultPrevented",stopImmediatePropagation:"isImmediatePropagationStopped",stopPropagation:"isPropagationStopped"};a.fn.delegate=function(b,c,d){return this.each(function(e,f){i(f,c,d,b,function(c){return function(d){var e,g=a(d.target).closest(b,f).get(0);return g?(e=a.extend(k(d),{currentTarget:g,liveFired:f}),c.apply(g,[e].concat([].slice.call(arguments,1)))):void 0}})})},a.fn.undelegate=function(a,b,c){return this.each(function(){j(this,b,c,a)})},a.fn.live=function(b,c){return a(document.body).delegate(this.selector,b,c),this},a.fn.die=function(b,c){return a(document.body).undelegate(this.selector,b,c),this},a.fn.on=function(b,c,d){return!c||a.isFunction(c)?this.bind(b,c||d):this.delegate(c,b,d)},a.fn.off=function(b,c,d){return!c||a.isFunction(c)?this.unbind(b,c||d):this.undelegate(c,b,d)},a.fn.trigger=function(b,c){return("string"==typeof b||a.isPlainObject(b))&&(b=a.Event(b)),l(b),b.data=c,this.each(function(){"dispatchEvent"in this&&this.dispatchEvent(b)})},a.fn.triggerHandler=function(b,d){var e,f;return this.each(function(g,h){e=k("string"==typeof b?a.Event(b):b),e.data=d,e.target=h,a.each(c(h,b.type||b),function(a,b){return f=b.proxy(e),e.isImmediatePropagationStopped()?!1:void 0})}),f},"focusin focusout load resize scroll unload click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select keydown keypress keyup error".split(" ").forEach(function(b){a.fn[b]=function(a){return a?this.bind(b,a):this.trigger(b)}}),["focus","blur"].forEach(function(b){a.fn[b]=function(a){return a?this.bind(b,a):this.each(function(){try{this[b]()}catch(a){}}),this}}),a.Event=function(a,b){"string"!=typeof a&&(b=a,a=b.type);var c=document.createEvent(o[a]||"Events"),d=!0;if(b)for(var e in b)"bubbles"==e?d=!!b[e]:c[e]=b[e];return c.initEvent(a,d,!0,null,null,null,null,null,null,null,null,null,null,null,null),c.isDefaultPrevented=function(){return this.defaultPrevented},c}}(Zepto),function(a){function b(b,c,d){var e=a.Event(c);return a(b).trigger(e,d),!e.defaultPrevented}function c(a,c,d,e){return a.global?b(c||s,d,e):void 0}function d(b){b.global&&0===a.active++&&c(b,null,"ajaxStart")}function e(b){b.global&&!--a.active&&c(b,null,"ajaxStop")}function f(a,b){var d=b.context;return b.beforeSend.call(d,a,b)===!1||c(b,d,"ajaxBeforeSend",[a,b])===!1?!1:void c(b,d,"ajaxSend",[a,b])}function g(a,b,d){var e=d.context,f="success";d.success.call(e,a,f,b),c(d,e,"ajaxSuccess",[b,d,a]),i(f,b,d)}function h(a,b,d,e){var f=e.context;e.error.call(f,d,b,a),c(e,f,"ajaxError",[d,e,a]),i(b,d,e)}function i(a,b,d){var f=d.context;d.complete.call(f,b,a),c(d,f,"ajaxComplete",[b,d]),e(d)}function j(){}function k(a){return a&&(a=a.split(";",2)[0]),a&&(a==x?"html":a==w?"json":u.test(a)?"script":v.test(a)&&"xml")||"text"}function l(a,b){return(a+"&"+b).replace(/[&?]{1,2}/,"?")}function m(b){b.processData&&b.data&&"string"!=a.type(b.data)&&(b.data=a.param(b.data,b.traditional)),b.data&&(!b.type||"GET"==b.type.toUpperCase())&&(b.url=l(b.url,b.data))}function n(b,c,d,e){var f=!a.isFunction(c);return{url:b,data:f?c:void 0,success:f?a.isFunction(d)?d:void 0:c,dataType:f?e||d:d}}function o(b,c,d,e){var f,g=a.isArray(c);a.each(c,function(c,h){f=a.type(h),e&&(c=d?e:e+"["+(g?"":c)+"]"),!e&&g?b.add(h.name,h.value):"array"==f||!d&&"object"==f?o(b,h,d,c):b.add(c,h)})}var p,q,r=0,s=window.document,t=/)<[^<]*)*<\/script>/gi,u=/^(?:text|application)\/javascript/i,v=/^(?:text|application)\/xml/i,w="application/json",x="text/html",y=/^\s*$/;a.active=0,a.ajaxJSONP=function(b){if("type"in b){var c,d="jsonp"+ ++r,e=s.createElement("script"),i=function(){clearTimeout(c),a(e).remove(),delete window[d]},k=function(a){i(),a&&"timeout"!=a||(window[d]=j),h(null,a||"abort",l,b)},l={abort:k};return f(l,b)===!1?(k("abort"),!1):(window[d]=function(a){i(),g(a,l,b)},e.onerror=function(){k("error")},e.src=b.url.replace(/=\?/,"="+d),a("head").append(e),b.timeout>0&&(c=setTimeout(function(){k("timeout")},b.timeout)),l)}return a.ajax(b)},a.ajaxSettings={type:"GET",beforeSend:j,success:j,error:j,complete:j,context:null,global:!0,xhr:function(){return new window.XMLHttpRequest},accepts:{script:"text/javascript, application/javascript",json:w,xml:"application/xml, text/xml",html:x,text:"text/plain"},crossDomain:!1,timeout:0,processData:!0,cache:!0},a.ajax=function(b){var c=a.extend({},b||{});for(p in a.ajaxSettings)void 0===c[p]&&(c[p]=a.ajaxSettings[p]);d(c),c.crossDomain||(c.crossDomain=/^([\w-]+:)?\/\/([^\/]+)/.test(c.url)&&RegExp.$2!=window.location.host),c.url||(c.url=window.location.toString()),m(c),c.cache===!1&&(c.url=l(c.url,"_="+Date.now()));var e=c.dataType,i=/=\?/.test(c.url);if("jsonp"==e||i)return i||(c.url=l(c.url,"callback=?")),a.ajaxJSONP(c);var n,o=c.accepts[e],r={},s=/^([\w-]+:)\/\//.test(c.url)?RegExp.$1:window.location.protocol,t=c.xhr();c.crossDomain||(r["X-Requested-With"]="XMLHttpRequest"),o&&(r.Accept=o,o.indexOf(",")>-1&&(o=o.split(",",2)[0]),t.overrideMimeType&&t.overrideMimeType(o)),(c.contentType||c.contentType!==!1&&c.data&&"GET"!=c.type.toUpperCase())&&(r["Content-Type"]=c.contentType||"application/x-www-form-urlencoded"),c.headers=a.extend(r,c.headers||{}),t.onreadystatechange=function(){if(4==t.readyState){t.onreadystatechange=j,clearTimeout(n);var b,d=!1;if(t.status>=200&&t.status<300||304==t.status||0==t.status&&"file:"==s){e=e||k(t.getResponseHeader("content-type")),b=t.responseText;try{"script"==e?(1,eval)(b):"xml"==e?b=t.responseXML:"json"==e&&(b=y.test(b)?null:a.parseJSON(b))}catch(f){d=f}d?h(d,"parsererror",t,c):g(b,t,c)}else h(null,t.status?"error":"abort",t,c)}};var u="async"in c?c.async:!0;t.open(c.type,c.url,u);for(q in c.headers)t.setRequestHeader(q,c.headers[q]);return f(t,c)===!1?(t.abort(),!1):(c.timeout>0&&(n=setTimeout(function(){t.onreadystatechange=j,t.abort(),h(null,"timeout",t,c)},c.timeout)),t.send(c.data?c.data:null),t)},a.get=function(b,c,d,e){return a.ajax(n.apply(null,arguments))},a.post=function(b,c,d,e){var f=n.apply(null,arguments);return f.type="POST",a.ajax(f)},a.getJSON=function(b,c,d){var e=n.apply(null,arguments);return e.dataType="json",a.ajax(e)},a.fn.load=function(b,c,d){if(!this.length)return this;var e,f=this,g=b.split(/\s/),h=n(b,c,d),i=h.success;return g.length>1&&(h.url=g[0],e=g[1]),h.success=function(b){f.html(e?a("
").html(b.replace(t,"")).find(e):b),i&&i.apply(f,arguments)},a.ajax(h),this};var z=encodeURIComponent;a.param=function(a,b){var c=[];return c.add=function(a,b){this.push(z(a)+"="+z(b))},o(c,a,b),c.join("&").replace(/%20/g,"+")}}(Zepto),function(a){a.fn.serializeArray=function(){var b,c=[];return a(Array.prototype.slice.call(this.get(0).elements)).each(function(){b=a(this);var d=b.attr("type");"fieldset"!=this.nodeName.toLowerCase()&&!this.disabled&&"submit"!=d&&"reset"!=d&&"button"!=d&&("radio"!=d&&"checkbox"!=d||this.checked)&&c.push({name:b.attr("name"),value:b.val()})}),c},a.fn.serialize=function(){var a=[];return this.serializeArray().forEach(function(b){a.push(encodeURIComponent(b.name)+"="+encodeURIComponent(b.value))}),a.join("&")},a.fn.submit=function(b){if(b)this.bind("submit",b);else if(this.length){var c=a.Event("submit");this.eq(0).trigger(c),c.defaultPrevented||this.get(0).submit()}return this}}(Zepto),function(a,b){function c(a){return d(a.replace(/([a-z])([A-Z])/,"$1-$2"))}function d(a){return a.toLowerCase()}function e(a){return f?f+a:d(a)}var f,g,h,i,j,k,l,m,n="",o={Webkit:"webkit",Moz:"",O:"o",ms:"MS"},p=window.document,q=p.createElement("div"),r=/^((translate|rotate|scale)(X|Y|Z|3d)?|matrix(3d)?|perspective|skew(X|Y)?)$/i,s={};a.each(o,function(a,c){return q.style[a+"TransitionProperty"]!==b?(n="-"+d(a)+"-",f=c,!1):void 0}),g=n+"transform",s[h=n+"transition-property"]=s[i=n+"transition-duration"]=s[j=n+"transition-timing-function"]=s[k=n+"animation-name"]=s[l=n+"animation-duration"]=s[m=n+"animation-timing-function"]="",a.fx={off:f===b&&q.style.transitionProperty===b,speeds:{_default:400,fast:200,slow:600},cssPrefix:n,transitionEnd:e("TransitionEnd"),animationEnd:e("AnimationEnd")},a.fn.animate=function(b,c,d,e){return a.isPlainObject(c)&&(d=c.easing,e=c.complete,c=c.duration),c&&(c=("number"==typeof c?c:a.fx.speeds[c]||a.fx.speeds._default)/1e3),this.anim(b,c,d,e)},a.fn.anim=function(d,e,f,n){var o,p,q,t={},u="",v=this,w=a.fx.transitionEnd;if(e===b&&(e=.4),a.fx.off&&(e=0),"string"==typeof d)t[k]=d,t[l]=e+"s",t[m]=f||"linear",w=a.fx.animationEnd;else{p=[];for(o in d)r.test(o)?u+=o+"("+d[o]+") ":(t[o]=d[o],p.push(c(o)));u&&(t[g]=u,p.push(g)),e>0&&"object"==typeof d&&(t[h]=p.join(", "),t[i]=e+"s",t[j]=f||"linear")}return q=function(b){if("undefined"!=typeof b){if(b.target!==b.currentTarget)return;a(b.target).unbind(w,q)}a(this).css(s),n&&n.call(this)},e>0&&this.bind(w,q),this.size()&&this.get(0).clientLeft,this.css(t),0>=e&&setTimeout(function(){v.each(function(){q.call(this)})},0),this},q=null}(Zepto),function(a){function b(a){return"tagName"in a?a:a.parentNode}function c(a,b,c,d){var e=Math.abs(a-b),f=Math.abs(c-d);return e>=f?a-b>0?"Left":"Right":c-d>0?"Up":"Down"}function d(){j=null,k.last&&(k.el.trigger("longTap"),k={})}function e(){j&&clearTimeout(j),j=null}function f(){g&&clearTimeout(g),h&&clearTimeout(h),i&&clearTimeout(i),j&&clearTimeout(j),g=h=i=j=null,k={}}var g,h,i,j,k={},l=750;a(document).ready(function(){var m,n;a(document.body).bind("touchstart",function(c){m=Date.now(),n=m-(k.last||m),k.el=a(b(c.touches[0].target)),g&&clearTimeout(g),k.x1=c.touches[0].pageX,k.y1=c.touches[0].pageY,n>0&&250>=n&&(k.isDoubleTap=!0),k.last=m,j=setTimeout(d,l)}).bind("touchmove",function(a){e(),k.x2=a.touches[0].pageX,k.y2=a.touches[0].pageY,Math.abs(k.x1-k.x2)>10&&a.preventDefault()}).bind("touchend",function(b){e(),k.x2&&Math.abs(k.x1-k.x2)>30||k.y2&&Math.abs(k.y1-k.y2)>30?i=setTimeout(function(){k.el.trigger("swipe"),k.el.trigger("swipe"+c(k.x1,k.x2,k.y1,k.y2)),k={}},0):"last"in k&&(h=setTimeout(function(){var b=a.Event("tap");b.cancelTouch=f,k.el.trigger(b),k.isDoubleTap?(k.el.trigger("doubleTap"),k={}):g=setTimeout(function(){g=null,k.el.trigger("singleTap"),k={}},250)},0))}).bind("touchcancel",f),a(window).bind("scroll",f)}),["swipe","swipeLeft","swipeRight","swipeUp","swipeDown","doubleTap","tap","singleTap","longTap"].forEach(function(b){a.fn[b]=function(a){return this.bind(b,a)}})}(Zepto),FastClick.prototype.deviceIsAndroid=navigator.userAgent.indexOf("Android")>0,FastClick.prototype.deviceIsIOS=/iP(ad|hone|od)/.test(navigator.userAgent),FastClick.prototype.deviceIsIOS4=FastClick.prototype.deviceIsIOS&&/OS 4_\d(_\d)?/.test(navigator.userAgent),FastClick.prototype.deviceIsIOSWithBadTarget=FastClick.prototype.deviceIsIOS&&/OS ([6-9]|\d{2})_\d/.test(navigator.userAgent),FastClick.prototype.needsClick=function(a){"use strict";switch(a.nodeName.toLowerCase()){case"button":case"input":return this.deviceIsIOS&&"file"===a.type?!0:a.disabled;case"label":case"video":return!0;default:return/\bneedsclick\b/.test(a.className)}},FastClick.prototype.needsFocus=function(a){"use strict";switch(a.nodeName.toLowerCase()){case"textarea":case"select":return!0;case"input":switch(a.type){case"button":case"checkbox":case"file":case"image":case"radio":case"submit":return!1}return!a.disabled&&!a.readOnly;default:return/\bneedsfocus\b/.test(a.className)}},FastClick.prototype.sendClick=function(a,b){"use strict";var c,d;document.activeElement&&document.activeElement!==a&&document.activeElement.blur(),d=b.changedTouches[0],c=document.createEvent("MouseEvents"),c.initMouseEvent("click",!0,!0,window,1,d.screenX,d.screenY,d.clientX,d.clientY,!1,!1,!1,!1,0,null),c.forwardedTouchEvent=!0,a.dispatchEvent(c)},FastClick.prototype.focus=function(a){"use strict";var b;this.deviceIsIOS&&a.setSelectionRange?(b=a.value.length,a.setSelectionRange(b,b)):a.focus()},FastClick.prototype.updateScrollParent=function(a){"use strict";var b,c;if(b=a.fastClickScrollParent,!b||!b.contains(a)){c=a;do{if(c.scrollHeight>c.offsetHeight){b=c,a.fastClickScrollParent=c;break}c=c.parentElement}while(c)}b&&(b.fastClickLastScrollTop=b.scrollTop)},FastClick.prototype.getTargetElementFromEventTarget=function(a){"use strict";return a.nodeType===Node.TEXT_NODE?a.parentNode:a},FastClick.prototype.onTouchStart=function(a){"use strict";var b,c,d;if(b=this.getTargetElementFromEventTarget(a.target),c=a.targetTouches[0],this.deviceIsIOS){if(d=window.getSelection(),d.rangeCount&&!d.isCollapsed)return!0;if(!this.deviceIsIOS4){if(c.identifier===this.lastTouchIdentifier)return a.preventDefault(),!1;this.lastTouchIdentifier=c.identifier,this.updateScrollParent(b)}}return this.trackingClick=!0,this.trackingClickStart=a.timeStamp,this.targetElement=b,this.touchStartX=c.pageX,this.touchStartY=c.pageY,a.timeStamp-this.lastClickTime<200&&a.preventDefault(),!0},FastClick.prototype.touchHasMoved=function(a){"use strict";var b=a.changedTouches[0];return Math.abs(b.pageX-this.touchStartX)>10||Math.abs(b.pageY-this.touchStartY)>10?!0:!1},FastClick.prototype.findControl=function(a){"use strict";return void 0!==a.control?a.control:a.htmlFor?document.getElementById(a.htmlFor):a.querySelector("button, input:not([type=hidden]), keygen, meter, output, progress, select, textarea")},FastClick.prototype.onTouchEnd=function(a){"use strict";var b,c,d,e,f,g=this.targetElement;if(this.touchHasMoved(a)&&(this.trackingClick=!1,this.targetElement=null),!this.trackingClick)return!0;if(a.timeStamp-this.lastClickTime<200)return this.cancelNextClick=!0,!0;if(this.lastClickTime=a.timeStamp,c=this.trackingClickStart,this.trackingClick=!1,this.trackingClickStart=0, -this.deviceIsIOSWithBadTarget&&(f=a.changedTouches[0],g=document.elementFromPoint(f.pageX-window.pageXOffset,f.pageY-window.pageYOffset)),d=g.tagName.toLowerCase(),"label"===d){if(b=this.findControl(g)){if(this.focus(g),this.deviceIsAndroid)return!1;g=b}}else if(this.needsFocus(g))return a.timeStamp-c>100||this.deviceIsIOS&&window.top!==window&&"input"===d?(this.targetElement=null,!1):(this.focus(g),this.deviceIsIOS4&&"select"===d||(this.targetElement=null,a.preventDefault()),!1);return this.deviceIsIOS&&!this.deviceIsIOS4&&(e=g.fastClickScrollParent,e&&e.fastClickLastScrollTop!==e.scrollTop)?!0:(this.needsClick(g)||(a.preventDefault(),this.sendClick(g,a)),!1)},FastClick.prototype.onTouchCancel=function(){"use strict";this.trackingClick=!1,this.targetElement=null},FastClick.prototype.onMouse=function(a){"use strict";return this.targetElement?a.forwardedTouchEvent?!0:a.cancelable&&(!this.needsClick(this.targetElement)||this.cancelNextClick)?(a.stopImmediatePropagation?a.stopImmediatePropagation():a.propagationStopped=!0,a.stopPropagation(),a.preventDefault(),!1):!0:!0},FastClick.prototype.onClick=function(a){"use strict";var b;return this.trackingClick?(this.targetElement=null,this.trackingClick=!1,!0):"submit"===a.target.type&&0===a.detail?!0:(b=this.onMouse(a),b||(this.targetElement=null),b)},FastClick.prototype.destroy=function(){"use strict";var a=this.layer;this.deviceIsAndroid&&(a.removeEventListener("mouseover",this.onMouse,!0),a.removeEventListener("mousedown",this.onMouse,!0),a.removeEventListener("mouseup",this.onMouse,!0)),a.removeEventListener("click",this.onClick,!0),a.removeEventListener("touchstart",this.onTouchStart,!1),a.removeEventListener("touchend",this.onTouchEnd,!1),a.removeEventListener("touchcancel",this.onTouchCancel,!1)},FastClick.attach=function(a){"use strict";return new FastClick(a)},"undefined"!=typeof define&&define.amd&&define(function(){"use strict";return FastClick}),"undefined"!=typeof module&&module.exports&&(module.exports=FastClick.attach,module.exports.FastClick=FastClick);var OSUR={util:{}};OSUR.util.hasTouchEvents=function(){var a;return("ontouchstart"in window||window.DocumentTouch&&document instanceof DocumentTouch)&&(a=!0),a},$(function(){$(".command-once").on("click",function(a){$.ajax({type:"POST",url:$(this).attr("href"),success:function(a){},error:function(a,b){}})}),$(".command-repeater").on("mousedown touchstart",function(a){$.ajax({type:"POST",url:$(this).attr("href")+"/send_start",success:function(a){},error:function(a,b){}}),$(this).attr("data-active",!0)}),$(".command-repeater").on("mouseup touchend touchleave touchcancel",function(a){$.ajax({type:"POST",url:$(this).attr("href")+"/send_stop",success:function(a){},error:function(a,b){}}),$(this).attr("data-active",!1)}),$(window).on("mouseup touchend touchleave touchcancel",function(a){$(".command-repeater[data-active=true]").trigger("mouseup")}),$(".macro-link").on("click",function(a){a.preventDefault(),$.ajax({type:"POST",url:$(this).attr("href"),success:function(a){},error:function(a,b){}})}),OSUR.util.hasTouchEvents()?($("body").addClass("has-touch"),$(".command-link, .remote-link").on("touchstart",function(a){$(this).addClass("active")}),$(".command-link, .remote-link").on("touchend touchleave touchcancel",function(a){$(this).removeClass("active")}),$("body").on("touchcancel",function(a){$(".command-link").removeClass("active")})):$("body").addClass("no-touch"),$(".back").on("click",function(a){$(".remote.active").removeClass("active"),$(".remotes-nav").removeClass("hidden"),$(".macros-nav").removeClass("hidden"),$(".back").addClass("hidden"),$("#title").html($("#title").attr("data-text")),$("#titlebar").removeClass("is-remote")}),$(".remotes-nav a").on("click",function(a){a.preventDefault();var b=$(this).attr("href");$(".remotes-nav").addClass("hidden"),$(".macros-nav").addClass("hidden"),$(b).addClass("active"),$(".back").removeClass("hidden"),$("#title").html($(this).html()),$("#titlebar").addClass("is-remote")}),OSUR.fastClick=new FastClick(document.body)}); \ No newline at end of file +this.deviceIsIOSWithBadTarget&&(f=a.changedTouches[0],g=document.elementFromPoint(f.pageX-window.pageXOffset,f.pageY-window.pageYOffset)),d=g.tagName.toLowerCase(),"label"===d){if(b=this.findControl(g)){if(this.focus(g),this.deviceIsAndroid)return!1;g=b}}else if(this.needsFocus(g))return a.timeStamp-c>100||this.deviceIsIOS&&window.top!==window&&"input"===d?(this.targetElement=null,!1):(this.focus(g),this.deviceIsIOS4&&"select"===d||(this.targetElement=null,a.preventDefault()),!1);return this.deviceIsIOS&&!this.deviceIsIOS4&&(e=g.fastClickScrollParent,e&&e.fastClickLastScrollTop!==e.scrollTop)?!0:(this.needsClick(g)||(a.preventDefault(),this.sendClick(g,a)),!1)},FastClick.prototype.onTouchCancel=function(){"use strict";this.trackingClick=!1,this.targetElement=null},FastClick.prototype.onMouse=function(a){"use strict";return this.targetElement?a.forwardedTouchEvent?!0:a.cancelable&&(!this.needsClick(this.targetElement)||this.cancelNextClick)?(a.stopImmediatePropagation?a.stopImmediatePropagation():a.propagationStopped=!0,a.stopPropagation(),a.preventDefault(),!1):!0:!0},FastClick.prototype.onClick=function(a){"use strict";var b;return this.trackingClick?(this.targetElement=null,this.trackingClick=!1,!0):"submit"===a.target.type&&0===a.detail?!0:(b=this.onMouse(a),b||(this.targetElement=null),b)},FastClick.prototype.destroy=function(){"use strict";var a=this.layer;this.deviceIsAndroid&&(a.removeEventListener("mouseover",this.onMouse,!0),a.removeEventListener("mousedown",this.onMouse,!0),a.removeEventListener("mouseup",this.onMouse,!0)),a.removeEventListener("click",this.onClick,!0),a.removeEventListener("touchstart",this.onTouchStart,!1),a.removeEventListener("touchend",this.onTouchEnd,!1),a.removeEventListener("touchcancel",this.onTouchCancel,!1)},FastClick.attach=function(a){"use strict";return new FastClick(a)},"undefined"!=typeof define&&define.amd&&define(function(){"use strict";return FastClick}),"undefined"!=typeof module&&module.exports&&(module.exports=FastClick.attach,module.exports.FastClick=FastClick);var OSUR={util:{}};OSUR.util.hasTouchEvents=function(){var a;return("ontouchstart"in window||window.DocumentTouch&&document instanceof DocumentTouch)&&(a=!0),a},$(function(){$(".command-once").on("click",function(a){$.ajax({type:"POST",url:$(this).attr("href"),success:function(a){},error:function(a,b){}})}),$(".command-repeater").on("mousedown touchstart",function(a){$.ajax({type:"POST",url:$(this).attr("href")+"/send_start",success:function(a){},error:function(a,b){}}),$(this).attr("data-active",!0)}),$(".command-repeater").on("mouseup touchend touchleave touchcancel",function(a){$.ajax({type:"POST",url:$(this).attr("href")+"/send_stop",success:function(a){},error:function(a,b){}}),$(this).attr("data-active",!1)}),$(window).on("mouseup touchend touchleave touchcancel",function(a){$(".command-repeater[data-active=true]").trigger("mouseup")}),$(".macro-link").on("click",function(a){a.preventDefault(),$.ajax({type:"POST",url:$(this).attr("href"),success:function(a){},error:function(a,b){}})}),OSUR.util.hasTouchEvents()?($("body").addClass("has-touch"),$(".command-link, .remote-link").on("touchstart",function(a){$(this).addClass("active")}),$(".command-link, .remote-link").on("touchend touchleave touchcancel",function(a){$(this).removeClass("active")}),$("body").on("touchcancel",function(a){$(".command-link").removeClass("active")})):$("body").addClass("no-touch"),$(".back").on("click",function(a){$(".remote.active").removeClass("active"),$(".macro.active").removeClass("active"),$(".remotes-nav").removeClass("hidden"),$(".macros-nav").removeClass("hidden"),$(".back").addClass("hidden"),$("#title").html($("#title").attr("data-text")),$("#titlebar").removeClass("is-remote"),$("#menubar").removeClass("is-remote")}),$(".remotes-navmenu a").on("click",function(a){a.preventDefault();var b=$(this).attr("href");$(".remotes-nav").addClass("hidden"),$(".macros-nav").addClass("hidden"),$(".remote.active").removeClass("active"),$(".macro.active").removeClass("active"),$(b).addClass("active"),$(".back").removeClass("hidden"),$("#title").html($(this).html()),$("#titlebar").addClass("is-remote"),$("#menubar").addClass("is-remote")}),$(".macros-navmenu a").on("click",function(a){a.preventDefault();var b=$(this).attr("href");$(".remotes-nav").addClass("hidden"),$(".macros-nav").addClass("hidden"),$(".remote.active").removeClass("active"),$(".macro.active").removeClass("active"),$(b).addClass("active"),$(".back").removeClass("hidden"),$("#title").html($(this).html()),$("#titlebar").addClass("is-remote"),$("#menubar").addClass("is-remote")}),OSUR.fastClick=new FastClick(document.body)}); \ No newline at end of file diff --git a/static/js/vendor/pushy.js b/static/js/vendor/pushy.js new file mode 100644 index 0000000..fb6a0f3 --- /dev/null +++ b/static/js/vendor/pushy.js @@ -0,0 +1,107 @@ +/*! Pushy - v0.9.2 - 2014-9-13 +* Pushy is a responsive off-canvas navigation menu using CSS transforms & transitions. +* https://github.com/christophery/pushy/ +* by Christopher Yee */ + +$(function() { + var pushy = $('.pushy'), //menu css class + body = $('body'), + container = $('#container'), //container css class + push = $('.push'), //css class to add pushy capability + siteOverlay = $('.site-overlay'), //site overlay + pushyClass = "pushy-left pushy-open", //menu position & menu open class + pushyActiveClass = "pushy-active", //css class to toggle site overlay + containerClass = "container-push", //container open class + pushClass = "push-push", //css class to add pushy capability + menuBtn = $('.menu-btn, .pushy a'), //css classes to toggle the menu + menuSpeed = 200, //jQuery fallback menu speed + menuWidth = pushy.width() + "px"; //jQuery fallback menu width + + function togglePushy(){ + body.toggleClass(pushyActiveClass); //toggle site overlay + pushy.toggleClass(pushyClass); + container.toggleClass(containerClass); + push.toggleClass(pushClass); //css class to add pushy capability + } + + function openPushyFallback(){ + body.addClass(pushyActiveClass); + pushy.animate({left: "0px"}, menuSpeed); + container.animate({left: menuWidth}, menuSpeed); + push.animate({left: menuWidth}, menuSpeed); //css class to add pushy capability + } + + function closePushyFallback(){ + body.removeClass(pushyActiveClass); + pushy.animate({left: "-" + menuWidth}, menuSpeed); + container.animate({left: "0px"}, menuSpeed); + push.animate({left: "0px"}, menuSpeed); //css class to add pushy capability + } + + //checks if 3d transforms are supported removing the modernizr dependency + cssTransforms3d = (function csstransforms3d(){ + var el = document.createElement('p'), + supported = false, + transforms = { + 'webkitTransform':'-webkit-transform', + 'OTransform':'-o-transform', + 'msTransform':'-ms-transform', + 'MozTransform':'-moz-transform', + 'transform':'transform' + }; + + // Add it to the body to get the computed style + document.body.insertBefore(el, null); + + for(var t in transforms){ + if( el.style[t] !== undefined ){ + el.style[t] = 'translate3d(1px,1px,1px)'; + supported = window.getComputedStyle(el).getPropertyValue(transforms[t]); + } + } + + document.body.removeChild(el); + + return (supported !== undefined && supported.length > 0 && supported !== "none"); + })(); + + if(cssTransforms3d){ + //toggle menu + menuBtn.click(function() { + togglePushy(); + }); + //close menu when clicking site overlay + siteOverlay.click(function(){ + togglePushy(); + }); + }else{ + //jQuery fallback + pushy.css({left: "-" + menuWidth}); //hide menu by default + container.css({"overflow-x": "hidden"}); //fixes IE scrollbar issue + + //keep track of menu state (open/close) + var state = true; + + //toggle menu + menuBtn.click(function() { + if (state) { + openPushyFallback(); + state = false; + } else { + closePushyFallback(); + state = true; + } + }); + + //close menu when clicking site overlay + siteOverlay.click(function(){ + if (state) { + openPushyFallback(); + state = false; + } else { + closePushyFallback(); + state = true; + } + }); + } +}); \ No newline at end of file diff --git a/static/js/vendor/pushy.min.js b/static/js/vendor/pushy.min.js new file mode 100644 index 0000000..16e4c18 --- /dev/null +++ b/static/js/vendor/pushy.min.js @@ -0,0 +1 @@ +$(function(){function a(){e.toggleClass(j),d.toggleClass(i),f.toggleClass(k),g.toggleClass(l)}function b(){e.addClass(j),d.animate({left:"0px"},n),f.animate({left:o},n),g.animate({left:o},n)}function c(){e.removeClass(j),d.animate({left:"-"+o},n),f.animate({left:"0px"},n),g.animate({left:"0px"},n)}var d=$(".pushy"),e=$("body"),f=$("#container"),g=$(".push"),h=$(".site-overlay"),i="pushy-left pushy-open",j="pushy-active",k="container-push",l="push-push",m=$(".menu-btn, .pushy a"),n=200,o=d.width()+"px";if(cssTransforms3d=function(){var a=document.createElement("p"),b=!1,c={webkitTransform:"-webkit-transform",OTransform:"-o-transform",msTransform:"-ms-transform",MozTransform:"-moz-transform",transform:"transform"};document.body.insertBefore(a,null);for(var d in c)void 0!==a.style[d]&&(a.style[d]="translate3d(1px,1px,1px)",b=window.getComputedStyle(a).getPropertyValue(c[d]));return document.body.removeChild(a),void 0!==b&&b.length>0&&"none"!==b}())m.click(function(){a()}),h.click(function(){a()});else{d.css({left:"-"+o}),f.css({"overflow-x":"hidden"});var p=!0;m.click(function(){p?(b(),p=!1):(c(),p=!0)}),h.click(function(){p?(b(),p=!1):(c(),p=!0)})}}); \ No newline at end of file diff --git a/templates/index.swig b/templates/index.swig index 29be68f..418fd93 100644 --- a/templates/index.swig +++ b/templates/index.swig @@ -12,46 +12,90 @@ + -
-

- - Universal Remote -

-
    - {% for remote in remotes %} - {% set remoteName = loop.key %} -
  • {{ labelForRemote(remoteName) }}
  • - {% endfor %} + + -
    + +
    + +
    + +
    +
    + +

    + + Universal Remote +

    -
      - {% for macro in macros %} - {% set macroName = loop.key %} -
    • - {% endfor %} -
    + {% for macro in macros %} + {% set area = loop.key %} +
      +

      {{area}}

      + {% for aMacroCommand in macro %} + {% set macroName = loop.key %} +
    • + {% endfor %} +
    + {% endfor %} + +
    -
      - {% for remote in remotes %} - {% set remoteName = loop.key %} -
    • -
        - {% for command in remote %} -
      • - -
      • - {% endfor %} -
      -
    • +
        + {% for macro in macros %} + {% set area = loop.key %} +
      • +
          + {% for command in macro %} + {% set macroName = loop.key %} +
        • + + {% endfor %} + +
        + {% endfor %} +
      + +
        + {% for remote in remotes %} + {% set remoteName = loop.key %} +
      • +
          + {% for command in remote %} +
        • + +
        • + {% endfor %} +
        +
      • {% endfor %} -
      +
    @@ -62,5 +106,6 @@ + diff --git a/test/fixtures/config.json b/test/fixtures/config.json index 1a3e2b4..ec8c14b 100644 --- a/test/fixtures/config.json +++ b/test/fixtures/config.json @@ -6,17 +6,32 @@ } }, "macros": { - "Play Xbox 360": [ - [ "SonyTV", "Power" ], - [ "SonyTV", "Xbox360" ], - [ "Yamaha", "Power" ], - [ "Yamaha", "Xbox360" ], - [ "Xbox360", "Power" ] - ], - "Listen to Music": [ - [ "Yamaha", "Power" ], - [ "Yamaha", "AirPlay" ] - ] + "LivingRoom": { + "Play Xbox 360": [ + [ "SonyTV", "Power" ], + [ "SonyTV", "Xbox360" ], + [ "Yamaha", "Power" ], + [ "Yamaha", "Xbox360" ], + [ "Xbox360", "Power" ] + ], + "Listen to Music": [ + [ "Yamaha", "Power" ], + [ "Yamaha", "AirPlay" ] + ] + }, + "Downstairs": { + "Play Nintendo": [ + [ "SonyTV", "Power" ], + [ "SonyTV", "Xbox360" ], + [ "Yamaha", "Power" ], + [ "Yamaha", "Xbox360" ], + [ "Xbox360", "Power" ] + ], + "Listen to Radio": [ + [ "Yamaha", "Power" ], + [ "Yamaha", "AirPlay" ] + ] + } }, "commandLabels": { "LircNamespace": { @@ -28,6 +43,7 @@ } }, "remoteLabels": { - "LircNamespace": "LIRC namespace" + "LircNamespace": "LIRC namespace", + "Xbox360": "Xbox 360" } } diff --git a/test/lirc_web.js b/test/lirc_web.js index ec4360e..08e08c4 100644 --- a/test/lirc_web.js +++ b/test/lirc_web.js @@ -29,7 +29,7 @@ describe('lirc_web', function() { }); it('should have GET route for JSON list of commands for macro', function(done) { - assert(request(app).get('/macros/Play%20Xbox%20360.json').expect(200, done)); + assert(request(app).get('/macros/Play Xbox 360.json').expect(200, done)); }); it('should return 404 for unknown remote', function(done) { @@ -53,7 +53,7 @@ describe('lirc_web', function() { // Sending macros it('should have POST route for sending a macro', function(done) { - assert(request(app).post('/macros/xbox_360').expect(200, done)); + assert(request(app).post('/macros/LivingRoom/Play Xbox 360').expect(200, done)); }); }); From 5f3370e4f3b0d5605be7fe7f62d7e1d41e566004 Mon Sep 17 00:00:00 2001 From: dirz88 Date: Sun, 17 Jan 2016 17:48:27 +0100 Subject: [PATCH 2/2] test --- .eslintrc | 13 + .gitignore | 3 +- .travis.yml | 6 + CHANGELOG.md | 82 ++++++ Gruntfile.js | 109 ++++---- Makefile | 12 - README.md | 80 ++++-- app.js | 234 ++++++++++++------ example_configs/config.json | 23 +- .../upstart/standalone_upstart.conf | 37 +++ lib/labels.js | 14 +- lib/macros.js | 28 +++ package.json | 46 ++-- static/css/app.less | 2 +- static/css/compiled/app.css | 1 + static/favicons/android-chrome-144x144.png | Bin 0 -> 5106 bytes static/favicons/android-chrome-192x192.png | Bin 0 -> 6749 bytes static/favicons/android-chrome-36x36.png | Bin 0 -> 1354 bytes static/favicons/android-chrome-48x48.png | Bin 0 -> 1698 bytes static/favicons/android-chrome-72x72.png | Bin 0 -> 2687 bytes static/favicons/android-chrome-96x96.png | Bin 0 -> 3507 bytes static/favicons/apple-touch-icon-114x114.png | Bin 0 -> 1962 bytes static/favicons/apple-touch-icon-120x120.png | Bin 0 -> 2030 bytes static/favicons/apple-touch-icon-144x144.png | Bin 0 -> 2308 bytes static/favicons/apple-touch-icon-152x152.png | Bin 0 -> 2446 bytes static/favicons/apple-touch-icon-180x180.png | Bin 0 -> 2747 bytes static/favicons/apple-touch-icon-57x57.png | Bin 0 -> 1238 bytes static/favicons/apple-touch-icon-60x60.png | Bin 0 -> 1285 bytes static/favicons/apple-touch-icon-72x72.png | Bin 0 -> 1402 bytes static/favicons/apple-touch-icon-76x76.png | Bin 0 -> 1515 bytes .../favicons/apple-touch-icon-precomposed.png | Bin 0 -> 5483 bytes static/favicons/apple-touch-icon.png | Bin 0 -> 2747 bytes static/favicons/browserconfig.xml | 12 + static/favicons/favicon-16x16.png | Bin 0 -> 816 bytes static/favicons/favicon-194x194.png | Bin 0 -> 3609 bytes static/favicons/favicon-32x32.png | Bin 0 -> 1251 bytes static/favicons/favicon-96x96.png | Bin 0 -> 3507 bytes static/favicons/favicon.ico | Bin 0 -> 15086 bytes static/favicons/manifest.json | 41 +++ static/favicons/mstile-144x144.png | Bin 0 -> 4841 bytes static/favicons/mstile-150x150.png | Bin 0 -> 4780 bytes static/favicons/mstile-310x150.png | Bin 0 -> 5273 bytes static/favicons/mstile-310x310.png | Bin 0 -> 10330 bytes static/favicons/mstile-70x70.png | Bin 0 -> 3385 bytes static/favicons/safari-pinned-tab.svg | 35 +++ templates/favicons.swig | 22 ++ templates/index.swig | 41 +++ test/fixtures/config.json | 31 +++ test/fixtures/remotes.json | 10 + test/lib/macros.js | 62 +++++ test/lirc_web.js | 224 ++++++++++------- 51 files changed, 894 insertions(+), 274 deletions(-) create mode 100644 .eslintrc create mode 100644 .travis.yml create mode 100644 CHANGELOG.md delete mode 100644 Makefile create mode 100644 example_configs/upstart/standalone_upstart.conf create mode 100644 lib/macros.js create mode 100644 static/favicons/android-chrome-144x144.png create mode 100644 static/favicons/android-chrome-192x192.png create mode 100644 static/favicons/android-chrome-36x36.png create mode 100644 static/favicons/android-chrome-48x48.png create mode 100644 static/favicons/android-chrome-72x72.png create mode 100644 static/favicons/android-chrome-96x96.png create mode 100644 static/favicons/apple-touch-icon-114x114.png create mode 100644 static/favicons/apple-touch-icon-120x120.png create mode 100644 static/favicons/apple-touch-icon-144x144.png create mode 100644 static/favicons/apple-touch-icon-152x152.png create mode 100644 static/favicons/apple-touch-icon-180x180.png create mode 100644 static/favicons/apple-touch-icon-57x57.png create mode 100644 static/favicons/apple-touch-icon-60x60.png create mode 100644 static/favicons/apple-touch-icon-72x72.png create mode 100644 static/favicons/apple-touch-icon-76x76.png create mode 100644 static/favicons/apple-touch-icon-precomposed.png create mode 100644 static/favicons/apple-touch-icon.png create mode 100644 static/favicons/browserconfig.xml create mode 100644 static/favicons/favicon-16x16.png create mode 100644 static/favicons/favicon-194x194.png create mode 100644 static/favicons/favicon-32x32.png create mode 100644 static/favicons/favicon-96x96.png create mode 100644 static/favicons/favicon.ico create mode 100644 static/favicons/manifest.json create mode 100644 static/favicons/mstile-144x144.png create mode 100644 static/favicons/mstile-150x150.png create mode 100644 static/favicons/mstile-310x150.png create mode 100644 static/favicons/mstile-310x310.png create mode 100644 static/favicons/mstile-70x70.png create mode 100644 static/favicons/safari-pinned-tab.svg create mode 100644 templates/favicons.swig create mode 100644 test/lib/macros.js diff --git a/.eslintrc b/.eslintrc new file mode 100644 index 0000000..96ef944 --- /dev/null +++ b/.eslintrc @@ -0,0 +1,13 @@ +{ + "extends": "airbnb/legacy", + "rules": { + "func-names": 0, + "no-console": 0, + "guard-for-in": 0, + "max-len": 1 + }, + "env": { + "node": true, + "mocha": true, + } +} diff --git a/.gitignore b/.gitignore index b0e4c52..9f9d0a8 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,5 @@ node_modules/ .DS_Store .project .settings/ -*.sw* +*.swp +npm-debug.log diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..971e9e6 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,6 @@ +language: node_js +node_js: + - "node" +script: + - npm run lint-js + - npm test diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..0b99a21 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,82 @@ +# Change Log + +All notable changes to this project will be documented in this file. + +As of `v0.1.0`, this project adheres to [Semantic Versioning](http://semver.org/). + +## Unreleased + +## [0.2.4] - 2016-01-13 + +* Extracts macros into a standalone lib/macros.js file +* Adds Favicon (thanks @flochtililoch) +* Adds npm run test:watch action (thanks @OvisMaximus) +* Travis build will now run linter +* Updated dependencies +* Uses local version of jQuery for testing now + +## [0.2.3] - 2016-01-03 + +* Fixing bug where labels were loaded before config (thanks @flochtililoch) + +## [0.2.2] - 2016-01-01 + +* Removing `Makefile` for running tests. Only need `package.json`. +* Fixing .gitignore error for the global lirc_web build + +## [0.2.1] - 2015-12-31 + +* `lirc_web` can now be installed globally and called by `lirc_web` from CLI +* Adding ESLint to the mix and ensuring all JS conforms to Airbnb ES5 standards + +## [0.2.0] - 2015-12-30 + +* Adding `blacklist` configuration option to hide unused keys from UI (thanks @OvisMaximus) +* Adding support for SSL (thanks @de-live-gdev) +* Fixing example config in the README (thanks @de-live-gdev) +* Fixes url escaping bug with macros and remotes (issue #23) + +## [0.1.0] - 2015-12-30 + +* Locking npm versions to ensure future install work +* Adding `CHANGELOG.md` +* Adding `/refresh` link on bottom to reload UI after making changes to LIRC (thanks @f00f) +* Adding ability to set custom labels on command and remote names (thanks @elysion) +* Adding Apple mobile app capability, disabling zoom (thanks @elysion) +* Moving Lato fonts locally to remove external network dependency + +## [0.0.8] - 2014-01-18 + +* Adding `macros` configuration option +* Fixing bug with setInterval causing repeaters to potentially never stop + +## [0.0.7] - 2013-12-29 + +* Adding `send_start` and `send_stop` support to UI +* Adding `config.json` configuration file which allows users to set options +* Adding `repeaters` as a configuration file +* Adding documentation about API to README +* Setting up proper test suite with LIRC test fixtures + +## [0.0.6] - 2013-12-01 + +* Adding `upstart` example configuration files + +## [0.0.5] - 2013-08-21 + +* Locking swig dependency due to breaking change in new version +* `urlencode` command names (thanks @joe-forbes) + +## [0.0.4] - 2013-05-16 + +* Fixing iOS caching error that was preventing commands from sending + +## [0.0.3] - 2013-03-31 + +## [0.0.2] - 2013-03-22 + +* Include compiled JS and CSS for ease of installation + +## [0.0.1] - 2013-03-20 + +* Initial commit and integration with `lirc_node` diff --git a/Gruntfile.js b/Gruntfile.js index f4c12d6..08536c2 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -1,53 +1,60 @@ -module.exports = function(grunt) { - - // Load some tasks - grunt.loadNpmTasks('grunt-contrib-less'); - grunt.loadNpmTasks('grunt-contrib-uglify'); - grunt.loadNpmTasks('grunt-contrib-watch'); - grunt.loadNpmTasks('grunt-develop'); - - // Project configuration. - grunt.initConfig({ - pkg: grunt.file.readJSON('package.json'), - - uglify: { - app: { - src: ['static/js/vendor/zepto.min.js', - 'static/js/vendor/zepto.touch.js', - 'static/js/vendor/fastclick.js', - 'static/js/app/*.js'], - dest: 'static/js/compiled/app.js' - } - }, - - less: { - app: { - files: { - "static/css/compiled/app.css": "static/css/app.less" - } - } +module.exports = function (grunt) { + // Load some tasks + grunt.loadNpmTasks('grunt-contrib-less'); + grunt.loadNpmTasks('grunt-contrib-uglify'); + grunt.loadNpmTasks('grunt-contrib-watch'); + grunt.loadNpmTasks('grunt-develop'); + require('load-grunt-tasks')(grunt); // npm install --save-dev load-grunt-tasks + + // Project configuration. + grunt.initConfig({ + pkg: grunt.file.readJSON('package.json'), + + uglify: { + app: { + src: ['static/js/vendor/zepto.min.js', + 'static/js/vendor/zepto.touch.js', + 'static/js/vendor/fastclick.js', + 'static/js/app/*.js'], + dest: 'static/js/compiled/app.js', + }, + }, + + less: { + app: { + files: { + 'static/css/compiled/app.css': 'static/css/app.less', }, - - watch: { - scripts: { - files: ['static/js/app/*.js', 'static/js/vendor/*.js'], - tasks: ['uglify:app'] - }, - stylesheets: { - files: ['static/css/*.less'], - tasks: ['less'] - } - }, - - develop: { - server: { - file: 'app.js', - env: { NODE_ENV: 'development'} - } - } - }); - - grunt.registerTask('default', ['uglify', 'less']); - grunt.registerTask('server', ['uglify', 'less', 'develop', 'watch']); - + }, + }, + + eslint: { + target: ['Gruntfile.js', 'app.js', 'lib/**/*.js', 'test/**/*.js'], + }, + + watch: { + scripts: { + files: ['static/js/app/*.js', 'static/js/vendor/*.js'], + tasks: ['uglify:app'], + }, + stylesheets: { + files: ['static/css/*.less'], + tasks: ['less'], + }, + serverscripts: { + files: ['<%= eslint.target %>'], + tasks: ['eslint'], + }, + }, + + develop: { + server: { + file: 'app.js', + env: { NODE_ENV: 'development' }, + }, + }, + }); + + grunt.registerTask('default', ['uglify', 'less', 'eslint']); + grunt.registerTask('server', ['uglify', 'less', 'eslint', 'develop', 'watch']); }; diff --git a/Makefile b/Makefile deleted file mode 100644 index 45cb1c0..0000000 --- a/Makefile +++ /dev/null @@ -1,12 +0,0 @@ -TESTS = test/*.js -REPORTER = dot - -test: - @NODE_ENV=test ./node_modules/.bin/mocha \ - --require should \ - --require test/common.js \ - --reporter $(REPORTER) \ - --growl \ - $(TESTS) - -.PHONY: test bench diff --git a/README.md b/README.md index 3848e0e..d3aad5b 100644 --- a/README.md +++ b/README.md @@ -7,35 +7,53 @@ This project allows you to control LIRC from any web browser - phone, tablet, or This is part of the [Open Source Universal Remote](http://opensourceuniversalremote.com) project. +[![Build Status](https://travis-ci.org/alexbain/lirc_web.png)](https://travis-ci.org/alexbain/lirc_web) ## Installation -You'll need to have [LIRC](http://lirc.org) installed and configured on your machine to use ``lirc_web``. In addition, you'll need to install [nodejs](http://nodejs.org). Once you have LIRC and nodejs installed and configured, you'll be able to install ``lirc_web`` and it's dependencies: +You'll need to have [LIRC](http://lirc.org) installed and configured on your machine to use ``lirc_web``. In addition, you'll need to install [nodejs](http://nodejs.org). Once you have LIRC and nodejs installed and configured, you'll be able to install ``lirc_web``: - git clone git://github.com/alexbain/lirc_web.git - cd lirc_web - npm install - node app.js + npm install -g lirc_web + lirc_web + +Note that you may need to run the `npm install` command with `sudo`. -You're set! Verify the web interface works by opening ``http://SERVER:3000/`` in a web browser. +### Viewing -If you want to have the app available via port 80 and start on boot, there are example NGINX and Upstart configuration files included in the ``example_configs/`` directory. +Verify the web interface works by opening ``http://SERVER:3000/`` in a web browser. +If you want to have `lirc_web` available via port 80 and start on boot, there are example NGINX and Upstart configuration files included in the ``example_configs/`` directory. ## Configuration -As of v0.0.8, ``lirc_web`` supports customization through a configuration file (``config.json``) in the root of the project. There are currently four configuration options: +As of v0.0.8, ``lirc_web`` supports customization through a configuration file. + +You may place this configuration file in one of two locations and `lirc_web` will detect it: + +1. Place a file named `.lirc_web_config.json` in the home directory of the user running `lirc_web` (global installation) +2. Place a file named `config.json` in the root of the `lirc_web` project directory (local / development installation) + +These are the available configuration options: 1. ``repeaters`` - buttons that repeatedly send their commands while pressed. A common example are the volume buttons on most remote controls. While you hold the volume buttons down, the remote will repeatedly send the volume command to your device. 2. ``macros`` - a collection of commands that should be executed one after another. This allows you to automate actions like "Play Xbox 360" or "Listen to music via AirPlay". Each step in a macro is described in the format ``[ "REMOTE", "COMMAND" ]``, where ``REMOTE`` and ``COMMAND`` are defined by what you have programmed into LIRC. You can add delays between steps of macros in the format of ``[ "delay", 500 ]``. Note that the delay is measured in milliseconds so 1000 milliseconds = 1 second. 3. ``commandLabels`` - a way to rename commands that LIRC understands (``KEY_POWER``, ``KEY_VOLUMEUP``) with labels that humans prefer (``Power``, ``Volume Up``). 4. ``remoteLabels`` - a way to rename the remotes that LIRC understands (``XBOX360``) with labels that humans prefer (``Xbox 360``). +5. ``blacklists`` - a way to hide unused commands from your remotes. +6. ``server`` - server configuration settings (ports, [SSL](http://serverfault.com/a/366374)). #### Example config.json: { + "server" : { + "port" : 3000, + "ssl" : false, + "ssl_cert" : "/home/pi/lirc_web/server.cert", + "ssl_key" : "/home/pi/lirc_web/server.key", + "ssl_port" : 3001 + }, "repeaters": { "SonyTV": { "VolumeUp": true, @@ -69,12 +87,20 @@ As of v0.0.8, ``lirc_web`` supports customization through a configuration file ( "remoteLabels": { "Xbox360": "Xbox 360" } + "blacklists": { + "Yamaha": [ + "AUX2", + "AUX3" + ] + } } +Please see the `example_configs/` directory. + ## Using the JSON API -Building an app on top of lirc_web is straight forward with the included JSON based RESTful API. +Building an app on top of `lirc_web` is straight forward with the included JSON based RESTful API. API endpoints: @@ -90,9 +116,13 @@ API endpoints: ## Development Would you like to contribute to and improve ``lirc_web``? Fantastic. To contribute -patches, run tests or benchmarks, install ``lirc_web`` using the instructions above. Once that is complete, you'll need to setup the development environment. +patches, run tests or benchmarks, install ``lirc_web`` locally: + + git clone git://github.com/alexbain/lirc_web.git + cd lirc_web + npm install -Now, you'll need to setup the development environment. ``lirc_web`` uses the [GruntJS](http://gruntjs.com/) built system to make development easier. +Next, you'll need to setup the development environment. ``lirc_web`` uses the [GruntJS](http://gruntjs.com/) built system to make development easier. Install GruntJS (build environment): @@ -109,15 +139,35 @@ Install GruntJS (build environment): You can run the test suite by running: ``` -make test +npm test +``` + +If you develop test driven, you may want to launch a continuous test which automatically restarts when server or tests are modified: + ``` +npm run test:watch +``` + +You can run the linter to confirm JS conforms to standards by running: + +``` +npm run lint-js +``` + +You can also run the linter continuously via grunt: +``` +grunt watch +``` + ## Contributing Before you submit a pull request with your change, please be sure to: -* Add new tests that prove your change works as expected. -* Ensure all existing tests are still passing. +* Add new tests that prove your change works as expected +* Ensure all existing tests are still passing +* Run the linter to ensure your code conforms to the js styleguide +* Update CHANGELOG.md file ('Unreleased' section) with concise bullet points Once you're sure everything is still working, open a pull request with a clear description of what you changed and why. I will not accept a pull request which @@ -130,7 +180,7 @@ The exception to this would be refactoring existing code or changing documentati (The MIT License) -Copyright (c) 2013 Alex Bain <alex@alexba.in> +Copyright (c) 2013-2016 Alex Bain <alex@alexba.in> Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the diff --git a/app.js b/app.js index 17493af..e7a5582 100644 --- a/app.js +++ b/app.js @@ -1,126 +1,177 @@ -// lirc_web - v0.0.8 -// Alex Bain +#! /usr/bin/env node // Requirements -var express = require('express'), - lirc_node = require('lirc_node'), - consolidate = require('consolidate'), - path = require('path'), - swig = require('swig'), - labels = require('./lib/labels'); +var express = require('express'); +var logger = require('morgan'); +var compress = require('compression'); +var lircNode = require('lirc_node'); +var consolidate = require('consolidate'); +var swig = require('swig'); +var labels = require('./lib/labels'); +var https = require('https'); +var fs = require('fs'); +var macros = require('./lib/macros'); // Precompile templates var JST = { - index: swig.compileFile(__dirname + '/templates/index.swig') + index: swig.compileFile(__dirname + '/templates/index.swig'), }; // Create app var app = module.exports = express(); -// App configuration -app.engine('.html', consolidate.swig); -app.configure(function() { - app.use(express.logger()); - app.set('views', __dirname + '/views'); - app.set('view engine', 'jade'); - app.use(express.compress()); - app.use(express.static(__dirname + '/static')); -}); - // lirc_web configuration var config = {}; -// Based on node environment, initialize connection to lirc_node or use test data -if (process.env.NODE_ENV == 'test' || process.env.NODE_ENV == 'development') { - lirc_node.remotes = require(__dirname + '/test/fixtures/remotes.json'); - config = require(__dirname + '/test/fixtures/config.json'); -} else { - _init(); -} +// Server & SSL options +var port = 3000; +var sslOptions = { + key: null, + cert: null, +}; + +var labelFor = {}; + +// App configuration +app.engine('.html', consolidate.swig); +app.use(logger('combined')); +app.set('views', __dirname + '/views'); +app.set('view engine', 'jade'); +app.use(compress()); +app.use(express.static(__dirname + '/static')); function _init() { - lirc_node.init(); + var home = process.env.HOME; + + lircNode.init(); - // Config file is optional + // Config file is optional + try { try { - config = require(__dirname + '/config.json'); - } catch(e) { - console.log("DEBUG:", e); - console.log("WARNING: Cannot find config.json!"); + config = require(__dirname + '/config.json'); + } catch (e) { + config = require(home + '/.lirc_web_config.json'); } + } catch (e) { + console.log('DEBUG:', e); + console.log('WARNING: Cannot find config.json!'); + } } +function refineRemotes(myRemotes) { + var newRemotes = {}; + var newRemoteCommands = null; + var remote = null; + + function isBlacklistExisting(remoteName) { + return config.blacklists && config.blacklists[remoteName]; + } + + function getCommandsForRemote(remoteName) { + var remoteCommands = myRemotes[remoteName]; + var blacklist = null; + + if (isBlacklistExisting(remoteName)) { + blacklist = config.blacklists[remoteName]; + + remoteCommands = remoteCommands.filter(function (command) { + return blacklist.indexOf(command) < 0; + }); + } + + return remoteCommands; + } + + for (remote in myRemotes) { + newRemoteCommands = getCommandsForRemote(remote); + newRemotes[remote] = newRemoteCommands; + } + + return newRemotes; +} + +// Based on node environment, initialize connection to lircNode or use test data +if (process.env.NODE_ENV === 'test' || process.env.NODE_ENV === 'development') { + lircNode.remotes = require(__dirname + '/test/fixtures/remotes.json'); + config = require(__dirname + '/test/fixtures/config.json'); +} else { + _init(); +} + +// initialize Labels for remotes / commands +labelFor = labels(config.remoteLabels, config.commandLabels); + // Routes -var labelFor = labels(config.remoteLabels, config.commandLabels) - -// Web UI -app.get('/', function(req, res) { - res.send(JST['index'].render({ - remotes: lirc_node.remotes, - macros: config.macros, - repeaters: config.repeaters, - labelForRemote: labelFor.remote, - labelForCommand: labelFor.command - })); +// Index +app.get('/', function (req, res) { + var refinedRemotes = refineRemotes(lircNode.remotes); + res.send(JST.index({ + remotes: refinedRemotes, + macros: config.macros, + repeaters: config.repeaters, + labelForRemote: labelFor.remote, + labelForCommand: labelFor.command, + })); }); // Refresh -app.get('/refresh', function(req, res) { - _init(); - res.redirect('/'); +app.get('/refresh', function (req, res) { + _init(); + res.redirect('/'); }); // List all remotes in JSON format -app.get('/remotes.json', function(req, res) { - res.json(lirc_node.remotes); +app.get('/remotes.json', function (req, res) { + res.json(refineRemotes(lircNode.remotes)); }); // List all commands for :remote in JSON format -app.get('/remotes/:remote.json', function(req, res) { - if (lirc_node.remotes[req.params.remote]) { - res.json(lirc_node.remotes[req.params.remote]); - } else { - res.send(404); - } +app.get('/remotes/:remote.json', function (req, res) { + if (lircNode.remotes[req.params.remote]) { + res.json(refineRemotes(lircNode.remotes)[req.params.remote]); + } else { + res.sendStatus(404); + } }); // List all macros in JSON format -app.get('/macros.json', function(req, res) { - res.json(config.macros); +app.get('/macros.json', function (req, res) { + res.json(config.macros); }); // List all commands for :macro in JSON format -app.get('/macros/:macro.json', function(req, res) { - if (config.macros && config.macros[req.params.macro]) { - res.json(config.macros[req.params.macro]); - } else { - res.send(404); - } +app.get('/macros/:macro.json', function (req, res) { + if (config.macros && config.macros[req.params.macro]) { + res.json(config.macros[req.params.macro]); + } else { + res.sendStatus(404); + } }); // Send :remote/:command one time -app.post('/remotes/:remote/:command', function(req, res) { - lirc_node.irsend.send_once(req.params.remote, req.params.command, function() {}); - res.setHeader('Cache-Control', 'no-cache'); - res.send(200); +app.post('/remotes/:remote/:command', function (req, res) { + lircNode.irsend.send_once(req.params.remote, req.params.command, function () {}); + res.setHeader('Cache-Control', 'no-cache'); + res.sendStatus(200); }); // Start sending :remote/:command repeatedly -app.post('/remotes/:remote/:command/send_start', function(req, res) { - lirc_node.irsend.send_start(req.params.remote, req.params.command, function() {}); - res.setHeader('Cache-Control', 'no-cache'); - res.send(200); +app.post('/remotes/:remote/:command/send_start', function (req, res) { + lircNode.irsend.send_start(req.params.remote, req.params.command, function () {}); + res.setHeader('Cache-Control', 'no-cache'); + res.sendStatus(200); }); // Stop sending :remote/:command repeatedly -app.post('/remotes/:remote/:command/send_stop', function(req, res) { - lirc_node.irsend.send_stop(req.params.remote, req.params.command, function() {}); - res.setHeader('Cache-Control', 'no-cache'); - res.send(200); +app.post('/remotes/:remote/:command/send_stop', function (req, res) { + lircNode.irsend.send_stop(req.params.remote, req.params.command, function () {}); + res.setHeader('Cache-Control', 'no-cache'); + res.sendStatus(200); }); // Execute a macro (a collection of commands to one or more remotes) +<<<<<<< HEAD app.post('/macros/:area/:macro', function(req, res) { // If the macro exists, execute each command in the macro with 100msec @@ -148,11 +199,38 @@ app.post('/macros/:area/:macro', function(req, res) { nextCommand(); } +======= +app.post('/macros/:macro', function (req, res) { + // If the macro exists, execute it + if (config.macros && config.macros[req.params.macro]) { + macros.exec(config.macros[req.params.macro], lircNode); res.setHeader('Cache-Control', 'no-cache'); - res.send(200); + res.sendStatus(200); + } else { +>>>>>>> refs/remotes/alexbain/master + res.setHeader('Cache-Control', 'no-cache'); + res.sendStatus(404); + } }); +// Listen (http) +if (config.server && config.server.port) { + port = config.server.port; +} +// only start server, when called as application +if (!module.parent) { + app.listen(port); + console.log('Open Source Universal Remote UI + API has started on port ' + port + ' (http).'); +} + +// Listen (https) +if (config.server && config.server.ssl && config.server.ssl_cert && config.server.ssl_key && config.server.ssl_port) { + sslOptions = { + key: fs.readFileSync(config.server.ssl_key), + cert: fs.readFileSync(config.server.ssl_cert), + }; -// Default port is 3000 -app.listen(3000); -console.log("Open Source Universal Remote UI + API has started on port 3000."); + https.createServer(sslOptions, app).listen(config.server.ssl_port); + + console.log('Open Source Universal Remote UI + API has started on port ' + config.server.ssl_port + ' (https).'); +} diff --git a/example_configs/config.json b/example_configs/config.json index dd6436e..22b108e 100644 --- a/example_configs/config.json +++ b/example_configs/config.json @@ -1,4 +1,11 @@ { + "server" : { + "port" : 3000, + "ssl" : false, + "ssl_cert" : "/home/pi/lirc_web/server.cert", + "ssl_key" : "/home/pi/lirc_web/server.key", + "ssl_port" : 3001 + }, "repeaters": { "SonyTV": { "VolumeUp": true, @@ -7,11 +14,11 @@ }, "macros": { "Xbox360": [ - { "SonyTV": "Power" }, - { "SonyTV": "Xbox360" }, - { "Yamaha": "Power" }, - { "Yamaha": "Xbox360" }, - { "Xbox360": "Power" } + [ "SonyTV", "Power" ], + [ "SonyTV", "Xbox360" ], + [ "Yamaha", "Power" ], + [ "Yamaha", "Xbox360" ], + [ "Xbox360", "Power" ] ] }, "commandLabels": { @@ -23,5 +30,11 @@ }, "remoteLabels": { "Xbox360": "Xbox 360" + }, + "blacklists": { + "Yamaha": [ + "AUX2", + "AUX3" + ] } } diff --git a/example_configs/upstart/standalone_upstart.conf b/example_configs/upstart/standalone_upstart.conf new file mode 100644 index 0000000..20263d8 --- /dev/null +++ b/example_configs/upstart/standalone_upstart.conf @@ -0,0 +1,37 @@ +# /etc/init/open-source-universal-remote.conf +description "universalremote.local" + +start on runlevel [2345] +stop on runlevel [016] + +# Restart when job dies +respawn + +# Give up restart after 5 respawns in 60 seconds +respawn limit 5 60 + +script + + # Store the pid file in /var/run + echo $$ > /var/run/open-source-universal-remote.pid + + # Should run in 'production' mode + env NODE_ENV=production + + # File to execute, where to pipe logs + exec lirc_web 2>&1 >> /var/log/open-source-universal-remote.upstart.log + +end script + +pre-start script + # Log that lirc_web started up + echo "[`date -u +%Y-%m-%dT%T.%3NZ`] (sys) Starting" >> /var/log/open-source-universal-remote.upstart.log +end script + +pre-stop script + # Remove pid file + rm /var/run/open-source-universal-remote.pid + + # Log that lirc_web shut down + echo "[`date -u +%Y-%m-%dT%T.%3NZ`] (sys) Stopping" >> /var/log/open-source-universal-remote.upstart.log +end script diff --git a/lib/labels.js b/lib/labels.js index 19d39e9..ae5efcf 100644 --- a/lib/labels.js +++ b/lib/labels.js @@ -1,9 +1,4 @@ -module.exports = function(remoteLabels, commandLabels) { - return { - command: getCommandLabel, - remote: getRemoteLabel - }; - +module.exports = function Labels(remoteLabels, commandLabels) { function getCommandLabel(remote, command) { return commandLabels && commandLabels[remote] && commandLabels[remote][command] ? commandLabels[remote][command] : command; } @@ -11,4 +6,9 @@ module.exports = function(remoteLabels, commandLabels) { function getRemoteLabel(remote) { return remoteLabels && remoteLabels[remote] ? remoteLabels[remote] : remote; } -} + + return { + command: getCommandLabel, + remote: getRemoteLabel, + }; +}; diff --git a/lib/macros.js b/lib/macros.js new file mode 100644 index 0000000..5fd977a --- /dev/null +++ b/lib/macros.js @@ -0,0 +1,28 @@ +function exec(macro, lircNode, iter) { + var i = iter || 0; + + // select the command from the sequence + var command = macro[i]; + + if (!command) { return false; } + + i = i + 1; + + // if the command is delay, wait N msec and then execute next command + if (command[0] === 'delay') { + setTimeout(function () { + exec(macro, lircNode, i); + }, command[1]); + } else { + // By default, wait 100msec before calling next command + lircNode.irsend.send_once(command[0], command[1], function () { + setTimeout(function () { + exec(macro, lircNode, i); + }, 100); + }); + } + + return true; +} + +exports.exec = exec; diff --git a/package.json b/package.json index 99e8493..7182ebb 100644 --- a/package.json +++ b/package.json @@ -2,29 +2,43 @@ "name": "lirc_web", "description": "A NodeJS / Express app that creates a web UI + API for LIRC", "main": "app.js", - "version": "0.0.8", - "private": true, + "version": "0.2.4", + "preferGlobal": true, "dependencies": { - "express": "3.0.6", - "lirc_node": "*", - "swig": "0.13.5", - "consolidate": "0.11.0" + "compression": "^1.6.0", + "consolidate": "^0.13.1", + "express": "^4.13.0", + "lirc_node": "0.0.4", + "lodash": "^3.10.1", + "morgan": "^1.6.1", + "swig": "^1.4.2" }, "devDependencies": { - "mocha": "*", - "should": "7.1.0", - "sinon": "*", - "supertest": "1.1.0", - "nodemon": "*", + "eslint": "^1.10.0", + "eslint-config-airbnb": "^3.1.0", + "eslint-plugin-react": "^3.15.0", "grunt": "0.4.5", - "grunt-contrib-less": "1.0.1", - "grunt-contrib-uglify": "0.9.2", + "grunt-contrib-less": "^1.1.0", + "grunt-contrib-uglify": "^0.11.0", "grunt-contrib-watch": "0.6.1", - "jsdom": "*", - "grunt-develop": "0.3.0" + "grunt-develop": "^0.4.0", + "grunt-eslint": "17.3.1", + "jquery": "^2.2.0", + "jsdom": "^7.2.2", + "load-grunt-tasks": "^3.4.0", + "mocha": "2.3.4", + "nodemon": "1.8.1", + "should": "^8.1.1", + "sinon": "1.17.2", + "supertest": "1.1.0" + }, + "bin": { + "lirc_web": "./app.js" }, "scripts": { - "test": "make test" + "test": "NODE_ENV=test mocha --require should --require test/common.js --reporter dot test/**", + "test:watch": "NODE_ENV=test mocha --require should --require test/common.js --watch --reporter dot lib/** test/** ", + "lint-js": "eslint app.js lib/** test/**" }, "repository": { "type": "git", diff --git a/static/css/app.less b/static/css/app.less index 5da70c8..ce284a4 100644 --- a/static/css/app.less +++ b/static/css/app.less @@ -184,7 +184,7 @@ footer { padding: 10px 0; } -footer a { +footer p, footer a { font-size: 12px; font-weight: 300; } diff --git a/static/css/compiled/app.css b/static/css/compiled/app.css index a63e80b..a6fc13e 100644 --- a/static/css/compiled/app.css +++ b/static/css/compiled/app.css @@ -137,6 +137,7 @@ footer { width: 100%; padding: 10px 0; } +footer p, footer a { font-size: 12px; font-weight: 300; diff --git a/static/favicons/android-chrome-144x144.png b/static/favicons/android-chrome-144x144.png new file mode 100644 index 0000000000000000000000000000000000000000..137dd2f1b732505c469ab299dc7fcdeb5b734067 GIT binary patch literal 5106 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 FzW{r?RjL30 literal 0 HcmV?d00001 diff --git a/static/favicons/android-chrome-192x192.png b/static/favicons/android-chrome-192x192.png new file mode 100644 index 0000000000000000000000000000000000000000..e5efc49736d9d09d384ffe29f7e1185780577392 GIT binary patch literal 6749 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 literal 0 HcmV?d00001 diff --git a/static/favicons/android-chrome-36x36.png b/static/favicons/android-chrome-36x36.png new file mode 100644 index 0000000000000000000000000000000000000000..75a7d4b4cdaddc450e607b87104038cae766cc0f GIT binary patch literal 1354 zcmeAS@N?(olHy`uVBq!ia0vp^Dj>|k3?#4J%UA`ZSkfJR9T^xl_H+M9WCijSl0AZa z85pY67#JE_7#My5g&JNkFq9fFFuY1&V6d9Oz#v{QXIG#N0|VoU0G|+7paSFRw+yG< zFqv`NWab^ynRkq*-7*HUX8_3?rZeuC&bnhV{kHM++eXuF8BDneQbr8~G!SSnF-95B zKvzbjM*N0@d<1j@s$YOypt#fgyB>?~Th6*;I^*{8%kLk&{~5F95m52`bMG#{{2sab zk_SC!}mY$zyAqd@vv;y^RK`DzWVaJ>&PqX*>~*b z-aY;F+mAp0Dt13NoOY{n_jCJscUup>tl#${Y~{m?FTNX0yY=_qf9D1FHeCO>{@O?9 z`S)BF+*^9_y~V6MJ8ysb|NsB&GjAZ60`FkaaY0Ewh4CO0)kN5w?JK*Pd9 z!$84AMMp+8H#U<4Cv+4wRplf#^>o$Lco!{IQJrxy)Ns=Yms6)sYHMg|YHMq1oj85c z?bL=v+DXY_fuX_I6WX&bhFlK{J!rtmVPUbgwB+j-UY_0_Ul!l977`m6%JgE3xlem= zOxU{V*|8)(=FPpiOV-TY+q`(+6#kq;}cryS{Z4e=W4pOwD2o0@7Kc8t)>Sp z?uy7?jktLwJZrtxuI{hddb|v%EI20MVN&ZTpCOc xr!XsT4pBILcX6_VDQ#B* z004%!w{e3I`Qa$aK-HhWsTKm+5G)Z30QasbO9JGe9v)=xMg)LFT>wZq3jpt+tCR@< zh=YVv{s4es0f1I?L4&Im07yH#IFf830-DJ)W`UZC575j3Bj$kK)gDyVfH8}=Ka+=M z@r;=~WM&VN(esf8G5??a(e6Wl|H_g1@5w*=#!n|<{`FCx&534#s2rXxdnmSk9L)s7 zIb+Lf>)|zHC?+qoMmV=D39S|$yfNrsF^Xb>MoiGEfN#PAeaZ!QUe1-hn6t>|o92RP z4=1PIu5D~=w2z80`TVrj$uljJ;&<=oS0s5|BKOjf(fN17v#SxDF|%ASr(-(y{x~v+ zXOau1woI1y%w;@&W0}uCQ9llfRv!z*Hibiko5Q%mq3Zs5_8&77i)&9t#dbwQPQ@cP zexFHdnpk_kzP`Ru&@F0vB`$t8>ryg8`B~8aX63=qqIn+QqwG}!e{n^!-Yyh})d&+B zUz_FezqcRlf<}r?b8~Ws@NY{>-7+G#1r)8kS0pexCEnt-`PV}OQ43Ft;#|#icYb+X zgP!=EbiAS_+G-z6{u{rHhTien#kW&~k8Z|pJ~zoy+}5`weD<*Z{)0I!*`zzs*1Oe| z6ckmoO?c%rSsh?KItoDNqe zF9VwXaJ&tcWdAtG|DgQkB#CsYb?dI8K!G5zXjiLssvh%a zoXjl$)?%#Z7v%&$tSg>E!MkF=>dI!cm0^pUd_0K0IClqcJ2KhM+rb^@ORR13QMoOj zEQ3Rw3ev5&LNib;8SL}}hOjNawuXn7L>l_DsbxXbF7Bfg9V7+p=M7kzV=Nl=>l=Rg zA>JzxJyRR|dhfvBIHcm~6;=u$icC06I_>01U~ixaEpGYk(h`lXTE?zC-O9x?^qlj| z0|+{(?cpU(`R2)ojtpO(RJ%>Q`*k@xdAvNm=5^QIvRuoMB`yUGZi--;#qcC@8Xg`R z%Hz)Y=GUsy&Rx>{mcna3wz@zSIWYJ8ajsYC@OJR_=~lALW7>pDa7L95hFsz0dw|}} zO^+DQsr0L@W>mo<`tA-tt%`Y3as{@dx6<38Kdr9YyDlocCO)xmCS7fLdayA>z#VM7FlUUM6oIElMuhDOD$~P#PCkN&BZ(7p z8Hn>L29bKEwVoWQ>^fZRRAGA&=Y@;g#R@h@_9ywM5+hrZ_#)MjuTKDQ?#cX12JT0o zLEJ;LJxmJ@piwbFu~Y~EBP7by0Esd{p-4zm4AKOHM(snQFi0ePe__-=1d+#s!;U6= zTHqS9?KvbMCz1}++ydzE=-A^&!;Vqmw1j9XJS>_P1OV{`qYIlAV?6csJDfYa@MCU( z3_{s3NLdY`=D<*wQG?s082=V5gQy|FJNsWg;dXJgxeA>r_JssPU`k7swqR|N4Oswq MTPK?eYyZ?g0qj5~=>Px# literal 0 HcmV?d00001 diff --git a/static/favicons/android-chrome-72x72.png b/static/favicons/android-chrome-72x72.png new file mode 100644 index 0000000000000000000000000000000000000000..57920ba8d646c70911832440edce50eb9c6d2ec4 GIT binary patch literal 2687 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 literal 0 HcmV?d00001 diff --git a/static/favicons/android-chrome-96x96.png b/static/favicons/android-chrome-96x96.png new file mode 100644 index 0000000000000000000000000000000000000000..b88f7fcade2be1dac2a3a77a6e864e25030f356a GIT binary patch literal 3507 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 literal 0 HcmV?d00001 diff --git a/static/favicons/apple-touch-icon-120x120.png b/static/favicons/apple-touch-icon-120x120.png new file mode 100644 index 0000000000000000000000000000000000000000..42adb7a174cdea80fd99fb5bed84b82d01da2648 GIT binary patch literal 2030 zcmZ`)c~sJg7XFo(n%Q!&v12nY%kr!*+d_D$5> z4b8oB9d`<`ayL_xRS^^r5L9;XG4K8L{&@G?d%yGDbG~!``Of+7`*^t^Ki#88FQ)pD~dr~S?XK#C;*q+tO-p*^K706-E1 z0JsPMaL54w6I6bi?{NTFf70iSKWuGnO|4lI$y5}+oIqc}j|wwCapPOqztzrq;Tf=E zno~a2K96!Vm&})_hS-vdZ^E>$g^)TXyo`Q4pK>IJtWs;3B1QAGIK7*5v1-P-fVv`A zzx=Xz<_+C0k5V@wvdtmaPl&Jtj$0A++}oLm_j9Qo3r|SA;$cDU_obF;aR+s|o4!&q zEI5`&Ig4k+HM6q6F1AyaC%7`+s#2=dTyLLuEu;q5&a%Y{xk}?wNWI=R&s|ZjD%9UN zQq9`h*n)I=QQpT~tr!#L4e+1!Ek5k!JtXoTlX!(gg6i)}?bKz4P`)HpiDl|Zo~&U? z6w}Cp7SkMFQ%;xC1FL6~{+iGE%I~JF%q%G$_3{vPOt&IhY!j<|OemD9iiQOlAGr#( zh9y>bmosPr`Kc0`V?O1NMi#o2eWQIorDHzo!(33!tVbDrc1e*%T!;=^4rp~T+J3M@APn}0y(CK6Hqnd zTfwl&AzS0LJ#B*{zxuNHypQLNXCUjDD++Z?6U#b#`Y?`ssd_fFo_VsEHprIT>{$57 zSn;c146r1ZYi1=1wNE+2HkbTvQY4nE-Aie0lx4@)l*S+8Tb&Eo9?qrenZ;EVs)dbe zW=A#5oyF6Sy{1%;FKIMuDvjp(=f!@Oq?Ihb);3R?U5##-6G&8(T-mLz1y~8qE|={8 zcIE+*n+FQ))%t8*sHZy=$khGiw3g_h;b&objCF<^bpO!+s?yUs6$Xc%@Q%Oqcp6X5A+lGzH)e1V{9T{$)ap_>p}uIJ60o9{I1~FTu!pbXdr<*A2%V zM=8hMyI$PH=gS{=fG`(H$Qu)!FuDCo6$oP(x|5q=QYWd|7sv~48ES8idK8&dLf%f6 z+yP-G-HY0g%<|diejz^+&qHx?56>*SSjyId56%PxeW%2(--$i=F%^s*jPSm3w^LjT z!c_j+P4};L!B5ZC!_l#7m%Xy)adu2C`9gOm;XRXIFp+EuzLmoC@BBkrsIW{<^sIUK zW1v&mCDrIDv6HtID$iOqK5>aYCoHb;Uh5CHBFZ%*+x+-mShDP0lc&(5zdU z5gCpna`Xjzn!fuy~wTRTyH~Jf{|UoW&w1DV>7)NKQ@$q>PnmsDfUld14LYO zjy?1=VQ33DvR6N1Np8Jk(JjLJ@lqS7G}kXHSh8L81LV%DhV(}5st zrNj`}A8;*r-;OOIcVPn)1|wG|-oG2Dzt?A2*BUm^i+3Zmjcuqe8E-maT=uhIHt&T3 zhxHqy58KCd?7zU^0uDoutiES%hNSJ&UCs(uCixo2xr&K{myMHnY|tDnt&(@Gmhty@`+PNl=w&!+-*$wlbl21X&L?}P85#PX)14}0Fd z;>HzN4}~X~x*XAf_@Wo0-M`tqx#$9`9-Z7@9e4mop%>`qq#4MX!g5FTh65n!Crh0J zHX+xaqd7ZWEiy5|Jtt8XQv)WPERj0kZT~3?oMs(%?^w5A;Mc!{%>oSzK!+jG;SN_4 z!nFV#hFBjx2(dnBZS4;^>Hx8Iuz~D{SUW%<_byjY|BoOxE-X6Y#{U;cH-ICwf{Q8s z0cbyDvKcBNE+RT6+zfpK6>b)dLSNCwa6VJKNk9IAmDN}8{sB145703;JaoknY!3E( zVXOl-gQgw%6sBX|;BVGHHU616K>V4g*PnK}*y9kuHQ9z1yV<_dz5w7bFKC?`;?BPT DVP8&< literal 0 HcmV?d00001 diff --git a/static/favicons/apple-touch-icon-144x144.png b/static/favicons/apple-touch-icon-144x144.png new file mode 100644 index 0000000000000000000000000000000000000000..7ef17dd371506c2b796f84a5f767d916dfb15d12 GIT binary patch literal 2308 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 e%YaCrN-hBE7ZG&wLN%2D0000v_Ux4< z+fb1VGh=3qG0eg&%s%7O_uu!&_s2Q!^PcB<&w0*s&hy5@?5}KB*rNadz;}Httj=mM50ZvP3Y@Q?TCa$Cp zFz(dP*%T8@vT=2jY-Gm*tc)m@%F4%=?MjJ8S=hL4npF|Ose*Ll{p{_!xu7NrqGdj; zjT+ugjq9O3`@CqLk9VmgCHB$F$Cf|Mtc)%2iOWLzs+c2`^2IWdRMtGrxl}ll&`Ybw zuqS8&#;Qapku^+F*Y{f!h-GLBuWEwTf#=S#MO8mp!xY{! zUox{KY@Fh}{kD|yl^*?hF}RuXR|6%We(r9=TvMWNSXy)eoS&l#&`Dqa~#QZSH?wegDu|!OsgvFPPFY+I? zQ7sB*rWk_P1B^~QcbXx9e;`vhqKa`=YCj{fmp(vV%^zKg=%5;+uqRO1+!1Crnwf)U z#`n<7a`A?l*ai$6za%UhTVCQxMkqYz3X)R=(KrhWeKQS3VXu`EBigASez89jR*bW7 z4&}t^306=u1`DlA7&a6&{h(z&wVxjKY4J=Bu3~(-1pL%vl*4s4{n`buBoOL|GGwj~7f0(?7;JF&<|w9f0hNGR*C zya8@|#R7tu4%*!={-zV$atunx6?9oceE}DgP{Ou7w zxmJ?LO;#^tc|02Bm=t*P@|VL4sM52`tp#}~pTlYj&o91({RR%2C=`g;{fQu5*6xH} zl}ZbJ716e@NuVA+ca@ZyrjQ~hFBcm#v~!4LHTU+w7xSok0O$Cwmc>?1br?#GPe@eX zzpt#@zwfnW|5c@Wmix1 z*~MRrD^MvPx6eO_QtYHyYQmk&6z+P!aVsTr8lv={jfkd%a>w%W(z1nsgSvLFY;)%w zh)$t#BYt~}b+ivYaQzk7qq=kgJ(``Nk@jWCvNPrq`t+3&=U#s*dSqlTWwZm=YBE)) zQhKLNIab{;STjC}#2v&Y zU1`O1{xNj)a*kJv`_51DA2I!!jo^Y?L5i0_{cKPAGq>v6G4A46#XDCe{TWt~jQkPLiKg+Siqzoc8;=!sV@1cmaMOuzI^T zv8nR@irIcUiVowZ$ikl6Yhe~tH=-F(n*&9%n?uyF(c!P$o1%*`3h1NWM|gksRRgon z;Z9w_K{6k@QO?JBYfs{;*4v3QeIAvV2jE}7^3xpozRxD(_5L_Wt5M)H7H7`Ny=8;og)CAR69$DRwH|x)8zlwTX&%F?lj@*&ZFbDbK|5@!)2W zL2So1&}$n1s6kMqbHLN&I?VFlx`R}b2M0x?usxo2nbgzP0~Ycx$u-N@iU zib{k0mtnG}LsrxaNu_YR)ekm3G*j8{(4j{~rC7L0IPGL(9WNIOd0}V zEp(hiipscG6v_=rw5?C5xv|C+ol>r5C+Al|$O$jfGo|vTnqRNzX5K42U94WXM~BoC zGPC`R9uyWOJKPyDZWhx{?pha(1Juc~ByLxCA-mvap1Ec{VtPClAn2dAZx@3G@Q>(dFC?nX0Mn7wD zd527)9e5+UpbK)}sFZgD2)!=6bWVkQUjMi%a>+tDpLe?2r87iJhxxvK$MkcpWAn7r zn6ZW((%soDnAe4H=fngD=n-CS9D84=8Zt)wfPFO?o|V~|c|lqCxbmBC31-lf_l1sL zxH|&=s|cIK8=wS-Mm5llu&&h7|2pAsLrf3o^Nt`jdU0B(6xR*<5W>EFxr~p2yE2eX5hO`Ra@2e zwc18iutns_9>0y+4bI?U%+G;dbgyRb=HW=ILK_2swp$s&xp*pLeFR`_X>U<;@mBPI E015^!qW}N^ literal 0 HcmV?d00001 diff --git a/static/favicons/apple-touch-icon-180x180.png b/static/favicons/apple-touch-icon-180x180.png new file mode 100644 index 0000000000000000000000000000000000000000..b6e9128dd3a254f4d752094cf00a70226e32bdcf GIT binary patch literal 2747 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 IM6N<$f)kF;PXGV_ literal 0 HcmV?d00001 diff --git a/static/favicons/apple-touch-icon-76x76.png b/static/favicons/apple-touch-icon-76x76.png new file mode 100644 index 0000000000000000000000000000000000000000..9a13ccce4f3e2f0032280b63a81eb7e5a81abbcd GIT binary patch literal 1515 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 H$7lZm&7^r# literal 0 HcmV?d00001 diff --git a/static/favicons/apple-touch-icon.png b/static/favicons/apple-touch-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..b6e9128dd3a254f4d752094cf00a70226e32bdcf GIT binary patch literal 2747 zcmZ`)c{J3E7ytHz7kWZb2&L7Fc(kCrc($=*Nl{3MnqoD+z@oYd*EajNa9<2|8+yHA&^-)_4@QU}ZzFwkd4I zvh;K3vioUG80L2hXH&4XwY{^(5k9OVK1Gq>>C+D_rOh&7=SaX;aE zAQnNz^DT3%#BOTi46|r-?b9?vKX2|0nmV+!$>0iKbx|Nq%a%pBvWfLc3Ma0UVpoEn zqwzpR^EFe95z=NtH#M}G)V&6Y+THIxUe*?-K!WH|%iTa60}Jy}q^L5V-Tk_i3H-9woB1GD4175pn|qP1C{Q)@g_6Fc-i*4QBIHHdZXdmR4;ss8Dq8hE~TG!jcLl~`5P zQ@dBN+=mHMynng^|3vY>K{f!Mn;dk zNd)zsE&r?AF)dv-IsWuB4dcTU&ta9XM8Z3;x_NwxRjTTT!bs!yItE71oyfg1;CJ2W z$-`^ivG0ZlspR6i%YE6kGsWL>B84#U9C@|Y`$TYE%6#;x8;2=S2A9ty?rZk-&mR~e zfrpyq(w4Z^JPl8nncQjHsXhrf`Z{gvfM&j;VaYXko-(WG+VJtQhAi{kwsQe5`WAd% z@ZLL%hKR+Y9WOusI(WNgNX760VjVh7x;>xGTV|U)jT#Lxt%~&96c&jFp0~+3qGxci zg`)T5wpTDc#*C$_P}!SjPa689mGc(m#DY1v*6KCh?=xf|SPLQEGc5zw^8M~3mxw(l zOMKv^9k}{?!F}Vnkxi3liJwwD2vh8UC5^(k+eO$X@G?%UJkmR2-j?!2Ru(Vro&gbi z4&9+jCr!bgA7nFfi+V*51@p_aCdM00BEG16ZsH#lNWVXOXi_O|Gt?#U9DiY;#!2f&4rpTo z8gU~I>Hp2dbnWLM2k<*4!|;}7k+0+5LG26sbG>EkZB<#`D! zD9a>Mxs}6SB{>C+MJgp`vu!XdbZ#P`R}z_e>yjHg>D^UQFm>qL*>gPe2WMwUU=E$ za%(K~mEz3|!k11jQz+!5mXGoioI?_9?VGS?cyU zWLpODw)$IzvWGtt3O5S~sKzo9Kyzv=G4VLN@V(l<@Nh$mV{d;ni<8h+O=bPkGrc9uPbjXa{D{H^XCsg79wo$ z0|JjW~r*z>|h`!1D?W+SWoQ`$sM^pr9}BrKI=_NQbixovevgv7t|=qYp&@x;C? z8er_{qx(5I`^BT;onJ>R>HQp6sGE>w9zwbaJZjfDDvr1$o5eiCKa~%Cog&p@#+pqq zij1pi2gzM(vd>sX7I(3yi%vjmLP1GkbZInhd1PMpWO4u_#L%H5;Z%TR;n|`s(F>&k z@0#Miz>(M55g1=vWX9l-VoPX6PFC2gyl!cj@bbQNxV@#Tr}TK7|FI9U65#QJuTkj% z!D&d?eB$wl%ISQ>7*>aa{s8OYOySJj%7l+Bz3CwRJRgbeyy=8fafI(AEB3TgO0KJKnW& z_J0Wef%n0_5&xgS5tH-XB{)Yqxj-B}pQ;5t3G@X&_ECdG1o^0egCO1j5Dr~rO8pvq z>&%&cyMaN_k|Q9ZE~(`$DW@)HoqkY6PR%r0ulv4;dcBj{z>n{}=s~nHT6`ed@}rd& YKvj@|FikGJ|JfYK?7At! + + + + + + + + #ffc40d + + + diff --git a/static/favicons/favicon-16x16.png b/static/favicons/favicon-16x16.png new file mode 100644 index 0000000000000000000000000000000000000000..02035543d99201e18bae5ccd9fc9ff9d6d4cb0d1 GIT binary patch literal 816 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 c%B<7@8zKUfT_f$hlNJC=Q>;n7(bbs$0OV literal 0 HcmV?d00001 diff --git a/static/favicons/favicon-32x32.png b/static/favicons/favicon-32x32.png new file mode 100644 index 0000000000000000000000000000000000000000..81402b6cd469279b673836f7938978bd411466d5 GIT binary patch literal 1251 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 B#;pJV literal 0 HcmV?d00001 diff --git a/static/favicons/mstile-310x150.png b/static/favicons/mstile-310x150.png new file mode 100644 index 0000000000000000000000000000000000000000..15eb069c3452e4025ff97405e83f94d67a5928e4 GIT binary patch literal 5273 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

    %s%opErKh5)r>?4?qN=B& zBB5A(<9`7B13a)^;r~D2LJ*V+04_z?I^b-sg-Qel2Y6xqJSA}9fu0iBK%6@W6qdiZ zdxRs%UQTYvdUyoAYy)DJ;#PL&7LXD+mwAj?K*BUmqsN0;s=-!b_`_e6-jUwpz3jtr csA7yV=!-BvZr51zAut69jkGc)8DG8qFQUCytN;K2 literal 0 HcmV?d00001 diff --git a/static/favicons/mstile-310x310.png b/static/favicons/mstile-310x310.png new file mode 100644 index 0000000000000000000000000000000000000000..bcb2f2b21bf1997a6fc2cf88bdf483335def8484 GIT binary patch literal 10330 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 literal 0 HcmV?d00001 diff --git a/static/favicons/mstile-70x70.png b/static/favicons/mstile-70x70.png new file mode 100644 index 0000000000000000000000000000000000000000..aaccb138330a07a9e034b1cad11c7d1f01fd7601 GIT binary patch literal 3385 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 literal 0 HcmV?d00001 diff --git a/static/favicons/safari-pinned-tab.svg b/static/favicons/safari-pinned-tab.svg new file mode 100644 index 0000000..a0023f4 --- /dev/null +++ b/static/favicons/safari-pinned-tab.svg @@ -0,0 +1,35 @@ + + + + +Created by potrace 1.11, written by Peter Selinger 2001-2013 + + + + + + + diff --git a/templates/favicons.swig b/templates/favicons.swig new file mode 100644 index 0000000..53d819a --- /dev/null +++ b/templates/favicons.swig @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/templates/index.swig b/templates/index.swig index 418fd93..0931806 100644 --- a/templates/index.swig +++ b/templates/index.swig @@ -12,6 +12,7 @@ +<<<<<<< HEAD @@ -19,6 +20,21 @@