diff --git a/lib/app/helpers.js b/lib/app/helpers.js index 9a40cb4e67..d2b3caf0ae 100644 --- a/lib/app/helpers.js +++ b/lib/app/helpers.js @@ -1,4 +1,7 @@ // Generated by CoffeeScript 1.3.3 +var moment; + +moment = require('moment'); module.exports.daysBetween = function(a, b) { var DAY; @@ -9,10 +12,20 @@ module.exports.daysBetween = function(a, b) { }; module.exports.viewHelpers = function(view) { - view.fn('taskClasses', function(type, completed, value) { - var classes; + view.fn('taskClasses', function(type, completed, value, repeat) { + var classes, dayMapping; classes = type; - if (completed) { + dayMapping = { + 0: 'su', + 1: 'm', + 2: 't', + 3: 'w', + 4: 'th', + 5: 'f', + 6: 's', + 7: 'su' + }; + if (completed || (repeat && repeat[dayMapping[moment().day()]] === false)) { classes += " completed"; } switch (false) { diff --git a/lib/app/index.js b/lib/app/index.js index f658bcb71c..5b9c3073c8 100644 --- a/lib/app/index.js +++ b/lib/app/index.js @@ -178,6 +178,22 @@ ready(function(model) { value: 20 }); case 'daily': + return list.push({ + type: type, + text: text, + notes: '', + value: 0, + repeat: { + su: true, + m: true, + t: true, + w: true, + th: true, + f: true, + s: true + }, + completed: false + }); case 'todo': return list.push({ type: type, @@ -222,6 +238,15 @@ ready(function(model) { return model.set('_user.completedIds', []); }); }; + exports.toggleDay = function(e, el) { + var task; + task = model.at(e.target); + if (/active/.test($(el).attr('class'))) { + return task.set('repeat.' + $(el).attr('data-day'), false); + } else { + return task.set('repeat.' + $(el).attr('data-day'), true); + } + }; exports.toggleTaskEdit = function(e, el) { var hideId, toggleId; hideId = $(el).attr('data-hide-id'); @@ -324,8 +349,10 @@ ready(function(model) { daysPassed = helpers.daysBetween(lastCron, today); if (daysPassed > 0) { model.set('_user.lastCron', today); - return _(daysPassed).times(function() { - return scoring.tally(model.at('_user')); + return _(daysPassed).times(function(n) { + var tallyFor; + tallyFor = moment(lastCron).add('d', n); + return scoring.tally(model.at('_user'), tallyFor); }); } }; @@ -335,10 +362,7 @@ ready(function(model) { setInterval(function() { return poormanscron(); }, 3600000); - exports.endOfDayTally = function(e, el) { + return exports.endOfDayTally = function(e, el) { return scoring.tally(model); }; - return exports.updateSchema = function(e, el) { - return schema.updateSchema(model); - }; }); diff --git a/lib/app/schema.js b/lib/app/schema.js index 101ee70d5e..e44750a7a1 100644 --- a/lib/app/schema.js +++ b/lib/app/schema.js @@ -22,6 +22,7 @@ module.exports.userSchema = userSchema = { }; module.exports.updateSchema = function(model) { + return; return model.fetch('users', function(err, users) { return _.each(users.get(), function(userObj) { var daysOld, sameTasks, user, userPath; diff --git a/lib/app/scoring.js b/lib/app/scoring.js index 0e8b5d04a8..7f8434bc4b 100644 --- a/lib/app/scoring.js +++ b/lib/app/scoring.js @@ -150,28 +150,40 @@ module.exports.score = score = function(spec) { return delta; }; -module.exports.tally = function(user) { +module.exports.tally = function(user, momentDate) { var expTally, lvl, todoTally; todoTally = 0; _.each(user.get('tasks'), function(taskObj, taskId, list) { - var absVal, completed, task, type, value, _ref; + var absVal, completed, dayMapping, repeat, task, type, value, _ref; if (taskObj.id == null) { return; } - _ref = [taskObj.type, taskObj.value, taskObj.completed], type = _ref[0], value = _ref[1], completed = _ref[2]; + _ref = [taskObj.type, taskObj.value, taskObj.completed, taskObj.repeat], type = _ref[0], value = _ref[1], completed = _ref[2], repeat = _ref[3]; task = user.at("tasks." + taskId); if (type === 'todo' || type === 'daily') { if (!completed) { - score({ - user: user, - task: task, - direction: 'down', - cron: true - }); + dayMapping = { + 0: 'su', + 1: 'm', + 2: 't', + 3: 'w', + 4: 'th', + 5: 'f', + 6: 's', + 7: 'su' + }; + if (repeat && repeat[dayMapping[momentDate.day()]] === true) { + score({ + user: user, + task: task, + direction: 'down', + cron: true + }); + } } if (type === 'daily') { task.push("history", { - date: new Date(), + date: new Date(momentDate), value: value }); } else { @@ -186,7 +198,7 @@ module.exports.tally = function(user) { } }); user.push('history.todos', { - date: new Date(), + date: new Date(momentDate), value: todoTally }); expTally = user.get('stats.exp'); diff --git a/package.json b/package.json index be8e930fe3..7c74f8f7dc 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,8 @@ "connect-mongo": ">=0.1.9", "derby-ui-boot": "*", "guid": "*", - "node.extend": "*" + "node.extend": "*", + "moment": "*" }, "private": true, "devDependencies": { diff --git a/public/js/moment.min.js b/public/js/moment.min.js new file mode 100644 index 0000000000..4c359231d7 --- /dev/null +++ b/public/js/moment.min.js @@ -0,0 +1,6 @@ +// moment.js +// version : 1.7.0 +// author : Tim Wood +// license : MIT +// momentjs.com +(function(a,b){function G(a,b,c){this._d=a,this._isUTC=!!b,this._a=a._a||null,a._a=null,this._lang=c||!1}function H(a){var b=this._data={},c=a.years||a.y||0,d=a.months||a.M||0,e=a.weeks||a.w||0,f=a.days||a.d||0,g=a.hours||a.h||0,h=a.minutes||a.m||0,i=a.seconds||a.s||0,j=a.milliseconds||a.ms||0;this._milliseconds=j+i*1e3+h*6e4+g*36e5,this._days=f+e*7,this._months=d+c*12,b.milliseconds=j%1e3,i+=I(j/1e3),b.seconds=i%60,h+=I(i/60),b.minutes=h%60,g+=I(h/60),b.hours=g%24,f+=I(g/24),f+=e*7,b.days=f%30,d+=I(f/30),b.months=d%12,c+=I(d/12),b.years=c,this._lang=!1}function I(a){return a<0?Math.ceil(a):Math.floor(a)}function J(a,b){var c=a+"";while(c.length70?1900:2e3);break;case"YYYY":c[0]=~~Math.abs(b);break;case"a":case"A":d.isPm=(b+"").toLowerCase()==="pm";break;case"H":case"HH":case"h":case"hh":c[3]=~~b;break;case"m":case"mm":c[4]=~~b;break;case"s":case"ss":c[5]=~~b;break;case"S":case"SS":case"SSS":c[6]=~~(("0."+b)*1e3);break;case"Z":case"ZZ":d.isUTC=!0,e=(b+"").match(z),e&&e[1]&&(d.tzh=~~e[1]),e&&e[2]&&(d.tzm=~~e[2]),e&&e[0]==="+"&&(d.tzh=-d.tzh,d.tzm=-d.tzm)}}function X(a,b){var c=[0,0,1,0,0,0,0],d={tzh:0,tzm:0},e=b.match(l),f,g;for(f=0;f0,j[4]=c,$.apply({},j)}function ab(a,b){c.fn[a]=function(a){var c=this._isUTC?"UTC":"";return a!=null?(this._d["set"+c+b](a),this):this._d["get"+c+b]()}}function bb(a){c.duration.fn[a]=function(){return this._data[a]}}function cb(a,b){c.duration.fn["as"+a]=function(){return+this/b}}var c,d="1.7.0",e=Math.round,f,g={},h="en",i=typeof module!="undefined"&&module.exports,j="months|monthsShort|weekdays|weekdaysShort|weekdaysMin|longDateFormat|calendar|relativeTime|ordinal|meridiem".split("|"),k=/^\/?Date\((\-?\d+)/i,l=/(\[[^\[]*\])|(\\)?(Mo|MM?M?M?|Do|DDDo|DD?D?D?|ddd?d?|do?|w[o|w]?|YYYY|YY|a|A|hh?|HH?|mm?|ss?|SS?S?|zz?|ZZ?)/g,m=/(LT|LL?L?L?)/g,n=/(^\[)|(\\)|\]$/g,o=/([0-9a-zA-Z\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]+)/gi,p=/\d\d?/,q=/\d{1,3}/,r=/\d{3}/,s=/\d{1,4}/,t=/[0-9a-z\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]+/i,u=/Z|[\+\-]\d\d:?\d\d/i,v=/T/i,w=/^\s*\d{4}-\d\d-\d\d(T(\d\d(:\d\d(:\d\d(\.\d\d?\d?)?)?)?)?([\+\-]\d\d:?\d\d)?)?/,x="YYYY-MM-DDTHH:mm:ssZ",y=[["HH:mm:ss.S",/T\d\d:\d\d:\d\d\.\d{1,3}/],["HH:mm:ss",/T\d\d:\d\d:\d\d/],["HH:mm",/T\d\d:\d\d/],["HH",/T\d\d/]],z=/([\+\-]|\d\d)/gi,A="Month|Date|Hours|Minutes|Seconds|Milliseconds".split("|"),B={Milliseconds:1,Seconds:1e3,Minutes:6e4,Hours:36e5,Days:864e5,Months:2592e6,Years:31536e6},C={},D={M:"(a=t.month()+1)",MMM:'v("monthsShort",t.month())',MMMM:'v("months",t.month())',D:"(a=t.date())",DDD:"(a=new Date(t.year(),t.month(),t.date()),b=new Date(t.year(),0,1),a=~~(((a-b)/864e5)+1.5))",d:"(a=t.day())",dd:'v("weekdaysMin",t.day())',ddd:'v("weekdaysShort",t.day())',dddd:'v("weekdays",t.day())',w:"(a=new Date(t.year(),t.month(),t.date()-t.day()+5),b=new Date(a.getFullYear(),0,4),a=~~((a-b)/864e5/7+1.5))",YY:"p(t.year()%100,2)",YYYY:"p(t.year(),4)",a:"m(t.hours(),t.minutes(),!0)",A:"m(t.hours(),t.minutes(),!1)",H:"t.hours()",h:"t.hours()%12||12",m:"t.minutes()",s:"t.seconds()",S:"~~(t.milliseconds()/100)",SS:"p(~~(t.milliseconds()/10),2)",SSS:"p(t.milliseconds(),3)",Z:'((a=-t.zone())<0?((a=-a),"-"):"+")+p(~~(a/60),2)+":"+p(~~a%60,2)',ZZ:'((a=-t.zone())<0?((a=-a),"-"):"+")+p(~~(10*a/6),4)'},E="DDD w M D d".split(" "),F="M D H h m s w".split(" ");while(E.length)f=E.pop(),D[f+"o"]=D[f]+"+o(a)";while(F.length)f=F.pop(),D[f+f]="p("+D[f]+",2)";D.DDDD="p("+D.DDD+",3)",c=function(d,e){if(d===null||d==="")return null;var f,g;return c.isMoment(d)?new G(new a(+d._d),d._isUTC,d._lang):(e?L(e)?f=Y(d,e):f=X(d,e):(g=k.exec(d),f=d===b?new a:g?new a(+g[1]):d instanceof a?d:L(d)?N(d):typeof d=="string"?Z(d):new a(d)),new G(f))},c.utc=function(a,b){return L(a)?new G(N(a,!0),!0):(typeof a=="string"&&!u.exec(a)&&(a+=" +0000",b&&(b+=" Z")),c(a,b).utc())},c.unix=function(a){return c(a*1e3)},c.duration=function(a,b){var d=c.isDuration(a),e=typeof a=="number",f=d?a._data:e?{}:a,g;return e&&(b?f[b]=a:f.milliseconds=a),g=new H(f),d&&(g._lang=a._lang),g},c.humanizeDuration=function(a,b,d){return c.duration(a,b===!0?null:b).humanize(b===!0?!0:d)},c.version=d,c.defaultFormat=x,c.lang=function(a,b){var d;if(!a)return h;(b||!g[a])&&O(a,b);if(g[a]){for(d=0;d11?c?"pm":"PM":c?"am":"AM"},calendar:{sameDay:"[Today at] LT",nextDay:"[Tomorrow at] LT",nextWeek:"dddd [at] LT",lastDay:"[Yesterday at] LT",lastWeek:"[last] dddd [at] LT",sameElse:"L"},relativeTime:{future:"in %s",past:"%s ago",s:"a few seconds",m:"a minute",mm:"%d minutes",h:"an hour",hh:"%d hours",d:"a day",dd:"%d days",M:"a month",MM:"%d months",y:"a year",yy:"%d years"},ordinal:function(a){var b=a%10;return~~(a%100/10)===1?"th":b===1?"st":b===2?"nd":b===3?"rd":"th"}}),c.fn=G.prototype={clone:function(){return c(this)},valueOf:function(){return+this._d},unix:function(){return Math.floor(+this._d/1e3)},toString:function(){return this._d.toString()},toDate:function(){return this._d},toArray:function(){var a=this;return[a.year(),a.month(),a.date(),a.hours(),a.minutes(),a.seconds(),a.milliseconds(),!!this._isUTC]},isValid:function(){return this._a?!M(this._a,(this._a[7]?c.utc(this):this).toArray()):!isNaN(this._d.getTime())},utc:function(){return this._isUTC=!0,this},local:function(){return this._isUTC=!1,this},format:function(a){return U(this,a?a:c.defaultFormat)},add:function(a,b){var d=b?c.duration(+b,a):c.duration(a);return K(this,d,1),this},subtract:function(a,b){var d=b?c.duration(+b,a):c.duration(a);return K(this,d,-1),this},diff:function(a,b,d){var f=this._isUTC?c(a).utc():c(a).local(),g=(this.zone()-f.zone())*6e4,h=this._d-f._d-g,i=this.year()-f.year(),j=this.month()-f.month(),k=this.date()-f.date(),l;return b==="months"?l=i*12+j+k/30:b==="years"?l=i+(j+k/30)/12:l=b==="seconds"?h/1e3:b==="minutes"?h/6e4:b==="hours"?h/36e5:b==="days"?h/864e5:b==="weeks"?h/6048e5:h,d?l:e(l)},from:function(a,b){return c.duration(this.diff(a)).lang(this._lang).humanize(!b)},fromNow:function(a){return this.from(c(),a)},calendar:function(){var a=this.diff(c().sod(),"days",!0),b=this.lang().calendar,d=b.sameElse,e=a<-6?d:a<-1?b.lastWeek:a<0?b.lastDay:a<1?b.sameDay:a<2?b.nextDay:a<7?b.nextWeek:d;return this.format(typeof e=="function"?e.apply(this):e)},isLeapYear:function(){var a=this.year();return a%4===0&&a%100!==0||a%400===0},isDST:function(){return this.zone() + #TODO replace this function with moment().diff()? DAY = 1000 * 60 * 60 * 24 # calculate as midnight a = new Date( (new Date(a)).toDateString() ) @@ -7,10 +9,14 @@ module.exports.daysBetween = (a, b) -> return Math.abs(Math.floor((a.getTime() - b.getTime()) / DAY)) module.exports.viewHelpers = (view) -> - view.fn 'taskClasses', (type, completed, value) -> + view.fn 'taskClasses', (type, completed, value, repeat) -> #TODO figure out how to just pass in the task model, so i can access all these properties from one object classes = type - classes += " completed" if completed + + # show as completed if completed (naturally) or not required for today + dayMapping = {0:'su',1:'m',2:'t',3:'w',4:'th',5:'f',6:'s',7:'su'} + if completed or (repeat and repeat[dayMapping[moment().day()]]==false) + classes += " completed" switch when value<-8 then classes += ' color-worst' diff --git a/src/app/index.coffee b/src/app/index.coffee index 831f819395..1bc1f4f234 100644 --- a/src/app/index.coffee +++ b/src/app/index.coffee @@ -147,7 +147,10 @@ ready (model) -> when 'reward' list.push {type: type, text: text, notes: '', value: 20 } - when 'daily', 'todo' + when 'daily' + list.push {type: type, text: text, notes: '', value: 0, repeat:{su:true,m:true,t:true,w:true,th:true,f:true,s:true}, completed: false } + + when 'todo' list.push {type: type, text: text, notes: '', value: 0, completed: false } # list.on 'set', '*.completed', (i, completed, previous, isLocal) -> @@ -184,6 +187,13 @@ ready (model) -> _.each model.get('_completedList'), (task) -> model.del('_user.tasks.'+task.id) model.set('_user.completedIds', []) + + exports.toggleDay = (e, el) -> + task = model.at(e.target) + if /active/.test($(el).attr('class')) # previous state, not current + task.set('repeat.' + $(el).attr('data-day'), false) + else + task.set('repeat.' + $(el).attr('data-day'), true) exports.toggleTaskEdit = (e, el) -> hideId = $(el).attr('data-hide-id') @@ -267,8 +277,9 @@ ready (model) -> daysPassed = helpers.daysBetween(lastCron, today) if daysPassed > 0 model.set('_user.lastCron', today) # reset cron - _(daysPassed).times () -> - scoring.tally(model.at('_user')) + _(daysPassed).times (n) -> + tallyFor = moment(lastCron).add('d',n) + scoring.tally(model.at('_user'), tallyFor) # FIXME seems can't call poormanscron() instantly, have to call after some time (2s here) # Doesn't do anything otherwise. Don't know why... model not initialized enough yet? setTimeout () -> # Run once on refresh @@ -284,6 +295,6 @@ ready (model) -> scoring.tally(model) # Temporary solution to running updates against the schema when the code changes - exports.updateSchema = (e, el) -> - schema.updateSchema(model) - + # exports.updateSchema = (e, el) -> + # schema.updateSchema(model) + \ No newline at end of file diff --git a/src/app/schema.coffee b/src/app/schema.coffee index e19bd6fb03..f3ad11e1bb 100644 --- a/src/app/schema.coffee +++ b/src/app/schema.coffee @@ -11,6 +11,7 @@ module.exports.userSchema = userSchema = { } module.exports.updateSchema = (model) -> + return # Reset history, remove inactive users model.fetch 'users', (err, users) -> _.each users.get(), (userObj) -> @@ -40,4 +41,4 @@ module.exports.updateSchema = (model) -> _.each userObj.tasks, (taskObj) -> task = user.at "tasks.#{taskObj.id}" if task.get("history") - task.set "history", [] \ No newline at end of file + task.set "history", [] diff --git a/src/app/scoring.coffee b/src/app/scoring.coffee index 715829bbdd..bf2a632b14 100644 --- a/src/app/scoring.coffee +++ b/src/app/scoring.coffee @@ -139,24 +139,27 @@ module.exports.score = score = (spec = {user:null, task:null, direction:null, cr # At end of day, add value to all incomplete Daily & Todo tasks (further incentive) # For incomplete Dailys, deduct experience -module.exports.tally = (user) -> +module.exports.tally = (user, momentDate) -> todoTally = 0 _.each user.get('tasks'), (taskObj, taskId, list) -> #FIXME is it hiccuping here? taskId == "$_65255f4e-3728-4d50-bade-3b05633639af_2", & taskObj.id = undefined return unless taskObj.id? #this shouldn't be happening, some tasks seem to be corrupted - [type, value, completed] = [taskObj.type, taskObj.value, taskObj.completed] + [type, value, completed, repeat] = [taskObj.type, taskObj.value, taskObj.completed, taskObj.repeat] task = user.at("tasks.#{taskId}") if type in ['todo', 'daily'] # Deduct experience for missed Daily tasks, # but not for Todos (just increase todo's value) - score({user:user, task:task, direction:'down', cron:true}) unless completed + unless completed + dayMapping = {0:'su',1:'m',2:'t',3:'w',4:'th',5:'f',6:'s',7:'su'} + if repeat && repeat[dayMapping[momentDate.day()]]==true + score({user:user, task:task, direction:'down', cron:true}) if type == 'daily' - task.push "history", { date: new Date(), value: value } + task.push "history", { date: new Date(momentDate), value: value } else absVal = if (completed) then Math.abs(value) else value todoTally += absVal task.pass({cron:true}).set('completed', false) if type == 'daily' - user.push 'history.todos', { date: new Date(), value: todoTally } + user.push 'history.todos', { date: new Date(momentDate), value: todoTally } # tally experience expTally = user.get 'stats.exp' diff --git a/views/app/index.html b/views/app/index.html index 5761c0ae0d..95c9af1cd9 100644 --- a/views/app/index.html +++ b/views/app/index.html @@ -78,7 +78,6 @@ Debugging Options

Cron Tally - Update Schema {/} @@ -203,7 +202,7 @@ -
  • +
  •      
    @@ -259,6 +258,19 @@
    {/} + {#if equal(:task.type, 'daily')} + +
    + + + + + + + + +
    + {/} {#if equal(:task.type, 'reward')}