Last active
March 20, 2024 20:14
-
-
Save sychou/2c58c074f5b6ddef2a2ca4340ecec5ef to your computer and use it in GitHub Desktop.
Task Processor Obsidian Plugin
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
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; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"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