Files
plainleaf/client/components/plainleaf_tasks.ts
T
wushenghuaandCodex bab4b36fae
CI / config (push) Canceled after 0s
CI / test-frontend (push) Canceled after 0s
CI / test-rust (push) Canceled after 0s
CI / test-e2e (push) Canceled after 0s
CI / test-e2e-release (push) Canceled after 0s
CI / build (push) Canceled after 0s
CI / release (push) Canceled after 0s
CI / docker (push) Canceled after 0s
CI / docker-website (push) Canceled after 0s
feat: build Markdown-native task center
Co-Authored-By: Codex <noreply@anthropic.com>
2026-08-12 19:26:10 +08:00

163 lines
4.5 KiB
TypeScript

export type PlainleafTask = {
ref: string;
page: string;
name: string;
state: string;
done: boolean;
scheduled?: string;
deadline?: string;
completed?: string;
tags?: string[];
links?: string[];
pageLastModified?: string;
};
export type PlainleafTaskPatch = {
name?: string;
state?: string;
scheduled?: string | null;
deadline?: string | null;
completed?: string | null;
project?: string | null;
tags?: string[];
};
export type PlainleafNewTask = {
name: string;
scheduled?: string | null;
deadline?: string | null;
project?: string | null;
tags?: string[];
};
export type PlainleafTaskList =
"inbox" | "today" | "planned" | "anytime" | "projects" | "logbook";
export const plainleafTaskInbox = "任务/收件箱";
export function localDateKey(date = new Date()): string {
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, "0");
const day = String(date.getDate()).padStart(2, "0");
return `${year}-${month}-${day}`;
}
export function taskDate(value?: string): string | undefined {
const match = value?.match(/^\d{4}-\d{2}-\d{2}/);
return match?.[0];
}
export function isOverdueTask(
task: PlainleafTask,
today = localDateKey(),
): boolean {
const deadline = taskDate(task.deadline);
return !task.done && !!deadline && deadline < today;
}
export function taskBelongsToToday(
task: PlainleafTask,
today = localDateKey(),
): boolean {
if (task.done) return false;
const scheduled = taskDate(task.scheduled);
const deadline = taskDate(task.deadline);
return scheduled === today || (!!deadline && deadline <= today);
}
export function tasksForInbox(tasks: PlainleafTask[]): PlainleafTask[] {
return tasks.filter(
(task) =>
!task.done && task.page.replace(/\.md$/i, "") === plainleafTaskInbox,
);
}
export function tasksForAnytime(tasks: PlainleafTask[]): PlainleafTask[] {
return tasks.filter(
(task) =>
!task.done &&
!taskDate(task.scheduled) &&
!taskDate(task.deadline) &&
task.page.replace(/\.md$/i, "") !== plainleafTaskInbox,
);
}
function futureTaskDate(
task: PlainleafTask,
today: string,
): string | undefined {
const scheduled = taskDate(task.scheduled);
const deadline = taskDate(task.deadline);
if (scheduled && scheduled > today) return scheduled;
if (deadline && deadline > today) return deadline;
return undefined;
}
export function groupScheduledTasks(
tasks: PlainleafTask[],
today = localDateKey(),
): Array<{ date: string; tasks: PlainleafTask[] }> {
const groups = new Map<string, PlainleafTask[]>();
for (const task of tasks) {
if (task.done) continue;
const date = futureTaskDate(task, today);
if (!date) continue;
const group = groups.get(date) ?? [];
group.push(task);
groups.set(date, group);
}
return [...groups.entries()]
.sort(([a], [b]) => a.localeCompare(b))
.map(([date, groupedTasks]) => ({ date, tasks: groupedTasks }));
}
export function projectForTask(task: PlainleafTask): string | undefined {
const explicit = task.links?.find((link) => link.trim());
if (explicit) return explicit.trim();
const page = task.page.replace(/\.md$/i, "");
return page === plainleafTaskInbox ? undefined : page;
}
export function groupTasksByProject(
tasks: PlainleafTask[],
): Array<{ project: string; tasks: PlainleafTask[] }> {
const groups = new Map<string, PlainleafTask[]>();
for (const task of tasks) {
if (task.done) continue;
const project = projectForTask(task);
if (!project) continue;
const group = groups.get(project) ?? [];
group.push(task);
groups.set(project, group);
}
return [...groups.entries()]
.sort(([a], [b]) => a.localeCompare(b, "zh-CN"))
.map(([project, groupedTasks]) => ({ project, tasks: groupedTasks }));
}
export function logbookTasks(tasks: PlainleafTask[]): PlainleafTask[] {
return tasks
.filter((task) => task.done)
.sort((a, b) =>
(taskDate(b.completed) ?? b.pageLastModified ?? "").localeCompare(
taskDate(a.completed) ?? a.pageLastModified ?? "",
),
);
}
export function sortTodayTasks(
tasks: PlainleafTask[],
today = localDateKey(),
): PlainleafTask[] {
return tasks
.filter((task) => taskBelongsToToday(task, today))
.sort((a, b) => {
const rank = (task: PlainleafTask) => {
if (isOverdueTask(task, today)) return 0;
if (taskDate(task.deadline) === today) return 1;
return 2;
};
return rank(a) - rank(b) || a.name.localeCompare(b.name, "zh-CN");
});
}