├── README.md └── of-weekly-report.omnijs /README.md: -------------------------------------------------------------------------------- 1 | # omnifocus-weekly-report 2 | Omnifocus-weekly-report is an omnifocus plugin, it generates weekly report in markdown format, and email it. 3 | ## Customization 4 | Omnifocus-weekly-report covers tasks in all folders in default, but you can specify which folder should be included by alter some constants at the head part of this plugin code. 5 | 6 | Also you can customize the email address To/Cc, and the subtitles. 7 | 8 | Default: 9 | ``` javascript 10 | const completedTitle = "COMPLETED" 11 | const todoTitle = "NEXT WEEK" 12 | 13 | const targetFolderName = null 14 | const emailTo = "" 15 | const emailCc = "" 16 | ``` 17 | 18 | Customization: 19 | ``` javascript 20 | const completedTitle = "COMPLETED THIS WEEK" 21 | const todoTitle = "TASKS NEXT WEEK" 22 | 23 | const targetFolderName = "my company name" 24 | const emailTo = "someone@mycompany.com" 25 | const emailCc = "otherone@mycompany.com" 26 | ``` -------------------------------------------------------------------------------- /of-weekly-report.omnijs: -------------------------------------------------------------------------------- 1 | /*{ 2 | "type": "action", 3 | "targets": ["omnifocus"], 4 | "author": "Lin Yiming", 5 | "email": "lynnmatrix@hotmail.com", 6 | "identifier": "com.omni-automation.of.weekly-report", 7 | "version": "1.0", 8 | "description": "Generete weekly report in markdown", 9 | "label": "weekly report", 10 | "shortLabel": "weekly report", 11 | "paletteLabel": "weekly report", 12 | }*/ 13 | 14 | 15 | 16 | (() => { 17 | const completedTitle = "COMPLETED" 18 | const todoTitle = "NEXT WEEK" 19 | const targetFolderName = null 20 | const emailTo = "" 21 | const emailCc = "" 22 | 23 | var action = new PlugIn.Action(function(selection, sender){ 24 | 25 | let previousWeek = getPreviousWeek() 26 | 27 | let targetFolder = null != targetFolderName? folderNamed(targetFolderName):null 28 | 29 | var report = reportFolder(targetFolder, 0) 30 | report.done.unshift(`## ${completedTitle}`) 31 | report.plan.unshift(`## ${todoTitle}`) 32 | var doneText = report.done.join('\n') 33 | var planText = report.plan.join('\n') 34 | let title = 'Weekly Report ' + formatDate(previousWeek[0])+'-' + formatDate(previousWeek[1]) 35 | let reportText = "# " + title + "\n\n" + doneText +"\n\n"+ planText 36 | 37 | var email = new Email() 38 | email.subject = title 39 | email.body = reportText 40 | email.receiver = emailTo 41 | email.carbonCopy = emailCc 42 | email.generate() 43 | }); 44 | 45 | return action; 46 | })(); 47 | 48 | function createReport() { 49 | return { 50 | 'done': [], 51 | 'plan': [] 52 | } 53 | } 54 | function concatReport(baseReport, report, parent=null) { 55 | if (parent && report.done.length > 0) { 56 | baseReport.done = baseReport.done.concat([parent]) 57 | } 58 | if (parent && report.plan.length > 0) { 59 | baseReport.plan = baseReport.plan.concat([parent]) 60 | } 61 | 62 | baseReport.done = baseReport.done.concat(report.done) 63 | baseReport.plan = baseReport.plan.concat(report.plan) 64 | } 65 | 66 | function appendDone(baseReport, done) { 67 | baseReport.done.push(done) 68 | } 69 | function appendPlan(baseReport, plan) { 70 | baseReport.plan.push(plan) 71 | } 72 | 73 | function reportFolder(folder, level) { 74 | var folderReport = createReport() 75 | if (folder != null && folder.status != Folder.Status.Active) { 76 | return folderReport 77 | } 78 | let projectPrefix = Array(level+3).fill('#').join('') + " " 79 | 80 | let subFolders = folder == null ? folders : folder.folders 81 | subFolders.forEach((subFolder) => { 82 | var subFolderReport = reportFolder(subFolder, level + 1) 83 | concatReport(folderReport, subFolderReport, projectPrefix + subFolder.name) 84 | }) 85 | 86 | let subProjects = folder == null ? projects : folder.projects 87 | subProjects.forEach((subProject) => { 88 | var subProjectReport = reportProject(subProject) 89 | concatReport(folderReport, subProjectReport, projectPrefix + subProject.name) 90 | }) 91 | 92 | return folderReport 93 | } 94 | 95 | function reportProject(project) { 96 | var projectReport = createReport() 97 | if (project.status != Project.Status.Active) { 98 | return projectReport 99 | } 100 | 101 | project.tasks.forEach((task) => { 102 | _taskReport = reportTask(task, 0) 103 | 104 | concatReport(projectReport, _taskReport) 105 | }) 106 | return projectReport 107 | } 108 | 109 | function reportTask(task, level) { 110 | var intent = getIntent(level) 111 | let taskTitle = generateTitle(level, task) 112 | 113 | var taskReport = createReport() 114 | var subTaskReport = createReport() 115 | 116 | task.tasks.forEach((subTask) => { 117 | concatReport(subTaskReport, reportTask(subTask, level + 1)) 118 | }) 119 | 120 | concatReport(taskReport, subTaskReport, taskTitle) 121 | 122 | if (taskReport.done.length == 0) { 123 | 124 | if (task.completed && duringPreviousWeek(task.completionDate)) { 125 | appendDone(taskReport, taskTitle) 126 | if (task.note) { 127 | appendDone(taskReport, intent + " * " + task.note) 128 | } 129 | } 130 | } 131 | 132 | if (taskReport.plan.length == 0) { 133 | // 不是所有未完成都会列为计划 134 | if (![Task.Status.Completed, Task.Status.Dropped].includes(task.taskStatus)) { 135 | if (task.deferDate == null || task.deferDate < getNextWeekEnd()) { 136 | appendPlan(taskReport, taskTitle) 137 | } 138 | } 139 | } 140 | 141 | return taskReport 142 | } 143 | 144 | function generateTitle(level, task) { 145 | let intent = getIntent(level) 146 | let prefix = "- [" + (task.completed?"x":" ") + "] " 147 | let tagText = task.tags.map(tag => ` @${tag.name}`).join('') 148 | let dueText = (!task.completed && task.dueDate)? " =>|" + task.dueDate.toLocaleString(): "" 149 | return intent + prefix + task.name + tagText + dueText 150 | } 151 | 152 | function duringPreviousWeek(date) { 153 | let previousWeek = getPreviousWeek() 154 | return date >= previousWeek[0] && date < previousWeek[1] 155 | } 156 | 157 | function getIntent(level) { 158 | var intent = "" 159 | for (i=0;i < level;i++) { 160 | intent += " " 161 | } 162 | return intent 163 | } 164 | 165 | function getPreviousWeek(){ 166 | var date = new Date(); 167 | var day = date.getDay(); 168 | var prevWeekStart = new Date(); 169 | if(date.getDay() == 0){ 170 | prevWeekStart.setDate(date.getDate() - 7); 171 | } else { 172 | prevWeekStart.setDate(date.getDate() - day); 173 | } 174 | 175 | var cal = Calendar.current 176 | 177 | prevWeekStart = cal.startOfDay(prevWeekStart) 178 | var prevWeekEnd = new Date(prevWeekStart) 179 | prevWeekEnd.setDate(prevWeekStart.getDate() + 7) 180 | return [prevWeekStart, prevWeekEnd] 181 | } 182 | 183 | function getNextWeekEnd() { 184 | var nextWeekEnd = new Date(getPreviousWeek()[1]) 185 | nextWeekEnd.setDate(nextWeekEnd.getDate + 7) 186 | return nextWeekEnd 187 | } 188 | 189 | function formatDate(date) { 190 | 191 | var d = new Date(date), 192 | month = '' + (d.getMonth() + 1), 193 | day = '' + d.getDate(), 194 | year = d.getFullYear(); 195 | 196 | if (month.length < 2) 197 | month = '0' + month; 198 | if (day.length < 2) 199 | day = '0' + day; 200 | 201 | return [year, month, day].join(''); 202 | } 203 | --------------------------------------------------------------------------------