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:
callumalpass 2025-11-27 22:38:16 +11:00
parent 541a60d8a9
commit bd632ea401
10 changed files with 91 additions and 519 deletions

View file

@ -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

View file

@ -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"));

View file

@ -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"));

View file

@ -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"));

View file

@ -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"));

View file

@ -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("]]")
);
}

View file

@ -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();
}

View file

@ -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);

View file

@ -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"));

View file

@ -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);
}