Build a summary of tasks completed today
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// Build a summary of OmniFocus tasks completed today. | |
// | |
// This is intended to be run as a TextExpander macro, | |
// but will work anywhere you can invoke a JS script. | |
// | |
// v 1.0.2 (full release history at bottom) | |
var NoProjectMarker = "No Project"; | |
getTaskSummary(); | |
function getTaskSummary() { | |
var doc = Application('OmniFocus').defaultDocument; | |
var thisMorning = startOfDay(); | |
var tasks = doc.flattenedTasks.whose({completionDate: {'>=':thisMorning} })(); | |
if (tasks.length == 0) { | |
return "No tasks completed. Do something without writing it down?"; | |
} | |
// Group tasks by project, then extract progressed projects (projects with | |
// completed tasks) from allProjects. This gives us the list of projects | |
// in the same order as OmniFocus’s UI. | |
var groupedTasks = groupArrayByKey(tasks, function(v) { | |
var proj = v.containingProject(); | |
if (proj) { | |
return proj.id(); | |
} | |
return NoProjectMarker; | |
}); | |
var allProjects = doc.flattenedProjects(); | |
var progressedProjects = allProjects.filter(function(p) { | |
return p.id() in groupedTasks; | |
}); | |
// Build the summary | |
var summary = progressedProjects.reduce(function(s,p){ | |
return s + summaryForProject(p); | |
}, ""); | |
var tasksWithNoProject = groupedTasks[NoProjectMarker]; | |
if (tasksWithNoProject) { | |
summary += summaryForTasksWithTitle(tasksWithNoProject, "No Project\n"); | |
} | |
return summary; | |
// This needs to be in this scope because it captures groupedTasks | |
function summaryForProject(p) { | |
var projectID = p.id(); | |
var tasks = groupedTasks[projectID].filter(function(t) { | |
return projectID != t.id(); // Don't include the project itself | |
}); | |
return summaryForTasksWithTitle(tasks, projectSummaryLine(p)); | |
} | |
function summaryForTasksWithTitle(tasks, title) { | |
return title + tasks.reduce(summaryForTasks,"") + "\n"; | |
} | |
} | |
// Reducing function to summarize a list of tasks | |
function summaryForTasks(s,t) { | |
return s + lineForTask(t); | |
} | |
// Create a summary line for a project | |
function projectSummaryLine(project) { | |
var tokens = []; | |
tokens.push(project.name()); | |
var remainingStatus = remainingStatusForProject(project); | |
if (remainingStatus != "") { | |
tokens.push("(" + remainingStatus + ")"); | |
} | |
return tokens.join(" ") + "\n"; | |
} | |
// This is appended to the end of a project line | |
function remainingStatusForProject(project) { | |
if (!project.completedByChildren()) { | |
return ""; | |
} | |
var remainingCount = project.numberOfTasks() - project.numberOfCompletedTasks(); | |
if (remainingCount == 0) { | |
return "complete"; | |
} | |
return remainingCount + " remaining"; | |
} | |
function lineForTask(task) { | |
return " ✓ " + task.name() + "\n"; | |
} | |
// Group an array of items by the key returned by this function | |
function groupArrayByKey(array,keyForValue) { | |
var dict = {}; | |
for (var i = 0; i < array.length; i++) { | |
var value = array[i]; | |
var key = keyForValue(value); | |
if (!(key in dict)) { | |
dict[key] = []; | |
} | |
dict[key].push(value); | |
} | |
return dict; | |
} | |
function startOfDay() { | |
// The day started at midnight this morning | |
var d = new Date(); | |
d.setHours(0); | |
d.setMinutes(0); | |
d.setSeconds(0); | |
return d; | |
} | |
// Release History | |
// 1.0 Initial release | |
// 1.0.1 Fix for tasks without a project | |
// 1.0.2 Relaxed the type checking (!== is too strict) |
Thanks, John! I’ve relaxed the type checking to just check for truthiness.
Fantastic! Thanks for this.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
I thought I could clone & send a pull request back, but apparently not. On line 44
tasksWithNoProject
is sometimes undefined for me, but sinceundefined != null
it still tries to make a summary and crashes. Updating line 44 toif (tasksWithNoProject !== null && 'undefined' !== typeof tasksWithNoProject) {
fixed the problem for me.