mirror of
https://github.com/callumalpass/tasknotes.git
synced 2026-07-22 12:50:26 +00:00
refactor: replace TaskSelectorModal with TaskSelectorWithCreateModal
All task selector modals now use the new TaskSelectorWithCreateModal, which allows users to either select an existing task OR create a new task via NLP parsing with Shift+Enter. Updated usages: - main.ts: insertTaskNoteLink, time tracking, time entry editor - PomodoroView.ts: task selection for pomodoro sessions - StatusBarService.ts: selecting tracked tasks - TaskContextMenu.ts: dependency selection, subtask assignment - TaskModal.ts: dependency selection, subtask selection - calendar-core.ts: time entry creation from calendar Also added openTaskSelector() helper function for drop-in replacement of the old TaskSelectorModal callback pattern. Removed: - src/modals/TaskSelectorModal.ts - styles/task-selector-modal.css
This commit is contained in:
parent
541a60d8a9
commit
bd632ea401
10 changed files with 91 additions and 519 deletions
|
|
@ -18,7 +18,6 @@ const CSS_FILES = [
|
|||
'styles/task-modal.css', // Task modal components (Google Keep/Todoist style)
|
||||
'styles/reminder-modal.css', // Reminder modal component with proper BEM scoping
|
||||
'styles/date-picker.css', // Enhanced date/time picker styling
|
||||
'styles/task-selector-modal.css', // TaskSelectorModal component with proper BEM scoping
|
||||
'styles/task-selector-with-create-modal.css', // TaskSelectorWithCreateModal component with proper BEM scoping
|
||||
'styles/unscheduled-tasks-selector-modal.css', // UnscheduledTasksSelectorModal component with proper BEM scoping
|
||||
'styles/task-action-palette-modal.css', // TaskActionPaletteModal component with proper BEM scoping
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ import { generateRecurringInstances, extractTimeblocksFromNote, updateTimeblockI
|
|||
import { Notice } from "obsidian";
|
||||
import { getAllDailyNotes, getDailyNote, appHasDailyNotesPluginLoaded, createDailyNote } from "obsidian-daily-notes-interface";
|
||||
import { TimeblockCreationModal } from "../modals/TimeblockCreationModal";
|
||||
import { TaskSelectorModal } from "../modals/TaskSelectorModal";
|
||||
import { openTaskSelector } from "../modals/TaskSelectorWithCreateModal";
|
||||
import { TimeblockInfoModal } from "../modals/TimeblockInfoModal";
|
||||
|
||||
export interface CalendarEvent {
|
||||
|
|
@ -954,53 +954,46 @@ export async function handleTimeEntryCreation(
|
|||
}
|
||||
|
||||
// Open task selector modal
|
||||
const modal = new TaskSelectorModal(
|
||||
plugin.app,
|
||||
plugin,
|
||||
unarchivedTasks,
|
||||
async (selectedTask: any) => {
|
||||
if (selectedTask) {
|
||||
try {
|
||||
// Calculate duration
|
||||
const durationMinutes = Math.round(
|
||||
(end.getTime() - start.getTime()) / 60000
|
||||
);
|
||||
openTaskSelector(plugin, unarchivedTasks, async (selectedTask: any) => {
|
||||
if (selectedTask) {
|
||||
try {
|
||||
// Calculate duration
|
||||
const durationMinutes = Math.round(
|
||||
(end.getTime() - start.getTime()) / 60000
|
||||
);
|
||||
|
||||
// Create new time entry
|
||||
const newEntry = {
|
||||
startTime: start.toISOString(),
|
||||
endTime: end.toISOString(),
|
||||
description: "",
|
||||
duration: durationMinutes,
|
||||
};
|
||||
// Create new time entry
|
||||
const newEntry = {
|
||||
startTime: start.toISOString(),
|
||||
endTime: end.toISOString(),
|
||||
description: "",
|
||||
duration: durationMinutes,
|
||||
};
|
||||
|
||||
// Add to task's time entries
|
||||
const updatedTimeEntries = [...(selectedTask.timeEntries || []), newEntry];
|
||||
// Add to task's time entries
|
||||
const updatedTimeEntries = [...(selectedTask.timeEntries || []), newEntry];
|
||||
|
||||
// Save to file
|
||||
await plugin.taskService.updateTask(selectedTask, {
|
||||
timeEntries: updatedTimeEntries,
|
||||
});
|
||||
// Save to file
|
||||
await plugin.taskService.updateTask(selectedTask, {
|
||||
timeEntries: updatedTimeEntries,
|
||||
});
|
||||
|
||||
// Note: updateTask in TaskService already triggers EVENT_TASK_UPDATED internally
|
||||
// We just need to trigger EVENT_DATA_CHANGED
|
||||
plugin.emitter.trigger(EVENT_DATA_CHANGED);
|
||||
// Note: updateTask in TaskService already triggers EVENT_TASK_UPDATED internally
|
||||
// We just need to trigger EVENT_DATA_CHANGED
|
||||
plugin.emitter.trigger(EVENT_DATA_CHANGED);
|
||||
|
||||
new Notice(
|
||||
plugin.i18n.translate("modals.timeEntry.created", {
|
||||
taskTitle: selectedTask.title,
|
||||
duration: durationMinutes.toString(),
|
||||
})
|
||||
);
|
||||
} catch (error) {
|
||||
console.error("Error creating time entry:", error);
|
||||
new Notice(plugin.i18n.translate("modals.timeEntry.createFailed"));
|
||||
}
|
||||
new Notice(
|
||||
plugin.i18n.translate("modals.timeEntry.created", {
|
||||
taskTitle: selectedTask.title,
|
||||
duration: durationMinutes.toString(),
|
||||
})
|
||||
);
|
||||
} catch (error) {
|
||||
console.error("Error creating time entry:", error);
|
||||
new Notice(plugin.i18n.translate("modals.timeEntry.createFailed"));
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
modal.open();
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Error opening task selector for time entry:", error);
|
||||
new Notice(plugin.i18n.translate("modals.timeEntry.createFailed"));
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import { showConfirmationModal } from "../modals/ConfirmationModal";
|
|||
import { DateContextMenu } from "./DateContextMenu";
|
||||
import { RecurrenceContextMenu } from "./RecurrenceContextMenu";
|
||||
import { showTextInputModal } from "../modals/TextInputModal";
|
||||
import { TaskSelectorModal } from "../modals/TaskSelectorModal";
|
||||
import { openTaskSelector } from "../modals/TaskSelectorWithCreateModal";
|
||||
import { ProjectSelectModal } from "../modals/ProjectSelectModal";
|
||||
import {
|
||||
DEFAULT_DEPENDENCY_RELTYPE,
|
||||
|
|
@ -791,11 +791,10 @@ export class TaskContextMenu {
|
|||
return;
|
||||
}
|
||||
|
||||
const selector = new TaskSelectorModal(plugin.app, plugin, candidates, async (task) => {
|
||||
openTaskSelector(plugin, candidates, async (task) => {
|
||||
if (!task) return;
|
||||
await onSelect(task);
|
||||
});
|
||||
selector.open();
|
||||
} catch (error) {
|
||||
console.error("Failed to open task selector for dependencies:", error);
|
||||
new Notice(this.t("contextMenus.task.dependencies.notices.updateFailed"));
|
||||
|
|
@ -928,11 +927,10 @@ export class TaskContextMenu {
|
|||
return;
|
||||
}
|
||||
|
||||
const selector = new TaskSelectorModal(plugin.app, plugin, candidates, async (subtask) => {
|
||||
openTaskSelector(plugin, candidates, async (subtask) => {
|
||||
if (!subtask) return;
|
||||
await this.assignTaskAsSubtask(task, plugin, subtask);
|
||||
});
|
||||
selector.open();
|
||||
} catch (error) {
|
||||
console.error("Failed to open subtask assignment selector:", error);
|
||||
new Notice(this.t("contextMenus.task.organization.notices.subtaskSelectFailed"));
|
||||
|
|
|
|||
14
src/main.ts
14
src/main.ts
|
|
@ -43,7 +43,7 @@ import { PomodoroStatsView } from "./views/PomodoroStatsView";
|
|||
import { StatsView } from "./views/StatsView";
|
||||
import { TaskCreationModal } from "./modals/TaskCreationModal";
|
||||
import { TaskEditModal } from "./modals/TaskEditModal";
|
||||
import { TaskSelectorModal } from "./modals/TaskSelectorModal";
|
||||
import { openTaskSelector } from "./modals/TaskSelectorWithCreateModal";
|
||||
import { TimeEntryEditorModal } from "./modals/TimeEntryEditorModal";
|
||||
import { PomodoroService } from "./services/PomodoroService";
|
||||
import { formatTime, getActiveTimeEntry } from "./utils/helpers";
|
||||
|
|
@ -2439,7 +2439,7 @@ export default class TaskNotesPlugin extends Plugin {
|
|||
const unarchivedTasks = allTasks.filter((task) => !task.archived);
|
||||
|
||||
// Open task selector modal
|
||||
const modal = new TaskSelectorModal(this.app, this, unarchivedTasks, (selectedTask) => {
|
||||
openTaskSelector(this, unarchivedTasks, (selectedTask) => {
|
||||
if (selectedTask) {
|
||||
// Create link using Obsidian's generateMarkdownLink (respects user's link format settings)
|
||||
const file = this.app.vault.getAbstractFileByPath(selectedTask.path);
|
||||
|
|
@ -2468,8 +2468,6 @@ export default class TaskNotesPlugin extends Plugin {
|
|||
}
|
||||
}
|
||||
});
|
||||
|
||||
modal.open();
|
||||
} catch (error) {
|
||||
console.error("Error inserting tasknote link:", error);
|
||||
new Notice("Failed to insert tasknote link");
|
||||
|
|
@ -2497,7 +2495,7 @@ export default class TaskNotesPlugin extends Plugin {
|
|||
}
|
||||
|
||||
// Open task selector modal
|
||||
const modal = new TaskSelectorModal(this.app, this, availableTasks, async (selectedTask) => {
|
||||
openTaskSelector(this, availableTasks, async (selectedTask) => {
|
||||
if (selectedTask) {
|
||||
try {
|
||||
await this.startTimeTracking(selectedTask);
|
||||
|
|
@ -2512,8 +2510,6 @@ export default class TaskNotesPlugin extends Plugin {
|
|||
}
|
||||
}
|
||||
});
|
||||
|
||||
modal.open();
|
||||
} catch (error) {
|
||||
console.error("Error opening task selector for time tracking:", error);
|
||||
new Notice(this.i18n.translate("modals.timeTracking.startFailed"));
|
||||
|
|
@ -2540,13 +2536,11 @@ export default class TaskNotesPlugin extends Plugin {
|
|||
}
|
||||
|
||||
// Open task selector modal
|
||||
const modal = new TaskSelectorModal(this.app, this, tasksWithEntries, (selectedTask) => {
|
||||
openTaskSelector(this, tasksWithEntries, (selectedTask) => {
|
||||
if (selectedTask) {
|
||||
this.openTimeEntryEditor(selectedTask);
|
||||
}
|
||||
});
|
||||
|
||||
modal.open();
|
||||
} catch (error) {
|
||||
console.error("Error opening task selector for time entry editor:", error);
|
||||
new Notice(this.i18n.translate("modals.timeEntryEditor.openFailed"));
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ import {
|
|||
renderProjectLinks,
|
||||
type LinkServices,
|
||||
} from "../ui/renderers/linkRenderer";
|
||||
import { TaskSelectorModal } from "./TaskSelectorModal";
|
||||
import { openTaskSelector } from "./TaskSelectorWithCreateModal";
|
||||
import { generateLink, generateLinkWithDisplay } from "../utils/linkUtils";
|
||||
import { EmbeddableMarkdownEditor } from "../editor/EmbeddableMarkdownEditor";
|
||||
|
||||
|
|
@ -369,11 +369,10 @@ export abstract class TaskModal extends Modal {
|
|||
return;
|
||||
}
|
||||
|
||||
const modal = new TaskSelectorModal(this.app, this.plugin, candidates, (task) => {
|
||||
openTaskSelector(this.plugin, candidates, (task) => {
|
||||
if (!task) return;
|
||||
onSelect(task);
|
||||
});
|
||||
modal.open();
|
||||
} catch (error) {
|
||||
console.error("Failed to open task selector for dependencies:", error);
|
||||
new Notice(this.t("contextMenus.task.dependencies.notices.updateFailed"));
|
||||
|
|
@ -1804,19 +1803,13 @@ export abstract class TaskModal extends Modal {
|
|||
return;
|
||||
}
|
||||
|
||||
const selector = new TaskSelectorModal(
|
||||
this.app,
|
||||
this.plugin,
|
||||
candidates,
|
||||
async (subtask) => {
|
||||
if (!subtask) return;
|
||||
const file = this.app.vault.getAbstractFileByPath(subtask.path);
|
||||
if (file) {
|
||||
this.addSubtask(file);
|
||||
}
|
||||
openTaskSelector(this.plugin, candidates, async (subtask) => {
|
||||
if (!subtask) return;
|
||||
const file = this.app.vault.getAbstractFileByPath(subtask.path);
|
||||
if (file) {
|
||||
this.addSubtask(file);
|
||||
}
|
||||
);
|
||||
selector.open();
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Failed to open subtask selector:", error);
|
||||
new Notice(this.t("modals.task.organization.notices.subtaskSelectFailed"));
|
||||
|
|
|
|||
|
|
@ -1,231 +0,0 @@
|
|||
import { App, FuzzySuggestModal, FuzzyMatch, TFile, Notice } from "obsidian";
|
||||
import { TaskInfo } from "../types";
|
||||
import { isPastDate, isToday } from "../utils/dateUtils";
|
||||
import { filterEmptyProjects } from "../utils/helpers";
|
||||
import type TaskNotesPlugin from "../main";
|
||||
import { TranslationKey } from "../i18n";
|
||||
|
||||
export class TaskSelectorModal extends FuzzySuggestModal<TaskInfo> {
|
||||
private tasks: TaskInfo[];
|
||||
private onChooseTask: (task: TaskInfo | null) => void;
|
||||
private plugin: TaskNotesPlugin;
|
||||
private translate: (key: TranslationKey, variables?: Record<string, any>) => string;
|
||||
|
||||
constructor(
|
||||
app: App,
|
||||
plugin: TaskNotesPlugin,
|
||||
tasks: TaskInfo[],
|
||||
onChooseTask: (task: TaskInfo | null) => void
|
||||
) {
|
||||
super(app);
|
||||
this.plugin = plugin;
|
||||
this.tasks = tasks;
|
||||
this.onChooseTask = onChooseTask;
|
||||
this.translate = plugin.i18n.translate.bind(plugin.i18n);
|
||||
|
||||
this.setPlaceholder(this.translate("modals.taskSelector.placeholder"));
|
||||
this.setInstructions([
|
||||
{ command: "↑↓", purpose: this.translate("modals.taskSelector.instructions.navigate") },
|
||||
{ command: "↵", purpose: this.translate("modals.taskSelector.instructions.select") },
|
||||
{ command: "esc", purpose: this.translate("modals.taskSelector.instructions.dismiss") },
|
||||
]);
|
||||
|
||||
// Set modal title for accessibility
|
||||
this.titleEl.setText(this.translate("modals.taskSelector.title"));
|
||||
this.titleEl.setAttribute("id", "task-selector-title");
|
||||
|
||||
// Set aria attributes on the modal
|
||||
this.containerEl.setAttribute("aria-labelledby", "task-selector-title");
|
||||
this.containerEl.setAttribute("role", "dialog");
|
||||
this.containerEl.setAttribute("aria-modal", "true");
|
||||
}
|
||||
|
||||
getItems(): TaskInfo[] {
|
||||
// Filter archived tasks, sort by completion status first, then by due date and priority
|
||||
return this.tasks
|
||||
.filter((task) => !task.archived)
|
||||
.sort((a, b) => {
|
||||
// Sort by completion status first (incomplete tasks come first)
|
||||
const aCompleted = this.plugin.statusManager.isCompletedStatus(a.status);
|
||||
const bCompleted = this.plugin.statusManager.isCompletedStatus(b.status);
|
||||
if (aCompleted !== bCompleted) {
|
||||
return aCompleted ? 1 : -1; // Incomplete (false) comes before completed (true)
|
||||
}
|
||||
|
||||
// Sort by due date second (tasks with due dates come first)
|
||||
if (a.due && !b.due) return -1;
|
||||
if (!a.due && b.due) return 1;
|
||||
if (a.due && b.due) {
|
||||
const dateCompare = a.due.localeCompare(b.due);
|
||||
if (dateCompare !== 0) return dateCompare;
|
||||
}
|
||||
|
||||
// Then by priority (high -> normal -> low)
|
||||
const priorityOrder: Record<string, number> = { high: 0, normal: 1, low: 2 };
|
||||
const aPriority = priorityOrder[a.priority] ?? 1;
|
||||
const bPriority = priorityOrder[b.priority] ?? 1;
|
||||
if (aPriority !== bPriority) return aPriority - bPriority;
|
||||
|
||||
// Finally by title
|
||||
return a.title.localeCompare(b.title);
|
||||
});
|
||||
}
|
||||
|
||||
getItemText(task: TaskInfo): string {
|
||||
// Include title, due date, and priority in searchable text
|
||||
let text = task.title;
|
||||
if (task.due) {
|
||||
text += ` ${task.due}`;
|
||||
}
|
||||
if (task.priority !== "normal") {
|
||||
text += ` ${task.priority}`;
|
||||
}
|
||||
if (task.contexts && task.contexts.length > 0) {
|
||||
text += ` ${task.contexts.join(" ")}`;
|
||||
}
|
||||
const filteredProjects = filterEmptyProjects(task.projects || []);
|
||||
if (filteredProjects.length > 0) {
|
||||
text += ` ${filteredProjects.join(" ")}`;
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
renderSuggestion(item: FuzzyMatch<TaskInfo>, el: HTMLElement) {
|
||||
const task = item.item;
|
||||
const container = el.createDiv({ cls: "task-selector-modal__suggestion" });
|
||||
|
||||
// Title with priority indicator
|
||||
const titleDiv = container.createDiv({ cls: "task-selector-modal__title" });
|
||||
const priorityClass =
|
||||
task.priority !== "normal"
|
||||
? `task-selector-modal__task-title--${task.priority}-priority`
|
||||
: "";
|
||||
titleDiv.createSpan({
|
||||
cls: `task-selector-modal__task-title ${priorityClass}`,
|
||||
text: task.title,
|
||||
});
|
||||
|
||||
// Metadata line
|
||||
const metaDiv = container.createDiv({ cls: "task-selector-modal__meta" });
|
||||
|
||||
// Due date
|
||||
if (task.due) {
|
||||
const isOverdue = isPastDate(task.due);
|
||||
const isDueToday = isToday(task.due);
|
||||
|
||||
let dueDateText = task.due;
|
||||
let dueDateClass = "task-selector-modal__due-date";
|
||||
|
||||
if (isOverdue) {
|
||||
dueDateText = this.translate("modals.taskSelector.dueDate.overdue", {
|
||||
date: task.due,
|
||||
});
|
||||
dueDateClass += " task-selector-modal__due-date--overdue";
|
||||
} else if (isDueToday) {
|
||||
dueDateText = this.translate("modals.taskSelector.dueDate.today");
|
||||
dueDateClass += " task-selector-modal__due-date--today";
|
||||
}
|
||||
|
||||
metaDiv.createSpan({
|
||||
cls: dueDateClass,
|
||||
text: dueDateText,
|
||||
});
|
||||
}
|
||||
|
||||
// Contexts
|
||||
if (task.contexts && task.contexts.length > 0) {
|
||||
const contextsSpan = metaDiv.createSpan({ cls: "task-selector-modal__contexts" });
|
||||
task.contexts.forEach((context, index) => {
|
||||
if (index > 0) contextsSpan.createSpan({ text: ", " });
|
||||
contextsSpan.createSpan({
|
||||
cls: "task-selector-modal__context-tag",
|
||||
text: `${context}`,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Projects
|
||||
const filteredProjects = filterEmptyProjects(task.projects || []);
|
||||
if (filteredProjects.length > 0) {
|
||||
const projectsSpan = metaDiv.createSpan({ cls: "task-selector-modal__projects" });
|
||||
filteredProjects.forEach((project, index) => {
|
||||
if (index > 0) projectsSpan.createSpan({ text: ", " });
|
||||
|
||||
const plusText = document.createTextNode("+");
|
||||
projectsSpan.appendChild(plusText);
|
||||
|
||||
if (isWikilinkProject(project)) {
|
||||
// Extract the note name from [[Note Name]]
|
||||
const noteName = project.slice(2, -2);
|
||||
|
||||
// Create a clickable link
|
||||
const linkEl = projectsSpan.createEl("a", {
|
||||
cls: "task-selector-modal__project-link internal-link",
|
||||
text: noteName,
|
||||
attr: { "data-href": noteName },
|
||||
});
|
||||
|
||||
// Add click handler to open the note
|
||||
linkEl.addEventListener("click", async (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
// Resolve the link to get the actual file
|
||||
const file = this.plugin.app.metadataCache.getFirstLinkpathDest(
|
||||
noteName,
|
||||
""
|
||||
);
|
||||
if (file instanceof TFile) {
|
||||
// Open the file in the current leaf
|
||||
await this.plugin.app.workspace.getLeaf(false).openFile(file);
|
||||
// Close this modal after opening the file
|
||||
this.close();
|
||||
} else {
|
||||
// File not found, show notice
|
||||
new Notice(
|
||||
this.translate("modals.taskSelector.notices.noteNotFound", {
|
||||
name: noteName,
|
||||
})
|
||||
);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// Plain text project
|
||||
projectsSpan.createSpan({
|
||||
cls: "task-selector-modal__project-tag",
|
||||
text: project,
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Status - show if it's not the default status
|
||||
const defaultStatus = this.plugin.settings.defaultTaskStatus;
|
||||
if (task.status && task.status !== defaultStatus) {
|
||||
const statusConfig = this.plugin.statusManager.getStatusConfig(task.status);
|
||||
metaDiv.createSpan({
|
||||
cls: `task-selector-modal__status`,
|
||||
text: statusConfig ? statusConfig.label : task.status,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
onChooseItem(item: TaskInfo, evt: MouseEvent | KeyboardEvent) {
|
||||
this.onChooseTask(item);
|
||||
}
|
||||
|
||||
onClose() {
|
||||
super.onClose();
|
||||
// If user closed without selecting, call callback with null
|
||||
// Note: This will be called even after selection, but the callback should handle that gracefully
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a project string is in wikilink format [[Note Name]]
|
||||
*/
|
||||
function isWikilinkProject(project: string): boolean {
|
||||
return Boolean(
|
||||
project && typeof project === "string" && project.startsWith("[[") && project.endsWith("]]")
|
||||
);
|
||||
}
|
||||
|
|
@ -22,8 +22,6 @@ export interface TaskSelectorWithCreateOptions {
|
|||
placeholder?: string;
|
||||
/** Optional title override */
|
||||
title?: string;
|
||||
/** Whether to allow task creation (default: true) */
|
||||
allowCreate?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -128,9 +126,7 @@ export class TaskSelectorWithCreateModal extends SuggestModal<TaskInfo> {
|
|||
private updateCreateFooter(query: string): void {
|
||||
if (!this.createFooterEl) return;
|
||||
|
||||
const allowCreate = this.options.allowCreate !== false;
|
||||
|
||||
if (!query || !allowCreate) {
|
||||
if (!query) {
|
||||
this.createFooterEl.style.display = "none";
|
||||
this.createFooterEl.empty();
|
||||
return;
|
||||
|
|
@ -498,3 +494,33 @@ export async function openTaskSelectorWithCreate(
|
|||
modal.open();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to open a task selector with create capability.
|
||||
* This is a drop-in replacement for TaskSelectorModal with a simpler callback pattern.
|
||||
* Users can select an existing task OR create a new one via Shift+Enter.
|
||||
*
|
||||
* @param plugin - The TaskNotes plugin instance
|
||||
* @param tasks - Array of tasks to choose from
|
||||
* @param onChooseTask - Callback when a task is selected or created (null if cancelled)
|
||||
* @param options - Optional configuration (placeholder, title)
|
||||
*/
|
||||
export function openTaskSelector(
|
||||
plugin: TaskNotesPlugin,
|
||||
tasks: TaskInfo[],
|
||||
onChooseTask: (task: TaskInfo | null) => void,
|
||||
options?: { placeholder?: string; title?: string }
|
||||
): void {
|
||||
const modal = new TaskSelectorWithCreateModal(plugin.app, plugin, tasks, {
|
||||
placeholder: options?.placeholder,
|
||||
title: options?.title,
|
||||
onResult: (result) => {
|
||||
if (result.type === "selected" || result.type === "created") {
|
||||
onChooseTask(result.task);
|
||||
} else {
|
||||
onChooseTask(null);
|
||||
}
|
||||
},
|
||||
});
|
||||
modal.open();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { TaskInfo } from "../types";
|
||||
import { RequestDeduplicator } from "../utils/RequestDeduplicator";
|
||||
import { setTooltip, TFile } from "obsidian";
|
||||
import { TaskSelectorModal } from "../modals/TaskSelectorModal";
|
||||
import { openTaskSelector } from "../modals/TaskSelectorWithCreateModal";
|
||||
|
||||
export class StatusBarService {
|
||||
private plugin: import("../main").default;
|
||||
|
|
@ -150,20 +150,14 @@ export class StatusBarService {
|
|||
}
|
||||
} else {
|
||||
// Multiple tracked tasks - show selector modal
|
||||
const modal = new TaskSelectorModal(
|
||||
this.plugin.app,
|
||||
this.plugin,
|
||||
trackedTasks,
|
||||
async (selectedTask) => {
|
||||
if (selectedTask) {
|
||||
const file = this.plugin.app.vault.getAbstractFileByPath(selectedTask.path);
|
||||
if (file instanceof TFile) {
|
||||
await this.plugin.app.workspace.getLeaf(false).openFile(file);
|
||||
}
|
||||
openTaskSelector(this.plugin, trackedTasks, async (selectedTask) => {
|
||||
if (selectedTask) {
|
||||
const file = this.plugin.app.vault.getAbstractFileByPath(selectedTask.path);
|
||||
if (file instanceof TFile) {
|
||||
await this.plugin.app.workspace.getLeaf(false).openFile(file);
|
||||
}
|
||||
}
|
||||
);
|
||||
modal.open();
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error handling status bar click:", error);
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ import {
|
|||
PomodoroState,
|
||||
TaskInfo,
|
||||
} from "../types";
|
||||
import { TaskSelectorModal } from "../modals/TaskSelectorModal";
|
||||
import { openTaskSelector } from "../modals/TaskSelectorWithCreateModal";
|
||||
import { createTaskCard } from "../ui/TaskCard";
|
||||
import { convertInternalToUserProperties } from "../utils/propertyMapping";
|
||||
|
||||
|
|
@ -714,16 +714,9 @@ export class PomodoroView extends ItemView {
|
|||
}
|
||||
|
||||
// Open task selector modal
|
||||
const modal = new TaskSelectorModal(
|
||||
this.app,
|
||||
this.plugin,
|
||||
unarchivedTasks,
|
||||
(selectedTask) => {
|
||||
this.selectTask(selectedTask);
|
||||
}
|
||||
);
|
||||
|
||||
modal.open();
|
||||
openTaskSelector(this.plugin, unarchivedTasks, (selectedTask) => {
|
||||
this.selectTask(selectedTask);
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Error opening task selector:", error);
|
||||
new Notice(this.t("views.pomodoro.notices.loadFailed"));
|
||||
|
|
|
|||
|
|
@ -1,187 +0,0 @@
|
|||
/* ================================================
|
||||
TASK SELECTOR - TASK STYLING ONLY
|
||||
================================================ */
|
||||
|
||||
/* ================================================
|
||||
TITLE SECTION
|
||||
================================================ */
|
||||
|
||||
/* Title Container */
|
||||
.task-selector-modal__title {
|
||||
margin-bottom: var(--tn-spacing-xs);
|
||||
}
|
||||
|
||||
/* Task Title Text */
|
||||
.task-selector-modal__task-title {
|
||||
font-weight: 500;
|
||||
font-size: var(--tn-font-size-sm);
|
||||
color: var(--text-normal);
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
/* Priority Modifiers */
|
||||
.task-selector-modal__task-title--high-priority {
|
||||
color: var(--tn-color-priority-high);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.task-selector-modal__task-title--low-priority {
|
||||
color: var(--tn-color-priority-low);
|
||||
font-weight: 400;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
/* ================================================
|
||||
METADATA SECTION
|
||||
================================================ */
|
||||
|
||||
/* Meta Container */
|
||||
.task-selector-modal__meta {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--tn-spacing-sm);
|
||||
flex-wrap: wrap;
|
||||
font-size: var(--tn-font-size-xs);
|
||||
color: var(--text-muted);
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
/* ================================================
|
||||
DUE DATE DISPLAY
|
||||
================================================ */
|
||||
|
||||
/* Base Due Date */
|
||||
.task-selector-modal__due-date {
|
||||
padding: 2px 6px;
|
||||
border-radius: var(--tn-border-radius-sm);
|
||||
background-color: var(--background-modifier-border);
|
||||
color: var(--text-muted);
|
||||
font-size: var(--tn-font-size-xs);
|
||||
font-weight: 500;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* Due Date Modifiers */
|
||||
.task-selector-modal__due-date--overdue {
|
||||
background-color: var(--tn-color-status-error-bg);
|
||||
color: var(--tn-color-status-error);
|
||||
border: 1px solid var(--tn-color-status-error);
|
||||
}
|
||||
|
||||
.task-selector-modal__due-date--today {
|
||||
background-color: var(--tn-color-status-warning-bg);
|
||||
color: var(--tn-color-status-warning);
|
||||
border: 1px solid var(--tn-color-status-warning);
|
||||
}
|
||||
|
||||
/* ================================================
|
||||
CONTEXTS SECTION
|
||||
================================================ */
|
||||
|
||||
/* Contexts Container */
|
||||
.task-selector-modal__contexts {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
/* Individual Context Tags */
|
||||
.task-selector-modal__context-tag {
|
||||
padding: 2px 4px;
|
||||
border-radius: var(--tn-border-radius-sm);
|
||||
background-color: var(--tn-color-context-bg);
|
||||
color: var(--tn-color-context);
|
||||
font-size: var(--tn-font-size-xs);
|
||||
font-weight: 500;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.task-selector-modal__context-tag::before {
|
||||
content: '@';
|
||||
opacity: 0.7;
|
||||
margin-right: 1px;
|
||||
}
|
||||
|
||||
/* ================================================
|
||||
PROJECTS SECTION
|
||||
================================================ */
|
||||
|
||||
/* Projects Container */
|
||||
.task-selector-modal__projects {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
/* Individual Project Tags */
|
||||
.task-selector-modal__project-tag {
|
||||
padding: 2px 4px;
|
||||
border-radius: var(--tn-border-radius-sm);
|
||||
background-color: var(--tn-color-project-bg, var(--background-modifier-border));
|
||||
color: var(--tn-color-project, var(--text-accent));
|
||||
font-size: var(--tn-font-size-xs);
|
||||
font-weight: 500;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.task-selector-modal__project-tag::before {
|
||||
content: '+';
|
||||
opacity: 0.7;
|
||||
margin-right: 1px;
|
||||
}
|
||||
|
||||
/* Project Links - for [[Note Name]] format projects */
|
||||
.task-selector-modal__project-link {
|
||||
color: var(--link-color);
|
||||
text-decoration: none;
|
||||
transition: all 0.2s ease;
|
||||
border-radius: var(--tn-border-radius-sm);
|
||||
padding: 1px 2px;
|
||||
margin: -1px -2px;
|
||||
cursor: var(--cursor);
|
||||
}
|
||||
|
||||
.task-selector-modal__project-link:hover {
|
||||
background: var(--background-modifier-hover);
|
||||
text-decoration: underline;
|
||||
color: var(--link-color-hover);
|
||||
}
|
||||
|
||||
/* ================================================
|
||||
STATUS DISPLAY
|
||||
================================================ */
|
||||
|
||||
/* Status Badge */
|
||||
.task-selector-modal__status {
|
||||
padding: 2px 6px;
|
||||
border-radius: var(--tn-border-radius-sm);
|
||||
background-color: var(--background-modifier-border);
|
||||
color: var(--text-muted);
|
||||
font-size: var(--tn-font-size-xs);
|
||||
font-weight: 500;
|
||||
text-transform: capitalize;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* ================================================
|
||||
THEME ADJUSTMENTS
|
||||
================================================ */
|
||||
|
||||
/* Improve contrast in dark themes */
|
||||
.theme-dark .task-selector-modal__due-date {
|
||||
background-color: var(--background-modifier-form-field);
|
||||
}
|
||||
|
||||
.theme-dark .task-selector-modal__context-tag {
|
||||
background-color: var(--background-modifier-form-field);
|
||||
}
|
||||
|
||||
.theme-dark .task-selector-modal__project-tag {
|
||||
background-color: var(--background-modifier-form-field);
|
||||
}
|
||||
|
||||
.theme-dark .task-selector-modal__status {
|
||||
background-color: var(--background-modifier-form-field);
|
||||
}
|
||||
Loading…
Reference in a new issue