Quickstart: Habit tracker
In this quickstart, you will create a simple habit tracker for your daily, weekly, or monthly habits.
How it works
When you select a repeating task and run the script, it will show the current streak and the days you completed the task.
Set it up
- Open the web app or desktop app.
- Click the MilkScript button at the top right, then click New...
- Copy the code below and paste it into the script editor.
const Status = {
Hit: '✅',
Miss: '❌',
Empty: '⚪️',
};
function* timeline(start, end) {
for (
let current = start;
current.getTime() < end.getTime();
current.setDate(current.getDate() + 1)
) {
yield current.getTime();
}
return end.getTime();
}
function* chunks(array, size) {
for (let i = 0; i < array.length; i += size) {
yield array.slice(i, i + size);
}
}
function getStatus(date, { hits, misses }) {
return hits.has(date)
? Status.Hit
: misses.has(date)
? Status.Miss
: Status.Empty;
}
function formatDay(date, context) {
return `\t${getStatus(date, context)}`;
}
function formatWeekHeader(week) {
return week.map(day =>
'\t' + new Date(day)
.toLocaleString('default', { weekday: 'short' })
.slice(0, 2)
)
.join('')
}
function formatWeek(week, i, context) {
const startOfMonth =
i === 0
? week[week.length - 1]
: week.find((date) => new Date(date).getDate() === 1);
return [
startOfMonth != null
? new Date(startOfMonth).toLocaleString('default', { month: 'short' })
: ' ',
...week.map((day) => formatDay(day, context)),
].join(' ');
}
function formatYear([year, [firstWeek, ...weeks]], context) {
return [
`\t\t\t ${year}`,
formatWeekHeader(firstWeek),
...[firstWeek, ...weeks].map((week, i) => formatWeek(week, i, context)),
].join('\n');
}
function createContext(task) {
const { min, hits, misses } = task
.getTaskSeries()
.getTasks()
.reduce(
({ min, hits, misses }, task) => {
const completed = task.getCompletedDate()?.setHours(0, 0, 0, 0);
const due = task.getDueDate()?.setHours(0, 0, 0, 0);
return {
min: Math.min(
min,
completed ?? Number.MAX_SAFE_INTEGER,
due ?? Number.MAX_SAFE_INTEGER
),
hits: completed != null && task.getPostponed() === 0 ? new Set([...hits, completed]) : hits,
misses: new Set(
[
...(due != null && due !== completed ? [...misses, due] : misses),
].filter((miss) => miss !== completed)
),
};
},
{ min: Number.MAX_SAFE_INTEGER, hits: new Set(), misses: new Set() }
);
return {
hits,
misses,
start: new Array(7)
.fill(new Date(min))
.map(
(date, i) =>
new Date(date.getFullYear(), date.getMonth(), date.getDate() - i)
)
.find((date) => date.getDay() === 0),
end: new Date(new Date().setHours(0, 0, 0, 0)),
};
}
function formatStreaks(days, context) {
const streaks = days.reduce(
([current, ...rest], date) => {
const dateStatus = getStatus(date, context);
return dateStatus === Status.Empty
? [current, ...rest]
: dateStatus === Status.Hit
? [current + 1, ...rest]
: current === 0
? [current, ...rest]
: [0, ...[current, ...rest]];
},
[0]
);
const [latest] = streaks;
const longest = streaks.reduce(
(result, streak) => Math.max(result, streak),
0
);
return [`Current streak: ${latest}`, `Longest streak: ${longest}`].join('\n');
}
function formatTask(task) {
const context = createContext(task);
const days = Array.from(timeline(context.start, context.end));
const weeks = Array.from(chunks(days, 7));
const years = weeks.reduce((result, week) => {
const year = new Date(week[week.length - 1]).getFullYear();
return new Map([...result, [year, [...(result.get(year) ?? []), week]]]);
}, new Map());
return [
formatStreaks(days, context),
...Array.from(years.entries()).map((year) => formatYear(year, context)),
].join('\n\n');
}
const [task] = rtm.getSelectedTasks();
task == null
? rtm.newMessage(
'Oops! There are no selected task.',
'Please select a repeating task and try again.'
)
: !task.isRecurring()
? rtm.newMessage(
'Oops! This task does not repeat.',
'Please set a repeat interval for the task and try again.'
)
: rtm.newFile(
formatTask(task),
rtm.MediaType.TEXT,
`Habit tracker: ${task.getName()}`
);
- At the top left, click Untitled script.
- Enter a name for your script (e.g. Habit tracker), then close the script editor.
Try it out
- Select a task that repeats every day, week, or month.
- Click the MilkScript button at the top right.
- Click the recently created script, then click Yes, run script.
- When the script execution completes, you'll see the current streak and the progress you're making with the task.