Task Batch actions

name

Task Batch actions

description

A plugin to batch task related actions inside a note

icon

check_circle

instructions

This is a plugin related to doing batch actions related to tasks. In the near future, I'll aim to add batch moving and completing tasks to this plugin in the future, but for now it only tags.

Select the text of a task, click on "Extract Selection" and select the inline tag you want to tag the tasks. This will automatically add the note tags to all selected tasks.



List of features:

Tag tasks in batch: Add an inline tag to various tasks at once

Move tasks in batch: Move a group of tasks to another note

Delete tags in batch: Deletes an inline tag from various tasks at once

Edit task details in batch: Edits the task details of a group of tasks. The current supported task details are:

Hide Until

Duration

Priority/Urgency

Task Score

Entry: https://github.com/Matheus-Felipe-C/task-batch-actions/lib/compiled.js


linkUpdate History

August 31st, 2024

Added support for the function batchEditTaskDetails to be opened through NoteOption

August 29th, 2024

Bugfix: Changing the duration of tasks no longer causes the task to disappear from calendar view

August 23rd, 2024

Improved error handling when trying to delete inline tags from a task with no inline tags

August 22nd, 2024

Feature: Added feature to edit task domains of multiple tasks

July 25th, 2024

Improved error handling to be more intuitive to users

Bugfix: Texts with literal strings (inline code) wouldn't get properly parsed

Now new inline tags added through the function _batchTagTasks are added in a new line

P.S. - Inline tags deleted through the plugin do not delete the new line

May 27th, 2024

Added feature to delete inline tags from multiple tasks

May 8th, 2024

Internal update by adding a helper function, should not affect end users

May 7th, 2024

Added feature to move tasks between notes

Added support for Esbuild

March 21st, 2024

Updated function names to make them more readable

February 15th, 2024

Bugfix: function Batch tag tasks in the NoteOption now won't return an exception anymore

December 28th, 2023

Added function Batch tag tasks in the NoteOption.

November 30th, 2023

Bugfix: tasks now get tagged if they contain links inside the task title.

November 10th, 2023

First version out in the open


linkCode block

// Javascript updated 31/08/2024, 09:18:00 by Amplenote Plugin Builder from source code within "https://github.com/Matheus-Felipe-C/task-batch-actions/lib/compiled.js"
(() => {
// plugin.js
var plugin = {
replaceText: {
"Tag tasks in batch": async function(app, text) {
try {
await this._batchTagTasks(app, text);
} catch (err) {
console.log(err);
app.alert(err);
}
},
"Move tasks in batch": async function(app, text) {
try {
await this._batchMoveTasks(app, text);
} catch (error) {
console.log(error);
app.alert(error);
}
},
"Delete tags in batch": async function(app, text) {
try {
await this._batchDeleteTags(app, text);
} catch (error) {
console.log(error);
app.alert(error);
}
},
"Edit task details in batch": async function(app, text) {
try {
await this._batchEditTaskDetails(app, text);
} catch (error) {
console.log(error);
app.alert(error);
}
}
},
noteOption: {
"Tag tasks in batch": async function(app, noteUUID) {
try {
const taskNames = await this._transformTaskIntoText(app, noteUUID);
await this._batchTagTasks(app, taskNames);
} catch (err) {
console.log(err);
app.alert(err);
}
},
"Move tasks in batch": async function(app, noteUUID) {
try {
const taskNames = await this._transformTaskIntoText(app, noteUUID);
await this._batchMoveTasks(app, taskNames);
} catch (error) {
console.log(error);
app.alert(error);
}
},
"Delete tags in batch": async function(app, noteUUID) {
try {
const taskNames = await this._transformTaskIntoText(app, noteUUID);
await this._batchDeleteTags(app, taskNames);
} catch (error) {
console.log(error);
app.alert(error);
}
},
"Edit task details in batch": async function(app, noteUUID) {
try {
const taskNames = await this._transformTaskIntoText(app, noteUUID);
await this._batchEditTaskDetails(app, taskNames);
} catch (error) {
console.log(error);
app.alert(error);
}
}
},
/**
* Adds inline tags to the selected tasks
* @param {*} app
* @param {string} text With the tasks separated by linebreaks
* @returns {void}
*/
async _batchTagTasks(app, text) {
const systemNotes = await app.filterNotes();
const tasksToTag = await this._transformTextIntoTaskArray(app, text);
const inlineTag = await app.prompt("Choose an inline tag", {
inputs: [
{ label: "Inline tag selection", type: "note", options: systemNotes }
]
});
if (!inlineTag)
throw new Error("Inline tag field cannot be empty");
console.log("Inline tag to add:\n");
console.log(inlineTag);
await Promise.all(tasksToTag.map(async (task) => {
const newtaskName = ` ${task.content}
[${inlineTag.name}](https://www.amplenote.com/notes/${inlineTag.uuid})`;
await app.updateTask(task.uuid, { content: newtaskName });
}));
console.log("Tasks tagged successfully!");
},
/**
* Moves the selected tasks to another note
* @param {*} app
* @param {string} text With tasks separated by linebreaks
* @returns {void}
*/
async _batchMoveTasks(app, text) {
const systemNotes = await app.filterNotes();
const tasksToMove = await this._transformTextIntoTaskArray(app, text);
const targetNote = await app.prompt("Choose a note", {
inputs: [
{ label: "Target note", type: "note", options: systemNotes }
]
});
if (!targetNote)
throw new Error("Target note cannot be empty");
console.log("Note to move tasks to: ");
console.log(targetNote);
await Promise.all(tasksToMove.map(async (task) => {
const noteUUID = targetNote.uuid;
await app.updateTask(task.uuid, { noteUUID });
console.log(task);
}));
},
/**
* Deletes the inline tags of the selected tasks.
* @param {*} app
* @param {string} text With the selected tasks separated by linebreaks
* @returns {void}
*/
async _batchDeleteTags(app, text) {
const taskArray = await this._transformTextIntoTaskArray(app, text);
let noteUUIDs = [];
taskArray.map((task) => {
const noteLink = task.content.match(/\((.*?)\)/g).map((match) => match.slice(1, -1));
noteLink.forEach((link) => {
const uuid = link.match(/\/([^\/]+)$/);
noteUUIDs.push(uuid[1]);
});
});
noteUUIDs = [...new Set(noteUUIDs)];
if (!noteUUIDs)
throw new Error("No inline tags found to delete");
let tagOptions = await Promise.all(noteUUIDs.map(async (note) => {
note = await app.findNote({ uuid: note });
const object = {
label: note.name,
value: `[${note.name}](https://www.amplenote.com/notes/${note.uuid})`
};
return object;
}));
console.log("NoteUUIDs of the inline tags:");
console.log(tagOptions);
const selectedTag = await app.prompt("Choose an inline tag to remove", {
inputs: [
{ label: "Inline tag selection", type: "select", options: tagOptions }
]
});
if (!selectedTag)
throw new Error("Inline tag cannot be empty");
await Promise.all(taskArray.map(async (task) => {
if (task.content.includes(selectedTag)) {
let newTaskContent = task.content.replace(selectedTag, "");
newTaskContent = newTaskContent.replace(/\\/g, "");
console.log("new task name: " + newTaskContent);
await app.updateTask(task.uuid, { content: newTaskContent });
}
}));
console.log("Inline tag removed!");
},
/**
* Edits the task details of all selected tasks
* All of the attributes that can be edited through this function are:
*
* - `Hide Until` as a date
* - `Priority` and `Urgency`
* - `Duration` in minutes
* @param {*} app
* @param {string} text
*/
async _batchEditTaskDetails(app, text) {
const tasks = await this._transformTextIntoTaskArray(app, text);
const prompt = await app.prompt("Select the properties to edit", {
inputs: [
{ label: "Hide task until (full dates only)", placeholder: "July 26", type: "text" },
{ label: "Duration (in minutes and numbers only)", type: "text" },
{ label: "important", type: "checkbox" },
{ label: "Urgent", type: "checkbox" },
{ label: "Score (numbers only)", type: "text" }
]
});
const [hideUntil, duration, priority, urgent, score] = prompt;
let durationNumber;
let scoreNumber;
if (duration) {
durationNumber = parseInt(duration);
if (isNaN(durationNumber))
throw new Error("Duration field must be a number");
}
if (score) {
scoreNumber = parseInt(score);
if (isNaN(scoreNumber))
throw new Error("Score field must be a number");
}
await Promise.all(tasks.map(async (task) => {
console.log(task);
await app.updateTask(task.uuid, { important: priority, urgent });
if (scoreNumber)
await app.updateTask(task.uuid, { score });
if (hideUntil) {
let hideDate = /* @__PURE__ */ new Date(`${hideUntil}, 2024`);
hideDate = hideDate.getTime() / 1e3;
console.log("Hide date in seconds: " + hideDate);
await app.updateTask(task.uuid, { hideUntil: hideDate });
}
if (durationNumber) {
if (!task.startAt) {
throw new Error("Can't set a duration for a task that isn't scheduled");
}
const startTime = task.startAt * 1e3;
const durationDate = new Date(startTime + durationNumber * 60 * 1e3);
console.log("Time to edit: ", durationDate);
await app.updateTask(task.uuid, { endAt: Math.floor(durationDate.getTime() / 1e3) });
}
}));
},
/**
* Gets the tasks from the selected note and transforms them into text
* @param {*} app
* @param {string} noteUUID
* @returns {string} with the tasks, each separated by a line break
*/
async _transformTaskIntoText(app, noteUUID) {
const noteTasks = await app.getNoteTasks({ uuid: noteUUID });
if (!noteTasks)
throw new Error("Current note has no tasks");
const taskOptions = noteTasks.map((task) => ({
label: task.content,
type: "checkbox"
}));
const selectedTasks = await app.prompt("Select the tasks you want to act on", {
inputs: taskOptions
});
if (!selectedTasks)
throw new Error("Choose at least one tasks in order to proceed");
let taskNames = "";
for (let i = 0; i < selectedTasks.length; i++) {
if (!selectedTasks[i])
continue;
let count = i;
let rightTask = taskOptions.find((el) => {
if (count == 0)
return true;
count--;
});
if (rightTask && rightTask.label) {
let contentToAdd = rightTask.label;
contentToAdd = contentToAdd.replace(/\\[\r\n]+/g, " ");
contentToAdd = contentToAdd.replace(/(?:__|[*#])|\[(.*?)\]\(.*?\)/gm, "$1");
taskNames += `${contentToAdd}
`;
console.log(contentToAdd);
}
}
taskNames = taskNames.trim();
return taskNames;
},
/**
* Transforms the tasks in string format into the original task object array
* @param {*} app
* @param {string} text With the selected tasks separated by line breaks
* @returns {tasks[]} Array with the selected tasks
*/
async _transformTextIntoTaskArray(app, text) {
const noteUUID = app.context.noteUUID;
const noteTasks = await app.getNoteTasks({ uuid: noteUUID });
if (!noteTasks)
throw new Error("Choose at least one tasks in order to proceed");
const textTaskArray = text.split("\n");
const taskArray = noteTasks.filter((task) => {
let taskContent = task.content.replace(/\\[\r\n]+/g, " ");
taskContent = taskContent.replace(/(?:__|[*#])|\[(.*?)\]\(.*?\)|`([^`]*)`/gm, "$1$2");
for (let i = 0; i < textTaskArray.length; i++) {
if (taskContent.includes(textTaskArray[i].trim())) {
return task;
}
}
return false;
});
console.log("Chosen tasks:");
console.log(taskArray);
return taskArray;
}
};
var plugin_default = plugin;
return plugin;
})()