├── .gitignore ├── db └── views │ ├── tokens │ ├── reduce.js │ └── map.js │ ├── by_type │ └── map.js │ ├── by_complete │ └── map.js │ ├── asleep │ └── map.js │ ├── review │ └── map.js │ ├── search │ └── map.js │ └── by_token │ └── map.js ├── hooks ├── before_build.rb └── after_push.rb ├── favicon.ico ├── images ├── grid.png ├── action.icns └── loader.gif ├── .couchapprc ├── sass ├── print.scss ├── partials │ └── _base.scss ├── ie.scss └── screen.scss ├── config.js ├── rewrites.js ├── Jimfile ├── LICENSE ├── README.md ├── css ├── print.css ├── ie.css └── screen.css ├── js ├── vendor │ ├── jquery.hotkeys-0.8.js │ ├── sammy.nested_params-0.6.1.js │ ├── jquery.color.js │ ├── sha1.js │ ├── sammy.json-0.6.1.js │ ├── sammy.mustache-0.6.1.js │ ├── sammy.storage-0.6.1.js │ ├── jquery.couch-0.11.js │ └── strftime.js ├── sammy.couch.js ├── models │ └── action.js └── app.js └── index.html /.gitignore: -------------------------------------------------------------------------------- 1 | .sass-cache 2 | .DS_Store -------------------------------------------------------------------------------- /db/views/tokens/reduce.js: -------------------------------------------------------------------------------- 1 | _count 2 | -------------------------------------------------------------------------------- /hooks/before_build.rb: -------------------------------------------------------------------------------- 1 | plugin 'jim' 2 | plugin 'compass' 3 | -------------------------------------------------------------------------------- /favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quirkey/action/HEAD/favicon.ico -------------------------------------------------------------------------------- /hooks/after_push.rb: -------------------------------------------------------------------------------- 1 | `growlnotify -m "Successful Push to #{app_url}"` 2 | -------------------------------------------------------------------------------- /images/grid.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quirkey/action/HEAD/images/grid.png -------------------------------------------------------------------------------- /images/action.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quirkey/action/HEAD/images/action.icns -------------------------------------------------------------------------------- /images/loader.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quirkey/action/HEAD/images/loader.gif -------------------------------------------------------------------------------- /db/views/by_type/map.js: -------------------------------------------------------------------------------- 1 | function(doc) { 2 | emit([doc.type, doc.updated_at], doc._id); 3 | } 4 | -------------------------------------------------------------------------------- /db/views/by_complete/map.js: -------------------------------------------------------------------------------- 1 | function(doc) { 2 | if (doc.type == 'action' && !doc.sleeping) { 3 | emit([doc.completed_at, doc.updated_at], doc._id); 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /db/views/asleep/map.js: -------------------------------------------------------------------------------- 1 | function(doc) { 2 | if (doc.type == 'action' && doc.sleeping) { 3 | emit([doc.slept_at, doc.slept_count, doc.updated_at], doc._id); 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /db/views/review/map.js: -------------------------------------------------------------------------------- 1 | function(doc) { 2 | if (doc.type == 'action' && doc.sleeping) { 3 | emit([doc.slept_at, doc.slept_count, doc.updated_at], doc._id); 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /.couchapprc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "default": { 4 | "db": "http://admin:admin@localhost:5984/action" 5 | }, 6 | "production": { 7 | "db": "http://admin:admin@c.ixxr.net/action" 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /db/views/search/map.js: -------------------------------------------------------------------------------- 1 | function(doc) { 2 | if (doc.type && doc.type == 'action' && doc.content) { 3 | var words = doc.content.split(/\s+/), i = 0; 4 | for (;i", "/": "?", "\\": "|" 32 | } 33 | }; 34 | 35 | function keyHandler( handleObj ) { 36 | // Only care when a possible input has been specified 37 | if ( typeof handleObj.data !== "string" ) { 38 | return; 39 | } 40 | 41 | var origHandler = handleObj.handler, 42 | keys = handleObj.data.toLowerCase().split(" "); 43 | 44 | handleObj.handler = function( event ) { 45 | // Don't fire in text-accepting inputs that we didn't directly bind to 46 | if ( this !== event.target && (/textarea|select/i.test( event.target.nodeName ) || 47 | event.target.type === "text") ) { 48 | return; 49 | } 50 | 51 | // Keypress represents characters, not special keys 52 | var special = event.type !== "keypress" && jQuery.hotkeys.specialKeys[ event.which ], 53 | character = String.fromCharCode( event.which ).toLowerCase(), 54 | key, modif = "", possible = {}; 55 | 56 | // check combinations (alt|ctrl|shift+anything) 57 | if ( event.altKey && special !== "alt" ) { 58 | modif += "alt+"; 59 | } 60 | 61 | if ( event.ctrlKey && special !== "ctrl" ) { 62 | modif += "ctrl+"; 63 | } 64 | 65 | // TODO: Need to make sure this works consistently across platforms 66 | if ( event.metaKey && !event.ctrlKey && special !== "meta" ) { 67 | modif += "meta+"; 68 | } 69 | 70 | if ( event.shiftKey && special !== "shift" ) { 71 | modif += "shift+"; 72 | } 73 | 74 | if ( special ) { 75 | possible[ modif + special ] = true; 76 | 77 | } else { 78 | possible[ modif + character ] = true; 79 | possible[ modif + jQuery.hotkeys.shiftNums[ character ] ] = true; 80 | 81 | // "$" can be triggered as "Shift+4" or "Shift+$" or just "$" 82 | if ( modif === "shift+" ) { 83 | possible[ jQuery.hotkeys.shiftNums[ character ] ] = true; 84 | } 85 | } 86 | 87 | for ( var i = 0, l = keys.length; i < l; i++ ) { 88 | if ( possible[ keys[i] ] ) { 89 | return origHandler.apply( this, arguments ); 90 | } 91 | } 92 | }; 93 | } 94 | 95 | jQuery.each([ "keydown", "keyup", "keypress" ], function() { 96 | jQuery.event.special[ this ] = { add: keyHandler }; 97 | }); 98 | 99 | })( jQuery ); 100 | -------------------------------------------------------------------------------- /css/ie.css: -------------------------------------------------------------------------------- 1 | /* line 7, ../sass/ie.scss */ 2 | body.bp { 3 | text-align: center; 4 | } 5 | /* line 48, ../../../.rvm/gems/ruby-1.8.7-p302@dev/gems/compass-0.10.6/frameworks/blueprint/stylesheets/blueprint/_ie.scss */ 6 | * html body.bp legend { 7 | margin: 0px -8px 16px 0; 8 | padding: 0; 9 | } 10 | /* line 52, ../../../.rvm/gems/ruby-1.8.7-p302@dev/gems/compass-0.10.6/frameworks/blueprint/stylesheets/blueprint/_ie.scss */ 11 | html > body.bp p code { 12 | *white-space: normal; 13 | } 14 | /* line 67, ../../../.rvm/gems/ruby-1.8.7-p302@dev/gems/compass-0.10.6/frameworks/blueprint/stylesheets/blueprint/_ie.scss */ 15 | body.bp .container { 16 | text-align: left; 17 | } 18 | /* line 69, ../../../.rvm/gems/ruby-1.8.7-p302@dev/gems/compass-0.10.6/frameworks/blueprint/stylesheets/blueprint/_ie.scss */ 19 | body.bp sup { 20 | vertical-align: text-top; 21 | } 22 | /* line 71, ../../../.rvm/gems/ruby-1.8.7-p302@dev/gems/compass-0.10.6/frameworks/blueprint/stylesheets/blueprint/_ie.scss */ 23 | body.bp sub { 24 | vertical-align: text-bottom; 25 | } 26 | /* line 73, ../../../.rvm/gems/ruby-1.8.7-p302@dev/gems/compass-0.10.6/frameworks/blueprint/stylesheets/blueprint/_ie.scss */ 27 | body.bp hr { 28 | margin: -8px auto 11px; 29 | } 30 | /* line 75, ../../../.rvm/gems/ruby-1.8.7-p302@dev/gems/compass-0.10.6/frameworks/blueprint/stylesheets/blueprint/_ie.scss */ 31 | body.bp img { 32 | -ms-interpolation-mode: bicubic; 33 | } 34 | /* line 77, ../../../.rvm/gems/ruby-1.8.7-p302@dev/gems/compass-0.10.6/frameworks/blueprint/stylesheets/blueprint/_ie.scss */ 35 | body.bp fieldset { 36 | padding-top: 0; 37 | } 38 | /* line 79, ../../../.rvm/gems/ruby-1.8.7-p302@dev/gems/compass-0.10.6/frameworks/blueprint/stylesheets/blueprint/_ie.scss */ 39 | body.bp textarea { 40 | overflow: auto; 41 | } 42 | /* line 82, ../../../.rvm/gems/ruby-1.8.7-p302@dev/gems/compass-0.10.6/frameworks/blueprint/stylesheets/blueprint/_ie.scss */ 43 | body.bp input.text { 44 | margin: 0.5em 0; 45 | background-color: white; 46 | border: 1px solid #bbbbbb; 47 | } 48 | /* line 86, ../../../.rvm/gems/ruby-1.8.7-p302@dev/gems/compass-0.10.6/frameworks/blueprint/stylesheets/blueprint/_ie.scss */ 49 | body.bp input.text:focus { 50 | border: 1px solid #666666; 51 | } 52 | /* line 88, ../../../.rvm/gems/ruby-1.8.7-p302@dev/gems/compass-0.10.6/frameworks/blueprint/stylesheets/blueprint/_ie.scss */ 53 | body.bp input.title { 54 | margin: 0.5em 0; 55 | background-color: white; 56 | border: 1px solid #bbbbbb; 57 | } 58 | /* line 92, ../../../.rvm/gems/ruby-1.8.7-p302@dev/gems/compass-0.10.6/frameworks/blueprint/stylesheets/blueprint/_ie.scss */ 59 | body.bp input.title:focus { 60 | border: 1px solid #666666; 61 | } 62 | /* line 94, ../../../.rvm/gems/ruby-1.8.7-p302@dev/gems/compass-0.10.6/frameworks/blueprint/stylesheets/blueprint/_ie.scss */ 63 | body.bp input.checkbox { 64 | position: relative; 65 | top: 0.25em; 66 | } 67 | /* line 97, ../../../.rvm/gems/ruby-1.8.7-p302@dev/gems/compass-0.10.6/frameworks/blueprint/stylesheets/blueprint/_ie.scss */ 68 | body.bp input.radio { 69 | position: relative; 70 | top: 0.25em; 71 | } 72 | /* line 100, ../../../.rvm/gems/ruby-1.8.7-p302@dev/gems/compass-0.10.6/frameworks/blueprint/stylesheets/blueprint/_ie.scss */ 73 | body.bp input.button { 74 | position: relative; 75 | top: 0.25em; 76 | } 77 | /* line 103, ../../../.rvm/gems/ruby-1.8.7-p302@dev/gems/compass-0.10.6/frameworks/blueprint/stylesheets/blueprint/_ie.scss */ 78 | body.bp textarea { 79 | margin: 0.5em 0; 80 | } 81 | /* line 105, ../../../.rvm/gems/ruby-1.8.7-p302@dev/gems/compass-0.10.6/frameworks/blueprint/stylesheets/blueprint/_ie.scss */ 82 | body.bp select { 83 | margin: 0.5em 0; 84 | } 85 | /* line 107, ../../../.rvm/gems/ruby-1.8.7-p302@dev/gems/compass-0.10.6/frameworks/blueprint/stylesheets/blueprint/_ie.scss */ 86 | body.bp button { 87 | position: relative; 88 | top: 0.25em; 89 | } 90 | -------------------------------------------------------------------------------- /js/sammy.couch.js: -------------------------------------------------------------------------------- 1 | (function($, Sammy) { 2 | 3 | Sammy = Sammy || {}; 4 | 5 | Sammy.Couch = function(app, dbname) { 6 | 7 | // set the default dbname form the URL 8 | dbname = dbname || window.location.pathname.split('/')[1]; 9 | 10 | var db = function() { 11 | if (!dbname) { 12 | throw("Please define a db to load from"); 13 | } 14 | return this._db = this._db || $.couch.db(dbname); 15 | }; 16 | 17 | var timestamp = function() { 18 | return new Date().getTime(); 19 | }; 20 | 21 | this.db = db(); 22 | 23 | this.createModel = function(type, options) { 24 | options = $.extend({ 25 | defaultDocument: function() { 26 | return { 27 | type: type, 28 | updated_at: timestamp() 29 | }; 30 | }, 31 | errorHandler: function(response) { 32 | app.trigger('error.' + type, {error: response}); 33 | } 34 | }, options || {}); 35 | 36 | var mergeCallbacks = function(callback) { 37 | var base = {error: options.errorHandler}; 38 | if ($.isFunction(callback)) { 39 | return $.extend(base, {success: callback}); 40 | } else { 41 | return $.extend(base, callback || {}); 42 | } 43 | }; 44 | 45 | var mergeDefaultDocument = function(doc) { 46 | return $.extend({}, options.defaultDocument(), doc); 47 | }; 48 | 49 | var model = { 50 | timestamp: timestamp, 51 | 52 | extend: function(obj) { 53 | $.extend(model, obj); 54 | }, 55 | 56 | all: function(callback) { 57 | return app.db.allDocs($.extend(mergeCallbacks(callback), { 58 | include_docs: true 59 | })); 60 | }, 61 | 62 | get: function(id, options, callback) { 63 | if ($.isFunction(options)) { 64 | callback = options; 65 | options = {}; 66 | } 67 | return app.db.openDoc(id, $.extend(mergeCallbacks(callback), options)); 68 | }, 69 | 70 | create: function(doc, callback) { 71 | return model.save(mergeDefaultDocument(doc), callback); 72 | }, 73 | 74 | save: function(doc, callback) { 75 | if ($.isFunction(model.beforeSave)) { 76 | doc = model.beforeSave(doc); 77 | } 78 | return app.db.saveDoc(doc, mergeCallbacks(callback)); 79 | }, 80 | 81 | update: function(id, doc, callback) { 82 | model.get(id, function(original_doc) { 83 | doc = $.extend(original_doc, doc); 84 | model.save(doc, callback); 85 | }); 86 | }, 87 | 88 | view: function(name, options, callback) { 89 | if ($.isFunction(options)) { 90 | callback = options; 91 | options = {}; 92 | } 93 | return app.db.view([dbname, name].join('/'), $.extend(mergeCallbacks(callback), options)); 94 | }, 95 | 96 | viewDocs: function(name, options, callback) { 97 | if ($.isFunction(options)) { 98 | callback = options; 99 | options = {}; 100 | } 101 | var wrapped_callback = function(json) { 102 | var docs = []; 103 | for (var i=0;i 2 | 3 | 4 | action! 5 | 6 | 7 | 8 | 11 | 12 | 13 | 14 |
15 | 25 |
26 | 27 |
28 | 29 |
30 | 73 | 74 | 75 | 76 | -------------------------------------------------------------------------------- /js/vendor/sammy.nested_params-0.6.1.js: -------------------------------------------------------------------------------- 1 | (function($) { 2 | 3 | Sammy = Sammy || {}; 4 | 5 | function parseValue(value) { 6 | value = unescape(value); 7 | if (value === "true") { 8 | return true; 9 | } else if (value === "false") { 10 | return false; 11 | } else { 12 | return value; 13 | } 14 | }; 15 | 16 | function parseNestedParam(params, field_name, field_value) { 17 | var match, name, rest; 18 | 19 | if (field_name.match(/^[^\[]+$/)) { 20 | // basic value 21 | params[field_name] = parseValue(field_value); 22 | } else if (match = field_name.match(/^([^\[]+)\[\](.*)$/)) { 23 | // array 24 | name = match[1]; 25 | rest = match[2]; 26 | 27 | if(params[name] && !$.isArray(params[name])) { throw('400 Bad Request'); } 28 | 29 | if (rest) { 30 | // array is not at the end of the parameter string 31 | match = rest.match(/^\[([^\]]+)\](.*)$/); 32 | if(!match) { throw('400 Bad Request'); } 33 | 34 | if (params[name]) { 35 | if(params[name][params[name].length - 1][match[1]]) { 36 | params[name].push(parseNestedParam({}, match[1] + match[2], field_value)); 37 | } else { 38 | $.extend(true, params[name][params[name].length - 1], parseNestedParam({}, match[1] + match[2], field_value)); 39 | } 40 | } else { 41 | params[name] = [parseNestedParam({}, match[1] + match[2], field_value)]; 42 | } 43 | } else { 44 | // array is at the end of the parameter string 45 | if (params[name]) { 46 | params[name].push(parseValue(field_value)); 47 | } else { 48 | params[name] = [parseValue(field_value)]; 49 | } 50 | } 51 | } else if (match = field_name.match(/^([^\[]+)\[([^\[]+)\](.*)$/)) { 52 | // hash 53 | name = match[1]; 54 | rest = match[2] + match[3]; 55 | 56 | if (params[name] && $.isArray(params[name])) { throw('400 Bad Request'); } 57 | 58 | if (params[name]) { 59 | $.extend(true, params[name], parseNestedParam(params[name], rest, field_value)); 60 | } else { 61 | params[name] = parseNestedParam({}, rest, field_value); 62 | } 63 | } 64 | return params; 65 | }; 66 | 67 | // Sammy.NestedParams overrides the default form parsing behavior to provide 68 | // extended functionality for parsing Rack/Rails style form name/value pairs into JS 69 | // Objects. In fact it passes the same suite of tests as Rack's nested query parsing. 70 | // The code and tests were ported to JavaScript/Sammy by http://github.com/endor 71 | // 72 | // This allows you to translate a form with properly named inputs into a JSON object. 73 | // 74 | // ### Example 75 | // 76 | // Given an HTML form like so: 77 | // 78 | //
79 | // 80 | // 81 | // 82 | // 83 | //
84 | // 85 | // And a Sammy app like: 86 | // 87 | // var app = $.sammy(function(app) { 88 | // this.use(Sammy.NestedParams); 89 | // 90 | // this.post('#/parse_me', function(context) { 91 | // $.log(this.params); 92 | // }); 93 | // }); 94 | // 95 | // If you filled out the form with some values and submitted it, you would see something 96 | // like this in your log: 97 | // 98 | // { 99 | // 'obj': { 100 | // 'first': 'value', 101 | // 'second': 'value', 102 | // 'hash': { 103 | // 'first': 'value', 104 | // 'second': 'value' 105 | // } 106 | // } 107 | // } 108 | // 109 | // It supports creating arrays with [] and other niceities. Check out the tests for 110 | // full specs. 111 | // 112 | Sammy.NestedParams = function(app) { 113 | 114 | app._parseParamPair = parseNestedParam; 115 | 116 | }; 117 | 118 | })(jQuery); 119 | -------------------------------------------------------------------------------- /js/vendor/jquery.color.js: -------------------------------------------------------------------------------- 1 | /* 2 | * jQuery Color Animations 3 | * Copyright 2007 John Resig 4 | * Released under the MIT and GPL licenses. 5 | */ 6 | 7 | (function(jQuery){ 8 | 9 | // We override the animation for all of these color styles 10 | jQuery.each(['backgroundColor', 'borderBottomColor', 'borderLeftColor', 'borderRightColor', 'borderTopColor', 'color', 'outlineColor'], function(i,attr){ 11 | jQuery.fx.step[attr] = function(fx){ 12 | if ( !fx.colorInit ) { 13 | fx.start = getColor( fx.elem, attr ); 14 | fx.end = getRGB( fx.end ); 15 | fx.colorInit = true; 16 | } 17 | 18 | fx.elem.style[attr] = "rgb(" + [ 19 | Math.max(Math.min( parseInt((fx.pos * (fx.end[0] - fx.start[0])) + fx.start[0]), 255), 0), 20 | Math.max(Math.min( parseInt((fx.pos * (fx.end[1] - fx.start[1])) + fx.start[1]), 255), 0), 21 | Math.max(Math.min( parseInt((fx.pos * (fx.end[2] - fx.start[2])) + fx.start[2]), 255), 0) 22 | ].join(",") + ")"; 23 | } 24 | }); 25 | 26 | // Color Conversion functions from highlightFade 27 | // By Blair Mitchelmore 28 | // http://jquery.offput.ca/highlightFade/ 29 | 30 | // Parse strings looking for color tuples [255,255,255] 31 | function getRGB(color) { 32 | var result; 33 | 34 | // Check if we're already dealing with an array of colors 35 | if ( color && color.constructor == Array && color.length == 3 ) 36 | return color; 37 | 38 | // Look for rgb(num,num,num) 39 | if (result = /rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/.exec(color)) 40 | return [parseInt(result[1]), parseInt(result[2]), parseInt(result[3])]; 41 | 42 | // Look for rgb(num%,num%,num%) 43 | if (result = /rgb\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*\)/.exec(color)) 44 | return [parseFloat(result[1])*2.55, parseFloat(result[2])*2.55, parseFloat(result[3])*2.55]; 45 | 46 | // Look for #a0b1c2 47 | if (result = /#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(color)) 48 | return [parseInt(result[1],16), parseInt(result[2],16), parseInt(result[3],16)]; 49 | 50 | // Look for #fff 51 | if (result = /#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/.exec(color)) 52 | return [parseInt(result[1]+result[1],16), parseInt(result[2]+result[2],16), parseInt(result[3]+result[3],16)]; 53 | 54 | // Look for rgba(0, 0, 0, 0) == transparent in Safari 3 55 | if (result = /rgba\(0, 0, 0, 0\)/.exec(color)) 56 | return colors['transparent']; 57 | 58 | // Otherwise, we're most likely dealing with a named color 59 | return colors[jQuery.trim(color).toLowerCase()]; 60 | } 61 | 62 | function getColor(elem, attr) { 63 | var color; 64 | 65 | do { 66 | color = jQuery.curCSS(elem, attr); 67 | 68 | // Keep going until we find an element that has color, or we hit the body 69 | if ( color != '' && color != 'transparent' || jQuery.nodeName(elem, "body") ) 70 | break; 71 | 72 | attr = "backgroundColor"; 73 | } while ( elem = elem.parentNode ); 74 | 75 | return getRGB(color); 76 | }; 77 | 78 | // Some named colors to work with 79 | // From Interface by Stefan Petre 80 | // http://interface.eyecon.ro/ 81 | 82 | var colors = { 83 | aqua:[0,255,255], 84 | azure:[240,255,255], 85 | beige:[245,245,220], 86 | black:[0,0,0], 87 | blue:[0,0,255], 88 | brown:[165,42,42], 89 | cyan:[0,255,255], 90 | darkblue:[0,0,139], 91 | darkcyan:[0,139,139], 92 | darkgrey:[169,169,169], 93 | darkgreen:[0,100,0], 94 | darkkhaki:[189,183,107], 95 | darkmagenta:[139,0,139], 96 | darkolivegreen:[85,107,47], 97 | darkorange:[255,140,0], 98 | darkorchid:[153,50,204], 99 | darkred:[139,0,0], 100 | darksalmon:[233,150,122], 101 | darkviolet:[148,0,211], 102 | fuchsia:[255,0,255], 103 | gold:[255,215,0], 104 | green:[0,128,0], 105 | indigo:[75,0,130], 106 | khaki:[240,230,140], 107 | lightblue:[173,216,230], 108 | lightcyan:[224,255,255], 109 | lightgreen:[144,238,144], 110 | lightgrey:[211,211,211], 111 | lightpink:[255,182,193], 112 | lightyellow:[255,255,224], 113 | lime:[0,255,0], 114 | magenta:[255,0,255], 115 | maroon:[128,0,0], 116 | navy:[0,0,128], 117 | olive:[128,128,0], 118 | orange:[255,165,0], 119 | pink:[255,192,203], 120 | purple:[128,0,128], 121 | violet:[128,0,128], 122 | red:[255,0,0], 123 | silver:[192,192,192], 124 | white:[255,255,255], 125 | yellow:[255,255,0], 126 | transparent: [255,255,255] 127 | }; 128 | 129 | })(jQuery); 130 | -------------------------------------------------------------------------------- /js/models/action.js: -------------------------------------------------------------------------------- 1 | Action = Sammy('#container').createModel('action'); 2 | Action.extend({ 3 | tokens: { 4 | modifiers: ['or','for','of','about','to','with','in','around','up','down','and','a','an','the','out','into','-', 'on','from', '#','/',':', '!'] 5 | }, 6 | 7 | chars: 'abcdefghijklmnopqrstuvwxyz0123456789'.split(''), 8 | 9 | loadTokens: function(callback) { 10 | Action.view('tokens', {group: true}, function(tokens) { 11 | var token_groups = {verb:{}, subject:{}}, 12 | max = {verb: 0, subject: 0}, 13 | sheet = [], 14 | token, group, key, value; 15 | for (var i = 0;i < tokens.rows.length;i++) { 16 | token = tokens.rows[i]; 17 | group = token['key'][0]; 18 | key = token['key'][1]; 19 | value = token['value']; 20 | if (token_groups[group]) { 21 | token_groups[group][key] = value; 22 | } 23 | if (value > max[group]) { max[group] = value; } 24 | } 25 | callback({max: max, token_groups: token_groups}); 26 | }); 27 | }, 28 | 29 | parse: function(content) { 30 | var arr = [], hash = {}; 31 | content = $.trim(content.toString()); // ensure string 32 | tokens = content.split(/(\s+|-|\/|\:|#)/g); 33 | 34 | var token, 35 | subject, 36 | token_ctx, 37 | pushToken = function(type, t) { 38 | if (type) { 39 | hash[type] ? hash[type].push(t) : hash[type] = [t]; 40 | arr.push([type, t]); 41 | } else { 42 | arr.push(t); 43 | } 44 | }, 45 | isModifier = function(t) { 46 | return ($.inArray(t, Action.tokens.modifiers) != -1); 47 | }; 48 | 49 | token_ctx = 'verb'; 50 | var current = []; 51 | // iterate through the tokens 52 | for (var i=0; i < tokens.length; i++) { 53 | token = tokens[i]; 54 | next_token = tokens[i + 1]; 55 | if ($.trim(token) != '') { 56 | switch (token_ctx) { 57 | case 'verb': 58 | pushToken('verb', token); 59 | token_ctx = 'subject'; 60 | break; 61 | case 'subject': 62 | if (isModifier(token)) { 63 | if (current.length > 0) { 64 | pushToken('subject', current.join(' ')); 65 | } 66 | pushToken(false, token); 67 | current = []; 68 | } else { 69 | current.push(token); 70 | } 71 | break; 72 | default: 73 | pushToken(false, token) 74 | } 75 | } 76 | } 77 | if (current.length > 0) { 78 | pushToken('subject', current.join(' ')); 79 | } 80 | return {array: arr, hash: hash}; 81 | }, 82 | 83 | parsedToHTML: function(parsed) { 84 | if (parsed['array']) { 85 | var html = []; 86 | for (var i=0; i"); 92 | html.push(token[1]); 93 | html.push(' '); 94 | } else { 95 | html.push(token + ' '); 96 | } 97 | } 98 | return html.join(''); 99 | } else { 100 | return ""; 101 | } 102 | }, 103 | 104 | beforeSave: function(doc) { 105 | doc.parsed = this.parse(doc.content); 106 | doc.parsed_html = this.parsedToHTML(doc.parsed); 107 | return doc; 108 | }, 109 | 110 | viewIndex: function(options, callback) { 111 | return Action.viewDocs('by_complete', $.extend({ 112 | startkey: [0,"a"], 113 | endkey: [null, null], 114 | descending: true 115 | }, options || {}), callback); 116 | }, 117 | 118 | viewCompleted: function(options, callback) { 119 | return Action.viewDocs('by_complete', $.extend({ 120 | startkey: ["a","a"], 121 | endkey: [1, null], 122 | descending: true 123 | }, options || {}), callback); 124 | }, 125 | 126 | viewByToken: function(options, callback) { 127 | return Action.viewDocs('by_token', $.extend({ 128 | startkey: [options.type, options.token + "a", "a"], 129 | endkey: [options.type, options.token, null], 130 | descending: true 131 | }, options || {}), callback); 132 | }, 133 | 134 | viewReview: function(options, callback) { 135 | return Action.viewDocs('review', $.extend({ 136 | startkey: ["a","a"], 137 | endkey: [1, null], 138 | descending: true 139 | }, options || {}), callback); 140 | }, 141 | 142 | viewAsleep: function(options, callback) { 143 | return Action.viewDocs('asleep', $.extend({ 144 | startkey: ["a","a"], 145 | endkey: [1, 0, null], 146 | descending: true 147 | }, options || {}), callback); 148 | }, 149 | 150 | viewSearch: function(query, callback) { 151 | var query = query.split(''); // = Action.chars[Action.chars.indexOf(query[0]) + 1]; 152 | // wildcard 153 | if (query[query.length - 1] == '*') { 154 | query.pop() 155 | query.push('a'); 156 | next = [].concat(query); 157 | next.push(null); 158 | } else { 159 | next = query 160 | } 161 | return Action.viewDocs('search', { 162 | startkey: query, 163 | endkey: next 164 | }, callback); 165 | } 166 | 167 | }); 168 | -------------------------------------------------------------------------------- /sass/screen.scss: -------------------------------------------------------------------------------- 1 | // Import all the default blueprint modules so that we can access their mixins. 2 | @import "blueprint"; 3 | 4 | // To configure blueprint, edit the partials/base.sass file. 5 | @import "partials/base"; 6 | 7 | // This import applies a global reset to any page that imports this stylesheet. 8 | @import "blueprint/reset"; 9 | 10 | // Import the non-default scaffolding module. 11 | @import "blueprint/scaffolding"; 12 | 13 | @import "compass/utilities/lists"; 14 | @import "compass/css3"; 15 | 16 | // To generate css equivalent to the blueprint css but with your 17 | // configuration applied, uncomment: 18 | // @include blueprint 19 | 20 | // But Compass recommends that you scope your blueprint styles 21 | // So that you can better control what pages use blueprint 22 | // when stylesheets are concatenated together. 23 | @include blueprint-scaffolding; 24 | 25 | body.bp { 26 | @include blueprint-typography(true); 27 | @include blueprint-utilities; 28 | @include blueprint-debug; 29 | @include blueprint-interaction; 30 | // Remove the scaffolding when you're ready to start doing visual design. 31 | // Or leave it in if you're happy with how blueprint looks out-of-the-box 32 | } 33 | 34 | form.bp { 35 | @include blueprint-form; 36 | } 37 | 38 | // --- 39 | 40 | $grey: #999; 41 | $lt-grey: #E5E5E5; 42 | $dk-grey: #626262; 43 | 44 | #container { 45 | @include container; 46 | } 47 | 48 | #header { 49 | @include column($blueprint-grid-columns); 50 | @include clearfix; 51 | position: relative; 52 | border-bottom: 1px solid $lt-grey; 53 | h1 { 54 | font-weight: bold; 55 | a { 56 | color: #4D5B60; 57 | text-decoration: none; 58 | } 59 | em { 60 | color: $grey; 61 | font-family: monospace; 62 | } 63 | .search-type, .search-token { 64 | font-size: 0.7em; 65 | padding: 0px 4px; 66 | } 67 | .search-type { 68 | color: $dk-grey; 69 | } 70 | } 71 | #loading { 72 | position: absolute; 73 | top: 5px; 74 | @include column($blueprint-grid-columns, true); 75 | text-align: center; 76 | } 77 | ul.menu { 78 | @include inline-block-list(20); 79 | @include opacity(0.3); 80 | @include clearfix; 81 | position: absolute; 82 | top: 10px; 83 | right: 10px; 84 | text-align: right; 85 | a { 86 | color: $dk-grey; 87 | padding: 0px 4px; 88 | font-weight: bold; 89 | &.review { 90 | color: #000; 91 | padding: 0px 8px; 92 | border-right: 1px solid $dk-grey; 93 | } 94 | } 95 | &:hover { 96 | @include transition("opacity", "0.5s", "ease"); 97 | @include opacity(1); 98 | } 99 | } 100 | } 101 | 102 | #footer { 103 | @include column($blueprint-grid-columns, true); 104 | font-size: 10px; 105 | color: $grey; 106 | margin-top: 20px; 107 | text-align:center; 108 | padding: 10px; 109 | } 110 | 111 | #main { 112 | @include column($blueprint-grid-columns, true); 113 | @include clearfix; 114 | } 115 | 116 | .action-form { 117 | @include column($blueprint-grid-columns, true); 118 | @include clearfix; 119 | padding: 20px 0px; 120 | position: relative; 121 | 122 | form { 123 | @include blueprint-inline-form; 124 | .content-input { 125 | @include column($blueprint-grid-columns - 3); 126 | padding: 4px; 127 | border:none; 128 | border-bottom: 1px solid $grey; 129 | font-size: 2em; 130 | color: $grey; 131 | &:focus { 132 | outline: none; 133 | } 134 | } 135 | .buttons { 136 | @include span(2, true); 137 | position: absolute; 138 | right: 0px; 139 | } 140 | input[type="submit"] { 141 | color: $dk-grey; 142 | font-size: 1.5em; 143 | font-weight: bold; 144 | padding: 8px 4px; 145 | background: none; 146 | border: none; 147 | margin-top: 16px; 148 | text-align: center; 149 | &:hover { 150 | color: #000; 151 | @include transition("background", "1s", "ease"); 152 | background: #CCC; 153 | @include border-radius(2) 154 | } 155 | } 156 | a.cancel { 157 | color: $grey; 158 | } 159 | } 160 | } 161 | 162 | .actions { 163 | @include column($blueprint-grid-columns, true); 164 | @include clearfix; 165 | .action { 166 | @include clearfix; 167 | position: relative; 168 | 169 | &.complete { 170 | opacity: 0.5; 171 | } 172 | &.focused { 173 | background: $lt-grey; 174 | .meta { 175 | color: $dk-grey; 176 | } 177 | } 178 | &.sleeping { 179 | opacity: 0.7; 180 | .content { 181 | font-size: 1.8em !important; 182 | } 183 | } 184 | &:hover { 185 | .meta { 186 | color: $dk-grey; 187 | } 188 | } 189 | .controls { 190 | @include column(1); 191 | @include prepend-top; 192 | } 193 | .meta { 194 | @include horizontal-list; 195 | @include column($blueprint-grid-columns - 2, true); 196 | @include prepend(1); 197 | color: $lt-grey; 198 | a { 199 | color: $grey; 200 | font-weight: bold; 201 | font-size: 1.1em; 202 | text-decoration: none; 203 | &:hover { 204 | text-decoration: underline 205 | } 206 | } 207 | } 208 | } 209 | .action-form { 210 | border-left: 1px solid $grey; 211 | .content-input, .action-preview { 212 | @include prepend(1); 213 | } 214 | } 215 | } 216 | body.bp { 217 | .actions .action .content, .action-preview { 218 | @include column($blueprint-grid-columns - 1, true); 219 | font-size: 2.4em; 220 | a { 221 | text-decoration: none; 222 | color: inherit; 223 | &:hover { 224 | text-decoration:underline; 225 | } 226 | } 227 | .verb { 228 | font-weight: bold; 229 | } 230 | .modifier { 231 | font-weight: normal; 232 | font-color: $dk-grey; 233 | } 234 | .subject { 235 | font-weight: bold; 236 | } 237 | sup { 238 | font-size: 35%; 239 | color: #FFF; 240 | background: $grey; 241 | @include border-radius(8px); 242 | padding: 2px 4px; 243 | } 244 | } 245 | .action-preview { 246 | padding: 0px 4px; 247 | } 248 | .replicator-form { 249 | .target { 250 | width: 300px; 251 | padding: 3px; 252 | margin-left: 5px; 253 | } 254 | .hidden { 255 | display: none; 256 | } 257 | } 258 | } 259 | -------------------------------------------------------------------------------- /js/vendor/sha1.js: -------------------------------------------------------------------------------- 1 | /* 2 | * A JavaScript implementation of the Secure Hash Algorithm, SHA-1, as defined 3 | * in FIPS PUB 180-1 4 | * Version 2.1a Copyright Paul Johnston 2000 - 2002. 5 | * Other contributors: Greg Holt, Andrew Kepert, Ydnar, Lostinet 6 | * Distributed under the BSD License 7 | * See http://pajhome.org.uk/crypt/md5 for details. 8 | */ 9 | 10 | /* 11 | * Configurable variables. You may need to tweak these to be compatible with 12 | * the server-side, but the defaults work in most cases. 13 | */ 14 | var hexcase = 0; /* hex output format. 0 - lowercase; 1 - uppercase */ 15 | var b64pad = "="; /* base-64 pad character. "=" for strict RFC compliance */ 16 | var chrsz = 8; /* bits per input character. 8 - ASCII; 16 - Unicode */ 17 | 18 | /* 19 | * These are the functions you'll usually want to call 20 | * They take string arguments and return either hex or base-64 encoded strings 21 | */ 22 | function hex_sha1(s){return binb2hex(core_sha1(str2binb(s),s.length * chrsz));} 23 | function b64_sha1(s){return binb2b64(core_sha1(str2binb(s),s.length * chrsz));} 24 | function str_sha1(s){return binb2str(core_sha1(str2binb(s),s.length * chrsz));} 25 | function hex_hmac_sha1(key, data){ return binb2hex(core_hmac_sha1(key, data));} 26 | function b64_hmac_sha1(key, data){ return binb2b64(core_hmac_sha1(key, data));} 27 | function str_hmac_sha1(key, data){ return binb2str(core_hmac_sha1(key, data));} 28 | 29 | /* 30 | * Perform a simple self-test to see if the VM is working 31 | */ 32 | function sha1_vm_test() 33 | { 34 | return hex_sha1("abc") == "a9993e364706816aba3e25717850c26c9cd0d89d"; 35 | } 36 | 37 | /* 38 | * Calculate the SHA-1 of an array of big-endian words, and a bit length 39 | */ 40 | function core_sha1(x, len) 41 | { 42 | /* append padding */ 43 | x[len >> 5] |= 0x80 << (24 - len % 32); 44 | x[((len + 64 >> 9) << 4) + 15] = len; 45 | 46 | var w = Array(80); 47 | var a = 1732584193; 48 | var b = -271733879; 49 | var c = -1732584194; 50 | var d = 271733878; 51 | var e = -1009589776; 52 | 53 | for(var i = 0; i < x.length; i += 16) 54 | { 55 | var olda = a; 56 | var oldb = b; 57 | var oldc = c; 58 | var oldd = d; 59 | var olde = e; 60 | 61 | for(var j = 0; j < 80; j++) 62 | { 63 | if(j < 16) w[j] = x[i + j]; 64 | else w[j] = rol(w[j-3] ^ w[j-8] ^ w[j-14] ^ w[j-16], 1); 65 | var t = safe_add(safe_add(rol(a, 5), sha1_ft(j, b, c, d)), 66 | safe_add(safe_add(e, w[j]), sha1_kt(j))); 67 | e = d; 68 | d = c; 69 | c = rol(b, 30); 70 | b = a; 71 | a = t; 72 | } 73 | 74 | a = safe_add(a, olda); 75 | b = safe_add(b, oldb); 76 | c = safe_add(c, oldc); 77 | d = safe_add(d, oldd); 78 | e = safe_add(e, olde); 79 | } 80 | return Array(a, b, c, d, e); 81 | 82 | } 83 | 84 | /* 85 | * Perform the appropriate triplet combination function for the current 86 | * iteration 87 | */ 88 | function sha1_ft(t, b, c, d) 89 | { 90 | if(t < 20) return (b & c) | ((~b) & d); 91 | if(t < 40) return b ^ c ^ d; 92 | if(t < 60) return (b & c) | (b & d) | (c & d); 93 | return b ^ c ^ d; 94 | } 95 | 96 | /* 97 | * Determine the appropriate additive constant for the current iteration 98 | */ 99 | function sha1_kt(t) 100 | { 101 | return (t < 20) ? 1518500249 : (t < 40) ? 1859775393 : 102 | (t < 60) ? -1894007588 : -899497514; 103 | } 104 | 105 | /* 106 | * Calculate the HMAC-SHA1 of a key and some data 107 | */ 108 | function core_hmac_sha1(key, data) 109 | { 110 | var bkey = str2binb(key); 111 | if(bkey.length > 16) bkey = core_sha1(bkey, key.length * chrsz); 112 | 113 | var ipad = Array(16), opad = Array(16); 114 | for(var i = 0; i < 16; i++) 115 | { 116 | ipad[i] = bkey[i] ^ 0x36363636; 117 | opad[i] = bkey[i] ^ 0x5C5C5C5C; 118 | } 119 | 120 | var hash = core_sha1(ipad.concat(str2binb(data)), 512 + data.length * chrsz); 121 | return core_sha1(opad.concat(hash), 512 + 160); 122 | } 123 | 124 | /* 125 | * Add integers, wrapping at 2^32. This uses 16-bit operations internally 126 | * to work around bugs in some JS interpreters. 127 | */ 128 | function safe_add(x, y) 129 | { 130 | var lsw = (x & 0xFFFF) + (y & 0xFFFF); 131 | var msw = (x >> 16) + (y >> 16) + (lsw >> 16); 132 | return (msw << 16) | (lsw & 0xFFFF); 133 | } 134 | 135 | /* 136 | * Bitwise rotate a 32-bit number to the left. 137 | */ 138 | function rol(num, cnt) 139 | { 140 | return (num << cnt) | (num >>> (32 - cnt)); 141 | } 142 | 143 | /* 144 | * Convert an 8-bit or 16-bit string to an array of big-endian words 145 | * In 8-bit function, characters >255 have their hi-byte silently ignored. 146 | */ 147 | function str2binb(str) 148 | { 149 | var bin = Array(); 150 | var mask = (1 << chrsz) - 1; 151 | for(var i = 0; i < str.length * chrsz; i += chrsz) 152 | bin[i>>5] |= (str.charCodeAt(i / chrsz) & mask) << (32 - chrsz - i%32); 153 | return bin; 154 | } 155 | 156 | /* 157 | * Convert an array of big-endian words to a string 158 | */ 159 | function binb2str(bin) 160 | { 161 | var str = ""; 162 | var mask = (1 << chrsz) - 1; 163 | for(var i = 0; i < bin.length * 32; i += chrsz) 164 | str += String.fromCharCode((bin[i>>5] >>> (32 - chrsz - i%32)) & mask); 165 | return str; 166 | } 167 | 168 | /* 169 | * Convert an array of big-endian words to a hex string. 170 | */ 171 | function binb2hex(binarray) 172 | { 173 | var hex_tab = hexcase ? "0123456789ABCDEF" : "0123456789abcdef"; 174 | var str = ""; 175 | for(var i = 0; i < binarray.length * 4; i++) 176 | { 177 | str += hex_tab.charAt((binarray[i>>2] >> ((3 - i%4)*8+4)) & 0xF) + 178 | hex_tab.charAt((binarray[i>>2] >> ((3 - i%4)*8 )) & 0xF); 179 | } 180 | return str; 181 | } 182 | 183 | /* 184 | * Convert an array of big-endian words to a base-64 string 185 | */ 186 | function binb2b64(binarray) 187 | { 188 | var tab = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; 189 | var str = ""; 190 | for(var i = 0; i < binarray.length * 4; i += 3) 191 | { 192 | var triplet = (((binarray[i >> 2] >> 8 * (3 - i %4)) & 0xFF) << 16) 193 | | (((binarray[i+1 >> 2] >> 8 * (3 - (i+1)%4)) & 0xFF) << 8 ) 194 | | ((binarray[i+2 >> 2] >> 8 * (3 - (i+2)%4)) & 0xFF); 195 | for(var j = 0; j < 4; j++) 196 | { 197 | if(i * 8 + j * 6 > binarray.length * 32) str += b64pad; 198 | else str += tab.charAt((triplet >> 6*(3-j)) & 0x3F); 199 | } 200 | } 201 | return str; 202 | } 203 | -------------------------------------------------------------------------------- /js/vendor/sammy.json-0.6.1.js: -------------------------------------------------------------------------------- 1 | (function($) { 2 | 3 | // json2.js - only included if native json does not exist 4 | // http://www.json.org/js.html 5 | if (!window.JSON) { 6 | window.JSON = {}; 7 | } 8 | (function () { 9 | 10 | function f(n) { 11 | // Format integers to have at least two digits. 12 | return n < 10 ? '0' + n : n; 13 | } 14 | 15 | if (typeof Date.prototype.toJSON !== 'function') { 16 | 17 | Date.prototype.toJSON = function (key) { 18 | 19 | return this.getUTCFullYear() + '-' + 20 | f(this.getUTCMonth() + 1) + '-' + 21 | f(this.getUTCDate()) + 'T' + 22 | f(this.getUTCHours()) + ':' + 23 | f(this.getUTCMinutes()) + ':' + 24 | f(this.getUTCSeconds()) + 'Z'; 25 | }; 26 | 27 | String.prototype.toJSON = 28 | Number.prototype.toJSON = 29 | Boolean.prototype.toJSON = function (key) { 30 | return this.valueOf(); 31 | }; 32 | } 33 | 34 | var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, 35 | escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, 36 | gap, 37 | indent, 38 | meta = { // table of character substitutions 39 | '\b': '\\b', 40 | '\t': '\\t', 41 | '\n': '\\n', 42 | '\f': '\\f', 43 | '\r': '\\r', 44 | '"' : '\\"', 45 | '\\': '\\\\' 46 | }, 47 | rep; 48 | 49 | 50 | function quote(string) { 51 | 52 | // If the string contains no control characters, no quote characters, and no 53 | // backslash characters, then we can safely slap some quotes around it. 54 | // Otherwise we must also replace the offending characters with safe escape 55 | // sequences. 56 | 57 | escapable.lastIndex = 0; 58 | return escapable.test(string) ? 59 | '"' + string.replace(escapable, function (a) { 60 | var c = meta[a]; 61 | return typeof c === 'string' ? c : 62 | '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4); 63 | }) + '"' : 64 | '"' + string + '"'; 65 | } 66 | 67 | 68 | function str(key, holder) { 69 | 70 | // Produce a string from holder[key]. 71 | 72 | var i, // The loop counter. 73 | k, // The member key. 74 | v, // The member value. 75 | length, 76 | mind = gap, 77 | partial, 78 | value = holder[key]; 79 | 80 | // If the value has a toJSON method, call it to obtain a replacement value. 81 | 82 | if (value && typeof value === 'object' && 83 | typeof value.toJSON === 'function') { 84 | value = value.toJSON(key); 85 | } 86 | 87 | // If we were called with a replacer function, then call the replacer to 88 | // obtain a replacement value. 89 | 90 | if (typeof rep === 'function') { 91 | value = rep.call(holder, key, value); 92 | } 93 | 94 | // What happens next depends on the value's type. 95 | 96 | switch (typeof value) { 97 | case 'string': 98 | return quote(value); 99 | 100 | case 'number': 101 | 102 | // JSON numbers must be finite. Encode non-finite numbers as null. 103 | 104 | return isFinite(value) ? String(value) : 'null'; 105 | 106 | case 'boolean': 107 | case 'null': 108 | 109 | // If the value is a boolean or null, convert it to a string. Note: 110 | // typeof null does not produce 'null'. The case is included here in 111 | // the remote chance that this gets fixed someday. 112 | 113 | return String(value); 114 | 115 | // If the type is 'object', we might be dealing with an object or an array or 116 | // null. 117 | 118 | case 'object': 119 | 120 | // Due to a specification blunder in ECMAScript, typeof null is 'object', 121 | // so watch out for that case. 122 | 123 | if (!value) { 124 | return 'null'; 125 | } 126 | 127 | // Make an array to hold the partial results of stringifying this object value. 128 | 129 | gap += indent; 130 | partial = []; 131 | 132 | // Is the value an array? 133 | 134 | if (Object.prototype.toString.apply(value) === '[object Array]') { 135 | 136 | // The value is an array. Stringify every element. Use null as a placeholder 137 | // for non-JSON values. 138 | 139 | length = value.length; 140 | for (i = 0; i < length; i += 1) { 141 | partial[i] = str(i, value) || 'null'; 142 | } 143 | 144 | // Join all of the elements together, separated with commas, and wrap them in 145 | // brackets. 146 | 147 | v = partial.length === 0 ? '[]' : 148 | gap ? '[\n' + gap + 149 | partial.join(',\n' + gap) + '\n' + 150 | mind + ']' : 151 | '[' + partial.join(',') + ']'; 152 | gap = mind; 153 | return v; 154 | } 155 | 156 | // If the replacer is an array, use it to select the members to be stringified. 157 | 158 | if (rep && typeof rep === 'object') { 159 | length = rep.length; 160 | for (i = 0; i < length; i += 1) { 161 | k = rep[i]; 162 | if (typeof k === 'string') { 163 | v = str(k, value); 164 | if (v) { 165 | partial.push(quote(k) + (gap ? ': ' : ':') + v); 166 | } 167 | } 168 | } 169 | } else { 170 | 171 | // Otherwise, iterate through all of the keys in the object. 172 | 173 | for (k in value) { 174 | if (Object.hasOwnProperty.call(value, k)) { 175 | v = str(k, value); 176 | if (v) { 177 | partial.push(quote(k) + (gap ? ': ' : ':') + v); 178 | } 179 | } 180 | } 181 | } 182 | 183 | // Join all of the member texts together, separated with commas, 184 | // and wrap them in braces. 185 | 186 | v = partial.length === 0 ? '{}' : 187 | gap ? '{\n' + gap + partial.join(',\n' + gap) + '\n' + 188 | mind + '}' : '{' + partial.join(',') + '}'; 189 | gap = mind; 190 | return v; 191 | } 192 | } 193 | 194 | // If the JSON object does not yet have a stringify method, give it one. 195 | 196 | if (typeof JSON.stringify !== 'function') { 197 | JSON.stringify = function (value, replacer, space) { 198 | 199 | // The stringify method takes a value and an optional replacer, and an optional 200 | // space parameter, and returns a JSON text. The replacer can be a function 201 | // that can replace values, or an array of strings that will select the keys. 202 | // A default replacer method can be provided. Use of the space parameter can 203 | // produce text that is more easily readable. 204 | 205 | var i; 206 | gap = ''; 207 | indent = ''; 208 | 209 | // If the space parameter is a number, make an indent string containing that 210 | // many spaces. 211 | 212 | if (typeof space === 'number') { 213 | for (i = 0; i < space; i += 1) { 214 | indent += ' '; 215 | } 216 | 217 | // If the space parameter is a string, it will be used as the indent string. 218 | 219 | } else if (typeof space === 'string') { 220 | indent = space; 221 | } 222 | 223 | // If there is a replacer, it must be a function or an array. 224 | // Otherwise, throw an error. 225 | 226 | rep = replacer; 227 | if (replacer && typeof replacer !== 'function' && 228 | (typeof replacer !== 'object' || 229 | typeof replacer.length !== 'number')) { 230 | throw new Error('JSON.stringify'); 231 | } 232 | 233 | // Make a fake root object containing our value under the key of ''. 234 | // Return the result of stringifying the value. 235 | 236 | return str('', {'': value}); 237 | }; 238 | } 239 | 240 | 241 | // If the JSON object does not yet have a parse method, give it one. 242 | 243 | if (typeof JSON.parse !== 'function') { 244 | JSON.parse = function (text, reviver) { 245 | 246 | // The parse method takes a text and an optional reviver function, and returns 247 | // a JavaScript value if the text is a valid JSON text. 248 | 249 | var j; 250 | 251 | function walk(holder, key) { 252 | 253 | // The walk method is used to recursively walk the resulting structure so 254 | // that modifications can be made. 255 | 256 | var k, v, value = holder[key]; 257 | if (value && typeof value === 'object') { 258 | for (k in value) { 259 | if (Object.hasOwnProperty.call(value, k)) { 260 | v = walk(value, k); 261 | if (v !== undefined) { 262 | value[k] = v; 263 | } else { 264 | delete value[k]; 265 | } 266 | } 267 | } 268 | } 269 | return reviver.call(holder, key, value); 270 | } 271 | 272 | 273 | // Parsing happens in four stages. In the first stage, we replace certain 274 | // Unicode characters with escape sequences. JavaScript handles many characters 275 | // incorrectly, either silently deleting them, or treating them as line endings. 276 | 277 | cx.lastIndex = 0; 278 | if (cx.test(text)) { 279 | text = text.replace(cx, function (a) { 280 | return '\\u' + 281 | ('0000' + a.charCodeAt(0).toString(16)).slice(-4); 282 | }); 283 | } 284 | 285 | // In the second stage, we run the text against regular expressions that look 286 | // for non-JSON patterns. We are especially concerned with '()' and 'new' 287 | // because they can cause invocation, and '=' because it can cause mutation. 288 | // But just to be safe, we want to reject all unexpected forms. 289 | 290 | // We split the second stage into 4 regexp operations in order to work around 291 | // crippling inefficiencies in IE's and Safari's regexp engines. First we 292 | // replace the JSON backslash pairs with '@' (a non-JSON character). Second, we 293 | // replace all simple value tokens with ']' characters. Third, we delete all 294 | // open brackets that follow a colon or comma or that begin the text. Finally, 295 | // we look to see that the remaining characters are only whitespace or ']' or 296 | // ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval. 297 | 298 | if (/^[\],:{}\s]*$/. 299 | test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@'). 300 | replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']'). 301 | replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) { 302 | 303 | // In the third stage we use the eval function to compile the text into a 304 | // JavaScript structure. The '{' operator is subject to a syntactic ambiguity 305 | // in JavaScript: it can begin a block or an object literal. We wrap the text 306 | // in parens to eliminate the ambiguity. 307 | 308 | j = eval('(' + text + ')'); 309 | 310 | // In the optional fourth stage, we recursively walk the new structure, passing 311 | // each name/value pair to a reviver function for possible transformation. 312 | 313 | return typeof reviver === 'function' ? 314 | walk({'': j}, '') : j; 315 | } 316 | 317 | // If the text is not JSON parseable, then a SyntaxError is thrown. 318 | 319 | throw new SyntaxError('JSON.parse'); 320 | }; 321 | } 322 | }()); 323 | 324 | Sammy = Sammy || {}; 325 | 326 | // Sammy.JSON is a simple wrapper around Douglas Crockford's ever-useful json2.js 327 | // (http://www.json.org/js.html]) Sammy.JSON includes the top level JSON object if 328 | // it doesn't already exist (a.k.a. does not override the native implementation that 329 | // some browsers include). It also adds a json() helper to a Sammy app when 330 | // included. 331 | Sammy.JSON = function(app) { 332 | 333 | app.helpers({ 334 | // json is a polymorphic function that translates objects aback and forth 335 | // from JSON to JS. If given a string, it will parse into JS, if given a JS 336 | // object it will stringify into JSON. 337 | // 338 | // ### Example 339 | // 340 | // var app = $.sammy(function() { 341 | // this.use(Sammy.JSON); 342 | // 343 | // this.get('#/', function() { 344 | // this.json({user_id: 123}); //=> "{\"user_id\":\"123\"}" 345 | // this.json("{\"user_id\":\"123\"}"); //=> [object Object] 346 | // this.json("{\"user_id\":\"123\"}").user_id; //=> "123" 347 | // }); 348 | // }) 349 | // 350 | // 351 | json: function(object) { 352 | if (typeof object == 'string') { 353 | return JSON.parse(object); 354 | } else { 355 | return JSON.stringify(object); 356 | } 357 | } 358 | }); 359 | 360 | } 361 | 362 | })(jQuery); 363 | -------------------------------------------------------------------------------- /js/app.js: -------------------------------------------------------------------------------- 1 | (function($) { 2 | $.fn.d = function(n) { 3 | return $(this).attr('data-' + n); 4 | }; 5 | 6 | var app = $.sammy('#container', function() { 7 | this.use('JSON') 8 | .use('Mustache') 9 | .use('Storage') 10 | .use('NestedParams') 11 | .use('Couch', 'action'); 12 | 13 | var showLoading = function() { 14 | $('#loading').show(); 15 | }; 16 | 17 | var hideLoading = function() { 18 | $('#loading').hide(); 19 | }; 20 | 21 | // animate scroll the window to the current element 22 | var slideTo = function($el, offset, speed) { 23 | if (!speed) { speed = 400; } 24 | var top = $el.offset().top - (offset || 0); 25 | $('body,html').animate({'scrollTop': top + 'px'}, speed); 26 | }; 27 | 28 | var clearForm = function($scope) { 29 | $scope.find('.content-input').val(''); 30 | $scope.find('.action-preview').html(''); 31 | }; 32 | 33 | var keymap = { 34 | j: 'next-action', 35 | k: 'prev-action', 36 | esc: 'toggle-mode', 37 | x: 'toggle-action', 38 | z: 'sleep-action', 39 | i: 'edit-action', 40 | a: 'edit-action', 41 | n: 'new-action', 42 | g: 'first-action', 43 | 'shift+g': 'last-action' 44 | }; 45 | 46 | this.helpers({ 47 | serializeObject: function(obj) { 48 | var o = {}; 49 | var a = $(obj).serializeArray(); 50 | $.each(a, function() { 51 | if (o[this.name]) { 52 | if (!o[this.name].push) { 53 | o[this.name] = [o[this.name]]; 54 | } 55 | o[this.name].push(this.value || ''); 56 | } else { 57 | o[this.name] = this.value || ''; 58 | } 59 | }); 60 | return o; 61 | }, 62 | 63 | postReplication: function() { 64 | var helpers = this; 65 | $('.replicator-form').submit(function(e) { 66 | e.preventDefault(); 67 | $.ajax({ 68 | url: "/_replicate", 69 | type: "post", 70 | processData: false, 71 | data: JSON.stringify(helpers.serializeObject(e.target)), 72 | contentType: "application/json", 73 | success: function() { helpers.redirect('#/'); console.log('wee') } 74 | }); 75 | }) 76 | }, 77 | 78 | hexToRGB: function(hex) { 79 | hex = hex.replace(/^\#/,''); 80 | var rgb = [], i = 0; 81 | for (;i < 6;i+=2) { 82 | rgb.push(parseInt(hex.substring(i,i+2),16)); 83 | } 84 | return rgb; 85 | }, 86 | 87 | textToColor: function(text, dark) { 88 | var rgb = this.hexToRGB(hex_sha1(text).substr(3,9)); 89 | return "rgb(" + rgb.join(',') + ")"; 90 | }, 91 | 92 | timestr: function(milli) { 93 | if (!milli || $.trim(milli) == '') { return ''; } 94 | var date = new Date(parseInt(milli, 10)); 95 | return date.strftime('%c'); 96 | }, 97 | 98 | formatTimes: function() { 99 | var ctx = this; 100 | $('.timestr').text(function(i, original_text) { 101 | return ctx.timestr(original_text); 102 | }).removeClass('timestr').addClass('time'); 103 | }, 104 | 105 | buildTokenCSS: function() { 106 | var ctx = this; 107 | this.send(Action.loadTokens) 108 | .then(function(tokens) { 109 | ctx.app.tokens = tokens; // assign tokens 110 | var token, color, sheet = [], count; 111 | for (token in tokens.token_groups['verb']) { 112 | count = tokens.token_groups['verb'][token]; 113 | color = ctx.textToColor(token); 114 | sheet.push(['.verb-', token, ' { color:', color, ' !important;}'].join('')); 115 | } 116 | var $sheet = $('style#verb-sheet'); 117 | if ($sheet.length == 0) { 118 | $sheet = $('