Skip to content

Instantly share code, notes, and snippets.

@lenalebt
Last active January 11, 2024 02:55
Show Gist options
  • Save lenalebt/8e58aa21392c388d8c7d65bad3206e35 to your computer and use it in GitHub Desktop.
Save lenalebt/8e58aa21392c388d8c7d65bad3206e35 to your computer and use it in GitHub Desktop.
"Rollover Daily Todos" for Obsidian Templater

Rollover Daily Todos for Obsidian, but with the Templater Plugin

Rollver daily todos is a really nice extension for Obsidian.md that takes TODOs from yesterdays daily notes and rolls them over to today's notes. It has support for Obsidians built-in templates, but does - to my understanding - not really work well with the Templater Plugin. At least, I was unable to get it to work :-). Also, I wished it had some way to tell me that TODOs have been rolled over a few times already. Doing it this way is my way of working around these limitations.

I took some of the code of that "rollover daily todos" plugin and made it into a templater user script.

How to use

  • Copy rollover_daily_todos.js into your templater user scripts directory.
  • Include it in your daily notes template, like so:
# Tasks

<% await tp.user.rollover_daily_todos() %>

If your folder id different from "Tasks/Daily", you might need to call it like this:

# Tasks

<% await tp.user.rollover_daily_todos(folder="My/Daily/Notes/Folder") %>
  • Activate templater plugin in the settings. Make sure to tick "Trigger templater on new file creation" (2), enable folder templates (3) and add your daily notes template image

  • Import the user script like this in the settings (1): image

async function getAllUnfinishedTodos(file, tasksHeader) {
const contents = await this.app.vault.read(file);
const contentsForDailyTasks = contents.split(tasksHeader)[1] || contents;
const unfinishedTodosRegex = /\t*- \[ \].*/g
const unfinishedTodos = Array.from(contentsForDailyTasks.matchAll(unfinishedTodosRegex)).map(([todo]) => todo)
return unfinishedTodos;
}
function getLastDailyNote(folder, format) {
const { moment } = window
// get all notes in directory that aren't null
const dailyNoteFiles = this.app.vault.getAllLoadedFiles()
.filter(file => file.path.startsWith(folder))
.filter(file => file.basename != null)
// remove notes that are from the future
const todayMoment = moment()
let dailyNotesTodayOrEarlier = []
dailyNoteFiles.forEach(file => {
if (moment(file.basename, format).isSameOrBefore(todayMoment, 'day')) {
dailyNotesTodayOrEarlier.push(file)
}
})
// sort by date
const sorted = dailyNotesTodayOrEarlier.sort((a, b) => moment(b.basename, format).valueOf() - moment(a.basename, format).valueOf());
return sorted[1];
};
async function rollover_daily_todos(folder = "Tasks/Daily", format = "YYYY-MM-DD", tasksHeader = "# Tasks", rolloverMarkCharacter = "❗") {
return (await getAllUnfinishedTodos(getLastDailyNote(folder, format), tasksHeader)).map(task => `${task}${rolloverMarkCharacter}`).join('\n');
}
module.exports = rollover_daily_todos;
@VOOM108
Copy link

VOOM108 commented Feb 22, 2023

This is cool - for now, this works better for me, than the rollover-plugin. And one less plugin.

I make blocks with rollover-headers by having them as tasks, capitalized, bold and marked. Also, the tasks beneath are indented, so I can fold the block to show only the header. If there is no task for a block to roll over, I can even check the header, and it won't roll over.

@michaelm2391
Copy link

This script breaks Templater - see this thread:

SilentVoid13/Templater#1129

@lenalebt
Copy link
Author

@michaelm2391 interesting, as I am using it with templater and don't have the issues you are describing. I mean, I wrote it for that purpose 🤔 . From your error message, it probably fails in line 2, and probably this is due to it not finding any previous daily note!? Just thinking it through. What happens if you put a daily note in the folder Tasks/Daily (independent of that you have your daily notes somewhere else maybe)?

@michaelm2391
Copy link

I performed some basic troubleshooting and ascertained that my formatting must have been weird somehow. (I had C+P'ed your code, pasted it into a text file and renamed the extension to .js). After simply downloading the raw file of your above code it now causes no error.

Sorry for the confusion!

@lenalebt
Copy link
Author

No problem, stuff like that happens to me as well 😄

@cwilliams5
Copy link

This is sick, thanks for sharing. Just getting into Obsidian and was banging my head on the issue until I realized rollover daily todos is fighting with templater. This is an elegant solution, thanks for sharing it.

I would love if it had a few optional flags up top. Personally, I want it to (1) remove the todos from their old note as it brings them over or they just duplicate forever and (2) bring children with them. Both are features of the original plugin. And (3) I'd like it to go back more than one day, sometimes I dont use OD for a while. Like a check past X daily notes feature.

I could try my awful coding hand at this, but figured I'd throw it out here first. Either way thanks for sharing xD

@lenalebt
Copy link
Author

lenalebt commented Aug 7, 2023

(1) and (3) should be the default behavior of the script 🤔 .

(2) would be nice, I'm very open for changes to the script, but I won't find time for that currently...

@mariomui
Copy link

const { vault } = this.app;
const { moment } = window;

const getFolder = () => 'A_fleeting_notes';

async function rollover(
  folder = getFolder(),
  format = 'YYYY-MM-DD',
  tasksHeader = '- [ ] ==TASKS==',
  rolloverMarkCharacter = '❗'
) {
  const todos = await getAllUnfinishedTodos(
    getLastDailyNote(folder, format),
    tasksHeader
  );
  const note_content = todos.join('\n');
  return note_content;
}

async function getAllUnfinishedTodos(abstractFile, tasksHeader) {
  const contents = await vault.read(abstractFile);
  // const contentsForDailyTasks = contents.split(tasksHeader).first() || contents;
  const regex = /([\s\S]*?)(?=\n- \[\ \] ==TASKS==)/g;
  const matchedContents = regex.exec(contents);
  const unfinishedTodos = Array.from(
    matchedContents[0].matchAll(/\t*- \[ \].*/g)
  );

  return unfinishedTodos;
}

function getLastDailyNote(folder, format) {
  // get all notes in directory that aren't null
  const dailyNoteFiles = vault
    .getAllLoadedFiles()
    .filter((file) => file.path.startsWith(folder))
    .filter((file) => file.basename != null);

  // remove notes that are from the future
  const todayMoment = moment();
  let dailyNotesTodayOrEarlier = [];
  dailyNoteFiles.forEach((file) => {
    if (moment(file.basename, format).isSameOrBefore(todayMoment, 'day')) {
      dailyNotesTodayOrEarlier.push(file);
    }
  });

  // sort by date
  const sorted = dailyNotesTodayOrEarlier.sort(
    (a, b) =>
      moment(b.basename, format).valueOf() -
      moment(a.basename, format).valueOf()
  );
  return sorted[1];
}

module.exports = rollover;

I modified it so you could use the script along with rollover.

Anything above the - [ ] ==TODOS== mark will be scraped and rolled over. Everything below would be ignored.

This allows a template like :

<% await tp.user.rollover() %>

  • ==TASKS==

  • Work on napkin math ==30 minutes==

You could complete napkin math today, and tomorrow napkin math will come back. Its a hard coded dailly recurrying task.

Because rollover will also strip out any non tasks and leave them behind, the # Tasks will also be stripped away, the solution is to create your own seperator that is in task format. I've chosen - [ ] ==TASKS==

@roman-balzer
Copy link

async function getAllUnfinishedTodos(file, tasksHeader) {
    const contents = await this.app.vault.read(file);
    const contentsForDailyTasks = contents.split(tasksHeader)[1] || contents;
    const unfinishedTodosRegex = /\t*- \[ \].*/g
    const unfinishedTodos = Array.from(contentsForDailyTasks.matchAll(unfinishedTodosRegex)).map(([todo]) => todo)
    const fileWithoutTasks = contents.split(/.*\t*- \[ \].*\n?/g).join('')
    await this.app.vault.modify(file,fileWithoutTasks)
    return unfinishedTodos;
  }
  
  
  function getLastDailyNote(folder, format) {
      const { moment } = window
  
      // get all notes in directory that aren't null
      const dailyNoteFiles = this.app.vault.getAllLoadedFiles()
        .filter(file => file.path.startsWith(folder))
        .filter(file => file.basename != null)
  
      // remove notes that are from the future
      const todayMoment = moment()
      let dailyNotesTodayOrEarlier = []
      dailyNoteFiles.forEach(file => {
        if (moment(file.basename, format).isSameOrBefore(todayMoment, 'day')) {
          dailyNotesTodayOrEarlier.push(file)
        }
      })
  
      // sort by date
      const sorted = dailyNotesTodayOrEarlier.sort((a, b) => moment(b.basename, format).valueOf() - moment(a.basename, format).valueOf());
      return sorted[1];
  };
  
  const defaultOptions = {
    folder: "Tasks/Daily", 
    format: "YYYY-MM-DD", 
    tasksHeader: "# Tasks", 
    prefix: '',
    suffix: "❗"
  }
  async function rollover_daily_todos(options) {
      const { folder, format, tasksHeader, prefix, suffix } = { ...defaultOptions, ...options }
      return (await getAllUnfinishedTodos(getLastDailyNote(folder, format), tasksHeader)).map(task => `${prefix}${task}${suffix}`).join('\n');
  }
  
  module.exports = rollover_daily_todos;

Sharing my version as well.
Additional features in this:

  • Lines containing Tasks in previous note will be removed
  • Using an option object for function,
  • with additional prefix field, so that I can add them into a nested callout

@arbois
Copy link

arbois commented Sep 13, 2023

just a note to say thanks for an elegant solution that appears (so far) to not interfere with other plugins

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment