squab 1.3.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (41) hide show
  1. data/LICENSE.md +13 -0
  2. data/README.md +4 -0
  3. data/bin/squab +4 -0
  4. data/defaults.yaml +5 -0
  5. data/lib/squab.rb +2 -0
  6. data/lib/squab/db.rb +37 -0
  7. data/lib/squab/events.rb +301 -0
  8. data/lib/squab/web.rb +231 -0
  9. data/public/api.html +66 -0
  10. data/public/css/anytime.css +777 -0
  11. data/public/css/bootstrap-responsive.css +1058 -0
  12. data/public/css/bootstrap-responsive.min.css +9 -0
  13. data/public/css/bootstrap.css +5774 -0
  14. data/public/css/bootstrap.min.css +9 -0
  15. data/public/css/docs.css +1001 -0
  16. data/public/css/normalize.css +406 -0
  17. data/public/css/pickadate/default.css +240 -0
  18. data/public/css/pickadate/default.date.css +332 -0
  19. data/public/css/prettify.css +30 -0
  20. data/public/css/squab.css +307 -0
  21. data/public/events.html +85 -0
  22. data/public/img/glyphicons-halflings-white.png +0 -0
  23. data/public/img/glyphicons-halflings.png +0 -0
  24. data/public/js/collection/events.js +74 -0
  25. data/public/js/lib/backbone-min.js +4 -0
  26. data/public/js/lib/datejs/core.js +48 -0
  27. data/public/js/lib/datejs/date-en-US.js +145 -0
  28. data/public/js/lib/jquery-latest.js +9440 -0
  29. data/public/js/lib/lodash.min.js +48 -0
  30. data/public/js/lib/pickadate/legacy.js +140 -0
  31. data/public/js/lib/pickadate/picker.date.js +957 -0
  32. data/public/js/lib/pickadate/picker.js +791 -0
  33. data/public/js/lib/typeahead.min.js +7 -0
  34. data/public/js/model/event.js +38 -0
  35. data/public/js/router.js +38 -0
  36. data/public/js/squab.js +6 -0
  37. data/public/js/view/day_view.js +22 -0
  38. data/public/js/view/event_view.js +14 -0
  39. data/public/js/view/events_view.js +46 -0
  40. data/public/js/view/search_view.js +130 -0
  41. metadata +220 -0
@@ -0,0 +1,7 @@
1
+ /*!
2
+ * typeahead.js 0.9.3
3
+ * https://github.com/twitter/typeahead
4
+ * Copyright 2013 Twitter, Inc. and other contributors; Licensed MIT
5
+ */
6
+
7
+ !function(a){var b="0.9.3",c={isMsie:function(){var a=/(msie) ([\w.]+)/i.exec(navigator.userAgent);return a?parseInt(a[2],10):!1},isBlankString:function(a){return!a||/^\s*$/.test(a)},escapeRegExChars:function(a){return a.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g,"\\$&")},isString:function(a){return"string"==typeof a},isNumber:function(a){return"number"==typeof a},isArray:a.isArray,isFunction:a.isFunction,isObject:a.isPlainObject,isUndefined:function(a){return"undefined"==typeof a},bind:a.proxy,bindAll:function(b){var c;for(var d in b)a.isFunction(c=b[d])&&(b[d]=a.proxy(c,b))},indexOf:function(a,b){for(var c=0;c<a.length;c++)if(a[c]===b)return c;return-1},each:a.each,map:a.map,filter:a.grep,every:function(b,c){var d=!0;return b?(a.each(b,function(a,e){return(d=c.call(null,e,a,b))?void 0:!1}),!!d):d},some:function(b,c){var d=!1;return b?(a.each(b,function(a,e){return(d=c.call(null,e,a,b))?!1:void 0}),!!d):d},mixin:a.extend,getUniqueId:function(){var a=0;return function(){return a++}}(),defer:function(a){setTimeout(a,0)},debounce:function(a,b,c){var d,e;return function(){var f,g,h=this,i=arguments;return f=function(){d=null,c||(e=a.apply(h,i))},g=c&&!d,clearTimeout(d),d=setTimeout(f,b),g&&(e=a.apply(h,i)),e}},throttle:function(a,b){var c,d,e,f,g,h;return g=0,h=function(){g=new Date,e=null,f=a.apply(c,d)},function(){var i=new Date,j=b-(i-g);return c=this,d=arguments,0>=j?(clearTimeout(e),e=null,g=i,f=a.apply(c,d)):e||(e=setTimeout(h,j)),f}},tokenizeQuery:function(b){return a.trim(b).toLowerCase().split(/[\s]+/)},tokenizeText:function(b){return a.trim(b).toLowerCase().split(/[\s\-_]+/)},getProtocol:function(){return location.protocol},noop:function(){}},d=function(){var a=/\s+/;return{on:function(b,c){var d;if(!c)return this;for(this._callbacks=this._callbacks||{},b=b.split(a);d=b.shift();)this._callbacks[d]=this._callbacks[d]||[],this._callbacks[d].push(c);return this},trigger:function(b,c){var d,e;if(!this._callbacks)return this;for(b=b.split(a);d=b.shift();)if(e=this._callbacks[d])for(var f=0;f<e.length;f+=1)e[f].call(this,{type:d,data:c});return this}}}(),e=function(){function b(b){b&&b.el||a.error("EventBus initialized without el"),this.$el=a(b.el)}var d="typeahead:";return c.mixin(b.prototype,{trigger:function(a){var b=[].slice.call(arguments,1);this.$el.trigger(d+a,b)}}),b}(),f=function(){function a(a){this.prefix=["__",a,"__"].join(""),this.ttlKey="__ttl__",this.keyMatcher=new RegExp("^"+this.prefix)}function b(){return(new Date).getTime()}function d(a){return JSON.stringify(c.isUndefined(a)?null:a)}function e(a){return JSON.parse(a)}var f,g;try{f=window.localStorage,f.setItem("~~~","!"),f.removeItem("~~~")}catch(h){f=null}return g=f&&window.JSON?{_prefix:function(a){return this.prefix+a},_ttlKey:function(a){return this._prefix(a)+this.ttlKey},get:function(a){return this.isExpired(a)&&this.remove(a),e(f.getItem(this._prefix(a)))},set:function(a,e,g){return c.isNumber(g)?f.setItem(this._ttlKey(a),d(b()+g)):f.removeItem(this._ttlKey(a)),f.setItem(this._prefix(a),d(e))},remove:function(a){return f.removeItem(this._ttlKey(a)),f.removeItem(this._prefix(a)),this},clear:function(){var a,b,c=[],d=f.length;for(a=0;d>a;a++)(b=f.key(a)).match(this.keyMatcher)&&c.push(b.replace(this.keyMatcher,""));for(a=c.length;a--;)this.remove(c[a]);return this},isExpired:function(a){var d=e(f.getItem(this._ttlKey(a)));return c.isNumber(d)&&b()>d?!0:!1}}:{get:c.noop,set:c.noop,remove:c.noop,clear:c.noop,isExpired:c.noop},c.mixin(a.prototype,g),a}(),g=function(){function a(a){c.bindAll(this),a=a||{},this.sizeLimit=a.sizeLimit||10,this.cache={},this.cachedKeysByAge=[]}return c.mixin(a.prototype,{get:function(a){return this.cache[a]},set:function(a,b){var c;this.cachedKeysByAge.length===this.sizeLimit&&(c=this.cachedKeysByAge.shift(),delete this.cache[c]),this.cache[a]=b,this.cachedKeysByAge.push(a)}}),a}(),h=function(){function b(a){c.bindAll(this),a=c.isString(a)?{url:a}:a,i=i||new g,h=c.isNumber(a.maxParallelRequests)?a.maxParallelRequests:h||6,this.url=a.url,this.wildcard=a.wildcard||"%QUERY",this.filter=a.filter,this.replace=a.replace,this.ajaxSettings={type:"get",cache:a.cache,timeout:a.timeout,dataType:a.dataType||"json",beforeSend:a.beforeSend},this._get=(/^throttle$/i.test(a.rateLimitFn)?c.throttle:c.debounce)(this._get,a.rateLimitWait||300)}function d(){j++}function e(){j--}function f(){return h>j}var h,i,j=0,k={};return c.mixin(b.prototype,{_get:function(a,b){function c(c){var e=d.filter?d.filter(c):c;b&&b(e),i.set(a,c)}var d=this;f()?this._sendRequest(a).done(c):this.onDeckRequestArgs=[].slice.call(arguments,0)},_sendRequest:function(b){function c(){e(),k[b]=null,f.onDeckRequestArgs&&(f._get.apply(f,f.onDeckRequestArgs),f.onDeckRequestArgs=null)}var f=this,g=k[b];return g||(d(),g=k[b]=a.ajax(b,this.ajaxSettings).always(c)),g},get:function(a,b){var d,e,f=this,g=encodeURIComponent(a||"");return b=b||c.noop,d=this.replace?this.replace(this.url,g):this.url.replace(this.wildcard,g),(e=i.get(d))?c.defer(function(){b(f.filter?f.filter(e):e)}):this._get(d,b),!!e}}),b}(),i=function(){function d(b){c.bindAll(this),c.isString(b.template)&&!b.engine&&a.error("no template engine specified"),b.local||b.prefetch||b.remote||a.error("one of local, prefetch, or remote is required"),this.name=b.name||c.getUniqueId(),this.limit=b.limit||5,this.minLength=b.minLength||1,this.header=b.header,this.footer=b.footer,this.valueKey=b.valueKey||"value",this.template=e(b.template,b.engine,this.valueKey),this.local=b.local,this.prefetch=b.prefetch,this.remote=b.remote,this.itemHash={},this.adjacencyList={},this.storage=b.name?new f(b.name):null}function e(a,b,d){var e,f;return c.isFunction(a)?e=a:c.isString(a)?(f=b.compile(a),e=c.bind(f.render,f)):e=function(a){return"<p>"+a[d]+"</p>"},e}var g={thumbprint:"thumbprint",protocol:"protocol",itemHash:"itemHash",adjacencyList:"adjacencyList"};return c.mixin(d.prototype,{_processLocalData:function(a){this._mergeProcessedData(this._processData(a))},_loadPrefetchData:function(d){function e(a){var b=d.filter?d.filter(a):a,e=m._processData(b),f=e.itemHash,h=e.adjacencyList;m.storage&&(m.storage.set(g.itemHash,f,d.ttl),m.storage.set(g.adjacencyList,h,d.ttl),m.storage.set(g.thumbprint,n,d.ttl),m.storage.set(g.protocol,c.getProtocol(),d.ttl)),m._mergeProcessedData(e)}var f,h,i,j,k,l,m=this,n=b+(d.thumbprint||"");return this.storage&&(f=this.storage.get(g.thumbprint),h=this.storage.get(g.protocol),i=this.storage.get(g.itemHash),j=this.storage.get(g.adjacencyList)),k=f!==n||h!==c.getProtocol(),d=c.isString(d)?{url:d}:d,d.ttl=c.isNumber(d.ttl)?d.ttl:864e5,i&&j&&!k?(this._mergeProcessedData({itemHash:i,adjacencyList:j}),l=a.Deferred().resolve()):l=a.getJSON(d.url).done(e),l},_transformDatum:function(a){var b=c.isString(a)?a:a[this.valueKey],d=a.tokens||c.tokenizeText(b),e={value:b,tokens:d};return c.isString(a)?(e.datum={},e.datum[this.valueKey]=a):e.datum=a,e.tokens=c.filter(e.tokens,function(a){return!c.isBlankString(a)}),e.tokens=c.map(e.tokens,function(a){return a.toLowerCase()}),e},_processData:function(a){var b=this,d={},e={};return c.each(a,function(a,f){var g=b._transformDatum(f),h=c.getUniqueId(g.value);d[h]=g,c.each(g.tokens,function(a,b){var d=b.charAt(0),f=e[d]||(e[d]=[h]);!~c.indexOf(f,h)&&f.push(h)})}),{itemHash:d,adjacencyList:e}},_mergeProcessedData:function(a){var b=this;c.mixin(this.itemHash,a.itemHash),c.each(a.adjacencyList,function(a,c){var d=b.adjacencyList[a];b.adjacencyList[a]=d?d.concat(c):c})},_getLocalSuggestions:function(a){var b,d=this,e=[],f=[],g=[];return c.each(a,function(a,b){var d=b.charAt(0);!~c.indexOf(e,d)&&e.push(d)}),c.each(e,function(a,c){var e=d.adjacencyList[c];return e?(f.push(e),(!b||e.length<b.length)&&(b=e),void 0):!1}),f.length<e.length?[]:(c.each(b,function(b,e){var h,i,j=d.itemHash[e];h=c.every(f,function(a){return~c.indexOf(a,e)}),i=h&&c.every(a,function(a){return c.some(j.tokens,function(b){return 0===b.indexOf(a)})}),i&&g.push(j)}),g)},initialize:function(){var b;return this.local&&this._processLocalData(this.local),this.transport=this.remote?new h(this.remote):null,b=this.prefetch?this._loadPrefetchData(this.prefetch):a.Deferred().resolve(),this.local=this.prefetch=this.remote=null,this.initialize=function(){return b},b},getSuggestions:function(a,b){function d(a){f=f.slice(0),c.each(a,function(a,b){var d,e=g._transformDatum(b);return d=c.some(f,function(a){return e.value===a.value}),!d&&f.push(e),f.length<g.limit}),b&&b(f)}var e,f,g=this,h=!1;a.length<this.minLength||(e=c.tokenizeQuery(a),f=this._getLocalSuggestions(e).slice(0,this.limit),f.length<this.limit&&this.transport&&(h=this.transport.get(a,d)),!h&&b&&b(f))}}),d}(),j=function(){function b(b){var d=this;c.bindAll(this),this.specialKeyCodeMap={9:"tab",27:"esc",37:"left",39:"right",13:"enter",38:"up",40:"down"},this.$hint=a(b.hint),this.$input=a(b.input).on("blur.tt",this._handleBlur).on("focus.tt",this._handleFocus).on("keydown.tt",this._handleSpecialKeyEvent),c.isMsie()?this.$input.on("keydown.tt keypress.tt cut.tt paste.tt",function(a){d.specialKeyCodeMap[a.which||a.keyCode]||c.defer(d._compareQueryToInputValue)}):this.$input.on("input.tt",this._compareQueryToInputValue),this.query=this.$input.val(),this.$overflowHelper=e(this.$input)}function e(b){return a("<span></span>").css({position:"absolute",left:"-9999px",visibility:"hidden",whiteSpace:"nowrap",fontFamily:b.css("font-family"),fontSize:b.css("font-size"),fontStyle:b.css("font-style"),fontVariant:b.css("font-variant"),fontWeight:b.css("font-weight"),wordSpacing:b.css("word-spacing"),letterSpacing:b.css("letter-spacing"),textIndent:b.css("text-indent"),textRendering:b.css("text-rendering"),textTransform:b.css("text-transform")}).insertAfter(b)}function f(a,b){return a=(a||"").replace(/^\s*/g,"").replace(/\s{2,}/g," "),b=(b||"").replace(/^\s*/g,"").replace(/\s{2,}/g," "),a===b}return c.mixin(b.prototype,d,{_handleFocus:function(){this.trigger("focused")},_handleBlur:function(){this.trigger("blured")},_handleSpecialKeyEvent:function(a){var b=this.specialKeyCodeMap[a.which||a.keyCode];b&&this.trigger(b+"Keyed",a)},_compareQueryToInputValue:function(){var a=this.getInputValue(),b=f(this.query,a),c=b?this.query.length!==a.length:!1;c?this.trigger("whitespaceChanged",{value:this.query}):b||this.trigger("queryChanged",{value:this.query=a})},destroy:function(){this.$hint.off(".tt"),this.$input.off(".tt"),this.$hint=this.$input=this.$overflowHelper=null},focus:function(){this.$input.focus()},blur:function(){this.$input.blur()},getQuery:function(){return this.query},setQuery:function(a){this.query=a},getInputValue:function(){return this.$input.val()},setInputValue:function(a,b){this.$input.val(a),!b&&this._compareQueryToInputValue()},getHintValue:function(){return this.$hint.val()},setHintValue:function(a){this.$hint.val(a)},getLanguageDirection:function(){return(this.$input.css("direction")||"ltr").toLowerCase()},isOverflow:function(){return this.$overflowHelper.text(this.getInputValue()),this.$overflowHelper.width()>this.$input.width()},isCursorAtEnd:function(){var a,b=this.$input.val().length,d=this.$input[0].selectionStart;return c.isNumber(d)?d===b:document.selection?(a=document.selection.createRange(),a.moveStart("character",-b),b===a.text.length):!0}}),b}(),k=function(){function b(b){c.bindAll(this),this.isOpen=!1,this.isEmpty=!0,this.isMouseOverDropdown=!1,this.$menu=a(b.menu).on("mouseenter.tt",this._handleMouseenter).on("mouseleave.tt",this._handleMouseleave).on("click.tt",".tt-suggestion",this._handleSelection).on("mouseover.tt",".tt-suggestion",this._handleMouseover)}function e(a){return a.data("suggestion")}var f={suggestionsList:'<span class="tt-suggestions"></span>'},g={suggestionsList:{display:"block"},suggestion:{whiteSpace:"nowrap",cursor:"pointer"},suggestionChild:{whiteSpace:"normal"}};return c.mixin(b.prototype,d,{_handleMouseenter:function(){this.isMouseOverDropdown=!0},_handleMouseleave:function(){this.isMouseOverDropdown=!1},_handleMouseover:function(b){var c=a(b.currentTarget);this._getSuggestions().removeClass("tt-is-under-cursor"),c.addClass("tt-is-under-cursor")},_handleSelection:function(b){var c=a(b.currentTarget);this.trigger("suggestionSelected",e(c))},_show:function(){this.$menu.css("display","block")},_hide:function(){this.$menu.hide()},_moveCursor:function(a){var b,c,d,f;if(this.isVisible()){if(b=this._getSuggestions(),c=b.filter(".tt-is-under-cursor"),c.removeClass("tt-is-under-cursor"),d=b.index(c)+a,d=(d+1)%(b.length+1)-1,-1===d)return this.trigger("cursorRemoved"),void 0;-1>d&&(d=b.length-1),f=b.eq(d).addClass("tt-is-under-cursor"),this._ensureVisibility(f),this.trigger("cursorMoved",e(f))}},_getSuggestions:function(){return this.$menu.find(".tt-suggestions > .tt-suggestion")},_ensureVisibility:function(a){var b=this.$menu.height()+parseInt(this.$menu.css("paddingTop"),10)+parseInt(this.$menu.css("paddingBottom"),10),c=this.$menu.scrollTop(),d=a.position().top,e=d+a.outerHeight(!0);0>d?this.$menu.scrollTop(c+d):e>b&&this.$menu.scrollTop(c+(e-b))},destroy:function(){this.$menu.off(".tt"),this.$menu=null},isVisible:function(){return this.isOpen&&!this.isEmpty},closeUnlessMouseIsOverDropdown:function(){this.isMouseOverDropdown||this.close()},close:function(){this.isOpen&&(this.isOpen=!1,this.isMouseOverDropdown=!1,this._hide(),this.$menu.find(".tt-suggestions > .tt-suggestion").removeClass("tt-is-under-cursor"),this.trigger("closed"))},open:function(){this.isOpen||(this.isOpen=!0,!this.isEmpty&&this._show(),this.trigger("opened"))},setLanguageDirection:function(a){var b={left:"0",right:"auto"},c={left:"auto",right:" 0"};"ltr"===a?this.$menu.css(b):this.$menu.css(c)},moveCursorUp:function(){this._moveCursor(-1)},moveCursorDown:function(){this._moveCursor(1)},getSuggestionUnderCursor:function(){var a=this._getSuggestions().filter(".tt-is-under-cursor").first();return a.length>0?e(a):null},getFirstSuggestion:function(){var a=this._getSuggestions().first();return a.length>0?e(a):null},renderSuggestions:function(b,d){var e,h,i,j,k,l="tt-dataset-"+b.name,m='<div class="tt-suggestion">%body</div>',n=this.$menu.find("."+l);0===n.length&&(h=a(f.suggestionsList).css(g.suggestionsList),n=a("<div></div>").addClass(l).append(b.header).append(h).append(b.footer).appendTo(this.$menu)),d.length>0?(this.isEmpty=!1,this.isOpen&&this._show(),i=document.createElement("div"),j=document.createDocumentFragment(),c.each(d,function(c,d){d.dataset=b.name,e=b.template(d.datum),i.innerHTML=m.replace("%body",e),k=a(i.firstChild).css(g.suggestion).data("suggestion",d),k.children().each(function(){a(this).css(g.suggestionChild)}),j.appendChild(k[0])}),n.show().find(".tt-suggestions").html(j)):this.clearSuggestions(b.name),this.trigger("suggestionsRendered")},clearSuggestions:function(a){var b=a?this.$menu.find(".tt-dataset-"+a):this.$menu.find('[class^="tt-dataset-"]'),c=b.find(".tt-suggestions");b.hide(),c.empty(),0===this._getSuggestions().length&&(this.isEmpty=!0,this._hide())}}),b}(),l=function(){function b(a){var b,d,f;c.bindAll(this),this.$node=e(a.input),this.datasets=a.datasets,this.dir=null,this.eventBus=a.eventBus,b=this.$node.find(".tt-dropdown-menu"),d=this.$node.find(".tt-query"),f=this.$node.find(".tt-hint"),this.dropdownView=new k({menu:b}).on("suggestionSelected",this._handleSelection).on("cursorMoved",this._clearHint).on("cursorMoved",this._setInputValueToSuggestionUnderCursor).on("cursorRemoved",this._setInputValueToQuery).on("cursorRemoved",this._updateHint).on("suggestionsRendered",this._updateHint).on("opened",this._updateHint).on("closed",this._clearHint).on("opened closed",this._propagateEvent),this.inputView=new j({input:d,hint:f}).on("focused",this._openDropdown).on("blured",this._closeDropdown).on("blured",this._setInputValueToQuery).on("enterKeyed tabKeyed",this._handleSelection).on("queryChanged",this._clearHint).on("queryChanged",this._clearSuggestions).on("queryChanged",this._getSuggestions).on("whitespaceChanged",this._updateHint).on("queryChanged whitespaceChanged",this._openDropdown).on("queryChanged whitespaceChanged",this._setLanguageDirection).on("escKeyed",this._closeDropdown).on("escKeyed",this._setInputValueToQuery).on("tabKeyed upKeyed downKeyed",this._managePreventDefault).on("upKeyed downKeyed",this._moveDropdownCursor).on("upKeyed downKeyed",this._openDropdown).on("tabKeyed leftKeyed rightKeyed",this._autocomplete)}function e(b){var c=a(g.wrapper),d=a(g.dropdown),e=a(b),f=a(g.hint);c=c.css(h.wrapper),d=d.css(h.dropdown),f.css(h.hint).css({backgroundAttachment:e.css("background-attachment"),backgroundClip:e.css("background-clip"),backgroundColor:e.css("background-color"),backgroundImage:e.css("background-image"),backgroundOrigin:e.css("background-origin"),backgroundPosition:e.css("background-position"),backgroundRepeat:e.css("background-repeat"),backgroundSize:e.css("background-size")}),e.data("ttAttrs",{dir:e.attr("dir"),autocomplete:e.attr("autocomplete"),spellcheck:e.attr("spellcheck"),style:e.attr("style")}),e.addClass("tt-query").attr({autocomplete:"off",spellcheck:!1}).css(h.query);try{!e.attr("dir")&&e.attr("dir","auto")}catch(i){}return e.wrap(c).parent().prepend(f).append(d)}function f(a){var b=a.find(".tt-query");c.each(b.data("ttAttrs"),function(a,d){c.isUndefined(d)?b.removeAttr(a):b.attr(a,d)}),b.detach().removeData("ttAttrs").removeClass("tt-query").insertAfter(a),a.remove()}var g={wrapper:'<span class="twitter-typeahead"></span>',hint:'<input class="tt-hint" type="text" autocomplete="off" spellcheck="off" disabled>',dropdown:'<span class="tt-dropdown-menu"></span>'},h={wrapper:{position:"relative",display:"inline-block"},hint:{position:"absolute",top:"0",left:"0",borderColor:"transparent",boxShadow:"none"},query:{position:"relative",verticalAlign:"top",backgroundColor:"transparent"},dropdown:{position:"absolute",top:"100%",left:"0",zIndex:"100",display:"none"}};return c.isMsie()&&c.mixin(h.query,{backgroundImage:"url(data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7)"}),c.isMsie()&&c.isMsie()<=7&&(c.mixin(h.wrapper,{display:"inline",zoom:"1"}),c.mixin(h.query,{marginTop:"-1px"})),c.mixin(b.prototype,d,{_managePreventDefault:function(a){var b,c,d=a.data,e=!1;switch(a.type){case"tabKeyed":b=this.inputView.getHintValue(),c=this.inputView.getInputValue(),e=b&&b!==c;break;case"upKeyed":case"downKeyed":e=!d.shiftKey&&!d.ctrlKey&&!d.metaKey}e&&d.preventDefault()},_setLanguageDirection:function(){var a=this.inputView.getLanguageDirection();a!==this.dir&&(this.dir=a,this.$node.css("direction",a),this.dropdownView.setLanguageDirection(a))},_updateHint:function(){var a,b,d,e,f,g=this.dropdownView.getFirstSuggestion(),h=g?g.value:null,i=this.dropdownView.isVisible(),j=this.inputView.isOverflow();h&&i&&!j&&(a=this.inputView.getInputValue(),b=a.replace(/\s{2,}/g," ").replace(/^\s+/g,""),d=c.escapeRegExChars(b),e=new RegExp("^(?:"+d+")(.*$)","i"),f=e.exec(h),this.inputView.setHintValue(a+(f?f[1]:"")))},_clearHint:function(){this.inputView.setHintValue("")},_clearSuggestions:function(){this.dropdownView.clearSuggestions()},_setInputValueToQuery:function(){this.inputView.setInputValue(this.inputView.getQuery())},_setInputValueToSuggestionUnderCursor:function(a){var b=a.data;this.inputView.setInputValue(b.value,!0)},_openDropdown:function(){this.dropdownView.open()},_closeDropdown:function(a){this.dropdownView["blured"===a.type?"closeUnlessMouseIsOverDropdown":"close"]()},_moveDropdownCursor:function(a){var b=a.data;b.shiftKey||b.ctrlKey||b.metaKey||this.dropdownView["upKeyed"===a.type?"moveCursorUp":"moveCursorDown"]()},_handleSelection:function(a){var b="suggestionSelected"===a.type,d=b?a.data:this.dropdownView.getSuggestionUnderCursor();d&&(this.inputView.setInputValue(d.value),b?this.inputView.focus():a.data.preventDefault(),b&&c.isMsie()?c.defer(this.dropdownView.close):this.dropdownView.close(),this.eventBus.trigger("selected",d.datum,d.dataset))},_getSuggestions:function(){var a=this,b=this.inputView.getQuery();c.isBlankString(b)||c.each(this.datasets,function(c,d){d.getSuggestions(b,function(c){b===a.inputView.getQuery()&&a.dropdownView.renderSuggestions(d,c)})})},_autocomplete:function(a){var b,c,d,e,f;("rightKeyed"!==a.type&&"leftKeyed"!==a.type||(b=this.inputView.isCursorAtEnd(),c="ltr"===this.inputView.getLanguageDirection()?"leftKeyed"===a.type:"rightKeyed"===a.type,b&&!c))&&(d=this.inputView.getQuery(),e=this.inputView.getHintValue(),""!==e&&d!==e&&(f=this.dropdownView.getFirstSuggestion(),this.inputView.setInputValue(f.value),this.eventBus.trigger("autocompleted",f.datum,f.dataset)))},_propagateEvent:function(a){this.eventBus.trigger(a.type)},destroy:function(){this.inputView.destroy(),this.dropdownView.destroy(),f(this.$node),this.$node=null},setQuery:function(a){this.inputView.setQuery(a),this.inputView.setInputValue(a),this._clearHint(),this._clearSuggestions(),this._getSuggestions()}}),b}();!function(){var b,d={},f="ttView";b={initialize:function(b){function g(){var b,d=a(this),g=new e({el:d});b=c.map(h,function(a){return a.initialize()}),d.data(f,new l({input:d,eventBus:g=new e({el:d}),datasets:h})),a.when.apply(a,b).always(function(){c.defer(function(){g.trigger("initialized")})})}var h;return b=c.isArray(b)?b:[b],0===b.length&&a.error("no datasets provided"),h=c.map(b,function(a){var b=d[a.name]?d[a.name]:new i(a);return a.name&&(d[a.name]=b),b}),this.each(g)},destroy:function(){function b(){var b=a(this),c=b.data(f);c&&(c.destroy(),b.removeData(f))}return this.each(b)},setQuery:function(b){function c(){var c=a(this).data(f);c&&c.setQuery(b)}return this.each(c)}},jQuery.fn.typeahead=function(a){return b[a]?b[a].apply(this,[].slice.call(arguments,1)):b.initialize.apply(this,arguments)}}()}(window.jQuery);
@@ -0,0 +1,38 @@
1
+ var MONTH_NAMES = [ "January", "February", "March", "April", "May", "June",
2
+ "July", "August", "September", "October", "November", "December" ];
3
+
4
+ var Event = Backbone.Model.extend({
5
+ initialize: function() {
6
+ this.initDate();
7
+ this.on('change:date', this.initDate);
8
+ },
9
+ dateObject: null,
10
+ initDate: function() {
11
+ var date = this.get('date');
12
+ if (!date) return false;
13
+ this.dateObject = new Date(this.get('date') * 1000);
14
+ },
15
+ formattedDate: function() {
16
+ var date = this.dateObject,
17
+ today = new Date(),
18
+ yesterday = new Date(today),
19
+ dateString = '';
20
+ yesterday.setDate(today.getDate() - 1);
21
+ if (date.toLocaleDateString() === today.toLocaleDateString()) {
22
+ dateString += "Today, ";
23
+ } else if (date.toLocaleDateString() === yesterday.toLocaleDateString()) {
24
+ dateString += "Yesterday, ";
25
+ }
26
+ dateString += MONTH_NAMES[date.getMonth()] + ' ' + date.getDate() + ', ' + date.getFullYear();
27
+ return dateString;
28
+ },
29
+ localeDateString: function() {
30
+ return this.dateObject.toLocaleDateString();
31
+ },
32
+ localeTimeString: function() {
33
+ return this.dateObject.toLocaleTimeString();
34
+ },
35
+ formattedDateTime: function() {
36
+ return this.formattedDate() + ' ' + this.localeTimeString();
37
+ }
38
+ });
@@ -0,0 +1,38 @@
1
+ var EventRouter = Backbone.Router.extend({
2
+ initialize: function() {
3
+ var events = new Events();
4
+
5
+ this.searchView = new SearchView({ collection: events });
6
+ new EventsView({ collection: events, searchView: this.searchView });
7
+ },
8
+ routes: {
9
+ '(:source)(/:uid)(/:from)(/:to)(/:value)(/:url)': 'search'
10
+ },
11
+ _decodeParam: function(param) {
12
+ return param ? decodeURIComponent(param) : '';
13
+ },
14
+ search: function(source, uid, from, to, value, url) {
15
+ var query = {
16
+ source: this._decodeParam(source) || this.searchView.EMPTY_QUERY_SYMBOL,
17
+ uid: this._decodeParam(uid) || this.searchView.EMPTY_QUERY_SYMBOL,
18
+ from: from || this.searchView.EMPTY_QUERY_SYMBOL,
19
+ to: to || this.searchView.EMPTY_QUERY_SYMBOL,
20
+ value: this._decodeParam(value) || this.searchView.EMPTY_QUERY_SYMBOL,
21
+ url: this._decodeParam(url) || this.searchView.EMPTY_QUERY_SYMBOL
22
+ }
23
+ _.each(query, function(value, key) {
24
+ if (value === this.searchView.EMPTY_QUERY_SYMBOL) {
25
+ query[key] = value = '';
26
+ }
27
+ if ( (key === 'to' || key === 'from')) {
28
+ if (!value) {
29
+ delete query[key];
30
+ return;
31
+ }
32
+ value = this.searchView._toISOString(new Date(value * 1000));
33
+ }
34
+ $('input[name="' + key + '"]').val(value);
35
+ }, this);
36
+ this.searchView.search(query);
37
+ }
38
+ });
@@ -0,0 +1,6 @@
1
+ var initialize = function() {
2
+ new EventRouter();
3
+ Backbone.history.start({ pushState: true });
4
+ }
5
+
6
+ $(initialize);
@@ -0,0 +1,22 @@
1
+ // Assign a collection of events for a single day
2
+ var DayView = Backbone.View.extend({
3
+ tagName: 'div',
4
+ className: 'day',
5
+ template: _.template($('#day-template').html()),
6
+ initialize: function(opts) {
7
+ _.extend(this, opts);
8
+ this.render();
9
+ this.listenTo(this.collection, 'add', this.addEvent);
10
+ },
11
+ date: null,
12
+ addEvent: function(event) {
13
+ if (event.localeDateString() !== this.date) return;
14
+ var eventView = new EventView({model: event});
15
+ this.$el.find('.day-events').prepend(eventView.render().el);
16
+ },
17
+ render: function() {
18
+ this.$el.html(this.template({ formattedDate: this.formattedDate }));
19
+ this.collection.each(this.addEvent, this);
20
+ return this;
21
+ }
22
+ });
@@ -0,0 +1,14 @@
1
+ var EventView = Backbone.View.extend({
2
+ tagName: 'li',
3
+ template: _.template($('#event-template').html()),
4
+ formattedModel: function() {
5
+ var data = this.model.toJSON();
6
+ data.time = this.model.localeTimeString();
7
+ data.dateString = this.model.formattedDateTime();
8
+ return data;
9
+ },
10
+ render: function() {
11
+ this.$el.html(this.template(this.formattedModel()));
12
+ return this;
13
+ }
14
+ });
@@ -0,0 +1,46 @@
1
+ var EventsView = Backbone.View.extend({
2
+ el: '#events',
3
+ dayViews: null,
4
+ initialize: function(opts) {
5
+ if (!this.collection) {
6
+ this.collection = new Events();
7
+ }
8
+ this.searchView = opts.searchView || new SearchView({ collection: this.collection });
9
+ this.dayViews = [];
10
+
11
+ this.listenTo(this.collection, 'add', this.addEvent);
12
+ this.listenTo(this.collection, 'reset', this.render);
13
+ },
14
+ events: {
15
+ 'click .source': 'searchSource',
16
+ 'click .user' : 'searchUser'
17
+ },
18
+ addDayView: function(event) {
19
+ var date = event.localeDateString(),
20
+ formattedDate = event.formattedDate();
21
+ if (_.contains(_.pluck(this.dayViews, 'date'), date)) return;
22
+ var dayView = new DayView({
23
+ date: date,
24
+ formattedDate: formattedDate,
25
+ collection: this.collection
26
+ });
27
+ this.dayViews.push(dayView);
28
+ this.$el.prepend(dayView.render().el);
29
+ },
30
+ render: function() {
31
+ _.each(this.dayViews, function(dayView) {
32
+ dayView.remove();
33
+ });
34
+ this.dayViews = [];
35
+ this.collection.each(this.addDayView, this);
36
+ return this;
37
+ },
38
+ searchSource: function(e) {
39
+ $('input[name=source]').val($(e.target).text());
40
+ this.searchView.submit();
41
+ },
42
+ searchUser: function(e) {
43
+ $('input[name=uid]').val($(e.target).text());
44
+ this.searchView.submit();
45
+ }
46
+ });
@@ -0,0 +1,130 @@
1
+ var SearchView = Backbone.View.extend({
2
+ EMPTY_QUERY_SYMBOL: 'Ø',
3
+ el: '#search-form',
4
+ events: {
5
+ 'submit' : 'submit',
6
+ 'change #autorefresh': 'toggleAutoRefresh'
7
+ },
8
+ initialize: function() {
9
+ this.listenTo(this.collection, 'search', function() {
10
+ this.$el.find('input[type="submit"]')
11
+ .val('Searching...')
12
+ .addClass('searching');
13
+ });
14
+ this.listenTo(this.collection, 'searched', function() {
15
+ this.$el.find('input[type="submit"]')
16
+ .val('Search')
17
+ .removeClass('searching');
18
+ });
19
+ this.listenTo(this.collection, 'reset polled', function() {
20
+ $('#update-time').text(new Date().toLocaleTimeString());
21
+ });
22
+ this.listenTo(this.collection, 'startedPolling', function() {
23
+ $('#update-notice').addClass('visible');
24
+ });
25
+ this.listenTo(this.collection, 'stoppedPolling', function() {
26
+ $('#update-notice').removeClass('visible');
27
+ });
28
+ this.initTypeahead();
29
+ if (this.browserSupportsDateInputs()) {
30
+ var today = new Date();
31
+ $('#js-datepickers').remove();
32
+ $('input[type="date"]').attr('max', this._toISOString(today));
33
+ } else {
34
+ this.initDatepickers();
35
+ $('#native-datepickers').remove();
36
+ }
37
+ },
38
+ submit: function(e) {
39
+ e && e.preventDefault();
40
+ var fields = this.$el.serializeArray();
41
+ var query = {};
42
+
43
+ _.each(fields, function(field) {
44
+ if (field.name === "from" || field.name === "to") {
45
+ if (!field.value) return;
46
+ query[field.name] = new Date(field.value).getTime()/1000;
47
+ } else {
48
+ query[field.name] = field.value;
49
+ }
50
+ });
51
+ this.setRoute(query);
52
+ this.search(query);
53
+ },
54
+ search: function(query) {
55
+ return this.collection.search(query);
56
+ },
57
+ _encodeParam: function(param) {
58
+ return param ? encodeURIComponent(param) : this.EMPTY_QUERY_SYMBOL;
59
+ },
60
+ setRoute: function(query) {
61
+ var routeString = this._encodeParam(query.source)
62
+ + '/' + this._encodeParam(query.uid)
63
+ + '/' + this._encodeParam(query.from)
64
+ + '/' + this._encodeParam(query.to)
65
+ + '/' + this._encodeParam(query.value)
66
+ + '/' + this._encodeParam(query.url);
67
+ Backbone.history.navigate(routeString);
68
+ },
69
+ toggleAutoRefresh: function() {
70
+ if ($('#autorefresh').attr('checked')) {
71
+ this.collection.poll();
72
+ this.collection.startPolling();
73
+ } else {
74
+ this.collection.stopPolling();
75
+ }
76
+ },
77
+ initTypeahead: function() {
78
+ this.$el.find('input[name="source"]').typeahead({
79
+ name: 'sources',
80
+ prefetch: '/api/v1/sources'
81
+ });
82
+ this.$el.find('input[name="uid"]').typeahead({
83
+ name: 'users',
84
+ prefetch: '/api/v1/users'
85
+ });
86
+ },
87
+ browserSupportsDateInputs: function() {
88
+ var dateInput = $('input[type="date"]')[0];
89
+
90
+ dateInput.value = "test";
91
+ return dateInput.value != "test";
92
+ },
93
+ initDatepickers: function() {
94
+ this.initDatepicker('#from-text', '#from-date');
95
+ this.initDatepicker('#to-text', '#to-date');
96
+ },
97
+ initDatepicker: function(textFieldSelector, dateFieldSelector) {
98
+ var pickerOpts = {
99
+ format: 'mmmm d, yyyy',
100
+ max: new Date()
101
+ },
102
+ $dateField = $(dateFieldSelector).pickadate(pickerOpts),
103
+ picker = $dateField.pickadate('picker'),
104
+ $textField = $(textFieldSelector);
105
+ $textField.on({
106
+ change: function() {
107
+ var parsedDate = Date.parse(this.value);
108
+ if (parsedDate) {
109
+ picker.set('select', [parsedDate.getFullYear(), parsedDate.getMonth(), parsedDate.getDate()]);
110
+ }
111
+ else {
112
+ console.error('Invalid date')
113
+ }
114
+ },
115
+ focus: function() {
116
+ // picker.open(false);
117
+ },
118
+ blur: function() {
119
+ picker.close();
120
+ }
121
+ });
122
+ picker.on('set', function() {
123
+ $textField.val(this.get('value'))
124
+ });
125
+ return picker;
126
+ },
127
+ _toISOString: function(dateObject) {
128
+ return dateObject.toISOString().split('T')[0];
129
+ }
130
+ });
metadata ADDED
@@ -0,0 +1,220 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: squab
3
+ version: !ruby/object:Gem::Version
4
+ hash: 31
5
+ prerelease:
6
+ segments:
7
+ - 1
8
+ - 3
9
+ - 2
10
+ version: 1.3.2
11
+ platform: ruby
12
+ authors:
13
+ - Grier Johnson
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2013-12-02 00:00:00 -05:00
19
+ default_executable: squab
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ name: sinatra
23
+ prerelease: false
24
+ requirement: &id001 !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ hash: 29
30
+ segments:
31
+ - 1
32
+ - 3
33
+ - 3
34
+ version: 1.3.3
35
+ type: :runtime
36
+ version_requirements: *id001
37
+ - !ruby/object:Gem::Dependency
38
+ name: sinatra-contrib
39
+ prerelease: false
40
+ requirement: &id002 !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ">="
44
+ - !ruby/object:Gem::Version
45
+ hash: 29
46
+ segments:
47
+ - 1
48
+ - 3
49
+ - 3
50
+ version: 1.3.3
51
+ type: :runtime
52
+ version_requirements: *id002
53
+ - !ruby/object:Gem::Dependency
54
+ name: rack
55
+ prerelease: false
56
+ requirement: &id003 !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ hash: 5
62
+ segments:
63
+ - 1
64
+ - 4
65
+ - 1
66
+ version: 1.4.1
67
+ type: :runtime
68
+ version_requirements: *id003
69
+ - !ruby/object:Gem::Dependency
70
+ name: sequel
71
+ prerelease: false
72
+ requirement: &id004 !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ">="
76
+ - !ruby/object:Gem::Version
77
+ hash: 167
78
+ segments:
79
+ - 3
80
+ - 40
81
+ - 0
82
+ version: 3.40.0
83
+ type: :runtime
84
+ version_requirements: *id004
85
+ - !ruby/object:Gem::Dependency
86
+ name: sqlite3
87
+ prerelease: false
88
+ requirement: &id005 !ruby/object:Gem::Requirement
89
+ none: false
90
+ requirements:
91
+ - - ">="
92
+ - !ruby/object:Gem::Version
93
+ hash: 23
94
+ segments:
95
+ - 1
96
+ - 3
97
+ - 6
98
+ version: 1.3.6
99
+ type: :runtime
100
+ version_requirements: *id005
101
+ - !ruby/object:Gem::Dependency
102
+ name: json
103
+ prerelease: false
104
+ requirement: &id006 !ruby/object:Gem::Requirement
105
+ none: false
106
+ requirements:
107
+ - - ">="
108
+ - !ruby/object:Gem::Version
109
+ hash: 3
110
+ segments:
111
+ - 1
112
+ - 7
113
+ - 4
114
+ version: 1.7.4
115
+ type: :runtime
116
+ version_requirements: *id006
117
+ - !ruby/object:Gem::Dependency
118
+ name: thin
119
+ prerelease: false
120
+ requirement: &id007 !ruby/object:Gem::Requirement
121
+ none: false
122
+ requirements:
123
+ - - ">="
124
+ - !ruby/object:Gem::Version
125
+ hash: 3
126
+ segments:
127
+ - 1
128
+ - 5
129
+ - 0
130
+ version: 1.5.0
131
+ type: :runtime
132
+ version_requirements: *id007
133
+ description: A rest API fronted database and web based front-end around a event database. Useful for visualizing and tracking events in a time-stream style format.
134
+ email:
135
+ - github@squareup.com
136
+ executables:
137
+ - squab
138
+ extensions: []
139
+
140
+ extra_rdoc_files:
141
+ - LICENSE.md
142
+ files:
143
+ - lib/squab/web.rb
144
+ - lib/squab/events.rb
145
+ - lib/squab/db.rb
146
+ - lib/squab.rb
147
+ - bin/squab
148
+ - public/css/pickadate/default.css
149
+ - public/css/pickadate/default.date.css
150
+ - public/css/bootstrap.min.css
151
+ - public/css/bootstrap-responsive.min.css
152
+ - public/css/docs.css
153
+ - public/css/anytime.css
154
+ - public/css/normalize.css
155
+ - public/css/squab.css
156
+ - public/css/bootstrap-responsive.css
157
+ - public/css/bootstrap.css
158
+ - public/css/prettify.css
159
+ - public/js/router.js
160
+ - public/js/model/event.js
161
+ - public/js/squab.js
162
+ - public/js/view/day_view.js
163
+ - public/js/view/event_view.js
164
+ - public/js/view/events_view.js
165
+ - public/js/view/search_view.js
166
+ - public/js/lib/pickadate/picker.js
167
+ - public/js/lib/pickadate/legacy.js
168
+ - public/js/lib/pickadate/picker.date.js
169
+ - public/js/lib/backbone-min.js
170
+ - public/js/lib/jquery-latest.js
171
+ - public/js/lib/datejs/date-en-US.js
172
+ - public/js/lib/datejs/core.js
173
+ - public/js/lib/lodash.min.js
174
+ - public/js/lib/typeahead.min.js
175
+ - public/js/collection/events.js
176
+ - public/api.html
177
+ - public/img/glyphicons-halflings-white.png
178
+ - public/img/glyphicons-halflings.png
179
+ - public/events.html
180
+ - defaults.yaml
181
+ - README.md
182
+ - LICENSE.md
183
+ has_rdoc: true
184
+ homepage: http://github.com/square/squab
185
+ licenses:
186
+ - Apache 2.0
187
+ post_install_message:
188
+ rdoc_options:
189
+ - --charset=UTF-8
190
+ require_paths:
191
+ - lib
192
+ required_ruby_version: !ruby/object:Gem::Requirement
193
+ none: false
194
+ requirements:
195
+ - - ">="
196
+ - !ruby/object:Gem::Version
197
+ hash: 3
198
+ segments:
199
+ - 0
200
+ version: "0"
201
+ required_rubygems_version: !ruby/object:Gem::Requirement
202
+ none: false
203
+ requirements:
204
+ - - ">="
205
+ - !ruby/object:Gem::Version
206
+ hash: 23
207
+ segments:
208
+ - 1
209
+ - 3
210
+ - 6
211
+ version: 1.3.6
212
+ requirements: []
213
+
214
+ rubyforge_project:
215
+ rubygems_version: 1.6.2
216
+ signing_key:
217
+ specification_version: 3
218
+ summary: A simple event stream database and visualizer
219
+ test_files: []
220
+