Skip to content

Instantly share code, notes, and snippets.

@FeralFlora
Last active March 4, 2025 12:44
Show Gist options
  • Save FeralFlora/405404e2cfa1fc75053a2b2674b4de42 to your computer and use it in GitHub Desktop.
Save FeralFlora/405404e2cfa1fc75053a2b2674b4de42 to your computer and use it in GitHub Desktop.
Task toggling function for Obsidian tasks. Useful for bulk editing tasks to toggle status, change tags and the scheduled date.

Works on Dataview task objects (for example dervied from nextActions.js) and toggles status, tags and scheduled dates of markdown tasks. Preserves Obsidian block IDs at the end of lines.

Toggles tasks done using the executeToggleTaskDoneCommand API of the Tasks plugin for Obsidian.

/**
* Toggles a task in a file based on the given Dataview task action and the given status or scheduled date.
*
* @param {Object} action - The Dataview task object containing the task details.
* @param {string} status - The status to toggle the task to.
* @param {string} [date=null] - The date to schedule the task, if applicable.
* @return {Promise<void>} A promise that resolves when the task is toggled.
*/
async function processFile(file, action, toggledTask) {
const cachedData = await app.vault.cachedRead(file);
return app.vault.process(file, (data) => {
if (cachedData !== data) {
data = cachedData;
}
let lines = data.split("\n");
lines[action.line] = toggledTask;
data = lines.join("\n");
return data;
});
}
function taskStale(action, lines) {
return lines[action.line] !== action.text;
}
async function toggleTask(action, status, date = null, newTag = null) {
const dv = app.plugins.plugins["dataview"].api;
const { DateTime} = dv.luxon;
const file = app.vault.getFileByPath(action.path);
const data = await app.vault.cachedRead(file);
const lines = data.split("\n");
const tasksAPI = app.plugins.plugins["obsidian-tasks-plugin"].apiV1;
let taskText = "";
if (taskStale(action, data)) {
taskText = lines[action.line];
} else {
taskText = `- [ ] ${action.text}`;
}
// Regular expressions for task checkboxes, tags and scheduled dates
const checkboxRegex = /\-\s{1}\[.\]/g;
const todoTagRegex = /(\#todo\/\S+)/m;
const scheduledDateRegex = /⏳\s{1}(\d{4}-\d{2}-\d{2})/gu;
const path = action.path;
let toggledTask = "";
switch(status) {
case "done":
toggledTask = tasksAPI.executeToggleTaskDoneCommand(taskText, path);
break;
case "dropped":
toggledTask = taskText.replace(checkboxRegex, "- [-]").replace(todoTagRegex, "");
break;
case "half-done":
toggledTask = taskText.replace(checkboxRegex, "- [/]");
break;
case "schedule":
const scheduledDate = date;
// I had to add .c after scheduled for luxon to be able to parse the date.
const scheduled = action.scheduled ? true : false;
if (scheduled) {
const oldScheduledDate = DateTime.fromObject(action.scheduled.c).toFormat("yyyy-MM-dd");
if (scheduledDate === oldScheduledDate) {
new Notice("Task already scheduled today");
return;
}
}
const regexMatch = taskText.match(scheduledDateRegex);
const caretIndex = taskText.indexOf("^");
if (regexMatch) {
toggledTask = taskText.replace(scheduledDateRegex, (match, p1) => {
return match.replace(p1, scheduledDate);
});
} else if (caretIndex !== -1) {
const beforeCaret = taskText.slice(0, caretIndex);
const afterCaret = taskText.slice(caretIndex);
toggledTask = `${beforeCaret}⏳ ${scheduledDate} ${afterCaret}`;
} else {
toggledTask = taskText + `⏳ ${scheduledDate}`;
}
break;
case "re-tag":
if (!newTag.startsWith("#")) {
newTag = `#${newTag}`;
}
toggledTask = taskText.replace(todoTagRegex, newTag);
break;
default:
new Notice("Status not recognised");
return;
}
await processFile(file, action, toggledTask);
}
module.exports = toggleTask;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment