Displaying Overdue, To-Do (today) and Done (today) tasks in collapsible callouts containing tasks count in header suitable for side panel in Obsidian

I want to share the little dataviewjs code that I have made with the help of great forum members. (Special thanks to @holroy who I have learned a lot from.) I’ve searched the forum and couldn’t find the exact thing that I wanted. I hope somebody found it useful; that’s the reason I’m sharing this, and if you have a better solution, please let me know.

The wish:
Creating 3 Obsidian collapsible callouts in daily note template for overdue, to-do (today’s tasks), and done (done today), displaying the tasks’ count in each callout header so that one can see the tasks’ count without expanding the callout. That’s specifically useful when there is no task in the desired category.

The Logic:
The code tries to create task queries (with the tasks plugin) using dataviewjs. You may ask why I didn’t use just the tasks plugin. That’s because with just using the Tasks plugin, I could not find a way to extract the task count from the query and display it in the callout title. Therefore, I have used dataview to get tasks’ counts. Now, you may ask why I didn’t use just dataview query twice (once for getting tasks’ counts and once for getting the actual list). That’s because I wanted to display tasks in shortmode (just task description and icons for start, due, etc.), which is not possible in dataview queries. (as far as I know)

Appearance:
Lastly, I wanted to display daily notes in Obsidian’s side panel. Therefore, I have tried to obtain a minimal appearance with little distance between elements and fixed size scrollable callouts. The custom CSS that I have tried to make with the help of related posts is not complete yet, but it works enough for the basic needs. You can change and complete it, if you have even wanted to have such lists and appearance.

The Code (daily notes template)

---
date: "{{date:YYYY-MM-DD}}"
tags:
  - notes/cat/daily
parent: "[[MOC Daily notes]]"
cssclass: "daily_note"
---

```dataviewjs
const {dailyUtils} = await cJS()
var prev_next_days = dailyUtils.create_prev_next_links(dv)

// --- OVERDUE ---
const dv_qry_overdue = `\
TASK
FROM #task
WHERE contains(tags, "#task") and ( \
  (date(scheduled) and scheduled < date("{{date:YYYY-MM-DD}}") ) or \
  (date(due) and due < date("{{date:YYYY-MM-DD}}") ) \
  ) \
  and !completed`

const tasks_qry_overdue = `\
> (scheduled before {{date:YYYY-MM-DD}}) OR (due before {{date:YYYY-MM-DD}})
> not done
> short mode
> hide tags
> hide task count
> group by filename
> sort by priority`

// --- TO-DO ---
const dv_qry_todo = `\
TASK
FROM #task
WHERE contains(tags, "#task") and ( \
  (date(scheduled) and scheduled = date("{{date:YYYY-MM-DD}}") ) or \
  (date(due) and due = date("{{date:YYYY-MM-DD}}") ) \
  ) \
  and !completed`

const tasks_qry_todo = `\
> (scheduled on {{date:YYYY-MM-DD}}) OR (due on {{date:YYYY-MM-DD}})
> not done
> short mode
> hide tags
> hide task count
> group by filename
> sort by priority`

// --- DONE ---
const dv_qry_done = `\
TASK
FROM #task
WHERE contains(tags, "#task") and 
  completed and
  ( date(completion) and completion = date("{{date:YYYY-MM-DD}}") )`

const tasks_qry_done = `\
> done on {{date:YYYY-MM-DD}} 
> short mode
> hide tags
> hide task count
> group by filename
> sort by priority`

// --- OVERDUE ---
var taskQuery = await dv.query(dv_qry_overdue) 
if ( !taskQuery.successful )
  dv.paragraph("~~~~\n" + taskQuery.error + "\n~~~~") 
else {
  const callout_overdue = `> [!danger|daily]- Overdue (${ taskQuery.value.values.length }) \n> \`\`\`tasks\n` + tasks_qry_overdue + `\n> \`\`\`\n`
  // --- TO-DO ---
  taskQuery = await dv.query(dv_qry_todo) 
  if ( !taskQuery.successful ) 
    dv.paragraph("~~~~\n" + taskQuery.error + "\n~~~~") 
  else {
    const callout_todo = `> [!todo|daily]+ To-Do (${ taskQuery.value.values.length }) \n> \`\`\`tasks\n` + tasks_qry_todo + `\n> \`\`\`\n`
    // --- DONE / CANCELLED ---
    taskQuery = await dv.query(dv_qry_done) 
    if ( !taskQuery.successful )  
      dv.paragraph("~~~~\n" + taskQuery.error + "\n~~~~") 
    else {
      const callout_done = `> [!done|daily]- Done (${ taskQuery.value.values.length }) \n> \`\`\`tasks\n` + tasks_qry_done + `\n> \`\`\`\n`
      //Write the result
      dv.el('prev_next', prev_next_days);
	 dv.el('callout_overdue', callout_overdue);
	  dv.el('separator_1', `&nbsp;`);
	  dv.el('callout_todo', callout_todo);
	  dv.el('separator_2', `&nbsp;`);
	  dv.el('callout_done', callout_done);
	  //dv.el('dvq_done', `\n\`\`\`dataview\n` + dv_qry_done + `\n\`\`\`\n`);
    }
  }
}

The CSS

.markdown-source-view.daily_note .cm-editor .cm-scroller {
  font-family: 'Cascadia Code', Menlo, Monaco, 'Courier New', monospace !important; 
  font-size: 11px;
}

/* Tasks grouping options */
.daily_note h4.tasks-group-heading {
    background-color: rgba(255, 255, 0, 0.15) !important;
    font-size: 9px;
    line-height: 9px;
}

.daily_note .tasks-list-text {
    font-size: 10px; 
}

.callout[data-callout-metadata="daily"]{
  --p-spacing: .8rem;
  --shown-line-count: 6; 
  padding: 0px;
}

.callout[data-callout-metadata="daily"] .callout-title {
   padding: 5px;
}

.callout[data-callout-metadata="daily"] .callout-content {
  max-height: calc(var(--line-height-normal) * 1rem * var(--shown-line-count));
  padding-left: 30px;
}

Screenshot

That’s it! Have a good luck.

Does it rely on anything besides Dataview? I pasted the code into one of my notes and I just get an error: SyntaxError: Unexpected token '{'
I assume this is talking about the first line const {dailyUtils} = await cJS(), which I’m not familiar with what it’s looking for.

There is a small JavaScript code that I’ve used to create dynamic previous and next day links. You can install the CustomJS plugin and save the following code in the defined scripts folder inside its options.

class dailyUtils {
	create_prev_next_links(dv) {
		var folder_path = dv.current().file.folder
		folder_path = require('path').dirname(folder_path);
		var p = dv.pages('"' + folder_path + '"').where(p => p.file.day).map(p => [p.file.name, p.file.day.toISODate()]).sort(p => p[1]);
		var t = dv.current().file.day ? dv.current().file.day.toISODate() : luxon.DateTime.now().toISODate();
		var format = app['internalPlugins']['plugins']['daily-notes']['instance']['options']['format'] || 'YYYY-MM-DD';
		var current = '(' + moment(t).format(format) + ')';
		var nav = [];
		var today = p.find(p => p[1] == t);
		var next = p.find(p => p[1] > t);
		var prev = undefined;
		p.forEach(function (p, i) {
			if (p[1] < t) {
				prev = p;
			}
		});
		nav.push(prev ? '[[' + prev[0] + '|◀]]' : '◁');
		//nav.push(today ? today[0] : none);
		nav.push(today ? today[0] : current);
		nav.push(next ? '[[' + next[0] + '|▶]]' : '▷');

		return nav[0] + ' **' + nav[1] + '** ' + nav[2];
	}
}

The code is from another post that I’ve modified a little bit; unfortunately, I can’t remember the post’s owner to refer.
Alternatively, you can remove the following three lines if you don’t need those links.

const {dailyUtils} = await cJS()
var prev_next_days = dailyUtils.create_prev_next_links(dv)
dv.el('prev_next', prev_next_days);

So, you need Dataview, Tasks and CustomJS plugins. Sorry, that I’ve forgotten to mention the last plugin. It’s not part of the main idea. Let me know if that works for you.

I rem’d out the three lines and still get the error. Looking through the code, something’s not right. I’m nowhere close to a coding expert, but this doesn’t look like valid code to me:

if ( !taskQuery.successful )
  dv.paragraph("~~~~\n" + taskQuery.error + "\n~~~~") 
else {

There are many instances that look like this. Did your code somehow not paste correctly?

I’ve also commented the mentioned lines. Here is the code again in case of incorrect pasting from last time, and hope it works for you. Have you enabled JavaScript queries in Dataview options? That works fine for me. Are your Obsidian and its plugins up-to-date?

//const {dailyUtils} = await cJS()
//var prev_next_days = dailyUtils.create_prev_next_links(dv)

// --- OVERDUE ---
const dv_qry_overdue = `\
TASK
FROM #task
WHERE contains(tags, "#task") and ( \
  (date(scheduled) and scheduled < date("2024-11-15") ) or \
  (date(due) and due < date("2024-11-15") ) \
  ) \
  and !completed`

const tasks_qry_overdue = `\
> (scheduled before 2024-11-15) OR (due before 2024-11-15)
> not done
> short mode
> hide tags
> hide task count
> group by filename
> sort by priority`

// --- TO-DO ---
const dv_qry_todo = `\
TASK
FROM #task
WHERE contains(tags, "#task") and ( \
  (date(scheduled) and scheduled = date("2024-11-15") ) or \
  (date(due) and due = date("2024-11-15") ) \
  ) \
  and !completed`

const tasks_qry_todo = `\
> (scheduled on 2024-11-15) OR (due on 2024-11-15)
> not done
> short mode
> hide tags
> hide task count
> group by filename
> sort by priority`

// --- DONE / CANCELLED ---
const dv_qry_done = `\
TASK
FROM #task
WHERE contains(tags, "#task") and 
  completed and
  ( date(completion) and completion = date("2024-11-15") )`

const tasks_qry_done = `\
> (done on 2024-11-15) OR (cancelled on 2024-11-15)
> short mode
> hide tags
> hide task count
> group by filename
> sort by priority`

// --- OVERDUE ---
var taskQuery = await dv.query(dv_qry_overdue) 
if ( !taskQuery.successful )
  dv.paragraph("~~~~\n" + taskQuery.error + "\n~~~~") 
else {
  const callout_overdue = `> [!danger|daily]- Overdue (${ taskQuery.value.values.length }) \n> \`\`\`tasks\n` + tasks_qry_overdue + `\n> \`\`\`\n`
  // --- TO-DO ---
  taskQuery = await dv.query(dv_qry_todo) 
  if ( !taskQuery.successful ) 
    dv.paragraph("~~~~\n" + taskQuery.error + "\n~~~~") 
  else {
    const callout_todo = `> [!todo|daily]+ To-Do (${ taskQuery.value.values.length }) \n> \`\`\`tasks\n` + tasks_qry_todo + `\n> \`\`\`\n`
    // --- DONE / CANCELLED ---
    taskQuery = await dv.query(dv_qry_done) 
    if ( !taskQuery.successful )  
      dv.paragraph("~~~~\n" + taskQuery.error + "\n~~~~") 
    else {
      const callout_done = `> [!done|daily]- Done (${ taskQuery.value.values.length }) \n> \`\`\`tasks\n` + tasks_qry_done + `\n> \`\`\`\n`
      //Write the result
      //dv.el('prev_next', prev_next_days);
	  dv.el('callout_overdue', callout_overdue);
	  dv.el('separator_1', `&nbsp;`);
	  dv.el('callout_todo', callout_todo);
	  dv.el('separator_2', `&nbsp;`);
	  dv.el('callout_done', callout_done);
	  //dv.el('dvq_done', `\n\`\`\`dataview\n` + dv_qry_done + `\n\`\`\`\n`);
    }
  }
}

Here is the original md file:
Daily notes Tmp.md (2.7 KB)

@jpfieber , this is correct code syntax for an alternation to show an error message if the query fails.

But another “depencendy” of this query is that it is meant to be used as a core template for a daily note, so if you don’t instantiate it through the core template plugin, then the parts with {{date:...}} will not get populated correctly, which might cause various error messages to occur.

1 Like

All in all I kind of like the work you’ve done here, although I might have coded it slightly different myself. But if it works, it works, and in programming there has always been more than one way to do it!

I hope you don’t mind me giving a few comments though, and I’ll base them on the query for doing the done query:

  • When you’re doing FROM #task, it shouldn’t be necessary to also do contains(tags, "#task") as these should mean the same. The difference is that the FROM makes the filtering on an earlier level, which in this case is the better option, as the WHERE clause then don’t need to run through all the files in the vault but only those having that particular tag
  • I’m thinking that it should suffice to do completion and completion = ... , unless you’re using that property for other stuff as well. This is a minor thingy though, but you shouldn’t need to convert a date into date :wink:
  • You’ve already set the date property for the file you insert the template, using the {{date:YYYY-MM-DD}}, and this should set the file.day value to be that date. This can be re-used in the script, so I’m thinking I would use const thisDate = dv.current().file.day at the top of the script, and then use ${thisDate} within the various queries.

Applying these minor changes, and the query could look like:

const thisDate = dv.current().file.day
...
const dv_qry_done = `
  TASK 
  FROM #task
  WHERE completed and
      ( completion and completion = ${ thisDate } )
`

This should work the same… :slight_smile:


Related to your comments on the logic itself. At first it seems like you’re doing the work twice executing the queries first using dataview and then using tasks. (Or even six times, since you do three separate queries in both) However, it’s understandable since I’m not sure if there is an api to execute a tasks query from within javascript. I’ve not seen it so far.

However, there is an option with a TASK query within dataview to change the visual format of the task lists. It’s not as neat as the options you’ve used in the tasks queries, but essentially if you do FLATTEN ... as visual, then the text generated by the ... part will be presented as the result of the query.

Here is a rude and crude example:

```dataview
TASK
WHERE completed OR completion
FLATTEN regexreplace(text, "\d{4}-\d{2}-\d{2}", "") as visual
LIMIT 30
```

And a random result:
image

So in theory, with a little more coding, you could possibly make the entire query with one query to dataview, and through some filtering and manipulating of that result, and still present the tasks as you do currently.

But let it said, your solution seem to work and you know what’s happening where in this query, and that could be a lot more worth than changing the script into something which you can’t adapt to your needs. Programming has that ability, to both be something unmanageable and something entirely different, and still produce results.


So all in all, kudos to you for piecing it together, and hopefully this post could give you a few pointers on other options being available as well for future endeavours.

1 Like

Ahh, I didn’t get that part, was trying to add it to an existing note. Makes sense now seeing the double brackets in the code. Thanks!

1 Like

@holroy, thank you so much. :blush: I’m relatively new to Obsidian, and over the past few months, I’ve been working on tailoring my vault alongside my job. The possibilities with Obsidian and its amazing community plugins are truly endless, but I wouldn’t have been able to achieve my goal without the support of community members like you.

Your recommendations have made the code much simpler and more readable:

  • Avoiding duplicate conditions
  • Skipping unnecessary date-to-date conversions
  • Using the file’s date property

I’ve tried using just Dataview, but as you mentioned, the results don’t compare to the Tasks plugin’s short mode, especially with its helpful features like editing and postponing tasks.

While my vault is small and manageable, I can see how querying a larger vault with thousands of notes could become slow and resource-intensive. I plan to refine the code further during my free time, trying to consolidate it into one query or, at the least, optimize it to use fewer queries.

Just one adjustment to your code:
Date comparisons in dataview queries don’t work correctly when using something like:

  ( scheduled and scheduled < ${ thisDate } ) 

I had to explicitly convert it to a date for it to work properly. Here is the final code (just reviewd parts) :


const thisDate = dv.current().file.day.toFormat("yyyy-MM-dd")

// --- OVERDUE ---
const dv_qry_overdue = `
TASK
FROM #task1
WHERE !completed and (
    ( scheduled and scheduled < date( ${ thisDate } ) ) or 
    ( due and due < date( ${ thisDate } ) )
    )
`

const tasks_qry_overdue = `\
> ( scheduled before ${ thisDate } ) OR ( due before ${ thisDate } )
> not done
> short mode
> hide tags
> hide task count
> group by filename
> sort by priority`

// --- TO-DO ---
const dv_qry_todo = `
TASK
FROM #task1
WHERE !completed and (
    ( scheduled and scheduled = date( ${ thisDate } ) ) or 
    ( due and due = date( ${ thisDate } ) ) 
    )
`

const tasks_qry_todo = `\
> ( scheduled on ${ thisDate } ) OR ( due on ${ thisDate } )
> not done
> short mode
> hide tags
> hide task count
> group by filename
> sort by priority`

// --- DONE ---
const dv_qry_done = `
TASK
FROM #task1
WHERE completed and 
    ( completion and completion = date( ${ thisDate } ) )
`

const tasks_qry_done = `\
> ( done on ${ thisDate } ) 
> short mode
> hide tags
> hide task count
> group by filename
> sort by priority`
1 Like