Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Tasks in AM from Obsidian Dialog #19

Merged
merged 3 commits into from
Dec 26, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
164 changes: 164 additions & 0 deletions src/addTaskModal.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
import { App, Modal, Setting, DropdownComponent, TextAreaComponent, FuzzySuggestModal, FuzzyMatch } from "obsidian";
import { Category } from "./interfaces";

function getTitleWithParent(category: Category, categories: Category[]): string {
let parent = category.parentId;

let parentTitle = [];
while (parent && parent !== "root") {
const parentCategory = categories.find(category => category._id === parent);
if (parentCategory) {
parentTitle.push(parentCategory.title);
parent = parentCategory.parentId;
} else {
break;
}
}
if (parentTitle.length > 0) {
return category.title + ` in ${parentTitle.reverse().join("/")}`;
}
return category.title;
}

// Suggester Modal Class for Category Selection
class CategorySuggesterModal extends FuzzySuggestModal<Category> {
getItems(): Category[] {
// Prepend a faux 'Inbox' category for matching purposes
const inboxCategory: Category = {
_id: "__inbox-faux__", // Arbitrary unique ID for the Inbox faux category
title: "Inbox",
type: "faux",
updatedAt: 0,
parentId: "root",
startDate: "",
endDate: "",
note: "",
isRecurring: false,
priority: "",
deepLink: "",
dueDate: "",
done: false,
};

// Include the Inbox at the beginning of the categories list
return [inboxCategory, ...this.categories];
}
getItemText(category: Category): string {
if (category.type === "faux") {
return "Inbox";
}
return getTitleWithParent(category, this.categories);
}
categories: Category[];
onChooseItem: (item: Category, _evt: MouseEvent | KeyboardEvent) => void;

constructor(app: App, categories: Category[], onChooseItem: (item: Category, _evt: MouseEvent | KeyboardEvent) => void) {
super(app);
this.categories = categories;
this.onChooseItem = onChooseItem;
this.setPlaceholder("Type to search for a Category");
}

onChooseSuggestion(item: FuzzyMatch<Category>, _evt: MouseEvent | KeyboardEvent): void {
this.onChooseItem(item.item, _evt);
}
}

export class AddTaskModal extends Modal {
result: { catId: string, task: string };
onSubmit: (result: { catId: string, task: string }) => void;
categories: Category[];

constructor(app: App, categories: Category[], onSubmit: (result: { catId: string; task: string; }) => void) {
super(app);
this.onSubmit = onSubmit;
this.categories = categories.sort((a, b) => {
return this.getFullPathToCategoryTitle(a, categories).localeCompare(this.getFullPathToCategoryTitle(b, categories));
});
this.result = { catId: '', task: '' }; // initialize result
}

onOpen() {
const { contentEl } = this;
let categoryInput: HTMLInputElement;

contentEl.createEl("h1", { text: "New Amazing Marvin Task" });

new Setting(contentEl)
.setName("Category")
.addText(text => {
categoryInput = text.inputEl;
text.onChange(value => {
// Simulate a dropdown by creating a suggester modal
const suggester = new CategorySuggesterModal(this.app, this.categories, (item: Category) => {
categoryInput.value = item.title;
this.result.catId = item._id;
suggester.close();
});
suggester.open();
});
});

new Setting(contentEl)
.setHeading().setName("Task");

new Setting(contentEl)
.addTextArea((textArea: TextAreaComponent) => {
textArea.inputEl.style.minHeight = "5em"; // Increase the size of the text area
textArea.onChange((value: string) => {
this.result.task = value;
});
}).settingEl.addClass("am-task-textarea-setting");

const shortcutsDesc = document.createDocumentFragment();
shortcutsDesc.appendText('The Task field accepts labels (@), time estimates (~), and scheduled dates (+). See ');
shortcutsDesc.appendChild(this.getShortcutsLink());
shortcutsDesc.appendText('.');

new Setting(contentEl)
.setDesc(shortcutsDesc);

// Submit Button
new Setting(contentEl)
.addButton((btn) =>
btn
.setButtonText("Submit")
.setCta()
.onClick(() => {
this.close();
if (this.onSubmit && this.result.catId && this.result.task) {
this.onSubmit(this.result);
}
}));
}

private getShortcutsLink(): HTMLAnchorElement {
const a = document.createElement('a');
a.href = 'https://help.amazingmarvin.com/en/articles/1949399-using-shortcuts-while-creating-a-task';
a.text = 'Using shortcuts while creating a task';
a.target = '_blank';
return a;
}

private getFullPathToCategoryTitle(category: Category, categories: Category[]): string {
let parent = category.parentId;

let parentTitle = [];
parentTitle.push('/');
while (parent && parent !== "root") {
const parentCategory = categories.find(c => c._id === parent);
if (parentCategory) {
parentTitle.push(parentCategory.title);
parent = parentCategory.parentId;
} else {
break;
}
}
return `${parentTitle.reverse().join("/")}${category.title}`;
}

onClose() {
let { contentEl } = this;
contentEl.empty();
}
}
82 changes: 80 additions & 2 deletions src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import {
requestUrl,
} from "obsidian";


import {
Category,
Task
Expand All @@ -21,6 +20,7 @@ import {
getDateFromFile
} from "obsidian-daily-notes-interface";
import { amTaskWatcher } from "./amTaskWatcher";
import { AddTaskModal } from "./addTaskModal";

let noticeTimeout: NodeJS.Timeout;

Expand All @@ -45,9 +45,11 @@ const CONSTANTS = {
categoriesEndpoint: '/api/categories',
childrenEndpoint: '/api/children',
scheduledOnDayEndpoint: '/api/todayItems',
dueOnDayEndpoint: '/api/dueItems'
dueOnDayEndpoint: '/api/dueItems',
addTaskEndpoint: '/api/addTask',
}


export default class AmazingMarvinPlugin extends Plugin {

settings: AmazingMarvinPluginSettings;
Expand Down Expand Up @@ -76,6 +78,38 @@ export default class AmazingMarvinPlugin extends Plugin {
this.registerEditorExtension(amTaskWatcher(this.app, this));
}

this.addCommand({
id: "create-marvin-task",
name: "Create Marvin Task",
editorCallback: async (editor, view) => {
// Fetch categories first and make sure they are loaded
try {
const categories = await this.fetchTasksAndCategories(CONSTANTS.categoriesEndpoint);
console.log('Categories:', categories); // For debug purposes
// Ensure categories are fetched before initializing the modal
if (categories.length > 0) {
new AddTaskModal(this.app, categories, async (taskDetails: { catId: string, task: string }) => {
console.log('Task details:', taskDetails);

this.addMarvinTask(taskDetails.catId, taskDetails.task)
.then(task => {
editor.replaceRange(`- [${task.done ? 'x' : ' '}] [⚓](${task.deepLink}) ${this.formatTaskDetails(task as Task, '')} ${task.title}`, editor.getCursor());
})
.catch(error => {
new Notice('Could not create Marvin task: ' + error.message);
});
}).open();
} else {
// Handle the case where categories could not be loaded
new Notice('Failed to load categories from Amazing Marvin.');
}
} catch (error) {
console.error('Error fetching categories:', error);
new Notice('Failed to load categories from Amazing Marvin.');
}
}
});

this.addCommand({
id: 'am-import',
name: 'Import Categories and Tasks',
Expand Down Expand Up @@ -117,6 +151,50 @@ export default class AmazingMarvinPlugin extends Plugin {
});

}
async addMarvinTask(catId: string, taskTitle: string): Promise<Task> {
const opt = this.settings;

let requestBody = (catId === '' || catId === undefined || catId === "root" || catId === "__inbox-faux__") ?
{
title : taskTitle,
timeZoneOffset: new Date().getTimezoneOffset(),
}
:
{
title: taskTitle,
timeZoneOffset: new Date().getTimezoneOffset(),
parentId: catId,
};

try {
const remoteResponse = await requestUrl({
url: `https://serv.amazingmarvin.com/api/addTask`,
method: 'POST',
headers: {
'X-API-Token': opt.apiKey,
'Content-Type': 'application/json'
},
body: JSON.stringify(requestBody)
});

if (remoteResponse.status === 200) {
new Notice("Task added in Amazing Marvin.");
return this.decorateWithDeepLink(remoteResponse.json) as Task;
}
} catch (error) {
const errorNote = document.createDocumentFragment();
errorNote.appendText('Error creating task in Amazing Marvin. Try again or do it');
const a = document.createElement('a');
a.href = 'https://app.amazingmarvin.com/';
a.text = 'manually';
a.target = '_blank';
errorNote.appendChild(a);

new Notice(errorNote, 0);
console.error('Error creating task:', error);
}
return Promise.reject(new Error('Error creating task'));
}

onunload() { }

Expand Down