I have adapted this GTD Obsidian tool by @AlanG for my Daily tasks. It shows four sections: tasks Started Today, Completed today, Pending as of Today, and Due today. They work in connection with the date in the Daily note. I use the format YYYYMMDD, so you will have to perform some minor changes if you work with the date-dashed version.
The core of the script is almost identical to the original GTD. I use the same core functions and variables. I also added some comments to make it self-explanatory.
tasksTodayDaily.js
/*
* ---------------------
* CONFIGURATION OPTIONS
* ---------------------
*
* Add the folders, tags, and headings which should be excluded.
* Tasks found on pages with these tags or paths will not appear in the Tasks page.
* Headings will to exclude tasks that fall under a heading with this name.
*/
const globalExclude = {
folders: [
'Extras'
],
tags: [
'#exclude-master-tasklist',
// '#completed'
],
headings: [
'🌱 Daily Habits'
]
}
/*
* When displaying tasks from a project, tasks that fall under headings with these
* names won't have these headings displayed when showing the project info
*/
const hideSubsectionName = [
'Todo',
'Tasks'
]
/*
* ----------------------------
* END OF CONFIGURATION OPTIONS
* ----------------------------
*/
// Set up the variables to store the tasks and projects
const tasks = []
const noNextAction = []
const excludeItems = ['', ...globalExclude.tags]
globalExclude.folders.forEach(folder => excludeItems.push(`"${folder}"`))
const globalExcludeString = excludeItems.join(' AND -')
// console.log("globalExcludeString", globalExcludeString)
// Define groups for Daily tasklist page
/*
For the daily view we have so far these groups:
* Tasks started today
* Tasks completed today
* Pending tasks carrying over from previous days
* Tasks due today, which already exists but doesn't properly work
*/
const Groups = {
Started: 0,
Completed: 1,
Pending: 2,
Due: 3,
Normal: 4
}
const self = dv.current();
const path = require('path');
/**
isStartedToday()
Shows only tasks that were started on the day of the Daily note
CRITERIA
--------
* Task text must not be empty
* Task must NOT be completed today
* Task header path corresponds to today's daily note and is not completed
* Task with plus-sign emoji and done emoji must not show as Started but Completed
* Completed tasks have four ways of marking:
* Done emoji with date with dashes
* Done emoji with date with no dashes
* The `completion `property` and a date with dashes
* The completion circle filled with a check mark
@param {task} t - An Obsidian task
@returns {boolean} true if matching all conditions, false otherwise
*/
function isStartedToday(t) {
let taskTextNotEmpty = t.text.length > 0;
// let notCompletedTodayDaily = self.file.path == t.header.path && !t.completed
let taskInDailyNotCompleted = self.file.path == t.header.path && !t.completed
let DateWithDashes = moment(self.file.name).format('YYYY-MM-DD')
let DateNoDashes = moment(self.file.name).format('YYYYMMDD')
// let containsDateNoDashes = t.text.match(self.file.name)
// && typeof(self.file.name) == 'number'
let matchPlusEmojiDateDashes = t.text.match('âž•' + DateWithDashes) // started today
let containsDateWithDashes = t.text.match(DateWithDashes)
let containsDateCompletionEmojiDashes = t.text.match('✅ ' + DateWithDashes) // completed
let containsDateCompletionEmojiNoDashes = t.text.match('✅ ' + DateNoDashes) // completed
let containsDateCompletionColonColon = t.text.match('completion:: ' + DateWithDashes) // completed
return (
// containsDateNoDashes
// || containsDateWithDashes
matchPlusEmojiDateDashes
&& !containsDateCompletionEmojiDashes
&& !containsDateCompletionEmojiNoDashes
&& !containsDateCompletionColonColon
&& taskInDailyNotCompleted
&& taskTextNotEmpty
)
}
/**
isCompletedToday()
Shows only tasks that were completed on the day of the Daily note
CRITERIA
--------
* Task text must not be empty
* Task must be completed
* Task header path corresponds to today's daily note
* Completed tasks have four ways of marking:
* Done emoji with date with dashes: '✅ ' + DateWithDashes
* Done emoji with date with no dashes: '✅ ' + DateNoDashes
* `completion` property and date with dashes: 'completion:: ' + DateWithDashes
* The completion circle filled with a check mark: t.completed
@param {task} t - An Obsidian task
@returns {boolean} true if matching all conditions, false otherwise
*/
function isCompletedToday(t) {
let taskTextNotEmpty = t.text.length > 0;
// capture tasks that are completed today
let completedTodayDaily = self.file.path == t.header.path && t.completed
// date in two formats
let DateWithDashes = moment(self.file.name).format('YYYY-MM-DD')
let DateNoDashes = moment(self.file.name).format('YYYYMMDD')
// capture tasks which text contains the current date, which could be start or completion
let containsDateNoDashes = t.text.match(self.file.name)
let containsDateWithDashes = t.text.match(DateWithDashes)
// boolean statements
let containsDateCompletionEmojiDashes = t.text.match('✅ ' + DateWithDashes)
let containsDateCompletionEmojiNoDashes = t.text.match('✅ ' + DateNoDashes)
let containsDateCompletionColonColon = t.text.match('completion:: ' + DateWithDashes)
return (
containsDateCompletionEmojiDashes
|| containsDateCompletionColonColon
|| containsDateCompletionEmojiNoDashes
|| completedTodayDaily
&& taskTextNotEmpty
)
}
/**
isOlderThanThisDailyNote()
Pending tasks.
Shows only older tasks that were not yet completed at the date of the Daily note
CRITERIA
--------
* Task text must not be empty
* Task must NOT be completed
* Task header path corresponds to today's daily note
* Task must be older than current daily note
* Task contains a `due` date property
@param {task} t - An Obsidian task
@returns {boolean} true if matching all conditions, false otherwise
*/
function isOlderThanThisDailyNote(t) {
let basename = path.parse(t.header.path).name
let DateWithDashes = moment(self.file.name).format('YYYY-MM-DD')
let mDailyDate = moment(self.file.name, 'YYYYMMDD', true); // Note
let mAnyTaskDate = moment(basename, 'YYYYMMDD', true); // task anywehere else
// < 0 does not include pending task if they were created today
// <= 0 includes pending task if they were created today
let isOlderNote = mAnyTaskDate.diff(mDailyDate) < 0 // task date - note date
let containsDateDueColonColon = t.text.match('due:: ' + DateWithDashes)
return (
isOlderNote
&& !t.completed
&& !containsDateDueColonColon
)
}
/**
tasksDueToday()
Shows only tasks that are marked due today in the Daily note
CRITERIA
--------
* Task text must not be empty
* Task must NOT be completed
* Task header path corresponds to today's daily note
* Task must be older than current daily note
@param {task} t - An Obsidian task
@returns {boolean} true if matching all conditions, false otherwise
*/
function tasksDueToday(t) {
let taskTextNotEmpty = t.text.length > 0;
let containsDateDueColonColon = false;
let includesDateDueColonColon = false
let DateWithDashes = moment(self.file.name).format('YYYY-MM-DD')
// includesDateDueColonColon = t.text.includes(DateWithDashes)
if (t.due) {
// console.log(DateWithDashes)
containsDateDueColonColon = t.text.match('due:: ' + DateWithDashes)
// console.log(containsDateDueColonColon)
}
return (
taskTextNotEmpty
&& t.due
&& !t.completed
//&& includesDateDueColonColon
&& containsDateDueColonColon
)
}
/**
* Take a task and the page it's from, and return a formatted element for the tasks array
* @param {*} task
* @param {*} page
* @returns {object}
*/
function generateTaskElement(task, page) {
// console.log(task)
let group = Groups.Normal
// if (task.tags.includes('#someday')) {
if (isStartedToday(task)) {
group = Groups.Started
} else if (isCompletedToday(task)) {
group = Groups.Completed
} else if (isOlderThanThisDailyNote(task)) {
group = Groups.Pending
} else if (tasksDueToday(task)) {
group = Groups.Due
}
return {
task: task,
date: (page.created ? page.created.ts || moment(page.created).valueOf() : null) || page.ctime.ts,
group: group
}
}
/*
* Process tasks
*/
dv.pages("-#exclude" + globalExcludeString)
.where(p => p.file.tasks.length && !p['kanban-plugin'] && !p['exclude_master_tasklist']).file
.forEach(page => {
page.tasks
.where(t =>
t.text && // where the task has text (is not blank)
// !t.completed && // and not completed // 1063
// t.completed && // 1004
!t.tags.includes('#exclude') && // and not excluded
(!t.header.subpath || !t.header.subpath.includes('exclude')) &&
!globalExclude.headings.includes(t.header.subpath) // and the heading is not excluded
)
.forEach(task => tasks.push(generateTaskElement(task, page)))
})
// Sort tasks into groups, then ascending by created time
tasks.sort((a, b) => a.group - b.group || a.date - b.date)
// console.log(tasks)
/**
* Output a formatted task list
* @param {number|null} group - Filter for tasks in a particular group, or null for all tasks
* @param {string|null} header - The text header for the task list
*/
function taskList(group, header) {
const list = isNaN(group) ? tasks : tasks.filter(x => x.group === group)
if (list.length) {
if (header) dv.header(2, header)
// dv.taskList(list.map(x => x.task), false)
dv.taskList(list.map(x => x.task), true)
return list.length
} else return 0
}
let todayWithDashes = "*" + moment(self.file.name).format('YYYY-MM-DD') + "*"
// Output the task list
let nStarted = taskList(Groups.Started, 'Started today: ' + todayWithDashes)
let nCompleted = taskList(Groups.Completed, 'Completed today: ' + todayWithDashes)
let nPending = taskList(Groups.Pending, 'Still Pending Tasks Today: ' + todayWithDashes)
let nDue = taskList(Groups.Due, 'Due Today: ' + todayWithDashes)
if (nStarted == 0 && nCompleted == 0 && nPending == 0 && nDue == 0) {
dv.header(2, "No tasks to report")
}
Put this script under your Scripts folder, but anywhere else should be okay. Call it from your Daily template with:
```dataviewjs
dv.view("tasksTodayDaily")
```
The only problem I have found so far is that line 66 const path = require('path');
does not work in the iPad. I guess not all the JavaScripts libraries are loaded in Obsidian for iOS.
The task menu called with Alt+T also works with this new script as well as the projects and their dates (due, completion, etc.)
Enjoy!
PS. This is a screenshot of a mockup Daily notes displaying all sections.
Suggestions and critique welcome.