Skip to content

Instantly share code, notes, and snippets.

@sychou
Last active March 20, 2024 20:14
Show Gist options
  • Save sychou/2c58c074f5b6ddef2a2ca4340ecec5ef to your computer and use it in GitHub Desktop.
Save sychou/2c58c074f5b6ddef2a2ca4340ecec5ef to your computer and use it in GitHub Desktop.
Task Processor Obsidian Plugin
/*
Task Processor Plugin for Obsidian
To use this plugin:
1. Create a directory named "task-proc" under your vault plugin directory
(e.g. .obsidian/plugins/task-proc)
2. Copy this file (main.js) and manifest.json to the "task-proc" directory
*/
// Until support is added for settings, be sure to manually check/change the
// const taskQueueFilePath to the correct path for your system.
const taskQueueFilePath = "Commonplace/Task Queue.md";
const sectionsToMarkSkipped = [
"Routines",
"Currently Working On...",
"Currently Reading and Watching...",
];
const { Plugin } = require("obsidian");
class TaskProcPlugin extends Plugin {
async onload() {
await this.loadSettings();
console.log("TaskProcPlugin loaded!");
this.addCommand({
id: "move-task-to-queue",
name: "Move Task to Queue",
editorCheckCallback: (checking, editor, view) => {
if (!checking) {
this.moveTextToDocument(editor, taskQueueFilePath);
}
return true; // Indicates the command is available in the current context
},
});
this.addCommand({
id: "move-task-to-daily-note",
name: "Move Task to Daily Note",
editorCheckCallback: (checking, editor, view) => {
if (!checking) {
// const fileName = this.getDateBasedFileName();
// this.moveTextToDocument(editor, fileName);
this.moveTextToDailyNote(editor);
}
return true; // Indicates the command is available in the current context
},
});
this.addCommand({
id: "clean-daily-note",
name: "Clean Daily Note",
editorCheckCallback: (checking, editor, view) => {
if (!checking) {
this.cleanDailyNote();
}
return true; // Indicates the command is available in the current context
},
});
}
async loadSettings() {
// Placeholder for loading plugin settings
console.log("Settings loaded.");
}
async moveTextToDocument(editor, filePath) {
if (editor) {
let textToMove = "";
let from, to;
// Check if there is a selection
const selection = editor.getSelection();
if (selection) {
// There's a selection. Expand it to full lines.
from = editor.getCursor("from");
to = editor.getCursor("to");
// Adjust `from` to start from the beginning of the first selected line
from.ch = 0;
// If the selection ends at the beginning of a line, do not include this line in the deletion
if (to.ch === 0 && to.line > from.line) {
to.line -= 1;
} else {
// Adjust `to` to end at the start of the line after the last selected line
to = { line: to.line + 1, ch: 0 };
}
// Get the full selected text including whole lines
textToMove = editor.getRange(from, to);
} else {
// No selection, use the full line at the cursor.
const cursor = editor.getCursor();
textToMove = editor.getLine(cursor.line);
from = { line: cursor.line, ch: 0 };
to = { line: cursor.line + 1, ch: 0 };
}
// Append the selected text or line to the specified file
await this.appendLineToFile(textToMove, filePath);
// Delete the selected text or line from the current file
editor.replaceRange("", from, to);
}
}
async appendLineToFile(text, filePath) {
let fileContent = "";
let newFileContent = "";
try {
// Check if the file exists
let file = this.app.vault.getAbstractFileByPath(filePath);
if (file) {
// If the file exists, read its content
fileContent = await this.app.vault.read(file);
} else {
// If the file does not exist, create it with an initial header
fileContent = "## Tasks\n";
await this.app.vault.create(filePath, fileContent);
}
// Find the "## Tasks" section and insert the new content
const lines = fileContent.split("\n");
let foundTasksSection = false;
let newSectionStartIndex = lines.length; // Default to end of file if no new section is found
for (let i = 0; i < lines.length; i++) {
if (lines[i].startsWith("## Tasks")) {
foundTasksSection = true;
} else if (foundTasksSection && lines[i].match(/^#+\s/)) {
// Found the start of a new section after "## Tasks"
newSectionStartIndex = i;
break;
}
}
// Clean up text so that it doesn't have empty newlines
text = text.replace(/\n\n/g, "");
// Insert the new content in the "## Tasks" section
if (foundTasksSection) {
// Remove any trailing newlines from the section before combining content
const beforeSection = lines
.slice(0, newSectionStartIndex)
.join("\n")
.replace(/\n+$/, "");
// Remove any preceding newlines from the section before combining content
const afterSection = lines
.slice(newSectionStartIndex)
.join("\n")
.replace(/^\n+/, "");
newFileContent = `${beforeSection}\n${text}\n${afterSection}`;
} else {
// If "## Tasks" section was not found, append it to the end of the file
// Remove any trailing newlines from the file before combining content
fileContent = fileContent.replace(/\n+$/, "");
newFileContent = `${fileContent}\n${text}`;
}
// Write the modified content back to the file
await this.app.vault.modify(file, newFileContent);
} catch (err) {
console.error("Error appending text to file:", err);
}
}
// Method to move text to today's daily note
async moveTextToDailyNote(editor) {
const dailyNotesPlugin = this.app.internalPlugins.plugins["daily-notes"];
if (dailyNotesPlugin && dailyNotesPlugin.enabled) {
const dailyNote = await this.app.internalPlugins
.getEnabledPluginById("daily-notes")
.getDailyNote();
if (dailyNote) {
const filePath = dailyNote.path; // Or use dailyNote.path for the full path
this.moveTextToDocument(editor, filePath);
} else {
console.error("Failed to get today's daily note.");
}
} else {
console.error("Daily Notes plugin is not enabled.");
}
}
async cleanDailyNote() {
const activeFile = this.app.workspace.getActiveFile();
if (!activeFile) {
console.error("No active note found.");
return;
}
let content = await this.app.vault.read(activeFile);
let lines = content.split("\n");
let currentSection = "";
let taskQueueLines = [];
let newLines = [];
lines.forEach((line, index) => {
const isHeader = line.match(/^##\s.+/);
const isTask = line.startsWith("- [ ]");
if (isHeader) {
currentSection = line.substring(3).trim();
newLines.push(line);
} else if (isTask) {
if (currentSection === "Tasks") {
taskQueueLines.push(line); // Collect task for the Task Queue
} else if (sectionsToMarkSkipped.includes(currentSection)) {
newLines.push(line.replace("[ ]", "[-]")); // Mark the task as skipped
} else {
newLines.push(line); // Include other tasks normally
}
} else {
newLines.push(line); // Add non-empty lines or follow-up empty lines after content
}
});
// Trim potential extra newlines at the end of the document
while (newLines[newLines.length - 1] === "") {
newLines.pop();
}
// Ensure there's a newline at the end of the document if it ends with content
newLines.push("");
// Update the active note with modified content
await this.app.vault.modify(activeFile, newLines.join("\n"));
// Append tasks to Task Queue if any
if (taskQueueLines.length > 0) {
await this.appendTasksToTaskQueue(taskQueueLines.join("\n") + "\n");
}
}
async appendTasksToTaskQueue(tasksText) {
// Trim to ensure we don't append just whitespace
tasksText = tasksText.trim();
// Proceed only if there's actual text to append
if (tasksText.length > 0) {
let taskQueueFile =
this.app.vault.getAbstractFileByPath(taskQueueFilePath);
if (!taskQueueFile) {
// If the Task Queue file doesn't exist, create it with the tasks text
await this.app.vault.create(taskQueueFilePath, tasksText);
} else {
// If it exists, read its content
let existingContent = await this.app.vault.read(taskQueueFile);
// Trim the right end of the existing content to remove trailing newlines and whitespace
existingContent = existingContent.replace(/\s+$/, "");
// Determine content to append; add a newline if the existing content is not empty
let contentToAppend =
existingContent.length > 0 ? "\n" + tasksText : tasksText;
// Append the tasks, ensuring no unnecessary empty lines at the end
await this.app.vault.modify(
taskQueueFile,
existingContent + contentToAppend,
);
}
}
}
}
module.exports = TaskProcPlugin;
{
"id": "task-proc",
"name": "Task Processor",
"version": "1.2.0",
"minAppVersion": "0.0.0",
"description": "An Obsidian task processor",
"author": "Sean Chou",
"authorUrl": "https://x.com/sychou"
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment