---
目标完成度:
锻炼:
阅读:
语言:
睡觉习惯:
date: <% tp.file.title %>
---
<%*
const MAX_DAYS = 30;
// 延续目标老化:超过 STALE_DAYS 天仍未完成的,移入积压池、不再进日记
const STALE_DAYS = 30;
const BACKLOG_PATH = "5.todo/before/积压池.md";
const SINCE_RE = /\s*\[since:: (\d{4}-\d{2}-\d{2})\]/;
const stripSince = (line) => line.replace(SINCE_RE, "").trim();
const baseDate = moment(tp.file.title, "YYYY-MM-DD", true).isValid()
? moment(tp.file.title, "YYYY-MM-DD")
: moment();
let tomorrowGoals = "";
let pastGoalsBlock = "";
let carriedForwardBlock = "";
let foundDate = null;
let daysAgo = 0;
function getSection(content, heading) {
const match = content.match(new RegExp(`## ${heading}\\n([\\s\\S]*?)(?=\\n## |\\n---|\\n#[^#]|$)`));
return match ? match[1].trim() : "";
}
function getUncheckedTasks(block) {
return block
.split('\n')
.map(line => line.trimEnd())
.filter(line => /^- \[ \] .+/.test(line))
.join('\n');
}
function getTomorrowGoals(block) {
return block
.split('\n')
.filter(line => !/^-\s*\[\s*\]\s*$/.test(line.trim()) && line.trim() !== '-' && line.trim() !== '- ')
.filter(line => !/^-\s*\[[xX]\]/.test(line.trim()))
.join('\n')
.trim();
}
function dedupeLines(lines) {
return lines
.filter(line => line.trim())
.filter((line, index, arr) => arr.findIndex(item => stripSince(item) === stripSince(line)) === index);
}
for (let i = 1; i <= MAX_DAYS; i++) {
const targetDate = baseDate.clone().subtract(i, 'days').format('YYYY-MM-DD');
const targetPath = `5.todo/date/${targetDate}.md`;
const targetFile = app.vault.getAbstractFileByPath(targetPath);
if (!targetFile) continue;
// Read the note source directly so Templater doesn't recursively execute nested templates.
const content = await app.vault.read(targetFile);
const candidateTomorrowGoals = getTomorrowGoals(getSection(content, "明日目标"));
const tomorrowLines = new Set(
candidateTomorrowGoals
.split('\n')
.map(line => stripSince(line))
.filter(Boolean)
);
const candidatePastGoalsBlock = dedupeLines([
...getUncheckedTasks(getSection(content, "今日目标")).split('\n'),
...getUncheckedTasks(getSection(content, "往日目标")).split('\n'),
])
.filter(line => !tomorrowLines.has(stripSince(line)))
.join('\n');
const candidateCarriedForwardBlock = dedupeLines([
...getUncheckedTasks(getSection(content, "今日记录")).split('\n'),
...getUncheckedTasks(getSection(content, "延续目标")).split('\n'),
])
.filter(line => !tomorrowLines.has(stripSince(line)))
.join('\n');
if (candidateTomorrowGoals || candidatePastGoalsBlock || candidateCarriedForwardBlock) {
tomorrowGoals = candidateTomorrowGoals;
pastGoalsBlock = candidatePastGoalsBlock;
carriedForwardBlock = candidateCarriedForwardBlock;
foundDate = targetDate;
daysAgo = i;
break;
}
}
// 给延续任务补 [since::] 来源日期,按 STALE_DAYS 分流:新鲜的进日记,过期的移积压池
const freshCarried = [];
const staleCarried = [];
for (const raw of carriedForwardBlock.split('\n')) {
if (!raw.trim()) continue;
const line = SINCE_RE.test(raw) ? raw : `${raw} [since:: ${foundDate}]`;
const since = moment(line.match(SINCE_RE)[1], "YYYY-MM-DD");
(baseDate.diff(since, 'days') > STALE_DAYS ? staleCarried : freshCarried).push(line);
}
if (staleCarried.length) {
let backlog = app.vault.getAbstractFileByPath(BACKLOG_PATH);
if (!backlog) {
backlog = await app.vault.create(BACKLOG_PATH,
`# 积压池\n\n超过 ${STALE_DAYS} 天未完成的延续目标由 daily 模板自动移入,周复盘收网。要复活的任务加 [scheduled:: ] 或手动移回日记。\n`);
}
const backlogContent = await app.vault.read(backlog);
const toAppend = staleCarried.filter(l => !backlogContent.includes(stripSince(l)));
if (toAppend.length) {
await app.vault.modify(backlog,
backlogContent.trimEnd() + "\n" + toAppend.map(l => `${l} [搁置:: ${baseDate.format("YYYY-MM-DD")}]`).join("\n") + "\n");
}
}
const todayGoals = tomorrowGoals || "- [ ] ";
const carriedBlock = freshCarried.length ? freshCarried.join('\n') : "(无延续任务)";
const pastGoals = pastGoalsBlock ? pastGoalsBlock : "(无往日目标)";
const sourcePrefix = foundDate && daysAgo > 1
? `> [!note] 来自 ${foundDate}(${daysAgo} 天前)\n\n`
: "";
-%>
## 今日目标
<% todayGoals %>
## 往日目标
<% sourcePrefix + pastGoals %>
## scheduled
```dataviewjs
const date = dv.date(dv.current().file.name);
if (date) {
const scheduledTasks = dv.pages('"5.todo"').file.tasks
.where(t => !t.completed && t.scheduled && t.scheduled <= date)
.sort(t => t.scheduled, 'asc');
const todayTasks = scheduledTasks.where(t => t.scheduled.equals(date));
const carriedTasks = scheduledTasks.where(t => t.scheduled < date);
if (todayTasks.length > 0) {
dv.taskList(todayTasks, false);
}
if (carriedTasks.length > 0) {
dv.taskList(carriedTasks, false);
}
if (todayTasks.length === 0 && carriedTasks.length === 0) {
dv.paragraph("(无 scheduled 任务)");
}
}
```
## 延续目标
<% sourcePrefix + carriedBlock %>
## 今日复盘
- 做了什么:
- 没做的话为什么:
- 今日亮点:
- 想法/反思/心情:
## 明日目标
- [ ]
## 今日记录