diff --git a/common/script/index.coffee b/common/script/index.coffee
index f251ad7db5..5c44457319 100644
--- a/common/script/index.coffee
+++ b/common/script/index.coffee
@@ -72,13 +72,51 @@ api.daysSince = (yesterday, options = {}) ->
Math.abs api.startOfDay(_.defaults {now:yesterday}, o).diff(api.startOfDay(_.defaults {now:o.now}, o), 'days')
###
- Should the user do this taks on this date, given the task's repeat options and user.preferences.dayStart?
+ Should the user do this task on this date, given the task's repeat options and user.preferences.dayStart?
###
-api.shouldDo = (day, repeat, options={}) ->
- return false unless repeat
+api.shouldDo = (day, dailyTask, options = {}) ->
+ return false unless dailyTask.type == 'daily' && dailyTask.repeat
+ # MIGRATION HACK, FIXME
+ if dailyTask.startDate == null
+ console.log("null!!!!!")
+ dailyTask.startDate = moment().toDate();
+ if dailyTask.startDate instanceof String
+ console.log("startDate is a string: " + dailyTask.startDate + ". Converting to date");
+ dailyTask.startDate = moment(dailyTask.startDate).toDate();
o = sanitizeOptions options
- selected = repeat[api.dayMapping[api.startOfDay(_.defaults {now:day}, o).day()]]
- return selected
+ dayOfWeek = api.startOfDay(_.defaults {now:day}, o).day()
+
+ # check if event is in the future
+ hasStartedCheck = moment(day).isAfter(dailyTask.startDate) || moment(day).isSame(dailyTask.startDate)
+
+ if dailyTask.frequency == 'daily'
+ daysSinceTaskStart = api.numDaysApart(day, dailyTask.startDate, o)
+ everyXCheck = (daysSinceTaskStart % dailyTask.everyX == 0)
+ return everyXCheck && hasStartedCheck
+ else if dailyTask.frequency == 'weekly'
+ dayOfWeekCheck = dailyTask.repeat[api.dayMapping[dayOfWeek]];
+ weeksSinceTaskStartWeek = api.numWeeksApart(day, dailyTask.startDate, o)
+ everyXCheck = (weeksSinceTaskStartWeek % dailyTask.everyX == 0)
+ return dayOfWeekCheck && everyXCheck && hasStartedCheck
+ else
+ # unexpected frequency string
+ return true
+
+api.numDaysApart = (day1, day2, o) ->
+ startOfDay1 = api.startOfDay(_.defaults {now:day1}, o)
+ startOfDay2 = api.startOfDay(_.defaults {now:day2}, o)
+ numDays = Math.abs(startOfDay1.diff(startOfDay2, 'days'))
+ return numDays
+
+# weeks between the two days, counting Monday as the start of each week
+api.numWeeksApart = (day1, day2, o) ->
+ startOfDay1 = api.startOfDay(_.defaults {now:day1}, o)
+ startOfDay2 = api.startOfDay(_.defaults {now:day2}, o)
+ startWeekOfDay1 = startOfDay1.startOf('week')
+ startWeekOfDay2 = startOfDay2.startOf('week')
+ numWeeks = Math.abs(startWeekOfDay1.diff(startWeekOfDay2, 'weeks'))
+ return numWeeks
+
###
------------------------------------------------------
@@ -280,7 +318,7 @@ api.taskClasses = (task, filters=[], dayStart=0, lastCron=+new Date, showComplet
# show as completed if completed (naturally) or not required for today
if type in ['todo', 'daily']
- if completed or (type is 'daily' and !api.shouldDo(+new Date, task.repeat, {dayStart}))
+ if completed or (type is 'daily' and !api.shouldDo(+new Date, task, {dayStart}))
classes += " completed"
else
classes += " uncompleted"
@@ -1537,7 +1575,7 @@ api.wrap = (user, main=true) ->
scheduleMisses = 0
_.times daysMissed, (n) ->
thatDay = moment(now).subtract({days: n + 1})
- if api.shouldDo(thatDay, repeat, user.preferences)
+ if api.shouldDo(thatDay, task, user.preferences)
scheduleMisses++
if user.stats.buffs.stealth
user.stats.buffs.stealth--
diff --git a/website/public/js/controllers/tasksCtrl.js b/website/public/js/controllers/tasksCtrl.js
index c070727f8f..41ec17655a 100644
--- a/website/public/js/controllers/tasksCtrl.js
+++ b/website/public/js/controllers/tasksCtrl.js
@@ -4,6 +4,9 @@ habitrpg.controller("TasksCtrl", ['$scope', '$rootScope', '$location', 'User','N
function($scope, $rootScope, $location, User, Notification, $http, ApiUrl, $timeout, Shared, Guide) {
$scope.obj = User.user; // used for task-lists
$scope.user = User.user;
+ // HACK: flagDict is for storing whether the datePicker popup is open or not. This should just be a boolean flag,
+ // but apparently due to angular bug need to put the bool in intermediate dict...
+ $scope.flagDict = {};
$scope.score = function(task, direction) {
switch (task.type) {
@@ -127,6 +130,24 @@ habitrpg.controller("TasksCtrl", ['$scope', '$rootScope', '$location', 'User','N
*/
$scope._today = moment().add({days: 1});
+ /*
+ ------------------------
+ Dailies
+ ------------------------
+ */
+ $scope.updateTaskStartDate = function(task) {
+ // TODO: Weirdness here...startDate should be a Date and _dateString
+ // should be a string, but the datePicker input fields sets _dateString
+ // to a Date even though input type="text"...
+ task.startDate = task._dateString;
+ };
+
+ $scope.open = function($event) {
+ $event.stopPropagation();
+ $scope.flagDict['opened'] = !$scope.flagDict['opened'];
+ }
+
+
/*
------------------------
Checklists
@@ -212,7 +233,7 @@ habitrpg.controller("TasksCtrl", ['$scope', '$rootScope', '$location', 'User','N
$scope.shouldShow = function(task, list, prefs){
if (task._editing) // never hide a task while being edited
return true;
- var shouldDo = task.type == 'daily' ? habitrpgShared.shouldDo(new Date, task.repeat, prefs) : true;
+ var shouldDo = task.type == 'daily' ? habitrpgShared.shouldDo(new Date, task, prefs) : true;
switch (list.view) {
case "yellowred": // Habits
return task.value < 1;
diff --git a/website/public/js/directives/directives.js b/website/public/js/directives/directives.js
index 424913d00e..92a596af70 100644
--- a/website/public/js/directives/directives.js
+++ b/website/public/js/directives/directives.js
@@ -45,6 +45,7 @@ habitrpg
task._editing = !task._editing;
task._tags = User.user.preferences.tagsCollapsed;
task._advanced = User.user.preferences.advancedCollapsed;
+ task._dateString = moment(task.startDate).format('YYYY-MM-DD');
if($rootScope.charts[task.id]) $rootScope.charts[task.id] = false;
};
}],
diff --git a/website/src/models/task.js b/website/src/models/task.js
index 2646263e80..14ee018439 100644
--- a/website/src/models/task.js
+++ b/website/src/models/task.js
@@ -50,10 +50,14 @@ var checklist = [{
var DailySchema = new Schema(
_.defaults({
- type: {type:String, 'default': 'daily'},
+ type: {type: String, 'default': 'daily'},
+ //TODO: Cleaner to store interval as enum instead of str?
+ frequency: {type: String, 'default': 'daily'}, // 'daily', 'weekly'
+ everyX: {type: Number, 'default': 1}, // e.g. once every X weeks
+ startDate: {type: Date},
history: Array,
completed: {type: Boolean, 'default': false},
- repeat: {
+ repeat: { // used only for 'weekly' frequency, TODO: Rename to daysOfWeek or something with 'week'
m: {type: Boolean, 'default': true},
t: {type: Boolean, 'default': true},
w: {type: Boolean, 'default': true},
diff --git a/website/views/shared/tasks/task.jade b/website/views/shared/tasks/task.jade
index 05661a939e..2afe1608a0 100644
--- a/website/views/shared/tasks/task.jade
+++ b/website/views/shared/tasks/task.jade
@@ -166,22 +166,56 @@ li(bindonce='list', id='task-{{::task.id}}', ng-repeat='task in obj[list.type+"s
// if Daily, calendar
fieldset(ng-if='::task.type=="daily"', class="option-group")
legend.option-title=env.t('repeat')
- ul.repeat-days(bindonce)
- // note, does not use data-toggle="buttons-checkbox" - it would interfere with our own click binding
+
+ // Choose the frequency
+ ul.priority-multiplier
li
- button(ng-class='{active: task.repeat.su}', type='button', ng-click='task.challenge.id || (task.repeat.su = !task.repeat.su)') {{::moment.weekdaysMin(0)}}
+ button(ng-class='{active: task.frequency=="daily"}', type='button', ng-click='task.frequency="daily"')='Daily'
li
- button(ng-class='{active: task.repeat.m}', type='button', ng-click='task.challenge.id || (task.repeat.m = !task.repeat.m)') {{::moment.weekdaysMin(1)}}
- li
- button(ng-class='{active: task.repeat.t}', type='button', ng-click='task.challenge.id || (task.repeat.t = !task.repeat.t)') {{::moment.weekdaysMin(2)}}
- li
- button(ng-class='{active: task.repeat.w}', type='button', ng-click='task.challenge.id || (task.repeat.w = !task.repeat.w)') {{::moment.weekdaysMin(3)}}
- li
- button(ng-class='{active: task.repeat.th}', type='button', ng-click='task.challenge.id || (task.repeat.th = !task.repeat.th)') {{::moment.weekdaysMin(4)}}
- li
- button(ng-class='{active: task.repeat.f}', type='button', ng-click='task.challenge.id || (task.repeat.f= !task.repeat.f)') {{::moment.weekdaysMin(5)}}
- li
- button(ng-class='{active: task.repeat.s}', type='button', ng-click='task.challenge.id || (task.repeat.s = !task.repeat.s)') {{::moment.weekdaysMin(6)}}
+ button(ng-class='{active: task.frequency=="weekly"}', type='button', ng-click='task.frequency="weekly"')='Weekly'
+
+ // If frequency is daily
+ fieldset(ng-show='task.frequency=="daily"')
+ | Once every
+ select(ng-model='task.everyX')
+ option(ng-repeat='x in [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]') {{x}}
+ | days
+ | Start date:
+ p(class='input-group')
+ input(type='text' class='form-control' datepicker-popup ng-model='task._dateString' ng-change='updateTaskStartDate(task)' is-open='flagDict.opened')
+ span(class='input-group-btn')
+ button(type="button" ng-click="open($event)")
+ span.input-group-addon.glyphicon.glyphicon-calendar
+ // If frequency is weekly
+ fieldset(ng-show='task.frequency=="weekly"')
+ | Once every
+ select(ng-model='task.everyX')
+ option(ng-repeat='x in [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]') {{x}}
+ | weeks on these days:
+ ul.repeat-days(bindonce)
+ // note, does not use data-toggle="buttons-checkbox" - it would interfere with our own click binding
+ li
+ button(ng-class='{active: task.repeat.su}', type='button', ng-click='task.challenge.id || (task.repeat.su = !task.repeat.su)') {{::moment.weekdaysMin(0)}}
+ li
+ button(ng-class='{active: task.repeat.m}', type='button', ng-click='task.challenge.id || (task.repeat.m = !task.repeat.m)') {{::moment.weekdaysMin(1)}}
+ li
+ button(ng-class='{active: task.repeat.t}', type='button', ng-click='task.challenge.id || (task.repeat.t = !task.repeat.t)') {{::moment.weekdaysMin(2)}}
+ li
+ button(ng-class='{active: task.repeat.w}', type='button', ng-click='task.challenge.id || (task.repeat.w = !task.repeat.w)') {{::moment.weekdaysMin(3)}}
+ li
+ button(ng-class='{active: task.repeat.th}', type='button', ng-click='task.challenge.id || (task.repeat.th = !task.repeat.th)') {{::moment.weekdaysMin(4)}}
+ li
+ button(ng-class='{active: task.repeat.f}', type='button', ng-click='task.challenge.id || (task.repeat.f= !task.repeat.f)') {{::moment.weekdaysMin(5)}}
+ li
+ button(ng-class='{active: task.repeat.s}', type='button', ng-click='task.challenge.id || (task.repeat.s = !task.repeat.s)') {{::moment.weekdaysMin(6)}}
+ |
+ | Start date:
+ p(class='input-group')
+ input(type='text' class='form-control' datepicker-popup ng-model='task._dateString' ng-change='updateTaskStartDate(task)' is-open='flagDict.opened')
+ span(class='input-group-btn')
+ button(type="button" ng-click="open($event)")
+ span.input-group-addon.glyphicon.glyphicon-calendar
+
// if Reward, pricing
fieldset.option-group.option-short(ng-if='task.type=="reward" && !task.challenge.id')