mirror of
https://github.com/HabitRPG/habitica.git
synced 2025-12-17 14:47:53 +01:00
Enabled repeatables
This commit is contained in:
@@ -4,8 +4,10 @@
|
||||
Cron and time / day functions
|
||||
------------------------------------------------------
|
||||
*/
|
||||
import _ from 'lodash'; // eslint-disable-line lodash/import-scope
|
||||
import defaults from 'lodash/defaults';
|
||||
import invert from 'lodash/invert';
|
||||
import moment from 'moment';
|
||||
import 'moment-recur';
|
||||
|
||||
export const DAY_MAPPING = {
|
||||
0: 'su',
|
||||
@@ -17,6 +19,8 @@ export const DAY_MAPPING = {
|
||||
6: 's',
|
||||
};
|
||||
|
||||
export const DAY_MAPPING_STRING_TO_NUMBER = invert(DAY_MAPPING);
|
||||
|
||||
/*
|
||||
Each time we perform date maths (cron, task-due-days, etc), we need to consider user preferences.
|
||||
Specifically {dayStart} (custom day start) and {timezoneOffset}. This function sanitizes / defaults those values.
|
||||
@@ -25,13 +29,13 @@ export const DAY_MAPPING = {
|
||||
|
||||
function sanitizeOptions (o) {
|
||||
let ref = Number(o.dayStart || 0);
|
||||
let dayStart = !_.isNaN(ref) && ref >= 0 && ref <= 24 ? ref : 0;
|
||||
let dayStart = !Number.isNaN(ref) && ref >= 0 && ref <= 24 ? ref : 0;
|
||||
|
||||
let timezoneOffset;
|
||||
let timezoneOffsetDefault = Number(moment().zone());
|
||||
if (_.isFinite(o.timezoneOffsetOverride)) {
|
||||
if (Number.isFinite(o.timezoneOffsetOverride)) {
|
||||
timezoneOffset = Number(o.timezoneOffsetOverride);
|
||||
} else if (_.isFinite(o.timezoneOffset)) {
|
||||
} else if (Number.isFinite(o.timezoneOffset)) {
|
||||
timezoneOffset = Number(o.timezoneOffset);
|
||||
} else {
|
||||
timezoneOffset = timezoneOffsetDefault;
|
||||
@@ -81,44 +85,65 @@ export function startOfDay (options = {}) {
|
||||
export function daysSince (yesterday, options = {}) {
|
||||
let o = sanitizeOptions(options);
|
||||
|
||||
return startOfDay(_.defaults({ now: o.now }, o)).diff(startOfDay(_.defaults({ now: yesterday }, o)), 'days');
|
||||
return startOfDay(defaults({ now: o.now }, o)).diff(startOfDay(defaults({ now: yesterday }, o)), 'days');
|
||||
}
|
||||
|
||||
/*
|
||||
Should the user do this task on this date, given the task's repeat options and user.preferences.dayStart?
|
||||
*/
|
||||
|
||||
export function shouldDo (day, dailyTask, options = {}) {
|
||||
export function shouldDo (day, dailyTask) {
|
||||
if (dailyTask.type !== 'daily') {
|
||||
return false;
|
||||
}
|
||||
let o = sanitizeOptions(options);
|
||||
let startOfDayWithCDSTime = startOfDay(_.defaults({ now: day }, o));
|
||||
|
||||
// The time portion of the Start Date is never visible to or modifiable by the user so we must ignore it.
|
||||
// Therefore, we must also ignore the time portion of the user's day start (startOfDayWithCDSTime), otherwise the date comparison will be wrong for some times.
|
||||
// NB: The user's day start date has already been converted to the PREVIOUS day's date if the time portion was before CDS.
|
||||
let taskStartDate = moment(dailyTask.startDate).zone(o.timezoneOffset);
|
||||
day = moment(day).startOf('day').toDate();
|
||||
let startDate = moment(dailyTask.startDate).startOf('day').toDate();
|
||||
|
||||
taskStartDate = moment(taskStartDate).startOf('day');
|
||||
if (taskStartDate > startOfDayWithCDSTime.startOf('day')) {
|
||||
return false; // Daily starts in the future
|
||||
}
|
||||
if (dailyTask.frequency === 'daily') { // "Every X Days"
|
||||
if (!dailyTask.everyX) {
|
||||
return false; // error condition
|
||||
}
|
||||
let daysSinceTaskStart = startOfDayWithCDSTime.startOf('day').diff(taskStartDate, 'days');
|
||||
let daysOfTheWeek = [];
|
||||
|
||||
return daysSinceTaskStart % dailyTask.everyX === 0;
|
||||
} else if (dailyTask.frequency === 'weekly') { // "On Certain Days of the Week"
|
||||
if (!dailyTask.repeat) {
|
||||
return false; // error condition
|
||||
if (dailyTask.repeat) {
|
||||
for (let [repeatDay, active] of Object.entries(dailyTask.repeat)) {
|
||||
if (active) daysOfTheWeek.push(parseInt(DAY_MAPPING_STRING_TO_NUMBER[repeatDay], 10));
|
||||
}
|
||||
}
|
||||
let dayOfWeekNum = startOfDayWithCDSTime.day(); // e.g., 0 for Sunday
|
||||
|
||||
return dailyTask.repeat[DAY_MAPPING[dayOfWeekNum]];
|
||||
} else {
|
||||
return false; // error condition - unexpected frequency string
|
||||
if (dailyTask.frequency === 'daily') {
|
||||
if (!dailyTask.everyX) return false; // error condition
|
||||
let schedule = moment(startDate).recur()
|
||||
.every(dailyTask.everyX).days();
|
||||
return schedule.matches(day);
|
||||
} else if (dailyTask.frequency === 'weekly') {
|
||||
let schedule = moment(startDate).recur();
|
||||
|
||||
if (dailyTask.everyX > 1) {
|
||||
schedule = schedule.every(dailyTask.everyX).weeks();
|
||||
}
|
||||
|
||||
schedule = schedule.every(daysOfTheWeek).daysOfWeek();
|
||||
|
||||
return schedule.matches(day);
|
||||
} else if (dailyTask.frequency === 'monthly') {
|
||||
let schedule = moment(startDate).recur();
|
||||
|
||||
let differenceInMonths = moment(day).month() + 1 - moment(startDate).month() + 1;
|
||||
let matchEveryX = differenceInMonths % dailyTask.everyX === 0;
|
||||
|
||||
if (dailyTask.weeksOfMonth && dailyTask.weeksOfMonth.length > 0) {
|
||||
schedule = schedule.every(daysOfTheWeek).daysOfWeek()
|
||||
.every(dailyTask.weeksOfMonth).weeksOfMonthByDay();
|
||||
} else if (dailyTask.daysOfMonth && dailyTask.daysOfMonth.length > 0) {
|
||||
schedule = schedule.every(dailyTask.daysOfMonth).daysOfMonth();
|
||||
}
|
||||
|
||||
return schedule.matches(day) && matchEveryX;
|
||||
} else if (dailyTask.frequency === 'yearly') {
|
||||
let schedule = moment(startDate).recur();
|
||||
|
||||
schedule = schedule.every(dailyTask.everyX).years();
|
||||
|
||||
return schedule.matches(day);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
@@ -8,15 +8,15 @@ fieldset.option-group.advanced-option(ng-show="task.type === 'daily'")
|
||||
|
||||
br
|
||||
|
||||
//- select.form-control(ng-model='task._edit.frequency', ng-disabled='!canEdit(task)')
|
||||
//- option(value='daily')=env.t('daily')
|
||||
//- option(value='weekly')=env.t('weekly')
|
||||
//- option(value='monthly')=env.t('monthly')
|
||||
//- option(value='yearly')=env.t('yearly')
|
||||
|
||||
select.form-control(ng-model='task._edit.frequency', ng-disabled='!canEdit(task)')
|
||||
option(value='weekly')=env.t('repeatWeek')
|
||||
option(value='daily')=env.t('repeatDays')
|
||||
option(value='daily')=env.t('daily')
|
||||
option(value='weekly')=env.t('weekly')
|
||||
option(value='monthly')=env.t('monthly')
|
||||
option(value='yearly')=env.t('yearly')
|
||||
|
||||
//- select.form-control(ng-model='task._edit.frequency', ng-disabled='!canEdit(task)')
|
||||
//- option(value='weekly')=env.t('repeatWeek')
|
||||
//- option(value='daily')=env.t('repeatDays')
|
||||
|
||||
include ./dailies/repeat_options
|
||||
|
||||
@@ -29,6 +29,6 @@ fieldset.option-group.advanced-option(ng-show="task.type === 'daily'")
|
||||
input(type="radio", ng-model='task._edit.repeatsOn', value='dayOfWeek')
|
||||
=env.t('dayOfWeek')
|
||||
|
||||
//- .form-group
|
||||
//- legend.option-title=env.t('summary')
|
||||
//- div {{summary}}
|
||||
.form-group
|
||||
legend.option-title=env.t('summary')
|
||||
div {{summary}}
|
||||
Reference in New Issue
Block a user