mirror of
https://github.com/taskgenius/taskgenius-plugin.git
synced 2026-07-22 06:40:25 +00:00
refactor: remove void keywords and standardize code formatting
- Remove unnecessary void keywords from async function calls - Standardize code formatting with consistent trailing commas - Add date operation utilities for task date management - Add TextPromptModal component for user input - Enhance FluentActionHandlers with improved task update handling
This commit is contained in:
parent
9aaabe716f
commit
7d41359c6e
17 changed files with 790 additions and 188 deletions
|
|
@ -1207,7 +1207,7 @@ export const DEFAULT_SETTINGS: TaskProgressBarSettings = {
|
|||
enableIndexer: true, // Enable indexer by default
|
||||
enableView: true, // Enable view by default
|
||||
enableInlineEditor: true, // Enable inline editing by default
|
||||
enableDynamicMetadataPositioning: true, // Enable intelligent metadata positioning by default
|
||||
enableDynamicMetadataPositioning: false, // Enable intelligent metadata positioning by default
|
||||
defaultViewMode: "list", // Global default view mode for all views
|
||||
|
||||
// Global Filter Defaults
|
||||
|
|
|
|||
|
|
@ -74,7 +74,7 @@ export class ChangelogView extends ItemView {
|
|||
markdown: "",
|
||||
sourceUrl: "",
|
||||
};
|
||||
void this.render();
|
||||
this.render();
|
||||
}
|
||||
|
||||
async setContent(content: ChangelogContent) {
|
||||
|
|
@ -87,7 +87,7 @@ export class ChangelogView extends ItemView {
|
|||
showError(message: string) {
|
||||
this.isLoading = false;
|
||||
this.error = message;
|
||||
void this.render();
|
||||
this.render();
|
||||
}
|
||||
|
||||
private async render(): Promise<void> {
|
||||
|
|
|
|||
|
|
@ -215,8 +215,8 @@ export class FluentIntegration {
|
|||
v1Leaves.forEach((leaf) => leaf.detach());
|
||||
|
||||
// Close all Fluent views
|
||||
const v2Leaves = workspace.getLeavesOfType(FLUENT_TASK_VIEW);
|
||||
v2Leaves.forEach((leaf) => leaf.detach());
|
||||
const fluentLeaves = workspace.getLeavesOfType(FLUENT_TASK_VIEW);
|
||||
fluentLeaves.forEach((leaf) => leaf.detach());
|
||||
|
||||
// Toggle the setting
|
||||
if (!this.plugin.settings.fluentView) {
|
||||
|
|
|
|||
|
|
@ -98,7 +98,10 @@ export class TopNavigation extends Component {
|
|||
|
||||
this.updateNotificationBadge();
|
||||
} catch (error) {
|
||||
console.warn("[FluentTopNavigation] Failed to update notification count:", error);
|
||||
console.warn(
|
||||
"[FluentTopNavigation] Failed to update notification count:",
|
||||
error,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -169,7 +172,9 @@ export class TopNavigation extends Component {
|
|||
cls: "fluent-nav-icon-button",
|
||||
});
|
||||
setIcon(settingsBtn, "settings");
|
||||
this.registerDomEvent(settingsBtn, "click", () => this.onSettingsClick());
|
||||
this.registerDomEvent(settingsBtn, "click", () =>
|
||||
this.onSettingsClick(),
|
||||
);
|
||||
}
|
||||
|
||||
private createViewTab(
|
||||
|
|
|
|||
|
|
@ -5,6 +5,15 @@ import { QuickCaptureModal } from "@/components/features/quick-capture/modals/Qu
|
|||
import { ConfirmModal } from "@/components/ui/modals/ConfirmModal";
|
||||
import { createTaskCheckbox } from "@/components/features/task/view/details";
|
||||
import { emitTaskSelected } from "@/components/features/fluent/events/ui-event";
|
||||
import { DatePickerModal } from "@/components/ui/date-picker/DatePickerModal";
|
||||
import { TextPromptModal } from "@/components/ui/modals/TextPromptModal";
|
||||
import type { CreateTaskArgs } from "@/dataflow/api/WriteAPI";
|
||||
import {
|
||||
getExistingDateTypes as getExistingDateTypesFromTask,
|
||||
postponeDate as postponeDateUtil,
|
||||
smartPostponeRelatedDates,
|
||||
TaskDateType,
|
||||
} from "@/utils/dateOperations";
|
||||
import { t } from "@/translations/helper";
|
||||
import { ViewMode } from "../components/FluentTopNavigation";
|
||||
|
||||
|
|
@ -151,6 +160,7 @@ export class FluentActionHandlers extends Component {
|
|||
async handleTaskUpdate(
|
||||
originalTask: Task,
|
||||
updatedTask: Task,
|
||||
successMessage?: string,
|
||||
): Promise<void> {
|
||||
if (!this.plugin.writeAPI) {
|
||||
console.error("WriteAPI not available");
|
||||
|
|
@ -176,10 +186,10 @@ export class FluentActionHandlers extends Component {
|
|||
// Notify about task update
|
||||
this.onTaskUpdated?.(originalTask.id, updated);
|
||||
|
||||
new Notice(t("Task updated"));
|
||||
new Notice(successMessage ?? t("Task updated"));
|
||||
} catch (error) {
|
||||
console.error("Failed to update task:", error);
|
||||
new Notice("Failed to update task");
|
||||
new Notice(t("Failed to update task"));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -218,85 +228,464 @@ export class FluentActionHandlers extends Component {
|
|||
item.onClick(() => {
|
||||
this.toggleTaskCompletion(task);
|
||||
});
|
||||
})
|
||||
.addItem((item) => {
|
||||
item.setIcon("square-pen");
|
||||
item.setTitle(t("Switch status"));
|
||||
const submenu = item.setSubmenu();
|
||||
});
|
||||
|
||||
// Get unique statuses from taskStatusMarks
|
||||
const statusMarks = this.plugin.settings.taskStatusMarks;
|
||||
const uniqueStatuses = new Map<string, string>();
|
||||
menu.addItem((item) => {
|
||||
item.setIcon("square-pen");
|
||||
item.setTitle(t("Switch status"));
|
||||
const submenu = item.setSubmenu();
|
||||
|
||||
// Build a map of unique mark -> status name to avoid duplicates
|
||||
for (const status of Object.keys(statusMarks)) {
|
||||
const mark =
|
||||
statusMarks[status as keyof typeof statusMarks];
|
||||
if (!Array.from(uniqueStatuses.values()).includes(mark)) {
|
||||
uniqueStatuses.set(status, mark);
|
||||
}
|
||||
// Get unique statuses from taskStatusMarks
|
||||
const statusMarks = this.plugin.settings.taskStatusMarks;
|
||||
const uniqueStatuses = new Map<string, string>();
|
||||
|
||||
// Build a map of unique mark -> status name to avoid duplicates
|
||||
for (const status of Object.keys(statusMarks)) {
|
||||
const mark = statusMarks[status as keyof typeof statusMarks];
|
||||
if (!Array.from(uniqueStatuses.values()).includes(mark)) {
|
||||
uniqueStatuses.set(status, mark);
|
||||
}
|
||||
}
|
||||
|
||||
// Create menu items from unique statuses
|
||||
for (const [status, mark] of uniqueStatuses) {
|
||||
submenu.addItem((item) => {
|
||||
item.titleEl.createEl(
|
||||
"span",
|
||||
{
|
||||
cls: "status-option-checkbox",
|
||||
},
|
||||
(el) => {
|
||||
createTaskCheckbox(mark, task, el);
|
||||
},
|
||||
);
|
||||
item.titleEl.createEl("span", {
|
||||
cls: "status-option",
|
||||
text: status,
|
||||
});
|
||||
item.onClick(async () => {
|
||||
const willComplete = this.isCompletedMark(mark);
|
||||
const updatedTask = {
|
||||
...task,
|
||||
status: mark,
|
||||
completed: willComplete,
|
||||
};
|
||||
|
||||
if (!task.completed && willComplete) {
|
||||
updatedTask.metadata.completedDate = Date.now();
|
||||
} else if (task.completed && !willComplete) {
|
||||
updatedTask.metadata.completedDate = undefined;
|
||||
}
|
||||
|
||||
await this.handleTaskUpdate(task, updatedTask);
|
||||
});
|
||||
// Create menu items from unique statuses
|
||||
for (const [status, mark] of uniqueStatuses) {
|
||||
submenu.addItem((subItem) => {
|
||||
subItem.titleEl.createEl(
|
||||
"span",
|
||||
{
|
||||
cls: "status-option-checkbox",
|
||||
},
|
||||
(el) => {
|
||||
createTaskCheckbox(mark, task, el);
|
||||
},
|
||||
);
|
||||
subItem.titleEl.createEl("span", {
|
||||
cls: "status-option",
|
||||
text: status,
|
||||
});
|
||||
subItem.onClick(async () => {
|
||||
const willComplete = this.isCompletedMark(mark);
|
||||
const updatedTask = {
|
||||
...task,
|
||||
status: mark,
|
||||
completed: willComplete,
|
||||
};
|
||||
|
||||
if (!task.completed && willComplete) {
|
||||
updatedTask.metadata.completedDate = Date.now();
|
||||
} else if (task.completed && !willComplete) {
|
||||
updatedTask.metadata.completedDate = undefined;
|
||||
}
|
||||
|
||||
await this.handleTaskUpdate(task, updatedTask);
|
||||
});
|
||||
}
|
||||
})
|
||||
.addSeparator()
|
||||
.addItem((item) => {
|
||||
item.setTitle(t("Edit"));
|
||||
item.setIcon("pencil");
|
||||
item.onClick(() => {
|
||||
this.handleTaskSelection(task);
|
||||
});
|
||||
})
|
||||
.addItem((item) => {
|
||||
item.setTitle(t("Edit in File"));
|
||||
item.setIcon("pencil");
|
||||
item.onClick(() => {
|
||||
this.editTask(task);
|
||||
}
|
||||
});
|
||||
|
||||
this.addPriorityMenuItems(menu, task);
|
||||
this.addDateMenuItems(menu, task);
|
||||
|
||||
menu.addSeparator();
|
||||
menu.addItem((item) => {
|
||||
item.setTitle(t("Set Project"));
|
||||
item.setIcon("folder");
|
||||
item.onClick(() => {
|
||||
this.setProject(task);
|
||||
});
|
||||
});
|
||||
menu.addItem((item) => {
|
||||
item.setTitle(t("Add Tags"));
|
||||
item.setIcon("tag");
|
||||
item.onClick(() => {
|
||||
this.addTags(task);
|
||||
});
|
||||
});
|
||||
|
||||
menu.addSeparator();
|
||||
menu.addItem((item) => {
|
||||
item.setTitle(t("Duplicate Task"));
|
||||
item.setIcon("copy");
|
||||
item.onClick(() => {
|
||||
this.duplicateTask(task);
|
||||
});
|
||||
});
|
||||
|
||||
menu.addSeparator();
|
||||
menu.addItem((item) => {
|
||||
item.setTitle(t("Edit"));
|
||||
item.setIcon("pencil");
|
||||
item.onClick(() => {
|
||||
this.handleTaskSelection(task);
|
||||
});
|
||||
});
|
||||
menu.addItem((item) => {
|
||||
item.setTitle(t("Edit in File"));
|
||||
item.setIcon("pencil");
|
||||
item.onClick(() => {
|
||||
this.editTask(task);
|
||||
});
|
||||
});
|
||||
|
||||
menu.addSeparator();
|
||||
menu.addItem((item) => {
|
||||
item.setTitle(t("Delete Task"));
|
||||
item.setIcon("trash");
|
||||
item.onClick(() => {
|
||||
this.confirmAndDeleteTask(event, task);
|
||||
});
|
||||
});
|
||||
|
||||
menu.showAtMouseEvent(event);
|
||||
}
|
||||
|
||||
private addPriorityMenuItems(menu: Menu, task: Task): void {
|
||||
menu.addSeparator();
|
||||
|
||||
menu.addItem((item) => {
|
||||
const subMenu = item
|
||||
.setTitle(t("Set Priority"))
|
||||
.setIcon("flag")
|
||||
.setSubmenu();
|
||||
|
||||
const priorityLevels = [
|
||||
{ level: 5, label: "Highest", icon: "alert-triangle" },
|
||||
{ level: 4, label: "High", icon: "arrow-up" },
|
||||
{ level: 3, label: "Medium", icon: "minus" },
|
||||
{ level: 2, label: "Low", icon: "arrow-down" },
|
||||
{ level: 1, label: "Lowest", icon: "chevron-down" },
|
||||
] as const;
|
||||
|
||||
priorityLevels.forEach(({ level, label, icon }) => {
|
||||
subMenu.addItem((subItem) => {
|
||||
subItem
|
||||
.setTitle(t(label))
|
||||
.setIcon(icon)
|
||||
.setChecked(task.metadata.priority === level)
|
||||
.onClick(() => {
|
||||
this.setPriority(task, level);
|
||||
});
|
||||
});
|
||||
})
|
||||
.addSeparator()
|
||||
.addItem((item) => {
|
||||
item.setTitle(t("Delete Task"));
|
||||
item.setIcon("trash");
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private async setPriority(task: Task, priority: number): Promise<void> {
|
||||
const updatedTask: Task = {
|
||||
...task,
|
||||
metadata: {
|
||||
...task.metadata,
|
||||
priority,
|
||||
},
|
||||
};
|
||||
|
||||
await this.handleTaskUpdate(task, updatedTask, t("Priority updated"));
|
||||
}
|
||||
|
||||
private addDateMenuItems(menu: Menu, task: Task): void {
|
||||
menu.addSeparator();
|
||||
|
||||
const existingDates = this.getExistingDateTypes(task);
|
||||
const dateTypes: TaskDateType[] = [
|
||||
"dueDate",
|
||||
"startDate",
|
||||
"scheduledDate",
|
||||
];
|
||||
|
||||
dateTypes.forEach((dateType) => {
|
||||
menu.addItem((item) => {
|
||||
item.setTitle(t(`Set ${this.getDateLabel(dateType)}`));
|
||||
item.setIcon("calendar");
|
||||
item.onClick(() => {
|
||||
this.confirmAndDeleteTask(event, task);
|
||||
this.openDatePicker(task, dateType);
|
||||
});
|
||||
});
|
||||
|
||||
menu.showAtMouseEvent(event);
|
||||
if (existingDates.includes(dateType)) {
|
||||
this.addPostponeDateMenu(menu, task, dateType);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private addPostponeDateMenu(
|
||||
menu: Menu,
|
||||
task: Task,
|
||||
dateType: TaskDateType,
|
||||
): void {
|
||||
menu.addItem((item) => {
|
||||
const subMenu = item
|
||||
.setTitle(t(`Postpone ${this.getDateLabel(dateType)}`))
|
||||
.setIcon("calendar-plus")
|
||||
.setSubmenu();
|
||||
|
||||
const quickOptions = [
|
||||
{ days: 1, label: "1 day" },
|
||||
{ days: 2, label: "2 days" },
|
||||
{ days: 3, label: "3 days" },
|
||||
{ days: 7, label: "1 week" },
|
||||
] as const;
|
||||
|
||||
quickOptions.forEach(({ days, label }) => {
|
||||
subMenu.addItem((subItem) => {
|
||||
subItem.setTitle(t(label)).onClick(() => {
|
||||
this.postponeDate(task, dateType, days);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
subMenu.addSeparator();
|
||||
subMenu.addItem((subItem) => {
|
||||
subItem
|
||||
.setTitle(t("Custom..."))
|
||||
.setIcon("calendar")
|
||||
.onClick(() => {
|
||||
this.openDatePicker(task, dateType, true);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private async postponeDate(
|
||||
task: Task,
|
||||
dateType: TaskDateType,
|
||||
offsetDays: number,
|
||||
): Promise<void> {
|
||||
try {
|
||||
const currentTimestamp = task.metadata?.[dateType];
|
||||
if (typeof currentTimestamp !== "number") {
|
||||
new Notice(t("No date to postpone"));
|
||||
return;
|
||||
}
|
||||
|
||||
const newTimestamp = postponeDateUtil(currentTimestamp, offsetDays);
|
||||
const updatedMetadata = smartPostponeRelatedDates(
|
||||
task,
|
||||
dateType,
|
||||
newTimestamp,
|
||||
offsetDays,
|
||||
);
|
||||
|
||||
const updatedTask: Task = {
|
||||
...task,
|
||||
metadata: updatedMetadata,
|
||||
};
|
||||
|
||||
const action = offsetDays > 0 ? "postponed" : "advanced";
|
||||
const message = `${this.getDateLabel(dateType)} ${action} by ${Math.abs(offsetDays)} day(s)`;
|
||||
|
||||
await this.handleTaskUpdate(task, updatedTask, t(message));
|
||||
} catch (error) {
|
||||
console.error(
|
||||
"[FluentActionHandlers] Failed to postpone date:",
|
||||
error,
|
||||
);
|
||||
new Notice(t("Failed to postpone date"));
|
||||
}
|
||||
}
|
||||
|
||||
private openDatePicker(
|
||||
task: Task,
|
||||
dateType: TaskDateType,
|
||||
isPostpone: boolean = false,
|
||||
): void {
|
||||
const currentDate = task.metadata?.[dateType];
|
||||
const initialDate =
|
||||
typeof currentDate === "number"
|
||||
? new Date(currentDate).toISOString().split("T")[0]
|
||||
: undefined;
|
||||
|
||||
const modal = new DatePickerModal(
|
||||
this.app,
|
||||
this.plugin,
|
||||
initialDate,
|
||||
this.getDateMark(dateType),
|
||||
);
|
||||
|
||||
modal.onDateSelected = async (dateStr: string | null) => {
|
||||
if (!dateStr) return;
|
||||
|
||||
const newTimestamp = new Date(dateStr).getTime();
|
||||
if (Number.isNaN(newTimestamp)) {
|
||||
new Notice(t("Invalid date selected"));
|
||||
return;
|
||||
}
|
||||
|
||||
if (isPostpone && typeof currentDate === "number") {
|
||||
const offsetDays = Math.round(
|
||||
(newTimestamp - currentDate) / (24 * 60 * 60 * 1000),
|
||||
);
|
||||
|
||||
const updatedMetadata = smartPostponeRelatedDates(
|
||||
task,
|
||||
dateType,
|
||||
newTimestamp,
|
||||
offsetDays,
|
||||
);
|
||||
|
||||
const updatedTask: Task = {
|
||||
...task,
|
||||
metadata: updatedMetadata,
|
||||
};
|
||||
|
||||
await this.handleTaskUpdate(
|
||||
task,
|
||||
updatedTask,
|
||||
t(`${this.getDateLabel(dateType)} updated`),
|
||||
);
|
||||
} else {
|
||||
const updatedTask: Task = {
|
||||
...task,
|
||||
metadata: {
|
||||
...task.metadata,
|
||||
[dateType]: newTimestamp,
|
||||
},
|
||||
};
|
||||
|
||||
await this.handleTaskUpdate(
|
||||
task,
|
||||
updatedTask,
|
||||
t(`${this.getDateLabel(dateType)} updated`),
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
modal.open();
|
||||
}
|
||||
|
||||
private async setProject(task: Task): Promise<void> {
|
||||
const currentProject = task.metadata?.project || "";
|
||||
const newProject = await new TextPromptModal(this.app, this.plugin, {
|
||||
title: t("Set Project"),
|
||||
placeholder: t("Enter project name"),
|
||||
initialValue: currentProject,
|
||||
okLabel: t("Save"),
|
||||
cancelLabel: t("Cancel"),
|
||||
allowEmpty: true,
|
||||
suggestion: "project",
|
||||
}).openAndWait();
|
||||
|
||||
if (newProject === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
const normalized = newProject.trim();
|
||||
|
||||
const updatedTask: Task = {
|
||||
...task,
|
||||
metadata: {
|
||||
...task.metadata,
|
||||
project: normalized || undefined,
|
||||
},
|
||||
};
|
||||
|
||||
await this.handleTaskUpdate(task, updatedTask, t("Project updated"));
|
||||
}
|
||||
|
||||
private async addTags(task: Task): Promise<void> {
|
||||
const newTagsInput = await new TextPromptModal(this.app, this.plugin, {
|
||||
title: t("Add Tags"),
|
||||
placeholder: t("tag-one, tag-two"),
|
||||
okLabel: t("Add"),
|
||||
cancelLabel: t("Cancel"),
|
||||
suggestion: "tag",
|
||||
}).openAndWait();
|
||||
if (newTagsInput === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
const tags = newTagsInput
|
||||
.split(",")
|
||||
.map((tag) => tag.trim())
|
||||
.filter(Boolean);
|
||||
|
||||
if (tags.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const existingTags = task.metadata.tags ?? [];
|
||||
const mergedTags = Array.from(new Set([...existingTags, ...tags]));
|
||||
|
||||
const updatedTask: Task = {
|
||||
...task,
|
||||
metadata: {
|
||||
...task.metadata,
|
||||
tags: mergedTags,
|
||||
},
|
||||
};
|
||||
|
||||
await this.handleTaskUpdate(task, updatedTask, t("Tags added"));
|
||||
}
|
||||
|
||||
private async duplicateTask(task: Task): Promise<void> {
|
||||
try {
|
||||
if (!this.plugin.writeAPI) {
|
||||
new Notice(t("WriteAPI not available"));
|
||||
return;
|
||||
}
|
||||
|
||||
const formatDate = (timestamp?: number): string | undefined => {
|
||||
if (typeof timestamp !== "number") {
|
||||
return undefined;
|
||||
}
|
||||
return new Date(timestamp).toISOString().split("T")[0];
|
||||
};
|
||||
|
||||
const tags = task.metadata.tags ?? [];
|
||||
|
||||
const duplicateArgs: CreateTaskArgs = {
|
||||
content: `${task.content} (Copy)`,
|
||||
filePath: task.filePath,
|
||||
completed: false,
|
||||
tags: tags.length > 0 ? [...tags] : undefined,
|
||||
project: task.metadata.project,
|
||||
context: task.metadata.context,
|
||||
priority: task.metadata.priority,
|
||||
startDate: formatDate(task.metadata.startDate),
|
||||
dueDate: formatDate(task.metadata.dueDate),
|
||||
};
|
||||
|
||||
const result = await this.plugin.writeAPI.createTask(duplicateArgs);
|
||||
|
||||
if (result.success) {
|
||||
new Notice(t("Task duplicated"));
|
||||
this.onTaskUpdated?.(task.id, task);
|
||||
} else {
|
||||
throw new Error(result.error || "Failed to duplicate task");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(
|
||||
"[FluentActionHandlers] Failed to duplicate task:",
|
||||
error,
|
||||
);
|
||||
new Notice(t("Failed to duplicate task"));
|
||||
}
|
||||
}
|
||||
private getExistingDateTypes(task: Task): TaskDateType[] {
|
||||
return getExistingDateTypesFromTask(task);
|
||||
}
|
||||
|
||||
private getDateLabel(dateType: TaskDateType): string {
|
||||
switch (dateType) {
|
||||
case "startDate":
|
||||
return "Start Date";
|
||||
case "scheduledDate":
|
||||
return "Scheduled Date";
|
||||
case "dueDate":
|
||||
default:
|
||||
return "Due Date";
|
||||
}
|
||||
}
|
||||
|
||||
private getDateMark(dateType: TaskDateType): string {
|
||||
switch (dateType) {
|
||||
case "scheduledDate":
|
||||
return "⏳";
|
||||
case "startDate":
|
||||
return "🚀";
|
||||
case "dueDate":
|
||||
default:
|
||||
return "📅";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -128,7 +128,7 @@ export class HabitList {
|
|||
.onClick(() => {
|
||||
this.deleteHabit(habit);
|
||||
});
|
||||
}
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
|
|
@ -148,7 +148,7 @@ export class HabitList {
|
|||
// 更新已有习惯
|
||||
const habits = this.plugin.settings.habit.habits;
|
||||
const index = habits.findIndex(
|
||||
(h) => h.id === habitData.id
|
||||
(h) => h.id === habitData.id,
|
||||
);
|
||||
if (index > -1) {
|
||||
habits[index] = updatedHabit;
|
||||
|
|
@ -161,10 +161,10 @@ export class HabitList {
|
|||
// 保存设置并刷新显示
|
||||
this.plugin.saveSettings();
|
||||
// 重新初始化习惯索引,通知视图刷新
|
||||
void this.plugin.habitManager?.initializeHabits();
|
||||
this.plugin.habitManager?.initializeHabits();
|
||||
this.render();
|
||||
new Notice(habitData ? t("Habit updated") : t("Habit added"));
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
dialog.open();
|
||||
|
|
@ -180,7 +180,7 @@ export class HabitList {
|
|||
content.setText(
|
||||
t(`Are you sure you want to delete the habit `) +
|
||||
`"${habitName}"?` +
|
||||
t("This action cannot be undone.")
|
||||
t("This action cannot be undone."),
|
||||
);
|
||||
|
||||
modal.contentEl.createDiv(
|
||||
|
|
@ -209,19 +209,19 @@ export class HabitList {
|
|||
|
||||
const habits = this.plugin.settings.habit.habits;
|
||||
const index = habits.findIndex(
|
||||
(h) => h.id === habit.id
|
||||
(h) => h.id === habit.id,
|
||||
);
|
||||
if (index > -1) {
|
||||
habits.splice(index, 1);
|
||||
this.plugin.saveSettings();
|
||||
// 重新初始化习惯索引,通知视图刷新
|
||||
void this.plugin.habitManager?.initializeHabits();
|
||||
this.plugin.habitManager?.initializeHabits();
|
||||
this.render();
|
||||
new Notice(t("Habit deleted"));
|
||||
}
|
||||
modal.close();
|
||||
});
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
modal.open();
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ export class FileFilterRuleEditorModal extends Modal {
|
|||
constructor(
|
||||
app: App,
|
||||
plugin: TaskProgressBarPlugin,
|
||||
options: FileFilterRuleEditorModalOptions = {}
|
||||
options: FileFilterRuleEditorModalOptions = {},
|
||||
) {
|
||||
super(app);
|
||||
this.plugin = plugin;
|
||||
|
|
@ -38,7 +38,7 @@ export class FileFilterRuleEditorModal extends Modal {
|
|||
contentEl.createEl("h2", { text: t("Edit File Filter Rules") });
|
||||
contentEl.createEl("p", {
|
||||
text: t(
|
||||
"Configure which files, folders, or patterns are included during task indexing."
|
||||
"Configure which files, folders, or patterns are included during task indexing.",
|
||||
),
|
||||
cls: "setting-item-description",
|
||||
});
|
||||
|
|
@ -47,14 +47,14 @@ export class FileFilterRuleEditorModal extends Modal {
|
|||
.setName(t("Filter Mode"))
|
||||
.setDesc(
|
||||
t(
|
||||
"Whitelist: include only specified paths. Blacklist: exclude specified paths."
|
||||
)
|
||||
"Whitelist: include only specified paths. Blacklist: exclude specified paths.",
|
||||
),
|
||||
)
|
||||
.addDropdown((dropdown) =>
|
||||
dropdown
|
||||
.addOption(
|
||||
FilterMode.WHITELIST,
|
||||
t("Whitelist (Include only)")
|
||||
t("Whitelist (Include only)"),
|
||||
)
|
||||
.addOption(FilterMode.BLACKLIST, t("Blacklist (Exclude)"))
|
||||
.setValue(this.plugin.settings.fileFilter.mode)
|
||||
|
|
@ -62,7 +62,7 @@ export class FileFilterRuleEditorModal extends Modal {
|
|||
this.plugin.settings.fileFilter.mode = value;
|
||||
await this.plugin.saveSettings();
|
||||
this.updateStats();
|
||||
})
|
||||
}),
|
||||
);
|
||||
|
||||
const actionSetting = new Setting(contentEl);
|
||||
|
|
@ -74,18 +74,18 @@ export class FileFilterRuleEditorModal extends Modal {
|
|||
.setButtonText(t("Add File Rule"))
|
||||
.setCta()
|
||||
.onClick(() => {
|
||||
void this.addRule("file");
|
||||
})
|
||||
this.addRule("file");
|
||||
}),
|
||||
)
|
||||
.addButton((button) =>
|
||||
button.setButtonText(t("Add Folder Rule")).onClick(() => {
|
||||
void this.addRule("folder");
|
||||
})
|
||||
this.addRule("folder");
|
||||
}),
|
||||
)
|
||||
.addButton((button) =>
|
||||
button.setButtonText(t("Add Pattern Rule")).onClick(() => {
|
||||
void this.addRule("pattern");
|
||||
})
|
||||
this.addRule("pattern");
|
||||
}),
|
||||
);
|
||||
|
||||
this.rulesContainer = contentEl.createDiv({
|
||||
|
|
@ -100,14 +100,14 @@ export class FileFilterRuleEditorModal extends Modal {
|
|||
button
|
||||
.setButtonText(t("Done"))
|
||||
.setCta()
|
||||
.onClick(() => this.close())
|
||||
.onClick(() => this.close()),
|
||||
);
|
||||
|
||||
this.renderRules();
|
||||
this.updateStats();
|
||||
|
||||
if (this.options.autoAddRuleType) {
|
||||
void this.addRule(this.options.autoAddRuleType).then((rule) => {
|
||||
this.addRule(this.options.autoAddRuleType).then((rule) => {
|
||||
this.pendingRule = rule;
|
||||
});
|
||||
}
|
||||
|
|
@ -116,11 +116,11 @@ export class FileFilterRuleEditorModal extends Modal {
|
|||
onClose() {
|
||||
if (this.pendingRule && !this.pendingRule.path.trim()) {
|
||||
const index = this.plugin.settings.fileFilter.rules.indexOf(
|
||||
this.pendingRule
|
||||
this.pendingRule,
|
||||
);
|
||||
if (index !== -1) {
|
||||
this.plugin.settings.fileFilter.rules.splice(index, 1);
|
||||
void this.plugin.saveSettings();
|
||||
this.plugin.saveSettings();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -130,7 +130,7 @@ export class FileFilterRuleEditorModal extends Modal {
|
|||
}
|
||||
|
||||
private async addRule(
|
||||
type: FileFilterRule["type"]
|
||||
type: FileFilterRule["type"],
|
||||
): Promise<FileFilterRule> {
|
||||
const newRule: FileFilterRule = {
|
||||
type,
|
||||
|
|
@ -207,8 +207,8 @@ export class FileFilterRuleEditorModal extends Modal {
|
|||
rule.type === "pattern"
|
||||
? "*.tmp, temp/*"
|
||||
: rule.type === "folder"
|
||||
? "path/to/folder"
|
||||
: "path/to/file.md",
|
||||
? "path/to/folder"
|
||||
: "path/to/file.md",
|
||||
});
|
||||
|
||||
if (rule.type === "folder") {
|
||||
|
|
@ -216,7 +216,7 @@ export class FileFilterRuleEditorModal extends Modal {
|
|||
this.plugin.app,
|
||||
pathInput,
|
||||
this.plugin,
|
||||
"single"
|
||||
"single",
|
||||
);
|
||||
} else if (rule.type === "file") {
|
||||
new FileSuggest(pathInput, this.plugin, (file) => {
|
||||
|
|
@ -269,7 +269,7 @@ export class FileFilterRuleEditorModal extends Modal {
|
|||
container.empty();
|
||||
|
||||
const activeRules = this.plugin.settings.fileFilter.rules.filter(
|
||||
(rule) => rule.enabled
|
||||
(rule) => rule.enabled,
|
||||
).length;
|
||||
|
||||
const stats = [
|
||||
|
|
|
|||
|
|
@ -199,7 +199,7 @@ export class FileNameInput {
|
|||
if (updated) {
|
||||
this.plugin.settings.quickCapture.fileNameTemplates = templates;
|
||||
if (typeof this.plugin.saveSettings === "function") {
|
||||
void this.plugin.saveSettings();
|
||||
this.plugin.saveSettings();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -601,7 +601,7 @@ export class QuickCaptureModal extends BaseQuickCaptureModal {
|
|||
} else {
|
||||
if (this.previewPlainEl) {
|
||||
const snapshot = this.capturedContent;
|
||||
void this.computeFileModePreviewContent(snapshot).then(
|
||||
this.computeFileModePreviewContent(snapshot).then(
|
||||
(finalContent) => {
|
||||
if (
|
||||
this.previewPlainEl &&
|
||||
|
|
|
|||
122
src/components/ui/modals/TextPromptModal.ts
Normal file
122
src/components/ui/modals/TextPromptModal.ts
Normal file
|
|
@ -0,0 +1,122 @@
|
|||
import { App, Modal, Notice, Setting } from "obsidian";
|
||||
import TaskProgressBarPlugin from "@/index";
|
||||
import { ProjectSuggest, TagSuggest } from "@/components/ui/inputs/AutoComplete";
|
||||
import { t } from "@/translations/helper";
|
||||
|
||||
export type TextPromptSuggestion = "project" | "tag";
|
||||
|
||||
export interface TextPromptOptions {
|
||||
title: string;
|
||||
placeholder?: string;
|
||||
initialValue?: string;
|
||||
okLabel?: string;
|
||||
cancelLabel?: string;
|
||||
allowEmpty?: boolean;
|
||||
suggestion?: TextPromptSuggestion;
|
||||
}
|
||||
|
||||
export class TextPromptModal extends Modal {
|
||||
private readonly allowEmpty: boolean;
|
||||
private currentValue: string;
|
||||
private resolvePromise: ((value: string | null) => void) | null = null;
|
||||
private resolved = false;
|
||||
private inputEl: HTMLInputElement | null = null;
|
||||
private inputKeydownHandler?: (event: KeyboardEvent) => void;
|
||||
|
||||
constructor(
|
||||
app: App,
|
||||
private readonly plugin: TaskProgressBarPlugin,
|
||||
private readonly options: TextPromptOptions,
|
||||
) {
|
||||
super(app);
|
||||
this.currentValue = options.initialValue ?? "";
|
||||
this.allowEmpty = options.allowEmpty ?? false;
|
||||
}
|
||||
|
||||
openAndWait(): Promise<string | null> {
|
||||
return new Promise((resolve) => {
|
||||
this.resolvePromise = resolve;
|
||||
this.open();
|
||||
});
|
||||
}
|
||||
|
||||
onOpen(): void {
|
||||
this.titleEl.setText(this.options.title);
|
||||
const { contentEl } = this;
|
||||
contentEl.empty();
|
||||
|
||||
const inputSetting = new Setting(contentEl);
|
||||
inputSetting.setName(t("Set value")).addText((text) => {
|
||||
text.setPlaceholder(this.options.placeholder ?? "");
|
||||
text.setValue(this.currentValue);
|
||||
text.onChange((value) => {
|
||||
this.currentValue = value;
|
||||
});
|
||||
this.inputEl = text.inputEl;
|
||||
});
|
||||
|
||||
if (this.inputEl) {
|
||||
if (this.options.suggestion === "project") {
|
||||
new ProjectSuggest(this.app, this.inputEl, this.plugin);
|
||||
} else if (this.options.suggestion === "tag") {
|
||||
new TagSuggest(this.app, this.inputEl, this.plugin);
|
||||
}
|
||||
|
||||
this.inputKeydownHandler = (event: KeyboardEvent) => {
|
||||
if (event.key === "Enter" && !event.isComposing) {
|
||||
event.preventDefault();
|
||||
this.handleSubmit();
|
||||
}
|
||||
};
|
||||
this.inputEl.addEventListener("keydown", this.inputKeydownHandler);
|
||||
}
|
||||
|
||||
const buttonSetting = new Setting(contentEl);
|
||||
buttonSetting.addButton((btn) => {
|
||||
btn.setButtonText(this.options.cancelLabel ?? t("Cancel"));
|
||||
btn.onClick(() => {
|
||||
this.finish(null);
|
||||
});
|
||||
});
|
||||
buttonSetting.addButton((btn) => {
|
||||
btn.setButtonText(this.options.okLabel ?? t("Save")).setCta();
|
||||
btn.onClick(() => {
|
||||
this.handleSubmit();
|
||||
});
|
||||
});
|
||||
|
||||
window.setTimeout(() => {
|
||||
if (this.inputEl) {
|
||||
this.inputEl.focus();
|
||||
this.inputEl.select();
|
||||
}
|
||||
}, 50);
|
||||
}
|
||||
|
||||
onClose(): void {
|
||||
if (this.inputEl && this.inputKeydownHandler) {
|
||||
this.inputEl.removeEventListener("keydown", this.inputKeydownHandler);
|
||||
}
|
||||
this.contentEl.empty();
|
||||
if (!this.resolved) {
|
||||
this.resolved = true;
|
||||
this.resolvePromise?.(null);
|
||||
}
|
||||
}
|
||||
|
||||
private handleSubmit(): void {
|
||||
const trimmedValue = this.currentValue.trim();
|
||||
if (!this.allowEmpty && trimmedValue.length === 0) {
|
||||
new Notice(t("Value cannot be empty"));
|
||||
return;
|
||||
}
|
||||
this.finish(trimmedValue);
|
||||
}
|
||||
|
||||
private finish(value: string | null): void {
|
||||
if (this.resolved) return;
|
||||
this.resolved = true;
|
||||
this.resolvePromise?.(value);
|
||||
this.close();
|
||||
}
|
||||
}
|
||||
|
|
@ -60,7 +60,7 @@ export class FileSource {
|
|||
constructor(
|
||||
private app: App,
|
||||
initialConfig?: Partial<FileSourceConfiguration>,
|
||||
private fileFilterManager?: FileFilterManager
|
||||
private fileFilterManager?: FileFilterManager,
|
||||
) {
|
||||
this.config = new FileSourceConfig(initialConfig);
|
||||
}
|
||||
|
|
@ -94,7 +94,7 @@ export class FileSource {
|
|||
console.log(
|
||||
`[FileSource] Initialized with strategies: ${this.config
|
||||
.getEnabledStrategies()
|
||||
.join(", ")}`
|
||||
.join(", ")}`,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -108,7 +108,7 @@ export class FileSource {
|
|||
if (payload?.path) {
|
||||
this.handleFileUpdate(payload.path, payload.reason);
|
||||
}
|
||||
})
|
||||
}),
|
||||
);
|
||||
|
||||
// Subscribe to more granular events if they exist
|
||||
|
|
@ -121,8 +121,8 @@ export class FileSource {
|
|||
if (payload?.path) {
|
||||
this.handleFileMetadataChange(payload.path);
|
||||
}
|
||||
}
|
||||
)
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
this.eventRefs.push(
|
||||
|
|
@ -133,8 +133,8 @@ export class FileSource {
|
|||
if (payload?.path) {
|
||||
this.handleFileContentChange(payload.path);
|
||||
}
|
||||
}
|
||||
)
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -162,7 +162,7 @@ export class FileSource {
|
|||
} catch (error) {
|
||||
console.error(
|
||||
`[FileSource] Error processing file update for ${filePath}:`,
|
||||
error
|
||||
error,
|
||||
);
|
||||
}
|
||||
}, this.DEBOUNCE_DELAY);
|
||||
|
|
@ -191,7 +191,7 @@ export class FileSource {
|
|||
*/
|
||||
private async processFileUpdate(
|
||||
filePath: string,
|
||||
reason: string
|
||||
reason: string,
|
||||
): Promise<void> {
|
||||
if (reason === "delete") {
|
||||
await this.removeFileTask(filePath);
|
||||
|
|
@ -232,12 +232,12 @@ export class FileSource {
|
|||
return this.evaluateRecognitionStrategies(
|
||||
filePath,
|
||||
fileContent,
|
||||
fileCache
|
||||
fileCache,
|
||||
);
|
||||
} catch (error) {
|
||||
console.error(
|
||||
`[FileSource] Error reading file ${filePath}:`,
|
||||
error
|
||||
error,
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
|
@ -249,10 +249,10 @@ export class FileSource {
|
|||
private evaluateRecognitionStrategies(
|
||||
filePath: string,
|
||||
fileContent: string,
|
||||
fileCache: CachedMetadata | null
|
||||
fileCache: CachedMetadata | null,
|
||||
): boolean {
|
||||
const config = this.config.getConfig();
|
||||
const {recognitionStrategies} = config;
|
||||
const { recognitionStrategies } = config;
|
||||
|
||||
// Check metadata strategy
|
||||
if (recognitionStrategies.metadata.enabled) {
|
||||
|
|
@ -261,7 +261,7 @@ export class FileSource {
|
|||
filePath,
|
||||
fileContent,
|
||||
fileCache,
|
||||
recognitionStrategies.metadata
|
||||
recognitionStrategies.metadata,
|
||||
)
|
||||
) {
|
||||
return true;
|
||||
|
|
@ -275,7 +275,7 @@ export class FileSource {
|
|||
filePath,
|
||||
fileContent,
|
||||
fileCache,
|
||||
recognitionStrategies.tags
|
||||
recognitionStrategies.tags,
|
||||
)
|
||||
) {
|
||||
return true;
|
||||
|
|
@ -289,7 +289,7 @@ export class FileSource {
|
|||
filePath,
|
||||
fileContent,
|
||||
fileCache,
|
||||
recognitionStrategies.templates
|
||||
recognitionStrategies.templates,
|
||||
)
|
||||
) {
|
||||
return true;
|
||||
|
|
@ -303,7 +303,7 @@ export class FileSource {
|
|||
filePath,
|
||||
fileContent,
|
||||
fileCache,
|
||||
recognitionStrategies.paths
|
||||
recognitionStrategies.paths,
|
||||
)
|
||||
) {
|
||||
return true;
|
||||
|
|
@ -320,17 +320,17 @@ export class FileSource {
|
|||
filePath: string,
|
||||
fileContent: string,
|
||||
fileCache: CachedMetadata | null,
|
||||
config: any
|
||||
config: any,
|
||||
): boolean {
|
||||
if (!fileCache?.frontmatter) return false;
|
||||
|
||||
const {taskFields, requireAllFields} = config;
|
||||
const { taskFields, requireAllFields } = config;
|
||||
const frontmatter = fileCache.frontmatter;
|
||||
|
||||
const matchingFields = taskFields.filter(
|
||||
(field: string) =>
|
||||
frontmatter.hasOwnProperty(field) &&
|
||||
frontmatter[field] !== undefined
|
||||
frontmatter[field] !== undefined,
|
||||
);
|
||||
|
||||
if (requireAllFields) {
|
||||
|
|
@ -347,11 +347,11 @@ export class FileSource {
|
|||
filePath: string,
|
||||
fileContent: string,
|
||||
fileCache: CachedMetadata | null,
|
||||
config: any
|
||||
config: any,
|
||||
): boolean {
|
||||
if (!fileCache?.tags) return false;
|
||||
|
||||
const {taskTags, matchMode} = config;
|
||||
const { taskTags, matchMode } = config;
|
||||
const fileTags = fileCache.tags.map((tag) => tag.tag);
|
||||
|
||||
return taskTags.some((taskTag: string) => {
|
||||
|
|
@ -377,7 +377,7 @@ export class FileSource {
|
|||
filePath: string,
|
||||
fileContent: string,
|
||||
fileCache: CachedMetadata | null,
|
||||
config: TemplateRecognitionConfig
|
||||
config: TemplateRecognitionConfig,
|
||||
): boolean {
|
||||
if (
|
||||
!config.enabled ||
|
||||
|
|
@ -415,7 +415,7 @@ export class FileSource {
|
|||
filePath: string,
|
||||
fileContent: string,
|
||||
fileCache: CachedMetadata | null,
|
||||
config: PathRecognitionConfig
|
||||
config: PathRecognitionConfig,
|
||||
): boolean {
|
||||
if (
|
||||
!config.enabled ||
|
||||
|
|
@ -436,7 +436,7 @@ export class FileSource {
|
|||
case "prefix":
|
||||
if (normalizedPath.startsWith(normalizedPattern)) {
|
||||
console.log(
|
||||
`[FileSource] Path matches prefix pattern: ${pattern} for ${filePath}`
|
||||
`[FileSource] Path matches prefix pattern: ${pattern} for ${filePath}`,
|
||||
);
|
||||
return true;
|
||||
}
|
||||
|
|
@ -447,14 +447,14 @@ export class FileSource {
|
|||
const regex = new RegExp(normalizedPattern);
|
||||
if (regex.test(normalizedPath)) {
|
||||
console.log(
|
||||
`[FileSource] Path matches regex pattern: ${pattern} for ${filePath}`
|
||||
`[FileSource] Path matches regex pattern: ${pattern} for ${filePath}`,
|
||||
);
|
||||
return true;
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn(
|
||||
`[FileSource] Invalid regex pattern: ${pattern}`,
|
||||
e
|
||||
e,
|
||||
);
|
||||
}
|
||||
break;
|
||||
|
|
@ -464,7 +464,7 @@ export class FileSource {
|
|||
this.matchGlobPattern(normalizedPath, normalizedPattern)
|
||||
) {
|
||||
console.log(
|
||||
`[FileSource] Path matches glob pattern: ${pattern} for ${filePath}`
|
||||
`[FileSource] Path matches glob pattern: ${pattern} for ${filePath}`,
|
||||
);
|
||||
return true;
|
||||
}
|
||||
|
|
@ -501,7 +501,7 @@ export class FileSource {
|
|||
} catch (e) {
|
||||
console.warn(
|
||||
`[FileSource] Failed to compile glob pattern: ${pattern}`,
|
||||
e
|
||||
e,
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
|
@ -511,7 +511,7 @@ export class FileSource {
|
|||
* Create a new file task
|
||||
*/
|
||||
async createFileTask(
|
||||
filePath: string
|
||||
filePath: string,
|
||||
): Promise<Task<FileSourceTaskMetadata> | null> {
|
||||
const file = this.app.vault.getAbstractFileByPath(filePath) as TFile;
|
||||
if (!file) return null;
|
||||
|
|
@ -523,7 +523,7 @@ export class FileSource {
|
|||
const fileTask = await this.buildFileTask(
|
||||
filePath,
|
||||
fileContent,
|
||||
fileCache
|
||||
fileCache,
|
||||
);
|
||||
if (!fileTask) return null;
|
||||
|
||||
|
|
@ -540,7 +540,7 @@ export class FileSource {
|
|||
} catch (error) {
|
||||
console.error(
|
||||
`[FileSource] Error creating file task for ${filePath}:`,
|
||||
error
|
||||
error,
|
||||
);
|
||||
return null;
|
||||
}
|
||||
|
|
@ -550,7 +550,7 @@ export class FileSource {
|
|||
* Update an existing file task
|
||||
*/
|
||||
async updateFileTask(
|
||||
filePath: string
|
||||
filePath: string,
|
||||
): Promise<Task<FileSourceTaskMetadata> | null> {
|
||||
// For Phase 1, just recreate the task
|
||||
// Phase 2 will add smart update detection
|
||||
|
|
@ -570,7 +570,7 @@ export class FileSource {
|
|||
// Update statistics
|
||||
this.stats.trackedFileCount = Math.max(
|
||||
0,
|
||||
this.stats.trackedFileCount - 1
|
||||
this.stats.trackedFileCount - 1,
|
||||
);
|
||||
|
||||
// Emit removal event
|
||||
|
|
@ -592,7 +592,7 @@ export class FileSource {
|
|||
private async buildFileTask(
|
||||
filePath: string,
|
||||
fileContent: string,
|
||||
fileCache: CachedMetadata | null
|
||||
fileCache: CachedMetadata | null,
|
||||
): Promise<Task<FileSourceTaskMetadata> | null> {
|
||||
const config = this.config.getConfig();
|
||||
const file = this.app.vault.getAbstractFileByPath(filePath) as TFile;
|
||||
|
|
@ -602,7 +602,7 @@ export class FileSource {
|
|||
const strategy = this.getMatchingStrategy(
|
||||
filePath,
|
||||
fileContent,
|
||||
fileCache
|
||||
fileCache,
|
||||
);
|
||||
if (!strategy) return null;
|
||||
|
||||
|
|
@ -610,7 +610,7 @@ export class FileSource {
|
|||
const content = this.generateTaskContent(
|
||||
filePath,
|
||||
fileContent,
|
||||
fileCache
|
||||
fileCache,
|
||||
);
|
||||
const safeContent =
|
||||
typeof content === "string"
|
||||
|
|
@ -622,7 +622,7 @@ export class FileSource {
|
|||
filePath,
|
||||
fileContent,
|
||||
fileCache,
|
||||
strategy
|
||||
strategy,
|
||||
);
|
||||
|
||||
// Create the file task
|
||||
|
|
@ -658,7 +658,7 @@ export class FileSource {
|
|||
private getMatchingStrategy(
|
||||
filePath: string,
|
||||
fileContent: string,
|
||||
fileCache: CachedMetadata | null
|
||||
fileCache: CachedMetadata | null,
|
||||
): { name: RecognitionStrategy; criteria: string } | null {
|
||||
const config = this.config.getConfig();
|
||||
|
||||
|
|
@ -668,10 +668,10 @@ export class FileSource {
|
|||
filePath,
|
||||
fileContent,
|
||||
fileCache,
|
||||
config.recognitionStrategies.metadata
|
||||
config.recognitionStrategies.metadata,
|
||||
)
|
||||
) {
|
||||
return {name: "metadata", criteria: "frontmatter"};
|
||||
return { name: "metadata", criteria: "frontmatter" };
|
||||
}
|
||||
|
||||
if (
|
||||
|
|
@ -680,10 +680,10 @@ export class FileSource {
|
|||
filePath,
|
||||
fileContent,
|
||||
fileCache,
|
||||
config.recognitionStrategies.tags
|
||||
config.recognitionStrategies.tags,
|
||||
)
|
||||
) {
|
||||
return {name: "tag", criteria: "file-tags"};
|
||||
return { name: "tag", criteria: "file-tags" };
|
||||
}
|
||||
|
||||
// Check path strategy
|
||||
|
|
@ -693,7 +693,7 @@ export class FileSource {
|
|||
filePath,
|
||||
fileContent,
|
||||
fileCache,
|
||||
config.recognitionStrategies.paths
|
||||
config.recognitionStrategies.paths,
|
||||
)
|
||||
) {
|
||||
return {
|
||||
|
|
@ -715,7 +715,7 @@ export class FileSource {
|
|||
fileCache?.frontmatter?.template === templatePath ||
|
||||
fileCache?.frontmatter?.templateFile === templatePath
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
if (matchesTemplate) {
|
||||
|
|
@ -735,7 +735,7 @@ export class FileSource {
|
|||
private generateTaskContent(
|
||||
filePath: string,
|
||||
fileContent: string,
|
||||
fileCache: CachedMetadata | null
|
||||
fileCache: CachedMetadata | null,
|
||||
): string {
|
||||
const config = this.config.getConfig().fileTaskProperties;
|
||||
const fileName = filePath.split("/").pop() || filePath;
|
||||
|
|
@ -805,7 +805,7 @@ export class FileSource {
|
|||
filePath: string,
|
||||
fileContent: string,
|
||||
fileCache: CachedMetadata | null,
|
||||
strategy: { name: RecognitionStrategy; criteria: string }
|
||||
strategy: { name: RecognitionStrategy; criteria: string },
|
||||
): Partial<FileSourceTaskMetadata> {
|
||||
const config = this.config.getConfig();
|
||||
const frontmatter = fileCache?.frontmatter || {};
|
||||
|
|
@ -847,21 +847,20 @@ export class FileSource {
|
|||
: config.fileTaskProperties.defaultStatus;
|
||||
if (rawStatus && status !== rawStatus) {
|
||||
console.log(
|
||||
`[FileSource] Mapped status '${rawStatus}' to '${status}' for ${filePath}`
|
||||
`[FileSource] Mapped status '${rawStatus}' to '${status}' for ${filePath}`,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
// TODO: Future enhancement - read frontmatter.repeat and map into FileSourceTaskMetadata (e.g., as recurrence)
|
||||
// TODO: Future enhancement - read frontmatter.repeat and map into FileSourceTaskMetadata (e.g., as recurrence)
|
||||
|
||||
// Extract standard task metadata
|
||||
const metadata: Partial<FileSourceTaskMetadata> = {
|
||||
dueDate: this.parseDate(frontmatter.dueDate || frontmatter.due),
|
||||
startDate: this.parseDate(
|
||||
frontmatter.startDate || frontmatter.start
|
||||
frontmatter.startDate || frontmatter.start,
|
||||
),
|
||||
scheduledDate: this.parseDate(
|
||||
frontmatter.scheduledDate || frontmatter.scheduled
|
||||
frontmatter.scheduledDate || frontmatter.scheduled,
|
||||
),
|
||||
priority:
|
||||
frontmatter.priority ||
|
||||
|
|
@ -919,7 +918,7 @@ export class FileSource {
|
|||
*/
|
||||
private updateFileTaskCache(
|
||||
filePath: string,
|
||||
task: Task<FileSourceTaskMetadata>
|
||||
task: Task<FileSourceTaskMetadata>,
|
||||
): void {
|
||||
const frontmatterHash = this.generateFrontmatterHash(filePath);
|
||||
|
||||
|
|
@ -944,7 +943,7 @@ export class FileSource {
|
|||
// Simple hash of frontmatter JSON
|
||||
const frontmatterStr = JSON.stringify(
|
||||
fileCache.frontmatter,
|
||||
Object.keys(fileCache.frontmatter).sort()
|
||||
Object.keys(fileCache.frontmatter).sort(),
|
||||
);
|
||||
return this.simpleHash(frontmatterStr);
|
||||
}
|
||||
|
|
@ -967,7 +966,7 @@ export class FileSource {
|
|||
*/
|
||||
private shouldUpdateFileTask(
|
||||
filePath: string,
|
||||
changeType: "metadata" | "content"
|
||||
changeType: "metadata" | "content",
|
||||
): boolean {
|
||||
// Simple check for Phase 1 - always update if file is tracked
|
||||
return this.fileTaskCache.has(filePath);
|
||||
|
|
@ -978,7 +977,7 @@ export class FileSource {
|
|||
*/
|
||||
private updateStatistics(
|
||||
strategy: RecognitionStrategy,
|
||||
delta: number
|
||||
delta: number,
|
||||
): void {
|
||||
this.stats.recognitionBreakdown[strategy] += delta;
|
||||
this.stats.trackedFileCount += delta;
|
||||
|
|
@ -991,7 +990,7 @@ export class FileSource {
|
|||
*/
|
||||
private emitFileTaskUpdate(
|
||||
action: "created" | "updated" | "removed",
|
||||
task: Task<FileSourceTaskMetadata>
|
||||
task: Task<FileSourceTaskMetadata>,
|
||||
): void {
|
||||
const seq = Seq.next();
|
||||
this.lastUpdateSeq = seq;
|
||||
|
|
@ -1025,7 +1024,7 @@ export class FileSource {
|
|||
if (this.fileFilterManager) {
|
||||
const include = this.fileFilterManager.shouldIncludePath(
|
||||
filePath,
|
||||
"file"
|
||||
"file",
|
||||
);
|
||||
if (!include) return false;
|
||||
}
|
||||
|
|
@ -1041,7 +1040,7 @@ export class FileSource {
|
|||
|
||||
const mdFiles = this.app.vault.getMarkdownFiles();
|
||||
console.log(
|
||||
`[FileSource] Found ${mdFiles.length} markdown files to check`
|
||||
`[FileSource] Found ${mdFiles.length} markdown files to check`,
|
||||
);
|
||||
|
||||
let scannedCount = 0;
|
||||
|
|
@ -1053,7 +1052,7 @@ export class FileSource {
|
|||
relevantCount++;
|
||||
try {
|
||||
const shouldBeTask = await this.shouldCreateFileTask(
|
||||
file.path
|
||||
file.path,
|
||||
);
|
||||
if (shouldBeTask) {
|
||||
const task = await this.createFileTask(file.path);
|
||||
|
|
@ -1065,40 +1064,40 @@ export class FileSource {
|
|||
} catch (error) {
|
||||
console.error(
|
||||
`[FileSource] Error scanning ${file.path}:`,
|
||||
error
|
||||
error,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.log(
|
||||
`[FileSource] Initial scan complete: ${mdFiles.length} total files, ${relevantCount} relevant, ${scannedCount} scanned, ${taskCount} file tasks created`
|
||||
`[FileSource] Initial scan complete: ${mdFiles.length} total files, ${relevantCount} relevant, ${scannedCount} scanned, ${taskCount} file tasks created`,
|
||||
);
|
||||
|
||||
if (taskCount === 0 && relevantCount > 0) {
|
||||
console.log(
|
||||
`[FileSource] No file tasks created. Check if your files match the configured recognition strategies:`
|
||||
`[FileSource] No file tasks created. Check if your files match the configured recognition strategies:`,
|
||||
);
|
||||
const config = this.config.getConfig();
|
||||
if (config.recognitionStrategies.metadata.enabled) {
|
||||
console.log(
|
||||
`[FileSource] - Metadata strategy: requires frontmatter with fields: ${config.recognitionStrategies.metadata.taskFields.join(
|
||||
", "
|
||||
)}`
|
||||
", ",
|
||||
)}`,
|
||||
);
|
||||
}
|
||||
if (config.recognitionStrategies.tags.enabled) {
|
||||
console.log(
|
||||
`[FileSource] - Tag strategy: requires tags: ${config.recognitionStrategies.tags.taskTags.join(
|
||||
", "
|
||||
)}`
|
||||
", ",
|
||||
)}`,
|
||||
);
|
||||
}
|
||||
if (config.recognitionStrategies.paths.enabled) {
|
||||
console.log(
|
||||
`[FileSource] - Path strategy: requires files in paths: ${config.recognitionStrategies.paths.taskPaths.join(
|
||||
", "
|
||||
)}`
|
||||
", ",
|
||||
)}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -1116,7 +1115,7 @@ export class FileSource {
|
|||
|
||||
if (newConfig.enabled && !this.isInitialized) {
|
||||
// FileSource is being enabled
|
||||
void this.initialize();
|
||||
this.initialize();
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -1129,7 +1128,7 @@ export class FileSource {
|
|||
* Get current statistics
|
||||
*/
|
||||
getStats(): FileSourceStats {
|
||||
return {...this.stats};
|
||||
return { ...this.stats };
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -1151,14 +1150,14 @@ export class FileSource {
|
|||
* Sync FileSource status mapping from plugin TaskStatus settings
|
||||
*/
|
||||
public syncStatusMappingFromSettings(
|
||||
taskStatuses: Record<string, string>
|
||||
taskStatuses: Record<string, string>,
|
||||
): void {
|
||||
try {
|
||||
this.config.syncWithTaskStatuses(taskStatuses);
|
||||
} catch (e) {
|
||||
console.warn(
|
||||
"[FileSource] Failed to sync status mapping from settings",
|
||||
e
|
||||
e,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -1216,7 +1215,7 @@ export class FileSource {
|
|||
this.stats = {
|
||||
initialized: false,
|
||||
trackedFileCount: 0,
|
||||
recognitionBreakdown: {metadata: 0, tag: 0, template: 0, path: 0},
|
||||
recognitionBreakdown: { metadata: 0, tag: 0, template: 0, path: 0 },
|
||||
lastUpdate: 0,
|
||||
lastUpdateSeq: 0,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -61,7 +61,7 @@ export class IcsSource {
|
|||
// Listen for ICS cache updates
|
||||
this.app.workspace.on("ics-cache-updated" as any, () => {
|
||||
console.log("[IcsSource] ICS cache updated, reloading events...");
|
||||
void this.loadAndEmitIcsEvents();
|
||||
this.loadAndEmitIcsEvents();
|
||||
});
|
||||
|
||||
// Listen for ICS configuration changes
|
||||
|
|
|
|||
|
|
@ -597,7 +597,7 @@ export default class TaskProgressBarPlugin extends Plugin {
|
|||
}
|
||||
|
||||
const isBeta = targetVersion.toLowerCase().includes("beta");
|
||||
void this.changelogManager.openChangelog(targetVersion, isBeta);
|
||||
this.changelogManager.openChangelog(targetVersion, isBeta);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
|
@ -1781,7 +1781,7 @@ export default class TaskProgressBarPlugin extends Plugin {
|
|||
}
|
||||
|
||||
const isBeta = manifestVersion.toLowerCase().includes("beta");
|
||||
void this.changelogManager.openChangelog(manifestVersion, isBeta);
|
||||
this.changelogManager.openChangelog(manifestVersion, isBeta);
|
||||
} catch (error) {
|
||||
console.error("[TG] Failed to show changelog:", error);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1328,7 +1328,7 @@
|
|||
}
|
||||
|
||||
/* Section shared styles */
|
||||
.section-title {
|
||||
.onboarding-section-title {
|
||||
font-size: 1.25em;
|
||||
font-weight: 600;
|
||||
color: var(--text-normal);
|
||||
|
|
|
|||
|
|
@ -103,6 +103,7 @@
|
|||
/* Main container adjustments */
|
||||
.tg-settings-main-container {
|
||||
width: 100%;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/* Ensure search results appear below the header bar */
|
||||
|
|
|
|||
66
src/utils/dateOperations.ts
Normal file
66
src/utils/dateOperations.ts
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
import { addDays } from "date-fns";
|
||||
import { StandardTaskMetadata, Task } from "@/types/task";
|
||||
|
||||
export type TaskDateType = "dueDate" | "startDate" | "scheduledDate";
|
||||
|
||||
/**
|
||||
* Calculate a new timestamp by shifting days relative to the current value.
|
||||
*/
|
||||
export function postponeDate(currentTimestamp: number, offsetDays: number): number {
|
||||
if (!offsetDays) {
|
||||
return currentTimestamp;
|
||||
}
|
||||
|
||||
if (typeof currentTimestamp !== "number" || Number.isNaN(currentTimestamp)) {
|
||||
return currentTimestamp;
|
||||
}
|
||||
|
||||
return addDays(new Date(currentTimestamp), offsetDays).getTime();
|
||||
}
|
||||
|
||||
/**
|
||||
* Update task metadata while keeping related date fields consistent.
|
||||
*/
|
||||
export function smartPostponeRelatedDates(
|
||||
task: Task,
|
||||
dateType: TaskDateType,
|
||||
newTimestamp: number,
|
||||
offsetDays: number,
|
||||
): StandardTaskMetadata {
|
||||
const metadata: StandardTaskMetadata = {
|
||||
...(task.metadata as StandardTaskMetadata),
|
||||
[dateType]: newTimestamp,
|
||||
};
|
||||
|
||||
if (!offsetDays) {
|
||||
return metadata;
|
||||
}
|
||||
|
||||
if (
|
||||
dateType === "dueDate" &&
|
||||
typeof task.metadata?.startDate === "number" &&
|
||||
task.metadata.startDate > newTimestamp
|
||||
) {
|
||||
metadata.startDate = postponeDate(task.metadata.startDate, offsetDays);
|
||||
}
|
||||
|
||||
if (
|
||||
dateType === "dueDate" &&
|
||||
typeof task.metadata?.scheduledDate === "number" &&
|
||||
task.metadata.scheduledDate > newTimestamp
|
||||
) {
|
||||
metadata.scheduledDate = postponeDate(task.metadata.scheduledDate, offsetDays);
|
||||
}
|
||||
|
||||
return metadata;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the list of date fields that are currently set on the task.
|
||||
*/
|
||||
export function getExistingDateTypes(task: Task): TaskDateType[] {
|
||||
const metadata = task.metadata ?? {};
|
||||
const dateTypes: TaskDateType[] = ["dueDate", "startDate", "scheduledDate"];
|
||||
|
||||
return dateTypes.filter((type) => typeof (metadata as StandardTaskMetadata)[type] === "number");
|
||||
}
|
||||
24
styles.css
24
styles.css
File diff suppressed because one or more lines are too long
Loading…
Reference in a new issue