From 7e35b592c0cbd8c5f01482680811d7236e4eebd7 Mon Sep 17 00:00:00 2001
From: Ariver <shanghai3168@gmail.com>
Date: Tue, 25 Aug 2026 16:31:26 +0800
Subject: [PATCH] 日记与笔记文件: YY.MMDD 前缀批量改为 YYYY-MM-DD
---
00index/0inbox/BUG.md | 5
X0.Diary/2026-03-24.md | 0
X0.Diary/2026-08-03.md | 0
X0.Diary/26.0320/2026-03-20_150838_clash 设置备忘.md | 0
X0.Diary/2026-08-07.md | 0
X0.Diary/2026-07-14.md | 0
X0.Diary/2026-05-26.md | 0
X0.Diary/2026-07-18.md | 0
X0.Diary/2026-06-25.md | 0
X0.Diary/2026-07-20.md | 0
01GTD/26.07注销个体户/Drawing 26.0714_注销龙华个体户.excalidraw.md | 2
X0.Diary/2026-08-13.md | 0
X0.Diary/26.0321/26.0321-体验 ijoy/2026-03-21-体验ijoy并做笔记.md | 0
X0.Diary/2026-03-26/Drawing 26.0326.excalidraw.md | 0
X0.Diary/2026-07-28.md | 0
00index/标签聚合/1.6玩软件/操作系统/ios.md | 2
02DS/AI-vibe-coding/case1-mac-app-launcher/X-存档/X-需求描述/2026-05-13-01-列表视图增加标签的拖动排序.md | 0
X0.Diary/2026-08-06.md | 0
X0.Diary/2026-05-23.md | 0
X0.Diary/2026-07-15.md | 0
X0.Diary/2026-08-23.md | 0
X0.Diary/2026-04-05.md | 0
X0.Diary/2026-04-01.md | 0
X0.Diary/2026-05-09.md | 0
X0.Diary/2026-07-21.md | 2
X1.Knomo/_knomo-system/indexes/memo-index-2026-07.json | 199 ++++
X0.Diary/2026-03-26/2026-03-26.md | 0
X0.Diary/2026-08-22.md | 0
X2.Archived/scripts/sync_0inbox.py | 23
X0.Diary/2026-08-12.md | 0
02DS/VIbeCoding进阶-阅读资料/05-专题-UI-视觉设计能力/UI-设计规范/2026-07-16-1220-新点子-智能提炼UIKIT.md | 0
X0.Diary/2026-08-05.md | 0
X0.Diary/2026-03-22.md | 5
X1.Knomo/_knomo-system/indexes/memo-index-2026-08.json | 2200 ++++++++++++++++++++++++++++++++++++++++++++
X0.Diary/2026-08-09.md | 0
X0.Diary/2026-08-24.md | 0
X0.Diary/2026-05-24.md | 0
X0.Diary/2026-07-31.md | 0
X0.Diary/2026-07-16.md | 0
X0.Diary/2026-07-22.md | 0
01GTD/2603-出租嘉润/2026-03-23_141214_.md | 0
X0.Diary/2026-05-30.md | 0
X0.Diary/2026-08-19.md | 0
X0.Diary/2026-08-21.md | 0
X0.Diary/2026-08-11.md | 0
02DS/Ob/插件Notechain/2026-08-14-2228-Notechain.md | 0
01GTD/2603-出租嘉润/26.0322给人发图-嘉润 2304/2026-03-23_135156_.md | 0
X0.Diary/2026-08-04.md | 0
X0.Diary/2026-03-23.md | 0
X0.Diary/2026-08-08.md | 0
X0.Diary/2026-08-25.md | 7
X0.Diary/26.0321/2026-03-21-和产研开会讨论工作.md | 0
01GTD/26.07注销个体户/2026-07-14-172251注销龙华个体户.md | 0
X0.Diary/2026-07-17.md | 0
/dev/null | 437 --------
X0.Diary/2026-08-14.md | 2
X0.Diary/2026-08-20.md | 0
X0.Diary/2026-08-18.md | 0
X0.Diary/2026-07-27.md | 0
X0.Diary/2026-08-10.md | 0
X0.Diary/2026-07-23.md | 0
X0.Diary/2026-07-12-名词解释-npx.md | 0
62 files changed, 2,424 insertions(+), 460 deletions(-)
diff --git a/00index/0inbox/Ancient.md b/00index/0inbox/Ancient.md
deleted file mode 100644
index ffa51f7..0000000
--- a/00index/0inbox/Ancient.md
+++ /dev/null
@@ -1,437 +0,0 @@
----
-tags:
- - tagfeed-live
-cssclasses:
- - tagfeed
-words:
- 2026-08-23: 2728
----
-
-# 🏷 #Ancient(实时聚合页)
-
-> 本页由 DataviewJS 实时计算,笔记有变动时自动刷新,无需任何后台服务。
-
-```dataviewjs
-// ============================================================
-// tagfeed-live:Logseq 风格标签实时聚合(标准 DataviewJS 脚本)
-//
-// 用法:
-// 1. 新建一个笔记(例如 00index/标签聚合/标签名.md)
-// 2. 在笔记里插入一个 dataviewjs 代码块(三个反引号 + dataviewjs),
-// 把本脚本全部粘贴进去
-// 3. 只需要改下面「配置区」的两行:
-// - targetTag 换成你要聚合的标签(不带 #,中英文都可以;多个标签写成数组 ["a", "b"])
-// - excludedPaths 换成你想排除的文件夹
-// 4. 完成。以后任何笔记里出现/删除这个标签,本页自动刷新,
-// 不需要跑任何命令(Dataview 会在文件变动后自动重新渲染)。
-// ============================================================
-
-// ---------------- 配置区(通常只改这里) ----------------
-const targetTag = "Ancient"; // 要聚合的标签(不带 #);多个标签用数组:["lyric", "歌词"]
-const excludedPaths = ["X2.Archived", "00index", "X0.Clippings", "X1.Knomo", "02DS/02copilot", ".agents", ".claude", ".copilot", ".opencode", ".smart-env", ".workbuddy", ".trash"]; // 要排除的文件夹/路径前缀(vault 相对路径)
- // 例如:["00index", "模板", "X1.Knomo"]
-// 补充说明:
-// - 所有隐藏文件/文件夹(. 开头:.agents/.claude/.copilot/.opencode/.smart-env/.workbuddy/.trash 等)与 X.Attachment 始终跳过
-// - tagfeed 系统页会自动跳过:静态聚合页(文件头含 <!-- tagfeed 标记)
-// 和实时页(frontmatter 带 tagfeed / tagfeed-live 标签),
-// 无需手动排除 00index
-// - 本脚本所在的当前笔记也会自动跳过
-// ---------------------------------------------------------
-
-try {
-const SKIP_DIRS = [".obsidian", ".git", ".trash", "X.Attachment"];
-const MARKER = "<!-- tagfeed";
-const WEEKDAYS = ["周一", "周二", "周三", "周四", "周五", "周六", "周日"];
-const PREV_CHARS = 500; // 预览字数上限:≤上限全文展示;>上限截断并附「查看全文」链接
-
-const esc = s => String(s).replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
-const TAG_LIST = Array.isArray(targetTag) ? targetTag : [targetTag];
-const TAG_RE = new RegExp("(?<![\\w\\u4e00-\\u9fff#])#(?:" + TAG_LIST.map(esc).join("|") + ")(?![\\w\\u4e00-\\u9fff/\\-])");
-const ANY_TAG = /(?<![\w#])#([\w\u4e00-\u9fff][\w\u4e00-\u9fff/\-]*)/g;
-const LIST_RE = /^(\s*)([-*+]|\d+[.)])(?:\s+(.*))?$/;
-const HEAD_RE = /^(#{1,6})\s+(.*)$/;
-const DATEH_RE = /\[\[(\d{4})-(\d{2})-(\d{2})\]\]/;
-const TIME_RE = /(\d{1,2}):(\d{2})(?::(\d{2}))?/;
-const FENCE_RE = /^\s*(`{3,}|~~~)/;
-const indentOf = ln => { // tab 按 2 空格计(与 Python 版一致)
- const expanded = ln.replace(/\t/g, " ");
- return expanded.length - expanded.replace(/^ +/, "").length;
-};
-
-function cleanMatch(ln) {
- let s = ln.replace(/`[^`\n]*`/g, "");
- s = s.replace(/%%[\s\S]*?%%/g, "");
- s = s.replace(/<!--[\s\S]*?-->/g, "");
- return s;
-}
-function vdate(y, m, d) {
- const dt = new Date(y, m - 1, d);
- return (dt.getFullYear() === y && dt.getMonth() === m - 1 && dt.getDate() === d) ? dt : null;
-}
-function dateFromName(name) { // 文件名含 YYYY-MM-DD,或日记命名 26.0805 -> 2026-08-05
- const m1 = name.match(/(\d{4})-(\d{2})-(\d{2})/);
- if (m1) {
- const d1 = vdate(+m1[1], +m1[2], +m1[3]);
- if (d1) return d1;
- }
- const m = name.match(/^(\d{2})\.(\d{2})(\d{2})(?:\D|$)/);
- return m ? vdate(2000 + +m[1], +m[2], +m[3]) : null;
-}
-function parseFrontmatter(text) {
- const lines = text.split("\n");
- if (!lines.length || lines[0].trim() !== "---") return { tags: new Set(), date: null, bodyStart: 0 };
- const n = Math.min(lines.length, 200);
- for (let i = 1; i < n; i++) {
- const t = lines[i].trim();
- if (t === "---" || t === "...") {
- const block = lines.slice(1, i).join("\n");
- const tags = new Set();
- const im = block.match(/^tags:\s*\[(.*?)\]/m);
- if (im) {
- for (const x0 of im[1].split(",")) {
- const x = x0.trim().replace(/^["']|["']$/g, "").replace(/^#/, "").trim();
- if (x) tags.add(x);
- }
- } else {
- const bm = block.match(/^tags:\s*\n((?:[ \t]+-[ \t]+.*\n?)+)/m);
- if (bm) for (const ln of bm[1].split("\n")) {
- const m2 = ln.match(/^\s*-\s*["']?([\w\u4e00-\u9fff/\-]+)["']?\s*$/);
- if (m2) tags.add(m2[1]);
- }
- }
- let fdate = null;
- for (const key of ["date", "created", "published"]) {
- const dm = block.match(new RegExp("^" + key + ":\\s*[\"']?(\\d{4})-(\\d{2})-(\\d{2})", "m"));
- if (dm) { fdate = vdate(+dm[1], +dm[2], +dm[3]); break; }
- }
- return { tags, date: fdate, bodyStart: i + 1 };
- }
- }
- return { tags: new Set(), date: null, bodyStart: 0 };
-}
-const stripTag = s => String(s).replace(TAG_RE, "").replace(/[ \t]{2,}/g, " ").trim();
-
-function scanText(text, note, relpath, fileDate, mtimeDate) {
- const fm = parseFrontmatter(text);
- if (fm.tags.has("tagfeed") || fm.tags.has("tagfeed-live")) return []; // 跳过 tagfeed 系统页(静态聚合页/实时页)
- const lines = text.split("\n");
- const entries = [], seen = new Set();
- let order = 0, inFence = false, inPct = false, inHtml = false;
- let curHead = null, curHeadRaw = null, curDateHead = null;
-
- const entryDate = () => curDateHead || fileDate || fm.date || mtimeDate;
- const anchorOf = () => (curDateHead ? null : curHeadRaw);
- const breadcrumb = () => {
- if (curHead && !DATEH_RE.test(curHead)) {
- const t = curHead.replace(ANY_TAG, "").replace(/\s+/g, " ").trim();
- if (t) return t;
- }
- return null;
- };
- function addBlock(keyIdx, block) {
- const key = relpath + "|" + keyIdx;
- if (seen.has(key) || !block.length) return;
- seen.add(key);
- const d = entryDate();
- if (!d) return;
- let t = null;
- const tm = block[0][1].slice(0, 30).match(TIME_RE);
- if (tm) t = [+tm[1], +tm[2], +(tm[3] || 0)];
- order++;
- entries.push({ date: d, time: t, order, relpath, note,
- breadcrumb: breadcrumb(), lines: block,
- pageLevel: false, notes: [note], anchor: anchorOf() });
- }
- function collectChildren(i, parentIndent) {
- const kids = [], raw = [];
- let j = i + 1;
- while (j < lines.length) {
- const ln = lines[j];
- if (ln.trim() === "") {
- let k = j + 1;
- while (k < lines.length && lines[k].trim() === "") k++;
- if (k < lines.length && LIST_RE.test(lines[k]) && indentOf(lines[k]) > parentIndent) { j = k; continue; }
- break;
- }
- const mi = ln.match(LIST_RE);
- if (mi && indentOf(ln) > parentIndent) {
- raw.push([indentOf(ln), (mi[3] || "").replace(/\s+$/, "")]);
- j++;
- } else break;
- }
- for (const [ind, txt] of raw) kids.push([Math.floor((ind - parentIndent) / 2), txt]);
- return kids;
- }
-
- for (let i = fm.bodyStart; i < lines.length; i++) {
- const ln = lines[i];
- if (FENCE_RE.test(ln)) { inFence = !inFence; continue; }
- if (inFence) continue;
- if (inPct) { if (ln.includes("%%")) inPct = false; continue; }
- if (inHtml) { if (ln.includes("-->")) inHtml = false; continue; }
- const stripped = ln.trim();
- if (stripped.startsWith("%%") && !stripped.slice(2).includes("%%")) { inPct = true; continue; }
- if (stripped.startsWith("<!--") && !stripped.includes("-->")) { inHtml = true; continue; }
-
- const hm = ln.match(HEAD_RE);
- if (hm) {
- const htext = hm[2];
- const dh = htext.match(DATEH_RE);
- if (dh) { curDateHead = vdate(+dh[1], +dh[2], +dh[3]); curHead = null; curHeadRaw = null; }
- else { curHead = htext; curHeadRaw = htext.trim(); }
- if (TAG_RE.test(cleanMatch(htext))) {
- const sec = [[0, "**" + stripTag(htext) + "**"]];
- let j = i + 1;
- const level = hm[1].length;
- let count = 0;
- while (j < lines.length && count < 300) {
- const sl = lines[j];
- const shm = sl.match(HEAD_RE);
- if (shm && shm[1].length <= level) break;
- if (FENCE_RE.test(sl)) break;
- if (sl.trim()) {
- const smi = sl.match(LIST_RE);
- if (smi) sec.push([1 + Math.floor(indentOf(sl) / 2), (smi[3] || "").replace(/\s+$/, "")]);
- else sec.push([1, sl.trim()]);
- count++;
- }
- j++;
- }
- addBlock(i, sec);
- }
- continue;
- }
-
- const clean = cleanMatch(ln);
- if (!TAG_RE.test(clean)) continue;
-
- const mi = ln.match(LIST_RE);
- if (mi) {
- const ind = indentOf(ln);
- const itemText = mi[3] || "";
- const cleaned = stripTag(itemText);
- const kids = collectChildren(i, ind);
- addBlock(i, cleaned ? [[0, cleaned], ...kids] : [[0, itemText.trim()], ...kids]);
- continue;
- }
-
- // 独立标签行:向下吸收 list items,或向上归属到紧邻的块
- if (ln.replace(ANY_TAG, "").trim() === "" || ln.replace(ANY_TAG, "").trim() === "%%") {
- let sec = [];
- let j = i + 1;
- while (j < lines.length) {
- const mj = lines[j].match(LIST_RE);
- if (mj) { sec.push([indentOf(lines[j]), (mj[3] || "").replace(/\s+$/, "")]); j++; }
- else break;
- }
- let keyIdx = i;
- if (!sec.length) {
- const up = [];
- let k = i - 1, bulletIdx = null;
- while (k >= fm.bodyStart) {
- const bk = lines[k];
- if (bk.trim() === "" || HEAD_RE.test(bk) || FENCE_RE.test(bk)) break;
- up.push(k);
- if (LIST_RE.test(bk)) { bulletIdx = k; break; }
- k--;
- }
- if (bulletIdx !== null) {
- const inds = [];
- for (let bl = bulletIdx; bl < i; bl++) {
- const mb = lines[bl].match(LIST_RE);
- if (mb) { inds.push(indentOf(lines[bl])); sec.push([indentOf(lines[bl]), (mb[3] || "").replace(/\s+$/, "")]); }
- else sec.push([(inds.length ? inds[inds.length - 1] + 2 : 0), lines[bl].trim()]);
- }
- keyIdx = bulletIdx;
- } else if (up.length) {
- sec = up.slice().reverse().map(k2 => [0, lines[k2].trim()]);
- } else {
- // 兜底:标签行紧挨在某个标题下方(中间只有空行)→ 捕获该标题下的整个小节
- let h = i - 1;
- while (h >= fm.bodyStart && lines[h].trim() === "") h--;
- const hmk = h >= fm.bodyStart ? lines[h].match(HEAD_RE) : null;
- if (hmk) {
- const level = hmk[1].length;
- const title = hmk[2].replace(ANY_TAG, "").replace(/\s+/g, " ").trim();
- const sec2 = title ? [[0, "**" + title + "**"]] : [];
- let j2 = i + 1, count2 = 0;
- while (j2 < lines.length && count2 < 300) {
- const sl = lines[j2];
- const shm = sl.match(HEAD_RE);
- if (shm && shm[1].length <= level) break;
- if (FENCE_RE.test(sl)) break;
- if (sl.trim()) {
- const smi = sl.match(LIST_RE);
- if (smi) sec2.push([1 + Math.floor(indentOf(sl) / 2), (smi[3] || "").replace(/\s+$/, "")]);
- else sec2.push([1, sl.trim().replace(/^>\s*/, "")]);
- count2++;
- }
- j2++;
- }
- if (sec2.length) sec = sec2;
- }
- }
- }
- if (sec.length) {
- const mi0 = Math.min(...sec.map(x => x[0]));
- addBlock(keyIdx, sec.map(([ind, t]) => [Math.floor((ind - mi0) / 2), t]));
- }
- continue;
- }
-
- // 普通段落带标签:吸收到空行为止
- const para = [stripTag(clean)];
- let j = i + 1;
- while (j < lines.length) {
- const nl = lines[j];
- if (nl.trim() === "" || HEAD_RE.test(nl) || LIST_RE.test(nl) || FENCE_RE.test(nl)) break;
- para.push(nl.trim());
- j++;
- }
- addBlock(i, [[0, para.filter(p => p).join(" ")]]);
- }
-
- // frontmatter 标签 → 页面级条目(附正文开头预览)
- if (TAG_LIST.some(t => fm.tags.has(t))) {
- const d = fm.date || fileDate || mtimeDate;
- if (d) {
- order++;
- // 收集正文文本至 PREV_CHARS:超过上限的长文在渲染时截断并附「查看全文」链接
- const prev = [];
- let prevTotal = 0, prevCount = 0;
- for (let k = fm.bodyStart; k < lines.length && prevCount < 200; k++) {
- const s = lines[k].trim();
- if (!s || s.startsWith("#") || s === "---") continue;
- const clean = s.replace(ANY_TAG, "");
- if (!clean) continue;
- prev.push(clean);
- prevTotal += clean.length;
- prevCount++;
- if (prevTotal > PREV_CHARS) break; // 超过上限:已足够判断,停止收集
- }
- const preview = prev.join(" ");
- entries.push({ date: d, time: null, order, relpath, note, breadcrumb: null,
- lines: preview ? [[0, preview]] : [],
- pageLevel: true, notes: [note], anchor: null });
- }
- }
- return entries;
-}
-
-// ---------------- 扫描整个 vault ----------------
-const currentPath = (dv.current() && dv.current().file) ? dv.current().file.path : null;
-let all = [];
-for (const f of app.vault.getMarkdownFiles()) {
- if (f.path === currentPath || f.path.endsWith(".excalidraw.md")) continue;
- if (SKIP_DIRS.some(d => f.path.startsWith(d + "/"))) continue;
- if (f.path.split("/").some(seg => seg.startsWith("."))) continue; // 排除所有隐藏文件/文件夹(.开头)
- if (excludedPaths.some(p => f.path === p || f.path.startsWith(p.endsWith("/") ? p : p + "/"))) continue;
- let text;
- try { text = await app.vault.cachedRead(f); } catch (e) { continue; }
- if (text.slice(0, 1000).includes(MARKER)) continue; // 跳过 tagfeed 生成的静态页
- all = all.concat(scanText(text, f.basename, f.path, dateFromName(f.basename), new Date(f.stat.mtime)));
-}
-
-// ---------------- 去重:同日期+同内容合并,来源并排 ----------------
-const timeCmp = (a, b) => (a[0] - b[0]) || (a[1] - b[1]) || (a[2] - b[2]);
-const mergedMap = new Map();
-for (const e of all) {
- const fp = e.date.getTime() + "|" + e.lines.map(x => x[0] + ":" + x[1]).join("\n");
- const m = mergedMap.get(fp);
- if (m) {
- if (!m.notes.includes(e.note)) m.notes.push(e.note);
- if (e.time && (!m.time || timeCmp(e.time, m.time) < 0)) m.time = e.time;
- // 若先扫到的来源没有面包屑/锚点,而后面的有,则采用后者(与扫描顺序无关,结果确定)
- if (!m.breadcrumb && e.breadcrumb) {
- m.breadcrumb = e.breadcrumb;
- m.anchor = e.anchor;
- m.relpath = e.relpath;
- m.note = e.note;
- }
- } else mergedMap.set(fp, e);
-}
-const entries = [...mergedMap.values()];
-
-// ---------------- 渲染:按日期倒序 ----------------
-const byDate = new Map();
-for (const e of entries) {
- const k = e.date.getTime();
- if (!byDate.has(k)) byDate.set(k, []);
- byDate.get(k).push(e);
-}
-const dates = [...byDate.keys()].sort((a, b) => b - a);
-const noteSet = new Set();
-for (const e of entries) for (const n of e.notes) noteSet.add(n);
-
-const out = [];
-out.push("> **" + entries.length + "** 个内容块 | **" + noteSet.size + "** 篇笔记 | 按日期倒序 | 随笔记变动自动刷新");
-
-const pad2 = x => String(x).padStart(2, "0");
-const fmtDate = d => d.getFullYear() + "-" + pad2(d.getMonth() + 1) + "-" + pad2(d.getDate());
-const invTime = t => t.map(x => pad2(99 - x)).join("");
-
-for (const dk of dates) {
- const d = new Date(dk);
- out.push("## 📅 " + fmtDate(d) + " · " + WEEKDAYS[d.getDay() === 0 ? 6 : d.getDay() - 1]);
- out.push("");
- const group = byDate.get(dk);
-
- const sub = new Map(); // (笔记+面包屑) -> entries
- for (const e of group) {
- const key = e.relpath + "|" + (e.breadcrumb || "");
- if (!sub.has(key)) sub.set(key, []);
- sub.get(key).push(e);
- }
- const subKey = ([key, es]) => {
- const times = es.filter(e => e.time).map(e => e.time);
- const best = times.length ? times.reduce((a, b) => (timeCmp(a, b) > 0 ? a : b)) : null;
- return (best ? "0" : "1") + (best ? invTime(best) : "000000") + key;
- };
-
- for (const [key, es] of [...sub.entries()].sort((a, b) => (subKey(a) < subKey(b) ? -1 : subKey(a) > subKey(b) ? 1 : 0))) {
- const noteList = [];
- for (const e of es) for (const n of e.notes) if (!noteList.includes(n)) noteList.push(n);
- noteList.sort();
- const crumb = es.map(e => e.breadcrumb).find(b => b) || null;
- out.push("- 🔗 " + noteList.map(n => "[[" + n + "]]").join(" · ") + (crumb ? " · " + crumb : ""));
- const sorted = es.slice().sort((a, b) => {
- const ka = (a.time ? "0" : "1") + (a.time ? invTime(a.time) : "") + String(a.order).padStart(6, "0");
- const kb = (b.time ? "0" : "1") + (b.time ? invTime(b.time) : "") + String(b.order).padStart(6, "0");
- return ka < kb ? -1 : ka > kb ? 1 : 0;
- });
- for (const e of sorted) {
- let bl = e.lines, truncated = false;
- const totalChars = bl.reduce((s, x) => s + x[1].length, 0);
- if (totalChars > PREV_CHARS) { // 只有超过上限才截断;上限内全文展示
- truncated = true;
- const shown = [];
- let chars = 0;
- for (const [lvl, txt] of bl) {
- if (chars + txt.length > PREV_CHARS) {
- if (PREV_CHARS - chars > 0) shown.push([lvl, txt.slice(0, PREV_CHARS - chars) + "…"]);
- break;
- }
- shown.push([lvl, txt]);
- chars += txt.length;
- }
- bl = shown;
- }
- for (const [lvl, txt] of bl) out.push("\t".repeat(lvl + 1) + "- " + txt);
- if (truncated) {
- const target = e.note + (e.anchor ? "#" + e.anchor : "");
- const label = e.pageLevel ? "页面级标签 · 查看全文" : "查看全文";
- out.push("\t- [[" + target + "|↗ " + label + "]]");
- }
- }
- out.push("");
- }
- out.push("");
-}
-dv.paragraph(out.join("\n"));
-
-} catch (err) {
- dv.paragraph("> ❌ tagfeed-live 执行失败:" + err.message);
- console.error("tagfeed-live:", err);
-}
-
-```
diff --git a/00index/0inbox/BUG.md b/00index/0inbox/BUG.md
index c4eecad..e9b0ccb 100644
--- a/00index/0inbox/BUG.md
+++ b/00index/0inbox/BUG.md
@@ -5,9 +5,10 @@
- tagfeed
words:
2026-08-23: 2728
+ 2026-08-25: 2811
---
-# 🏷 #BUG(实时聚合页)
+# 🏷 #bug(实时聚合页)
> 本页由 DataviewJS 实时计算,笔记有变动时自动刷新,无需任何后台服务。
@@ -27,7 +28,7 @@
// ============================================================
// ---------------- 配置区(通常只改这里) ----------------
-const targetTag = "BUG"; // 要聚合的标签(不带 #);多个标签用数组:["lyric", "歌词"]
+const targetTag = "bug"; // 要聚合的标签(不带 #);多个标签用数组:["lyric", "歌词"]
const excludedPaths = ["X2.Archived", "00index", "X0.Clippings", "X1.Knomo", "02DS/02copilot", ".agents", ".claude", ".copilot", ".opencode", ".smart-env", ".workbuddy", ".trash"]; // 要排除的文件夹/路径前缀(vault 相对路径)
// 例如:["00index", "模板", "X1.Knomo"]
// 补充说明:
diff --git a/00index/0inbox/China.md b/00index/0inbox/China.md
deleted file mode 100644
index b343b89..0000000
--- a/00index/0inbox/China.md
+++ /dev/null
@@ -1,437 +0,0 @@
----
-tags:
- - tagfeed-live
-cssclasses:
- - tagfeed
-words:
- 2026-08-23: 2728
----
-
-# 🏷 #China(实时聚合页)
-
-> 本页由 DataviewJS 实时计算,笔记有变动时自动刷新,无需任何后台服务。
-
-```dataviewjs
-// ============================================================
-// tagfeed-live:Logseq 风格标签实时聚合(标准 DataviewJS 脚本)
-//
-// 用法:
-// 1. 新建一个笔记(例如 00index/标签聚合/标签名.md)
-// 2. 在笔记里插入一个 dataviewjs 代码块(三个反引号 + dataviewjs),
-// 把本脚本全部粘贴进去
-// 3. 只需要改下面「配置区」的两行:
-// - targetTag 换成你要聚合的标签(不带 #,中英文都可以;多个标签写成数组 ["a", "b"])
-// - excludedPaths 换成你想排除的文件夹
-// 4. 完成。以后任何笔记里出现/删除这个标签,本页自动刷新,
-// 不需要跑任何命令(Dataview 会在文件变动后自动重新渲染)。
-// ============================================================
-
-// ---------------- 配置区(通常只改这里) ----------------
-const targetTag = "China"; // 要聚合的标签(不带 #);多个标签用数组:["lyric", "歌词"]
-const excludedPaths = ["X2.Archived", "00index", "X0.Clippings", "X1.Knomo", "02DS/02copilot", ".agents", ".claude", ".copilot", ".opencode", ".smart-env", ".workbuddy", ".trash"]; // 要排除的文件夹/路径前缀(vault 相对路径)
- // 例如:["00index", "模板", "X1.Knomo"]
-// 补充说明:
-// - 所有隐藏文件/文件夹(. 开头:.agents/.claude/.copilot/.opencode/.smart-env/.workbuddy/.trash 等)与 X.Attachment 始终跳过
-// - tagfeed 系统页会自动跳过:静态聚合页(文件头含 <!-- tagfeed 标记)
-// 和实时页(frontmatter 带 tagfeed / tagfeed-live 标签),
-// 无需手动排除 00index
-// - 本脚本所在的当前笔记也会自动跳过
-// ---------------------------------------------------------
-
-try {
-const SKIP_DIRS = [".obsidian", ".git", ".trash", "X.Attachment"];
-const MARKER = "<!-- tagfeed";
-const WEEKDAYS = ["周一", "周二", "周三", "周四", "周五", "周六", "周日"];
-const PREV_CHARS = 500; // 预览字数上限:≤上限全文展示;>上限截断并附「查看全文」链接
-
-const esc = s => String(s).replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
-const TAG_LIST = Array.isArray(targetTag) ? targetTag : [targetTag];
-const TAG_RE = new RegExp("(?<![\\w\\u4e00-\\u9fff#])#(?:" + TAG_LIST.map(esc).join("|") + ")(?![\\w\\u4e00-\\u9fff/\\-])");
-const ANY_TAG = /(?<![\w#])#([\w\u4e00-\u9fff][\w\u4e00-\u9fff/\-]*)/g;
-const LIST_RE = /^(\s*)([-*+]|\d+[.)])(?:\s+(.*))?$/;
-const HEAD_RE = /^(#{1,6})\s+(.*)$/;
-const DATEH_RE = /\[\[(\d{4})-(\d{2})-(\d{2})\]\]/;
-const TIME_RE = /(\d{1,2}):(\d{2})(?::(\d{2}))?/;
-const FENCE_RE = /^\s*(`{3,}|~~~)/;
-const indentOf = ln => { // tab 按 2 空格计(与 Python 版一致)
- const expanded = ln.replace(/\t/g, " ");
- return expanded.length - expanded.replace(/^ +/, "").length;
-};
-
-function cleanMatch(ln) {
- let s = ln.replace(/`[^`\n]*`/g, "");
- s = s.replace(/%%[\s\S]*?%%/g, "");
- s = s.replace(/<!--[\s\S]*?-->/g, "");
- return s;
-}
-function vdate(y, m, d) {
- const dt = new Date(y, m - 1, d);
- return (dt.getFullYear() === y && dt.getMonth() === m - 1 && dt.getDate() === d) ? dt : null;
-}
-function dateFromName(name) { // 文件名含 YYYY-MM-DD,或日记命名 26.0805 -> 2026-08-05
- const m1 = name.match(/(\d{4})-(\d{2})-(\d{2})/);
- if (m1) {
- const d1 = vdate(+m1[1], +m1[2], +m1[3]);
- if (d1) return d1;
- }
- const m = name.match(/^(\d{2})\.(\d{2})(\d{2})(?:\D|$)/);
- return m ? vdate(2000 + +m[1], +m[2], +m[3]) : null;
-}
-function parseFrontmatter(text) {
- const lines = text.split("\n");
- if (!lines.length || lines[0].trim() !== "---") return { tags: new Set(), date: null, bodyStart: 0 };
- const n = Math.min(lines.length, 200);
- for (let i = 1; i < n; i++) {
- const t = lines[i].trim();
- if (t === "---" || t === "...") {
- const block = lines.slice(1, i).join("\n");
- const tags = new Set();
- const im = block.match(/^tags:\s*\[(.*?)\]/m);
- if (im) {
- for (const x0 of im[1].split(",")) {
- const x = x0.trim().replace(/^["']|["']$/g, "").replace(/^#/, "").trim();
- if (x) tags.add(x);
- }
- } else {
- const bm = block.match(/^tags:\s*\n((?:[ \t]+-[ \t]+.*\n?)+)/m);
- if (bm) for (const ln of bm[1].split("\n")) {
- const m2 = ln.match(/^\s*-\s*["']?([\w\u4e00-\u9fff/\-]+)["']?\s*$/);
- if (m2) tags.add(m2[1]);
- }
- }
- let fdate = null;
- for (const key of ["date", "created", "published"]) {
- const dm = block.match(new RegExp("^" + key + ":\\s*[\"']?(\\d{4})-(\\d{2})-(\\d{2})", "m"));
- if (dm) { fdate = vdate(+dm[1], +dm[2], +dm[3]); break; }
- }
- return { tags, date: fdate, bodyStart: i + 1 };
- }
- }
- return { tags: new Set(), date: null, bodyStart: 0 };
-}
-const stripTag = s => String(s).replace(TAG_RE, "").replace(/[ \t]{2,}/g, " ").trim();
-
-function scanText(text, note, relpath, fileDate, mtimeDate) {
- const fm = parseFrontmatter(text);
- if (fm.tags.has("tagfeed") || fm.tags.has("tagfeed-live")) return []; // 跳过 tagfeed 系统页(静态聚合页/实时页)
- const lines = text.split("\n");
- const entries = [], seen = new Set();
- let order = 0, inFence = false, inPct = false, inHtml = false;
- let curHead = null, curHeadRaw = null, curDateHead = null;
-
- const entryDate = () => curDateHead || fileDate || fm.date || mtimeDate;
- const anchorOf = () => (curDateHead ? null : curHeadRaw);
- const breadcrumb = () => {
- if (curHead && !DATEH_RE.test(curHead)) {
- const t = curHead.replace(ANY_TAG, "").replace(/\s+/g, " ").trim();
- if (t) return t;
- }
- return null;
- };
- function addBlock(keyIdx, block) {
- const key = relpath + "|" + keyIdx;
- if (seen.has(key) || !block.length) return;
- seen.add(key);
- const d = entryDate();
- if (!d) return;
- let t = null;
- const tm = block[0][1].slice(0, 30).match(TIME_RE);
- if (tm) t = [+tm[1], +tm[2], +(tm[3] || 0)];
- order++;
- entries.push({ date: d, time: t, order, relpath, note,
- breadcrumb: breadcrumb(), lines: block,
- pageLevel: false, notes: [note], anchor: anchorOf() });
- }
- function collectChildren(i, parentIndent) {
- const kids = [], raw = [];
- let j = i + 1;
- while (j < lines.length) {
- const ln = lines[j];
- if (ln.trim() === "") {
- let k = j + 1;
- while (k < lines.length && lines[k].trim() === "") k++;
- if (k < lines.length && LIST_RE.test(lines[k]) && indentOf(lines[k]) > parentIndent) { j = k; continue; }
- break;
- }
- const mi = ln.match(LIST_RE);
- if (mi && indentOf(ln) > parentIndent) {
- raw.push([indentOf(ln), (mi[3] || "").replace(/\s+$/, "")]);
- j++;
- } else break;
- }
- for (const [ind, txt] of raw) kids.push([Math.floor((ind - parentIndent) / 2), txt]);
- return kids;
- }
-
- for (let i = fm.bodyStart; i < lines.length; i++) {
- const ln = lines[i];
- if (FENCE_RE.test(ln)) { inFence = !inFence; continue; }
- if (inFence) continue;
- if (inPct) { if (ln.includes("%%")) inPct = false; continue; }
- if (inHtml) { if (ln.includes("-->")) inHtml = false; continue; }
- const stripped = ln.trim();
- if (stripped.startsWith("%%") && !stripped.slice(2).includes("%%")) { inPct = true; continue; }
- if (stripped.startsWith("<!--") && !stripped.includes("-->")) { inHtml = true; continue; }
-
- const hm = ln.match(HEAD_RE);
- if (hm) {
- const htext = hm[2];
- const dh = htext.match(DATEH_RE);
- if (dh) { curDateHead = vdate(+dh[1], +dh[2], +dh[3]); curHead = null; curHeadRaw = null; }
- else { curHead = htext; curHeadRaw = htext.trim(); }
- if (TAG_RE.test(cleanMatch(htext))) {
- const sec = [[0, "**" + stripTag(htext) + "**"]];
- let j = i + 1;
- const level = hm[1].length;
- let count = 0;
- while (j < lines.length && count < 300) {
- const sl = lines[j];
- const shm = sl.match(HEAD_RE);
- if (shm && shm[1].length <= level) break;
- if (FENCE_RE.test(sl)) break;
- if (sl.trim()) {
- const smi = sl.match(LIST_RE);
- if (smi) sec.push([1 + Math.floor(indentOf(sl) / 2), (smi[3] || "").replace(/\s+$/, "")]);
- else sec.push([1, sl.trim()]);
- count++;
- }
- j++;
- }
- addBlock(i, sec);
- }
- continue;
- }
-
- const clean = cleanMatch(ln);
- if (!TAG_RE.test(clean)) continue;
-
- const mi = ln.match(LIST_RE);
- if (mi) {
- const ind = indentOf(ln);
- const itemText = mi[3] || "";
- const cleaned = stripTag(itemText);
- const kids = collectChildren(i, ind);
- addBlock(i, cleaned ? [[0, cleaned], ...kids] : [[0, itemText.trim()], ...kids]);
- continue;
- }
-
- // 独立标签行:向下吸收 list items,或向上归属到紧邻的块
- if (ln.replace(ANY_TAG, "").trim() === "" || ln.replace(ANY_TAG, "").trim() === "%%") {
- let sec = [];
- let j = i + 1;
- while (j < lines.length) {
- const mj = lines[j].match(LIST_RE);
- if (mj) { sec.push([indentOf(lines[j]), (mj[3] || "").replace(/\s+$/, "")]); j++; }
- else break;
- }
- let keyIdx = i;
- if (!sec.length) {
- const up = [];
- let k = i - 1, bulletIdx = null;
- while (k >= fm.bodyStart) {
- const bk = lines[k];
- if (bk.trim() === "" || HEAD_RE.test(bk) || FENCE_RE.test(bk)) break;
- up.push(k);
- if (LIST_RE.test(bk)) { bulletIdx = k; break; }
- k--;
- }
- if (bulletIdx !== null) {
- const inds = [];
- for (let bl = bulletIdx; bl < i; bl++) {
- const mb = lines[bl].match(LIST_RE);
- if (mb) { inds.push(indentOf(lines[bl])); sec.push([indentOf(lines[bl]), (mb[3] || "").replace(/\s+$/, "")]); }
- else sec.push([(inds.length ? inds[inds.length - 1] + 2 : 0), lines[bl].trim()]);
- }
- keyIdx = bulletIdx;
- } else if (up.length) {
- sec = up.slice().reverse().map(k2 => [0, lines[k2].trim()]);
- } else {
- // 兜底:标签行紧挨在某个标题下方(中间只有空行)→ 捕获该标题下的整个小节
- let h = i - 1;
- while (h >= fm.bodyStart && lines[h].trim() === "") h--;
- const hmk = h >= fm.bodyStart ? lines[h].match(HEAD_RE) : null;
- if (hmk) {
- const level = hmk[1].length;
- const title = hmk[2].replace(ANY_TAG, "").replace(/\s+/g, " ").trim();
- const sec2 = title ? [[0, "**" + title + "**"]] : [];
- let j2 = i + 1, count2 = 0;
- while (j2 < lines.length && count2 < 300) {
- const sl = lines[j2];
- const shm = sl.match(HEAD_RE);
- if (shm && shm[1].length <= level) break;
- if (FENCE_RE.test(sl)) break;
- if (sl.trim()) {
- const smi = sl.match(LIST_RE);
- if (smi) sec2.push([1 + Math.floor(indentOf(sl) / 2), (smi[3] || "").replace(/\s+$/, "")]);
- else sec2.push([1, sl.trim().replace(/^>\s*/, "")]);
- count2++;
- }
- j2++;
- }
- if (sec2.length) sec = sec2;
- }
- }
- }
- if (sec.length) {
- const mi0 = Math.min(...sec.map(x => x[0]));
- addBlock(keyIdx, sec.map(([ind, t]) => [Math.floor((ind - mi0) / 2), t]));
- }
- continue;
- }
-
- // 普通段落带标签:吸收到空行为止
- const para = [stripTag(clean)];
- let j = i + 1;
- while (j < lines.length) {
- const nl = lines[j];
- if (nl.trim() === "" || HEAD_RE.test(nl) || LIST_RE.test(nl) || FENCE_RE.test(nl)) break;
- para.push(nl.trim());
- j++;
- }
- addBlock(i, [[0, para.filter(p => p).join(" ")]]);
- }
-
- // frontmatter 标签 → 页面级条目(附正文开头预览)
- if (TAG_LIST.some(t => fm.tags.has(t))) {
- const d = fm.date || fileDate || mtimeDate;
- if (d) {
- order++;
- // 收集正文文本至 PREV_CHARS:超过上限的长文在渲染时截断并附「查看全文」链接
- const prev = [];
- let prevTotal = 0, prevCount = 0;
- for (let k = fm.bodyStart; k < lines.length && prevCount < 200; k++) {
- const s = lines[k].trim();
- if (!s || s.startsWith("#") || s === "---") continue;
- const clean = s.replace(ANY_TAG, "");
- if (!clean) continue;
- prev.push(clean);
- prevTotal += clean.length;
- prevCount++;
- if (prevTotal > PREV_CHARS) break; // 超过上限:已足够判断,停止收集
- }
- const preview = prev.join(" ");
- entries.push({ date: d, time: null, order, relpath, note, breadcrumb: null,
- lines: preview ? [[0, preview]] : [],
- pageLevel: true, notes: [note], anchor: null });
- }
- }
- return entries;
-}
-
-// ---------------- 扫描整个 vault ----------------
-const currentPath = (dv.current() && dv.current().file) ? dv.current().file.path : null;
-let all = [];
-for (const f of app.vault.getMarkdownFiles()) {
- if (f.path === currentPath || f.path.endsWith(".excalidraw.md")) continue;
- if (SKIP_DIRS.some(d => f.path.startsWith(d + "/"))) continue;
- if (f.path.split("/").some(seg => seg.startsWith("."))) continue; // 排除所有隐藏文件/文件夹(.开头)
- if (excludedPaths.some(p => f.path === p || f.path.startsWith(p.endsWith("/") ? p : p + "/"))) continue;
- let text;
- try { text = await app.vault.cachedRead(f); } catch (e) { continue; }
- if (text.slice(0, 1000).includes(MARKER)) continue; // 跳过 tagfeed 生成的静态页
- all = all.concat(scanText(text, f.basename, f.path, dateFromName(f.basename), new Date(f.stat.mtime)));
-}
-
-// ---------------- 去重:同日期+同内容合并,来源并排 ----------------
-const timeCmp = (a, b) => (a[0] - b[0]) || (a[1] - b[1]) || (a[2] - b[2]);
-const mergedMap = new Map();
-for (const e of all) {
- const fp = e.date.getTime() + "|" + e.lines.map(x => x[0] + ":" + x[1]).join("\n");
- const m = mergedMap.get(fp);
- if (m) {
- if (!m.notes.includes(e.note)) m.notes.push(e.note);
- if (e.time && (!m.time || timeCmp(e.time, m.time) < 0)) m.time = e.time;
- // 若先扫到的来源没有面包屑/锚点,而后面的有,则采用后者(与扫描顺序无关,结果确定)
- if (!m.breadcrumb && e.breadcrumb) {
- m.breadcrumb = e.breadcrumb;
- m.anchor = e.anchor;
- m.relpath = e.relpath;
- m.note = e.note;
- }
- } else mergedMap.set(fp, e);
-}
-const entries = [...mergedMap.values()];
-
-// ---------------- 渲染:按日期倒序 ----------------
-const byDate = new Map();
-for (const e of entries) {
- const k = e.date.getTime();
- if (!byDate.has(k)) byDate.set(k, []);
- byDate.get(k).push(e);
-}
-const dates = [...byDate.keys()].sort((a, b) => b - a);
-const noteSet = new Set();
-for (const e of entries) for (const n of e.notes) noteSet.add(n);
-
-const out = [];
-out.push("> **" + entries.length + "** 个内容块 | **" + noteSet.size + "** 篇笔记 | 按日期倒序 | 随笔记变动自动刷新");
-
-const pad2 = x => String(x).padStart(2, "0");
-const fmtDate = d => d.getFullYear() + "-" + pad2(d.getMonth() + 1) + "-" + pad2(d.getDate());
-const invTime = t => t.map(x => pad2(99 - x)).join("");
-
-for (const dk of dates) {
- const d = new Date(dk);
- out.push("## 📅 " + fmtDate(d) + " · " + WEEKDAYS[d.getDay() === 0 ? 6 : d.getDay() - 1]);
- out.push("");
- const group = byDate.get(dk);
-
- const sub = new Map(); // (笔记+面包屑) -> entries
- for (const e of group) {
- const key = e.relpath + "|" + (e.breadcrumb || "");
- if (!sub.has(key)) sub.set(key, []);
- sub.get(key).push(e);
- }
- const subKey = ([key, es]) => {
- const times = es.filter(e => e.time).map(e => e.time);
- const best = times.length ? times.reduce((a, b) => (timeCmp(a, b) > 0 ? a : b)) : null;
- return (best ? "0" : "1") + (best ? invTime(best) : "000000") + key;
- };
-
- for (const [key, es] of [...sub.entries()].sort((a, b) => (subKey(a) < subKey(b) ? -1 : subKey(a) > subKey(b) ? 1 : 0))) {
- const noteList = [];
- for (const e of es) for (const n of e.notes) if (!noteList.includes(n)) noteList.push(n);
- noteList.sort();
- const crumb = es.map(e => e.breadcrumb).find(b => b) || null;
- out.push("- 🔗 " + noteList.map(n => "[[" + n + "]]").join(" · ") + (crumb ? " · " + crumb : ""));
- const sorted = es.slice().sort((a, b) => {
- const ka = (a.time ? "0" : "1") + (a.time ? invTime(a.time) : "") + String(a.order).padStart(6, "0");
- const kb = (b.time ? "0" : "1") + (b.time ? invTime(b.time) : "") + String(b.order).padStart(6, "0");
- return ka < kb ? -1 : ka > kb ? 1 : 0;
- });
- for (const e of sorted) {
- let bl = e.lines, truncated = false;
- const totalChars = bl.reduce((s, x) => s + x[1].length, 0);
- if (totalChars > PREV_CHARS) { // 只有超过上限才截断;上限内全文展示
- truncated = true;
- const shown = [];
- let chars = 0;
- for (const [lvl, txt] of bl) {
- if (chars + txt.length > PREV_CHARS) {
- if (PREV_CHARS - chars > 0) shown.push([lvl, txt.slice(0, PREV_CHARS - chars) + "…"]);
- break;
- }
- shown.push([lvl, txt]);
- chars += txt.length;
- }
- bl = shown;
- }
- for (const [lvl, txt] of bl) out.push("\t".repeat(lvl + 1) + "- " + txt);
- if (truncated) {
- const target = e.note + (e.anchor ? "#" + e.anchor : "");
- const label = e.pageLevel ? "页面级标签 · 查看全文" : "查看全文";
- out.push("\t- [[" + target + "|↗ " + label + "]]");
- }
- }
- out.push("");
- }
- out.push("");
-}
-dv.paragraph(out.join("\n"));
-
-} catch (err) {
- dv.paragraph("> ❌ tagfeed-live 执行失败:" + err.message);
- console.error("tagfeed-live:", err);
-}
-
-```
diff --git a/00index/0inbox/Convert.md b/00index/0inbox/Convert.md
deleted file mode 100644
index 7411def..0000000
--- a/00index/0inbox/Convert.md
+++ /dev/null
@@ -1,437 +0,0 @@
----
-tags:
- - tagfeed-live
-cssclasses:
- - tagfeed
-words:
- 2026-08-23: 2728
----
-
-# 🏷 #Convert(实时聚合页)
-
-> 本页由 DataviewJS 实时计算,笔记有变动时自动刷新,无需任何后台服务。
-
-```dataviewjs
-// ============================================================
-// tagfeed-live:Logseq 风格标签实时聚合(标准 DataviewJS 脚本)
-//
-// 用法:
-// 1. 新建一个笔记(例如 00index/标签聚合/标签名.md)
-// 2. 在笔记里插入一个 dataviewjs 代码块(三个反引号 + dataviewjs),
-// 把本脚本全部粘贴进去
-// 3. 只需要改下面「配置区」的两行:
-// - targetTag 换成你要聚合的标签(不带 #,中英文都可以;多个标签写成数组 ["a", "b"])
-// - excludedPaths 换成你想排除的文件夹
-// 4. 完成。以后任何笔记里出现/删除这个标签,本页自动刷新,
-// 不需要跑任何命令(Dataview 会在文件变动后自动重新渲染)。
-// ============================================================
-
-// ---------------- 配置区(通常只改这里) ----------------
-const targetTag = "Convert"; // 要聚合的标签(不带 #);多个标签用数组:["lyric", "歌词"]
-const excludedPaths = ["X2.Archived", "00index", "X0.Clippings", "X1.Knomo", "02DS/02copilot", ".agents", ".claude", ".copilot", ".opencode", ".smart-env", ".workbuddy", ".trash"]; // 要排除的文件夹/路径前缀(vault 相对路径)
- // 例如:["00index", "模板", "X1.Knomo"]
-// 补充说明:
-// - 所有隐藏文件/文件夹(. 开头:.agents/.claude/.copilot/.opencode/.smart-env/.workbuddy/.trash 等)与 X.Attachment 始终跳过
-// - tagfeed 系统页会自动跳过:静态聚合页(文件头含 <!-- tagfeed 标记)
-// 和实时页(frontmatter 带 tagfeed / tagfeed-live 标签),
-// 无需手动排除 00index
-// - 本脚本所在的当前笔记也会自动跳过
-// ---------------------------------------------------------
-
-try {
-const SKIP_DIRS = [".obsidian", ".git", ".trash", "X.Attachment"];
-const MARKER = "<!-- tagfeed";
-const WEEKDAYS = ["周一", "周二", "周三", "周四", "周五", "周六", "周日"];
-const PREV_CHARS = 500; // 预览字数上限:≤上限全文展示;>上限截断并附「查看全文」链接
-
-const esc = s => String(s).replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
-const TAG_LIST = Array.isArray(targetTag) ? targetTag : [targetTag];
-const TAG_RE = new RegExp("(?<![\\w\\u4e00-\\u9fff#])#(?:" + TAG_LIST.map(esc).join("|") + ")(?![\\w\\u4e00-\\u9fff/\\-])");
-const ANY_TAG = /(?<![\w#])#([\w\u4e00-\u9fff][\w\u4e00-\u9fff/\-]*)/g;
-const LIST_RE = /^(\s*)([-*+]|\d+[.)])(?:\s+(.*))?$/;
-const HEAD_RE = /^(#{1,6})\s+(.*)$/;
-const DATEH_RE = /\[\[(\d{4})-(\d{2})-(\d{2})\]\]/;
-const TIME_RE = /(\d{1,2}):(\d{2})(?::(\d{2}))?/;
-const FENCE_RE = /^\s*(`{3,}|~~~)/;
-const indentOf = ln => { // tab 按 2 空格计(与 Python 版一致)
- const expanded = ln.replace(/\t/g, " ");
- return expanded.length - expanded.replace(/^ +/, "").length;
-};
-
-function cleanMatch(ln) {
- let s = ln.replace(/`[^`\n]*`/g, "");
- s = s.replace(/%%[\s\S]*?%%/g, "");
- s = s.replace(/<!--[\s\S]*?-->/g, "");
- return s;
-}
-function vdate(y, m, d) {
- const dt = new Date(y, m - 1, d);
- return (dt.getFullYear() === y && dt.getMonth() === m - 1 && dt.getDate() === d) ? dt : null;
-}
-function dateFromName(name) { // 文件名含 YYYY-MM-DD,或日记命名 26.0805 -> 2026-08-05
- const m1 = name.match(/(\d{4})-(\d{2})-(\d{2})/);
- if (m1) {
- const d1 = vdate(+m1[1], +m1[2], +m1[3]);
- if (d1) return d1;
- }
- const m = name.match(/^(\d{2})\.(\d{2})(\d{2})(?:\D|$)/);
- return m ? vdate(2000 + +m[1], +m[2], +m[3]) : null;
-}
-function parseFrontmatter(text) {
- const lines = text.split("\n");
- if (!lines.length || lines[0].trim() !== "---") return { tags: new Set(), date: null, bodyStart: 0 };
- const n = Math.min(lines.length, 200);
- for (let i = 1; i < n; i++) {
- const t = lines[i].trim();
- if (t === "---" || t === "...") {
- const block = lines.slice(1, i).join("\n");
- const tags = new Set();
- const im = block.match(/^tags:\s*\[(.*?)\]/m);
- if (im) {
- for (const x0 of im[1].split(",")) {
- const x = x0.trim().replace(/^["']|["']$/g, "").replace(/^#/, "").trim();
- if (x) tags.add(x);
- }
- } else {
- const bm = block.match(/^tags:\s*\n((?:[ \t]+-[ \t]+.*\n?)+)/m);
- if (bm) for (const ln of bm[1].split("\n")) {
- const m2 = ln.match(/^\s*-\s*["']?([\w\u4e00-\u9fff/\-]+)["']?\s*$/);
- if (m2) tags.add(m2[1]);
- }
- }
- let fdate = null;
- for (const key of ["date", "created", "published"]) {
- const dm = block.match(new RegExp("^" + key + ":\\s*[\"']?(\\d{4})-(\\d{2})-(\\d{2})", "m"));
- if (dm) { fdate = vdate(+dm[1], +dm[2], +dm[3]); break; }
- }
- return { tags, date: fdate, bodyStart: i + 1 };
- }
- }
- return { tags: new Set(), date: null, bodyStart: 0 };
-}
-const stripTag = s => String(s).replace(TAG_RE, "").replace(/[ \t]{2,}/g, " ").trim();
-
-function scanText(text, note, relpath, fileDate, mtimeDate) {
- const fm = parseFrontmatter(text);
- if (fm.tags.has("tagfeed") || fm.tags.has("tagfeed-live")) return []; // 跳过 tagfeed 系统页(静态聚合页/实时页)
- const lines = text.split("\n");
- const entries = [], seen = new Set();
- let order = 0, inFence = false, inPct = false, inHtml = false;
- let curHead = null, curHeadRaw = null, curDateHead = null;
-
- const entryDate = () => curDateHead || fileDate || fm.date || mtimeDate;
- const anchorOf = () => (curDateHead ? null : curHeadRaw);
- const breadcrumb = () => {
- if (curHead && !DATEH_RE.test(curHead)) {
- const t = curHead.replace(ANY_TAG, "").replace(/\s+/g, " ").trim();
- if (t) return t;
- }
- return null;
- };
- function addBlock(keyIdx, block) {
- const key = relpath + "|" + keyIdx;
- if (seen.has(key) || !block.length) return;
- seen.add(key);
- const d = entryDate();
- if (!d) return;
- let t = null;
- const tm = block[0][1].slice(0, 30).match(TIME_RE);
- if (tm) t = [+tm[1], +tm[2], +(tm[3] || 0)];
- order++;
- entries.push({ date: d, time: t, order, relpath, note,
- breadcrumb: breadcrumb(), lines: block,
- pageLevel: false, notes: [note], anchor: anchorOf() });
- }
- function collectChildren(i, parentIndent) {
- const kids = [], raw = [];
- let j = i + 1;
- while (j < lines.length) {
- const ln = lines[j];
- if (ln.trim() === "") {
- let k = j + 1;
- while (k < lines.length && lines[k].trim() === "") k++;
- if (k < lines.length && LIST_RE.test(lines[k]) && indentOf(lines[k]) > parentIndent) { j = k; continue; }
- break;
- }
- const mi = ln.match(LIST_RE);
- if (mi && indentOf(ln) > parentIndent) {
- raw.push([indentOf(ln), (mi[3] || "").replace(/\s+$/, "")]);
- j++;
- } else break;
- }
- for (const [ind, txt] of raw) kids.push([Math.floor((ind - parentIndent) / 2), txt]);
- return kids;
- }
-
- for (let i = fm.bodyStart; i < lines.length; i++) {
- const ln = lines[i];
- if (FENCE_RE.test(ln)) { inFence = !inFence; continue; }
- if (inFence) continue;
- if (inPct) { if (ln.includes("%%")) inPct = false; continue; }
- if (inHtml) { if (ln.includes("-->")) inHtml = false; continue; }
- const stripped = ln.trim();
- if (stripped.startsWith("%%") && !stripped.slice(2).includes("%%")) { inPct = true; continue; }
- if (stripped.startsWith("<!--") && !stripped.includes("-->")) { inHtml = true; continue; }
-
- const hm = ln.match(HEAD_RE);
- if (hm) {
- const htext = hm[2];
- const dh = htext.match(DATEH_RE);
- if (dh) { curDateHead = vdate(+dh[1], +dh[2], +dh[3]); curHead = null; curHeadRaw = null; }
- else { curHead = htext; curHeadRaw = htext.trim(); }
- if (TAG_RE.test(cleanMatch(htext))) {
- const sec = [[0, "**" + stripTag(htext) + "**"]];
- let j = i + 1;
- const level = hm[1].length;
- let count = 0;
- while (j < lines.length && count < 300) {
- const sl = lines[j];
- const shm = sl.match(HEAD_RE);
- if (shm && shm[1].length <= level) break;
- if (FENCE_RE.test(sl)) break;
- if (sl.trim()) {
- const smi = sl.match(LIST_RE);
- if (smi) sec.push([1 + Math.floor(indentOf(sl) / 2), (smi[3] || "").replace(/\s+$/, "")]);
- else sec.push([1, sl.trim()]);
- count++;
- }
- j++;
- }
- addBlock(i, sec);
- }
- continue;
- }
-
- const clean = cleanMatch(ln);
- if (!TAG_RE.test(clean)) continue;
-
- const mi = ln.match(LIST_RE);
- if (mi) {
- const ind = indentOf(ln);
- const itemText = mi[3] || "";
- const cleaned = stripTag(itemText);
- const kids = collectChildren(i, ind);
- addBlock(i, cleaned ? [[0, cleaned], ...kids] : [[0, itemText.trim()], ...kids]);
- continue;
- }
-
- // 独立标签行:向下吸收 list items,或向上归属到紧邻的块
- if (ln.replace(ANY_TAG, "").trim() === "" || ln.replace(ANY_TAG, "").trim() === "%%") {
- let sec = [];
- let j = i + 1;
- while (j < lines.length) {
- const mj = lines[j].match(LIST_RE);
- if (mj) { sec.push([indentOf(lines[j]), (mj[3] || "").replace(/\s+$/, "")]); j++; }
- else break;
- }
- let keyIdx = i;
- if (!sec.length) {
- const up = [];
- let k = i - 1, bulletIdx = null;
- while (k >= fm.bodyStart) {
- const bk = lines[k];
- if (bk.trim() === "" || HEAD_RE.test(bk) || FENCE_RE.test(bk)) break;
- up.push(k);
- if (LIST_RE.test(bk)) { bulletIdx = k; break; }
- k--;
- }
- if (bulletIdx !== null) {
- const inds = [];
- for (let bl = bulletIdx; bl < i; bl++) {
- const mb = lines[bl].match(LIST_RE);
- if (mb) { inds.push(indentOf(lines[bl])); sec.push([indentOf(lines[bl]), (mb[3] || "").replace(/\s+$/, "")]); }
- else sec.push([(inds.length ? inds[inds.length - 1] + 2 : 0), lines[bl].trim()]);
- }
- keyIdx = bulletIdx;
- } else if (up.length) {
- sec = up.slice().reverse().map(k2 => [0, lines[k2].trim()]);
- } else {
- // 兜底:标签行紧挨在某个标题下方(中间只有空行)→ 捕获该标题下的整个小节
- let h = i - 1;
- while (h >= fm.bodyStart && lines[h].trim() === "") h--;
- const hmk = h >= fm.bodyStart ? lines[h].match(HEAD_RE) : null;
- if (hmk) {
- const level = hmk[1].length;
- const title = hmk[2].replace(ANY_TAG, "").replace(/\s+/g, " ").trim();
- const sec2 = title ? [[0, "**" + title + "**"]] : [];
- let j2 = i + 1, count2 = 0;
- while (j2 < lines.length && count2 < 300) {
- const sl = lines[j2];
- const shm = sl.match(HEAD_RE);
- if (shm && shm[1].length <= level) break;
- if (FENCE_RE.test(sl)) break;
- if (sl.trim()) {
- const smi = sl.match(LIST_RE);
- if (smi) sec2.push([1 + Math.floor(indentOf(sl) / 2), (smi[3] || "").replace(/\s+$/, "")]);
- else sec2.push([1, sl.trim().replace(/^>\s*/, "")]);
- count2++;
- }
- j2++;
- }
- if (sec2.length) sec = sec2;
- }
- }
- }
- if (sec.length) {
- const mi0 = Math.min(...sec.map(x => x[0]));
- addBlock(keyIdx, sec.map(([ind, t]) => [Math.floor((ind - mi0) / 2), t]));
- }
- continue;
- }
-
- // 普通段落带标签:吸收到空行为止
- const para = [stripTag(clean)];
- let j = i + 1;
- while (j < lines.length) {
- const nl = lines[j];
- if (nl.trim() === "" || HEAD_RE.test(nl) || LIST_RE.test(nl) || FENCE_RE.test(nl)) break;
- para.push(nl.trim());
- j++;
- }
- addBlock(i, [[0, para.filter(p => p).join(" ")]]);
- }
-
- // frontmatter 标签 → 页面级条目(附正文开头预览)
- if (TAG_LIST.some(t => fm.tags.has(t))) {
- const d = fm.date || fileDate || mtimeDate;
- if (d) {
- order++;
- // 收集正文文本至 PREV_CHARS:超过上限的长文在渲染时截断并附「查看全文」链接
- const prev = [];
- let prevTotal = 0, prevCount = 0;
- for (let k = fm.bodyStart; k < lines.length && prevCount < 200; k++) {
- const s = lines[k].trim();
- if (!s || s.startsWith("#") || s === "---") continue;
- const clean = s.replace(ANY_TAG, "");
- if (!clean) continue;
- prev.push(clean);
- prevTotal += clean.length;
- prevCount++;
- if (prevTotal > PREV_CHARS) break; // 超过上限:已足够判断,停止收集
- }
- const preview = prev.join(" ");
- entries.push({ date: d, time: null, order, relpath, note, breadcrumb: null,
- lines: preview ? [[0, preview]] : [],
- pageLevel: true, notes: [note], anchor: null });
- }
- }
- return entries;
-}
-
-// ---------------- 扫描整个 vault ----------------
-const currentPath = (dv.current() && dv.current().file) ? dv.current().file.path : null;
-let all = [];
-for (const f of app.vault.getMarkdownFiles()) {
- if (f.path === currentPath || f.path.endsWith(".excalidraw.md")) continue;
- if (SKIP_DIRS.some(d => f.path.startsWith(d + "/"))) continue;
- if (f.path.split("/").some(seg => seg.startsWith("."))) continue; // 排除所有隐藏文件/文件夹(.开头)
- if (excludedPaths.some(p => f.path === p || f.path.startsWith(p.endsWith("/") ? p : p + "/"))) continue;
- let text;
- try { text = await app.vault.cachedRead(f); } catch (e) { continue; }
- if (text.slice(0, 1000).includes(MARKER)) continue; // 跳过 tagfeed 生成的静态页
- all = all.concat(scanText(text, f.basename, f.path, dateFromName(f.basename), new Date(f.stat.mtime)));
-}
-
-// ---------------- 去重:同日期+同内容合并,来源并排 ----------------
-const timeCmp = (a, b) => (a[0] - b[0]) || (a[1] - b[1]) || (a[2] - b[2]);
-const mergedMap = new Map();
-for (const e of all) {
- const fp = e.date.getTime() + "|" + e.lines.map(x => x[0] + ":" + x[1]).join("\n");
- const m = mergedMap.get(fp);
- if (m) {
- if (!m.notes.includes(e.note)) m.notes.push(e.note);
- if (e.time && (!m.time || timeCmp(e.time, m.time) < 0)) m.time = e.time;
- // 若先扫到的来源没有面包屑/锚点,而后面的有,则采用后者(与扫描顺序无关,结果确定)
- if (!m.breadcrumb && e.breadcrumb) {
- m.breadcrumb = e.breadcrumb;
- m.anchor = e.anchor;
- m.relpath = e.relpath;
- m.note = e.note;
- }
- } else mergedMap.set(fp, e);
-}
-const entries = [...mergedMap.values()];
-
-// ---------------- 渲染:按日期倒序 ----------------
-const byDate = new Map();
-for (const e of entries) {
- const k = e.date.getTime();
- if (!byDate.has(k)) byDate.set(k, []);
- byDate.get(k).push(e);
-}
-const dates = [...byDate.keys()].sort((a, b) => b - a);
-const noteSet = new Set();
-for (const e of entries) for (const n of e.notes) noteSet.add(n);
-
-const out = [];
-out.push("> **" + entries.length + "** 个内容块 | **" + noteSet.size + "** 篇笔记 | 按日期倒序 | 随笔记变动自动刷新");
-
-const pad2 = x => String(x).padStart(2, "0");
-const fmtDate = d => d.getFullYear() + "-" + pad2(d.getMonth() + 1) + "-" + pad2(d.getDate());
-const invTime = t => t.map(x => pad2(99 - x)).join("");
-
-for (const dk of dates) {
- const d = new Date(dk);
- out.push("## 📅 " + fmtDate(d) + " · " + WEEKDAYS[d.getDay() === 0 ? 6 : d.getDay() - 1]);
- out.push("");
- const group = byDate.get(dk);
-
- const sub = new Map(); // (笔记+面包屑) -> entries
- for (const e of group) {
- const key = e.relpath + "|" + (e.breadcrumb || "");
- if (!sub.has(key)) sub.set(key, []);
- sub.get(key).push(e);
- }
- const subKey = ([key, es]) => {
- const times = es.filter(e => e.time).map(e => e.time);
- const best = times.length ? times.reduce((a, b) => (timeCmp(a, b) > 0 ? a : b)) : null;
- return (best ? "0" : "1") + (best ? invTime(best) : "000000") + key;
- };
-
- for (const [key, es] of [...sub.entries()].sort((a, b) => (subKey(a) < subKey(b) ? -1 : subKey(a) > subKey(b) ? 1 : 0))) {
- const noteList = [];
- for (const e of es) for (const n of e.notes) if (!noteList.includes(n)) noteList.push(n);
- noteList.sort();
- const crumb = es.map(e => e.breadcrumb).find(b => b) || null;
- out.push("- 🔗 " + noteList.map(n => "[[" + n + "]]").join(" · ") + (crumb ? " · " + crumb : ""));
- const sorted = es.slice().sort((a, b) => {
- const ka = (a.time ? "0" : "1") + (a.time ? invTime(a.time) : "") + String(a.order).padStart(6, "0");
- const kb = (b.time ? "0" : "1") + (b.time ? invTime(b.time) : "") + String(b.order).padStart(6, "0");
- return ka < kb ? -1 : ka > kb ? 1 : 0;
- });
- for (const e of sorted) {
- let bl = e.lines, truncated = false;
- const totalChars = bl.reduce((s, x) => s + x[1].length, 0);
- if (totalChars > PREV_CHARS) { // 只有超过上限才截断;上限内全文展示
- truncated = true;
- const shown = [];
- let chars = 0;
- for (const [lvl, txt] of bl) {
- if (chars + txt.length > PREV_CHARS) {
- if (PREV_CHARS - chars > 0) shown.push([lvl, txt.slice(0, PREV_CHARS - chars) + "…"]);
- break;
- }
- shown.push([lvl, txt]);
- chars += txt.length;
- }
- bl = shown;
- }
- for (const [lvl, txt] of bl) out.push("\t".repeat(lvl + 1) + "- " + txt);
- if (truncated) {
- const target = e.note + (e.anchor ? "#" + e.anchor : "");
- const label = e.pageLevel ? "页面级标签 · 查看全文" : "查看全文";
- out.push("\t- [[" + target + "|↗ " + label + "]]");
- }
- }
- out.push("");
- }
- out.push("");
-}
-dv.paragraph(out.join("\n"));
-
-} catch (err) {
- dv.paragraph("> ❌ tagfeed-live 执行失败:" + err.message);
- console.error("tagfeed-live:", err);
-}
-
-```
diff --git a/00index/0inbox/Determine.md b/00index/0inbox/Determine.md
deleted file mode 100644
index 0e51acb..0000000
--- a/00index/0inbox/Determine.md
+++ /dev/null
@@ -1,437 +0,0 @@
----
-tags:
- - tagfeed-live
-cssclasses:
- - tagfeed
-words:
- 2026-08-23: 2728
----
-
-# 🏷 #Determine(实时聚合页)
-
-> 本页由 DataviewJS 实时计算,笔记有变动时自动刷新,无需任何后台服务。
-
-```dataviewjs
-// ============================================================
-// tagfeed-live:Logseq 风格标签实时聚合(标准 DataviewJS 脚本)
-//
-// 用法:
-// 1. 新建一个笔记(例如 00index/标签聚合/标签名.md)
-// 2. 在笔记里插入一个 dataviewjs 代码块(三个反引号 + dataviewjs),
-// 把本脚本全部粘贴进去
-// 3. 只需要改下面「配置区」的两行:
-// - targetTag 换成你要聚合的标签(不带 #,中英文都可以;多个标签写成数组 ["a", "b"])
-// - excludedPaths 换成你想排除的文件夹
-// 4. 完成。以后任何笔记里出现/删除这个标签,本页自动刷新,
-// 不需要跑任何命令(Dataview 会在文件变动后自动重新渲染)。
-// ============================================================
-
-// ---------------- 配置区(通常只改这里) ----------------
-const targetTag = "Determine"; // 要聚合的标签(不带 #);多个标签用数组:["lyric", "歌词"]
-const excludedPaths = ["X2.Archived", "00index", "X0.Clippings", "X1.Knomo", "02DS/02copilot", ".agents", ".claude", ".copilot", ".opencode", ".smart-env", ".workbuddy", ".trash"]; // 要排除的文件夹/路径前缀(vault 相对路径)
- // 例如:["00index", "模板", "X1.Knomo"]
-// 补充说明:
-// - 所有隐藏文件/文件夹(. 开头:.agents/.claude/.copilot/.opencode/.smart-env/.workbuddy/.trash 等)与 X.Attachment 始终跳过
-// - tagfeed 系统页会自动跳过:静态聚合页(文件头含 <!-- tagfeed 标记)
-// 和实时页(frontmatter 带 tagfeed / tagfeed-live 标签),
-// 无需手动排除 00index
-// - 本脚本所在的当前笔记也会自动跳过
-// ---------------------------------------------------------
-
-try {
-const SKIP_DIRS = [".obsidian", ".git", ".trash", "X.Attachment"];
-const MARKER = "<!-- tagfeed";
-const WEEKDAYS = ["周一", "周二", "周三", "周四", "周五", "周六", "周日"];
-const PREV_CHARS = 500; // 预览字数上限:≤上限全文展示;>上限截断并附「查看全文」链接
-
-const esc = s => String(s).replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
-const TAG_LIST = Array.isArray(targetTag) ? targetTag : [targetTag];
-const TAG_RE = new RegExp("(?<![\\w\\u4e00-\\u9fff#])#(?:" + TAG_LIST.map(esc).join("|") + ")(?![\\w\\u4e00-\\u9fff/\\-])");
-const ANY_TAG = /(?<![\w#])#([\w\u4e00-\u9fff][\w\u4e00-\u9fff/\-]*)/g;
-const LIST_RE = /^(\s*)([-*+]|\d+[.)])(?:\s+(.*))?$/;
-const HEAD_RE = /^(#{1,6})\s+(.*)$/;
-const DATEH_RE = /\[\[(\d{4})-(\d{2})-(\d{2})\]\]/;
-const TIME_RE = /(\d{1,2}):(\d{2})(?::(\d{2}))?/;
-const FENCE_RE = /^\s*(`{3,}|~~~)/;
-const indentOf = ln => { // tab 按 2 空格计(与 Python 版一致)
- const expanded = ln.replace(/\t/g, " ");
- return expanded.length - expanded.replace(/^ +/, "").length;
-};
-
-function cleanMatch(ln) {
- let s = ln.replace(/`[^`\n]*`/g, "");
- s = s.replace(/%%[\s\S]*?%%/g, "");
- s = s.replace(/<!--[\s\S]*?-->/g, "");
- return s;
-}
-function vdate(y, m, d) {
- const dt = new Date(y, m - 1, d);
- return (dt.getFullYear() === y && dt.getMonth() === m - 1 && dt.getDate() === d) ? dt : null;
-}
-function dateFromName(name) { // 文件名含 YYYY-MM-DD,或日记命名 26.0805 -> 2026-08-05
- const m1 = name.match(/(\d{4})-(\d{2})-(\d{2})/);
- if (m1) {
- const d1 = vdate(+m1[1], +m1[2], +m1[3]);
- if (d1) return d1;
- }
- const m = name.match(/^(\d{2})\.(\d{2})(\d{2})(?:\D|$)/);
- return m ? vdate(2000 + +m[1], +m[2], +m[3]) : null;
-}
-function parseFrontmatter(text) {
- const lines = text.split("\n");
- if (!lines.length || lines[0].trim() !== "---") return { tags: new Set(), date: null, bodyStart: 0 };
- const n = Math.min(lines.length, 200);
- for (let i = 1; i < n; i++) {
- const t = lines[i].trim();
- if (t === "---" || t === "...") {
- const block = lines.slice(1, i).join("\n");
- const tags = new Set();
- const im = block.match(/^tags:\s*\[(.*?)\]/m);
- if (im) {
- for (const x0 of im[1].split(",")) {
- const x = x0.trim().replace(/^["']|["']$/g, "").replace(/^#/, "").trim();
- if (x) tags.add(x);
- }
- } else {
- const bm = block.match(/^tags:\s*\n((?:[ \t]+-[ \t]+.*\n?)+)/m);
- if (bm) for (const ln of bm[1].split("\n")) {
- const m2 = ln.match(/^\s*-\s*["']?([\w\u4e00-\u9fff/\-]+)["']?\s*$/);
- if (m2) tags.add(m2[1]);
- }
- }
- let fdate = null;
- for (const key of ["date", "created", "published"]) {
- const dm = block.match(new RegExp("^" + key + ":\\s*[\"']?(\\d{4})-(\\d{2})-(\\d{2})", "m"));
- if (dm) { fdate = vdate(+dm[1], +dm[2], +dm[3]); break; }
- }
- return { tags, date: fdate, bodyStart: i + 1 };
- }
- }
- return { tags: new Set(), date: null, bodyStart: 0 };
-}
-const stripTag = s => String(s).replace(TAG_RE, "").replace(/[ \t]{2,}/g, " ").trim();
-
-function scanText(text, note, relpath, fileDate, mtimeDate) {
- const fm = parseFrontmatter(text);
- if (fm.tags.has("tagfeed") || fm.tags.has("tagfeed-live")) return []; // 跳过 tagfeed 系统页(静态聚合页/实时页)
- const lines = text.split("\n");
- const entries = [], seen = new Set();
- let order = 0, inFence = false, inPct = false, inHtml = false;
- let curHead = null, curHeadRaw = null, curDateHead = null;
-
- const entryDate = () => curDateHead || fileDate || fm.date || mtimeDate;
- const anchorOf = () => (curDateHead ? null : curHeadRaw);
- const breadcrumb = () => {
- if (curHead && !DATEH_RE.test(curHead)) {
- const t = curHead.replace(ANY_TAG, "").replace(/\s+/g, " ").trim();
- if (t) return t;
- }
- return null;
- };
- function addBlock(keyIdx, block) {
- const key = relpath + "|" + keyIdx;
- if (seen.has(key) || !block.length) return;
- seen.add(key);
- const d = entryDate();
- if (!d) return;
- let t = null;
- const tm = block[0][1].slice(0, 30).match(TIME_RE);
- if (tm) t = [+tm[1], +tm[2], +(tm[3] || 0)];
- order++;
- entries.push({ date: d, time: t, order, relpath, note,
- breadcrumb: breadcrumb(), lines: block,
- pageLevel: false, notes: [note], anchor: anchorOf() });
- }
- function collectChildren(i, parentIndent) {
- const kids = [], raw = [];
- let j = i + 1;
- while (j < lines.length) {
- const ln = lines[j];
- if (ln.trim() === "") {
- let k = j + 1;
- while (k < lines.length && lines[k].trim() === "") k++;
- if (k < lines.length && LIST_RE.test(lines[k]) && indentOf(lines[k]) > parentIndent) { j = k; continue; }
- break;
- }
- const mi = ln.match(LIST_RE);
- if (mi && indentOf(ln) > parentIndent) {
- raw.push([indentOf(ln), (mi[3] || "").replace(/\s+$/, "")]);
- j++;
- } else break;
- }
- for (const [ind, txt] of raw) kids.push([Math.floor((ind - parentIndent) / 2), txt]);
- return kids;
- }
-
- for (let i = fm.bodyStart; i < lines.length; i++) {
- const ln = lines[i];
- if (FENCE_RE.test(ln)) { inFence = !inFence; continue; }
- if (inFence) continue;
- if (inPct) { if (ln.includes("%%")) inPct = false; continue; }
- if (inHtml) { if (ln.includes("-->")) inHtml = false; continue; }
- const stripped = ln.trim();
- if (stripped.startsWith("%%") && !stripped.slice(2).includes("%%")) { inPct = true; continue; }
- if (stripped.startsWith("<!--") && !stripped.includes("-->")) { inHtml = true; continue; }
-
- const hm = ln.match(HEAD_RE);
- if (hm) {
- const htext = hm[2];
- const dh = htext.match(DATEH_RE);
- if (dh) { curDateHead = vdate(+dh[1], +dh[2], +dh[3]); curHead = null; curHeadRaw = null; }
- else { curHead = htext; curHeadRaw = htext.trim(); }
- if (TAG_RE.test(cleanMatch(htext))) {
- const sec = [[0, "**" + stripTag(htext) + "**"]];
- let j = i + 1;
- const level = hm[1].length;
- let count = 0;
- while (j < lines.length && count < 300) {
- const sl = lines[j];
- const shm = sl.match(HEAD_RE);
- if (shm && shm[1].length <= level) break;
- if (FENCE_RE.test(sl)) break;
- if (sl.trim()) {
- const smi = sl.match(LIST_RE);
- if (smi) sec.push([1 + Math.floor(indentOf(sl) / 2), (smi[3] || "").replace(/\s+$/, "")]);
- else sec.push([1, sl.trim()]);
- count++;
- }
- j++;
- }
- addBlock(i, sec);
- }
- continue;
- }
-
- const clean = cleanMatch(ln);
- if (!TAG_RE.test(clean)) continue;
-
- const mi = ln.match(LIST_RE);
- if (mi) {
- const ind = indentOf(ln);
- const itemText = mi[3] || "";
- const cleaned = stripTag(itemText);
- const kids = collectChildren(i, ind);
- addBlock(i, cleaned ? [[0, cleaned], ...kids] : [[0, itemText.trim()], ...kids]);
- continue;
- }
-
- // 独立标签行:向下吸收 list items,或向上归属到紧邻的块
- if (ln.replace(ANY_TAG, "").trim() === "" || ln.replace(ANY_TAG, "").trim() === "%%") {
- let sec = [];
- let j = i + 1;
- while (j < lines.length) {
- const mj = lines[j].match(LIST_RE);
- if (mj) { sec.push([indentOf(lines[j]), (mj[3] || "").replace(/\s+$/, "")]); j++; }
- else break;
- }
- let keyIdx = i;
- if (!sec.length) {
- const up = [];
- let k = i - 1, bulletIdx = null;
- while (k >= fm.bodyStart) {
- const bk = lines[k];
- if (bk.trim() === "" || HEAD_RE.test(bk) || FENCE_RE.test(bk)) break;
- up.push(k);
- if (LIST_RE.test(bk)) { bulletIdx = k; break; }
- k--;
- }
- if (bulletIdx !== null) {
- const inds = [];
- for (let bl = bulletIdx; bl < i; bl++) {
- const mb = lines[bl].match(LIST_RE);
- if (mb) { inds.push(indentOf(lines[bl])); sec.push([indentOf(lines[bl]), (mb[3] || "").replace(/\s+$/, "")]); }
- else sec.push([(inds.length ? inds[inds.length - 1] + 2 : 0), lines[bl].trim()]);
- }
- keyIdx = bulletIdx;
- } else if (up.length) {
- sec = up.slice().reverse().map(k2 => [0, lines[k2].trim()]);
- } else {
- // 兜底:标签行紧挨在某个标题下方(中间只有空行)→ 捕获该标题下的整个小节
- let h = i - 1;
- while (h >= fm.bodyStart && lines[h].trim() === "") h--;
- const hmk = h >= fm.bodyStart ? lines[h].match(HEAD_RE) : null;
- if (hmk) {
- const level = hmk[1].length;
- const title = hmk[2].replace(ANY_TAG, "").replace(/\s+/g, " ").trim();
- const sec2 = title ? [[0, "**" + title + "**"]] : [];
- let j2 = i + 1, count2 = 0;
- while (j2 < lines.length && count2 < 300) {
- const sl = lines[j2];
- const shm = sl.match(HEAD_RE);
- if (shm && shm[1].length <= level) break;
- if (FENCE_RE.test(sl)) break;
- if (sl.trim()) {
- const smi = sl.match(LIST_RE);
- if (smi) sec2.push([1 + Math.floor(indentOf(sl) / 2), (smi[3] || "").replace(/\s+$/, "")]);
- else sec2.push([1, sl.trim().replace(/^>\s*/, "")]);
- count2++;
- }
- j2++;
- }
- if (sec2.length) sec = sec2;
- }
- }
- }
- if (sec.length) {
- const mi0 = Math.min(...sec.map(x => x[0]));
- addBlock(keyIdx, sec.map(([ind, t]) => [Math.floor((ind - mi0) / 2), t]));
- }
- continue;
- }
-
- // 普通段落带标签:吸收到空行为止
- const para = [stripTag(clean)];
- let j = i + 1;
- while (j < lines.length) {
- const nl = lines[j];
- if (nl.trim() === "" || HEAD_RE.test(nl) || LIST_RE.test(nl) || FENCE_RE.test(nl)) break;
- para.push(nl.trim());
- j++;
- }
- addBlock(i, [[0, para.filter(p => p).join(" ")]]);
- }
-
- // frontmatter 标签 → 页面级条目(附正文开头预览)
- if (TAG_LIST.some(t => fm.tags.has(t))) {
- const d = fm.date || fileDate || mtimeDate;
- if (d) {
- order++;
- // 收集正文文本至 PREV_CHARS:超过上限的长文在渲染时截断并附「查看全文」链接
- const prev = [];
- let prevTotal = 0, prevCount = 0;
- for (let k = fm.bodyStart; k < lines.length && prevCount < 200; k++) {
- const s = lines[k].trim();
- if (!s || s.startsWith("#") || s === "---") continue;
- const clean = s.replace(ANY_TAG, "");
- if (!clean) continue;
- prev.push(clean);
- prevTotal += clean.length;
- prevCount++;
- if (prevTotal > PREV_CHARS) break; // 超过上限:已足够判断,停止收集
- }
- const preview = prev.join(" ");
- entries.push({ date: d, time: null, order, relpath, note, breadcrumb: null,
- lines: preview ? [[0, preview]] : [],
- pageLevel: true, notes: [note], anchor: null });
- }
- }
- return entries;
-}
-
-// ---------------- 扫描整个 vault ----------------
-const currentPath = (dv.current() && dv.current().file) ? dv.current().file.path : null;
-let all = [];
-for (const f of app.vault.getMarkdownFiles()) {
- if (f.path === currentPath || f.path.endsWith(".excalidraw.md")) continue;
- if (SKIP_DIRS.some(d => f.path.startsWith(d + "/"))) continue;
- if (f.path.split("/").some(seg => seg.startsWith("."))) continue; // 排除所有隐藏文件/文件夹(.开头)
- if (excludedPaths.some(p => f.path === p || f.path.startsWith(p.endsWith("/") ? p : p + "/"))) continue;
- let text;
- try { text = await app.vault.cachedRead(f); } catch (e) { continue; }
- if (text.slice(0, 1000).includes(MARKER)) continue; // 跳过 tagfeed 生成的静态页
- all = all.concat(scanText(text, f.basename, f.path, dateFromName(f.basename), new Date(f.stat.mtime)));
-}
-
-// ---------------- 去重:同日期+同内容合并,来源并排 ----------------
-const timeCmp = (a, b) => (a[0] - b[0]) || (a[1] - b[1]) || (a[2] - b[2]);
-const mergedMap = new Map();
-for (const e of all) {
- const fp = e.date.getTime() + "|" + e.lines.map(x => x[0] + ":" + x[1]).join("\n");
- const m = mergedMap.get(fp);
- if (m) {
- if (!m.notes.includes(e.note)) m.notes.push(e.note);
- if (e.time && (!m.time || timeCmp(e.time, m.time) < 0)) m.time = e.time;
- // 若先扫到的来源没有面包屑/锚点,而后面的有,则采用后者(与扫描顺序无关,结果确定)
- if (!m.breadcrumb && e.breadcrumb) {
- m.breadcrumb = e.breadcrumb;
- m.anchor = e.anchor;
- m.relpath = e.relpath;
- m.note = e.note;
- }
- } else mergedMap.set(fp, e);
-}
-const entries = [...mergedMap.values()];
-
-// ---------------- 渲染:按日期倒序 ----------------
-const byDate = new Map();
-for (const e of entries) {
- const k = e.date.getTime();
- if (!byDate.has(k)) byDate.set(k, []);
- byDate.get(k).push(e);
-}
-const dates = [...byDate.keys()].sort((a, b) => b - a);
-const noteSet = new Set();
-for (const e of entries) for (const n of e.notes) noteSet.add(n);
-
-const out = [];
-out.push("> **" + entries.length + "** 个内容块 | **" + noteSet.size + "** 篇笔记 | 按日期倒序 | 随笔记变动自动刷新");
-
-const pad2 = x => String(x).padStart(2, "0");
-const fmtDate = d => d.getFullYear() + "-" + pad2(d.getMonth() + 1) + "-" + pad2(d.getDate());
-const invTime = t => t.map(x => pad2(99 - x)).join("");
-
-for (const dk of dates) {
- const d = new Date(dk);
- out.push("## 📅 " + fmtDate(d) + " · " + WEEKDAYS[d.getDay() === 0 ? 6 : d.getDay() - 1]);
- out.push("");
- const group = byDate.get(dk);
-
- const sub = new Map(); // (笔记+面包屑) -> entries
- for (const e of group) {
- const key = e.relpath + "|" + (e.breadcrumb || "");
- if (!sub.has(key)) sub.set(key, []);
- sub.get(key).push(e);
- }
- const subKey = ([key, es]) => {
- const times = es.filter(e => e.time).map(e => e.time);
- const best = times.length ? times.reduce((a, b) => (timeCmp(a, b) > 0 ? a : b)) : null;
- return (best ? "0" : "1") + (best ? invTime(best) : "000000") + key;
- };
-
- for (const [key, es] of [...sub.entries()].sort((a, b) => (subKey(a) < subKey(b) ? -1 : subKey(a) > subKey(b) ? 1 : 0))) {
- const noteList = [];
- for (const e of es) for (const n of e.notes) if (!noteList.includes(n)) noteList.push(n);
- noteList.sort();
- const crumb = es.map(e => e.breadcrumb).find(b => b) || null;
- out.push("- 🔗 " + noteList.map(n => "[[" + n + "]]").join(" · ") + (crumb ? " · " + crumb : ""));
- const sorted = es.slice().sort((a, b) => {
- const ka = (a.time ? "0" : "1") + (a.time ? invTime(a.time) : "") + String(a.order).padStart(6, "0");
- const kb = (b.time ? "0" : "1") + (b.time ? invTime(b.time) : "") + String(b.order).padStart(6, "0");
- return ka < kb ? -1 : ka > kb ? 1 : 0;
- });
- for (const e of sorted) {
- let bl = e.lines, truncated = false;
- const totalChars = bl.reduce((s, x) => s + x[1].length, 0);
- if (totalChars > PREV_CHARS) { // 只有超过上限才截断;上限内全文展示
- truncated = true;
- const shown = [];
- let chars = 0;
- for (const [lvl, txt] of bl) {
- if (chars + txt.length > PREV_CHARS) {
- if (PREV_CHARS - chars > 0) shown.push([lvl, txt.slice(0, PREV_CHARS - chars) + "…"]);
- break;
- }
- shown.push([lvl, txt]);
- chars += txt.length;
- }
- bl = shown;
- }
- for (const [lvl, txt] of bl) out.push("\t".repeat(lvl + 1) + "- " + txt);
- if (truncated) {
- const target = e.note + (e.anchor ? "#" + e.anchor : "");
- const label = e.pageLevel ? "页面级标签 · 查看全文" : "查看全文";
- out.push("\t- [[" + target + "|↗ " + label + "]]");
- }
- }
- out.push("");
- }
- out.push("");
-}
-dv.paragraph(out.join("\n"));
-
-} catch (err) {
- dv.paragraph("> ❌ tagfeed-live 执行失败:" + err.message);
- console.error("tagfeed-live:", err);
-}
-
-```
diff --git a/00index/0inbox/Evidence.md b/00index/0inbox/Evidence.md
deleted file mode 100644
index 68077d3..0000000
--- a/00index/0inbox/Evidence.md
+++ /dev/null
@@ -1,437 +0,0 @@
----
-tags:
- - tagfeed-live
-cssclasses:
- - tagfeed
-words:
- 2026-08-23: 2728
----
-
-# 🏷 #Evidence(实时聚合页)
-
-> 本页由 DataviewJS 实时计算,笔记有变动时自动刷新,无需任何后台服务。
-
-```dataviewjs
-// ============================================================
-// tagfeed-live:Logseq 风格标签实时聚合(标准 DataviewJS 脚本)
-//
-// 用法:
-// 1. 新建一个笔记(例如 00index/标签聚合/标签名.md)
-// 2. 在笔记里插入一个 dataviewjs 代码块(三个反引号 + dataviewjs),
-// 把本脚本全部粘贴进去
-// 3. 只需要改下面「配置区」的两行:
-// - targetTag 换成你要聚合的标签(不带 #,中英文都可以;多个标签写成数组 ["a", "b"])
-// - excludedPaths 换成你想排除的文件夹
-// 4. 完成。以后任何笔记里出现/删除这个标签,本页自动刷新,
-// 不需要跑任何命令(Dataview 会在文件变动后自动重新渲染)。
-// ============================================================
-
-// ---------------- 配置区(通常只改这里) ----------------
-const targetTag = "Evidence"; // 要聚合的标签(不带 #);多个标签用数组:["lyric", "歌词"]
-const excludedPaths = ["X2.Archived", "00index", "X0.Clippings", "X1.Knomo", "02DS/02copilot", ".agents", ".claude", ".copilot", ".opencode", ".smart-env", ".workbuddy", ".trash"]; // 要排除的文件夹/路径前缀(vault 相对路径)
- // 例如:["00index", "模板", "X1.Knomo"]
-// 补充说明:
-// - 所有隐藏文件/文件夹(. 开头:.agents/.claude/.copilot/.opencode/.smart-env/.workbuddy/.trash 等)与 X.Attachment 始终跳过
-// - tagfeed 系统页会自动跳过:静态聚合页(文件头含 <!-- tagfeed 标记)
-// 和实时页(frontmatter 带 tagfeed / tagfeed-live 标签),
-// 无需手动排除 00index
-// - 本脚本所在的当前笔记也会自动跳过
-// ---------------------------------------------------------
-
-try {
-const SKIP_DIRS = [".obsidian", ".git", ".trash", "X.Attachment"];
-const MARKER = "<!-- tagfeed";
-const WEEKDAYS = ["周一", "周二", "周三", "周四", "周五", "周六", "周日"];
-const PREV_CHARS = 500; // 预览字数上限:≤上限全文展示;>上限截断并附「查看全文」链接
-
-const esc = s => String(s).replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
-const TAG_LIST = Array.isArray(targetTag) ? targetTag : [targetTag];
-const TAG_RE = new RegExp("(?<![\\w\\u4e00-\\u9fff#])#(?:" + TAG_LIST.map(esc).join("|") + ")(?![\\w\\u4e00-\\u9fff/\\-])");
-const ANY_TAG = /(?<![\w#])#([\w\u4e00-\u9fff][\w\u4e00-\u9fff/\-]*)/g;
-const LIST_RE = /^(\s*)([-*+]|\d+[.)])(?:\s+(.*))?$/;
-const HEAD_RE = /^(#{1,6})\s+(.*)$/;
-const DATEH_RE = /\[\[(\d{4})-(\d{2})-(\d{2})\]\]/;
-const TIME_RE = /(\d{1,2}):(\d{2})(?::(\d{2}))?/;
-const FENCE_RE = /^\s*(`{3,}|~~~)/;
-const indentOf = ln => { // tab 按 2 空格计(与 Python 版一致)
- const expanded = ln.replace(/\t/g, " ");
- return expanded.length - expanded.replace(/^ +/, "").length;
-};
-
-function cleanMatch(ln) {
- let s = ln.replace(/`[^`\n]*`/g, "");
- s = s.replace(/%%[\s\S]*?%%/g, "");
- s = s.replace(/<!--[\s\S]*?-->/g, "");
- return s;
-}
-function vdate(y, m, d) {
- const dt = new Date(y, m - 1, d);
- return (dt.getFullYear() === y && dt.getMonth() === m - 1 && dt.getDate() === d) ? dt : null;
-}
-function dateFromName(name) { // 文件名含 YYYY-MM-DD,或日记命名 26.0805 -> 2026-08-05
- const m1 = name.match(/(\d{4})-(\d{2})-(\d{2})/);
- if (m1) {
- const d1 = vdate(+m1[1], +m1[2], +m1[3]);
- if (d1) return d1;
- }
- const m = name.match(/^(\d{2})\.(\d{2})(\d{2})(?:\D|$)/);
- return m ? vdate(2000 + +m[1], +m[2], +m[3]) : null;
-}
-function parseFrontmatter(text) {
- const lines = text.split("\n");
- if (!lines.length || lines[0].trim() !== "---") return { tags: new Set(), date: null, bodyStart: 0 };
- const n = Math.min(lines.length, 200);
- for (let i = 1; i < n; i++) {
- const t = lines[i].trim();
- if (t === "---" || t === "...") {
- const block = lines.slice(1, i).join("\n");
- const tags = new Set();
- const im = block.match(/^tags:\s*\[(.*?)\]/m);
- if (im) {
- for (const x0 of im[1].split(",")) {
- const x = x0.trim().replace(/^["']|["']$/g, "").replace(/^#/, "").trim();
- if (x) tags.add(x);
- }
- } else {
- const bm = block.match(/^tags:\s*\n((?:[ \t]+-[ \t]+.*\n?)+)/m);
- if (bm) for (const ln of bm[1].split("\n")) {
- const m2 = ln.match(/^\s*-\s*["']?([\w\u4e00-\u9fff/\-]+)["']?\s*$/);
- if (m2) tags.add(m2[1]);
- }
- }
- let fdate = null;
- for (const key of ["date", "created", "published"]) {
- const dm = block.match(new RegExp("^" + key + ":\\s*[\"']?(\\d{4})-(\\d{2})-(\\d{2})", "m"));
- if (dm) { fdate = vdate(+dm[1], +dm[2], +dm[3]); break; }
- }
- return { tags, date: fdate, bodyStart: i + 1 };
- }
- }
- return { tags: new Set(), date: null, bodyStart: 0 };
-}
-const stripTag = s => String(s).replace(TAG_RE, "").replace(/[ \t]{2,}/g, " ").trim();
-
-function scanText(text, note, relpath, fileDate, mtimeDate) {
- const fm = parseFrontmatter(text);
- if (fm.tags.has("tagfeed") || fm.tags.has("tagfeed-live")) return []; // 跳过 tagfeed 系统页(静态聚合页/实时页)
- const lines = text.split("\n");
- const entries = [], seen = new Set();
- let order = 0, inFence = false, inPct = false, inHtml = false;
- let curHead = null, curHeadRaw = null, curDateHead = null;
-
- const entryDate = () => curDateHead || fileDate || fm.date || mtimeDate;
- const anchorOf = () => (curDateHead ? null : curHeadRaw);
- const breadcrumb = () => {
- if (curHead && !DATEH_RE.test(curHead)) {
- const t = curHead.replace(ANY_TAG, "").replace(/\s+/g, " ").trim();
- if (t) return t;
- }
- return null;
- };
- function addBlock(keyIdx, block) {
- const key = relpath + "|" + keyIdx;
- if (seen.has(key) || !block.length) return;
- seen.add(key);
- const d = entryDate();
- if (!d) return;
- let t = null;
- const tm = block[0][1].slice(0, 30).match(TIME_RE);
- if (tm) t = [+tm[1], +tm[2], +(tm[3] || 0)];
- order++;
- entries.push({ date: d, time: t, order, relpath, note,
- breadcrumb: breadcrumb(), lines: block,
- pageLevel: false, notes: [note], anchor: anchorOf() });
- }
- function collectChildren(i, parentIndent) {
- const kids = [], raw = [];
- let j = i + 1;
- while (j < lines.length) {
- const ln = lines[j];
- if (ln.trim() === "") {
- let k = j + 1;
- while (k < lines.length && lines[k].trim() === "") k++;
- if (k < lines.length && LIST_RE.test(lines[k]) && indentOf(lines[k]) > parentIndent) { j = k; continue; }
- break;
- }
- const mi = ln.match(LIST_RE);
- if (mi && indentOf(ln) > parentIndent) {
- raw.push([indentOf(ln), (mi[3] || "").replace(/\s+$/, "")]);
- j++;
- } else break;
- }
- for (const [ind, txt] of raw) kids.push([Math.floor((ind - parentIndent) / 2), txt]);
- return kids;
- }
-
- for (let i = fm.bodyStart; i < lines.length; i++) {
- const ln = lines[i];
- if (FENCE_RE.test(ln)) { inFence = !inFence; continue; }
- if (inFence) continue;
- if (inPct) { if (ln.includes("%%")) inPct = false; continue; }
- if (inHtml) { if (ln.includes("-->")) inHtml = false; continue; }
- const stripped = ln.trim();
- if (stripped.startsWith("%%") && !stripped.slice(2).includes("%%")) { inPct = true; continue; }
- if (stripped.startsWith("<!--") && !stripped.includes("-->")) { inHtml = true; continue; }
-
- const hm = ln.match(HEAD_RE);
- if (hm) {
- const htext = hm[2];
- const dh = htext.match(DATEH_RE);
- if (dh) { curDateHead = vdate(+dh[1], +dh[2], +dh[3]); curHead = null; curHeadRaw = null; }
- else { curHead = htext; curHeadRaw = htext.trim(); }
- if (TAG_RE.test(cleanMatch(htext))) {
- const sec = [[0, "**" + stripTag(htext) + "**"]];
- let j = i + 1;
- const level = hm[1].length;
- let count = 0;
- while (j < lines.length && count < 300) {
- const sl = lines[j];
- const shm = sl.match(HEAD_RE);
- if (shm && shm[1].length <= level) break;
- if (FENCE_RE.test(sl)) break;
- if (sl.trim()) {
- const smi = sl.match(LIST_RE);
- if (smi) sec.push([1 + Math.floor(indentOf(sl) / 2), (smi[3] || "").replace(/\s+$/, "")]);
- else sec.push([1, sl.trim()]);
- count++;
- }
- j++;
- }
- addBlock(i, sec);
- }
- continue;
- }
-
- const clean = cleanMatch(ln);
- if (!TAG_RE.test(clean)) continue;
-
- const mi = ln.match(LIST_RE);
- if (mi) {
- const ind = indentOf(ln);
- const itemText = mi[3] || "";
- const cleaned = stripTag(itemText);
- const kids = collectChildren(i, ind);
- addBlock(i, cleaned ? [[0, cleaned], ...kids] : [[0, itemText.trim()], ...kids]);
- continue;
- }
-
- // 独立标签行:向下吸收 list items,或向上归属到紧邻的块
- if (ln.replace(ANY_TAG, "").trim() === "" || ln.replace(ANY_TAG, "").trim() === "%%") {
- let sec = [];
- let j = i + 1;
- while (j < lines.length) {
- const mj = lines[j].match(LIST_RE);
- if (mj) { sec.push([indentOf(lines[j]), (mj[3] || "").replace(/\s+$/, "")]); j++; }
- else break;
- }
- let keyIdx = i;
- if (!sec.length) {
- const up = [];
- let k = i - 1, bulletIdx = null;
- while (k >= fm.bodyStart) {
- const bk = lines[k];
- if (bk.trim() === "" || HEAD_RE.test(bk) || FENCE_RE.test(bk)) break;
- up.push(k);
- if (LIST_RE.test(bk)) { bulletIdx = k; break; }
- k--;
- }
- if (bulletIdx !== null) {
- const inds = [];
- for (let bl = bulletIdx; bl < i; bl++) {
- const mb = lines[bl].match(LIST_RE);
- if (mb) { inds.push(indentOf(lines[bl])); sec.push([indentOf(lines[bl]), (mb[3] || "").replace(/\s+$/, "")]); }
- else sec.push([(inds.length ? inds[inds.length - 1] + 2 : 0), lines[bl].trim()]);
- }
- keyIdx = bulletIdx;
- } else if (up.length) {
- sec = up.slice().reverse().map(k2 => [0, lines[k2].trim()]);
- } else {
- // 兜底:标签行紧挨在某个标题下方(中间只有空行)→ 捕获该标题下的整个小节
- let h = i - 1;
- while (h >= fm.bodyStart && lines[h].trim() === "") h--;
- const hmk = h >= fm.bodyStart ? lines[h].match(HEAD_RE) : null;
- if (hmk) {
- const level = hmk[1].length;
- const title = hmk[2].replace(ANY_TAG, "").replace(/\s+/g, " ").trim();
- const sec2 = title ? [[0, "**" + title + "**"]] : [];
- let j2 = i + 1, count2 = 0;
- while (j2 < lines.length && count2 < 300) {
- const sl = lines[j2];
- const shm = sl.match(HEAD_RE);
- if (shm && shm[1].length <= level) break;
- if (FENCE_RE.test(sl)) break;
- if (sl.trim()) {
- const smi = sl.match(LIST_RE);
- if (smi) sec2.push([1 + Math.floor(indentOf(sl) / 2), (smi[3] || "").replace(/\s+$/, "")]);
- else sec2.push([1, sl.trim().replace(/^>\s*/, "")]);
- count2++;
- }
- j2++;
- }
- if (sec2.length) sec = sec2;
- }
- }
- }
- if (sec.length) {
- const mi0 = Math.min(...sec.map(x => x[0]));
- addBlock(keyIdx, sec.map(([ind, t]) => [Math.floor((ind - mi0) / 2), t]));
- }
- continue;
- }
-
- // 普通段落带标签:吸收到空行为止
- const para = [stripTag(clean)];
- let j = i + 1;
- while (j < lines.length) {
- const nl = lines[j];
- if (nl.trim() === "" || HEAD_RE.test(nl) || LIST_RE.test(nl) || FENCE_RE.test(nl)) break;
- para.push(nl.trim());
- j++;
- }
- addBlock(i, [[0, para.filter(p => p).join(" ")]]);
- }
-
- // frontmatter 标签 → 页面级条目(附正文开头预览)
- if (TAG_LIST.some(t => fm.tags.has(t))) {
- const d = fm.date || fileDate || mtimeDate;
- if (d) {
- order++;
- // 收集正文文本至 PREV_CHARS:超过上限的长文在渲染时截断并附「查看全文」链接
- const prev = [];
- let prevTotal = 0, prevCount = 0;
- for (let k = fm.bodyStart; k < lines.length && prevCount < 200; k++) {
- const s = lines[k].trim();
- if (!s || s.startsWith("#") || s === "---") continue;
- const clean = s.replace(ANY_TAG, "");
- if (!clean) continue;
- prev.push(clean);
- prevTotal += clean.length;
- prevCount++;
- if (prevTotal > PREV_CHARS) break; // 超过上限:已足够判断,停止收集
- }
- const preview = prev.join(" ");
- entries.push({ date: d, time: null, order, relpath, note, breadcrumb: null,
- lines: preview ? [[0, preview]] : [],
- pageLevel: true, notes: [note], anchor: null });
- }
- }
- return entries;
-}
-
-// ---------------- 扫描整个 vault ----------------
-const currentPath = (dv.current() && dv.current().file) ? dv.current().file.path : null;
-let all = [];
-for (const f of app.vault.getMarkdownFiles()) {
- if (f.path === currentPath || f.path.endsWith(".excalidraw.md")) continue;
- if (SKIP_DIRS.some(d => f.path.startsWith(d + "/"))) continue;
- if (f.path.split("/").some(seg => seg.startsWith("."))) continue; // 排除所有隐藏文件/文件夹(.开头)
- if (excludedPaths.some(p => f.path === p || f.path.startsWith(p.endsWith("/") ? p : p + "/"))) continue;
- let text;
- try { text = await app.vault.cachedRead(f); } catch (e) { continue; }
- if (text.slice(0, 1000).includes(MARKER)) continue; // 跳过 tagfeed 生成的静态页
- all = all.concat(scanText(text, f.basename, f.path, dateFromName(f.basename), new Date(f.stat.mtime)));
-}
-
-// ---------------- 去重:同日期+同内容合并,来源并排 ----------------
-const timeCmp = (a, b) => (a[0] - b[0]) || (a[1] - b[1]) || (a[2] - b[2]);
-const mergedMap = new Map();
-for (const e of all) {
- const fp = e.date.getTime() + "|" + e.lines.map(x => x[0] + ":" + x[1]).join("\n");
- const m = mergedMap.get(fp);
- if (m) {
- if (!m.notes.includes(e.note)) m.notes.push(e.note);
- if (e.time && (!m.time || timeCmp(e.time, m.time) < 0)) m.time = e.time;
- // 若先扫到的来源没有面包屑/锚点,而后面的有,则采用后者(与扫描顺序无关,结果确定)
- if (!m.breadcrumb && e.breadcrumb) {
- m.breadcrumb = e.breadcrumb;
- m.anchor = e.anchor;
- m.relpath = e.relpath;
- m.note = e.note;
- }
- } else mergedMap.set(fp, e);
-}
-const entries = [...mergedMap.values()];
-
-// ---------------- 渲染:按日期倒序 ----------------
-const byDate = new Map();
-for (const e of entries) {
- const k = e.date.getTime();
- if (!byDate.has(k)) byDate.set(k, []);
- byDate.get(k).push(e);
-}
-const dates = [...byDate.keys()].sort((a, b) => b - a);
-const noteSet = new Set();
-for (const e of entries) for (const n of e.notes) noteSet.add(n);
-
-const out = [];
-out.push("> **" + entries.length + "** 个内容块 | **" + noteSet.size + "** 篇笔记 | 按日期倒序 | 随笔记变动自动刷新");
-
-const pad2 = x => String(x).padStart(2, "0");
-const fmtDate = d => d.getFullYear() + "-" + pad2(d.getMonth() + 1) + "-" + pad2(d.getDate());
-const invTime = t => t.map(x => pad2(99 - x)).join("");
-
-for (const dk of dates) {
- const d = new Date(dk);
- out.push("## 📅 " + fmtDate(d) + " · " + WEEKDAYS[d.getDay() === 0 ? 6 : d.getDay() - 1]);
- out.push("");
- const group = byDate.get(dk);
-
- const sub = new Map(); // (笔记+面包屑) -> entries
- for (const e of group) {
- const key = e.relpath + "|" + (e.breadcrumb || "");
- if (!sub.has(key)) sub.set(key, []);
- sub.get(key).push(e);
- }
- const subKey = ([key, es]) => {
- const times = es.filter(e => e.time).map(e => e.time);
- const best = times.length ? times.reduce((a, b) => (timeCmp(a, b) > 0 ? a : b)) : null;
- return (best ? "0" : "1") + (best ? invTime(best) : "000000") + key;
- };
-
- for (const [key, es] of [...sub.entries()].sort((a, b) => (subKey(a) < subKey(b) ? -1 : subKey(a) > subKey(b) ? 1 : 0))) {
- const noteList = [];
- for (const e of es) for (const n of e.notes) if (!noteList.includes(n)) noteList.push(n);
- noteList.sort();
- const crumb = es.map(e => e.breadcrumb).find(b => b) || null;
- out.push("- 🔗 " + noteList.map(n => "[[" + n + "]]").join(" · ") + (crumb ? " · " + crumb : ""));
- const sorted = es.slice().sort((a, b) => {
- const ka = (a.time ? "0" : "1") + (a.time ? invTime(a.time) : "") + String(a.order).padStart(6, "0");
- const kb = (b.time ? "0" : "1") + (b.time ? invTime(b.time) : "") + String(b.order).padStart(6, "0");
- return ka < kb ? -1 : ka > kb ? 1 : 0;
- });
- for (const e of sorted) {
- let bl = e.lines, truncated = false;
- const totalChars = bl.reduce((s, x) => s + x[1].length, 0);
- if (totalChars > PREV_CHARS) { // 只有超过上限才截断;上限内全文展示
- truncated = true;
- const shown = [];
- let chars = 0;
- for (const [lvl, txt] of bl) {
- if (chars + txt.length > PREV_CHARS) {
- if (PREV_CHARS - chars > 0) shown.push([lvl, txt.slice(0, PREV_CHARS - chars) + "…"]);
- break;
- }
- shown.push([lvl, txt]);
- chars += txt.length;
- }
- bl = shown;
- }
- for (const [lvl, txt] of bl) out.push("\t".repeat(lvl + 1) + "- " + txt);
- if (truncated) {
- const target = e.note + (e.anchor ? "#" + e.anchor : "");
- const label = e.pageLevel ? "页面级标签 · 查看全文" : "查看全文";
- out.push("\t- [[" + target + "|↗ " + label + "]]");
- }
- }
- out.push("");
- }
- out.push("");
-}
-dv.paragraph(out.join("\n"));
-
-} catch (err) {
- dv.paragraph("> ❌ tagfeed-live 执行失败:" + err.message);
- console.error("tagfeed-live:", err);
-}
-
-```
diff --git a/00index/0inbox/FIXME.md b/00index/0inbox/FIXME.md
deleted file mode 100644
index 014cdb9..0000000
--- a/00index/0inbox/FIXME.md
+++ /dev/null
@@ -1,437 +0,0 @@
----
-tags:
- - tagfeed-live
-cssclasses:
- - tagfeed
-words:
- 2026-08-23: 2728
----
-
-# 🏷 #FIXME(实时聚合页)
-
-> 本页由 DataviewJS 实时计算,笔记有变动时自动刷新,无需任何后台服务。
-
-```dataviewjs
-// ============================================================
-// tagfeed-live:Logseq 风格标签实时聚合(标准 DataviewJS 脚本)
-//
-// 用法:
-// 1. 新建一个笔记(例如 00index/标签聚合/标签名.md)
-// 2. 在笔记里插入一个 dataviewjs 代码块(三个反引号 + dataviewjs),
-// 把本脚本全部粘贴进去
-// 3. 只需要改下面「配置区」的两行:
-// - targetTag 换成你要聚合的标签(不带 #,中英文都可以;多个标签写成数组 ["a", "b"])
-// - excludedPaths 换成你想排除的文件夹
-// 4. 完成。以后任何笔记里出现/删除这个标签,本页自动刷新,
-// 不需要跑任何命令(Dataview 会在文件变动后自动重新渲染)。
-// ============================================================
-
-// ---------------- 配置区(通常只改这里) ----------------
-const targetTag = "FIXME"; // 要聚合的标签(不带 #);多个标签用数组:["lyric", "歌词"]
-const excludedPaths = ["X2.Archived", "00index", "X0.Clippings", "X1.Knomo", "02DS/02copilot", ".agents", ".claude", ".copilot", ".opencode", ".smart-env", ".workbuddy", ".trash"]; // 要排除的文件夹/路径前缀(vault 相对路径)
- // 例如:["00index", "模板", "X1.Knomo"]
-// 补充说明:
-// - 所有隐藏文件/文件夹(. 开头:.agents/.claude/.copilot/.opencode/.smart-env/.workbuddy/.trash 等)与 X.Attachment 始终跳过
-// - tagfeed 系统页会自动跳过:静态聚合页(文件头含 <!-- tagfeed 标记)
-// 和实时页(frontmatter 带 tagfeed / tagfeed-live 标签),
-// 无需手动排除 00index
-// - 本脚本所在的当前笔记也会自动跳过
-// ---------------------------------------------------------
-
-try {
-const SKIP_DIRS = [".obsidian", ".git", ".trash", "X.Attachment"];
-const MARKER = "<!-- tagfeed";
-const WEEKDAYS = ["周一", "周二", "周三", "周四", "周五", "周六", "周日"];
-const PREV_CHARS = 500; // 预览字数上限:≤上限全文展示;>上限截断并附「查看全文」链接
-
-const esc = s => String(s).replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
-const TAG_LIST = Array.isArray(targetTag) ? targetTag : [targetTag];
-const TAG_RE = new RegExp("(?<![\\w\\u4e00-\\u9fff#])#(?:" + TAG_LIST.map(esc).join("|") + ")(?![\\w\\u4e00-\\u9fff/\\-])");
-const ANY_TAG = /(?<![\w#])#([\w\u4e00-\u9fff][\w\u4e00-\u9fff/\-]*)/g;
-const LIST_RE = /^(\s*)([-*+]|\d+[.)])(?:\s+(.*))?$/;
-const HEAD_RE = /^(#{1,6})\s+(.*)$/;
-const DATEH_RE = /\[\[(\d{4})-(\d{2})-(\d{2})\]\]/;
-const TIME_RE = /(\d{1,2}):(\d{2})(?::(\d{2}))?/;
-const FENCE_RE = /^\s*(`{3,}|~~~)/;
-const indentOf = ln => { // tab 按 2 空格计(与 Python 版一致)
- const expanded = ln.replace(/\t/g, " ");
- return expanded.length - expanded.replace(/^ +/, "").length;
-};
-
-function cleanMatch(ln) {
- let s = ln.replace(/`[^`\n]*`/g, "");
- s = s.replace(/%%[\s\S]*?%%/g, "");
- s = s.replace(/<!--[\s\S]*?-->/g, "");
- return s;
-}
-function vdate(y, m, d) {
- const dt = new Date(y, m - 1, d);
- return (dt.getFullYear() === y && dt.getMonth() === m - 1 && dt.getDate() === d) ? dt : null;
-}
-function dateFromName(name) { // 文件名含 YYYY-MM-DD,或日记命名 26.0805 -> 2026-08-05
- const m1 = name.match(/(\d{4})-(\d{2})-(\d{2})/);
- if (m1) {
- const d1 = vdate(+m1[1], +m1[2], +m1[3]);
- if (d1) return d1;
- }
- const m = name.match(/^(\d{2})\.(\d{2})(\d{2})(?:\D|$)/);
- return m ? vdate(2000 + +m[1], +m[2], +m[3]) : null;
-}
-function parseFrontmatter(text) {
- const lines = text.split("\n");
- if (!lines.length || lines[0].trim() !== "---") return { tags: new Set(), date: null, bodyStart: 0 };
- const n = Math.min(lines.length, 200);
- for (let i = 1; i < n; i++) {
- const t = lines[i].trim();
- if (t === "---" || t === "...") {
- const block = lines.slice(1, i).join("\n");
- const tags = new Set();
- const im = block.match(/^tags:\s*\[(.*?)\]/m);
- if (im) {
- for (const x0 of im[1].split(",")) {
- const x = x0.trim().replace(/^["']|["']$/g, "").replace(/^#/, "").trim();
- if (x) tags.add(x);
- }
- } else {
- const bm = block.match(/^tags:\s*\n((?:[ \t]+-[ \t]+.*\n?)+)/m);
- if (bm) for (const ln of bm[1].split("\n")) {
- const m2 = ln.match(/^\s*-\s*["']?([\w\u4e00-\u9fff/\-]+)["']?\s*$/);
- if (m2) tags.add(m2[1]);
- }
- }
- let fdate = null;
- for (const key of ["date", "created", "published"]) {
- const dm = block.match(new RegExp("^" + key + ":\\s*[\"']?(\\d{4})-(\\d{2})-(\\d{2})", "m"));
- if (dm) { fdate = vdate(+dm[1], +dm[2], +dm[3]); break; }
- }
- return { tags, date: fdate, bodyStart: i + 1 };
- }
- }
- return { tags: new Set(), date: null, bodyStart: 0 };
-}
-const stripTag = s => String(s).replace(TAG_RE, "").replace(/[ \t]{2,}/g, " ").trim();
-
-function scanText(text, note, relpath, fileDate, mtimeDate) {
- const fm = parseFrontmatter(text);
- if (fm.tags.has("tagfeed") || fm.tags.has("tagfeed-live")) return []; // 跳过 tagfeed 系统页(静态聚合页/实时页)
- const lines = text.split("\n");
- const entries = [], seen = new Set();
- let order = 0, inFence = false, inPct = false, inHtml = false;
- let curHead = null, curHeadRaw = null, curDateHead = null;
-
- const entryDate = () => curDateHead || fileDate || fm.date || mtimeDate;
- const anchorOf = () => (curDateHead ? null : curHeadRaw);
- const breadcrumb = () => {
- if (curHead && !DATEH_RE.test(curHead)) {
- const t = curHead.replace(ANY_TAG, "").replace(/\s+/g, " ").trim();
- if (t) return t;
- }
- return null;
- };
- function addBlock(keyIdx, block) {
- const key = relpath + "|" + keyIdx;
- if (seen.has(key) || !block.length) return;
- seen.add(key);
- const d = entryDate();
- if (!d) return;
- let t = null;
- const tm = block[0][1].slice(0, 30).match(TIME_RE);
- if (tm) t = [+tm[1], +tm[2], +(tm[3] || 0)];
- order++;
- entries.push({ date: d, time: t, order, relpath, note,
- breadcrumb: breadcrumb(), lines: block,
- pageLevel: false, notes: [note], anchor: anchorOf() });
- }
- function collectChildren(i, parentIndent) {
- const kids = [], raw = [];
- let j = i + 1;
- while (j < lines.length) {
- const ln = lines[j];
- if (ln.trim() === "") {
- let k = j + 1;
- while (k < lines.length && lines[k].trim() === "") k++;
- if (k < lines.length && LIST_RE.test(lines[k]) && indentOf(lines[k]) > parentIndent) { j = k; continue; }
- break;
- }
- const mi = ln.match(LIST_RE);
- if (mi && indentOf(ln) > parentIndent) {
- raw.push([indentOf(ln), (mi[3] || "").replace(/\s+$/, "")]);
- j++;
- } else break;
- }
- for (const [ind, txt] of raw) kids.push([Math.floor((ind - parentIndent) / 2), txt]);
- return kids;
- }
-
- for (let i = fm.bodyStart; i < lines.length; i++) {
- const ln = lines[i];
- if (FENCE_RE.test(ln)) { inFence = !inFence; continue; }
- if (inFence) continue;
- if (inPct) { if (ln.includes("%%")) inPct = false; continue; }
- if (inHtml) { if (ln.includes("-->")) inHtml = false; continue; }
- const stripped = ln.trim();
- if (stripped.startsWith("%%") && !stripped.slice(2).includes("%%")) { inPct = true; continue; }
- if (stripped.startsWith("<!--") && !stripped.includes("-->")) { inHtml = true; continue; }
-
- const hm = ln.match(HEAD_RE);
- if (hm) {
- const htext = hm[2];
- const dh = htext.match(DATEH_RE);
- if (dh) { curDateHead = vdate(+dh[1], +dh[2], +dh[3]); curHead = null; curHeadRaw = null; }
- else { curHead = htext; curHeadRaw = htext.trim(); }
- if (TAG_RE.test(cleanMatch(htext))) {
- const sec = [[0, "**" + stripTag(htext) + "**"]];
- let j = i + 1;
- const level = hm[1].length;
- let count = 0;
- while (j < lines.length && count < 300) {
- const sl = lines[j];
- const shm = sl.match(HEAD_RE);
- if (shm && shm[1].length <= level) break;
- if (FENCE_RE.test(sl)) break;
- if (sl.trim()) {
- const smi = sl.match(LIST_RE);
- if (smi) sec.push([1 + Math.floor(indentOf(sl) / 2), (smi[3] || "").replace(/\s+$/, "")]);
- else sec.push([1, sl.trim()]);
- count++;
- }
- j++;
- }
- addBlock(i, sec);
- }
- continue;
- }
-
- const clean = cleanMatch(ln);
- if (!TAG_RE.test(clean)) continue;
-
- const mi = ln.match(LIST_RE);
- if (mi) {
- const ind = indentOf(ln);
- const itemText = mi[3] || "";
- const cleaned = stripTag(itemText);
- const kids = collectChildren(i, ind);
- addBlock(i, cleaned ? [[0, cleaned], ...kids] : [[0, itemText.trim()], ...kids]);
- continue;
- }
-
- // 独立标签行:向下吸收 list items,或向上归属到紧邻的块
- if (ln.replace(ANY_TAG, "").trim() === "" || ln.replace(ANY_TAG, "").trim() === "%%") {
- let sec = [];
- let j = i + 1;
- while (j < lines.length) {
- const mj = lines[j].match(LIST_RE);
- if (mj) { sec.push([indentOf(lines[j]), (mj[3] || "").replace(/\s+$/, "")]); j++; }
- else break;
- }
- let keyIdx = i;
- if (!sec.length) {
- const up = [];
- let k = i - 1, bulletIdx = null;
- while (k >= fm.bodyStart) {
- const bk = lines[k];
- if (bk.trim() === "" || HEAD_RE.test(bk) || FENCE_RE.test(bk)) break;
- up.push(k);
- if (LIST_RE.test(bk)) { bulletIdx = k; break; }
- k--;
- }
- if (bulletIdx !== null) {
- const inds = [];
- for (let bl = bulletIdx; bl < i; bl++) {
- const mb = lines[bl].match(LIST_RE);
- if (mb) { inds.push(indentOf(lines[bl])); sec.push([indentOf(lines[bl]), (mb[3] || "").replace(/\s+$/, "")]); }
- else sec.push([(inds.length ? inds[inds.length - 1] + 2 : 0), lines[bl].trim()]);
- }
- keyIdx = bulletIdx;
- } else if (up.length) {
- sec = up.slice().reverse().map(k2 => [0, lines[k2].trim()]);
- } else {
- // 兜底:标签行紧挨在某个标题下方(中间只有空行)→ 捕获该标题下的整个小节
- let h = i - 1;
- while (h >= fm.bodyStart && lines[h].trim() === "") h--;
- const hmk = h >= fm.bodyStart ? lines[h].match(HEAD_RE) : null;
- if (hmk) {
- const level = hmk[1].length;
- const title = hmk[2].replace(ANY_TAG, "").replace(/\s+/g, " ").trim();
- const sec2 = title ? [[0, "**" + title + "**"]] : [];
- let j2 = i + 1, count2 = 0;
- while (j2 < lines.length && count2 < 300) {
- const sl = lines[j2];
- const shm = sl.match(HEAD_RE);
- if (shm && shm[1].length <= level) break;
- if (FENCE_RE.test(sl)) break;
- if (sl.trim()) {
- const smi = sl.match(LIST_RE);
- if (smi) sec2.push([1 + Math.floor(indentOf(sl) / 2), (smi[3] || "").replace(/\s+$/, "")]);
- else sec2.push([1, sl.trim().replace(/^>\s*/, "")]);
- count2++;
- }
- j2++;
- }
- if (sec2.length) sec = sec2;
- }
- }
- }
- if (sec.length) {
- const mi0 = Math.min(...sec.map(x => x[0]));
- addBlock(keyIdx, sec.map(([ind, t]) => [Math.floor((ind - mi0) / 2), t]));
- }
- continue;
- }
-
- // 普通段落带标签:吸收到空行为止
- const para = [stripTag(clean)];
- let j = i + 1;
- while (j < lines.length) {
- const nl = lines[j];
- if (nl.trim() === "" || HEAD_RE.test(nl) || LIST_RE.test(nl) || FENCE_RE.test(nl)) break;
- para.push(nl.trim());
- j++;
- }
- addBlock(i, [[0, para.filter(p => p).join(" ")]]);
- }
-
- // frontmatter 标签 → 页面级条目(附正文开头预览)
- if (TAG_LIST.some(t => fm.tags.has(t))) {
- const d = fm.date || fileDate || mtimeDate;
- if (d) {
- order++;
- // 收集正文文本至 PREV_CHARS:超过上限的长文在渲染时截断并附「查看全文」链接
- const prev = [];
- let prevTotal = 0, prevCount = 0;
- for (let k = fm.bodyStart; k < lines.length && prevCount < 200; k++) {
- const s = lines[k].trim();
- if (!s || s.startsWith("#") || s === "---") continue;
- const clean = s.replace(ANY_TAG, "");
- if (!clean) continue;
- prev.push(clean);
- prevTotal += clean.length;
- prevCount++;
- if (prevTotal > PREV_CHARS) break; // 超过上限:已足够判断,停止收集
- }
- const preview = prev.join(" ");
- entries.push({ date: d, time: null, order, relpath, note, breadcrumb: null,
- lines: preview ? [[0, preview]] : [],
- pageLevel: true, notes: [note], anchor: null });
- }
- }
- return entries;
-}
-
-// ---------------- 扫描整个 vault ----------------
-const currentPath = (dv.current() && dv.current().file) ? dv.current().file.path : null;
-let all = [];
-for (const f of app.vault.getMarkdownFiles()) {
- if (f.path === currentPath || f.path.endsWith(".excalidraw.md")) continue;
- if (SKIP_DIRS.some(d => f.path.startsWith(d + "/"))) continue;
- if (f.path.split("/").some(seg => seg.startsWith("."))) continue; // 排除所有隐藏文件/文件夹(.开头)
- if (excludedPaths.some(p => f.path === p || f.path.startsWith(p.endsWith("/") ? p : p + "/"))) continue;
- let text;
- try { text = await app.vault.cachedRead(f); } catch (e) { continue; }
- if (text.slice(0, 1000).includes(MARKER)) continue; // 跳过 tagfeed 生成的静态页
- all = all.concat(scanText(text, f.basename, f.path, dateFromName(f.basename), new Date(f.stat.mtime)));
-}
-
-// ---------------- 去重:同日期+同内容合并,来源并排 ----------------
-const timeCmp = (a, b) => (a[0] - b[0]) || (a[1] - b[1]) || (a[2] - b[2]);
-const mergedMap = new Map();
-for (const e of all) {
- const fp = e.date.getTime() + "|" + e.lines.map(x => x[0] + ":" + x[1]).join("\n");
- const m = mergedMap.get(fp);
- if (m) {
- if (!m.notes.includes(e.note)) m.notes.push(e.note);
- if (e.time && (!m.time || timeCmp(e.time, m.time) < 0)) m.time = e.time;
- // 若先扫到的来源没有面包屑/锚点,而后面的有,则采用后者(与扫描顺序无关,结果确定)
- if (!m.breadcrumb && e.breadcrumb) {
- m.breadcrumb = e.breadcrumb;
- m.anchor = e.anchor;
- m.relpath = e.relpath;
- m.note = e.note;
- }
- } else mergedMap.set(fp, e);
-}
-const entries = [...mergedMap.values()];
-
-// ---------------- 渲染:按日期倒序 ----------------
-const byDate = new Map();
-for (const e of entries) {
- const k = e.date.getTime();
- if (!byDate.has(k)) byDate.set(k, []);
- byDate.get(k).push(e);
-}
-const dates = [...byDate.keys()].sort((a, b) => b - a);
-const noteSet = new Set();
-for (const e of entries) for (const n of e.notes) noteSet.add(n);
-
-const out = [];
-out.push("> **" + entries.length + "** 个内容块 | **" + noteSet.size + "** 篇笔记 | 按日期倒序 | 随笔记变动自动刷新");
-
-const pad2 = x => String(x).padStart(2, "0");
-const fmtDate = d => d.getFullYear() + "-" + pad2(d.getMonth() + 1) + "-" + pad2(d.getDate());
-const invTime = t => t.map(x => pad2(99 - x)).join("");
-
-for (const dk of dates) {
- const d = new Date(dk);
- out.push("## 📅 " + fmtDate(d) + " · " + WEEKDAYS[d.getDay() === 0 ? 6 : d.getDay() - 1]);
- out.push("");
- const group = byDate.get(dk);
-
- const sub = new Map(); // (笔记+面包屑) -> entries
- for (const e of group) {
- const key = e.relpath + "|" + (e.breadcrumb || "");
- if (!sub.has(key)) sub.set(key, []);
- sub.get(key).push(e);
- }
- const subKey = ([key, es]) => {
- const times = es.filter(e => e.time).map(e => e.time);
- const best = times.length ? times.reduce((a, b) => (timeCmp(a, b) > 0 ? a : b)) : null;
- return (best ? "0" : "1") + (best ? invTime(best) : "000000") + key;
- };
-
- for (const [key, es] of [...sub.entries()].sort((a, b) => (subKey(a) < subKey(b) ? -1 : subKey(a) > subKey(b) ? 1 : 0))) {
- const noteList = [];
- for (const e of es) for (const n of e.notes) if (!noteList.includes(n)) noteList.push(n);
- noteList.sort();
- const crumb = es.map(e => e.breadcrumb).find(b => b) || null;
- out.push("- 🔗 " + noteList.map(n => "[[" + n + "]]").join(" · ") + (crumb ? " · " + crumb : ""));
- const sorted = es.slice().sort((a, b) => {
- const ka = (a.time ? "0" : "1") + (a.time ? invTime(a.time) : "") + String(a.order).padStart(6, "0");
- const kb = (b.time ? "0" : "1") + (b.time ? invTime(b.time) : "") + String(b.order).padStart(6, "0");
- return ka < kb ? -1 : ka > kb ? 1 : 0;
- });
- for (const e of sorted) {
- let bl = e.lines, truncated = false;
- const totalChars = bl.reduce((s, x) => s + x[1].length, 0);
- if (totalChars > PREV_CHARS) { // 只有超过上限才截断;上限内全文展示
- truncated = true;
- const shown = [];
- let chars = 0;
- for (const [lvl, txt] of bl) {
- if (chars + txt.length > PREV_CHARS) {
- if (PREV_CHARS - chars > 0) shown.push([lvl, txt.slice(0, PREV_CHARS - chars) + "…"]);
- break;
- }
- shown.push([lvl, txt]);
- chars += txt.length;
- }
- bl = shown;
- }
- for (const [lvl, txt] of bl) out.push("\t".repeat(lvl + 1) + "- " + txt);
- if (truncated) {
- const target = e.note + (e.anchor ? "#" + e.anchor : "");
- const label = e.pageLevel ? "页面级标签 · 查看全文" : "查看全文";
- out.push("\t- [[" + target + "|↗ " + label + "]]");
- }
- }
- out.push("");
- }
- out.push("");
-}
-dv.paragraph(out.join("\n"));
-
-} catch (err) {
- dv.paragraph("> ❌ tagfeed-live 执行失败:" + err.message);
- console.error("tagfeed-live:", err);
-}
-
-```
diff --git a/00index/0inbox/Heading.md b/00index/0inbox/Heading.md
deleted file mode 100644
index 0dd3953..0000000
--- a/00index/0inbox/Heading.md
+++ /dev/null
@@ -1,437 +0,0 @@
----
-tags:
- - tagfeed-live
-cssclasses:
- - tagfeed
-words:
- 2026-08-23: 2728
----
-
-# 🏷 #Heading(实时聚合页)
-
-> 本页由 DataviewJS 实时计算,笔记有变动时自动刷新,无需任何后台服务。
-
-```dataviewjs
-// ============================================================
-// tagfeed-live:Logseq 风格标签实时聚合(标准 DataviewJS 脚本)
-//
-// 用法:
-// 1. 新建一个笔记(例如 00index/标签聚合/标签名.md)
-// 2. 在笔记里插入一个 dataviewjs 代码块(三个反引号 + dataviewjs),
-// 把本脚本全部粘贴进去
-// 3. 只需要改下面「配置区」的两行:
-// - targetTag 换成你要聚合的标签(不带 #,中英文都可以;多个标签写成数组 ["a", "b"])
-// - excludedPaths 换成你想排除的文件夹
-// 4. 完成。以后任何笔记里出现/删除这个标签,本页自动刷新,
-// 不需要跑任何命令(Dataview 会在文件变动后自动重新渲染)。
-// ============================================================
-
-// ---------------- 配置区(通常只改这里) ----------------
-const targetTag = "Heading"; // 要聚合的标签(不带 #);多个标签用数组:["lyric", "歌词"]
-const excludedPaths = ["X2.Archived", "00index", "X0.Clippings", "X1.Knomo", "02DS/02copilot", ".agents", ".claude", ".copilot", ".opencode", ".smart-env", ".workbuddy", ".trash"]; // 要排除的文件夹/路径前缀(vault 相对路径)
- // 例如:["00index", "模板", "X1.Knomo"]
-// 补充说明:
-// - 所有隐藏文件/文件夹(. 开头:.agents/.claude/.copilot/.opencode/.smart-env/.workbuddy/.trash 等)与 X.Attachment 始终跳过
-// - tagfeed 系统页会自动跳过:静态聚合页(文件头含 <!-- tagfeed 标记)
-// 和实时页(frontmatter 带 tagfeed / tagfeed-live 标签),
-// 无需手动排除 00index
-// - 本脚本所在的当前笔记也会自动跳过
-// ---------------------------------------------------------
-
-try {
-const SKIP_DIRS = [".obsidian", ".git", ".trash", "X.Attachment"];
-const MARKER = "<!-- tagfeed";
-const WEEKDAYS = ["周一", "周二", "周三", "周四", "周五", "周六", "周日"];
-const PREV_CHARS = 500; // 预览字数上限:≤上限全文展示;>上限截断并附「查看全文」链接
-
-const esc = s => String(s).replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
-const TAG_LIST = Array.isArray(targetTag) ? targetTag : [targetTag];
-const TAG_RE = new RegExp("(?<![\\w\\u4e00-\\u9fff#])#(?:" + TAG_LIST.map(esc).join("|") + ")(?![\\w\\u4e00-\\u9fff/\\-])");
-const ANY_TAG = /(?<![\w#])#([\w\u4e00-\u9fff][\w\u4e00-\u9fff/\-]*)/g;
-const LIST_RE = /^(\s*)([-*+]|\d+[.)])(?:\s+(.*))?$/;
-const HEAD_RE = /^(#{1,6})\s+(.*)$/;
-const DATEH_RE = /\[\[(\d{4})-(\d{2})-(\d{2})\]\]/;
-const TIME_RE = /(\d{1,2}):(\d{2})(?::(\d{2}))?/;
-const FENCE_RE = /^\s*(`{3,}|~~~)/;
-const indentOf = ln => { // tab 按 2 空格计(与 Python 版一致)
- const expanded = ln.replace(/\t/g, " ");
- return expanded.length - expanded.replace(/^ +/, "").length;
-};
-
-function cleanMatch(ln) {
- let s = ln.replace(/`[^`\n]*`/g, "");
- s = s.replace(/%%[\s\S]*?%%/g, "");
- s = s.replace(/<!--[\s\S]*?-->/g, "");
- return s;
-}
-function vdate(y, m, d) {
- const dt = new Date(y, m - 1, d);
- return (dt.getFullYear() === y && dt.getMonth() === m - 1 && dt.getDate() === d) ? dt : null;
-}
-function dateFromName(name) { // 文件名含 YYYY-MM-DD,或日记命名 26.0805 -> 2026-08-05
- const m1 = name.match(/(\d{4})-(\d{2})-(\d{2})/);
- if (m1) {
- const d1 = vdate(+m1[1], +m1[2], +m1[3]);
- if (d1) return d1;
- }
- const m = name.match(/^(\d{2})\.(\d{2})(\d{2})(?:\D|$)/);
- return m ? vdate(2000 + +m[1], +m[2], +m[3]) : null;
-}
-function parseFrontmatter(text) {
- const lines = text.split("\n");
- if (!lines.length || lines[0].trim() !== "---") return { tags: new Set(), date: null, bodyStart: 0 };
- const n = Math.min(lines.length, 200);
- for (let i = 1; i < n; i++) {
- const t = lines[i].trim();
- if (t === "---" || t === "...") {
- const block = lines.slice(1, i).join("\n");
- const tags = new Set();
- const im = block.match(/^tags:\s*\[(.*?)\]/m);
- if (im) {
- for (const x0 of im[1].split(",")) {
- const x = x0.trim().replace(/^["']|["']$/g, "").replace(/^#/, "").trim();
- if (x) tags.add(x);
- }
- } else {
- const bm = block.match(/^tags:\s*\n((?:[ \t]+-[ \t]+.*\n?)+)/m);
- if (bm) for (const ln of bm[1].split("\n")) {
- const m2 = ln.match(/^\s*-\s*["']?([\w\u4e00-\u9fff/\-]+)["']?\s*$/);
- if (m2) tags.add(m2[1]);
- }
- }
- let fdate = null;
- for (const key of ["date", "created", "published"]) {
- const dm = block.match(new RegExp("^" + key + ":\\s*[\"']?(\\d{4})-(\\d{2})-(\\d{2})", "m"));
- if (dm) { fdate = vdate(+dm[1], +dm[2], +dm[3]); break; }
- }
- return { tags, date: fdate, bodyStart: i + 1 };
- }
- }
- return { tags: new Set(), date: null, bodyStart: 0 };
-}
-const stripTag = s => String(s).replace(TAG_RE, "").replace(/[ \t]{2,}/g, " ").trim();
-
-function scanText(text, note, relpath, fileDate, mtimeDate) {
- const fm = parseFrontmatter(text);
- if (fm.tags.has("tagfeed") || fm.tags.has("tagfeed-live")) return []; // 跳过 tagfeed 系统页(静态聚合页/实时页)
- const lines = text.split("\n");
- const entries = [], seen = new Set();
- let order = 0, inFence = false, inPct = false, inHtml = false;
- let curHead = null, curHeadRaw = null, curDateHead = null;
-
- const entryDate = () => curDateHead || fileDate || fm.date || mtimeDate;
- const anchorOf = () => (curDateHead ? null : curHeadRaw);
- const breadcrumb = () => {
- if (curHead && !DATEH_RE.test(curHead)) {
- const t = curHead.replace(ANY_TAG, "").replace(/\s+/g, " ").trim();
- if (t) return t;
- }
- return null;
- };
- function addBlock(keyIdx, block) {
- const key = relpath + "|" + keyIdx;
- if (seen.has(key) || !block.length) return;
- seen.add(key);
- const d = entryDate();
- if (!d) return;
- let t = null;
- const tm = block[0][1].slice(0, 30).match(TIME_RE);
- if (tm) t = [+tm[1], +tm[2], +(tm[3] || 0)];
- order++;
- entries.push({ date: d, time: t, order, relpath, note,
- breadcrumb: breadcrumb(), lines: block,
- pageLevel: false, notes: [note], anchor: anchorOf() });
- }
- function collectChildren(i, parentIndent) {
- const kids = [], raw = [];
- let j = i + 1;
- while (j < lines.length) {
- const ln = lines[j];
- if (ln.trim() === "") {
- let k = j + 1;
- while (k < lines.length && lines[k].trim() === "") k++;
- if (k < lines.length && LIST_RE.test(lines[k]) && indentOf(lines[k]) > parentIndent) { j = k; continue; }
- break;
- }
- const mi = ln.match(LIST_RE);
- if (mi && indentOf(ln) > parentIndent) {
- raw.push([indentOf(ln), (mi[3] || "").replace(/\s+$/, "")]);
- j++;
- } else break;
- }
- for (const [ind, txt] of raw) kids.push([Math.floor((ind - parentIndent) / 2), txt]);
- return kids;
- }
-
- for (let i = fm.bodyStart; i < lines.length; i++) {
- const ln = lines[i];
- if (FENCE_RE.test(ln)) { inFence = !inFence; continue; }
- if (inFence) continue;
- if (inPct) { if (ln.includes("%%")) inPct = false; continue; }
- if (inHtml) { if (ln.includes("-->")) inHtml = false; continue; }
- const stripped = ln.trim();
- if (stripped.startsWith("%%") && !stripped.slice(2).includes("%%")) { inPct = true; continue; }
- if (stripped.startsWith("<!--") && !stripped.includes("-->")) { inHtml = true; continue; }
-
- const hm = ln.match(HEAD_RE);
- if (hm) {
- const htext = hm[2];
- const dh = htext.match(DATEH_RE);
- if (dh) { curDateHead = vdate(+dh[1], +dh[2], +dh[3]); curHead = null; curHeadRaw = null; }
- else { curHead = htext; curHeadRaw = htext.trim(); }
- if (TAG_RE.test(cleanMatch(htext))) {
- const sec = [[0, "**" + stripTag(htext) + "**"]];
- let j = i + 1;
- const level = hm[1].length;
- let count = 0;
- while (j < lines.length && count < 300) {
- const sl = lines[j];
- const shm = sl.match(HEAD_RE);
- if (shm && shm[1].length <= level) break;
- if (FENCE_RE.test(sl)) break;
- if (sl.trim()) {
- const smi = sl.match(LIST_RE);
- if (smi) sec.push([1 + Math.floor(indentOf(sl) / 2), (smi[3] || "").replace(/\s+$/, "")]);
- else sec.push([1, sl.trim()]);
- count++;
- }
- j++;
- }
- addBlock(i, sec);
- }
- continue;
- }
-
- const clean = cleanMatch(ln);
- if (!TAG_RE.test(clean)) continue;
-
- const mi = ln.match(LIST_RE);
- if (mi) {
- const ind = indentOf(ln);
- const itemText = mi[3] || "";
- const cleaned = stripTag(itemText);
- const kids = collectChildren(i, ind);
- addBlock(i, cleaned ? [[0, cleaned], ...kids] : [[0, itemText.trim()], ...kids]);
- continue;
- }
-
- // 独立标签行:向下吸收 list items,或向上归属到紧邻的块
- if (ln.replace(ANY_TAG, "").trim() === "" || ln.replace(ANY_TAG, "").trim() === "%%") {
- let sec = [];
- let j = i + 1;
- while (j < lines.length) {
- const mj = lines[j].match(LIST_RE);
- if (mj) { sec.push([indentOf(lines[j]), (mj[3] || "").replace(/\s+$/, "")]); j++; }
- else break;
- }
- let keyIdx = i;
- if (!sec.length) {
- const up = [];
- let k = i - 1, bulletIdx = null;
- while (k >= fm.bodyStart) {
- const bk = lines[k];
- if (bk.trim() === "" || HEAD_RE.test(bk) || FENCE_RE.test(bk)) break;
- up.push(k);
- if (LIST_RE.test(bk)) { bulletIdx = k; break; }
- k--;
- }
- if (bulletIdx !== null) {
- const inds = [];
- for (let bl = bulletIdx; bl < i; bl++) {
- const mb = lines[bl].match(LIST_RE);
- if (mb) { inds.push(indentOf(lines[bl])); sec.push([indentOf(lines[bl]), (mb[3] || "").replace(/\s+$/, "")]); }
- else sec.push([(inds.length ? inds[inds.length - 1] + 2 : 0), lines[bl].trim()]);
- }
- keyIdx = bulletIdx;
- } else if (up.length) {
- sec = up.slice().reverse().map(k2 => [0, lines[k2].trim()]);
- } else {
- // 兜底:标签行紧挨在某个标题下方(中间只有空行)→ 捕获该标题下的整个小节
- let h = i - 1;
- while (h >= fm.bodyStart && lines[h].trim() === "") h--;
- const hmk = h >= fm.bodyStart ? lines[h].match(HEAD_RE) : null;
- if (hmk) {
- const level = hmk[1].length;
- const title = hmk[2].replace(ANY_TAG, "").replace(/\s+/g, " ").trim();
- const sec2 = title ? [[0, "**" + title + "**"]] : [];
- let j2 = i + 1, count2 = 0;
- while (j2 < lines.length && count2 < 300) {
- const sl = lines[j2];
- const shm = sl.match(HEAD_RE);
- if (shm && shm[1].length <= level) break;
- if (FENCE_RE.test(sl)) break;
- if (sl.trim()) {
- const smi = sl.match(LIST_RE);
- if (smi) sec2.push([1 + Math.floor(indentOf(sl) / 2), (smi[3] || "").replace(/\s+$/, "")]);
- else sec2.push([1, sl.trim().replace(/^>\s*/, "")]);
- count2++;
- }
- j2++;
- }
- if (sec2.length) sec = sec2;
- }
- }
- }
- if (sec.length) {
- const mi0 = Math.min(...sec.map(x => x[0]));
- addBlock(keyIdx, sec.map(([ind, t]) => [Math.floor((ind - mi0) / 2), t]));
- }
- continue;
- }
-
- // 普通段落带标签:吸收到空行为止
- const para = [stripTag(clean)];
- let j = i + 1;
- while (j < lines.length) {
- const nl = lines[j];
- if (nl.trim() === "" || HEAD_RE.test(nl) || LIST_RE.test(nl) || FENCE_RE.test(nl)) break;
- para.push(nl.trim());
- j++;
- }
- addBlock(i, [[0, para.filter(p => p).join(" ")]]);
- }
-
- // frontmatter 标签 → 页面级条目(附正文开头预览)
- if (TAG_LIST.some(t => fm.tags.has(t))) {
- const d = fm.date || fileDate || mtimeDate;
- if (d) {
- order++;
- // 收集正文文本至 PREV_CHARS:超过上限的长文在渲染时截断并附「查看全文」链接
- const prev = [];
- let prevTotal = 0, prevCount = 0;
- for (let k = fm.bodyStart; k < lines.length && prevCount < 200; k++) {
- const s = lines[k].trim();
- if (!s || s.startsWith("#") || s === "---") continue;
- const clean = s.replace(ANY_TAG, "");
- if (!clean) continue;
- prev.push(clean);
- prevTotal += clean.length;
- prevCount++;
- if (prevTotal > PREV_CHARS) break; // 超过上限:已足够判断,停止收集
- }
- const preview = prev.join(" ");
- entries.push({ date: d, time: null, order, relpath, note, breadcrumb: null,
- lines: preview ? [[0, preview]] : [],
- pageLevel: true, notes: [note], anchor: null });
- }
- }
- return entries;
-}
-
-// ---------------- 扫描整个 vault ----------------
-const currentPath = (dv.current() && dv.current().file) ? dv.current().file.path : null;
-let all = [];
-for (const f of app.vault.getMarkdownFiles()) {
- if (f.path === currentPath || f.path.endsWith(".excalidraw.md")) continue;
- if (SKIP_DIRS.some(d => f.path.startsWith(d + "/"))) continue;
- if (f.path.split("/").some(seg => seg.startsWith("."))) continue; // 排除所有隐藏文件/文件夹(.开头)
- if (excludedPaths.some(p => f.path === p || f.path.startsWith(p.endsWith("/") ? p : p + "/"))) continue;
- let text;
- try { text = await app.vault.cachedRead(f); } catch (e) { continue; }
- if (text.slice(0, 1000).includes(MARKER)) continue; // 跳过 tagfeed 生成的静态页
- all = all.concat(scanText(text, f.basename, f.path, dateFromName(f.basename), new Date(f.stat.mtime)));
-}
-
-// ---------------- 去重:同日期+同内容合并,来源并排 ----------------
-const timeCmp = (a, b) => (a[0] - b[0]) || (a[1] - b[1]) || (a[2] - b[2]);
-const mergedMap = new Map();
-for (const e of all) {
- const fp = e.date.getTime() + "|" + e.lines.map(x => x[0] + ":" + x[1]).join("\n");
- const m = mergedMap.get(fp);
- if (m) {
- if (!m.notes.includes(e.note)) m.notes.push(e.note);
- if (e.time && (!m.time || timeCmp(e.time, m.time) < 0)) m.time = e.time;
- // 若先扫到的来源没有面包屑/锚点,而后面的有,则采用后者(与扫描顺序无关,结果确定)
- if (!m.breadcrumb && e.breadcrumb) {
- m.breadcrumb = e.breadcrumb;
- m.anchor = e.anchor;
- m.relpath = e.relpath;
- m.note = e.note;
- }
- } else mergedMap.set(fp, e);
-}
-const entries = [...mergedMap.values()];
-
-// ---------------- 渲染:按日期倒序 ----------------
-const byDate = new Map();
-for (const e of entries) {
- const k = e.date.getTime();
- if (!byDate.has(k)) byDate.set(k, []);
- byDate.get(k).push(e);
-}
-const dates = [...byDate.keys()].sort((a, b) => b - a);
-const noteSet = new Set();
-for (const e of entries) for (const n of e.notes) noteSet.add(n);
-
-const out = [];
-out.push("> **" + entries.length + "** 个内容块 | **" + noteSet.size + "** 篇笔记 | 按日期倒序 | 随笔记变动自动刷新");
-
-const pad2 = x => String(x).padStart(2, "0");
-const fmtDate = d => d.getFullYear() + "-" + pad2(d.getMonth() + 1) + "-" + pad2(d.getDate());
-const invTime = t => t.map(x => pad2(99 - x)).join("");
-
-for (const dk of dates) {
- const d = new Date(dk);
- out.push("## 📅 " + fmtDate(d) + " · " + WEEKDAYS[d.getDay() === 0 ? 6 : d.getDay() - 1]);
- out.push("");
- const group = byDate.get(dk);
-
- const sub = new Map(); // (笔记+面包屑) -> entries
- for (const e of group) {
- const key = e.relpath + "|" + (e.breadcrumb || "");
- if (!sub.has(key)) sub.set(key, []);
- sub.get(key).push(e);
- }
- const subKey = ([key, es]) => {
- const times = es.filter(e => e.time).map(e => e.time);
- const best = times.length ? times.reduce((a, b) => (timeCmp(a, b) > 0 ? a : b)) : null;
- return (best ? "0" : "1") + (best ? invTime(best) : "000000") + key;
- };
-
- for (const [key, es] of [...sub.entries()].sort((a, b) => (subKey(a) < subKey(b) ? -1 : subKey(a) > subKey(b) ? 1 : 0))) {
- const noteList = [];
- for (const e of es) for (const n of e.notes) if (!noteList.includes(n)) noteList.push(n);
- noteList.sort();
- const crumb = es.map(e => e.breadcrumb).find(b => b) || null;
- out.push("- 🔗 " + noteList.map(n => "[[" + n + "]]").join(" · ") + (crumb ? " · " + crumb : ""));
- const sorted = es.slice().sort((a, b) => {
- const ka = (a.time ? "0" : "1") + (a.time ? invTime(a.time) : "") + String(a.order).padStart(6, "0");
- const kb = (b.time ? "0" : "1") + (b.time ? invTime(b.time) : "") + String(b.order).padStart(6, "0");
- return ka < kb ? -1 : ka > kb ? 1 : 0;
- });
- for (const e of sorted) {
- let bl = e.lines, truncated = false;
- const totalChars = bl.reduce((s, x) => s + x[1].length, 0);
- if (totalChars > PREV_CHARS) { // 只有超过上限才截断;上限内全文展示
- truncated = true;
- const shown = [];
- let chars = 0;
- for (const [lvl, txt] of bl) {
- if (chars + txt.length > PREV_CHARS) {
- if (PREV_CHARS - chars > 0) shown.push([lvl, txt.slice(0, PREV_CHARS - chars) + "…"]);
- break;
- }
- shown.push([lvl, txt]);
- chars += txt.length;
- }
- bl = shown;
- }
- for (const [lvl, txt] of bl) out.push("\t".repeat(lvl + 1) + "- " + txt);
- if (truncated) {
- const target = e.note + (e.anchor ? "#" + e.anchor : "");
- const label = e.pageLevel ? "页面级标签 · 查看全文" : "查看全文";
- out.push("\t- [[" + target + "|↗ " + label + "]]");
- }
- }
- out.push("");
- }
- out.push("");
-}
-dv.paragraph(out.join("\n"));
-
-} catch (err) {
- dv.paragraph("> ❌ tagfeed-live 执行失败:" + err.message);
- console.error("tagfeed-live:", err);
-}
-
-```
diff --git a/00index/0inbox/Import.md b/00index/0inbox/Import.md
deleted file mode 100644
index 01adb65..0000000
--- a/00index/0inbox/Import.md
+++ /dev/null
@@ -1,437 +0,0 @@
----
-tags:
- - tagfeed-live
-cssclasses:
- - tagfeed
-words:
- 2026-08-23: 2728
----
-
-# 🏷 #Import(实时聚合页)
-
-> 本页由 DataviewJS 实时计算,笔记有变动时自动刷新,无需任何后台服务。
-
-```dataviewjs
-// ============================================================
-// tagfeed-live:Logseq 风格标签实时聚合(标准 DataviewJS 脚本)
-//
-// 用法:
-// 1. 新建一个笔记(例如 00index/标签聚合/标签名.md)
-// 2. 在笔记里插入一个 dataviewjs 代码块(三个反引号 + dataviewjs),
-// 把本脚本全部粘贴进去
-// 3. 只需要改下面「配置区」的两行:
-// - targetTag 换成你要聚合的标签(不带 #,中英文都可以;多个标签写成数组 ["a", "b"])
-// - excludedPaths 换成你想排除的文件夹
-// 4. 完成。以后任何笔记里出现/删除这个标签,本页自动刷新,
-// 不需要跑任何命令(Dataview 会在文件变动后自动重新渲染)。
-// ============================================================
-
-// ---------------- 配置区(通常只改这里) ----------------
-const targetTag = "Import"; // 要聚合的标签(不带 #);多个标签用数组:["lyric", "歌词"]
-const excludedPaths = ["X2.Archived", "00index", "X0.Clippings", "X1.Knomo", "02DS/02copilot", ".agents", ".claude", ".copilot", ".opencode", ".smart-env", ".workbuddy", ".trash"]; // 要排除的文件夹/路径前缀(vault 相对路径)
- // 例如:["00index", "模板", "X1.Knomo"]
-// 补充说明:
-// - 所有隐藏文件/文件夹(. 开头:.agents/.claude/.copilot/.opencode/.smart-env/.workbuddy/.trash 等)与 X.Attachment 始终跳过
-// - tagfeed 系统页会自动跳过:静态聚合页(文件头含 <!-- tagfeed 标记)
-// 和实时页(frontmatter 带 tagfeed / tagfeed-live 标签),
-// 无需手动排除 00index
-// - 本脚本所在的当前笔记也会自动跳过
-// ---------------------------------------------------------
-
-try {
-const SKIP_DIRS = [".obsidian", ".git", ".trash", "X.Attachment"];
-const MARKER = "<!-- tagfeed";
-const WEEKDAYS = ["周一", "周二", "周三", "周四", "周五", "周六", "周日"];
-const PREV_CHARS = 500; // 预览字数上限:≤上限全文展示;>上限截断并附「查看全文」链接
-
-const esc = s => String(s).replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
-const TAG_LIST = Array.isArray(targetTag) ? targetTag : [targetTag];
-const TAG_RE = new RegExp("(?<![\\w\\u4e00-\\u9fff#])#(?:" + TAG_LIST.map(esc).join("|") + ")(?![\\w\\u4e00-\\u9fff/\\-])");
-const ANY_TAG = /(?<![\w#])#([\w\u4e00-\u9fff][\w\u4e00-\u9fff/\-]*)/g;
-const LIST_RE = /^(\s*)([-*+]|\d+[.)])(?:\s+(.*))?$/;
-const HEAD_RE = /^(#{1,6})\s+(.*)$/;
-const DATEH_RE = /\[\[(\d{4})-(\d{2})-(\d{2})\]\]/;
-const TIME_RE = /(\d{1,2}):(\d{2})(?::(\d{2}))?/;
-const FENCE_RE = /^\s*(`{3,}|~~~)/;
-const indentOf = ln => { // tab 按 2 空格计(与 Python 版一致)
- const expanded = ln.replace(/\t/g, " ");
- return expanded.length - expanded.replace(/^ +/, "").length;
-};
-
-function cleanMatch(ln) {
- let s = ln.replace(/`[^`\n]*`/g, "");
- s = s.replace(/%%[\s\S]*?%%/g, "");
- s = s.replace(/<!--[\s\S]*?-->/g, "");
- return s;
-}
-function vdate(y, m, d) {
- const dt = new Date(y, m - 1, d);
- return (dt.getFullYear() === y && dt.getMonth() === m - 1 && dt.getDate() === d) ? dt : null;
-}
-function dateFromName(name) { // 文件名含 YYYY-MM-DD,或日记命名 26.0805 -> 2026-08-05
- const m1 = name.match(/(\d{4})-(\d{2})-(\d{2})/);
- if (m1) {
- const d1 = vdate(+m1[1], +m1[2], +m1[3]);
- if (d1) return d1;
- }
- const m = name.match(/^(\d{2})\.(\d{2})(\d{2})(?:\D|$)/);
- return m ? vdate(2000 + +m[1], +m[2], +m[3]) : null;
-}
-function parseFrontmatter(text) {
- const lines = text.split("\n");
- if (!lines.length || lines[0].trim() !== "---") return { tags: new Set(), date: null, bodyStart: 0 };
- const n = Math.min(lines.length, 200);
- for (let i = 1; i < n; i++) {
- const t = lines[i].trim();
- if (t === "---" || t === "...") {
- const block = lines.slice(1, i).join("\n");
- const tags = new Set();
- const im = block.match(/^tags:\s*\[(.*?)\]/m);
- if (im) {
- for (const x0 of im[1].split(",")) {
- const x = x0.trim().replace(/^["']|["']$/g, "").replace(/^#/, "").trim();
- if (x) tags.add(x);
- }
- } else {
- const bm = block.match(/^tags:\s*\n((?:[ \t]+-[ \t]+.*\n?)+)/m);
- if (bm) for (const ln of bm[1].split("\n")) {
- const m2 = ln.match(/^\s*-\s*["']?([\w\u4e00-\u9fff/\-]+)["']?\s*$/);
- if (m2) tags.add(m2[1]);
- }
- }
- let fdate = null;
- for (const key of ["date", "created", "published"]) {
- const dm = block.match(new RegExp("^" + key + ":\\s*[\"']?(\\d{4})-(\\d{2})-(\\d{2})", "m"));
- if (dm) { fdate = vdate(+dm[1], +dm[2], +dm[3]); break; }
- }
- return { tags, date: fdate, bodyStart: i + 1 };
- }
- }
- return { tags: new Set(), date: null, bodyStart: 0 };
-}
-const stripTag = s => String(s).replace(TAG_RE, "").replace(/[ \t]{2,}/g, " ").trim();
-
-function scanText(text, note, relpath, fileDate, mtimeDate) {
- const fm = parseFrontmatter(text);
- if (fm.tags.has("tagfeed") || fm.tags.has("tagfeed-live")) return []; // 跳过 tagfeed 系统页(静态聚合页/实时页)
- const lines = text.split("\n");
- const entries = [], seen = new Set();
- let order = 0, inFence = false, inPct = false, inHtml = false;
- let curHead = null, curHeadRaw = null, curDateHead = null;
-
- const entryDate = () => curDateHead || fileDate || fm.date || mtimeDate;
- const anchorOf = () => (curDateHead ? null : curHeadRaw);
- const breadcrumb = () => {
- if (curHead && !DATEH_RE.test(curHead)) {
- const t = curHead.replace(ANY_TAG, "").replace(/\s+/g, " ").trim();
- if (t) return t;
- }
- return null;
- };
- function addBlock(keyIdx, block) {
- const key = relpath + "|" + keyIdx;
- if (seen.has(key) || !block.length) return;
- seen.add(key);
- const d = entryDate();
- if (!d) return;
- let t = null;
- const tm = block[0][1].slice(0, 30).match(TIME_RE);
- if (tm) t = [+tm[1], +tm[2], +(tm[3] || 0)];
- order++;
- entries.push({ date: d, time: t, order, relpath, note,
- breadcrumb: breadcrumb(), lines: block,
- pageLevel: false, notes: [note], anchor: anchorOf() });
- }
- function collectChildren(i, parentIndent) {
- const kids = [], raw = [];
- let j = i + 1;
- while (j < lines.length) {
- const ln = lines[j];
- if (ln.trim() === "") {
- let k = j + 1;
- while (k < lines.length && lines[k].trim() === "") k++;
- if (k < lines.length && LIST_RE.test(lines[k]) && indentOf(lines[k]) > parentIndent) { j = k; continue; }
- break;
- }
- const mi = ln.match(LIST_RE);
- if (mi && indentOf(ln) > parentIndent) {
- raw.push([indentOf(ln), (mi[3] || "").replace(/\s+$/, "")]);
- j++;
- } else break;
- }
- for (const [ind, txt] of raw) kids.push([Math.floor((ind - parentIndent) / 2), txt]);
- return kids;
- }
-
- for (let i = fm.bodyStart; i < lines.length; i++) {
- const ln = lines[i];
- if (FENCE_RE.test(ln)) { inFence = !inFence; continue; }
- if (inFence) continue;
- if (inPct) { if (ln.includes("%%")) inPct = false; continue; }
- if (inHtml) { if (ln.includes("-->")) inHtml = false; continue; }
- const stripped = ln.trim();
- if (stripped.startsWith("%%") && !stripped.slice(2).includes("%%")) { inPct = true; continue; }
- if (stripped.startsWith("<!--") && !stripped.includes("-->")) { inHtml = true; continue; }
-
- const hm = ln.match(HEAD_RE);
- if (hm) {
- const htext = hm[2];
- const dh = htext.match(DATEH_RE);
- if (dh) { curDateHead = vdate(+dh[1], +dh[2], +dh[3]); curHead = null; curHeadRaw = null; }
- else { curHead = htext; curHeadRaw = htext.trim(); }
- if (TAG_RE.test(cleanMatch(htext))) {
- const sec = [[0, "**" + stripTag(htext) + "**"]];
- let j = i + 1;
- const level = hm[1].length;
- let count = 0;
- while (j < lines.length && count < 300) {
- const sl = lines[j];
- const shm = sl.match(HEAD_RE);
- if (shm && shm[1].length <= level) break;
- if (FENCE_RE.test(sl)) break;
- if (sl.trim()) {
- const smi = sl.match(LIST_RE);
- if (smi) sec.push([1 + Math.floor(indentOf(sl) / 2), (smi[3] || "").replace(/\s+$/, "")]);
- else sec.push([1, sl.trim()]);
- count++;
- }
- j++;
- }
- addBlock(i, sec);
- }
- continue;
- }
-
- const clean = cleanMatch(ln);
- if (!TAG_RE.test(clean)) continue;
-
- const mi = ln.match(LIST_RE);
- if (mi) {
- const ind = indentOf(ln);
- const itemText = mi[3] || "";
- const cleaned = stripTag(itemText);
- const kids = collectChildren(i, ind);
- addBlock(i, cleaned ? [[0, cleaned], ...kids] : [[0, itemText.trim()], ...kids]);
- continue;
- }
-
- // 独立标签行:向下吸收 list items,或向上归属到紧邻的块
- if (ln.replace(ANY_TAG, "").trim() === "" || ln.replace(ANY_TAG, "").trim() === "%%") {
- let sec = [];
- let j = i + 1;
- while (j < lines.length) {
- const mj = lines[j].match(LIST_RE);
- if (mj) { sec.push([indentOf(lines[j]), (mj[3] || "").replace(/\s+$/, "")]); j++; }
- else break;
- }
- let keyIdx = i;
- if (!sec.length) {
- const up = [];
- let k = i - 1, bulletIdx = null;
- while (k >= fm.bodyStart) {
- const bk = lines[k];
- if (bk.trim() === "" || HEAD_RE.test(bk) || FENCE_RE.test(bk)) break;
- up.push(k);
- if (LIST_RE.test(bk)) { bulletIdx = k; break; }
- k--;
- }
- if (bulletIdx !== null) {
- const inds = [];
- for (let bl = bulletIdx; bl < i; bl++) {
- const mb = lines[bl].match(LIST_RE);
- if (mb) { inds.push(indentOf(lines[bl])); sec.push([indentOf(lines[bl]), (mb[3] || "").replace(/\s+$/, "")]); }
- else sec.push([(inds.length ? inds[inds.length - 1] + 2 : 0), lines[bl].trim()]);
- }
- keyIdx = bulletIdx;
- } else if (up.length) {
- sec = up.slice().reverse().map(k2 => [0, lines[k2].trim()]);
- } else {
- // 兜底:标签行紧挨在某个标题下方(中间只有空行)→ 捕获该标题下的整个小节
- let h = i - 1;
- while (h >= fm.bodyStart && lines[h].trim() === "") h--;
- const hmk = h >= fm.bodyStart ? lines[h].match(HEAD_RE) : null;
- if (hmk) {
- const level = hmk[1].length;
- const title = hmk[2].replace(ANY_TAG, "").replace(/\s+/g, " ").trim();
- const sec2 = title ? [[0, "**" + title + "**"]] : [];
- let j2 = i + 1, count2 = 0;
- while (j2 < lines.length && count2 < 300) {
- const sl = lines[j2];
- const shm = sl.match(HEAD_RE);
- if (shm && shm[1].length <= level) break;
- if (FENCE_RE.test(sl)) break;
- if (sl.trim()) {
- const smi = sl.match(LIST_RE);
- if (smi) sec2.push([1 + Math.floor(indentOf(sl) / 2), (smi[3] || "").replace(/\s+$/, "")]);
- else sec2.push([1, sl.trim().replace(/^>\s*/, "")]);
- count2++;
- }
- j2++;
- }
- if (sec2.length) sec = sec2;
- }
- }
- }
- if (sec.length) {
- const mi0 = Math.min(...sec.map(x => x[0]));
- addBlock(keyIdx, sec.map(([ind, t]) => [Math.floor((ind - mi0) / 2), t]));
- }
- continue;
- }
-
- // 普通段落带标签:吸收到空行为止
- const para = [stripTag(clean)];
- let j = i + 1;
- while (j < lines.length) {
- const nl = lines[j];
- if (nl.trim() === "" || HEAD_RE.test(nl) || LIST_RE.test(nl) || FENCE_RE.test(nl)) break;
- para.push(nl.trim());
- j++;
- }
- addBlock(i, [[0, para.filter(p => p).join(" ")]]);
- }
-
- // frontmatter 标签 → 页面级条目(附正文开头预览)
- if (TAG_LIST.some(t => fm.tags.has(t))) {
- const d = fm.date || fileDate || mtimeDate;
- if (d) {
- order++;
- // 收集正文文本至 PREV_CHARS:超过上限的长文在渲染时截断并附「查看全文」链接
- const prev = [];
- let prevTotal = 0, prevCount = 0;
- for (let k = fm.bodyStart; k < lines.length && prevCount < 200; k++) {
- const s = lines[k].trim();
- if (!s || s.startsWith("#") || s === "---") continue;
- const clean = s.replace(ANY_TAG, "");
- if (!clean) continue;
- prev.push(clean);
- prevTotal += clean.length;
- prevCount++;
- if (prevTotal > PREV_CHARS) break; // 超过上限:已足够判断,停止收集
- }
- const preview = prev.join(" ");
- entries.push({ date: d, time: null, order, relpath, note, breadcrumb: null,
- lines: preview ? [[0, preview]] : [],
- pageLevel: true, notes: [note], anchor: null });
- }
- }
- return entries;
-}
-
-// ---------------- 扫描整个 vault ----------------
-const currentPath = (dv.current() && dv.current().file) ? dv.current().file.path : null;
-let all = [];
-for (const f of app.vault.getMarkdownFiles()) {
- if (f.path === currentPath || f.path.endsWith(".excalidraw.md")) continue;
- if (SKIP_DIRS.some(d => f.path.startsWith(d + "/"))) continue;
- if (f.path.split("/").some(seg => seg.startsWith("."))) continue; // 排除所有隐藏文件/文件夹(.开头)
- if (excludedPaths.some(p => f.path === p || f.path.startsWith(p.endsWith("/") ? p : p + "/"))) continue;
- let text;
- try { text = await app.vault.cachedRead(f); } catch (e) { continue; }
- if (text.slice(0, 1000).includes(MARKER)) continue; // 跳过 tagfeed 生成的静态页
- all = all.concat(scanText(text, f.basename, f.path, dateFromName(f.basename), new Date(f.stat.mtime)));
-}
-
-// ---------------- 去重:同日期+同内容合并,来源并排 ----------------
-const timeCmp = (a, b) => (a[0] - b[0]) || (a[1] - b[1]) || (a[2] - b[2]);
-const mergedMap = new Map();
-for (const e of all) {
- const fp = e.date.getTime() + "|" + e.lines.map(x => x[0] + ":" + x[1]).join("\n");
- const m = mergedMap.get(fp);
- if (m) {
- if (!m.notes.includes(e.note)) m.notes.push(e.note);
- if (e.time && (!m.time || timeCmp(e.time, m.time) < 0)) m.time = e.time;
- // 若先扫到的来源没有面包屑/锚点,而后面的有,则采用后者(与扫描顺序无关,结果确定)
- if (!m.breadcrumb && e.breadcrumb) {
- m.breadcrumb = e.breadcrumb;
- m.anchor = e.anchor;
- m.relpath = e.relpath;
- m.note = e.note;
- }
- } else mergedMap.set(fp, e);
-}
-const entries = [...mergedMap.values()];
-
-// ---------------- 渲染:按日期倒序 ----------------
-const byDate = new Map();
-for (const e of entries) {
- const k = e.date.getTime();
- if (!byDate.has(k)) byDate.set(k, []);
- byDate.get(k).push(e);
-}
-const dates = [...byDate.keys()].sort((a, b) => b - a);
-const noteSet = new Set();
-for (const e of entries) for (const n of e.notes) noteSet.add(n);
-
-const out = [];
-out.push("> **" + entries.length + "** 个内容块 | **" + noteSet.size + "** 篇笔记 | 按日期倒序 | 随笔记变动自动刷新");
-
-const pad2 = x => String(x).padStart(2, "0");
-const fmtDate = d => d.getFullYear() + "-" + pad2(d.getMonth() + 1) + "-" + pad2(d.getDate());
-const invTime = t => t.map(x => pad2(99 - x)).join("");
-
-for (const dk of dates) {
- const d = new Date(dk);
- out.push("## 📅 " + fmtDate(d) + " · " + WEEKDAYS[d.getDay() === 0 ? 6 : d.getDay() - 1]);
- out.push("");
- const group = byDate.get(dk);
-
- const sub = new Map(); // (笔记+面包屑) -> entries
- for (const e of group) {
- const key = e.relpath + "|" + (e.breadcrumb || "");
- if (!sub.has(key)) sub.set(key, []);
- sub.get(key).push(e);
- }
- const subKey = ([key, es]) => {
- const times = es.filter(e => e.time).map(e => e.time);
- const best = times.length ? times.reduce((a, b) => (timeCmp(a, b) > 0 ? a : b)) : null;
- return (best ? "0" : "1") + (best ? invTime(best) : "000000") + key;
- };
-
- for (const [key, es] of [...sub.entries()].sort((a, b) => (subKey(a) < subKey(b) ? -1 : subKey(a) > subKey(b) ? 1 : 0))) {
- const noteList = [];
- for (const e of es) for (const n of e.notes) if (!noteList.includes(n)) noteList.push(n);
- noteList.sort();
- const crumb = es.map(e => e.breadcrumb).find(b => b) || null;
- out.push("- 🔗 " + noteList.map(n => "[[" + n + "]]").join(" · ") + (crumb ? " · " + crumb : ""));
- const sorted = es.slice().sort((a, b) => {
- const ka = (a.time ? "0" : "1") + (a.time ? invTime(a.time) : "") + String(a.order).padStart(6, "0");
- const kb = (b.time ? "0" : "1") + (b.time ? invTime(b.time) : "") + String(b.order).padStart(6, "0");
- return ka < kb ? -1 : ka > kb ? 1 : 0;
- });
- for (const e of sorted) {
- let bl = e.lines, truncated = false;
- const totalChars = bl.reduce((s, x) => s + x[1].length, 0);
- if (totalChars > PREV_CHARS) { // 只有超过上限才截断;上限内全文展示
- truncated = true;
- const shown = [];
- let chars = 0;
- for (const [lvl, txt] of bl) {
- if (chars + txt.length > PREV_CHARS) {
- if (PREV_CHARS - chars > 0) shown.push([lvl, txt.slice(0, PREV_CHARS - chars) + "…"]);
- break;
- }
- shown.push([lvl, txt]);
- chars += txt.length;
- }
- bl = shown;
- }
- for (const [lvl, txt] of bl) out.push("\t".repeat(lvl + 1) + "- " + txt);
- if (truncated) {
- const target = e.note + (e.anchor ? "#" + e.anchor : "");
- const label = e.pageLevel ? "页面级标签 · 查看全文" : "查看全文";
- out.push("\t- [[" + target + "|↗ " + label + "]]");
- }
- }
- out.push("");
- }
- out.push("");
-}
-dv.paragraph(out.join("\n"));
-
-} catch (err) {
- dv.paragraph("> ❌ tagfeed-live 执行失败:" + err.message);
- console.error("tagfeed-live:", err);
-}
-
-```
diff --git a/00index/0inbox/Land.md b/00index/0inbox/Land.md
deleted file mode 100644
index 5cd3c62..0000000
--- a/00index/0inbox/Land.md
+++ /dev/null
@@ -1,437 +0,0 @@
----
-tags:
- - tagfeed-live
-cssclasses:
- - tagfeed
-words:
- 2026-08-23: 2728
----
-
-# 🏷 #Land(实时聚合页)
-
-> 本页由 DataviewJS 实时计算,笔记有变动时自动刷新,无需任何后台服务。
-
-```dataviewjs
-// ============================================================
-// tagfeed-live:Logseq 风格标签实时聚合(标准 DataviewJS 脚本)
-//
-// 用法:
-// 1. 新建一个笔记(例如 00index/标签聚合/标签名.md)
-// 2. 在笔记里插入一个 dataviewjs 代码块(三个反引号 + dataviewjs),
-// 把本脚本全部粘贴进去
-// 3. 只需要改下面「配置区」的两行:
-// - targetTag 换成你要聚合的标签(不带 #,中英文都可以;多个标签写成数组 ["a", "b"])
-// - excludedPaths 换成你想排除的文件夹
-// 4. 完成。以后任何笔记里出现/删除这个标签,本页自动刷新,
-// 不需要跑任何命令(Dataview 会在文件变动后自动重新渲染)。
-// ============================================================
-
-// ---------------- 配置区(通常只改这里) ----------------
-const targetTag = "Land"; // 要聚合的标签(不带 #);多个标签用数组:["lyric", "歌词"]
-const excludedPaths = ["X2.Archived", "00index", "X0.Clippings", "X1.Knomo", "02DS/02copilot", ".agents", ".claude", ".copilot", ".opencode", ".smart-env", ".workbuddy", ".trash"]; // 要排除的文件夹/路径前缀(vault 相对路径)
- // 例如:["00index", "模板", "X1.Knomo"]
-// 补充说明:
-// - 所有隐藏文件/文件夹(. 开头:.agents/.claude/.copilot/.opencode/.smart-env/.workbuddy/.trash 等)与 X.Attachment 始终跳过
-// - tagfeed 系统页会自动跳过:静态聚合页(文件头含 <!-- tagfeed 标记)
-// 和实时页(frontmatter 带 tagfeed / tagfeed-live 标签),
-// 无需手动排除 00index
-// - 本脚本所在的当前笔记也会自动跳过
-// ---------------------------------------------------------
-
-try {
-const SKIP_DIRS = [".obsidian", ".git", ".trash", "X.Attachment"];
-const MARKER = "<!-- tagfeed";
-const WEEKDAYS = ["周一", "周二", "周三", "周四", "周五", "周六", "周日"];
-const PREV_CHARS = 500; // 预览字数上限:≤上限全文展示;>上限截断并附「查看全文」链接
-
-const esc = s => String(s).replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
-const TAG_LIST = Array.isArray(targetTag) ? targetTag : [targetTag];
-const TAG_RE = new RegExp("(?<![\\w\\u4e00-\\u9fff#])#(?:" + TAG_LIST.map(esc).join("|") + ")(?![\\w\\u4e00-\\u9fff/\\-])");
-const ANY_TAG = /(?<![\w#])#([\w\u4e00-\u9fff][\w\u4e00-\u9fff/\-]*)/g;
-const LIST_RE = /^(\s*)([-*+]|\d+[.)])(?:\s+(.*))?$/;
-const HEAD_RE = /^(#{1,6})\s+(.*)$/;
-const DATEH_RE = /\[\[(\d{4})-(\d{2})-(\d{2})\]\]/;
-const TIME_RE = /(\d{1,2}):(\d{2})(?::(\d{2}))?/;
-const FENCE_RE = /^\s*(`{3,}|~~~)/;
-const indentOf = ln => { // tab 按 2 空格计(与 Python 版一致)
- const expanded = ln.replace(/\t/g, " ");
- return expanded.length - expanded.replace(/^ +/, "").length;
-};
-
-function cleanMatch(ln) {
- let s = ln.replace(/`[^`\n]*`/g, "");
- s = s.replace(/%%[\s\S]*?%%/g, "");
- s = s.replace(/<!--[\s\S]*?-->/g, "");
- return s;
-}
-function vdate(y, m, d) {
- const dt = new Date(y, m - 1, d);
- return (dt.getFullYear() === y && dt.getMonth() === m - 1 && dt.getDate() === d) ? dt : null;
-}
-function dateFromName(name) { // 文件名含 YYYY-MM-DD,或日记命名 26.0805 -> 2026-08-05
- const m1 = name.match(/(\d{4})-(\d{2})-(\d{2})/);
- if (m1) {
- const d1 = vdate(+m1[1], +m1[2], +m1[3]);
- if (d1) return d1;
- }
- const m = name.match(/^(\d{2})\.(\d{2})(\d{2})(?:\D|$)/);
- return m ? vdate(2000 + +m[1], +m[2], +m[3]) : null;
-}
-function parseFrontmatter(text) {
- const lines = text.split("\n");
- if (!lines.length || lines[0].trim() !== "---") return { tags: new Set(), date: null, bodyStart: 0 };
- const n = Math.min(lines.length, 200);
- for (let i = 1; i < n; i++) {
- const t = lines[i].trim();
- if (t === "---" || t === "...") {
- const block = lines.slice(1, i).join("\n");
- const tags = new Set();
- const im = block.match(/^tags:\s*\[(.*?)\]/m);
- if (im) {
- for (const x0 of im[1].split(",")) {
- const x = x0.trim().replace(/^["']|["']$/g, "").replace(/^#/, "").trim();
- if (x) tags.add(x);
- }
- } else {
- const bm = block.match(/^tags:\s*\n((?:[ \t]+-[ \t]+.*\n?)+)/m);
- if (bm) for (const ln of bm[1].split("\n")) {
- const m2 = ln.match(/^\s*-\s*["']?([\w\u4e00-\u9fff/\-]+)["']?\s*$/);
- if (m2) tags.add(m2[1]);
- }
- }
- let fdate = null;
- for (const key of ["date", "created", "published"]) {
- const dm = block.match(new RegExp("^" + key + ":\\s*[\"']?(\\d{4})-(\\d{2})-(\\d{2})", "m"));
- if (dm) { fdate = vdate(+dm[1], +dm[2], +dm[3]); break; }
- }
- return { tags, date: fdate, bodyStart: i + 1 };
- }
- }
- return { tags: new Set(), date: null, bodyStart: 0 };
-}
-const stripTag = s => String(s).replace(TAG_RE, "").replace(/[ \t]{2,}/g, " ").trim();
-
-function scanText(text, note, relpath, fileDate, mtimeDate) {
- const fm = parseFrontmatter(text);
- if (fm.tags.has("tagfeed") || fm.tags.has("tagfeed-live")) return []; // 跳过 tagfeed 系统页(静态聚合页/实时页)
- const lines = text.split("\n");
- const entries = [], seen = new Set();
- let order = 0, inFence = false, inPct = false, inHtml = false;
- let curHead = null, curHeadRaw = null, curDateHead = null;
-
- const entryDate = () => curDateHead || fileDate || fm.date || mtimeDate;
- const anchorOf = () => (curDateHead ? null : curHeadRaw);
- const breadcrumb = () => {
- if (curHead && !DATEH_RE.test(curHead)) {
- const t = curHead.replace(ANY_TAG, "").replace(/\s+/g, " ").trim();
- if (t) return t;
- }
- return null;
- };
- function addBlock(keyIdx, block) {
- const key = relpath + "|" + keyIdx;
- if (seen.has(key) || !block.length) return;
- seen.add(key);
- const d = entryDate();
- if (!d) return;
- let t = null;
- const tm = block[0][1].slice(0, 30).match(TIME_RE);
- if (tm) t = [+tm[1], +tm[2], +(tm[3] || 0)];
- order++;
- entries.push({ date: d, time: t, order, relpath, note,
- breadcrumb: breadcrumb(), lines: block,
- pageLevel: false, notes: [note], anchor: anchorOf() });
- }
- function collectChildren(i, parentIndent) {
- const kids = [], raw = [];
- let j = i + 1;
- while (j < lines.length) {
- const ln = lines[j];
- if (ln.trim() === "") {
- let k = j + 1;
- while (k < lines.length && lines[k].trim() === "") k++;
- if (k < lines.length && LIST_RE.test(lines[k]) && indentOf(lines[k]) > parentIndent) { j = k; continue; }
- break;
- }
- const mi = ln.match(LIST_RE);
- if (mi && indentOf(ln) > parentIndent) {
- raw.push([indentOf(ln), (mi[3] || "").replace(/\s+$/, "")]);
- j++;
- } else break;
- }
- for (const [ind, txt] of raw) kids.push([Math.floor((ind - parentIndent) / 2), txt]);
- return kids;
- }
-
- for (let i = fm.bodyStart; i < lines.length; i++) {
- const ln = lines[i];
- if (FENCE_RE.test(ln)) { inFence = !inFence; continue; }
- if (inFence) continue;
- if (inPct) { if (ln.includes("%%")) inPct = false; continue; }
- if (inHtml) { if (ln.includes("-->")) inHtml = false; continue; }
- const stripped = ln.trim();
- if (stripped.startsWith("%%") && !stripped.slice(2).includes("%%")) { inPct = true; continue; }
- if (stripped.startsWith("<!--") && !stripped.includes("-->")) { inHtml = true; continue; }
-
- const hm = ln.match(HEAD_RE);
- if (hm) {
- const htext = hm[2];
- const dh = htext.match(DATEH_RE);
- if (dh) { curDateHead = vdate(+dh[1], +dh[2], +dh[3]); curHead = null; curHeadRaw = null; }
- else { curHead = htext; curHeadRaw = htext.trim(); }
- if (TAG_RE.test(cleanMatch(htext))) {
- const sec = [[0, "**" + stripTag(htext) + "**"]];
- let j = i + 1;
- const level = hm[1].length;
- let count = 0;
- while (j < lines.length && count < 300) {
- const sl = lines[j];
- const shm = sl.match(HEAD_RE);
- if (shm && shm[1].length <= level) break;
- if (FENCE_RE.test(sl)) break;
- if (sl.trim()) {
- const smi = sl.match(LIST_RE);
- if (smi) sec.push([1 + Math.floor(indentOf(sl) / 2), (smi[3] || "").replace(/\s+$/, "")]);
- else sec.push([1, sl.trim()]);
- count++;
- }
- j++;
- }
- addBlock(i, sec);
- }
- continue;
- }
-
- const clean = cleanMatch(ln);
- if (!TAG_RE.test(clean)) continue;
-
- const mi = ln.match(LIST_RE);
- if (mi) {
- const ind = indentOf(ln);
- const itemText = mi[3] || "";
- const cleaned = stripTag(itemText);
- const kids = collectChildren(i, ind);
- addBlock(i, cleaned ? [[0, cleaned], ...kids] : [[0, itemText.trim()], ...kids]);
- continue;
- }
-
- // 独立标签行:向下吸收 list items,或向上归属到紧邻的块
- if (ln.replace(ANY_TAG, "").trim() === "" || ln.replace(ANY_TAG, "").trim() === "%%") {
- let sec = [];
- let j = i + 1;
- while (j < lines.length) {
- const mj = lines[j].match(LIST_RE);
- if (mj) { sec.push([indentOf(lines[j]), (mj[3] || "").replace(/\s+$/, "")]); j++; }
- else break;
- }
- let keyIdx = i;
- if (!sec.length) {
- const up = [];
- let k = i - 1, bulletIdx = null;
- while (k >= fm.bodyStart) {
- const bk = lines[k];
- if (bk.trim() === "" || HEAD_RE.test(bk) || FENCE_RE.test(bk)) break;
- up.push(k);
- if (LIST_RE.test(bk)) { bulletIdx = k; break; }
- k--;
- }
- if (bulletIdx !== null) {
- const inds = [];
- for (let bl = bulletIdx; bl < i; bl++) {
- const mb = lines[bl].match(LIST_RE);
- if (mb) { inds.push(indentOf(lines[bl])); sec.push([indentOf(lines[bl]), (mb[3] || "").replace(/\s+$/, "")]); }
- else sec.push([(inds.length ? inds[inds.length - 1] + 2 : 0), lines[bl].trim()]);
- }
- keyIdx = bulletIdx;
- } else if (up.length) {
- sec = up.slice().reverse().map(k2 => [0, lines[k2].trim()]);
- } else {
- // 兜底:标签行紧挨在某个标题下方(中间只有空行)→ 捕获该标题下的整个小节
- let h = i - 1;
- while (h >= fm.bodyStart && lines[h].trim() === "") h--;
- const hmk = h >= fm.bodyStart ? lines[h].match(HEAD_RE) : null;
- if (hmk) {
- const level = hmk[1].length;
- const title = hmk[2].replace(ANY_TAG, "").replace(/\s+/g, " ").trim();
- const sec2 = title ? [[0, "**" + title + "**"]] : [];
- let j2 = i + 1, count2 = 0;
- while (j2 < lines.length && count2 < 300) {
- const sl = lines[j2];
- const shm = sl.match(HEAD_RE);
- if (shm && shm[1].length <= level) break;
- if (FENCE_RE.test(sl)) break;
- if (sl.trim()) {
- const smi = sl.match(LIST_RE);
- if (smi) sec2.push([1 + Math.floor(indentOf(sl) / 2), (smi[3] || "").replace(/\s+$/, "")]);
- else sec2.push([1, sl.trim().replace(/^>\s*/, "")]);
- count2++;
- }
- j2++;
- }
- if (sec2.length) sec = sec2;
- }
- }
- }
- if (sec.length) {
- const mi0 = Math.min(...sec.map(x => x[0]));
- addBlock(keyIdx, sec.map(([ind, t]) => [Math.floor((ind - mi0) / 2), t]));
- }
- continue;
- }
-
- // 普通段落带标签:吸收到空行为止
- const para = [stripTag(clean)];
- let j = i + 1;
- while (j < lines.length) {
- const nl = lines[j];
- if (nl.trim() === "" || HEAD_RE.test(nl) || LIST_RE.test(nl) || FENCE_RE.test(nl)) break;
- para.push(nl.trim());
- j++;
- }
- addBlock(i, [[0, para.filter(p => p).join(" ")]]);
- }
-
- // frontmatter 标签 → 页面级条目(附正文开头预览)
- if (TAG_LIST.some(t => fm.tags.has(t))) {
- const d = fm.date || fileDate || mtimeDate;
- if (d) {
- order++;
- // 收集正文文本至 PREV_CHARS:超过上限的长文在渲染时截断并附「查看全文」链接
- const prev = [];
- let prevTotal = 0, prevCount = 0;
- for (let k = fm.bodyStart; k < lines.length && prevCount < 200; k++) {
- const s = lines[k].trim();
- if (!s || s.startsWith("#") || s === "---") continue;
- const clean = s.replace(ANY_TAG, "");
- if (!clean) continue;
- prev.push(clean);
- prevTotal += clean.length;
- prevCount++;
- if (prevTotal > PREV_CHARS) break; // 超过上限:已足够判断,停止收集
- }
- const preview = prev.join(" ");
- entries.push({ date: d, time: null, order, relpath, note, breadcrumb: null,
- lines: preview ? [[0, preview]] : [],
- pageLevel: true, notes: [note], anchor: null });
- }
- }
- return entries;
-}
-
-// ---------------- 扫描整个 vault ----------------
-const currentPath = (dv.current() && dv.current().file) ? dv.current().file.path : null;
-let all = [];
-for (const f of app.vault.getMarkdownFiles()) {
- if (f.path === currentPath || f.path.endsWith(".excalidraw.md")) continue;
- if (SKIP_DIRS.some(d => f.path.startsWith(d + "/"))) continue;
- if (f.path.split("/").some(seg => seg.startsWith("."))) continue; // 排除所有隐藏文件/文件夹(.开头)
- if (excludedPaths.some(p => f.path === p || f.path.startsWith(p.endsWith("/") ? p : p + "/"))) continue;
- let text;
- try { text = await app.vault.cachedRead(f); } catch (e) { continue; }
- if (text.slice(0, 1000).includes(MARKER)) continue; // 跳过 tagfeed 生成的静态页
- all = all.concat(scanText(text, f.basename, f.path, dateFromName(f.basename), new Date(f.stat.mtime)));
-}
-
-// ---------------- 去重:同日期+同内容合并,来源并排 ----------------
-const timeCmp = (a, b) => (a[0] - b[0]) || (a[1] - b[1]) || (a[2] - b[2]);
-const mergedMap = new Map();
-for (const e of all) {
- const fp = e.date.getTime() + "|" + e.lines.map(x => x[0] + ":" + x[1]).join("\n");
- const m = mergedMap.get(fp);
- if (m) {
- if (!m.notes.includes(e.note)) m.notes.push(e.note);
- if (e.time && (!m.time || timeCmp(e.time, m.time) < 0)) m.time = e.time;
- // 若先扫到的来源没有面包屑/锚点,而后面的有,则采用后者(与扫描顺序无关,结果确定)
- if (!m.breadcrumb && e.breadcrumb) {
- m.breadcrumb = e.breadcrumb;
- m.anchor = e.anchor;
- m.relpath = e.relpath;
- m.note = e.note;
- }
- } else mergedMap.set(fp, e);
-}
-const entries = [...mergedMap.values()];
-
-// ---------------- 渲染:按日期倒序 ----------------
-const byDate = new Map();
-for (const e of entries) {
- const k = e.date.getTime();
- if (!byDate.has(k)) byDate.set(k, []);
- byDate.get(k).push(e);
-}
-const dates = [...byDate.keys()].sort((a, b) => b - a);
-const noteSet = new Set();
-for (const e of entries) for (const n of e.notes) noteSet.add(n);
-
-const out = [];
-out.push("> **" + entries.length + "** 个内容块 | **" + noteSet.size + "** 篇笔记 | 按日期倒序 | 随笔记变动自动刷新");
-
-const pad2 = x => String(x).padStart(2, "0");
-const fmtDate = d => d.getFullYear() + "-" + pad2(d.getMonth() + 1) + "-" + pad2(d.getDate());
-const invTime = t => t.map(x => pad2(99 - x)).join("");
-
-for (const dk of dates) {
- const d = new Date(dk);
- out.push("## 📅 " + fmtDate(d) + " · " + WEEKDAYS[d.getDay() === 0 ? 6 : d.getDay() - 1]);
- out.push("");
- const group = byDate.get(dk);
-
- const sub = new Map(); // (笔记+面包屑) -> entries
- for (const e of group) {
- const key = e.relpath + "|" + (e.breadcrumb || "");
- if (!sub.has(key)) sub.set(key, []);
- sub.get(key).push(e);
- }
- const subKey = ([key, es]) => {
- const times = es.filter(e => e.time).map(e => e.time);
- const best = times.length ? times.reduce((a, b) => (timeCmp(a, b) > 0 ? a : b)) : null;
- return (best ? "0" : "1") + (best ? invTime(best) : "000000") + key;
- };
-
- for (const [key, es] of [...sub.entries()].sort((a, b) => (subKey(a) < subKey(b) ? -1 : subKey(a) > subKey(b) ? 1 : 0))) {
- const noteList = [];
- for (const e of es) for (const n of e.notes) if (!noteList.includes(n)) noteList.push(n);
- noteList.sort();
- const crumb = es.map(e => e.breadcrumb).find(b => b) || null;
- out.push("- 🔗 " + noteList.map(n => "[[" + n + "]]").join(" · ") + (crumb ? " · " + crumb : ""));
- const sorted = es.slice().sort((a, b) => {
- const ka = (a.time ? "0" : "1") + (a.time ? invTime(a.time) : "") + String(a.order).padStart(6, "0");
- const kb = (b.time ? "0" : "1") + (b.time ? invTime(b.time) : "") + String(b.order).padStart(6, "0");
- return ka < kb ? -1 : ka > kb ? 1 : 0;
- });
- for (const e of sorted) {
- let bl = e.lines, truncated = false;
- const totalChars = bl.reduce((s, x) => s + x[1].length, 0);
- if (totalChars > PREV_CHARS) { // 只有超过上限才截断;上限内全文展示
- truncated = true;
- const shown = [];
- let chars = 0;
- for (const [lvl, txt] of bl) {
- if (chars + txt.length > PREV_CHARS) {
- if (PREV_CHARS - chars > 0) shown.push([lvl, txt.slice(0, PREV_CHARS - chars) + "…"]);
- break;
- }
- shown.push([lvl, txt]);
- chars += txt.length;
- }
- bl = shown;
- }
- for (const [lvl, txt] of bl) out.push("\t".repeat(lvl + 1) + "- " + txt);
- if (truncated) {
- const target = e.note + (e.anchor ? "#" + e.anchor : "");
- const label = e.pageLevel ? "页面级标签 · 查看全文" : "查看全文";
- out.push("\t- [[" + target + "|↗ " + label + "]]");
- }
- }
- out.push("");
- }
- out.push("");
-}
-dv.paragraph(out.join("\n"));
-
-} catch (err) {
- dv.paragraph("> ❌ tagfeed-live 执行失败:" + err.message);
- console.error("tagfeed-live:", err);
-}
-
-```
diff --git a/00index/0inbox/Make.md b/00index/0inbox/Make.md
deleted file mode 100644
index 78ce0ab..0000000
--- a/00index/0inbox/Make.md
+++ /dev/null
@@ -1,438 +0,0 @@
----
-tags:
- - tagfeed-live
-cssclasses:
- - tagfeed
-words:
- 2026-08-23: 2728
- 2026-08-24: 2755
----
-
-# 🏷 #Make(实时聚合页)
-
-> 本页由 DataviewJS 实时计算,笔记有变动时自动刷新,无需任何后台服务。
-
-```dataviewjs
-// ============================================================
-// tagfeed-live:Logseq 风格标签实时聚合(标准 DataviewJS 脚本)
-//
-// 用法:
-// 1. 新建一个笔记(例如 00index/标签聚合/标签名.md)
-// 2. 在笔记里插入一个 dataviewjs 代码块(三个反引号 + dataviewjs),
-// 把本脚本全部粘贴进去
-// 3. 只需要改下面「配置区」的两行:
-// - targetTag 换成你要聚合的标签(不带 #,中英文都可以;多个标签写成数组 ["a", "b"])
-// - excludedPaths 换成你想排除的文件夹
-// 4. 完成。以后任何笔记里出现/删除这个标签,本页自动刷新,
-// 不需要跑任何命令(Dataview 会在文件变动后自动重新渲染)。
-// ============================================================
-
-// ---------------- 配置区(通常只改这里) ----------------
-const targetTag = "Make"; // 要聚合的标签(不带 #);多个标签用数组:["lyric", "歌词"]
-const excludedPaths = ["X2.Archived", "00index", "X0.Clippings", "X1.Knomo", "02DS/02copilot", ".agents", ".claude", ".copilot", ".opencode", ".smart-env", ".workbuddy", ".trash"]; // 要排除的文件夹/路径前缀(vault 相对路径)
- // 例如:["00index", "模板", "X1.Knomo"]
-// 补充说明:
-// - 所有隐藏文件/文件夹(. 开头:.agents/.claude/.copilot/.opencode/.smart-env/.workbuddy/.trash 等)与 X.Attachment 始终跳过
-// - tagfeed 系统页会自动跳过:静态聚合页(文件头含 <!-- tagfeed 标记)
-// 和实时页(frontmatter 带 tagfeed / tagfeed-live 标签),
-// 无需手动排除 00index
-// - 本脚本所在的当前笔记也会自动跳过
-// ---------------------------------------------------------
-
-try {
-const SKIP_DIRS = [".obsidian", ".git", ".trash", "X.Attachment"];
-const MARKER = "<!-- tagfeed";
-const WEEKDAYS = ["周一", "周二", "周三", "周四", "周五", "周六", "周日"];
-const PREV_CHARS = 500; // 预览字数上限:≤上限全文展示;>上限截断并附「查看全文」链接
-
-const esc = s => String(s).replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
-const TAG_LIST = Array.isArray(targetTag) ? targetTag : [targetTag];
-const TAG_RE = new RegExp("(?<![\\w\\u4e00-\\u9fff#])#(?:" + TAG_LIST.map(esc).join("|") + ")(?![\\w\\u4e00-\\u9fff/\\-])");
-const ANY_TAG = /(?<![\w#])#([\w\u4e00-\u9fff][\w\u4e00-\u9fff/\-]*)/g;
-const LIST_RE = /^(\s*)([-*+]|\d+[.)])(?:\s+(.*))?$/;
-const HEAD_RE = /^(#{1,6})\s+(.*)$/;
-const DATEH_RE = /\[\[(\d{4})-(\d{2})-(\d{2})\]\]/;
-const TIME_RE = /(\d{1,2}):(\d{2})(?::(\d{2}))?/;
-const FENCE_RE = /^\s*(`{3,}|~~~)/;
-const indentOf = ln => { // tab 按 2 空格计(与 Python 版一致)
- const expanded = ln.replace(/\t/g, " ");
- return expanded.length - expanded.replace(/^ +/, "").length;
-};
-
-function cleanMatch(ln) {
- let s = ln.replace(/`[^`\n]*`/g, "");
- s = s.replace(/%%[\s\S]*?%%/g, "");
- s = s.replace(/<!--[\s\S]*?-->/g, "");
- return s;
-}
-function vdate(y, m, d) {
- const dt = new Date(y, m - 1, d);
- return (dt.getFullYear() === y && dt.getMonth() === m - 1 && dt.getDate() === d) ? dt : null;
-}
-function dateFromName(name) { // 文件名含 YYYY-MM-DD,或日记命名 26.0805 -> 2026-08-05
- const m1 = name.match(/(\d{4})-(\d{2})-(\d{2})/);
- if (m1) {
- const d1 = vdate(+m1[1], +m1[2], +m1[3]);
- if (d1) return d1;
- }
- const m = name.match(/^(\d{2})\.(\d{2})(\d{2})(?:\D|$)/);
- return m ? vdate(2000 + +m[1], +m[2], +m[3]) : null;
-}
-function parseFrontmatter(text) {
- const lines = text.split("\n");
- if (!lines.length || lines[0].trim() !== "---") return { tags: new Set(), date: null, bodyStart: 0 };
- const n = Math.min(lines.length, 200);
- for (let i = 1; i < n; i++) {
- const t = lines[i].trim();
- if (t === "---" || t === "...") {
- const block = lines.slice(1, i).join("\n");
- const tags = new Set();
- const im = block.match(/^tags:\s*\[(.*?)\]/m);
- if (im) {
- for (const x0 of im[1].split(",")) {
- const x = x0.trim().replace(/^["']|["']$/g, "").replace(/^#/, "").trim();
- if (x) tags.add(x);
- }
- } else {
- const bm = block.match(/^tags:\s*\n((?:[ \t]+-[ \t]+.*\n?)+)/m);
- if (bm) for (const ln of bm[1].split("\n")) {
- const m2 = ln.match(/^\s*-\s*["']?([\w\u4e00-\u9fff/\-]+)["']?\s*$/);
- if (m2) tags.add(m2[1]);
- }
- }
- let fdate = null;
- for (const key of ["date", "created", "published"]) {
- const dm = block.match(new RegExp("^" + key + ":\\s*[\"']?(\\d{4})-(\\d{2})-(\\d{2})", "m"));
- if (dm) { fdate = vdate(+dm[1], +dm[2], +dm[3]); break; }
- }
- return { tags, date: fdate, bodyStart: i + 1 };
- }
- }
- return { tags: new Set(), date: null, bodyStart: 0 };
-}
-const stripTag = s => String(s).replace(TAG_RE, "").replace(/[ \t]{2,}/g, " ").trim();
-
-function scanText(text, note, relpath, fileDate, mtimeDate) {
- const fm = parseFrontmatter(text);
- if (fm.tags.has("tagfeed") || fm.tags.has("tagfeed-live")) return []; // 跳过 tagfeed 系统页(静态聚合页/实时页)
- const lines = text.split("\n");
- const entries = [], seen = new Set();
- let order = 0, inFence = false, inPct = false, inHtml = false;
- let curHead = null, curHeadRaw = null, curDateHead = null;
-
- const entryDate = () => curDateHead || fileDate || fm.date || mtimeDate;
- const anchorOf = () => (curDateHead ? null : curHeadRaw);
- const breadcrumb = () => {
- if (curHead && !DATEH_RE.test(curHead)) {
- const t = curHead.replace(ANY_TAG, "").replace(/\s+/g, " ").trim();
- if (t) return t;
- }
- return null;
- };
- function addBlock(keyIdx, block) {
- const key = relpath + "|" + keyIdx;
- if (seen.has(key) || !block.length) return;
- seen.add(key);
- const d = entryDate();
- if (!d) return;
- let t = null;
- const tm = block[0][1].slice(0, 30).match(TIME_RE);
- if (tm) t = [+tm[1], +tm[2], +(tm[3] || 0)];
- order++;
- entries.push({ date: d, time: t, order, relpath, note,
- breadcrumb: breadcrumb(), lines: block,
- pageLevel: false, notes: [note], anchor: anchorOf() });
- }
- function collectChildren(i, parentIndent) {
- const kids = [], raw = [];
- let j = i + 1;
- while (j < lines.length) {
- const ln = lines[j];
- if (ln.trim() === "") {
- let k = j + 1;
- while (k < lines.length && lines[k].trim() === "") k++;
- if (k < lines.length && LIST_RE.test(lines[k]) && indentOf(lines[k]) > parentIndent) { j = k; continue; }
- break;
- }
- const mi = ln.match(LIST_RE);
- if (mi && indentOf(ln) > parentIndent) {
- raw.push([indentOf(ln), (mi[3] || "").replace(/\s+$/, "")]);
- j++;
- } else break;
- }
- for (const [ind, txt] of raw) kids.push([Math.floor((ind - parentIndent) / 2), txt]);
- return kids;
- }
-
- for (let i = fm.bodyStart; i < lines.length; i++) {
- const ln = lines[i];
- if (FENCE_RE.test(ln)) { inFence = !inFence; continue; }
- if (inFence) continue;
- if (inPct) { if (ln.includes("%%")) inPct = false; continue; }
- if (inHtml) { if (ln.includes("-->")) inHtml = false; continue; }
- const stripped = ln.trim();
- if (stripped.startsWith("%%") && !stripped.slice(2).includes("%%")) { inPct = true; continue; }
- if (stripped.startsWith("<!--") && !stripped.includes("-->")) { inHtml = true; continue; }
-
- const hm = ln.match(HEAD_RE);
- if (hm) {
- const htext = hm[2];
- const dh = htext.match(DATEH_RE);
- if (dh) { curDateHead = vdate(+dh[1], +dh[2], +dh[3]); curHead = null; curHeadRaw = null; }
- else { curHead = htext; curHeadRaw = htext.trim(); }
- if (TAG_RE.test(cleanMatch(htext))) {
- const sec = [[0, "**" + stripTag(htext) + "**"]];
- let j = i + 1;
- const level = hm[1].length;
- let count = 0;
- while (j < lines.length && count < 300) {
- const sl = lines[j];
- const shm = sl.match(HEAD_RE);
- if (shm && shm[1].length <= level) break;
- if (FENCE_RE.test(sl)) break;
- if (sl.trim()) {
- const smi = sl.match(LIST_RE);
- if (smi) sec.push([1 + Math.floor(indentOf(sl) / 2), (smi[3] || "").replace(/\s+$/, "")]);
- else sec.push([1, sl.trim()]);
- count++;
- }
- j++;
- }
- addBlock(i, sec);
- }
- continue;
- }
-
- const clean = cleanMatch(ln);
- if (!TAG_RE.test(clean)) continue;
-
- const mi = ln.match(LIST_RE);
- if (mi) {
- const ind = indentOf(ln);
- const itemText = mi[3] || "";
- const cleaned = stripTag(itemText);
- const kids = collectChildren(i, ind);
- addBlock(i, cleaned ? [[0, cleaned], ...kids] : [[0, itemText.trim()], ...kids]);
- continue;
- }
-
- // 独立标签行:向下吸收 list items,或向上归属到紧邻的块
- if (ln.replace(ANY_TAG, "").trim() === "" || ln.replace(ANY_TAG, "").trim() === "%%") {
- let sec = [];
- let j = i + 1;
- while (j < lines.length) {
- const mj = lines[j].match(LIST_RE);
- if (mj) { sec.push([indentOf(lines[j]), (mj[3] || "").replace(/\s+$/, "")]); j++; }
- else break;
- }
- let keyIdx = i;
- if (!sec.length) {
- const up = [];
- let k = i - 1, bulletIdx = null;
- while (k >= fm.bodyStart) {
- const bk = lines[k];
- if (bk.trim() === "" || HEAD_RE.test(bk) || FENCE_RE.test(bk)) break;
- up.push(k);
- if (LIST_RE.test(bk)) { bulletIdx = k; break; }
- k--;
- }
- if (bulletIdx !== null) {
- const inds = [];
- for (let bl = bulletIdx; bl < i; bl++) {
- const mb = lines[bl].match(LIST_RE);
- if (mb) { inds.push(indentOf(lines[bl])); sec.push([indentOf(lines[bl]), (mb[3] || "").replace(/\s+$/, "")]); }
- else sec.push([(inds.length ? inds[inds.length - 1] + 2 : 0), lines[bl].trim()]);
- }
- keyIdx = bulletIdx;
- } else if (up.length) {
- sec = up.slice().reverse().map(k2 => [0, lines[k2].trim()]);
- } else {
- // 兜底:标签行紧挨在某个标题下方(中间只有空行)→ 捕获该标题下的整个小节
- let h = i - 1;
- while (h >= fm.bodyStart && lines[h].trim() === "") h--;
- const hmk = h >= fm.bodyStart ? lines[h].match(HEAD_RE) : null;
- if (hmk) {
- const level = hmk[1].length;
- const title = hmk[2].replace(ANY_TAG, "").replace(/\s+/g, " ").trim();
- const sec2 = title ? [[0, "**" + title + "**"]] : [];
- let j2 = i + 1, count2 = 0;
- while (j2 < lines.length && count2 < 300) {
- const sl = lines[j2];
- const shm = sl.match(HEAD_RE);
- if (shm && shm[1].length <= level) break;
- if (FENCE_RE.test(sl)) break;
- if (sl.trim()) {
- const smi = sl.match(LIST_RE);
- if (smi) sec2.push([1 + Math.floor(indentOf(sl) / 2), (smi[3] || "").replace(/\s+$/, "")]);
- else sec2.push([1, sl.trim().replace(/^>\s*/, "")]);
- count2++;
- }
- j2++;
- }
- if (sec2.length) sec = sec2;
- }
- }
- }
- if (sec.length) {
- const mi0 = Math.min(...sec.map(x => x[0]));
- addBlock(keyIdx, sec.map(([ind, t]) => [Math.floor((ind - mi0) / 2), t]));
- }
- continue;
- }
-
- // 普通段落带标签:吸收到空行为止
- const para = [stripTag(clean)];
- let j = i + 1;
- while (j < lines.length) {
- const nl = lines[j];
- if (nl.trim() === "" || HEAD_RE.test(nl) || LIST_RE.test(nl) || FENCE_RE.test(nl)) break;
- para.push(nl.trim());
- j++;
- }
- addBlock(i, [[0, para.filter(p => p).join(" ")]]);
- }
-
- // frontmatter 标签 → 页面级条目(附正文开头预览)
- if (TAG_LIST.some(t => fm.tags.has(t))) {
- const d = fm.date || fileDate || mtimeDate;
- if (d) {
- order++;
- // 收集正文文本至 PREV_CHARS:超过上限的长文在渲染时截断并附「查看全文」链接
- const prev = [];
- let prevTotal = 0, prevCount = 0;
- for (let k = fm.bodyStart; k < lines.length && prevCount < 200; k++) {
- const s = lines[k].trim();
- if (!s || s.startsWith("#") || s === "---") continue;
- const clean = s.replace(ANY_TAG, "");
- if (!clean) continue;
- prev.push(clean);
- prevTotal += clean.length;
- prevCount++;
- if (prevTotal > PREV_CHARS) break; // 超过上限:已足够判断,停止收集
- }
- const preview = prev.join(" ");
- entries.push({ date: d, time: null, order, relpath, note, breadcrumb: null,
- lines: preview ? [[0, preview]] : [],
- pageLevel: true, notes: [note], anchor: null });
- }
- }
- return entries;
-}
-
-// ---------------- 扫描整个 vault ----------------
-const currentPath = (dv.current() && dv.current().file) ? dv.current().file.path : null;
-let all = [];
-for (const f of app.vault.getMarkdownFiles()) {
- if (f.path === currentPath || f.path.endsWith(".excalidraw.md")) continue;
- if (SKIP_DIRS.some(d => f.path.startsWith(d + "/"))) continue;
- if (f.path.split("/").some(seg => seg.startsWith("."))) continue; // 排除所有隐藏文件/文件夹(.开头)
- if (excludedPaths.some(p => f.path === p || f.path.startsWith(p.endsWith("/") ? p : p + "/"))) continue;
- let text;
- try { text = await app.vault.cachedRead(f); } catch (e) { continue; }
- if (text.slice(0, 1000).includes(MARKER)) continue; // 跳过 tagfeed 生成的静态页
- all = all.concat(scanText(text, f.basename, f.path, dateFromName(f.basename), new Date(f.stat.mtime)));
-}
-
-// ---------------- 去重:同日期+同内容合并,来源并排 ----------------
-const timeCmp = (a, b) => (a[0] - b[0]) || (a[1] - b[1]) || (a[2] - b[2]);
-const mergedMap = new Map();
-for (const e of all) {
- const fp = e.date.getTime() + "|" + e.lines.map(x => x[0] + ":" + x[1]).join("\n");
- const m = mergedMap.get(fp);
- if (m) {
- if (!m.notes.includes(e.note)) m.notes.push(e.note);
- if (e.time && (!m.time || timeCmp(e.time, m.time) < 0)) m.time = e.time;
- // 若先扫到的来源没有面包屑/锚点,而后面的有,则采用后者(与扫描顺序无关,结果确定)
- if (!m.breadcrumb && e.breadcrumb) {
- m.breadcrumb = e.breadcrumb;
- m.anchor = e.anchor;
- m.relpath = e.relpath;
- m.note = e.note;
- }
- } else mergedMap.set(fp, e);
-}
-const entries = [...mergedMap.values()];
-
-// ---------------- 渲染:按日期倒序 ----------------
-const byDate = new Map();
-for (const e of entries) {
- const k = e.date.getTime();
- if (!byDate.has(k)) byDate.set(k, []);
- byDate.get(k).push(e);
-}
-const dates = [...byDate.keys()].sort((a, b) => b - a);
-const noteSet = new Set();
-for (const e of entries) for (const n of e.notes) noteSet.add(n);
-
-const out = [];
-out.push("> **" + entries.length + "** 个内容块 | **" + noteSet.size + "** 篇笔记 | 按日期倒序 | 随笔记变动自动刷新");
-
-const pad2 = x => String(x).padStart(2, "0");
-const fmtDate = d => d.getFullYear() + "-" + pad2(d.getMonth() + 1) + "-" + pad2(d.getDate());
-const invTime = t => t.map(x => pad2(99 - x)).join("");
-
-for (const dk of dates) {
- const d = new Date(dk);
- out.push("## 📅 " + fmtDate(d) + " · " + WEEKDAYS[d.getDay() === 0 ? 6 : d.getDay() - 1]);
- out.push("");
- const group = byDate.get(dk);
-
- const sub = new Map(); // (笔记+面包屑) -> entries
- for (const e of group) {
- const key = e.relpath + "|" + (e.breadcrumb || "");
- if (!sub.has(key)) sub.set(key, []);
- sub.get(key).push(e);
- }
- const subKey = ([key, es]) => {
- const times = es.filter(e => e.time).map(e => e.time);
- const best = times.length ? times.reduce((a, b) => (timeCmp(a, b) > 0 ? a : b)) : null;
- return (best ? "0" : "1") + (best ? invTime(best) : "000000") + key;
- };
-
- for (const [key, es] of [...sub.entries()].sort((a, b) => (subKey(a) < subKey(b) ? -1 : subKey(a) > subKey(b) ? 1 : 0))) {
- const noteList = [];
- for (const e of es) for (const n of e.notes) if (!noteList.includes(n)) noteList.push(n);
- noteList.sort();
- const crumb = es.map(e => e.breadcrumb).find(b => b) || null;
- out.push("- 🔗 " + noteList.map(n => "[[" + n + "]]").join(" · ") + (crumb ? " · " + crumb : ""));
- const sorted = es.slice().sort((a, b) => {
- const ka = (a.time ? "0" : "1") + (a.time ? invTime(a.time) : "") + String(a.order).padStart(6, "0");
- const kb = (b.time ? "0" : "1") + (b.time ? invTime(b.time) : "") + String(b.order).padStart(6, "0");
- return ka < kb ? -1 : ka > kb ? 1 : 0;
- });
- for (const e of sorted) {
- let bl = e.lines, truncated = false;
- const totalChars = bl.reduce((s, x) => s + x[1].length, 0);
- if (totalChars > PREV_CHARS) { // 只有超过上限才截断;上限内全文展示
- truncated = true;
- const shown = [];
- let chars = 0;
- for (const [lvl, txt] of bl) {
- if (chars + txt.length > PREV_CHARS) {
- if (PREV_CHARS - chars > 0) shown.push([lvl, txt.slice(0, PREV_CHARS - chars) + "…"]);
- break;
- }
- shown.push([lvl, txt]);
- chars += txt.length;
- }
- bl = shown;
- }
- for (const [lvl, txt] of bl) out.push("\t".repeat(lvl + 1) + "- " + txt);
- if (truncated) {
- const target = e.note + (e.anchor ? "#" + e.anchor : "");
- const label = e.pageLevel ? "页面级标签 · 查看全文" : "查看全文";
- out.push("\t- [[" + target + "|↗ " + label + "]]");
- }
- }
- out.push("");
- }
- out.push("");
-}
-dv.paragraph(out.join("\n"));
-
-} catch (err) {
- dv.paragraph("> ❌ tagfeed-live 执行失败:" + err.message);
- console.error("tagfeed-live:", err);
-}
-
-```
diff --git a/00index/0inbox/Markdown.md b/00index/0inbox/Markdown.md
deleted file mode 100644
index 68bea50..0000000
--- a/00index/0inbox/Markdown.md
+++ /dev/null
@@ -1,437 +0,0 @@
----
-tags:
- - tagfeed-live
-cssclasses:
- - tagfeed
-words:
- 2026-08-23: 2728
----
-
-# 🏷 #Markdown(实时聚合页)
-
-> 本页由 DataviewJS 实时计算,笔记有变动时自动刷新,无需任何后台服务。
-
-```dataviewjs
-// ============================================================
-// tagfeed-live:Logseq 风格标签实时聚合(标准 DataviewJS 脚本)
-//
-// 用法:
-// 1. 新建一个笔记(例如 00index/标签聚合/标签名.md)
-// 2. 在笔记里插入一个 dataviewjs 代码块(三个反引号 + dataviewjs),
-// 把本脚本全部粘贴进去
-// 3. 只需要改下面「配置区」的两行:
-// - targetTag 换成你要聚合的标签(不带 #,中英文都可以;多个标签写成数组 ["a", "b"])
-// - excludedPaths 换成你想排除的文件夹
-// 4. 完成。以后任何笔记里出现/删除这个标签,本页自动刷新,
-// 不需要跑任何命令(Dataview 会在文件变动后自动重新渲染)。
-// ============================================================
-
-// ---------------- 配置区(通常只改这里) ----------------
-const targetTag = "Markdown"; // 要聚合的标签(不带 #);多个标签用数组:["lyric", "歌词"]
-const excludedPaths = ["X2.Archived", "00index", "X0.Clippings", "X1.Knomo", "02DS/02copilot", ".agents", ".claude", ".copilot", ".opencode", ".smart-env", ".workbuddy", ".trash"]; // 要排除的文件夹/路径前缀(vault 相对路径)
- // 例如:["00index", "模板", "X1.Knomo"]
-// 补充说明:
-// - 所有隐藏文件/文件夹(. 开头:.agents/.claude/.copilot/.opencode/.smart-env/.workbuddy/.trash 等)与 X.Attachment 始终跳过
-// - tagfeed 系统页会自动跳过:静态聚合页(文件头含 <!-- tagfeed 标记)
-// 和实时页(frontmatter 带 tagfeed / tagfeed-live 标签),
-// 无需手动排除 00index
-// - 本脚本所在的当前笔记也会自动跳过
-// ---------------------------------------------------------
-
-try {
-const SKIP_DIRS = [".obsidian", ".git", ".trash", "X.Attachment"];
-const MARKER = "<!-- tagfeed";
-const WEEKDAYS = ["周一", "周二", "周三", "周四", "周五", "周六", "周日"];
-const PREV_CHARS = 500; // 预览字数上限:≤上限全文展示;>上限截断并附「查看全文」链接
-
-const esc = s => String(s).replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
-const TAG_LIST = Array.isArray(targetTag) ? targetTag : [targetTag];
-const TAG_RE = new RegExp("(?<![\\w\\u4e00-\\u9fff#])#(?:" + TAG_LIST.map(esc).join("|") + ")(?![\\w\\u4e00-\\u9fff/\\-])");
-const ANY_TAG = /(?<![\w#])#([\w\u4e00-\u9fff][\w\u4e00-\u9fff/\-]*)/g;
-const LIST_RE = /^(\s*)([-*+]|\d+[.)])(?:\s+(.*))?$/;
-const HEAD_RE = /^(#{1,6})\s+(.*)$/;
-const DATEH_RE = /\[\[(\d{4})-(\d{2})-(\d{2})\]\]/;
-const TIME_RE = /(\d{1,2}):(\d{2})(?::(\d{2}))?/;
-const FENCE_RE = /^\s*(`{3,}|~~~)/;
-const indentOf = ln => { // tab 按 2 空格计(与 Python 版一致)
- const expanded = ln.replace(/\t/g, " ");
- return expanded.length - expanded.replace(/^ +/, "").length;
-};
-
-function cleanMatch(ln) {
- let s = ln.replace(/`[^`\n]*`/g, "");
- s = s.replace(/%%[\s\S]*?%%/g, "");
- s = s.replace(/<!--[\s\S]*?-->/g, "");
- return s;
-}
-function vdate(y, m, d) {
- const dt = new Date(y, m - 1, d);
- return (dt.getFullYear() === y && dt.getMonth() === m - 1 && dt.getDate() === d) ? dt : null;
-}
-function dateFromName(name) { // 文件名含 YYYY-MM-DD,或日记命名 26.0805 -> 2026-08-05
- const m1 = name.match(/(\d{4})-(\d{2})-(\d{2})/);
- if (m1) {
- const d1 = vdate(+m1[1], +m1[2], +m1[3]);
- if (d1) return d1;
- }
- const m = name.match(/^(\d{2})\.(\d{2})(\d{2})(?:\D|$)/);
- return m ? vdate(2000 + +m[1], +m[2], +m[3]) : null;
-}
-function parseFrontmatter(text) {
- const lines = text.split("\n");
- if (!lines.length || lines[0].trim() !== "---") return { tags: new Set(), date: null, bodyStart: 0 };
- const n = Math.min(lines.length, 200);
- for (let i = 1; i < n; i++) {
- const t = lines[i].trim();
- if (t === "---" || t === "...") {
- const block = lines.slice(1, i).join("\n");
- const tags = new Set();
- const im = block.match(/^tags:\s*\[(.*?)\]/m);
- if (im) {
- for (const x0 of im[1].split(",")) {
- const x = x0.trim().replace(/^["']|["']$/g, "").replace(/^#/, "").trim();
- if (x) tags.add(x);
- }
- } else {
- const bm = block.match(/^tags:\s*\n((?:[ \t]+-[ \t]+.*\n?)+)/m);
- if (bm) for (const ln of bm[1].split("\n")) {
- const m2 = ln.match(/^\s*-\s*["']?([\w\u4e00-\u9fff/\-]+)["']?\s*$/);
- if (m2) tags.add(m2[1]);
- }
- }
- let fdate = null;
- for (const key of ["date", "created", "published"]) {
- const dm = block.match(new RegExp("^" + key + ":\\s*[\"']?(\\d{4})-(\\d{2})-(\\d{2})", "m"));
- if (dm) { fdate = vdate(+dm[1], +dm[2], +dm[3]); break; }
- }
- return { tags, date: fdate, bodyStart: i + 1 };
- }
- }
- return { tags: new Set(), date: null, bodyStart: 0 };
-}
-const stripTag = s => String(s).replace(TAG_RE, "").replace(/[ \t]{2,}/g, " ").trim();
-
-function scanText(text, note, relpath, fileDate, mtimeDate) {
- const fm = parseFrontmatter(text);
- if (fm.tags.has("tagfeed") || fm.tags.has("tagfeed-live")) return []; // 跳过 tagfeed 系统页(静态聚合页/实时页)
- const lines = text.split("\n");
- const entries = [], seen = new Set();
- let order = 0, inFence = false, inPct = false, inHtml = false;
- let curHead = null, curHeadRaw = null, curDateHead = null;
-
- const entryDate = () => curDateHead || fileDate || fm.date || mtimeDate;
- const anchorOf = () => (curDateHead ? null : curHeadRaw);
- const breadcrumb = () => {
- if (curHead && !DATEH_RE.test(curHead)) {
- const t = curHead.replace(ANY_TAG, "").replace(/\s+/g, " ").trim();
- if (t) return t;
- }
- return null;
- };
- function addBlock(keyIdx, block) {
- const key = relpath + "|" + keyIdx;
- if (seen.has(key) || !block.length) return;
- seen.add(key);
- const d = entryDate();
- if (!d) return;
- let t = null;
- const tm = block[0][1].slice(0, 30).match(TIME_RE);
- if (tm) t = [+tm[1], +tm[2], +(tm[3] || 0)];
- order++;
- entries.push({ date: d, time: t, order, relpath, note,
- breadcrumb: breadcrumb(), lines: block,
- pageLevel: false, notes: [note], anchor: anchorOf() });
- }
- function collectChildren(i, parentIndent) {
- const kids = [], raw = [];
- let j = i + 1;
- while (j < lines.length) {
- const ln = lines[j];
- if (ln.trim() === "") {
- let k = j + 1;
- while (k < lines.length && lines[k].trim() === "") k++;
- if (k < lines.length && LIST_RE.test(lines[k]) && indentOf(lines[k]) > parentIndent) { j = k; continue; }
- break;
- }
- const mi = ln.match(LIST_RE);
- if (mi && indentOf(ln) > parentIndent) {
- raw.push([indentOf(ln), (mi[3] || "").replace(/\s+$/, "")]);
- j++;
- } else break;
- }
- for (const [ind, txt] of raw) kids.push([Math.floor((ind - parentIndent) / 2), txt]);
- return kids;
- }
-
- for (let i = fm.bodyStart; i < lines.length; i++) {
- const ln = lines[i];
- if (FENCE_RE.test(ln)) { inFence = !inFence; continue; }
- if (inFence) continue;
- if (inPct) { if (ln.includes("%%")) inPct = false; continue; }
- if (inHtml) { if (ln.includes("-->")) inHtml = false; continue; }
- const stripped = ln.trim();
- if (stripped.startsWith("%%") && !stripped.slice(2).includes("%%")) { inPct = true; continue; }
- if (stripped.startsWith("<!--") && !stripped.includes("-->")) { inHtml = true; continue; }
-
- const hm = ln.match(HEAD_RE);
- if (hm) {
- const htext = hm[2];
- const dh = htext.match(DATEH_RE);
- if (dh) { curDateHead = vdate(+dh[1], +dh[2], +dh[3]); curHead = null; curHeadRaw = null; }
- else { curHead = htext; curHeadRaw = htext.trim(); }
- if (TAG_RE.test(cleanMatch(htext))) {
- const sec = [[0, "**" + stripTag(htext) + "**"]];
- let j = i + 1;
- const level = hm[1].length;
- let count = 0;
- while (j < lines.length && count < 300) {
- const sl = lines[j];
- const shm = sl.match(HEAD_RE);
- if (shm && shm[1].length <= level) break;
- if (FENCE_RE.test(sl)) break;
- if (sl.trim()) {
- const smi = sl.match(LIST_RE);
- if (smi) sec.push([1 + Math.floor(indentOf(sl) / 2), (smi[3] || "").replace(/\s+$/, "")]);
- else sec.push([1, sl.trim()]);
- count++;
- }
- j++;
- }
- addBlock(i, sec);
- }
- continue;
- }
-
- const clean = cleanMatch(ln);
- if (!TAG_RE.test(clean)) continue;
-
- const mi = ln.match(LIST_RE);
- if (mi) {
- const ind = indentOf(ln);
- const itemText = mi[3] || "";
- const cleaned = stripTag(itemText);
- const kids = collectChildren(i, ind);
- addBlock(i, cleaned ? [[0, cleaned], ...kids] : [[0, itemText.trim()], ...kids]);
- continue;
- }
-
- // 独立标签行:向下吸收 list items,或向上归属到紧邻的块
- if (ln.replace(ANY_TAG, "").trim() === "" || ln.replace(ANY_TAG, "").trim() === "%%") {
- let sec = [];
- let j = i + 1;
- while (j < lines.length) {
- const mj = lines[j].match(LIST_RE);
- if (mj) { sec.push([indentOf(lines[j]), (mj[3] || "").replace(/\s+$/, "")]); j++; }
- else break;
- }
- let keyIdx = i;
- if (!sec.length) {
- const up = [];
- let k = i - 1, bulletIdx = null;
- while (k >= fm.bodyStart) {
- const bk = lines[k];
- if (bk.trim() === "" || HEAD_RE.test(bk) || FENCE_RE.test(bk)) break;
- up.push(k);
- if (LIST_RE.test(bk)) { bulletIdx = k; break; }
- k--;
- }
- if (bulletIdx !== null) {
- const inds = [];
- for (let bl = bulletIdx; bl < i; bl++) {
- const mb = lines[bl].match(LIST_RE);
- if (mb) { inds.push(indentOf(lines[bl])); sec.push([indentOf(lines[bl]), (mb[3] || "").replace(/\s+$/, "")]); }
- else sec.push([(inds.length ? inds[inds.length - 1] + 2 : 0), lines[bl].trim()]);
- }
- keyIdx = bulletIdx;
- } else if (up.length) {
- sec = up.slice().reverse().map(k2 => [0, lines[k2].trim()]);
- } else {
- // 兜底:标签行紧挨在某个标题下方(中间只有空行)→ 捕获该标题下的整个小节
- let h = i - 1;
- while (h >= fm.bodyStart && lines[h].trim() === "") h--;
- const hmk = h >= fm.bodyStart ? lines[h].match(HEAD_RE) : null;
- if (hmk) {
- const level = hmk[1].length;
- const title = hmk[2].replace(ANY_TAG, "").replace(/\s+/g, " ").trim();
- const sec2 = title ? [[0, "**" + title + "**"]] : [];
- let j2 = i + 1, count2 = 0;
- while (j2 < lines.length && count2 < 300) {
- const sl = lines[j2];
- const shm = sl.match(HEAD_RE);
- if (shm && shm[1].length <= level) break;
- if (FENCE_RE.test(sl)) break;
- if (sl.trim()) {
- const smi = sl.match(LIST_RE);
- if (smi) sec2.push([1 + Math.floor(indentOf(sl) / 2), (smi[3] || "").replace(/\s+$/, "")]);
- else sec2.push([1, sl.trim().replace(/^>\s*/, "")]);
- count2++;
- }
- j2++;
- }
- if (sec2.length) sec = sec2;
- }
- }
- }
- if (sec.length) {
- const mi0 = Math.min(...sec.map(x => x[0]));
- addBlock(keyIdx, sec.map(([ind, t]) => [Math.floor((ind - mi0) / 2), t]));
- }
- continue;
- }
-
- // 普通段落带标签:吸收到空行为止
- const para = [stripTag(clean)];
- let j = i + 1;
- while (j < lines.length) {
- const nl = lines[j];
- if (nl.trim() === "" || HEAD_RE.test(nl) || LIST_RE.test(nl) || FENCE_RE.test(nl)) break;
- para.push(nl.trim());
- j++;
- }
- addBlock(i, [[0, para.filter(p => p).join(" ")]]);
- }
-
- // frontmatter 标签 → 页面级条目(附正文开头预览)
- if (TAG_LIST.some(t => fm.tags.has(t))) {
- const d = fm.date || fileDate || mtimeDate;
- if (d) {
- order++;
- // 收集正文文本至 PREV_CHARS:超过上限的长文在渲染时截断并附「查看全文」链接
- const prev = [];
- let prevTotal = 0, prevCount = 0;
- for (let k = fm.bodyStart; k < lines.length && prevCount < 200; k++) {
- const s = lines[k].trim();
- if (!s || s.startsWith("#") || s === "---") continue;
- const clean = s.replace(ANY_TAG, "");
- if (!clean) continue;
- prev.push(clean);
- prevTotal += clean.length;
- prevCount++;
- if (prevTotal > PREV_CHARS) break; // 超过上限:已足够判断,停止收集
- }
- const preview = prev.join(" ");
- entries.push({ date: d, time: null, order, relpath, note, breadcrumb: null,
- lines: preview ? [[0, preview]] : [],
- pageLevel: true, notes: [note], anchor: null });
- }
- }
- return entries;
-}
-
-// ---------------- 扫描整个 vault ----------------
-const currentPath = (dv.current() && dv.current().file) ? dv.current().file.path : null;
-let all = [];
-for (const f of app.vault.getMarkdownFiles()) {
- if (f.path === currentPath || f.path.endsWith(".excalidraw.md")) continue;
- if (SKIP_DIRS.some(d => f.path.startsWith(d + "/"))) continue;
- if (f.path.split("/").some(seg => seg.startsWith("."))) continue; // 排除所有隐藏文件/文件夹(.开头)
- if (excludedPaths.some(p => f.path === p || f.path.startsWith(p.endsWith("/") ? p : p + "/"))) continue;
- let text;
- try { text = await app.vault.cachedRead(f); } catch (e) { continue; }
- if (text.slice(0, 1000).includes(MARKER)) continue; // 跳过 tagfeed 生成的静态页
- all = all.concat(scanText(text, f.basename, f.path, dateFromName(f.basename), new Date(f.stat.mtime)));
-}
-
-// ---------------- 去重:同日期+同内容合并,来源并排 ----------------
-const timeCmp = (a, b) => (a[0] - b[0]) || (a[1] - b[1]) || (a[2] - b[2]);
-const mergedMap = new Map();
-for (const e of all) {
- const fp = e.date.getTime() + "|" + e.lines.map(x => x[0] + ":" + x[1]).join("\n");
- const m = mergedMap.get(fp);
- if (m) {
- if (!m.notes.includes(e.note)) m.notes.push(e.note);
- if (e.time && (!m.time || timeCmp(e.time, m.time) < 0)) m.time = e.time;
- // 若先扫到的来源没有面包屑/锚点,而后面的有,则采用后者(与扫描顺序无关,结果确定)
- if (!m.breadcrumb && e.breadcrumb) {
- m.breadcrumb = e.breadcrumb;
- m.anchor = e.anchor;
- m.relpath = e.relpath;
- m.note = e.note;
- }
- } else mergedMap.set(fp, e);
-}
-const entries = [...mergedMap.values()];
-
-// ---------------- 渲染:按日期倒序 ----------------
-const byDate = new Map();
-for (const e of entries) {
- const k = e.date.getTime();
- if (!byDate.has(k)) byDate.set(k, []);
- byDate.get(k).push(e);
-}
-const dates = [...byDate.keys()].sort((a, b) => b - a);
-const noteSet = new Set();
-for (const e of entries) for (const n of e.notes) noteSet.add(n);
-
-const out = [];
-out.push("> **" + entries.length + "** 个内容块 | **" + noteSet.size + "** 篇笔记 | 按日期倒序 | 随笔记变动自动刷新");
-
-const pad2 = x => String(x).padStart(2, "0");
-const fmtDate = d => d.getFullYear() + "-" + pad2(d.getMonth() + 1) + "-" + pad2(d.getDate());
-const invTime = t => t.map(x => pad2(99 - x)).join("");
-
-for (const dk of dates) {
- const d = new Date(dk);
- out.push("## 📅 " + fmtDate(d) + " · " + WEEKDAYS[d.getDay() === 0 ? 6 : d.getDay() - 1]);
- out.push("");
- const group = byDate.get(dk);
-
- const sub = new Map(); // (笔记+面包屑) -> entries
- for (const e of group) {
- const key = e.relpath + "|" + (e.breadcrumb || "");
- if (!sub.has(key)) sub.set(key, []);
- sub.get(key).push(e);
- }
- const subKey = ([key, es]) => {
- const times = es.filter(e => e.time).map(e => e.time);
- const best = times.length ? times.reduce((a, b) => (timeCmp(a, b) > 0 ? a : b)) : null;
- return (best ? "0" : "1") + (best ? invTime(best) : "000000") + key;
- };
-
- for (const [key, es] of [...sub.entries()].sort((a, b) => (subKey(a) < subKey(b) ? -1 : subKey(a) > subKey(b) ? 1 : 0))) {
- const noteList = [];
- for (const e of es) for (const n of e.notes) if (!noteList.includes(n)) noteList.push(n);
- noteList.sort();
- const crumb = es.map(e => e.breadcrumb).find(b => b) || null;
- out.push("- 🔗 " + noteList.map(n => "[[" + n + "]]").join(" · ") + (crumb ? " · " + crumb : ""));
- const sorted = es.slice().sort((a, b) => {
- const ka = (a.time ? "0" : "1") + (a.time ? invTime(a.time) : "") + String(a.order).padStart(6, "0");
- const kb = (b.time ? "0" : "1") + (b.time ? invTime(b.time) : "") + String(b.order).padStart(6, "0");
- return ka < kb ? -1 : ka > kb ? 1 : 0;
- });
- for (const e of sorted) {
- let bl = e.lines, truncated = false;
- const totalChars = bl.reduce((s, x) => s + x[1].length, 0);
- if (totalChars > PREV_CHARS) { // 只有超过上限才截断;上限内全文展示
- truncated = true;
- const shown = [];
- let chars = 0;
- for (const [lvl, txt] of bl) {
- if (chars + txt.length > PREV_CHARS) {
- if (PREV_CHARS - chars > 0) shown.push([lvl, txt.slice(0, PREV_CHARS - chars) + "…"]);
- break;
- }
- shown.push([lvl, txt]);
- chars += txt.length;
- }
- bl = shown;
- }
- for (const [lvl, txt] of bl) out.push("\t".repeat(lvl + 1) + "- " + txt);
- if (truncated) {
- const target = e.note + (e.anchor ? "#" + e.anchor : "");
- const label = e.pageLevel ? "页面级标签 · 查看全文" : "查看全文";
- out.push("\t- [[" + target + "|↗ " + label + "]]");
- }
- }
- out.push("");
- }
- out.push("");
-}
-dv.paragraph(out.join("\n"));
-
-} catch (err) {
- dv.paragraph("> ❌ tagfeed-live 执行失败:" + err.message);
- console.error("tagfeed-live:", err);
-}
-
-```
diff --git a/00index/0inbox/Outline.md b/00index/0inbox/Outline.md
deleted file mode 100644
index 6d4c78d..0000000
--- a/00index/0inbox/Outline.md
+++ /dev/null
@@ -1,437 +0,0 @@
----
-tags:
- - tagfeed-live
-cssclasses:
- - tagfeed
-words:
- 2026-08-23: 2728
----
-
-# 🏷 #Outline(实时聚合页)
-
-> 本页由 DataviewJS 实时计算,笔记有变动时自动刷新,无需任何后台服务。
-
-```dataviewjs
-// ============================================================
-// tagfeed-live:Logseq 风格标签实时聚合(标准 DataviewJS 脚本)
-//
-// 用法:
-// 1. 新建一个笔记(例如 00index/标签聚合/标签名.md)
-// 2. 在笔记里插入一个 dataviewjs 代码块(三个反引号 + dataviewjs),
-// 把本脚本全部粘贴进去
-// 3. 只需要改下面「配置区」的两行:
-// - targetTag 换成你要聚合的标签(不带 #,中英文都可以;多个标签写成数组 ["a", "b"])
-// - excludedPaths 换成你想排除的文件夹
-// 4. 完成。以后任何笔记里出现/删除这个标签,本页自动刷新,
-// 不需要跑任何命令(Dataview 会在文件变动后自动重新渲染)。
-// ============================================================
-
-// ---------------- 配置区(通常只改这里) ----------------
-const targetTag = "Outline"; // 要聚合的标签(不带 #);多个标签用数组:["lyric", "歌词"]
-const excludedPaths = ["X2.Archived", "00index", "X0.Clippings", "X1.Knomo", "02DS/02copilot", ".agents", ".claude", ".copilot", ".opencode", ".smart-env", ".workbuddy", ".trash"]; // 要排除的文件夹/路径前缀(vault 相对路径)
- // 例如:["00index", "模板", "X1.Knomo"]
-// 补充说明:
-// - 所有隐藏文件/文件夹(. 开头:.agents/.claude/.copilot/.opencode/.smart-env/.workbuddy/.trash 等)与 X.Attachment 始终跳过
-// - tagfeed 系统页会自动跳过:静态聚合页(文件头含 <!-- tagfeed 标记)
-// 和实时页(frontmatter 带 tagfeed / tagfeed-live 标签),
-// 无需手动排除 00index
-// - 本脚本所在的当前笔记也会自动跳过
-// ---------------------------------------------------------
-
-try {
-const SKIP_DIRS = [".obsidian", ".git", ".trash", "X.Attachment"];
-const MARKER = "<!-- tagfeed";
-const WEEKDAYS = ["周一", "周二", "周三", "周四", "周五", "周六", "周日"];
-const PREV_CHARS = 500; // 预览字数上限:≤上限全文展示;>上限截断并附「查看全文」链接
-
-const esc = s => String(s).replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
-const TAG_LIST = Array.isArray(targetTag) ? targetTag : [targetTag];
-const TAG_RE = new RegExp("(?<![\\w\\u4e00-\\u9fff#])#(?:" + TAG_LIST.map(esc).join("|") + ")(?![\\w\\u4e00-\\u9fff/\\-])");
-const ANY_TAG = /(?<![\w#])#([\w\u4e00-\u9fff][\w\u4e00-\u9fff/\-]*)/g;
-const LIST_RE = /^(\s*)([-*+]|\d+[.)])(?:\s+(.*))?$/;
-const HEAD_RE = /^(#{1,6})\s+(.*)$/;
-const DATEH_RE = /\[\[(\d{4})-(\d{2})-(\d{2})\]\]/;
-const TIME_RE = /(\d{1,2}):(\d{2})(?::(\d{2}))?/;
-const FENCE_RE = /^\s*(`{3,}|~~~)/;
-const indentOf = ln => { // tab 按 2 空格计(与 Python 版一致)
- const expanded = ln.replace(/\t/g, " ");
- return expanded.length - expanded.replace(/^ +/, "").length;
-};
-
-function cleanMatch(ln) {
- let s = ln.replace(/`[^`\n]*`/g, "");
- s = s.replace(/%%[\s\S]*?%%/g, "");
- s = s.replace(/<!--[\s\S]*?-->/g, "");
- return s;
-}
-function vdate(y, m, d) {
- const dt = new Date(y, m - 1, d);
- return (dt.getFullYear() === y && dt.getMonth() === m - 1 && dt.getDate() === d) ? dt : null;
-}
-function dateFromName(name) { // 文件名含 YYYY-MM-DD,或日记命名 26.0805 -> 2026-08-05
- const m1 = name.match(/(\d{4})-(\d{2})-(\d{2})/);
- if (m1) {
- const d1 = vdate(+m1[1], +m1[2], +m1[3]);
- if (d1) return d1;
- }
- const m = name.match(/^(\d{2})\.(\d{2})(\d{2})(?:\D|$)/);
- return m ? vdate(2000 + +m[1], +m[2], +m[3]) : null;
-}
-function parseFrontmatter(text) {
- const lines = text.split("\n");
- if (!lines.length || lines[0].trim() !== "---") return { tags: new Set(), date: null, bodyStart: 0 };
- const n = Math.min(lines.length, 200);
- for (let i = 1; i < n; i++) {
- const t = lines[i].trim();
- if (t === "---" || t === "...") {
- const block = lines.slice(1, i).join("\n");
- const tags = new Set();
- const im = block.match(/^tags:\s*\[(.*?)\]/m);
- if (im) {
- for (const x0 of im[1].split(",")) {
- const x = x0.trim().replace(/^["']|["']$/g, "").replace(/^#/, "").trim();
- if (x) tags.add(x);
- }
- } else {
- const bm = block.match(/^tags:\s*\n((?:[ \t]+-[ \t]+.*\n?)+)/m);
- if (bm) for (const ln of bm[1].split("\n")) {
- const m2 = ln.match(/^\s*-\s*["']?([\w\u4e00-\u9fff/\-]+)["']?\s*$/);
- if (m2) tags.add(m2[1]);
- }
- }
- let fdate = null;
- for (const key of ["date", "created", "published"]) {
- const dm = block.match(new RegExp("^" + key + ":\\s*[\"']?(\\d{4})-(\\d{2})-(\\d{2})", "m"));
- if (dm) { fdate = vdate(+dm[1], +dm[2], +dm[3]); break; }
- }
- return { tags, date: fdate, bodyStart: i + 1 };
- }
- }
- return { tags: new Set(), date: null, bodyStart: 0 };
-}
-const stripTag = s => String(s).replace(TAG_RE, "").replace(/[ \t]{2,}/g, " ").trim();
-
-function scanText(text, note, relpath, fileDate, mtimeDate) {
- const fm = parseFrontmatter(text);
- if (fm.tags.has("tagfeed") || fm.tags.has("tagfeed-live")) return []; // 跳过 tagfeed 系统页(静态聚合页/实时页)
- const lines = text.split("\n");
- const entries = [], seen = new Set();
- let order = 0, inFence = false, inPct = false, inHtml = false;
- let curHead = null, curHeadRaw = null, curDateHead = null;
-
- const entryDate = () => curDateHead || fileDate || fm.date || mtimeDate;
- const anchorOf = () => (curDateHead ? null : curHeadRaw);
- const breadcrumb = () => {
- if (curHead && !DATEH_RE.test(curHead)) {
- const t = curHead.replace(ANY_TAG, "").replace(/\s+/g, " ").trim();
- if (t) return t;
- }
- return null;
- };
- function addBlock(keyIdx, block) {
- const key = relpath + "|" + keyIdx;
- if (seen.has(key) || !block.length) return;
- seen.add(key);
- const d = entryDate();
- if (!d) return;
- let t = null;
- const tm = block[0][1].slice(0, 30).match(TIME_RE);
- if (tm) t = [+tm[1], +tm[2], +(tm[3] || 0)];
- order++;
- entries.push({ date: d, time: t, order, relpath, note,
- breadcrumb: breadcrumb(), lines: block,
- pageLevel: false, notes: [note], anchor: anchorOf() });
- }
- function collectChildren(i, parentIndent) {
- const kids = [], raw = [];
- let j = i + 1;
- while (j < lines.length) {
- const ln = lines[j];
- if (ln.trim() === "") {
- let k = j + 1;
- while (k < lines.length && lines[k].trim() === "") k++;
- if (k < lines.length && LIST_RE.test(lines[k]) && indentOf(lines[k]) > parentIndent) { j = k; continue; }
- break;
- }
- const mi = ln.match(LIST_RE);
- if (mi && indentOf(ln) > parentIndent) {
- raw.push([indentOf(ln), (mi[3] || "").replace(/\s+$/, "")]);
- j++;
- } else break;
- }
- for (const [ind, txt] of raw) kids.push([Math.floor((ind - parentIndent) / 2), txt]);
- return kids;
- }
-
- for (let i = fm.bodyStart; i < lines.length; i++) {
- const ln = lines[i];
- if (FENCE_RE.test(ln)) { inFence = !inFence; continue; }
- if (inFence) continue;
- if (inPct) { if (ln.includes("%%")) inPct = false; continue; }
- if (inHtml) { if (ln.includes("-->")) inHtml = false; continue; }
- const stripped = ln.trim();
- if (stripped.startsWith("%%") && !stripped.slice(2).includes("%%")) { inPct = true; continue; }
- if (stripped.startsWith("<!--") && !stripped.includes("-->")) { inHtml = true; continue; }
-
- const hm = ln.match(HEAD_RE);
- if (hm) {
- const htext = hm[2];
- const dh = htext.match(DATEH_RE);
- if (dh) { curDateHead = vdate(+dh[1], +dh[2], +dh[3]); curHead = null; curHeadRaw = null; }
- else { curHead = htext; curHeadRaw = htext.trim(); }
- if (TAG_RE.test(cleanMatch(htext))) {
- const sec = [[0, "**" + stripTag(htext) + "**"]];
- let j = i + 1;
- const level = hm[1].length;
- let count = 0;
- while (j < lines.length && count < 300) {
- const sl = lines[j];
- const shm = sl.match(HEAD_RE);
- if (shm && shm[1].length <= level) break;
- if (FENCE_RE.test(sl)) break;
- if (sl.trim()) {
- const smi = sl.match(LIST_RE);
- if (smi) sec.push([1 + Math.floor(indentOf(sl) / 2), (smi[3] || "").replace(/\s+$/, "")]);
- else sec.push([1, sl.trim()]);
- count++;
- }
- j++;
- }
- addBlock(i, sec);
- }
- continue;
- }
-
- const clean = cleanMatch(ln);
- if (!TAG_RE.test(clean)) continue;
-
- const mi = ln.match(LIST_RE);
- if (mi) {
- const ind = indentOf(ln);
- const itemText = mi[3] || "";
- const cleaned = stripTag(itemText);
- const kids = collectChildren(i, ind);
- addBlock(i, cleaned ? [[0, cleaned], ...kids] : [[0, itemText.trim()], ...kids]);
- continue;
- }
-
- // 独立标签行:向下吸收 list items,或向上归属到紧邻的块
- if (ln.replace(ANY_TAG, "").trim() === "" || ln.replace(ANY_TAG, "").trim() === "%%") {
- let sec = [];
- let j = i + 1;
- while (j < lines.length) {
- const mj = lines[j].match(LIST_RE);
- if (mj) { sec.push([indentOf(lines[j]), (mj[3] || "").replace(/\s+$/, "")]); j++; }
- else break;
- }
- let keyIdx = i;
- if (!sec.length) {
- const up = [];
- let k = i - 1, bulletIdx = null;
- while (k >= fm.bodyStart) {
- const bk = lines[k];
- if (bk.trim() === "" || HEAD_RE.test(bk) || FENCE_RE.test(bk)) break;
- up.push(k);
- if (LIST_RE.test(bk)) { bulletIdx = k; break; }
- k--;
- }
- if (bulletIdx !== null) {
- const inds = [];
- for (let bl = bulletIdx; bl < i; bl++) {
- const mb = lines[bl].match(LIST_RE);
- if (mb) { inds.push(indentOf(lines[bl])); sec.push([indentOf(lines[bl]), (mb[3] || "").replace(/\s+$/, "")]); }
- else sec.push([(inds.length ? inds[inds.length - 1] + 2 : 0), lines[bl].trim()]);
- }
- keyIdx = bulletIdx;
- } else if (up.length) {
- sec = up.slice().reverse().map(k2 => [0, lines[k2].trim()]);
- } else {
- // 兜底:标签行紧挨在某个标题下方(中间只有空行)→ 捕获该标题下的整个小节
- let h = i - 1;
- while (h >= fm.bodyStart && lines[h].trim() === "") h--;
- const hmk = h >= fm.bodyStart ? lines[h].match(HEAD_RE) : null;
- if (hmk) {
- const level = hmk[1].length;
- const title = hmk[2].replace(ANY_TAG, "").replace(/\s+/g, " ").trim();
- const sec2 = title ? [[0, "**" + title + "**"]] : [];
- let j2 = i + 1, count2 = 0;
- while (j2 < lines.length && count2 < 300) {
- const sl = lines[j2];
- const shm = sl.match(HEAD_RE);
- if (shm && shm[1].length <= level) break;
- if (FENCE_RE.test(sl)) break;
- if (sl.trim()) {
- const smi = sl.match(LIST_RE);
- if (smi) sec2.push([1 + Math.floor(indentOf(sl) / 2), (smi[3] || "").replace(/\s+$/, "")]);
- else sec2.push([1, sl.trim().replace(/^>\s*/, "")]);
- count2++;
- }
- j2++;
- }
- if (sec2.length) sec = sec2;
- }
- }
- }
- if (sec.length) {
- const mi0 = Math.min(...sec.map(x => x[0]));
- addBlock(keyIdx, sec.map(([ind, t]) => [Math.floor((ind - mi0) / 2), t]));
- }
- continue;
- }
-
- // 普通段落带标签:吸收到空行为止
- const para = [stripTag(clean)];
- let j = i + 1;
- while (j < lines.length) {
- const nl = lines[j];
- if (nl.trim() === "" || HEAD_RE.test(nl) || LIST_RE.test(nl) || FENCE_RE.test(nl)) break;
- para.push(nl.trim());
- j++;
- }
- addBlock(i, [[0, para.filter(p => p).join(" ")]]);
- }
-
- // frontmatter 标签 → 页面级条目(附正文开头预览)
- if (TAG_LIST.some(t => fm.tags.has(t))) {
- const d = fm.date || fileDate || mtimeDate;
- if (d) {
- order++;
- // 收集正文文本至 PREV_CHARS:超过上限的长文在渲染时截断并附「查看全文」链接
- const prev = [];
- let prevTotal = 0, prevCount = 0;
- for (let k = fm.bodyStart; k < lines.length && prevCount < 200; k++) {
- const s = lines[k].trim();
- if (!s || s.startsWith("#") || s === "---") continue;
- const clean = s.replace(ANY_TAG, "");
- if (!clean) continue;
- prev.push(clean);
- prevTotal += clean.length;
- prevCount++;
- if (prevTotal > PREV_CHARS) break; // 超过上限:已足够判断,停止收集
- }
- const preview = prev.join(" ");
- entries.push({ date: d, time: null, order, relpath, note, breadcrumb: null,
- lines: preview ? [[0, preview]] : [],
- pageLevel: true, notes: [note], anchor: null });
- }
- }
- return entries;
-}
-
-// ---------------- 扫描整个 vault ----------------
-const currentPath = (dv.current() && dv.current().file) ? dv.current().file.path : null;
-let all = [];
-for (const f of app.vault.getMarkdownFiles()) {
- if (f.path === currentPath || f.path.endsWith(".excalidraw.md")) continue;
- if (SKIP_DIRS.some(d => f.path.startsWith(d + "/"))) continue;
- if (f.path.split("/").some(seg => seg.startsWith("."))) continue; // 排除所有隐藏文件/文件夹(.开头)
- if (excludedPaths.some(p => f.path === p || f.path.startsWith(p.endsWith("/") ? p : p + "/"))) continue;
- let text;
- try { text = await app.vault.cachedRead(f); } catch (e) { continue; }
- if (text.slice(0, 1000).includes(MARKER)) continue; // 跳过 tagfeed 生成的静态页
- all = all.concat(scanText(text, f.basename, f.path, dateFromName(f.basename), new Date(f.stat.mtime)));
-}
-
-// ---------------- 去重:同日期+同内容合并,来源并排 ----------------
-const timeCmp = (a, b) => (a[0] - b[0]) || (a[1] - b[1]) || (a[2] - b[2]);
-const mergedMap = new Map();
-for (const e of all) {
- const fp = e.date.getTime() + "|" + e.lines.map(x => x[0] + ":" + x[1]).join("\n");
- const m = mergedMap.get(fp);
- if (m) {
- if (!m.notes.includes(e.note)) m.notes.push(e.note);
- if (e.time && (!m.time || timeCmp(e.time, m.time) < 0)) m.time = e.time;
- // 若先扫到的来源没有面包屑/锚点,而后面的有,则采用后者(与扫描顺序无关,结果确定)
- if (!m.breadcrumb && e.breadcrumb) {
- m.breadcrumb = e.breadcrumb;
- m.anchor = e.anchor;
- m.relpath = e.relpath;
- m.note = e.note;
- }
- } else mergedMap.set(fp, e);
-}
-const entries = [...mergedMap.values()];
-
-// ---------------- 渲染:按日期倒序 ----------------
-const byDate = new Map();
-for (const e of entries) {
- const k = e.date.getTime();
- if (!byDate.has(k)) byDate.set(k, []);
- byDate.get(k).push(e);
-}
-const dates = [...byDate.keys()].sort((a, b) => b - a);
-const noteSet = new Set();
-for (const e of entries) for (const n of e.notes) noteSet.add(n);
-
-const out = [];
-out.push("> **" + entries.length + "** 个内容块 | **" + noteSet.size + "** 篇笔记 | 按日期倒序 | 随笔记变动自动刷新");
-
-const pad2 = x => String(x).padStart(2, "0");
-const fmtDate = d => d.getFullYear() + "-" + pad2(d.getMonth() + 1) + "-" + pad2(d.getDate());
-const invTime = t => t.map(x => pad2(99 - x)).join("");
-
-for (const dk of dates) {
- const d = new Date(dk);
- out.push("## 📅 " + fmtDate(d) + " · " + WEEKDAYS[d.getDay() === 0 ? 6 : d.getDay() - 1]);
- out.push("");
- const group = byDate.get(dk);
-
- const sub = new Map(); // (笔记+面包屑) -> entries
- for (const e of group) {
- const key = e.relpath + "|" + (e.breadcrumb || "");
- if (!sub.has(key)) sub.set(key, []);
- sub.get(key).push(e);
- }
- const subKey = ([key, es]) => {
- const times = es.filter(e => e.time).map(e => e.time);
- const best = times.length ? times.reduce((a, b) => (timeCmp(a, b) > 0 ? a : b)) : null;
- return (best ? "0" : "1") + (best ? invTime(best) : "000000") + key;
- };
-
- for (const [key, es] of [...sub.entries()].sort((a, b) => (subKey(a) < subKey(b) ? -1 : subKey(a) > subKey(b) ? 1 : 0))) {
- const noteList = [];
- for (const e of es) for (const n of e.notes) if (!noteList.includes(n)) noteList.push(n);
- noteList.sort();
- const crumb = es.map(e => e.breadcrumb).find(b => b) || null;
- out.push("- 🔗 " + noteList.map(n => "[[" + n + "]]").join(" · ") + (crumb ? " · " + crumb : ""));
- const sorted = es.slice().sort((a, b) => {
- const ka = (a.time ? "0" : "1") + (a.time ? invTime(a.time) : "") + String(a.order).padStart(6, "0");
- const kb = (b.time ? "0" : "1") + (b.time ? invTime(b.time) : "") + String(b.order).padStart(6, "0");
- return ka < kb ? -1 : ka > kb ? 1 : 0;
- });
- for (const e of sorted) {
- let bl = e.lines, truncated = false;
- const totalChars = bl.reduce((s, x) => s + x[1].length, 0);
- if (totalChars > PREV_CHARS) { // 只有超过上限才截断;上限内全文展示
- truncated = true;
- const shown = [];
- let chars = 0;
- for (const [lvl, txt] of bl) {
- if (chars + txt.length > PREV_CHARS) {
- if (PREV_CHARS - chars > 0) shown.push([lvl, txt.slice(0, PREV_CHARS - chars) + "…"]);
- break;
- }
- shown.push([lvl, txt]);
- chars += txt.length;
- }
- bl = shown;
- }
- for (const [lvl, txt] of bl) out.push("\t".repeat(lvl + 1) + "- " + txt);
- if (truncated) {
- const target = e.note + (e.anchor ? "#" + e.anchor : "");
- const label = e.pageLevel ? "页面级标签 · 查看全文" : "查看全文";
- out.push("\t- [[" + target + "|↗ " + label + "]]");
- }
- }
- out.push("");
- }
- out.push("");
-}
-dv.paragraph(out.join("\n"));
-
-} catch (err) {
- dv.paragraph("> ❌ tagfeed-live 执行失败:" + err.message);
- console.error("tagfeed-live:", err);
-}
-
-```
diff --git a/00index/0inbox/Ownership.md b/00index/0inbox/Ownership.md
deleted file mode 100644
index 7ed0e2b..0000000
--- a/00index/0inbox/Ownership.md
+++ /dev/null
@@ -1,437 +0,0 @@
----
-tags:
- - tagfeed-live
-cssclasses:
- - tagfeed
-words:
- 2026-08-23: 2728
----
-
-# 🏷 #Ownership(实时聚合页)
-
-> 本页由 DataviewJS 实时计算,笔记有变动时自动刷新,无需任何后台服务。
-
-```dataviewjs
-// ============================================================
-// tagfeed-live:Logseq 风格标签实时聚合(标准 DataviewJS 脚本)
-//
-// 用法:
-// 1. 新建一个笔记(例如 00index/标签聚合/标签名.md)
-// 2. 在笔记里插入一个 dataviewjs 代码块(三个反引号 + dataviewjs),
-// 把本脚本全部粘贴进去
-// 3. 只需要改下面「配置区」的两行:
-// - targetTag 换成你要聚合的标签(不带 #,中英文都可以;多个标签写成数组 ["a", "b"])
-// - excludedPaths 换成你想排除的文件夹
-// 4. 完成。以后任何笔记里出现/删除这个标签,本页自动刷新,
-// 不需要跑任何命令(Dataview 会在文件变动后自动重新渲染)。
-// ============================================================
-
-// ---------------- 配置区(通常只改这里) ----------------
-const targetTag = "Ownership"; // 要聚合的标签(不带 #);多个标签用数组:["lyric", "歌词"]
-const excludedPaths = ["X2.Archived", "00index", "X0.Clippings", "X1.Knomo", "02DS/02copilot", ".agents", ".claude", ".copilot", ".opencode", ".smart-env", ".workbuddy", ".trash"]; // 要排除的文件夹/路径前缀(vault 相对路径)
- // 例如:["00index", "模板", "X1.Knomo"]
-// 补充说明:
-// - 所有隐藏文件/文件夹(. 开头:.agents/.claude/.copilot/.opencode/.smart-env/.workbuddy/.trash 等)与 X.Attachment 始终跳过
-// - tagfeed 系统页会自动跳过:静态聚合页(文件头含 <!-- tagfeed 标记)
-// 和实时页(frontmatter 带 tagfeed / tagfeed-live 标签),
-// 无需手动排除 00index
-// - 本脚本所在的当前笔记也会自动跳过
-// ---------------------------------------------------------
-
-try {
-const SKIP_DIRS = [".obsidian", ".git", ".trash", "X.Attachment"];
-const MARKER = "<!-- tagfeed";
-const WEEKDAYS = ["周一", "周二", "周三", "周四", "周五", "周六", "周日"];
-const PREV_CHARS = 500; // 预览字数上限:≤上限全文展示;>上限截断并附「查看全文」链接
-
-const esc = s => String(s).replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
-const TAG_LIST = Array.isArray(targetTag) ? targetTag : [targetTag];
-const TAG_RE = new RegExp("(?<![\\w\\u4e00-\\u9fff#])#(?:" + TAG_LIST.map(esc).join("|") + ")(?![\\w\\u4e00-\\u9fff/\\-])");
-const ANY_TAG = /(?<![\w#])#([\w\u4e00-\u9fff][\w\u4e00-\u9fff/\-]*)/g;
-const LIST_RE = /^(\s*)([-*+]|\d+[.)])(?:\s+(.*))?$/;
-const HEAD_RE = /^(#{1,6})\s+(.*)$/;
-const DATEH_RE = /\[\[(\d{4})-(\d{2})-(\d{2})\]\]/;
-const TIME_RE = /(\d{1,2}):(\d{2})(?::(\d{2}))?/;
-const FENCE_RE = /^\s*(`{3,}|~~~)/;
-const indentOf = ln => { // tab 按 2 空格计(与 Python 版一致)
- const expanded = ln.replace(/\t/g, " ");
- return expanded.length - expanded.replace(/^ +/, "").length;
-};
-
-function cleanMatch(ln) {
- let s = ln.replace(/`[^`\n]*`/g, "");
- s = s.replace(/%%[\s\S]*?%%/g, "");
- s = s.replace(/<!--[\s\S]*?-->/g, "");
- return s;
-}
-function vdate(y, m, d) {
- const dt = new Date(y, m - 1, d);
- return (dt.getFullYear() === y && dt.getMonth() === m - 1 && dt.getDate() === d) ? dt : null;
-}
-function dateFromName(name) { // 文件名含 YYYY-MM-DD,或日记命名 26.0805 -> 2026-08-05
- const m1 = name.match(/(\d{4})-(\d{2})-(\d{2})/);
- if (m1) {
- const d1 = vdate(+m1[1], +m1[2], +m1[3]);
- if (d1) return d1;
- }
- const m = name.match(/^(\d{2})\.(\d{2})(\d{2})(?:\D|$)/);
- return m ? vdate(2000 + +m[1], +m[2], +m[3]) : null;
-}
-function parseFrontmatter(text) {
- const lines = text.split("\n");
- if (!lines.length || lines[0].trim() !== "---") return { tags: new Set(), date: null, bodyStart: 0 };
- const n = Math.min(lines.length, 200);
- for (let i = 1; i < n; i++) {
- const t = lines[i].trim();
- if (t === "---" || t === "...") {
- const block = lines.slice(1, i).join("\n");
- const tags = new Set();
- const im = block.match(/^tags:\s*\[(.*?)\]/m);
- if (im) {
- for (const x0 of im[1].split(",")) {
- const x = x0.trim().replace(/^["']|["']$/g, "").replace(/^#/, "").trim();
- if (x) tags.add(x);
- }
- } else {
- const bm = block.match(/^tags:\s*\n((?:[ \t]+-[ \t]+.*\n?)+)/m);
- if (bm) for (const ln of bm[1].split("\n")) {
- const m2 = ln.match(/^\s*-\s*["']?([\w\u4e00-\u9fff/\-]+)["']?\s*$/);
- if (m2) tags.add(m2[1]);
- }
- }
- let fdate = null;
- for (const key of ["date", "created", "published"]) {
- const dm = block.match(new RegExp("^" + key + ":\\s*[\"']?(\\d{4})-(\\d{2})-(\\d{2})", "m"));
- if (dm) { fdate = vdate(+dm[1], +dm[2], +dm[3]); break; }
- }
- return { tags, date: fdate, bodyStart: i + 1 };
- }
- }
- return { tags: new Set(), date: null, bodyStart: 0 };
-}
-const stripTag = s => String(s).replace(TAG_RE, "").replace(/[ \t]{2,}/g, " ").trim();
-
-function scanText(text, note, relpath, fileDate, mtimeDate) {
- const fm = parseFrontmatter(text);
- if (fm.tags.has("tagfeed") || fm.tags.has("tagfeed-live")) return []; // 跳过 tagfeed 系统页(静态聚合页/实时页)
- const lines = text.split("\n");
- const entries = [], seen = new Set();
- let order = 0, inFence = false, inPct = false, inHtml = false;
- let curHead = null, curHeadRaw = null, curDateHead = null;
-
- const entryDate = () => curDateHead || fileDate || fm.date || mtimeDate;
- const anchorOf = () => (curDateHead ? null : curHeadRaw);
- const breadcrumb = () => {
- if (curHead && !DATEH_RE.test(curHead)) {
- const t = curHead.replace(ANY_TAG, "").replace(/\s+/g, " ").trim();
- if (t) return t;
- }
- return null;
- };
- function addBlock(keyIdx, block) {
- const key = relpath + "|" + keyIdx;
- if (seen.has(key) || !block.length) return;
- seen.add(key);
- const d = entryDate();
- if (!d) return;
- let t = null;
- const tm = block[0][1].slice(0, 30).match(TIME_RE);
- if (tm) t = [+tm[1], +tm[2], +(tm[3] || 0)];
- order++;
- entries.push({ date: d, time: t, order, relpath, note,
- breadcrumb: breadcrumb(), lines: block,
- pageLevel: false, notes: [note], anchor: anchorOf() });
- }
- function collectChildren(i, parentIndent) {
- const kids = [], raw = [];
- let j = i + 1;
- while (j < lines.length) {
- const ln = lines[j];
- if (ln.trim() === "") {
- let k = j + 1;
- while (k < lines.length && lines[k].trim() === "") k++;
- if (k < lines.length && LIST_RE.test(lines[k]) && indentOf(lines[k]) > parentIndent) { j = k; continue; }
- break;
- }
- const mi = ln.match(LIST_RE);
- if (mi && indentOf(ln) > parentIndent) {
- raw.push([indentOf(ln), (mi[3] || "").replace(/\s+$/, "")]);
- j++;
- } else break;
- }
- for (const [ind, txt] of raw) kids.push([Math.floor((ind - parentIndent) / 2), txt]);
- return kids;
- }
-
- for (let i = fm.bodyStart; i < lines.length; i++) {
- const ln = lines[i];
- if (FENCE_RE.test(ln)) { inFence = !inFence; continue; }
- if (inFence) continue;
- if (inPct) { if (ln.includes("%%")) inPct = false; continue; }
- if (inHtml) { if (ln.includes("-->")) inHtml = false; continue; }
- const stripped = ln.trim();
- if (stripped.startsWith("%%") && !stripped.slice(2).includes("%%")) { inPct = true; continue; }
- if (stripped.startsWith("<!--") && !stripped.includes("-->")) { inHtml = true; continue; }
-
- const hm = ln.match(HEAD_RE);
- if (hm) {
- const htext = hm[2];
- const dh = htext.match(DATEH_RE);
- if (dh) { curDateHead = vdate(+dh[1], +dh[2], +dh[3]); curHead = null; curHeadRaw = null; }
- else { curHead = htext; curHeadRaw = htext.trim(); }
- if (TAG_RE.test(cleanMatch(htext))) {
- const sec = [[0, "**" + stripTag(htext) + "**"]];
- let j = i + 1;
- const level = hm[1].length;
- let count = 0;
- while (j < lines.length && count < 300) {
- const sl = lines[j];
- const shm = sl.match(HEAD_RE);
- if (shm && shm[1].length <= level) break;
- if (FENCE_RE.test(sl)) break;
- if (sl.trim()) {
- const smi = sl.match(LIST_RE);
- if (smi) sec.push([1 + Math.floor(indentOf(sl) / 2), (smi[3] || "").replace(/\s+$/, "")]);
- else sec.push([1, sl.trim()]);
- count++;
- }
- j++;
- }
- addBlock(i, sec);
- }
- continue;
- }
-
- const clean = cleanMatch(ln);
- if (!TAG_RE.test(clean)) continue;
-
- const mi = ln.match(LIST_RE);
- if (mi) {
- const ind = indentOf(ln);
- const itemText = mi[3] || "";
- const cleaned = stripTag(itemText);
- const kids = collectChildren(i, ind);
- addBlock(i, cleaned ? [[0, cleaned], ...kids] : [[0, itemText.trim()], ...kids]);
- continue;
- }
-
- // 独立标签行:向下吸收 list items,或向上归属到紧邻的块
- if (ln.replace(ANY_TAG, "").trim() === "" || ln.replace(ANY_TAG, "").trim() === "%%") {
- let sec = [];
- let j = i + 1;
- while (j < lines.length) {
- const mj = lines[j].match(LIST_RE);
- if (mj) { sec.push([indentOf(lines[j]), (mj[3] || "").replace(/\s+$/, "")]); j++; }
- else break;
- }
- let keyIdx = i;
- if (!sec.length) {
- const up = [];
- let k = i - 1, bulletIdx = null;
- while (k >= fm.bodyStart) {
- const bk = lines[k];
- if (bk.trim() === "" || HEAD_RE.test(bk) || FENCE_RE.test(bk)) break;
- up.push(k);
- if (LIST_RE.test(bk)) { bulletIdx = k; break; }
- k--;
- }
- if (bulletIdx !== null) {
- const inds = [];
- for (let bl = bulletIdx; bl < i; bl++) {
- const mb = lines[bl].match(LIST_RE);
- if (mb) { inds.push(indentOf(lines[bl])); sec.push([indentOf(lines[bl]), (mb[3] || "").replace(/\s+$/, "")]); }
- else sec.push([(inds.length ? inds[inds.length - 1] + 2 : 0), lines[bl].trim()]);
- }
- keyIdx = bulletIdx;
- } else if (up.length) {
- sec = up.slice().reverse().map(k2 => [0, lines[k2].trim()]);
- } else {
- // 兜底:标签行紧挨在某个标题下方(中间只有空行)→ 捕获该标题下的整个小节
- let h = i - 1;
- while (h >= fm.bodyStart && lines[h].trim() === "") h--;
- const hmk = h >= fm.bodyStart ? lines[h].match(HEAD_RE) : null;
- if (hmk) {
- const level = hmk[1].length;
- const title = hmk[2].replace(ANY_TAG, "").replace(/\s+/g, " ").trim();
- const sec2 = title ? [[0, "**" + title + "**"]] : [];
- let j2 = i + 1, count2 = 0;
- while (j2 < lines.length && count2 < 300) {
- const sl = lines[j2];
- const shm = sl.match(HEAD_RE);
- if (shm && shm[1].length <= level) break;
- if (FENCE_RE.test(sl)) break;
- if (sl.trim()) {
- const smi = sl.match(LIST_RE);
- if (smi) sec2.push([1 + Math.floor(indentOf(sl) / 2), (smi[3] || "").replace(/\s+$/, "")]);
- else sec2.push([1, sl.trim().replace(/^>\s*/, "")]);
- count2++;
- }
- j2++;
- }
- if (sec2.length) sec = sec2;
- }
- }
- }
- if (sec.length) {
- const mi0 = Math.min(...sec.map(x => x[0]));
- addBlock(keyIdx, sec.map(([ind, t]) => [Math.floor((ind - mi0) / 2), t]));
- }
- continue;
- }
-
- // 普通段落带标签:吸收到空行为止
- const para = [stripTag(clean)];
- let j = i + 1;
- while (j < lines.length) {
- const nl = lines[j];
- if (nl.trim() === "" || HEAD_RE.test(nl) || LIST_RE.test(nl) || FENCE_RE.test(nl)) break;
- para.push(nl.trim());
- j++;
- }
- addBlock(i, [[0, para.filter(p => p).join(" ")]]);
- }
-
- // frontmatter 标签 → 页面级条目(附正文开头预览)
- if (TAG_LIST.some(t => fm.tags.has(t))) {
- const d = fm.date || fileDate || mtimeDate;
- if (d) {
- order++;
- // 收集正文文本至 PREV_CHARS:超过上限的长文在渲染时截断并附「查看全文」链接
- const prev = [];
- let prevTotal = 0, prevCount = 0;
- for (let k = fm.bodyStart; k < lines.length && prevCount < 200; k++) {
- const s = lines[k].trim();
- if (!s || s.startsWith("#") || s === "---") continue;
- const clean = s.replace(ANY_TAG, "");
- if (!clean) continue;
- prev.push(clean);
- prevTotal += clean.length;
- prevCount++;
- if (prevTotal > PREV_CHARS) break; // 超过上限:已足够判断,停止收集
- }
- const preview = prev.join(" ");
- entries.push({ date: d, time: null, order, relpath, note, breadcrumb: null,
- lines: preview ? [[0, preview]] : [],
- pageLevel: true, notes: [note], anchor: null });
- }
- }
- return entries;
-}
-
-// ---------------- 扫描整个 vault ----------------
-const currentPath = (dv.current() && dv.current().file) ? dv.current().file.path : null;
-let all = [];
-for (const f of app.vault.getMarkdownFiles()) {
- if (f.path === currentPath || f.path.endsWith(".excalidraw.md")) continue;
- if (SKIP_DIRS.some(d => f.path.startsWith(d + "/"))) continue;
- if (f.path.split("/").some(seg => seg.startsWith("."))) continue; // 排除所有隐藏文件/文件夹(.开头)
- if (excludedPaths.some(p => f.path === p || f.path.startsWith(p.endsWith("/") ? p : p + "/"))) continue;
- let text;
- try { text = await app.vault.cachedRead(f); } catch (e) { continue; }
- if (text.slice(0, 1000).includes(MARKER)) continue; // 跳过 tagfeed 生成的静态页
- all = all.concat(scanText(text, f.basename, f.path, dateFromName(f.basename), new Date(f.stat.mtime)));
-}
-
-// ---------------- 去重:同日期+同内容合并,来源并排 ----------------
-const timeCmp = (a, b) => (a[0] - b[0]) || (a[1] - b[1]) || (a[2] - b[2]);
-const mergedMap = new Map();
-for (const e of all) {
- const fp = e.date.getTime() + "|" + e.lines.map(x => x[0] + ":" + x[1]).join("\n");
- const m = mergedMap.get(fp);
- if (m) {
- if (!m.notes.includes(e.note)) m.notes.push(e.note);
- if (e.time && (!m.time || timeCmp(e.time, m.time) < 0)) m.time = e.time;
- // 若先扫到的来源没有面包屑/锚点,而后面的有,则采用后者(与扫描顺序无关,结果确定)
- if (!m.breadcrumb && e.breadcrumb) {
- m.breadcrumb = e.breadcrumb;
- m.anchor = e.anchor;
- m.relpath = e.relpath;
- m.note = e.note;
- }
- } else mergedMap.set(fp, e);
-}
-const entries = [...mergedMap.values()];
-
-// ---------------- 渲染:按日期倒序 ----------------
-const byDate = new Map();
-for (const e of entries) {
- const k = e.date.getTime();
- if (!byDate.has(k)) byDate.set(k, []);
- byDate.get(k).push(e);
-}
-const dates = [...byDate.keys()].sort((a, b) => b - a);
-const noteSet = new Set();
-for (const e of entries) for (const n of e.notes) noteSet.add(n);
-
-const out = [];
-out.push("> **" + entries.length + "** 个内容块 | **" + noteSet.size + "** 篇笔记 | 按日期倒序 | 随笔记变动自动刷新");
-
-const pad2 = x => String(x).padStart(2, "0");
-const fmtDate = d => d.getFullYear() + "-" + pad2(d.getMonth() + 1) + "-" + pad2(d.getDate());
-const invTime = t => t.map(x => pad2(99 - x)).join("");
-
-for (const dk of dates) {
- const d = new Date(dk);
- out.push("## 📅 " + fmtDate(d) + " · " + WEEKDAYS[d.getDay() === 0 ? 6 : d.getDay() - 1]);
- out.push("");
- const group = byDate.get(dk);
-
- const sub = new Map(); // (笔记+面包屑) -> entries
- for (const e of group) {
- const key = e.relpath + "|" + (e.breadcrumb || "");
- if (!sub.has(key)) sub.set(key, []);
- sub.get(key).push(e);
- }
- const subKey = ([key, es]) => {
- const times = es.filter(e => e.time).map(e => e.time);
- const best = times.length ? times.reduce((a, b) => (timeCmp(a, b) > 0 ? a : b)) : null;
- return (best ? "0" : "1") + (best ? invTime(best) : "000000") + key;
- };
-
- for (const [key, es] of [...sub.entries()].sort((a, b) => (subKey(a) < subKey(b) ? -1 : subKey(a) > subKey(b) ? 1 : 0))) {
- const noteList = [];
- for (const e of es) for (const n of e.notes) if (!noteList.includes(n)) noteList.push(n);
- noteList.sort();
- const crumb = es.map(e => e.breadcrumb).find(b => b) || null;
- out.push("- 🔗 " + noteList.map(n => "[[" + n + "]]").join(" · ") + (crumb ? " · " + crumb : ""));
- const sorted = es.slice().sort((a, b) => {
- const ka = (a.time ? "0" : "1") + (a.time ? invTime(a.time) : "") + String(a.order).padStart(6, "0");
- const kb = (b.time ? "0" : "1") + (b.time ? invTime(b.time) : "") + String(b.order).padStart(6, "0");
- return ka < kb ? -1 : ka > kb ? 1 : 0;
- });
- for (const e of sorted) {
- let bl = e.lines, truncated = false;
- const totalChars = bl.reduce((s, x) => s + x[1].length, 0);
- if (totalChars > PREV_CHARS) { // 只有超过上限才截断;上限内全文展示
- truncated = true;
- const shown = [];
- let chars = 0;
- for (const [lvl, txt] of bl) {
- if (chars + txt.length > PREV_CHARS) {
- if (PREV_CHARS - chars > 0) shown.push([lvl, txt.slice(0, PREV_CHARS - chars) + "…"]);
- break;
- }
- shown.push([lvl, txt]);
- chars += txt.length;
- }
- bl = shown;
- }
- for (const [lvl, txt] of bl) out.push("\t".repeat(lvl + 1) + "- " + txt);
- if (truncated) {
- const target = e.note + (e.anchor ? "#" + e.anchor : "");
- const label = e.pageLevel ? "页面级标签 · 查看全文" : "查看全文";
- out.push("\t- [[" + target + "|↗ " + label + "]]");
- }
- }
- out.push("");
- }
- out.push("");
-}
-dv.paragraph(out.join("\n"));
-
-} catch (err) {
- dv.paragraph("> ❌ tagfeed-live 执行失败:" + err.message);
- console.error("tagfeed-live:", err);
-}
-
-```
diff --git a/00index/0inbox/Structure.md b/00index/0inbox/Structure.md
deleted file mode 100644
index 5afcaaa..0000000
--- a/00index/0inbox/Structure.md
+++ /dev/null
@@ -1,437 +0,0 @@
----
-tags:
- - tagfeed-live
-cssclasses:
- - tagfeed
-words:
- 2026-08-23: 2728
----
-
-# 🏷 #Structure(实时聚合页)
-
-> 本页由 DataviewJS 实时计算,笔记有变动时自动刷新,无需任何后台服务。
-
-```dataviewjs
-// ============================================================
-// tagfeed-live:Logseq 风格标签实时聚合(标准 DataviewJS 脚本)
-//
-// 用法:
-// 1. 新建一个笔记(例如 00index/标签聚合/标签名.md)
-// 2. 在笔记里插入一个 dataviewjs 代码块(三个反引号 + dataviewjs),
-// 把本脚本全部粘贴进去
-// 3. 只需要改下面「配置区」的两行:
-// - targetTag 换成你要聚合的标签(不带 #,中英文都可以;多个标签写成数组 ["a", "b"])
-// - excludedPaths 换成你想排除的文件夹
-// 4. 完成。以后任何笔记里出现/删除这个标签,本页自动刷新,
-// 不需要跑任何命令(Dataview 会在文件变动后自动重新渲染)。
-// ============================================================
-
-// ---------------- 配置区(通常只改这里) ----------------
-const targetTag = "Structure"; // 要聚合的标签(不带 #);多个标签用数组:["lyric", "歌词"]
-const excludedPaths = ["X2.Archived", "00index", "X0.Clippings", "X1.Knomo", "02DS/02copilot", ".agents", ".claude", ".copilot", ".opencode", ".smart-env", ".workbuddy", ".trash"]; // 要排除的文件夹/路径前缀(vault 相对路径)
- // 例如:["00index", "模板", "X1.Knomo"]
-// 补充说明:
-// - 所有隐藏文件/文件夹(. 开头:.agents/.claude/.copilot/.opencode/.smart-env/.workbuddy/.trash 等)与 X.Attachment 始终跳过
-// - tagfeed 系统页会自动跳过:静态聚合页(文件头含 <!-- tagfeed 标记)
-// 和实时页(frontmatter 带 tagfeed / tagfeed-live 标签),
-// 无需手动排除 00index
-// - 本脚本所在的当前笔记也会自动跳过
-// ---------------------------------------------------------
-
-try {
-const SKIP_DIRS = [".obsidian", ".git", ".trash", "X.Attachment"];
-const MARKER = "<!-- tagfeed";
-const WEEKDAYS = ["周一", "周二", "周三", "周四", "周五", "周六", "周日"];
-const PREV_CHARS = 500; // 预览字数上限:≤上限全文展示;>上限截断并附「查看全文」链接
-
-const esc = s => String(s).replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
-const TAG_LIST = Array.isArray(targetTag) ? targetTag : [targetTag];
-const TAG_RE = new RegExp("(?<![\\w\\u4e00-\\u9fff#])#(?:" + TAG_LIST.map(esc).join("|") + ")(?![\\w\\u4e00-\\u9fff/\\-])");
-const ANY_TAG = /(?<![\w#])#([\w\u4e00-\u9fff][\w\u4e00-\u9fff/\-]*)/g;
-const LIST_RE = /^(\s*)([-*+]|\d+[.)])(?:\s+(.*))?$/;
-const HEAD_RE = /^(#{1,6})\s+(.*)$/;
-const DATEH_RE = /\[\[(\d{4})-(\d{2})-(\d{2})\]\]/;
-const TIME_RE = /(\d{1,2}):(\d{2})(?::(\d{2}))?/;
-const FENCE_RE = /^\s*(`{3,}|~~~)/;
-const indentOf = ln => { // tab 按 2 空格计(与 Python 版一致)
- const expanded = ln.replace(/\t/g, " ");
- return expanded.length - expanded.replace(/^ +/, "").length;
-};
-
-function cleanMatch(ln) {
- let s = ln.replace(/`[^`\n]*`/g, "");
- s = s.replace(/%%[\s\S]*?%%/g, "");
- s = s.replace(/<!--[\s\S]*?-->/g, "");
- return s;
-}
-function vdate(y, m, d) {
- const dt = new Date(y, m - 1, d);
- return (dt.getFullYear() === y && dt.getMonth() === m - 1 && dt.getDate() === d) ? dt : null;
-}
-function dateFromName(name) { // 文件名含 YYYY-MM-DD,或日记命名 26.0805 -> 2026-08-05
- const m1 = name.match(/(\d{4})-(\d{2})-(\d{2})/);
- if (m1) {
- const d1 = vdate(+m1[1], +m1[2], +m1[3]);
- if (d1) return d1;
- }
- const m = name.match(/^(\d{2})\.(\d{2})(\d{2})(?:\D|$)/);
- return m ? vdate(2000 + +m[1], +m[2], +m[3]) : null;
-}
-function parseFrontmatter(text) {
- const lines = text.split("\n");
- if (!lines.length || lines[0].trim() !== "---") return { tags: new Set(), date: null, bodyStart: 0 };
- const n = Math.min(lines.length, 200);
- for (let i = 1; i < n; i++) {
- const t = lines[i].trim();
- if (t === "---" || t === "...") {
- const block = lines.slice(1, i).join("\n");
- const tags = new Set();
- const im = block.match(/^tags:\s*\[(.*?)\]/m);
- if (im) {
- for (const x0 of im[1].split(",")) {
- const x = x0.trim().replace(/^["']|["']$/g, "").replace(/^#/, "").trim();
- if (x) tags.add(x);
- }
- } else {
- const bm = block.match(/^tags:\s*\n((?:[ \t]+-[ \t]+.*\n?)+)/m);
- if (bm) for (const ln of bm[1].split("\n")) {
- const m2 = ln.match(/^\s*-\s*["']?([\w\u4e00-\u9fff/\-]+)["']?\s*$/);
- if (m2) tags.add(m2[1]);
- }
- }
- let fdate = null;
- for (const key of ["date", "created", "published"]) {
- const dm = block.match(new RegExp("^" + key + ":\\s*[\"']?(\\d{4})-(\\d{2})-(\\d{2})", "m"));
- if (dm) { fdate = vdate(+dm[1], +dm[2], +dm[3]); break; }
- }
- return { tags, date: fdate, bodyStart: i + 1 };
- }
- }
- return { tags: new Set(), date: null, bodyStart: 0 };
-}
-const stripTag = s => String(s).replace(TAG_RE, "").replace(/[ \t]{2,}/g, " ").trim();
-
-function scanText(text, note, relpath, fileDate, mtimeDate) {
- const fm = parseFrontmatter(text);
- if (fm.tags.has("tagfeed") || fm.tags.has("tagfeed-live")) return []; // 跳过 tagfeed 系统页(静态聚合页/实时页)
- const lines = text.split("\n");
- const entries = [], seen = new Set();
- let order = 0, inFence = false, inPct = false, inHtml = false;
- let curHead = null, curHeadRaw = null, curDateHead = null;
-
- const entryDate = () => curDateHead || fileDate || fm.date || mtimeDate;
- const anchorOf = () => (curDateHead ? null : curHeadRaw);
- const breadcrumb = () => {
- if (curHead && !DATEH_RE.test(curHead)) {
- const t = curHead.replace(ANY_TAG, "").replace(/\s+/g, " ").trim();
- if (t) return t;
- }
- return null;
- };
- function addBlock(keyIdx, block) {
- const key = relpath + "|" + keyIdx;
- if (seen.has(key) || !block.length) return;
- seen.add(key);
- const d = entryDate();
- if (!d) return;
- let t = null;
- const tm = block[0][1].slice(0, 30).match(TIME_RE);
- if (tm) t = [+tm[1], +tm[2], +(tm[3] || 0)];
- order++;
- entries.push({ date: d, time: t, order, relpath, note,
- breadcrumb: breadcrumb(), lines: block,
- pageLevel: false, notes: [note], anchor: anchorOf() });
- }
- function collectChildren(i, parentIndent) {
- const kids = [], raw = [];
- let j = i + 1;
- while (j < lines.length) {
- const ln = lines[j];
- if (ln.trim() === "") {
- let k = j + 1;
- while (k < lines.length && lines[k].trim() === "") k++;
- if (k < lines.length && LIST_RE.test(lines[k]) && indentOf(lines[k]) > parentIndent) { j = k; continue; }
- break;
- }
- const mi = ln.match(LIST_RE);
- if (mi && indentOf(ln) > parentIndent) {
- raw.push([indentOf(ln), (mi[3] || "").replace(/\s+$/, "")]);
- j++;
- } else break;
- }
- for (const [ind, txt] of raw) kids.push([Math.floor((ind - parentIndent) / 2), txt]);
- return kids;
- }
-
- for (let i = fm.bodyStart; i < lines.length; i++) {
- const ln = lines[i];
- if (FENCE_RE.test(ln)) { inFence = !inFence; continue; }
- if (inFence) continue;
- if (inPct) { if (ln.includes("%%")) inPct = false; continue; }
- if (inHtml) { if (ln.includes("-->")) inHtml = false; continue; }
- const stripped = ln.trim();
- if (stripped.startsWith("%%") && !stripped.slice(2).includes("%%")) { inPct = true; continue; }
- if (stripped.startsWith("<!--") && !stripped.includes("-->")) { inHtml = true; continue; }
-
- const hm = ln.match(HEAD_RE);
- if (hm) {
- const htext = hm[2];
- const dh = htext.match(DATEH_RE);
- if (dh) { curDateHead = vdate(+dh[1], +dh[2], +dh[3]); curHead = null; curHeadRaw = null; }
- else { curHead = htext; curHeadRaw = htext.trim(); }
- if (TAG_RE.test(cleanMatch(htext))) {
- const sec = [[0, "**" + stripTag(htext) + "**"]];
- let j = i + 1;
- const level = hm[1].length;
- let count = 0;
- while (j < lines.length && count < 300) {
- const sl = lines[j];
- const shm = sl.match(HEAD_RE);
- if (shm && shm[1].length <= level) break;
- if (FENCE_RE.test(sl)) break;
- if (sl.trim()) {
- const smi = sl.match(LIST_RE);
- if (smi) sec.push([1 + Math.floor(indentOf(sl) / 2), (smi[3] || "").replace(/\s+$/, "")]);
- else sec.push([1, sl.trim()]);
- count++;
- }
- j++;
- }
- addBlock(i, sec);
- }
- continue;
- }
-
- const clean = cleanMatch(ln);
- if (!TAG_RE.test(clean)) continue;
-
- const mi = ln.match(LIST_RE);
- if (mi) {
- const ind = indentOf(ln);
- const itemText = mi[3] || "";
- const cleaned = stripTag(itemText);
- const kids = collectChildren(i, ind);
- addBlock(i, cleaned ? [[0, cleaned], ...kids] : [[0, itemText.trim()], ...kids]);
- continue;
- }
-
- // 独立标签行:向下吸收 list items,或向上归属到紧邻的块
- if (ln.replace(ANY_TAG, "").trim() === "" || ln.replace(ANY_TAG, "").trim() === "%%") {
- let sec = [];
- let j = i + 1;
- while (j < lines.length) {
- const mj = lines[j].match(LIST_RE);
- if (mj) { sec.push([indentOf(lines[j]), (mj[3] || "").replace(/\s+$/, "")]); j++; }
- else break;
- }
- let keyIdx = i;
- if (!sec.length) {
- const up = [];
- let k = i - 1, bulletIdx = null;
- while (k >= fm.bodyStart) {
- const bk = lines[k];
- if (bk.trim() === "" || HEAD_RE.test(bk) || FENCE_RE.test(bk)) break;
- up.push(k);
- if (LIST_RE.test(bk)) { bulletIdx = k; break; }
- k--;
- }
- if (bulletIdx !== null) {
- const inds = [];
- for (let bl = bulletIdx; bl < i; bl++) {
- const mb = lines[bl].match(LIST_RE);
- if (mb) { inds.push(indentOf(lines[bl])); sec.push([indentOf(lines[bl]), (mb[3] || "").replace(/\s+$/, "")]); }
- else sec.push([(inds.length ? inds[inds.length - 1] + 2 : 0), lines[bl].trim()]);
- }
- keyIdx = bulletIdx;
- } else if (up.length) {
- sec = up.slice().reverse().map(k2 => [0, lines[k2].trim()]);
- } else {
- // 兜底:标签行紧挨在某个标题下方(中间只有空行)→ 捕获该标题下的整个小节
- let h = i - 1;
- while (h >= fm.bodyStart && lines[h].trim() === "") h--;
- const hmk = h >= fm.bodyStart ? lines[h].match(HEAD_RE) : null;
- if (hmk) {
- const level = hmk[1].length;
- const title = hmk[2].replace(ANY_TAG, "").replace(/\s+/g, " ").trim();
- const sec2 = title ? [[0, "**" + title + "**"]] : [];
- let j2 = i + 1, count2 = 0;
- while (j2 < lines.length && count2 < 300) {
- const sl = lines[j2];
- const shm = sl.match(HEAD_RE);
- if (shm && shm[1].length <= level) break;
- if (FENCE_RE.test(sl)) break;
- if (sl.trim()) {
- const smi = sl.match(LIST_RE);
- if (smi) sec2.push([1 + Math.floor(indentOf(sl) / 2), (smi[3] || "").replace(/\s+$/, "")]);
- else sec2.push([1, sl.trim().replace(/^>\s*/, "")]);
- count2++;
- }
- j2++;
- }
- if (sec2.length) sec = sec2;
- }
- }
- }
- if (sec.length) {
- const mi0 = Math.min(...sec.map(x => x[0]));
- addBlock(keyIdx, sec.map(([ind, t]) => [Math.floor((ind - mi0) / 2), t]));
- }
- continue;
- }
-
- // 普通段落带标签:吸收到空行为止
- const para = [stripTag(clean)];
- let j = i + 1;
- while (j < lines.length) {
- const nl = lines[j];
- if (nl.trim() === "" || HEAD_RE.test(nl) || LIST_RE.test(nl) || FENCE_RE.test(nl)) break;
- para.push(nl.trim());
- j++;
- }
- addBlock(i, [[0, para.filter(p => p).join(" ")]]);
- }
-
- // frontmatter 标签 → 页面级条目(附正文开头预览)
- if (TAG_LIST.some(t => fm.tags.has(t))) {
- const d = fm.date || fileDate || mtimeDate;
- if (d) {
- order++;
- // 收集正文文本至 PREV_CHARS:超过上限的长文在渲染时截断并附「查看全文」链接
- const prev = [];
- let prevTotal = 0, prevCount = 0;
- for (let k = fm.bodyStart; k < lines.length && prevCount < 200; k++) {
- const s = lines[k].trim();
- if (!s || s.startsWith("#") || s === "---") continue;
- const clean = s.replace(ANY_TAG, "");
- if (!clean) continue;
- prev.push(clean);
- prevTotal += clean.length;
- prevCount++;
- if (prevTotal > PREV_CHARS) break; // 超过上限:已足够判断,停止收集
- }
- const preview = prev.join(" ");
- entries.push({ date: d, time: null, order, relpath, note, breadcrumb: null,
- lines: preview ? [[0, preview]] : [],
- pageLevel: true, notes: [note], anchor: null });
- }
- }
- return entries;
-}
-
-// ---------------- 扫描整个 vault ----------------
-const currentPath = (dv.current() && dv.current().file) ? dv.current().file.path : null;
-let all = [];
-for (const f of app.vault.getMarkdownFiles()) {
- if (f.path === currentPath || f.path.endsWith(".excalidraw.md")) continue;
- if (SKIP_DIRS.some(d => f.path.startsWith(d + "/"))) continue;
- if (f.path.split("/").some(seg => seg.startsWith("."))) continue; // 排除所有隐藏文件/文件夹(.开头)
- if (excludedPaths.some(p => f.path === p || f.path.startsWith(p.endsWith("/") ? p : p + "/"))) continue;
- let text;
- try { text = await app.vault.cachedRead(f); } catch (e) { continue; }
- if (text.slice(0, 1000).includes(MARKER)) continue; // 跳过 tagfeed 生成的静态页
- all = all.concat(scanText(text, f.basename, f.path, dateFromName(f.basename), new Date(f.stat.mtime)));
-}
-
-// ---------------- 去重:同日期+同内容合并,来源并排 ----------------
-const timeCmp = (a, b) => (a[0] - b[0]) || (a[1] - b[1]) || (a[2] - b[2]);
-const mergedMap = new Map();
-for (const e of all) {
- const fp = e.date.getTime() + "|" + e.lines.map(x => x[0] + ":" + x[1]).join("\n");
- const m = mergedMap.get(fp);
- if (m) {
- if (!m.notes.includes(e.note)) m.notes.push(e.note);
- if (e.time && (!m.time || timeCmp(e.time, m.time) < 0)) m.time = e.time;
- // 若先扫到的来源没有面包屑/锚点,而后面的有,则采用后者(与扫描顺序无关,结果确定)
- if (!m.breadcrumb && e.breadcrumb) {
- m.breadcrumb = e.breadcrumb;
- m.anchor = e.anchor;
- m.relpath = e.relpath;
- m.note = e.note;
- }
- } else mergedMap.set(fp, e);
-}
-const entries = [...mergedMap.values()];
-
-// ---------------- 渲染:按日期倒序 ----------------
-const byDate = new Map();
-for (const e of entries) {
- const k = e.date.getTime();
- if (!byDate.has(k)) byDate.set(k, []);
- byDate.get(k).push(e);
-}
-const dates = [...byDate.keys()].sort((a, b) => b - a);
-const noteSet = new Set();
-for (const e of entries) for (const n of e.notes) noteSet.add(n);
-
-const out = [];
-out.push("> **" + entries.length + "** 个内容块 | **" + noteSet.size + "** 篇笔记 | 按日期倒序 | 随笔记变动自动刷新");
-
-const pad2 = x => String(x).padStart(2, "0");
-const fmtDate = d => d.getFullYear() + "-" + pad2(d.getMonth() + 1) + "-" + pad2(d.getDate());
-const invTime = t => t.map(x => pad2(99 - x)).join("");
-
-for (const dk of dates) {
- const d = new Date(dk);
- out.push("## 📅 " + fmtDate(d) + " · " + WEEKDAYS[d.getDay() === 0 ? 6 : d.getDay() - 1]);
- out.push("");
- const group = byDate.get(dk);
-
- const sub = new Map(); // (笔记+面包屑) -> entries
- for (const e of group) {
- const key = e.relpath + "|" + (e.breadcrumb || "");
- if (!sub.has(key)) sub.set(key, []);
- sub.get(key).push(e);
- }
- const subKey = ([key, es]) => {
- const times = es.filter(e => e.time).map(e => e.time);
- const best = times.length ? times.reduce((a, b) => (timeCmp(a, b) > 0 ? a : b)) : null;
- return (best ? "0" : "1") + (best ? invTime(best) : "000000") + key;
- };
-
- for (const [key, es] of [...sub.entries()].sort((a, b) => (subKey(a) < subKey(b) ? -1 : subKey(a) > subKey(b) ? 1 : 0))) {
- const noteList = [];
- for (const e of es) for (const n of e.notes) if (!noteList.includes(n)) noteList.push(n);
- noteList.sort();
- const crumb = es.map(e => e.breadcrumb).find(b => b) || null;
- out.push("- 🔗 " + noteList.map(n => "[[" + n + "]]").join(" · ") + (crumb ? " · " + crumb : ""));
- const sorted = es.slice().sort((a, b) => {
- const ka = (a.time ? "0" : "1") + (a.time ? invTime(a.time) : "") + String(a.order).padStart(6, "0");
- const kb = (b.time ? "0" : "1") + (b.time ? invTime(b.time) : "") + String(b.order).padStart(6, "0");
- return ka < kb ? -1 : ka > kb ? 1 : 0;
- });
- for (const e of sorted) {
- let bl = e.lines, truncated = false;
- const totalChars = bl.reduce((s, x) => s + x[1].length, 0);
- if (totalChars > PREV_CHARS) { // 只有超过上限才截断;上限内全文展示
- truncated = true;
- const shown = [];
- let chars = 0;
- for (const [lvl, txt] of bl) {
- if (chars + txt.length > PREV_CHARS) {
- if (PREV_CHARS - chars > 0) shown.push([lvl, txt.slice(0, PREV_CHARS - chars) + "…"]);
- break;
- }
- shown.push([lvl, txt]);
- chars += txt.length;
- }
- bl = shown;
- }
- for (const [lvl, txt] of bl) out.push("\t".repeat(lvl + 1) + "- " + txt);
- if (truncated) {
- const target = e.note + (e.anchor ? "#" + e.anchor : "");
- const label = e.pageLevel ? "页面级标签 · 查看全文" : "查看全文";
- out.push("\t- [[" + target + "|↗ " + label + "]]");
- }
- }
- out.push("");
- }
- out.push("");
-}
-dv.paragraph(out.join("\n"));
-
-} catch (err) {
- dv.paragraph("> ❌ tagfeed-live 执行失败:" + err.message);
- console.error("tagfeed-live:", err);
-}
-
-```
diff --git a/00index/0inbox/copilot-conversation.md b/00index/0inbox/copilot-conversation.md
deleted file mode 100644
index 31489d0..0000000
--- a/00index/0inbox/copilot-conversation.md
+++ /dev/null
@@ -1,438 +0,0 @@
----
-tags:
- - tagfeed-live
-cssclasses:
- - tagfeed
-words:
- 2026-08-23: 2728
- 2026-08-24: 2755
----
-
-# 🏷 #copilot-conversation(实时聚合页)
-
-> 本页由 DataviewJS 实时计算,笔记有变动时自动刷新,无需任何后台服务。
-
-```dataviewjs
-// ============================================================
-// tagfeed-live:Logseq 风格标签实时聚合(标准 DataviewJS 脚本)
-//
-// 用法:
-// 1. 新建一个笔记(例如 00index/标签聚合/标签名.md)
-// 2. 在笔记里插入一个 dataviewjs 代码块(三个反引号 + dataviewjs),
-// 把本脚本全部粘贴进去
-// 3. 只需要改下面「配置区」的两行:
-// - targetTag 换成你要聚合的标签(不带 #,中英文都可以;多个标签写成数组 ["a", "b"])
-// - excludedPaths 换成你想排除的文件夹
-// 4. 完成。以后任何笔记里出现/删除这个标签,本页自动刷新,
-// 不需要跑任何命令(Dataview 会在文件变动后自动重新渲染)。
-// ============================================================
-
-// ---------------- 配置区(通常只改这里) ----------------
-const targetTag = "copilot-conversation"; // 要聚合的标签(不带 #);多个标签用数组:["lyric", "歌词"]
-const excludedPaths = ["X2.Archived", "00index", "X0.Clippings", "X1.Knomo", "02DS/02copilot", ".agents", ".claude", ".copilot", ".opencode", ".smart-env", ".workbuddy", ".trash"]; // 要排除的文件夹/路径前缀(vault 相对路径)
- // 例如:["00index", "模板", "X1.Knomo"]
-// 补充说明:
-// - 所有隐藏文件/文件夹(. 开头:.agents/.claude/.copilot/.opencode/.smart-env/.workbuddy/.trash 等)与 X.Attachment 始终跳过
-// - tagfeed 系统页会自动跳过:静态聚合页(文件头含 <!-- tagfeed 标记)
-// 和实时页(frontmatter 带 tagfeed / tagfeed-live 标签),
-// 无需手动排除 00index
-// - 本脚本所在的当前笔记也会自动跳过
-// ---------------------------------------------------------
-
-try {
-const SKIP_DIRS = [".obsidian", ".git", ".trash", "X.Attachment"];
-const MARKER = "<!-- tagfeed";
-const WEEKDAYS = ["周一", "周二", "周三", "周四", "周五", "周六", "周日"];
-const PREV_CHARS = 500; // 预览字数上限:≤上限全文展示;>上限截断并附「查看全文」链接
-
-const esc = s => String(s).replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
-const TAG_LIST = Array.isArray(targetTag) ? targetTag : [targetTag];
-const TAG_RE = new RegExp("(?<![\\w\\u4e00-\\u9fff#])#(?:" + TAG_LIST.map(esc).join("|") + ")(?![\\w\\u4e00-\\u9fff/\\-])");
-const ANY_TAG = /(?<![\w#])#([\w\u4e00-\u9fff][\w\u4e00-\u9fff/\-]*)/g;
-const LIST_RE = /^(\s*)([-*+]|\d+[.)])(?:\s+(.*))?$/;
-const HEAD_RE = /^(#{1,6})\s+(.*)$/;
-const DATEH_RE = /\[\[(\d{4})-(\d{2})-(\d{2})\]\]/;
-const TIME_RE = /(\d{1,2}):(\d{2})(?::(\d{2}))?/;
-const FENCE_RE = /^\s*(`{3,}|~~~)/;
-const indentOf = ln => { // tab 按 2 空格计(与 Python 版一致)
- const expanded = ln.replace(/\t/g, " ");
- return expanded.length - expanded.replace(/^ +/, "").length;
-};
-
-function cleanMatch(ln) {
- let s = ln.replace(/`[^`\n]*`/g, "");
- s = s.replace(/%%[\s\S]*?%%/g, "");
- s = s.replace(/<!--[\s\S]*?-->/g, "");
- return s;
-}
-function vdate(y, m, d) {
- const dt = new Date(y, m - 1, d);
- return (dt.getFullYear() === y && dt.getMonth() === m - 1 && dt.getDate() === d) ? dt : null;
-}
-function dateFromName(name) { // 文件名含 YYYY-MM-DD,或日记命名 26.0805 -> 2026-08-05
- const m1 = name.match(/(\d{4})-(\d{2})-(\d{2})/);
- if (m1) {
- const d1 = vdate(+m1[1], +m1[2], +m1[3]);
- if (d1) return d1;
- }
- const m = name.match(/^(\d{2})\.(\d{2})(\d{2})(?:\D|$)/);
- return m ? vdate(2000 + +m[1], +m[2], +m[3]) : null;
-}
-function parseFrontmatter(text) {
- const lines = text.split("\n");
- if (!lines.length || lines[0].trim() !== "---") return { tags: new Set(), date: null, bodyStart: 0 };
- const n = Math.min(lines.length, 200);
- for (let i = 1; i < n; i++) {
- const t = lines[i].trim();
- if (t === "---" || t === "...") {
- const block = lines.slice(1, i).join("\n");
- const tags = new Set();
- const im = block.match(/^tags:\s*\[(.*?)\]/m);
- if (im) {
- for (const x0 of im[1].split(",")) {
- const x = x0.trim().replace(/^["']|["']$/g, "").replace(/^#/, "").trim();
- if (x) tags.add(x);
- }
- } else {
- const bm = block.match(/^tags:\s*\n((?:[ \t]+-[ \t]+.*\n?)+)/m);
- if (bm) for (const ln of bm[1].split("\n")) {
- const m2 = ln.match(/^\s*-\s*["']?([\w\u4e00-\u9fff/\-]+)["']?\s*$/);
- if (m2) tags.add(m2[1]);
- }
- }
- let fdate = null;
- for (const key of ["date", "created", "published"]) {
- const dm = block.match(new RegExp("^" + key + ":\\s*[\"']?(\\d{4})-(\\d{2})-(\\d{2})", "m"));
- if (dm) { fdate = vdate(+dm[1], +dm[2], +dm[3]); break; }
- }
- return { tags, date: fdate, bodyStart: i + 1 };
- }
- }
- return { tags: new Set(), date: null, bodyStart: 0 };
-}
-const stripTag = s => String(s).replace(TAG_RE, "").replace(/[ \t]{2,}/g, " ").trim();
-
-function scanText(text, note, relpath, fileDate, mtimeDate) {
- const fm = parseFrontmatter(text);
- if (fm.tags.has("tagfeed") || fm.tags.has("tagfeed-live")) return []; // 跳过 tagfeed 系统页(静态聚合页/实时页)
- const lines = text.split("\n");
- const entries = [], seen = new Set();
- let order = 0, inFence = false, inPct = false, inHtml = false;
- let curHead = null, curHeadRaw = null, curDateHead = null;
-
- const entryDate = () => curDateHead || fileDate || fm.date || mtimeDate;
- const anchorOf = () => (curDateHead ? null : curHeadRaw);
- const breadcrumb = () => {
- if (curHead && !DATEH_RE.test(curHead)) {
- const t = curHead.replace(ANY_TAG, "").replace(/\s+/g, " ").trim();
- if (t) return t;
- }
- return null;
- };
- function addBlock(keyIdx, block) {
- const key = relpath + "|" + keyIdx;
- if (seen.has(key) || !block.length) return;
- seen.add(key);
- const d = entryDate();
- if (!d) return;
- let t = null;
- const tm = block[0][1].slice(0, 30).match(TIME_RE);
- if (tm) t = [+tm[1], +tm[2], +(tm[3] || 0)];
- order++;
- entries.push({ date: d, time: t, order, relpath, note,
- breadcrumb: breadcrumb(), lines: block,
- pageLevel: false, notes: [note], anchor: anchorOf() });
- }
- function collectChildren(i, parentIndent) {
- const kids = [], raw = [];
- let j = i + 1;
- while (j < lines.length) {
- const ln = lines[j];
- if (ln.trim() === "") {
- let k = j + 1;
- while (k < lines.length && lines[k].trim() === "") k++;
- if (k < lines.length && LIST_RE.test(lines[k]) && indentOf(lines[k]) > parentIndent) { j = k; continue; }
- break;
- }
- const mi = ln.match(LIST_RE);
- if (mi && indentOf(ln) > parentIndent) {
- raw.push([indentOf(ln), (mi[3] || "").replace(/\s+$/, "")]);
- j++;
- } else break;
- }
- for (const [ind, txt] of raw) kids.push([Math.floor((ind - parentIndent) / 2), txt]);
- return kids;
- }
-
- for (let i = fm.bodyStart; i < lines.length; i++) {
- const ln = lines[i];
- if (FENCE_RE.test(ln)) { inFence = !inFence; continue; }
- if (inFence) continue;
- if (inPct) { if (ln.includes("%%")) inPct = false; continue; }
- if (inHtml) { if (ln.includes("-->")) inHtml = false; continue; }
- const stripped = ln.trim();
- if (stripped.startsWith("%%") && !stripped.slice(2).includes("%%")) { inPct = true; continue; }
- if (stripped.startsWith("<!--") && !stripped.includes("-->")) { inHtml = true; continue; }
-
- const hm = ln.match(HEAD_RE);
- if (hm) {
- const htext = hm[2];
- const dh = htext.match(DATEH_RE);
- if (dh) { curDateHead = vdate(+dh[1], +dh[2], +dh[3]); curHead = null; curHeadRaw = null; }
- else { curHead = htext; curHeadRaw = htext.trim(); }
- if (TAG_RE.test(cleanMatch(htext))) {
- const sec = [[0, "**" + stripTag(htext) + "**"]];
- let j = i + 1;
- const level = hm[1].length;
- let count = 0;
- while (j < lines.length && count < 300) {
- const sl = lines[j];
- const shm = sl.match(HEAD_RE);
- if (shm && shm[1].length <= level) break;
- if (FENCE_RE.test(sl)) break;
- if (sl.trim()) {
- const smi = sl.match(LIST_RE);
- if (smi) sec.push([1 + Math.floor(indentOf(sl) / 2), (smi[3] || "").replace(/\s+$/, "")]);
- else sec.push([1, sl.trim()]);
- count++;
- }
- j++;
- }
- addBlock(i, sec);
- }
- continue;
- }
-
- const clean = cleanMatch(ln);
- if (!TAG_RE.test(clean)) continue;
-
- const mi = ln.match(LIST_RE);
- if (mi) {
- const ind = indentOf(ln);
- const itemText = mi[3] || "";
- const cleaned = stripTag(itemText);
- const kids = collectChildren(i, ind);
- addBlock(i, cleaned ? [[0, cleaned], ...kids] : [[0, itemText.trim()], ...kids]);
- continue;
- }
-
- // 独立标签行:向下吸收 list items,或向上归属到紧邻的块
- if (ln.replace(ANY_TAG, "").trim() === "" || ln.replace(ANY_TAG, "").trim() === "%%") {
- let sec = [];
- let j = i + 1;
- while (j < lines.length) {
- const mj = lines[j].match(LIST_RE);
- if (mj) { sec.push([indentOf(lines[j]), (mj[3] || "").replace(/\s+$/, "")]); j++; }
- else break;
- }
- let keyIdx = i;
- if (!sec.length) {
- const up = [];
- let k = i - 1, bulletIdx = null;
- while (k >= fm.bodyStart) {
- const bk = lines[k];
- if (bk.trim() === "" || HEAD_RE.test(bk) || FENCE_RE.test(bk)) break;
- up.push(k);
- if (LIST_RE.test(bk)) { bulletIdx = k; break; }
- k--;
- }
- if (bulletIdx !== null) {
- const inds = [];
- for (let bl = bulletIdx; bl < i; bl++) {
- const mb = lines[bl].match(LIST_RE);
- if (mb) { inds.push(indentOf(lines[bl])); sec.push([indentOf(lines[bl]), (mb[3] || "").replace(/\s+$/, "")]); }
- else sec.push([(inds.length ? inds[inds.length - 1] + 2 : 0), lines[bl].trim()]);
- }
- keyIdx = bulletIdx;
- } else if (up.length) {
- sec = up.slice().reverse().map(k2 => [0, lines[k2].trim()]);
- } else {
- // 兜底:标签行紧挨在某个标题下方(中间只有空行)→ 捕获该标题下的整个小节
- let h = i - 1;
- while (h >= fm.bodyStart && lines[h].trim() === "") h--;
- const hmk = h >= fm.bodyStart ? lines[h].match(HEAD_RE) : null;
- if (hmk) {
- const level = hmk[1].length;
- const title = hmk[2].replace(ANY_TAG, "").replace(/\s+/g, " ").trim();
- const sec2 = title ? [[0, "**" + title + "**"]] : [];
- let j2 = i + 1, count2 = 0;
- while (j2 < lines.length && count2 < 300) {
- const sl = lines[j2];
- const shm = sl.match(HEAD_RE);
- if (shm && shm[1].length <= level) break;
- if (FENCE_RE.test(sl)) break;
- if (sl.trim()) {
- const smi = sl.match(LIST_RE);
- if (smi) sec2.push([1 + Math.floor(indentOf(sl) / 2), (smi[3] || "").replace(/\s+$/, "")]);
- else sec2.push([1, sl.trim().replace(/^>\s*/, "")]);
- count2++;
- }
- j2++;
- }
- if (sec2.length) sec = sec2;
- }
- }
- }
- if (sec.length) {
- const mi0 = Math.min(...sec.map(x => x[0]));
- addBlock(keyIdx, sec.map(([ind, t]) => [Math.floor((ind - mi0) / 2), t]));
- }
- continue;
- }
-
- // 普通段落带标签:吸收到空行为止
- const para = [stripTag(clean)];
- let j = i + 1;
- while (j < lines.length) {
- const nl = lines[j];
- if (nl.trim() === "" || HEAD_RE.test(nl) || LIST_RE.test(nl) || FENCE_RE.test(nl)) break;
- para.push(nl.trim());
- j++;
- }
- addBlock(i, [[0, para.filter(p => p).join(" ")]]);
- }
-
- // frontmatter 标签 → 页面级条目(附正文开头预览)
- if (TAG_LIST.some(t => fm.tags.has(t))) {
- const d = fm.date || fileDate || mtimeDate;
- if (d) {
- order++;
- // 收集正文文本至 PREV_CHARS:超过上限的长文在渲染时截断并附「查看全文」链接
- const prev = [];
- let prevTotal = 0, prevCount = 0;
- for (let k = fm.bodyStart; k < lines.length && prevCount < 200; k++) {
- const s = lines[k].trim();
- if (!s || s.startsWith("#") || s === "---") continue;
- const clean = s.replace(ANY_TAG, "");
- if (!clean) continue;
- prev.push(clean);
- prevTotal += clean.length;
- prevCount++;
- if (prevTotal > PREV_CHARS) break; // 超过上限:已足够判断,停止收集
- }
- const preview = prev.join(" ");
- entries.push({ date: d, time: null, order, relpath, note, breadcrumb: null,
- lines: preview ? [[0, preview]] : [],
- pageLevel: true, notes: [note], anchor: null });
- }
- }
- return entries;
-}
-
-// ---------------- 扫描整个 vault ----------------
-const currentPath = (dv.current() && dv.current().file) ? dv.current().file.path : null;
-let all = [];
-for (const f of app.vault.getMarkdownFiles()) {
- if (f.path === currentPath || f.path.endsWith(".excalidraw.md")) continue;
- if (SKIP_DIRS.some(d => f.path.startsWith(d + "/"))) continue;
- if (f.path.split("/").some(seg => seg.startsWith("."))) continue; // 排除所有隐藏文件/文件夹(.开头)
- if (excludedPaths.some(p => f.path === p || f.path.startsWith(p.endsWith("/") ? p : p + "/"))) continue;
- let text;
- try { text = await app.vault.cachedRead(f); } catch (e) { continue; }
- if (text.slice(0, 1000).includes(MARKER)) continue; // 跳过 tagfeed 生成的静态页
- all = all.concat(scanText(text, f.basename, f.path, dateFromName(f.basename), new Date(f.stat.mtime)));
-}
-
-// ---------------- 去重:同日期+同内容合并,来源并排 ----------------
-const timeCmp = (a, b) => (a[0] - b[0]) || (a[1] - b[1]) || (a[2] - b[2]);
-const mergedMap = new Map();
-for (const e of all) {
- const fp = e.date.getTime() + "|" + e.lines.map(x => x[0] + ":" + x[1]).join("\n");
- const m = mergedMap.get(fp);
- if (m) {
- if (!m.notes.includes(e.note)) m.notes.push(e.note);
- if (e.time && (!m.time || timeCmp(e.time, m.time) < 0)) m.time = e.time;
- // 若先扫到的来源没有面包屑/锚点,而后面的有,则采用后者(与扫描顺序无关,结果确定)
- if (!m.breadcrumb && e.breadcrumb) {
- m.breadcrumb = e.breadcrumb;
- m.anchor = e.anchor;
- m.relpath = e.relpath;
- m.note = e.note;
- }
- } else mergedMap.set(fp, e);
-}
-const entries = [...mergedMap.values()];
-
-// ---------------- 渲染:按日期倒序 ----------------
-const byDate = new Map();
-for (const e of entries) {
- const k = e.date.getTime();
- if (!byDate.has(k)) byDate.set(k, []);
- byDate.get(k).push(e);
-}
-const dates = [...byDate.keys()].sort((a, b) => b - a);
-const noteSet = new Set();
-for (const e of entries) for (const n of e.notes) noteSet.add(n);
-
-const out = [];
-out.push("> **" + entries.length + "** 个内容块 | **" + noteSet.size + "** 篇笔记 | 按日期倒序 | 随笔记变动自动刷新");
-
-const pad2 = x => String(x).padStart(2, "0");
-const fmtDate = d => d.getFullYear() + "-" + pad2(d.getMonth() + 1) + "-" + pad2(d.getDate());
-const invTime = t => t.map(x => pad2(99 - x)).join("");
-
-for (const dk of dates) {
- const d = new Date(dk);
- out.push("## 📅 " + fmtDate(d) + " · " + WEEKDAYS[d.getDay() === 0 ? 6 : d.getDay() - 1]);
- out.push("");
- const group = byDate.get(dk);
-
- const sub = new Map(); // (笔记+面包屑) -> entries
- for (const e of group) {
- const key = e.relpath + "|" + (e.breadcrumb || "");
- if (!sub.has(key)) sub.set(key, []);
- sub.get(key).push(e);
- }
- const subKey = ([key, es]) => {
- const times = es.filter(e => e.time).map(e => e.time);
- const best = times.length ? times.reduce((a, b) => (timeCmp(a, b) > 0 ? a : b)) : null;
- return (best ? "0" : "1") + (best ? invTime(best) : "000000") + key;
- };
-
- for (const [key, es] of [...sub.entries()].sort((a, b) => (subKey(a) < subKey(b) ? -1 : subKey(a) > subKey(b) ? 1 : 0))) {
- const noteList = [];
- for (const e of es) for (const n of e.notes) if (!noteList.includes(n)) noteList.push(n);
- noteList.sort();
- const crumb = es.map(e => e.breadcrumb).find(b => b) || null;
- out.push("- 🔗 " + noteList.map(n => "[[" + n + "]]").join(" · ") + (crumb ? " · " + crumb : ""));
- const sorted = es.slice().sort((a, b) => {
- const ka = (a.time ? "0" : "1") + (a.time ? invTime(a.time) : "") + String(a.order).padStart(6, "0");
- const kb = (b.time ? "0" : "1") + (b.time ? invTime(b.time) : "") + String(b.order).padStart(6, "0");
- return ka < kb ? -1 : ka > kb ? 1 : 0;
- });
- for (const e of sorted) {
- let bl = e.lines, truncated = false;
- const totalChars = bl.reduce((s, x) => s + x[1].length, 0);
- if (totalChars > PREV_CHARS) { // 只有超过上限才截断;上限内全文展示
- truncated = true;
- const shown = [];
- let chars = 0;
- for (const [lvl, txt] of bl) {
- if (chars + txt.length > PREV_CHARS) {
- if (PREV_CHARS - chars > 0) shown.push([lvl, txt.slice(0, PREV_CHARS - chars) + "…"]);
- break;
- }
- shown.push([lvl, txt]);
- chars += txt.length;
- }
- bl = shown;
- }
- for (const [lvl, txt] of bl) out.push("\t".repeat(lvl + 1) + "- " + txt);
- if (truncated) {
- const target = e.note + (e.anchor ? "#" + e.anchor : "");
- const label = e.pageLevel ? "页面级标签 · 查看全文" : "查看全文";
- out.push("\t- [[" + target + "|↗ " + label + "]]");
- }
- }
- out.push("");
- }
- out.push("");
-}
-dv.paragraph(out.join("\n"));
-
-} catch (err) {
- dv.paragraph("> ❌ tagfeed-live 执行失败:" + err.message);
- console.error("tagfeed-live:", err);
-}
-
-```
diff --git a/00index/0inbox/introduction.md b/00index/0inbox/introduction.md
deleted file mode 100644
index 48693f5..0000000
--- a/00index/0inbox/introduction.md
+++ /dev/null
@@ -1,437 +0,0 @@
----
-tags:
- - tagfeed-live
-cssclasses:
- - tagfeed
-words:
- 2026-08-23: 2728
----
-
-# 🏷 #introduction(实时聚合页)
-
-> 本页由 DataviewJS 实时计算,笔记有变动时自动刷新,无需任何后台服务。
-
-```dataviewjs
-// ============================================================
-// tagfeed-live:Logseq 风格标签实时聚合(标准 DataviewJS 脚本)
-//
-// 用法:
-// 1. 新建一个笔记(例如 00index/标签聚合/标签名.md)
-// 2. 在笔记里插入一个 dataviewjs 代码块(三个反引号 + dataviewjs),
-// 把本脚本全部粘贴进去
-// 3. 只需要改下面「配置区」的两行:
-// - targetTag 换成你要聚合的标签(不带 #,中英文都可以;多个标签写成数组 ["a", "b"])
-// - excludedPaths 换成你想排除的文件夹
-// 4. 完成。以后任何笔记里出现/删除这个标签,本页自动刷新,
-// 不需要跑任何命令(Dataview 会在文件变动后自动重新渲染)。
-// ============================================================
-
-// ---------------- 配置区(通常只改这里) ----------------
-const targetTag = "introduction"; // 要聚合的标签(不带 #);多个标签用数组:["lyric", "歌词"]
-const excludedPaths = ["X2.Archived", "00index", "X0.Clippings", "X1.Knomo", "02DS/02copilot", ".agents", ".claude", ".copilot", ".opencode", ".smart-env", ".workbuddy", ".trash"]; // 要排除的文件夹/路径前缀(vault 相对路径)
- // 例如:["00index", "模板", "X1.Knomo"]
-// 补充说明:
-// - 所有隐藏文件/文件夹(. 开头:.agents/.claude/.copilot/.opencode/.smart-env/.workbuddy/.trash 等)与 X.Attachment 始终跳过
-// - tagfeed 系统页会自动跳过:静态聚合页(文件头含 <!-- tagfeed 标记)
-// 和实时页(frontmatter 带 tagfeed / tagfeed-live 标签),
-// 无需手动排除 00index
-// - 本脚本所在的当前笔记也会自动跳过
-// ---------------------------------------------------------
-
-try {
-const SKIP_DIRS = [".obsidian", ".git", ".trash", "X.Attachment"];
-const MARKER = "<!-- tagfeed";
-const WEEKDAYS = ["周一", "周二", "周三", "周四", "周五", "周六", "周日"];
-const PREV_CHARS = 500; // 预览字数上限:≤上限全文展示;>上限截断并附「查看全文」链接
-
-const esc = s => String(s).replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
-const TAG_LIST = Array.isArray(targetTag) ? targetTag : [targetTag];
-const TAG_RE = new RegExp("(?<![\\w\\u4e00-\\u9fff#])#(?:" + TAG_LIST.map(esc).join("|") + ")(?![\\w\\u4e00-\\u9fff/\\-])");
-const ANY_TAG = /(?<![\w#])#([\w\u4e00-\u9fff][\w\u4e00-\u9fff/\-]*)/g;
-const LIST_RE = /^(\s*)([-*+]|\d+[.)])(?:\s+(.*))?$/;
-const HEAD_RE = /^(#{1,6})\s+(.*)$/;
-const DATEH_RE = /\[\[(\d{4})-(\d{2})-(\d{2})\]\]/;
-const TIME_RE = /(\d{1,2}):(\d{2})(?::(\d{2}))?/;
-const FENCE_RE = /^\s*(`{3,}|~~~)/;
-const indentOf = ln => { // tab 按 2 空格计(与 Python 版一致)
- const expanded = ln.replace(/\t/g, " ");
- return expanded.length - expanded.replace(/^ +/, "").length;
-};
-
-function cleanMatch(ln) {
- let s = ln.replace(/`[^`\n]*`/g, "");
- s = s.replace(/%%[\s\S]*?%%/g, "");
- s = s.replace(/<!--[\s\S]*?-->/g, "");
- return s;
-}
-function vdate(y, m, d) {
- const dt = new Date(y, m - 1, d);
- return (dt.getFullYear() === y && dt.getMonth() === m - 1 && dt.getDate() === d) ? dt : null;
-}
-function dateFromName(name) { // 文件名含 YYYY-MM-DD,或日记命名 26.0805 -> 2026-08-05
- const m1 = name.match(/(\d{4})-(\d{2})-(\d{2})/);
- if (m1) {
- const d1 = vdate(+m1[1], +m1[2], +m1[3]);
- if (d1) return d1;
- }
- const m = name.match(/^(\d{2})\.(\d{2})(\d{2})(?:\D|$)/);
- return m ? vdate(2000 + +m[1], +m[2], +m[3]) : null;
-}
-function parseFrontmatter(text) {
- const lines = text.split("\n");
- if (!lines.length || lines[0].trim() !== "---") return { tags: new Set(), date: null, bodyStart: 0 };
- const n = Math.min(lines.length, 200);
- for (let i = 1; i < n; i++) {
- const t = lines[i].trim();
- if (t === "---" || t === "...") {
- const block = lines.slice(1, i).join("\n");
- const tags = new Set();
- const im = block.match(/^tags:\s*\[(.*?)\]/m);
- if (im) {
- for (const x0 of im[1].split(",")) {
- const x = x0.trim().replace(/^["']|["']$/g, "").replace(/^#/, "").trim();
- if (x) tags.add(x);
- }
- } else {
- const bm = block.match(/^tags:\s*\n((?:[ \t]+-[ \t]+.*\n?)+)/m);
- if (bm) for (const ln of bm[1].split("\n")) {
- const m2 = ln.match(/^\s*-\s*["']?([\w\u4e00-\u9fff/\-]+)["']?\s*$/);
- if (m2) tags.add(m2[1]);
- }
- }
- let fdate = null;
- for (const key of ["date", "created", "published"]) {
- const dm = block.match(new RegExp("^" + key + ":\\s*[\"']?(\\d{4})-(\\d{2})-(\\d{2})", "m"));
- if (dm) { fdate = vdate(+dm[1], +dm[2], +dm[3]); break; }
- }
- return { tags, date: fdate, bodyStart: i + 1 };
- }
- }
- return { tags: new Set(), date: null, bodyStart: 0 };
-}
-const stripTag = s => String(s).replace(TAG_RE, "").replace(/[ \t]{2,}/g, " ").trim();
-
-function scanText(text, note, relpath, fileDate, mtimeDate) {
- const fm = parseFrontmatter(text);
- if (fm.tags.has("tagfeed") || fm.tags.has("tagfeed-live")) return []; // 跳过 tagfeed 系统页(静态聚合页/实时页)
- const lines = text.split("\n");
- const entries = [], seen = new Set();
- let order = 0, inFence = false, inPct = false, inHtml = false;
- let curHead = null, curHeadRaw = null, curDateHead = null;
-
- const entryDate = () => curDateHead || fileDate || fm.date || mtimeDate;
- const anchorOf = () => (curDateHead ? null : curHeadRaw);
- const breadcrumb = () => {
- if (curHead && !DATEH_RE.test(curHead)) {
- const t = curHead.replace(ANY_TAG, "").replace(/\s+/g, " ").trim();
- if (t) return t;
- }
- return null;
- };
- function addBlock(keyIdx, block) {
- const key = relpath + "|" + keyIdx;
- if (seen.has(key) || !block.length) return;
- seen.add(key);
- const d = entryDate();
- if (!d) return;
- let t = null;
- const tm = block[0][1].slice(0, 30).match(TIME_RE);
- if (tm) t = [+tm[1], +tm[2], +(tm[3] || 0)];
- order++;
- entries.push({ date: d, time: t, order, relpath, note,
- breadcrumb: breadcrumb(), lines: block,
- pageLevel: false, notes: [note], anchor: anchorOf() });
- }
- function collectChildren(i, parentIndent) {
- const kids = [], raw = [];
- let j = i + 1;
- while (j < lines.length) {
- const ln = lines[j];
- if (ln.trim() === "") {
- let k = j + 1;
- while (k < lines.length && lines[k].trim() === "") k++;
- if (k < lines.length && LIST_RE.test(lines[k]) && indentOf(lines[k]) > parentIndent) { j = k; continue; }
- break;
- }
- const mi = ln.match(LIST_RE);
- if (mi && indentOf(ln) > parentIndent) {
- raw.push([indentOf(ln), (mi[3] || "").replace(/\s+$/, "")]);
- j++;
- } else break;
- }
- for (const [ind, txt] of raw) kids.push([Math.floor((ind - parentIndent) / 2), txt]);
- return kids;
- }
-
- for (let i = fm.bodyStart; i < lines.length; i++) {
- const ln = lines[i];
- if (FENCE_RE.test(ln)) { inFence = !inFence; continue; }
- if (inFence) continue;
- if (inPct) { if (ln.includes("%%")) inPct = false; continue; }
- if (inHtml) { if (ln.includes("-->")) inHtml = false; continue; }
- const stripped = ln.trim();
- if (stripped.startsWith("%%") && !stripped.slice(2).includes("%%")) { inPct = true; continue; }
- if (stripped.startsWith("<!--") && !stripped.includes("-->")) { inHtml = true; continue; }
-
- const hm = ln.match(HEAD_RE);
- if (hm) {
- const htext = hm[2];
- const dh = htext.match(DATEH_RE);
- if (dh) { curDateHead = vdate(+dh[1], +dh[2], +dh[3]); curHead = null; curHeadRaw = null; }
- else { curHead = htext; curHeadRaw = htext.trim(); }
- if (TAG_RE.test(cleanMatch(htext))) {
- const sec = [[0, "**" + stripTag(htext) + "**"]];
- let j = i + 1;
- const level = hm[1].length;
- let count = 0;
- while (j < lines.length && count < 300) {
- const sl = lines[j];
- const shm = sl.match(HEAD_RE);
- if (shm && shm[1].length <= level) break;
- if (FENCE_RE.test(sl)) break;
- if (sl.trim()) {
- const smi = sl.match(LIST_RE);
- if (smi) sec.push([1 + Math.floor(indentOf(sl) / 2), (smi[3] || "").replace(/\s+$/, "")]);
- else sec.push([1, sl.trim()]);
- count++;
- }
- j++;
- }
- addBlock(i, sec);
- }
- continue;
- }
-
- const clean = cleanMatch(ln);
- if (!TAG_RE.test(clean)) continue;
-
- const mi = ln.match(LIST_RE);
- if (mi) {
- const ind = indentOf(ln);
- const itemText = mi[3] || "";
- const cleaned = stripTag(itemText);
- const kids = collectChildren(i, ind);
- addBlock(i, cleaned ? [[0, cleaned], ...kids] : [[0, itemText.trim()], ...kids]);
- continue;
- }
-
- // 独立标签行:向下吸收 list items,或向上归属到紧邻的块
- if (ln.replace(ANY_TAG, "").trim() === "" || ln.replace(ANY_TAG, "").trim() === "%%") {
- let sec = [];
- let j = i + 1;
- while (j < lines.length) {
- const mj = lines[j].match(LIST_RE);
- if (mj) { sec.push([indentOf(lines[j]), (mj[3] || "").replace(/\s+$/, "")]); j++; }
- else break;
- }
- let keyIdx = i;
- if (!sec.length) {
- const up = [];
- let k = i - 1, bulletIdx = null;
- while (k >= fm.bodyStart) {
- const bk = lines[k];
- if (bk.trim() === "" || HEAD_RE.test(bk) || FENCE_RE.test(bk)) break;
- up.push(k);
- if (LIST_RE.test(bk)) { bulletIdx = k; break; }
- k--;
- }
- if (bulletIdx !== null) {
- const inds = [];
- for (let bl = bulletIdx; bl < i; bl++) {
- const mb = lines[bl].match(LIST_RE);
- if (mb) { inds.push(indentOf(lines[bl])); sec.push([indentOf(lines[bl]), (mb[3] || "").replace(/\s+$/, "")]); }
- else sec.push([(inds.length ? inds[inds.length - 1] + 2 : 0), lines[bl].trim()]);
- }
- keyIdx = bulletIdx;
- } else if (up.length) {
- sec = up.slice().reverse().map(k2 => [0, lines[k2].trim()]);
- } else {
- // 兜底:标签行紧挨在某个标题下方(中间只有空行)→ 捕获该标题下的整个小节
- let h = i - 1;
- while (h >= fm.bodyStart && lines[h].trim() === "") h--;
- const hmk = h >= fm.bodyStart ? lines[h].match(HEAD_RE) : null;
- if (hmk) {
- const level = hmk[1].length;
- const title = hmk[2].replace(ANY_TAG, "").replace(/\s+/g, " ").trim();
- const sec2 = title ? [[0, "**" + title + "**"]] : [];
- let j2 = i + 1, count2 = 0;
- while (j2 < lines.length && count2 < 300) {
- const sl = lines[j2];
- const shm = sl.match(HEAD_RE);
- if (shm && shm[1].length <= level) break;
- if (FENCE_RE.test(sl)) break;
- if (sl.trim()) {
- const smi = sl.match(LIST_RE);
- if (smi) sec2.push([1 + Math.floor(indentOf(sl) / 2), (smi[3] || "").replace(/\s+$/, "")]);
- else sec2.push([1, sl.trim().replace(/^>\s*/, "")]);
- count2++;
- }
- j2++;
- }
- if (sec2.length) sec = sec2;
- }
- }
- }
- if (sec.length) {
- const mi0 = Math.min(...sec.map(x => x[0]));
- addBlock(keyIdx, sec.map(([ind, t]) => [Math.floor((ind - mi0) / 2), t]));
- }
- continue;
- }
-
- // 普通段落带标签:吸收到空行为止
- const para = [stripTag(clean)];
- let j = i + 1;
- while (j < lines.length) {
- const nl = lines[j];
- if (nl.trim() === "" || HEAD_RE.test(nl) || LIST_RE.test(nl) || FENCE_RE.test(nl)) break;
- para.push(nl.trim());
- j++;
- }
- addBlock(i, [[0, para.filter(p => p).join(" ")]]);
- }
-
- // frontmatter 标签 → 页面级条目(附正文开头预览)
- if (TAG_LIST.some(t => fm.tags.has(t))) {
- const d = fm.date || fileDate || mtimeDate;
- if (d) {
- order++;
- // 收集正文文本至 PREV_CHARS:超过上限的长文在渲染时截断并附「查看全文」链接
- const prev = [];
- let prevTotal = 0, prevCount = 0;
- for (let k = fm.bodyStart; k < lines.length && prevCount < 200; k++) {
- const s = lines[k].trim();
- if (!s || s.startsWith("#") || s === "---") continue;
- const clean = s.replace(ANY_TAG, "");
- if (!clean) continue;
- prev.push(clean);
- prevTotal += clean.length;
- prevCount++;
- if (prevTotal > PREV_CHARS) break; // 超过上限:已足够判断,停止收集
- }
- const preview = prev.join(" ");
- entries.push({ date: d, time: null, order, relpath, note, breadcrumb: null,
- lines: preview ? [[0, preview]] : [],
- pageLevel: true, notes: [note], anchor: null });
- }
- }
- return entries;
-}
-
-// ---------------- 扫描整个 vault ----------------
-const currentPath = (dv.current() && dv.current().file) ? dv.current().file.path : null;
-let all = [];
-for (const f of app.vault.getMarkdownFiles()) {
- if (f.path === currentPath || f.path.endsWith(".excalidraw.md")) continue;
- if (SKIP_DIRS.some(d => f.path.startsWith(d + "/"))) continue;
- if (f.path.split("/").some(seg => seg.startsWith("."))) continue; // 排除所有隐藏文件/文件夹(.开头)
- if (excludedPaths.some(p => f.path === p || f.path.startsWith(p.endsWith("/") ? p : p + "/"))) continue;
- let text;
- try { text = await app.vault.cachedRead(f); } catch (e) { continue; }
- if (text.slice(0, 1000).includes(MARKER)) continue; // 跳过 tagfeed 生成的静态页
- all = all.concat(scanText(text, f.basename, f.path, dateFromName(f.basename), new Date(f.stat.mtime)));
-}
-
-// ---------------- 去重:同日期+同内容合并,来源并排 ----------------
-const timeCmp = (a, b) => (a[0] - b[0]) || (a[1] - b[1]) || (a[2] - b[2]);
-const mergedMap = new Map();
-for (const e of all) {
- const fp = e.date.getTime() + "|" + e.lines.map(x => x[0] + ":" + x[1]).join("\n");
- const m = mergedMap.get(fp);
- if (m) {
- if (!m.notes.includes(e.note)) m.notes.push(e.note);
- if (e.time && (!m.time || timeCmp(e.time, m.time) < 0)) m.time = e.time;
- // 若先扫到的来源没有面包屑/锚点,而后面的有,则采用后者(与扫描顺序无关,结果确定)
- if (!m.breadcrumb && e.breadcrumb) {
- m.breadcrumb = e.breadcrumb;
- m.anchor = e.anchor;
- m.relpath = e.relpath;
- m.note = e.note;
- }
- } else mergedMap.set(fp, e);
-}
-const entries = [...mergedMap.values()];
-
-// ---------------- 渲染:按日期倒序 ----------------
-const byDate = new Map();
-for (const e of entries) {
- const k = e.date.getTime();
- if (!byDate.has(k)) byDate.set(k, []);
- byDate.get(k).push(e);
-}
-const dates = [...byDate.keys()].sort((a, b) => b - a);
-const noteSet = new Set();
-for (const e of entries) for (const n of e.notes) noteSet.add(n);
-
-const out = [];
-out.push("> **" + entries.length + "** 个内容块 | **" + noteSet.size + "** 篇笔记 | 按日期倒序 | 随笔记变动自动刷新");
-
-const pad2 = x => String(x).padStart(2, "0");
-const fmtDate = d => d.getFullYear() + "-" + pad2(d.getMonth() + 1) + "-" + pad2(d.getDate());
-const invTime = t => t.map(x => pad2(99 - x)).join("");
-
-for (const dk of dates) {
- const d = new Date(dk);
- out.push("## 📅 " + fmtDate(d) + " · " + WEEKDAYS[d.getDay() === 0 ? 6 : d.getDay() - 1]);
- out.push("");
- const group = byDate.get(dk);
-
- const sub = new Map(); // (笔记+面包屑) -> entries
- for (const e of group) {
- const key = e.relpath + "|" + (e.breadcrumb || "");
- if (!sub.has(key)) sub.set(key, []);
- sub.get(key).push(e);
- }
- const subKey = ([key, es]) => {
- const times = es.filter(e => e.time).map(e => e.time);
- const best = times.length ? times.reduce((a, b) => (timeCmp(a, b) > 0 ? a : b)) : null;
- return (best ? "0" : "1") + (best ? invTime(best) : "000000") + key;
- };
-
- for (const [key, es] of [...sub.entries()].sort((a, b) => (subKey(a) < subKey(b) ? -1 : subKey(a) > subKey(b) ? 1 : 0))) {
- const noteList = [];
- for (const e of es) for (const n of e.notes) if (!noteList.includes(n)) noteList.push(n);
- noteList.sort();
- const crumb = es.map(e => e.breadcrumb).find(b => b) || null;
- out.push("- 🔗 " + noteList.map(n => "[[" + n + "]]").join(" · ") + (crumb ? " · " + crumb : ""));
- const sorted = es.slice().sort((a, b) => {
- const ka = (a.time ? "0" : "1") + (a.time ? invTime(a.time) : "") + String(a.order).padStart(6, "0");
- const kb = (b.time ? "0" : "1") + (b.time ? invTime(b.time) : "") + String(b.order).padStart(6, "0");
- return ka < kb ? -1 : ka > kb ? 1 : 0;
- });
- for (const e of sorted) {
- let bl = e.lines, truncated = false;
- const totalChars = bl.reduce((s, x) => s + x[1].length, 0);
- if (totalChars > PREV_CHARS) { // 只有超过上限才截断;上限内全文展示
- truncated = true;
- const shown = [];
- let chars = 0;
- for (const [lvl, txt] of bl) {
- if (chars + txt.length > PREV_CHARS) {
- if (PREV_CHARS - chars > 0) shown.push([lvl, txt.slice(0, PREV_CHARS - chars) + "…"]);
- break;
- }
- shown.push([lvl, txt]);
- chars += txt.length;
- }
- bl = shown;
- }
- for (const [lvl, txt] of bl) out.push("\t".repeat(lvl + 1) + "- " + txt);
- if (truncated) {
- const target = e.note + (e.anchor ? "#" + e.anchor : "");
- const label = e.pageLevel ? "页面级标签 · 查看全文" : "查看全文";
- out.push("\t- [[" + target + "|↗ " + label + "]]");
- }
- }
- out.push("");
- }
- out.push("");
-}
-dv.paragraph(out.join("\n"));
-
-} catch (err) {
- dv.paragraph("> ❌ tagfeed-live 执行失败:" + err.message);
- console.error("tagfeed-live:", err);
-}
-
-```
diff --git a/00index/0inbox/ooo.md b/00index/0inbox/ooo.md
deleted file mode 100644
index a7a0c3b..0000000
--- a/00index/0inbox/ooo.md
+++ /dev/null
@@ -1,438 +0,0 @@
----
-tags:
- - tagfeed-live
-cssclasses:
- - tagfeed
-words:
- 2026-08-23: 2728
- 2026-08-24: 2755
----
-
-# 🏷 #ooo(实时聚合页)
-
-> 本页由 DataviewJS 实时计算,笔记有变动时自动刷新,无需任何后台服务。
-
-```dataviewjs
-// ============================================================
-// tagfeed-live:Logseq 风格标签实时聚合(标准 DataviewJS 脚本)
-//
-// 用法:
-// 1. 新建一个笔记(例如 00index/标签聚合/标签名.md)
-// 2. 在笔记里插入一个 dataviewjs 代码块(三个反引号 + dataviewjs),
-// 把本脚本全部粘贴进去
-// 3. 只需要改下面「配置区」的两行:
-// - targetTag 换成你要聚合的标签(不带 #,中英文都可以;多个标签写成数组 ["a", "b"])
-// - excludedPaths 换成你想排除的文件夹
-// 4. 完成。以后任何笔记里出现/删除这个标签,本页自动刷新,
-// 不需要跑任何命令(Dataview 会在文件变动后自动重新渲染)。
-// ============================================================
-
-// ---------------- 配置区(通常只改这里) ----------------
-const targetTag = "ooo"; // 要聚合的标签(不带 #);多个标签用数组:["lyric", "歌词"]
-const excludedPaths = ["X2.Archived", "00index", "X0.Clippings", "X1.Knomo", "02DS/02copilot", ".agents", ".claude", ".copilot", ".opencode", ".smart-env", ".workbuddy", ".trash"]; // 要排除的文件夹/路径前缀(vault 相对路径)
- // 例如:["00index", "模板", "X1.Knomo"]
-// 补充说明:
-// - 所有隐藏文件/文件夹(. 开头:.agents/.claude/.copilot/.opencode/.smart-env/.workbuddy/.trash 等)与 X.Attachment 始终跳过
-// - tagfeed 系统页会自动跳过:静态聚合页(文件头含 <!-- tagfeed 标记)
-// 和实时页(frontmatter 带 tagfeed / tagfeed-live 标签),
-// 无需手动排除 00index
-// - 本脚本所在的当前笔记也会自动跳过
-// ---------------------------------------------------------
-
-try {
-const SKIP_DIRS = [".obsidian", ".git", ".trash", "X.Attachment"];
-const MARKER = "<!-- tagfeed";
-const WEEKDAYS = ["周一", "周二", "周三", "周四", "周五", "周六", "周日"];
-const PREV_CHARS = 500; // 预览字数上限:≤上限全文展示;>上限截断并附「查看全文」链接
-
-const esc = s => String(s).replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
-const TAG_LIST = Array.isArray(targetTag) ? targetTag : [targetTag];
-const TAG_RE = new RegExp("(?<![\\w\\u4e00-\\u9fff#])#(?:" + TAG_LIST.map(esc).join("|") + ")(?![\\w\\u4e00-\\u9fff/\\-])");
-const ANY_TAG = /(?<![\w#])#([\w\u4e00-\u9fff][\w\u4e00-\u9fff/\-]*)/g;
-const LIST_RE = /^(\s*)([-*+]|\d+[.)])(?:\s+(.*))?$/;
-const HEAD_RE = /^(#{1,6})\s+(.*)$/;
-const DATEH_RE = /\[\[(\d{4})-(\d{2})-(\d{2})\]\]/;
-const TIME_RE = /(\d{1,2}):(\d{2})(?::(\d{2}))?/;
-const FENCE_RE = /^\s*(`{3,}|~~~)/;
-const indentOf = ln => { // tab 按 2 空格计(与 Python 版一致)
- const expanded = ln.replace(/\t/g, " ");
- return expanded.length - expanded.replace(/^ +/, "").length;
-};
-
-function cleanMatch(ln) {
- let s = ln.replace(/`[^`\n]*`/g, "");
- s = s.replace(/%%[\s\S]*?%%/g, "");
- s = s.replace(/<!--[\s\S]*?-->/g, "");
- return s;
-}
-function vdate(y, m, d) {
- const dt = new Date(y, m - 1, d);
- return (dt.getFullYear() === y && dt.getMonth() === m - 1 && dt.getDate() === d) ? dt : null;
-}
-function dateFromName(name) { // 文件名含 YYYY-MM-DD,或日记命名 26.0805 -> 2026-08-05
- const m1 = name.match(/(\d{4})-(\d{2})-(\d{2})/);
- if (m1) {
- const d1 = vdate(+m1[1], +m1[2], +m1[3]);
- if (d1) return d1;
- }
- const m = name.match(/^(\d{2})\.(\d{2})(\d{2})(?:\D|$)/);
- return m ? vdate(2000 + +m[1], +m[2], +m[3]) : null;
-}
-function parseFrontmatter(text) {
- const lines = text.split("\n");
- if (!lines.length || lines[0].trim() !== "---") return { tags: new Set(), date: null, bodyStart: 0 };
- const n = Math.min(lines.length, 200);
- for (let i = 1; i < n; i++) {
- const t = lines[i].trim();
- if (t === "---" || t === "...") {
- const block = lines.slice(1, i).join("\n");
- const tags = new Set();
- const im = block.match(/^tags:\s*\[(.*?)\]/m);
- if (im) {
- for (const x0 of im[1].split(",")) {
- const x = x0.trim().replace(/^["']|["']$/g, "").replace(/^#/, "").trim();
- if (x) tags.add(x);
- }
- } else {
- const bm = block.match(/^tags:\s*\n((?:[ \t]+-[ \t]+.*\n?)+)/m);
- if (bm) for (const ln of bm[1].split("\n")) {
- const m2 = ln.match(/^\s*-\s*["']?([\w\u4e00-\u9fff/\-]+)["']?\s*$/);
- if (m2) tags.add(m2[1]);
- }
- }
- let fdate = null;
- for (const key of ["date", "created", "published"]) {
- const dm = block.match(new RegExp("^" + key + ":\\s*[\"']?(\\d{4})-(\\d{2})-(\\d{2})", "m"));
- if (dm) { fdate = vdate(+dm[1], +dm[2], +dm[3]); break; }
- }
- return { tags, date: fdate, bodyStart: i + 1 };
- }
- }
- return { tags: new Set(), date: null, bodyStart: 0 };
-}
-const stripTag = s => String(s).replace(TAG_RE, "").replace(/[ \t]{2,}/g, " ").trim();
-
-function scanText(text, note, relpath, fileDate, mtimeDate) {
- const fm = parseFrontmatter(text);
- if (fm.tags.has("tagfeed") || fm.tags.has("tagfeed-live")) return []; // 跳过 tagfeed 系统页(静态聚合页/实时页)
- const lines = text.split("\n");
- const entries = [], seen = new Set();
- let order = 0, inFence = false, inPct = false, inHtml = false;
- let curHead = null, curHeadRaw = null, curDateHead = null;
-
- const entryDate = () => curDateHead || fileDate || fm.date || mtimeDate;
- const anchorOf = () => (curDateHead ? null : curHeadRaw);
- const breadcrumb = () => {
- if (curHead && !DATEH_RE.test(curHead)) {
- const t = curHead.replace(ANY_TAG, "").replace(/\s+/g, " ").trim();
- if (t) return t;
- }
- return null;
- };
- function addBlock(keyIdx, block) {
- const key = relpath + "|" + keyIdx;
- if (seen.has(key) || !block.length) return;
- seen.add(key);
- const d = entryDate();
- if (!d) return;
- let t = null;
- const tm = block[0][1].slice(0, 30).match(TIME_RE);
- if (tm) t = [+tm[1], +tm[2], +(tm[3] || 0)];
- order++;
- entries.push({ date: d, time: t, order, relpath, note,
- breadcrumb: breadcrumb(), lines: block,
- pageLevel: false, notes: [note], anchor: anchorOf() });
- }
- function collectChildren(i, parentIndent) {
- const kids = [], raw = [];
- let j = i + 1;
- while (j < lines.length) {
- const ln = lines[j];
- if (ln.trim() === "") {
- let k = j + 1;
- while (k < lines.length && lines[k].trim() === "") k++;
- if (k < lines.length && LIST_RE.test(lines[k]) && indentOf(lines[k]) > parentIndent) { j = k; continue; }
- break;
- }
- const mi = ln.match(LIST_RE);
- if (mi && indentOf(ln) > parentIndent) {
- raw.push([indentOf(ln), (mi[3] || "").replace(/\s+$/, "")]);
- j++;
- } else break;
- }
- for (const [ind, txt] of raw) kids.push([Math.floor((ind - parentIndent) / 2), txt]);
- return kids;
- }
-
- for (let i = fm.bodyStart; i < lines.length; i++) {
- const ln = lines[i];
- if (FENCE_RE.test(ln)) { inFence = !inFence; continue; }
- if (inFence) continue;
- if (inPct) { if (ln.includes("%%")) inPct = false; continue; }
- if (inHtml) { if (ln.includes("-->")) inHtml = false; continue; }
- const stripped = ln.trim();
- if (stripped.startsWith("%%") && !stripped.slice(2).includes("%%")) { inPct = true; continue; }
- if (stripped.startsWith("<!--") && !stripped.includes("-->")) { inHtml = true; continue; }
-
- const hm = ln.match(HEAD_RE);
- if (hm) {
- const htext = hm[2];
- const dh = htext.match(DATEH_RE);
- if (dh) { curDateHead = vdate(+dh[1], +dh[2], +dh[3]); curHead = null; curHeadRaw = null; }
- else { curHead = htext; curHeadRaw = htext.trim(); }
- if (TAG_RE.test(cleanMatch(htext))) {
- const sec = [[0, "**" + stripTag(htext) + "**"]];
- let j = i + 1;
- const level = hm[1].length;
- let count = 0;
- while (j < lines.length && count < 300) {
- const sl = lines[j];
- const shm = sl.match(HEAD_RE);
- if (shm && shm[1].length <= level) break;
- if (FENCE_RE.test(sl)) break;
- if (sl.trim()) {
- const smi = sl.match(LIST_RE);
- if (smi) sec.push([1 + Math.floor(indentOf(sl) / 2), (smi[3] || "").replace(/\s+$/, "")]);
- else sec.push([1, sl.trim()]);
- count++;
- }
- j++;
- }
- addBlock(i, sec);
- }
- continue;
- }
-
- const clean = cleanMatch(ln);
- if (!TAG_RE.test(clean)) continue;
-
- const mi = ln.match(LIST_RE);
- if (mi) {
- const ind = indentOf(ln);
- const itemText = mi[3] || "";
- const cleaned = stripTag(itemText);
- const kids = collectChildren(i, ind);
- addBlock(i, cleaned ? [[0, cleaned], ...kids] : [[0, itemText.trim()], ...kids]);
- continue;
- }
-
- // 独立标签行:向下吸收 list items,或向上归属到紧邻的块
- if (ln.replace(ANY_TAG, "").trim() === "" || ln.replace(ANY_TAG, "").trim() === "%%") {
- let sec = [];
- let j = i + 1;
- while (j < lines.length) {
- const mj = lines[j].match(LIST_RE);
- if (mj) { sec.push([indentOf(lines[j]), (mj[3] || "").replace(/\s+$/, "")]); j++; }
- else break;
- }
- let keyIdx = i;
- if (!sec.length) {
- const up = [];
- let k = i - 1, bulletIdx = null;
- while (k >= fm.bodyStart) {
- const bk = lines[k];
- if (bk.trim() === "" || HEAD_RE.test(bk) || FENCE_RE.test(bk)) break;
- up.push(k);
- if (LIST_RE.test(bk)) { bulletIdx = k; break; }
- k--;
- }
- if (bulletIdx !== null) {
- const inds = [];
- for (let bl = bulletIdx; bl < i; bl++) {
- const mb = lines[bl].match(LIST_RE);
- if (mb) { inds.push(indentOf(lines[bl])); sec.push([indentOf(lines[bl]), (mb[3] || "").replace(/\s+$/, "")]); }
- else sec.push([(inds.length ? inds[inds.length - 1] + 2 : 0), lines[bl].trim()]);
- }
- keyIdx = bulletIdx;
- } else if (up.length) {
- sec = up.slice().reverse().map(k2 => [0, lines[k2].trim()]);
- } else {
- // 兜底:标签行紧挨在某个标题下方(中间只有空行)→ 捕获该标题下的整个小节
- let h = i - 1;
- while (h >= fm.bodyStart && lines[h].trim() === "") h--;
- const hmk = h >= fm.bodyStart ? lines[h].match(HEAD_RE) : null;
- if (hmk) {
- const level = hmk[1].length;
- const title = hmk[2].replace(ANY_TAG, "").replace(/\s+/g, " ").trim();
- const sec2 = title ? [[0, "**" + title + "**"]] : [];
- let j2 = i + 1, count2 = 0;
- while (j2 < lines.length && count2 < 300) {
- const sl = lines[j2];
- const shm = sl.match(HEAD_RE);
- if (shm && shm[1].length <= level) break;
- if (FENCE_RE.test(sl)) break;
- if (sl.trim()) {
- const smi = sl.match(LIST_RE);
- if (smi) sec2.push([1 + Math.floor(indentOf(sl) / 2), (smi[3] || "").replace(/\s+$/, "")]);
- else sec2.push([1, sl.trim().replace(/^>\s*/, "")]);
- count2++;
- }
- j2++;
- }
- if (sec2.length) sec = sec2;
- }
- }
- }
- if (sec.length) {
- const mi0 = Math.min(...sec.map(x => x[0]));
- addBlock(keyIdx, sec.map(([ind, t]) => [Math.floor((ind - mi0) / 2), t]));
- }
- continue;
- }
-
- // 普通段落带标签:吸收到空行为止
- const para = [stripTag(clean)];
- let j = i + 1;
- while (j < lines.length) {
- const nl = lines[j];
- if (nl.trim() === "" || HEAD_RE.test(nl) || LIST_RE.test(nl) || FENCE_RE.test(nl)) break;
- para.push(nl.trim());
- j++;
- }
- addBlock(i, [[0, para.filter(p => p).join(" ")]]);
- }
-
- // frontmatter 标签 → 页面级条目(附正文开头预览)
- if (TAG_LIST.some(t => fm.tags.has(t))) {
- const d = fm.date || fileDate || mtimeDate;
- if (d) {
- order++;
- // 收集正文文本至 PREV_CHARS:超过上限的长文在渲染时截断并附「查看全文」链接
- const prev = [];
- let prevTotal = 0, prevCount = 0;
- for (let k = fm.bodyStart; k < lines.length && prevCount < 200; k++) {
- const s = lines[k].trim();
- if (!s || s.startsWith("#") || s === "---") continue;
- const clean = s.replace(ANY_TAG, "");
- if (!clean) continue;
- prev.push(clean);
- prevTotal += clean.length;
- prevCount++;
- if (prevTotal > PREV_CHARS) break; // 超过上限:已足够判断,停止收集
- }
- const preview = prev.join(" ");
- entries.push({ date: d, time: null, order, relpath, note, breadcrumb: null,
- lines: preview ? [[0, preview]] : [],
- pageLevel: true, notes: [note], anchor: null });
- }
- }
- return entries;
-}
-
-// ---------------- 扫描整个 vault ----------------
-const currentPath = (dv.current() && dv.current().file) ? dv.current().file.path : null;
-let all = [];
-for (const f of app.vault.getMarkdownFiles()) {
- if (f.path === currentPath || f.path.endsWith(".excalidraw.md")) continue;
- if (SKIP_DIRS.some(d => f.path.startsWith(d + "/"))) continue;
- if (f.path.split("/").some(seg => seg.startsWith("."))) continue; // 排除所有隐藏文件/文件夹(.开头)
- if (excludedPaths.some(p => f.path === p || f.path.startsWith(p.endsWith("/") ? p : p + "/"))) continue;
- let text;
- try { text = await app.vault.cachedRead(f); } catch (e) { continue; }
- if (text.slice(0, 1000).includes(MARKER)) continue; // 跳过 tagfeed 生成的静态页
- all = all.concat(scanText(text, f.basename, f.path, dateFromName(f.basename), new Date(f.stat.mtime)));
-}
-
-// ---------------- 去重:同日期+同内容合并,来源并排 ----------------
-const timeCmp = (a, b) => (a[0] - b[0]) || (a[1] - b[1]) || (a[2] - b[2]);
-const mergedMap = new Map();
-for (const e of all) {
- const fp = e.date.getTime() + "|" + e.lines.map(x => x[0] + ":" + x[1]).join("\n");
- const m = mergedMap.get(fp);
- if (m) {
- if (!m.notes.includes(e.note)) m.notes.push(e.note);
- if (e.time && (!m.time || timeCmp(e.time, m.time) < 0)) m.time = e.time;
- // 若先扫到的来源没有面包屑/锚点,而后面的有,则采用后者(与扫描顺序无关,结果确定)
- if (!m.breadcrumb && e.breadcrumb) {
- m.breadcrumb = e.breadcrumb;
- m.anchor = e.anchor;
- m.relpath = e.relpath;
- m.note = e.note;
- }
- } else mergedMap.set(fp, e);
-}
-const entries = [...mergedMap.values()];
-
-// ---------------- 渲染:按日期倒序 ----------------
-const byDate = new Map();
-for (const e of entries) {
- const k = e.date.getTime();
- if (!byDate.has(k)) byDate.set(k, []);
- byDate.get(k).push(e);
-}
-const dates = [...byDate.keys()].sort((a, b) => b - a);
-const noteSet = new Set();
-for (const e of entries) for (const n of e.notes) noteSet.add(n);
-
-const out = [];
-out.push("> **" + entries.length + "** 个内容块 | **" + noteSet.size + "** 篇笔记 | 按日期倒序 | 随笔记变动自动刷新");
-
-const pad2 = x => String(x).padStart(2, "0");
-const fmtDate = d => d.getFullYear() + "-" + pad2(d.getMonth() + 1) + "-" + pad2(d.getDate());
-const invTime = t => t.map(x => pad2(99 - x)).join("");
-
-for (const dk of dates) {
- const d = new Date(dk);
- out.push("## 📅 " + fmtDate(d) + " · " + WEEKDAYS[d.getDay() === 0 ? 6 : d.getDay() - 1]);
- out.push("");
- const group = byDate.get(dk);
-
- const sub = new Map(); // (笔记+面包屑) -> entries
- for (const e of group) {
- const key = e.relpath + "|" + (e.breadcrumb || "");
- if (!sub.has(key)) sub.set(key, []);
- sub.get(key).push(e);
- }
- const subKey = ([key, es]) => {
- const times = es.filter(e => e.time).map(e => e.time);
- const best = times.length ? times.reduce((a, b) => (timeCmp(a, b) > 0 ? a : b)) : null;
- return (best ? "0" : "1") + (best ? invTime(best) : "000000") + key;
- };
-
- for (const [key, es] of [...sub.entries()].sort((a, b) => (subKey(a) < subKey(b) ? -1 : subKey(a) > subKey(b) ? 1 : 0))) {
- const noteList = [];
- for (const e of es) for (const n of e.notes) if (!noteList.includes(n)) noteList.push(n);
- noteList.sort();
- const crumb = es.map(e => e.breadcrumb).find(b => b) || null;
- out.push("- 🔗 " + noteList.map(n => "[[" + n + "]]").join(" · ") + (crumb ? " · " + crumb : ""));
- const sorted = es.slice().sort((a, b) => {
- const ka = (a.time ? "0" : "1") + (a.time ? invTime(a.time) : "") + String(a.order).padStart(6, "0");
- const kb = (b.time ? "0" : "1") + (b.time ? invTime(b.time) : "") + String(b.order).padStart(6, "0");
- return ka < kb ? -1 : ka > kb ? 1 : 0;
- });
- for (const e of sorted) {
- let bl = e.lines, truncated = false;
- const totalChars = bl.reduce((s, x) => s + x[1].length, 0);
- if (totalChars > PREV_CHARS) { // 只有超过上限才截断;上限内全文展示
- truncated = true;
- const shown = [];
- let chars = 0;
- for (const [lvl, txt] of bl) {
- if (chars + txt.length > PREV_CHARS) {
- if (PREV_CHARS - chars > 0) shown.push([lvl, txt.slice(0, PREV_CHARS - chars) + "…"]);
- break;
- }
- shown.push([lvl, txt]);
- chars += txt.length;
- }
- bl = shown;
- }
- for (const [lvl, txt] of bl) out.push("\t".repeat(lvl + 1) + "- " + txt);
- if (truncated) {
- const target = e.note + (e.anchor ? "#" + e.anchor : "");
- const label = e.pageLevel ? "页面级标签 · 查看全文" : "查看全文";
- out.push("\t- [[" + target + "|↗ " + label + "]]");
- }
- }
- out.push("");
- }
- out.push("");
-}
-dv.paragraph(out.join("\n"));
-
-} catch (err) {
- dv.paragraph("> ❌ tagfeed-live 执行失败:" + err.message);
- console.error("tagfeed-live:", err);
-}
-
-```
diff --git a/00index/0inbox/project.md b/00index/0inbox/project.md
deleted file mode 100644
index 0e77867..0000000
--- a/00index/0inbox/project.md
+++ /dev/null
@@ -1,437 +0,0 @@
----
-tags:
- - tagfeed-live
-cssclasses:
- - tagfeed
-words:
- 2026-08-23: 2728
----
-
-# 🏷 #project(实时聚合页)
-
-> 本页由 DataviewJS 实时计算,笔记有变动时自动刷新,无需任何后台服务。
-
-```dataviewjs
-// ============================================================
-// tagfeed-live:Logseq 风格标签实时聚合(标准 DataviewJS 脚本)
-//
-// 用法:
-// 1. 新建一个笔记(例如 00index/标签聚合/标签名.md)
-// 2. 在笔记里插入一个 dataviewjs 代码块(三个反引号 + dataviewjs),
-// 把本脚本全部粘贴进去
-// 3. 只需要改下面「配置区」的两行:
-// - targetTag 换成你要聚合的标签(不带 #,中英文都可以;多个标签写成数组 ["a", "b"])
-// - excludedPaths 换成你想排除的文件夹
-// 4. 完成。以后任何笔记里出现/删除这个标签,本页自动刷新,
-// 不需要跑任何命令(Dataview 会在文件变动后自动重新渲染)。
-// ============================================================
-
-// ---------------- 配置区(通常只改这里) ----------------
-const targetTag = "project"; // 要聚合的标签(不带 #);多个标签用数组:["lyric", "歌词"]
-const excludedPaths = ["X2.Archived", "00index", "X0.Clippings", "X1.Knomo", "02DS/02copilot", ".agents", ".claude", ".copilot", ".opencode", ".smart-env", ".workbuddy", ".trash"]; // 要排除的文件夹/路径前缀(vault 相对路径)
- // 例如:["00index", "模板", "X1.Knomo"]
-// 补充说明:
-// - 所有隐藏文件/文件夹(. 开头:.agents/.claude/.copilot/.opencode/.smart-env/.workbuddy/.trash 等)与 X.Attachment 始终跳过
-// - tagfeed 系统页会自动跳过:静态聚合页(文件头含 <!-- tagfeed 标记)
-// 和实时页(frontmatter 带 tagfeed / tagfeed-live 标签),
-// 无需手动排除 00index
-// - 本脚本所在的当前笔记也会自动跳过
-// ---------------------------------------------------------
-
-try {
-const SKIP_DIRS = [".obsidian", ".git", ".trash", "X.Attachment"];
-const MARKER = "<!-- tagfeed";
-const WEEKDAYS = ["周一", "周二", "周三", "周四", "周五", "周六", "周日"];
-const PREV_CHARS = 500; // 预览字数上限:≤上限全文展示;>上限截断并附「查看全文」链接
-
-const esc = s => String(s).replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
-const TAG_LIST = Array.isArray(targetTag) ? targetTag : [targetTag];
-const TAG_RE = new RegExp("(?<![\\w\\u4e00-\\u9fff#])#(?:" + TAG_LIST.map(esc).join("|") + ")(?![\\w\\u4e00-\\u9fff/\\-])");
-const ANY_TAG = /(?<![\w#])#([\w\u4e00-\u9fff][\w\u4e00-\u9fff/\-]*)/g;
-const LIST_RE = /^(\s*)([-*+]|\d+[.)])(?:\s+(.*))?$/;
-const HEAD_RE = /^(#{1,6})\s+(.*)$/;
-const DATEH_RE = /\[\[(\d{4})-(\d{2})-(\d{2})\]\]/;
-const TIME_RE = /(\d{1,2}):(\d{2})(?::(\d{2}))?/;
-const FENCE_RE = /^\s*(`{3,}|~~~)/;
-const indentOf = ln => { // tab 按 2 空格计(与 Python 版一致)
- const expanded = ln.replace(/\t/g, " ");
- return expanded.length - expanded.replace(/^ +/, "").length;
-};
-
-function cleanMatch(ln) {
- let s = ln.replace(/`[^`\n]*`/g, "");
- s = s.replace(/%%[\s\S]*?%%/g, "");
- s = s.replace(/<!--[\s\S]*?-->/g, "");
- return s;
-}
-function vdate(y, m, d) {
- const dt = new Date(y, m - 1, d);
- return (dt.getFullYear() === y && dt.getMonth() === m - 1 && dt.getDate() === d) ? dt : null;
-}
-function dateFromName(name) { // 文件名含 YYYY-MM-DD,或日记命名 26.0805 -> 2026-08-05
- const m1 = name.match(/(\d{4})-(\d{2})-(\d{2})/);
- if (m1) {
- const d1 = vdate(+m1[1], +m1[2], +m1[3]);
- if (d1) return d1;
- }
- const m = name.match(/^(\d{2})\.(\d{2})(\d{2})(?:\D|$)/);
- return m ? vdate(2000 + +m[1], +m[2], +m[3]) : null;
-}
-function parseFrontmatter(text) {
- const lines = text.split("\n");
- if (!lines.length || lines[0].trim() !== "---") return { tags: new Set(), date: null, bodyStart: 0 };
- const n = Math.min(lines.length, 200);
- for (let i = 1; i < n; i++) {
- const t = lines[i].trim();
- if (t === "---" || t === "...") {
- const block = lines.slice(1, i).join("\n");
- const tags = new Set();
- const im = block.match(/^tags:\s*\[(.*?)\]/m);
- if (im) {
- for (const x0 of im[1].split(",")) {
- const x = x0.trim().replace(/^["']|["']$/g, "").replace(/^#/, "").trim();
- if (x) tags.add(x);
- }
- } else {
- const bm = block.match(/^tags:\s*\n((?:[ \t]+-[ \t]+.*\n?)+)/m);
- if (bm) for (const ln of bm[1].split("\n")) {
- const m2 = ln.match(/^\s*-\s*["']?([\w\u4e00-\u9fff/\-]+)["']?\s*$/);
- if (m2) tags.add(m2[1]);
- }
- }
- let fdate = null;
- for (const key of ["date", "created", "published"]) {
- const dm = block.match(new RegExp("^" + key + ":\\s*[\"']?(\\d{4})-(\\d{2})-(\\d{2})", "m"));
- if (dm) { fdate = vdate(+dm[1], +dm[2], +dm[3]); break; }
- }
- return { tags, date: fdate, bodyStart: i + 1 };
- }
- }
- return { tags: new Set(), date: null, bodyStart: 0 };
-}
-const stripTag = s => String(s).replace(TAG_RE, "").replace(/[ \t]{2,}/g, " ").trim();
-
-function scanText(text, note, relpath, fileDate, mtimeDate) {
- const fm = parseFrontmatter(text);
- if (fm.tags.has("tagfeed") || fm.tags.has("tagfeed-live")) return []; // 跳过 tagfeed 系统页(静态聚合页/实时页)
- const lines = text.split("\n");
- const entries = [], seen = new Set();
- let order = 0, inFence = false, inPct = false, inHtml = false;
- let curHead = null, curHeadRaw = null, curDateHead = null;
-
- const entryDate = () => curDateHead || fileDate || fm.date || mtimeDate;
- const anchorOf = () => (curDateHead ? null : curHeadRaw);
- const breadcrumb = () => {
- if (curHead && !DATEH_RE.test(curHead)) {
- const t = curHead.replace(ANY_TAG, "").replace(/\s+/g, " ").trim();
- if (t) return t;
- }
- return null;
- };
- function addBlock(keyIdx, block) {
- const key = relpath + "|" + keyIdx;
- if (seen.has(key) || !block.length) return;
- seen.add(key);
- const d = entryDate();
- if (!d) return;
- let t = null;
- const tm = block[0][1].slice(0, 30).match(TIME_RE);
- if (tm) t = [+tm[1], +tm[2], +(tm[3] || 0)];
- order++;
- entries.push({ date: d, time: t, order, relpath, note,
- breadcrumb: breadcrumb(), lines: block,
- pageLevel: false, notes: [note], anchor: anchorOf() });
- }
- function collectChildren(i, parentIndent) {
- const kids = [], raw = [];
- let j = i + 1;
- while (j < lines.length) {
- const ln = lines[j];
- if (ln.trim() === "") {
- let k = j + 1;
- while (k < lines.length && lines[k].trim() === "") k++;
- if (k < lines.length && LIST_RE.test(lines[k]) && indentOf(lines[k]) > parentIndent) { j = k; continue; }
- break;
- }
- const mi = ln.match(LIST_RE);
- if (mi && indentOf(ln) > parentIndent) {
- raw.push([indentOf(ln), (mi[3] || "").replace(/\s+$/, "")]);
- j++;
- } else break;
- }
- for (const [ind, txt] of raw) kids.push([Math.floor((ind - parentIndent) / 2), txt]);
- return kids;
- }
-
- for (let i = fm.bodyStart; i < lines.length; i++) {
- const ln = lines[i];
- if (FENCE_RE.test(ln)) { inFence = !inFence; continue; }
- if (inFence) continue;
- if (inPct) { if (ln.includes("%%")) inPct = false; continue; }
- if (inHtml) { if (ln.includes("-->")) inHtml = false; continue; }
- const stripped = ln.trim();
- if (stripped.startsWith("%%") && !stripped.slice(2).includes("%%")) { inPct = true; continue; }
- if (stripped.startsWith("<!--") && !stripped.includes("-->")) { inHtml = true; continue; }
-
- const hm = ln.match(HEAD_RE);
- if (hm) {
- const htext = hm[2];
- const dh = htext.match(DATEH_RE);
- if (dh) { curDateHead = vdate(+dh[1], +dh[2], +dh[3]); curHead = null; curHeadRaw = null; }
- else { curHead = htext; curHeadRaw = htext.trim(); }
- if (TAG_RE.test(cleanMatch(htext))) {
- const sec = [[0, "**" + stripTag(htext) + "**"]];
- let j = i + 1;
- const level = hm[1].length;
- let count = 0;
- while (j < lines.length && count < 300) {
- const sl = lines[j];
- const shm = sl.match(HEAD_RE);
- if (shm && shm[1].length <= level) break;
- if (FENCE_RE.test(sl)) break;
- if (sl.trim()) {
- const smi = sl.match(LIST_RE);
- if (smi) sec.push([1 + Math.floor(indentOf(sl) / 2), (smi[3] || "").replace(/\s+$/, "")]);
- else sec.push([1, sl.trim()]);
- count++;
- }
- j++;
- }
- addBlock(i, sec);
- }
- continue;
- }
-
- const clean = cleanMatch(ln);
- if (!TAG_RE.test(clean)) continue;
-
- const mi = ln.match(LIST_RE);
- if (mi) {
- const ind = indentOf(ln);
- const itemText = mi[3] || "";
- const cleaned = stripTag(itemText);
- const kids = collectChildren(i, ind);
- addBlock(i, cleaned ? [[0, cleaned], ...kids] : [[0, itemText.trim()], ...kids]);
- continue;
- }
-
- // 独立标签行:向下吸收 list items,或向上归属到紧邻的块
- if (ln.replace(ANY_TAG, "").trim() === "" || ln.replace(ANY_TAG, "").trim() === "%%") {
- let sec = [];
- let j = i + 1;
- while (j < lines.length) {
- const mj = lines[j].match(LIST_RE);
- if (mj) { sec.push([indentOf(lines[j]), (mj[3] || "").replace(/\s+$/, "")]); j++; }
- else break;
- }
- let keyIdx = i;
- if (!sec.length) {
- const up = [];
- let k = i - 1, bulletIdx = null;
- while (k >= fm.bodyStart) {
- const bk = lines[k];
- if (bk.trim() === "" || HEAD_RE.test(bk) || FENCE_RE.test(bk)) break;
- up.push(k);
- if (LIST_RE.test(bk)) { bulletIdx = k; break; }
- k--;
- }
- if (bulletIdx !== null) {
- const inds = [];
- for (let bl = bulletIdx; bl < i; bl++) {
- const mb = lines[bl].match(LIST_RE);
- if (mb) { inds.push(indentOf(lines[bl])); sec.push([indentOf(lines[bl]), (mb[3] || "").replace(/\s+$/, "")]); }
- else sec.push([(inds.length ? inds[inds.length - 1] + 2 : 0), lines[bl].trim()]);
- }
- keyIdx = bulletIdx;
- } else if (up.length) {
- sec = up.slice().reverse().map(k2 => [0, lines[k2].trim()]);
- } else {
- // 兜底:标签行紧挨在某个标题下方(中间只有空行)→ 捕获该标题下的整个小节
- let h = i - 1;
- while (h >= fm.bodyStart && lines[h].trim() === "") h--;
- const hmk = h >= fm.bodyStart ? lines[h].match(HEAD_RE) : null;
- if (hmk) {
- const level = hmk[1].length;
- const title = hmk[2].replace(ANY_TAG, "").replace(/\s+/g, " ").trim();
- const sec2 = title ? [[0, "**" + title + "**"]] : [];
- let j2 = i + 1, count2 = 0;
- while (j2 < lines.length && count2 < 300) {
- const sl = lines[j2];
- const shm = sl.match(HEAD_RE);
- if (shm && shm[1].length <= level) break;
- if (FENCE_RE.test(sl)) break;
- if (sl.trim()) {
- const smi = sl.match(LIST_RE);
- if (smi) sec2.push([1 + Math.floor(indentOf(sl) / 2), (smi[3] || "").replace(/\s+$/, "")]);
- else sec2.push([1, sl.trim().replace(/^>\s*/, "")]);
- count2++;
- }
- j2++;
- }
- if (sec2.length) sec = sec2;
- }
- }
- }
- if (sec.length) {
- const mi0 = Math.min(...sec.map(x => x[0]));
- addBlock(keyIdx, sec.map(([ind, t]) => [Math.floor((ind - mi0) / 2), t]));
- }
- continue;
- }
-
- // 普通段落带标签:吸收到空行为止
- const para = [stripTag(clean)];
- let j = i + 1;
- while (j < lines.length) {
- const nl = lines[j];
- if (nl.trim() === "" || HEAD_RE.test(nl) || LIST_RE.test(nl) || FENCE_RE.test(nl)) break;
- para.push(nl.trim());
- j++;
- }
- addBlock(i, [[0, para.filter(p => p).join(" ")]]);
- }
-
- // frontmatter 标签 → 页面级条目(附正文开头预览)
- if (TAG_LIST.some(t => fm.tags.has(t))) {
- const d = fm.date || fileDate || mtimeDate;
- if (d) {
- order++;
- // 收集正文文本至 PREV_CHARS:超过上限的长文在渲染时截断并附「查看全文」链接
- const prev = [];
- let prevTotal = 0, prevCount = 0;
- for (let k = fm.bodyStart; k < lines.length && prevCount < 200; k++) {
- const s = lines[k].trim();
- if (!s || s.startsWith("#") || s === "---") continue;
- const clean = s.replace(ANY_TAG, "");
- if (!clean) continue;
- prev.push(clean);
- prevTotal += clean.length;
- prevCount++;
- if (prevTotal > PREV_CHARS) break; // 超过上限:已足够判断,停止收集
- }
- const preview = prev.join(" ");
- entries.push({ date: d, time: null, order, relpath, note, breadcrumb: null,
- lines: preview ? [[0, preview]] : [],
- pageLevel: true, notes: [note], anchor: null });
- }
- }
- return entries;
-}
-
-// ---------------- 扫描整个 vault ----------------
-const currentPath = (dv.current() && dv.current().file) ? dv.current().file.path : null;
-let all = [];
-for (const f of app.vault.getMarkdownFiles()) {
- if (f.path === currentPath || f.path.endsWith(".excalidraw.md")) continue;
- if (SKIP_DIRS.some(d => f.path.startsWith(d + "/"))) continue;
- if (f.path.split("/").some(seg => seg.startsWith("."))) continue; // 排除所有隐藏文件/文件夹(.开头)
- if (excludedPaths.some(p => f.path === p || f.path.startsWith(p.endsWith("/") ? p : p + "/"))) continue;
- let text;
- try { text = await app.vault.cachedRead(f); } catch (e) { continue; }
- if (text.slice(0, 1000).includes(MARKER)) continue; // 跳过 tagfeed 生成的静态页
- all = all.concat(scanText(text, f.basename, f.path, dateFromName(f.basename), new Date(f.stat.mtime)));
-}
-
-// ---------------- 去重:同日期+同内容合并,来源并排 ----------------
-const timeCmp = (a, b) => (a[0] - b[0]) || (a[1] - b[1]) || (a[2] - b[2]);
-const mergedMap = new Map();
-for (const e of all) {
- const fp = e.date.getTime() + "|" + e.lines.map(x => x[0] + ":" + x[1]).join("\n");
- const m = mergedMap.get(fp);
- if (m) {
- if (!m.notes.includes(e.note)) m.notes.push(e.note);
- if (e.time && (!m.time || timeCmp(e.time, m.time) < 0)) m.time = e.time;
- // 若先扫到的来源没有面包屑/锚点,而后面的有,则采用后者(与扫描顺序无关,结果确定)
- if (!m.breadcrumb && e.breadcrumb) {
- m.breadcrumb = e.breadcrumb;
- m.anchor = e.anchor;
- m.relpath = e.relpath;
- m.note = e.note;
- }
- } else mergedMap.set(fp, e);
-}
-const entries = [...mergedMap.values()];
-
-// ---------------- 渲染:按日期倒序 ----------------
-const byDate = new Map();
-for (const e of entries) {
- const k = e.date.getTime();
- if (!byDate.has(k)) byDate.set(k, []);
- byDate.get(k).push(e);
-}
-const dates = [...byDate.keys()].sort((a, b) => b - a);
-const noteSet = new Set();
-for (const e of entries) for (const n of e.notes) noteSet.add(n);
-
-const out = [];
-out.push("> **" + entries.length + "** 个内容块 | **" + noteSet.size + "** 篇笔记 | 按日期倒序 | 随笔记变动自动刷新");
-
-const pad2 = x => String(x).padStart(2, "0");
-const fmtDate = d => d.getFullYear() + "-" + pad2(d.getMonth() + 1) + "-" + pad2(d.getDate());
-const invTime = t => t.map(x => pad2(99 - x)).join("");
-
-for (const dk of dates) {
- const d = new Date(dk);
- out.push("## 📅 " + fmtDate(d) + " · " + WEEKDAYS[d.getDay() === 0 ? 6 : d.getDay() - 1]);
- out.push("");
- const group = byDate.get(dk);
-
- const sub = new Map(); // (笔记+面包屑) -> entries
- for (const e of group) {
- const key = e.relpath + "|" + (e.breadcrumb || "");
- if (!sub.has(key)) sub.set(key, []);
- sub.get(key).push(e);
- }
- const subKey = ([key, es]) => {
- const times = es.filter(e => e.time).map(e => e.time);
- const best = times.length ? times.reduce((a, b) => (timeCmp(a, b) > 0 ? a : b)) : null;
- return (best ? "0" : "1") + (best ? invTime(best) : "000000") + key;
- };
-
- for (const [key, es] of [...sub.entries()].sort((a, b) => (subKey(a) < subKey(b) ? -1 : subKey(a) > subKey(b) ? 1 : 0))) {
- const noteList = [];
- for (const e of es) for (const n of e.notes) if (!noteList.includes(n)) noteList.push(n);
- noteList.sort();
- const crumb = es.map(e => e.breadcrumb).find(b => b) || null;
- out.push("- 🔗 " + noteList.map(n => "[[" + n + "]]").join(" · ") + (crumb ? " · " + crumb : ""));
- const sorted = es.slice().sort((a, b) => {
- const ka = (a.time ? "0" : "1") + (a.time ? invTime(a.time) : "") + String(a.order).padStart(6, "0");
- const kb = (b.time ? "0" : "1") + (b.time ? invTime(b.time) : "") + String(b.order).padStart(6, "0");
- return ka < kb ? -1 : ka > kb ? 1 : 0;
- });
- for (const e of sorted) {
- let bl = e.lines, truncated = false;
- const totalChars = bl.reduce((s, x) => s + x[1].length, 0);
- if (totalChars > PREV_CHARS) { // 只有超过上限才截断;上限内全文展示
- truncated = true;
- const shown = [];
- let chars = 0;
- for (const [lvl, txt] of bl) {
- if (chars + txt.length > PREV_CHARS) {
- if (PREV_CHARS - chars > 0) shown.push([lvl, txt.slice(0, PREV_CHARS - chars) + "…"]);
- break;
- }
- shown.push([lvl, txt]);
- chars += txt.length;
- }
- bl = shown;
- }
- for (const [lvl, txt] of bl) out.push("\t".repeat(lvl + 1) + "- " + txt);
- if (truncated) {
- const target = e.note + (e.anchor ? "#" + e.anchor : "");
- const label = e.pageLevel ? "页面级标签 · 查看全文" : "查看全文";
- out.push("\t- [[" + target + "|↗ " + label + "]]");
- }
- }
- out.push("");
- }
- out.push("");
-}
-dv.paragraph(out.join("\n"));
-
-} catch (err) {
- dv.paragraph("> ❌ tagfeed-live 执行失败:" + err.message);
- console.error("tagfeed-live:", err);
-}
-
-```
diff --git a/00index/0inbox/redText.md b/00index/0inbox/redText.md
deleted file mode 100644
index 9841f6a..0000000
--- a/00index/0inbox/redText.md
+++ /dev/null
@@ -1,437 +0,0 @@
----
-tags:
- - tagfeed-live
-cssclasses:
- - tagfeed
-words:
- 2026-08-23: 2728
----
-
-# 🏷 #redText(实时聚合页)
-
-> 本页由 DataviewJS 实时计算,笔记有变动时自动刷新,无需任何后台服务。
-
-```dataviewjs
-// ============================================================
-// tagfeed-live:Logseq 风格标签实时聚合(标准 DataviewJS 脚本)
-//
-// 用法:
-// 1. 新建一个笔记(例如 00index/标签聚合/标签名.md)
-// 2. 在笔记里插入一个 dataviewjs 代码块(三个反引号 + dataviewjs),
-// 把本脚本全部粘贴进去
-// 3. 只需要改下面「配置区」的两行:
-// - targetTag 换成你要聚合的标签(不带 #,中英文都可以;多个标签写成数组 ["a", "b"])
-// - excludedPaths 换成你想排除的文件夹
-// 4. 完成。以后任何笔记里出现/删除这个标签,本页自动刷新,
-// 不需要跑任何命令(Dataview 会在文件变动后自动重新渲染)。
-// ============================================================
-
-// ---------------- 配置区(通常只改这里) ----------------
-const targetTag = "redText"; // 要聚合的标签(不带 #);多个标签用数组:["lyric", "歌词"]
-const excludedPaths = ["X2.Archived", "00index", "X0.Clippings", "X1.Knomo", "02DS/02copilot", ".agents", ".claude", ".copilot", ".opencode", ".smart-env", ".workbuddy", ".trash"]; // 要排除的文件夹/路径前缀(vault 相对路径)
- // 例如:["00index", "模板", "X1.Knomo"]
-// 补充说明:
-// - 所有隐藏文件/文件夹(. 开头:.agents/.claude/.copilot/.opencode/.smart-env/.workbuddy/.trash 等)与 X.Attachment 始终跳过
-// - tagfeed 系统页会自动跳过:静态聚合页(文件头含 <!-- tagfeed 标记)
-// 和实时页(frontmatter 带 tagfeed / tagfeed-live 标签),
-// 无需手动排除 00index
-// - 本脚本所在的当前笔记也会自动跳过
-// ---------------------------------------------------------
-
-try {
-const SKIP_DIRS = [".obsidian", ".git", ".trash", "X.Attachment"];
-const MARKER = "<!-- tagfeed";
-const WEEKDAYS = ["周一", "周二", "周三", "周四", "周五", "周六", "周日"];
-const PREV_CHARS = 500; // 预览字数上限:≤上限全文展示;>上限截断并附「查看全文」链接
-
-const esc = s => String(s).replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
-const TAG_LIST = Array.isArray(targetTag) ? targetTag : [targetTag];
-const TAG_RE = new RegExp("(?<![\\w\\u4e00-\\u9fff#])#(?:" + TAG_LIST.map(esc).join("|") + ")(?![\\w\\u4e00-\\u9fff/\\-])");
-const ANY_TAG = /(?<![\w#])#([\w\u4e00-\u9fff][\w\u4e00-\u9fff/\-]*)/g;
-const LIST_RE = /^(\s*)([-*+]|\d+[.)])(?:\s+(.*))?$/;
-const HEAD_RE = /^(#{1,6})\s+(.*)$/;
-const DATEH_RE = /\[\[(\d{4})-(\d{2})-(\d{2})\]\]/;
-const TIME_RE = /(\d{1,2}):(\d{2})(?::(\d{2}))?/;
-const FENCE_RE = /^\s*(`{3,}|~~~)/;
-const indentOf = ln => { // tab 按 2 空格计(与 Python 版一致)
- const expanded = ln.replace(/\t/g, " ");
- return expanded.length - expanded.replace(/^ +/, "").length;
-};
-
-function cleanMatch(ln) {
- let s = ln.replace(/`[^`\n]*`/g, "");
- s = s.replace(/%%[\s\S]*?%%/g, "");
- s = s.replace(/<!--[\s\S]*?-->/g, "");
- return s;
-}
-function vdate(y, m, d) {
- const dt = new Date(y, m - 1, d);
- return (dt.getFullYear() === y && dt.getMonth() === m - 1 && dt.getDate() === d) ? dt : null;
-}
-function dateFromName(name) { // 文件名含 YYYY-MM-DD,或日记命名 26.0805 -> 2026-08-05
- const m1 = name.match(/(\d{4})-(\d{2})-(\d{2})/);
- if (m1) {
- const d1 = vdate(+m1[1], +m1[2], +m1[3]);
- if (d1) return d1;
- }
- const m = name.match(/^(\d{2})\.(\d{2})(\d{2})(?:\D|$)/);
- return m ? vdate(2000 + +m[1], +m[2], +m[3]) : null;
-}
-function parseFrontmatter(text) {
- const lines = text.split("\n");
- if (!lines.length || lines[0].trim() !== "---") return { tags: new Set(), date: null, bodyStart: 0 };
- const n = Math.min(lines.length, 200);
- for (let i = 1; i < n; i++) {
- const t = lines[i].trim();
- if (t === "---" || t === "...") {
- const block = lines.slice(1, i).join("\n");
- const tags = new Set();
- const im = block.match(/^tags:\s*\[(.*?)\]/m);
- if (im) {
- for (const x0 of im[1].split(",")) {
- const x = x0.trim().replace(/^["']|["']$/g, "").replace(/^#/, "").trim();
- if (x) tags.add(x);
- }
- } else {
- const bm = block.match(/^tags:\s*\n((?:[ \t]+-[ \t]+.*\n?)+)/m);
- if (bm) for (const ln of bm[1].split("\n")) {
- const m2 = ln.match(/^\s*-\s*["']?([\w\u4e00-\u9fff/\-]+)["']?\s*$/);
- if (m2) tags.add(m2[1]);
- }
- }
- let fdate = null;
- for (const key of ["date", "created", "published"]) {
- const dm = block.match(new RegExp("^" + key + ":\\s*[\"']?(\\d{4})-(\\d{2})-(\\d{2})", "m"));
- if (dm) { fdate = vdate(+dm[1], +dm[2], +dm[3]); break; }
- }
- return { tags, date: fdate, bodyStart: i + 1 };
- }
- }
- return { tags: new Set(), date: null, bodyStart: 0 };
-}
-const stripTag = s => String(s).replace(TAG_RE, "").replace(/[ \t]{2,}/g, " ").trim();
-
-function scanText(text, note, relpath, fileDate, mtimeDate) {
- const fm = parseFrontmatter(text);
- if (fm.tags.has("tagfeed") || fm.tags.has("tagfeed-live")) return []; // 跳过 tagfeed 系统页(静态聚合页/实时页)
- const lines = text.split("\n");
- const entries = [], seen = new Set();
- let order = 0, inFence = false, inPct = false, inHtml = false;
- let curHead = null, curHeadRaw = null, curDateHead = null;
-
- const entryDate = () => curDateHead || fileDate || fm.date || mtimeDate;
- const anchorOf = () => (curDateHead ? null : curHeadRaw);
- const breadcrumb = () => {
- if (curHead && !DATEH_RE.test(curHead)) {
- const t = curHead.replace(ANY_TAG, "").replace(/\s+/g, " ").trim();
- if (t) return t;
- }
- return null;
- };
- function addBlock(keyIdx, block) {
- const key = relpath + "|" + keyIdx;
- if (seen.has(key) || !block.length) return;
- seen.add(key);
- const d = entryDate();
- if (!d) return;
- let t = null;
- const tm = block[0][1].slice(0, 30).match(TIME_RE);
- if (tm) t = [+tm[1], +tm[2], +(tm[3] || 0)];
- order++;
- entries.push({ date: d, time: t, order, relpath, note,
- breadcrumb: breadcrumb(), lines: block,
- pageLevel: false, notes: [note], anchor: anchorOf() });
- }
- function collectChildren(i, parentIndent) {
- const kids = [], raw = [];
- let j = i + 1;
- while (j < lines.length) {
- const ln = lines[j];
- if (ln.trim() === "") {
- let k = j + 1;
- while (k < lines.length && lines[k].trim() === "") k++;
- if (k < lines.length && LIST_RE.test(lines[k]) && indentOf(lines[k]) > parentIndent) { j = k; continue; }
- break;
- }
- const mi = ln.match(LIST_RE);
- if (mi && indentOf(ln) > parentIndent) {
- raw.push([indentOf(ln), (mi[3] || "").replace(/\s+$/, "")]);
- j++;
- } else break;
- }
- for (const [ind, txt] of raw) kids.push([Math.floor((ind - parentIndent) / 2), txt]);
- return kids;
- }
-
- for (let i = fm.bodyStart; i < lines.length; i++) {
- const ln = lines[i];
- if (FENCE_RE.test(ln)) { inFence = !inFence; continue; }
- if (inFence) continue;
- if (inPct) { if (ln.includes("%%")) inPct = false; continue; }
- if (inHtml) { if (ln.includes("-->")) inHtml = false; continue; }
- const stripped = ln.trim();
- if (stripped.startsWith("%%") && !stripped.slice(2).includes("%%")) { inPct = true; continue; }
- if (stripped.startsWith("<!--") && !stripped.includes("-->")) { inHtml = true; continue; }
-
- const hm = ln.match(HEAD_RE);
- if (hm) {
- const htext = hm[2];
- const dh = htext.match(DATEH_RE);
- if (dh) { curDateHead = vdate(+dh[1], +dh[2], +dh[3]); curHead = null; curHeadRaw = null; }
- else { curHead = htext; curHeadRaw = htext.trim(); }
- if (TAG_RE.test(cleanMatch(htext))) {
- const sec = [[0, "**" + stripTag(htext) + "**"]];
- let j = i + 1;
- const level = hm[1].length;
- let count = 0;
- while (j < lines.length && count < 300) {
- const sl = lines[j];
- const shm = sl.match(HEAD_RE);
- if (shm && shm[1].length <= level) break;
- if (FENCE_RE.test(sl)) break;
- if (sl.trim()) {
- const smi = sl.match(LIST_RE);
- if (smi) sec.push([1 + Math.floor(indentOf(sl) / 2), (smi[3] || "").replace(/\s+$/, "")]);
- else sec.push([1, sl.trim()]);
- count++;
- }
- j++;
- }
- addBlock(i, sec);
- }
- continue;
- }
-
- const clean = cleanMatch(ln);
- if (!TAG_RE.test(clean)) continue;
-
- const mi = ln.match(LIST_RE);
- if (mi) {
- const ind = indentOf(ln);
- const itemText = mi[3] || "";
- const cleaned = stripTag(itemText);
- const kids = collectChildren(i, ind);
- addBlock(i, cleaned ? [[0, cleaned], ...kids] : [[0, itemText.trim()], ...kids]);
- continue;
- }
-
- // 独立标签行:向下吸收 list items,或向上归属到紧邻的块
- if (ln.replace(ANY_TAG, "").trim() === "" || ln.replace(ANY_TAG, "").trim() === "%%") {
- let sec = [];
- let j = i + 1;
- while (j < lines.length) {
- const mj = lines[j].match(LIST_RE);
- if (mj) { sec.push([indentOf(lines[j]), (mj[3] || "").replace(/\s+$/, "")]); j++; }
- else break;
- }
- let keyIdx = i;
- if (!sec.length) {
- const up = [];
- let k = i - 1, bulletIdx = null;
- while (k >= fm.bodyStart) {
- const bk = lines[k];
- if (bk.trim() === "" || HEAD_RE.test(bk) || FENCE_RE.test(bk)) break;
- up.push(k);
- if (LIST_RE.test(bk)) { bulletIdx = k; break; }
- k--;
- }
- if (bulletIdx !== null) {
- const inds = [];
- for (let bl = bulletIdx; bl < i; bl++) {
- const mb = lines[bl].match(LIST_RE);
- if (mb) { inds.push(indentOf(lines[bl])); sec.push([indentOf(lines[bl]), (mb[3] || "").replace(/\s+$/, "")]); }
- else sec.push([(inds.length ? inds[inds.length - 1] + 2 : 0), lines[bl].trim()]);
- }
- keyIdx = bulletIdx;
- } else if (up.length) {
- sec = up.slice().reverse().map(k2 => [0, lines[k2].trim()]);
- } else {
- // 兜底:标签行紧挨在某个标题下方(中间只有空行)→ 捕获该标题下的整个小节
- let h = i - 1;
- while (h >= fm.bodyStart && lines[h].trim() === "") h--;
- const hmk = h >= fm.bodyStart ? lines[h].match(HEAD_RE) : null;
- if (hmk) {
- const level = hmk[1].length;
- const title = hmk[2].replace(ANY_TAG, "").replace(/\s+/g, " ").trim();
- const sec2 = title ? [[0, "**" + title + "**"]] : [];
- let j2 = i + 1, count2 = 0;
- while (j2 < lines.length && count2 < 300) {
- const sl = lines[j2];
- const shm = sl.match(HEAD_RE);
- if (shm && shm[1].length <= level) break;
- if (FENCE_RE.test(sl)) break;
- if (sl.trim()) {
- const smi = sl.match(LIST_RE);
- if (smi) sec2.push([1 + Math.floor(indentOf(sl) / 2), (smi[3] || "").replace(/\s+$/, "")]);
- else sec2.push([1, sl.trim().replace(/^>\s*/, "")]);
- count2++;
- }
- j2++;
- }
- if (sec2.length) sec = sec2;
- }
- }
- }
- if (sec.length) {
- const mi0 = Math.min(...sec.map(x => x[0]));
- addBlock(keyIdx, sec.map(([ind, t]) => [Math.floor((ind - mi0) / 2), t]));
- }
- continue;
- }
-
- // 普通段落带标签:吸收到空行为止
- const para = [stripTag(clean)];
- let j = i + 1;
- while (j < lines.length) {
- const nl = lines[j];
- if (nl.trim() === "" || HEAD_RE.test(nl) || LIST_RE.test(nl) || FENCE_RE.test(nl)) break;
- para.push(nl.trim());
- j++;
- }
- addBlock(i, [[0, para.filter(p => p).join(" ")]]);
- }
-
- // frontmatter 标签 → 页面级条目(附正文开头预览)
- if (TAG_LIST.some(t => fm.tags.has(t))) {
- const d = fm.date || fileDate || mtimeDate;
- if (d) {
- order++;
- // 收集正文文本至 PREV_CHARS:超过上限的长文在渲染时截断并附「查看全文」链接
- const prev = [];
- let prevTotal = 0, prevCount = 0;
- for (let k = fm.bodyStart; k < lines.length && prevCount < 200; k++) {
- const s = lines[k].trim();
- if (!s || s.startsWith("#") || s === "---") continue;
- const clean = s.replace(ANY_TAG, "");
- if (!clean) continue;
- prev.push(clean);
- prevTotal += clean.length;
- prevCount++;
- if (prevTotal > PREV_CHARS) break; // 超过上限:已足够判断,停止收集
- }
- const preview = prev.join(" ");
- entries.push({ date: d, time: null, order, relpath, note, breadcrumb: null,
- lines: preview ? [[0, preview]] : [],
- pageLevel: true, notes: [note], anchor: null });
- }
- }
- return entries;
-}
-
-// ---------------- 扫描整个 vault ----------------
-const currentPath = (dv.current() && dv.current().file) ? dv.current().file.path : null;
-let all = [];
-for (const f of app.vault.getMarkdownFiles()) {
- if (f.path === currentPath || f.path.endsWith(".excalidraw.md")) continue;
- if (SKIP_DIRS.some(d => f.path.startsWith(d + "/"))) continue;
- if (f.path.split("/").some(seg => seg.startsWith("."))) continue; // 排除所有隐藏文件/文件夹(.开头)
- if (excludedPaths.some(p => f.path === p || f.path.startsWith(p.endsWith("/") ? p : p + "/"))) continue;
- let text;
- try { text = await app.vault.cachedRead(f); } catch (e) { continue; }
- if (text.slice(0, 1000).includes(MARKER)) continue; // 跳过 tagfeed 生成的静态页
- all = all.concat(scanText(text, f.basename, f.path, dateFromName(f.basename), new Date(f.stat.mtime)));
-}
-
-// ---------------- 去重:同日期+同内容合并,来源并排 ----------------
-const timeCmp = (a, b) => (a[0] - b[0]) || (a[1] - b[1]) || (a[2] - b[2]);
-const mergedMap = new Map();
-for (const e of all) {
- const fp = e.date.getTime() + "|" + e.lines.map(x => x[0] + ":" + x[1]).join("\n");
- const m = mergedMap.get(fp);
- if (m) {
- if (!m.notes.includes(e.note)) m.notes.push(e.note);
- if (e.time && (!m.time || timeCmp(e.time, m.time) < 0)) m.time = e.time;
- // 若先扫到的来源没有面包屑/锚点,而后面的有,则采用后者(与扫描顺序无关,结果确定)
- if (!m.breadcrumb && e.breadcrumb) {
- m.breadcrumb = e.breadcrumb;
- m.anchor = e.anchor;
- m.relpath = e.relpath;
- m.note = e.note;
- }
- } else mergedMap.set(fp, e);
-}
-const entries = [...mergedMap.values()];
-
-// ---------------- 渲染:按日期倒序 ----------------
-const byDate = new Map();
-for (const e of entries) {
- const k = e.date.getTime();
- if (!byDate.has(k)) byDate.set(k, []);
- byDate.get(k).push(e);
-}
-const dates = [...byDate.keys()].sort((a, b) => b - a);
-const noteSet = new Set();
-for (const e of entries) for (const n of e.notes) noteSet.add(n);
-
-const out = [];
-out.push("> **" + entries.length + "** 个内容块 | **" + noteSet.size + "** 篇笔记 | 按日期倒序 | 随笔记变动自动刷新");
-
-const pad2 = x => String(x).padStart(2, "0");
-const fmtDate = d => d.getFullYear() + "-" + pad2(d.getMonth() + 1) + "-" + pad2(d.getDate());
-const invTime = t => t.map(x => pad2(99 - x)).join("");
-
-for (const dk of dates) {
- const d = new Date(dk);
- out.push("## 📅 " + fmtDate(d) + " · " + WEEKDAYS[d.getDay() === 0 ? 6 : d.getDay() - 1]);
- out.push("");
- const group = byDate.get(dk);
-
- const sub = new Map(); // (笔记+面包屑) -> entries
- for (const e of group) {
- const key = e.relpath + "|" + (e.breadcrumb || "");
- if (!sub.has(key)) sub.set(key, []);
- sub.get(key).push(e);
- }
- const subKey = ([key, es]) => {
- const times = es.filter(e => e.time).map(e => e.time);
- const best = times.length ? times.reduce((a, b) => (timeCmp(a, b) > 0 ? a : b)) : null;
- return (best ? "0" : "1") + (best ? invTime(best) : "000000") + key;
- };
-
- for (const [key, es] of [...sub.entries()].sort((a, b) => (subKey(a) < subKey(b) ? -1 : subKey(a) > subKey(b) ? 1 : 0))) {
- const noteList = [];
- for (const e of es) for (const n of e.notes) if (!noteList.includes(n)) noteList.push(n);
- noteList.sort();
- const crumb = es.map(e => e.breadcrumb).find(b => b) || null;
- out.push("- 🔗 " + noteList.map(n => "[[" + n + "]]").join(" · ") + (crumb ? " · " + crumb : ""));
- const sorted = es.slice().sort((a, b) => {
- const ka = (a.time ? "0" : "1") + (a.time ? invTime(a.time) : "") + String(a.order).padStart(6, "0");
- const kb = (b.time ? "0" : "1") + (b.time ? invTime(b.time) : "") + String(b.order).padStart(6, "0");
- return ka < kb ? -1 : ka > kb ? 1 : 0;
- });
- for (const e of sorted) {
- let bl = e.lines, truncated = false;
- const totalChars = bl.reduce((s, x) => s + x[1].length, 0);
- if (totalChars > PREV_CHARS) { // 只有超过上限才截断;上限内全文展示
- truncated = true;
- const shown = [];
- let chars = 0;
- for (const [lvl, txt] of bl) {
- if (chars + txt.length > PREV_CHARS) {
- if (PREV_CHARS - chars > 0) shown.push([lvl, txt.slice(0, PREV_CHARS - chars) + "…"]);
- break;
- }
- shown.push([lvl, txt]);
- chars += txt.length;
- }
- bl = shown;
- }
- for (const [lvl, txt] of bl) out.push("\t".repeat(lvl + 1) + "- " + txt);
- if (truncated) {
- const target = e.note + (e.anchor ? "#" + e.anchor : "");
- const label = e.pageLevel ? "页面级标签 · 查看全文" : "查看全文";
- out.push("\t- [[" + target + "|↗ " + label + "]]");
- }
- }
- out.push("");
- }
- out.push("");
-}
-dv.paragraph(out.join("\n"));
-
-} catch (err) {
- dv.paragraph("> ❌ tagfeed-live 执行失败:" + err.message);
- console.error("tagfeed-live:", err);
-}
-
-```
diff --git "a/00index/0inbox/\344\275\240\345\245\275.md" "b/00index/0inbox/\344\275\240\345\245\275.md"
deleted file mode 100644
index 3afe9ba..0000000
--- "a/00index/0inbox/\344\275\240\345\245\275.md"
+++ /dev/null
@@ -1,437 +0,0 @@
----
-tags:
- - tagfeed-live
-cssclasses:
- - tagfeed
-words:
- 2026-08-23: 2728
----
-
-# 🏷 #你好(实时聚合页)
-
-> 本页由 DataviewJS 实时计算,笔记有变动时自动刷新,无需任何后台服务。
-
-```dataviewjs
-// ============================================================
-// tagfeed-live:Logseq 风格标签实时聚合(标准 DataviewJS 脚本)
-//
-// 用法:
-// 1. 新建一个笔记(例如 00index/标签聚合/标签名.md)
-// 2. 在笔记里插入一个 dataviewjs 代码块(三个反引号 + dataviewjs),
-// 把本脚本全部粘贴进去
-// 3. 只需要改下面「配置区」的两行:
-// - targetTag 换成你要聚合的标签(不带 #,中英文都可以;多个标签写成数组 ["a", "b"])
-// - excludedPaths 换成你想排除的文件夹
-// 4. 完成。以后任何笔记里出现/删除这个标签,本页自动刷新,
-// 不需要跑任何命令(Dataview 会在文件变动后自动重新渲染)。
-// ============================================================
-
-// ---------------- 配置区(通常只改这里) ----------------
-const targetTag = "你好"; // 要聚合的标签(不带 #);多个标签用数组:["lyric", "歌词"]
-const excludedPaths = ["X2.Archived", "00index", "X0.Clippings", "X1.Knomo", "02DS/02copilot", ".agents", ".claude", ".copilot", ".opencode", ".smart-env", ".workbuddy", ".trash"]; // 要排除的文件夹/路径前缀(vault 相对路径)
- // 例如:["00index", "模板", "X1.Knomo"]
-// 补充说明:
-// - 所有隐藏文件/文件夹(. 开头:.agents/.claude/.copilot/.opencode/.smart-env/.workbuddy/.trash 等)与 X.Attachment 始终跳过
-// - tagfeed 系统页会自动跳过:静态聚合页(文件头含 <!-- tagfeed 标记)
-// 和实时页(frontmatter 带 tagfeed / tagfeed-live 标签),
-// 无需手动排除 00index
-// - 本脚本所在的当前笔记也会自动跳过
-// ---------------------------------------------------------
-
-try {
-const SKIP_DIRS = [".obsidian", ".git", ".trash", "X.Attachment"];
-const MARKER = "<!-- tagfeed";
-const WEEKDAYS = ["周一", "周二", "周三", "周四", "周五", "周六", "周日"];
-const PREV_CHARS = 500; // 预览字数上限:≤上限全文展示;>上限截断并附「查看全文」链接
-
-const esc = s => String(s).replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
-const TAG_LIST = Array.isArray(targetTag) ? targetTag : [targetTag];
-const TAG_RE = new RegExp("(?<![\\w\\u4e00-\\u9fff#])#(?:" + TAG_LIST.map(esc).join("|") + ")(?![\\w\\u4e00-\\u9fff/\\-])");
-const ANY_TAG = /(?<![\w#])#([\w\u4e00-\u9fff][\w\u4e00-\u9fff/\-]*)/g;
-const LIST_RE = /^(\s*)([-*+]|\d+[.)])(?:\s+(.*))?$/;
-const HEAD_RE = /^(#{1,6})\s+(.*)$/;
-const DATEH_RE = /\[\[(\d{4})-(\d{2})-(\d{2})\]\]/;
-const TIME_RE = /(\d{1,2}):(\d{2})(?::(\d{2}))?/;
-const FENCE_RE = /^\s*(`{3,}|~~~)/;
-const indentOf = ln => { // tab 按 2 空格计(与 Python 版一致)
- const expanded = ln.replace(/\t/g, " ");
- return expanded.length - expanded.replace(/^ +/, "").length;
-};
-
-function cleanMatch(ln) {
- let s = ln.replace(/`[^`\n]*`/g, "");
- s = s.replace(/%%[\s\S]*?%%/g, "");
- s = s.replace(/<!--[\s\S]*?-->/g, "");
- return s;
-}
-function vdate(y, m, d) {
- const dt = new Date(y, m - 1, d);
- return (dt.getFullYear() === y && dt.getMonth() === m - 1 && dt.getDate() === d) ? dt : null;
-}
-function dateFromName(name) { // 文件名含 YYYY-MM-DD,或日记命名 26.0805 -> 2026-08-05
- const m1 = name.match(/(\d{4})-(\d{2})-(\d{2})/);
- if (m1) {
- const d1 = vdate(+m1[1], +m1[2], +m1[3]);
- if (d1) return d1;
- }
- const m = name.match(/^(\d{2})\.(\d{2})(\d{2})(?:\D|$)/);
- return m ? vdate(2000 + +m[1], +m[2], +m[3]) : null;
-}
-function parseFrontmatter(text) {
- const lines = text.split("\n");
- if (!lines.length || lines[0].trim() !== "---") return { tags: new Set(), date: null, bodyStart: 0 };
- const n = Math.min(lines.length, 200);
- for (let i = 1; i < n; i++) {
- const t = lines[i].trim();
- if (t === "---" || t === "...") {
- const block = lines.slice(1, i).join("\n");
- const tags = new Set();
- const im = block.match(/^tags:\s*\[(.*?)\]/m);
- if (im) {
- for (const x0 of im[1].split(",")) {
- const x = x0.trim().replace(/^["']|["']$/g, "").replace(/^#/, "").trim();
- if (x) tags.add(x);
- }
- } else {
- const bm = block.match(/^tags:\s*\n((?:[ \t]+-[ \t]+.*\n?)+)/m);
- if (bm) for (const ln of bm[1].split("\n")) {
- const m2 = ln.match(/^\s*-\s*["']?([\w\u4e00-\u9fff/\-]+)["']?\s*$/);
- if (m2) tags.add(m2[1]);
- }
- }
- let fdate = null;
- for (const key of ["date", "created", "published"]) {
- const dm = block.match(new RegExp("^" + key + ":\\s*[\"']?(\\d{4})-(\\d{2})-(\\d{2})", "m"));
- if (dm) { fdate = vdate(+dm[1], +dm[2], +dm[3]); break; }
- }
- return { tags, date: fdate, bodyStart: i + 1 };
- }
- }
- return { tags: new Set(), date: null, bodyStart: 0 };
-}
-const stripTag = s => String(s).replace(TAG_RE, "").replace(/[ \t]{2,}/g, " ").trim();
-
-function scanText(text, note, relpath, fileDate, mtimeDate) {
- const fm = parseFrontmatter(text);
- if (fm.tags.has("tagfeed") || fm.tags.has("tagfeed-live")) return []; // 跳过 tagfeed 系统页(静态聚合页/实时页)
- const lines = text.split("\n");
- const entries = [], seen = new Set();
- let order = 0, inFence = false, inPct = false, inHtml = false;
- let curHead = null, curHeadRaw = null, curDateHead = null;
-
- const entryDate = () => curDateHead || fileDate || fm.date || mtimeDate;
- const anchorOf = () => (curDateHead ? null : curHeadRaw);
- const breadcrumb = () => {
- if (curHead && !DATEH_RE.test(curHead)) {
- const t = curHead.replace(ANY_TAG, "").replace(/\s+/g, " ").trim();
- if (t) return t;
- }
- return null;
- };
- function addBlock(keyIdx, block) {
- const key = relpath + "|" + keyIdx;
- if (seen.has(key) || !block.length) return;
- seen.add(key);
- const d = entryDate();
- if (!d) return;
- let t = null;
- const tm = block[0][1].slice(0, 30).match(TIME_RE);
- if (tm) t = [+tm[1], +tm[2], +(tm[3] || 0)];
- order++;
- entries.push({ date: d, time: t, order, relpath, note,
- breadcrumb: breadcrumb(), lines: block,
- pageLevel: false, notes: [note], anchor: anchorOf() });
- }
- function collectChildren(i, parentIndent) {
- const kids = [], raw = [];
- let j = i + 1;
- while (j < lines.length) {
- const ln = lines[j];
- if (ln.trim() === "") {
- let k = j + 1;
- while (k < lines.length && lines[k].trim() === "") k++;
- if (k < lines.length && LIST_RE.test(lines[k]) && indentOf(lines[k]) > parentIndent) { j = k; continue; }
- break;
- }
- const mi = ln.match(LIST_RE);
- if (mi && indentOf(ln) > parentIndent) {
- raw.push([indentOf(ln), (mi[3] || "").replace(/\s+$/, "")]);
- j++;
- } else break;
- }
- for (const [ind, txt] of raw) kids.push([Math.floor((ind - parentIndent) / 2), txt]);
- return kids;
- }
-
- for (let i = fm.bodyStart; i < lines.length; i++) {
- const ln = lines[i];
- if (FENCE_RE.test(ln)) { inFence = !inFence; continue; }
- if (inFence) continue;
- if (inPct) { if (ln.includes("%%")) inPct = false; continue; }
- if (inHtml) { if (ln.includes("-->")) inHtml = false; continue; }
- const stripped = ln.trim();
- if (stripped.startsWith("%%") && !stripped.slice(2).includes("%%")) { inPct = true; continue; }
- if (stripped.startsWith("<!--") && !stripped.includes("-->")) { inHtml = true; continue; }
-
- const hm = ln.match(HEAD_RE);
- if (hm) {
- const htext = hm[2];
- const dh = htext.match(DATEH_RE);
- if (dh) { curDateHead = vdate(+dh[1], +dh[2], +dh[3]); curHead = null; curHeadRaw = null; }
- else { curHead = htext; curHeadRaw = htext.trim(); }
- if (TAG_RE.test(cleanMatch(htext))) {
- const sec = [[0, "**" + stripTag(htext) + "**"]];
- let j = i + 1;
- const level = hm[1].length;
- let count = 0;
- while (j < lines.length && count < 300) {
- const sl = lines[j];
- const shm = sl.match(HEAD_RE);
- if (shm && shm[1].length <= level) break;
- if (FENCE_RE.test(sl)) break;
- if (sl.trim()) {
- const smi = sl.match(LIST_RE);
- if (smi) sec.push([1 + Math.floor(indentOf(sl) / 2), (smi[3] || "").replace(/\s+$/, "")]);
- else sec.push([1, sl.trim()]);
- count++;
- }
- j++;
- }
- addBlock(i, sec);
- }
- continue;
- }
-
- const clean = cleanMatch(ln);
- if (!TAG_RE.test(clean)) continue;
-
- const mi = ln.match(LIST_RE);
- if (mi) {
- const ind = indentOf(ln);
- const itemText = mi[3] || "";
- const cleaned = stripTag(itemText);
- const kids = collectChildren(i, ind);
- addBlock(i, cleaned ? [[0, cleaned], ...kids] : [[0, itemText.trim()], ...kids]);
- continue;
- }
-
- // 独立标签行:向下吸收 list items,或向上归属到紧邻的块
- if (ln.replace(ANY_TAG, "").trim() === "" || ln.replace(ANY_TAG, "").trim() === "%%") {
- let sec = [];
- let j = i + 1;
- while (j < lines.length) {
- const mj = lines[j].match(LIST_RE);
- if (mj) { sec.push([indentOf(lines[j]), (mj[3] || "").replace(/\s+$/, "")]); j++; }
- else break;
- }
- let keyIdx = i;
- if (!sec.length) {
- const up = [];
- let k = i - 1, bulletIdx = null;
- while (k >= fm.bodyStart) {
- const bk = lines[k];
- if (bk.trim() === "" || HEAD_RE.test(bk) || FENCE_RE.test(bk)) break;
- up.push(k);
- if (LIST_RE.test(bk)) { bulletIdx = k; break; }
- k--;
- }
- if (bulletIdx !== null) {
- const inds = [];
- for (let bl = bulletIdx; bl < i; bl++) {
- const mb = lines[bl].match(LIST_RE);
- if (mb) { inds.push(indentOf(lines[bl])); sec.push([indentOf(lines[bl]), (mb[3] || "").replace(/\s+$/, "")]); }
- else sec.push([(inds.length ? inds[inds.length - 1] + 2 : 0), lines[bl].trim()]);
- }
- keyIdx = bulletIdx;
- } else if (up.length) {
- sec = up.slice().reverse().map(k2 => [0, lines[k2].trim()]);
- } else {
- // 兜底:标签行紧挨在某个标题下方(中间只有空行)→ 捕获该标题下的整个小节
- let h = i - 1;
- while (h >= fm.bodyStart && lines[h].trim() === "") h--;
- const hmk = h >= fm.bodyStart ? lines[h].match(HEAD_RE) : null;
- if (hmk) {
- const level = hmk[1].length;
- const title = hmk[2].replace(ANY_TAG, "").replace(/\s+/g, " ").trim();
- const sec2 = title ? [[0, "**" + title + "**"]] : [];
- let j2 = i + 1, count2 = 0;
- while (j2 < lines.length && count2 < 300) {
- const sl = lines[j2];
- const shm = sl.match(HEAD_RE);
- if (shm && shm[1].length <= level) break;
- if (FENCE_RE.test(sl)) break;
- if (sl.trim()) {
- const smi = sl.match(LIST_RE);
- if (smi) sec2.push([1 + Math.floor(indentOf(sl) / 2), (smi[3] || "").replace(/\s+$/, "")]);
- else sec2.push([1, sl.trim().replace(/^>\s*/, "")]);
- count2++;
- }
- j2++;
- }
- if (sec2.length) sec = sec2;
- }
- }
- }
- if (sec.length) {
- const mi0 = Math.min(...sec.map(x => x[0]));
- addBlock(keyIdx, sec.map(([ind, t]) => [Math.floor((ind - mi0) / 2), t]));
- }
- continue;
- }
-
- // 普通段落带标签:吸收到空行为止
- const para = [stripTag(clean)];
- let j = i + 1;
- while (j < lines.length) {
- const nl = lines[j];
- if (nl.trim() === "" || HEAD_RE.test(nl) || LIST_RE.test(nl) || FENCE_RE.test(nl)) break;
- para.push(nl.trim());
- j++;
- }
- addBlock(i, [[0, para.filter(p => p).join(" ")]]);
- }
-
- // frontmatter 标签 → 页面级条目(附正文开头预览)
- if (TAG_LIST.some(t => fm.tags.has(t))) {
- const d = fm.date || fileDate || mtimeDate;
- if (d) {
- order++;
- // 收集正文文本至 PREV_CHARS:超过上限的长文在渲染时截断并附「查看全文」链接
- const prev = [];
- let prevTotal = 0, prevCount = 0;
- for (let k = fm.bodyStart; k < lines.length && prevCount < 200; k++) {
- const s = lines[k].trim();
- if (!s || s.startsWith("#") || s === "---") continue;
- const clean = s.replace(ANY_TAG, "");
- if (!clean) continue;
- prev.push(clean);
- prevTotal += clean.length;
- prevCount++;
- if (prevTotal > PREV_CHARS) break; // 超过上限:已足够判断,停止收集
- }
- const preview = prev.join(" ");
- entries.push({ date: d, time: null, order, relpath, note, breadcrumb: null,
- lines: preview ? [[0, preview]] : [],
- pageLevel: true, notes: [note], anchor: null });
- }
- }
- return entries;
-}
-
-// ---------------- 扫描整个 vault ----------------
-const currentPath = (dv.current() && dv.current().file) ? dv.current().file.path : null;
-let all = [];
-for (const f of app.vault.getMarkdownFiles()) {
- if (f.path === currentPath || f.path.endsWith(".excalidraw.md")) continue;
- if (SKIP_DIRS.some(d => f.path.startsWith(d + "/"))) continue;
- if (f.path.split("/").some(seg => seg.startsWith("."))) continue; // 排除所有隐藏文件/文件夹(.开头)
- if (excludedPaths.some(p => f.path === p || f.path.startsWith(p.endsWith("/") ? p : p + "/"))) continue;
- let text;
- try { text = await app.vault.cachedRead(f); } catch (e) { continue; }
- if (text.slice(0, 1000).includes(MARKER)) continue; // 跳过 tagfeed 生成的静态页
- all = all.concat(scanText(text, f.basename, f.path, dateFromName(f.basename), new Date(f.stat.mtime)));
-}
-
-// ---------------- 去重:同日期+同内容合并,来源并排 ----------------
-const timeCmp = (a, b) => (a[0] - b[0]) || (a[1] - b[1]) || (a[2] - b[2]);
-const mergedMap = new Map();
-for (const e of all) {
- const fp = e.date.getTime() + "|" + e.lines.map(x => x[0] + ":" + x[1]).join("\n");
- const m = mergedMap.get(fp);
- if (m) {
- if (!m.notes.includes(e.note)) m.notes.push(e.note);
- if (e.time && (!m.time || timeCmp(e.time, m.time) < 0)) m.time = e.time;
- // 若先扫到的来源没有面包屑/锚点,而后面的有,则采用后者(与扫描顺序无关,结果确定)
- if (!m.breadcrumb && e.breadcrumb) {
- m.breadcrumb = e.breadcrumb;
- m.anchor = e.anchor;
- m.relpath = e.relpath;
- m.note = e.note;
- }
- } else mergedMap.set(fp, e);
-}
-const entries = [...mergedMap.values()];
-
-// ---------------- 渲染:按日期倒序 ----------------
-const byDate = new Map();
-for (const e of entries) {
- const k = e.date.getTime();
- if (!byDate.has(k)) byDate.set(k, []);
- byDate.get(k).push(e);
-}
-const dates = [...byDate.keys()].sort((a, b) => b - a);
-const noteSet = new Set();
-for (const e of entries) for (const n of e.notes) noteSet.add(n);
-
-const out = [];
-out.push("> **" + entries.length + "** 个内容块 | **" + noteSet.size + "** 篇笔记 | 按日期倒序 | 随笔记变动自动刷新");
-
-const pad2 = x => String(x).padStart(2, "0");
-const fmtDate = d => d.getFullYear() + "-" + pad2(d.getMonth() + 1) + "-" + pad2(d.getDate());
-const invTime = t => t.map(x => pad2(99 - x)).join("");
-
-for (const dk of dates) {
- const d = new Date(dk);
- out.push("## 📅 " + fmtDate(d) + " · " + WEEKDAYS[d.getDay() === 0 ? 6 : d.getDay() - 1]);
- out.push("");
- const group = byDate.get(dk);
-
- const sub = new Map(); // (笔记+面包屑) -> entries
- for (const e of group) {
- const key = e.relpath + "|" + (e.breadcrumb || "");
- if (!sub.has(key)) sub.set(key, []);
- sub.get(key).push(e);
- }
- const subKey = ([key, es]) => {
- const times = es.filter(e => e.time).map(e => e.time);
- const best = times.length ? times.reduce((a, b) => (timeCmp(a, b) > 0 ? a : b)) : null;
- return (best ? "0" : "1") + (best ? invTime(best) : "000000") + key;
- };
-
- for (const [key, es] of [...sub.entries()].sort((a, b) => (subKey(a) < subKey(b) ? -1 : subKey(a) > subKey(b) ? 1 : 0))) {
- const noteList = [];
- for (const e of es) for (const n of e.notes) if (!noteList.includes(n)) noteList.push(n);
- noteList.sort();
- const crumb = es.map(e => e.breadcrumb).find(b => b) || null;
- out.push("- 🔗 " + noteList.map(n => "[[" + n + "]]").join(" · ") + (crumb ? " · " + crumb : ""));
- const sorted = es.slice().sort((a, b) => {
- const ka = (a.time ? "0" : "1") + (a.time ? invTime(a.time) : "") + String(a.order).padStart(6, "0");
- const kb = (b.time ? "0" : "1") + (b.time ? invTime(b.time) : "") + String(b.order).padStart(6, "0");
- return ka < kb ? -1 : ka > kb ? 1 : 0;
- });
- for (const e of sorted) {
- let bl = e.lines, truncated = false;
- const totalChars = bl.reduce((s, x) => s + x[1].length, 0);
- if (totalChars > PREV_CHARS) { // 只有超过上限才截断;上限内全文展示
- truncated = true;
- const shown = [];
- let chars = 0;
- for (const [lvl, txt] of bl) {
- if (chars + txt.length > PREV_CHARS) {
- if (PREV_CHARS - chars > 0) shown.push([lvl, txt.slice(0, PREV_CHARS - chars) + "…"]);
- break;
- }
- shown.push([lvl, txt]);
- chars += txt.length;
- }
- bl = shown;
- }
- for (const [lvl, txt] of bl) out.push("\t".repeat(lvl + 1) + "- " + txt);
- if (truncated) {
- const target = e.note + (e.anchor ? "#" + e.anchor : "");
- const label = e.pageLevel ? "页面级标签 · 查看全文" : "查看全文";
- out.push("\t- [[" + target + "|↗ " + label + "]]");
- }
- }
- out.push("");
- }
- out.push("");
-}
-dv.paragraph(out.join("\n"));
-
-} catch (err) {
- dv.paragraph("> ❌ tagfeed-live 执行失败:" + err.message);
- console.error("tagfeed-live:", err);
-}
-
-```
diff --git "a/00index/0inbox/\351\237\263\351\242\221.md" "b/00index/0inbox/\351\237\263\351\242\221.md"
deleted file mode 100644
index d3885fd..0000000
--- "a/00index/0inbox/\351\237\263\351\242\221.md"
+++ /dev/null
@@ -1,437 +0,0 @@
----
-tags:
- - tagfeed-live
-cssclasses:
- - tagfeed
-words:
- 2026-08-23: 2728
----
-
-# 🏷 #音频(实时聚合页)
-
-> 本页由 DataviewJS 实时计算,笔记有变动时自动刷新,无需任何后台服务。
-
-```dataviewjs
-// ============================================================
-// tagfeed-live:Logseq 风格标签实时聚合(标准 DataviewJS 脚本)
-//
-// 用法:
-// 1. 新建一个笔记(例如 00index/标签聚合/标签名.md)
-// 2. 在笔记里插入一个 dataviewjs 代码块(三个反引号 + dataviewjs),
-// 把本脚本全部粘贴进去
-// 3. 只需要改下面「配置区」的两行:
-// - targetTag 换成你要聚合的标签(不带 #,中英文都可以;多个标签写成数组 ["a", "b"])
-// - excludedPaths 换成你想排除的文件夹
-// 4. 完成。以后任何笔记里出现/删除这个标签,本页自动刷新,
-// 不需要跑任何命令(Dataview 会在文件变动后自动重新渲染)。
-// ============================================================
-
-// ---------------- 配置区(通常只改这里) ----------------
-const targetTag = "音频"; // 要聚合的标签(不带 #);多个标签用数组:["lyric", "歌词"]
-const excludedPaths = ["X2.Archived", "00index", "X0.Clippings", "X1.Knomo", "02DS/02copilot", ".agents", ".claude", ".copilot", ".opencode", ".smart-env", ".workbuddy", ".trash"]; // 要排除的文件夹/路径前缀(vault 相对路径)
- // 例如:["00index", "模板", "X1.Knomo"]
-// 补充说明:
-// - 所有隐藏文件/文件夹(. 开头:.agents/.claude/.copilot/.opencode/.smart-env/.workbuddy/.trash 等)与 X.Attachment 始终跳过
-// - tagfeed 系统页会自动跳过:静态聚合页(文件头含 <!-- tagfeed 标记)
-// 和实时页(frontmatter 带 tagfeed / tagfeed-live 标签),
-// 无需手动排除 00index
-// - 本脚本所在的当前笔记也会自动跳过
-// ---------------------------------------------------------
-
-try {
-const SKIP_DIRS = [".obsidian", ".git", ".trash", "X.Attachment"];
-const MARKER = "<!-- tagfeed";
-const WEEKDAYS = ["周一", "周二", "周三", "周四", "周五", "周六", "周日"];
-const PREV_CHARS = 500; // 预览字数上限:≤上限全文展示;>上限截断并附「查看全文」链接
-
-const esc = s => String(s).replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
-const TAG_LIST = Array.isArray(targetTag) ? targetTag : [targetTag];
-const TAG_RE = new RegExp("(?<![\\w\\u4e00-\\u9fff#])#(?:" + TAG_LIST.map(esc).join("|") + ")(?![\\w\\u4e00-\\u9fff/\\-])");
-const ANY_TAG = /(?<![\w#])#([\w\u4e00-\u9fff][\w\u4e00-\u9fff/\-]*)/g;
-const LIST_RE = /^(\s*)([-*+]|\d+[.)])(?:\s+(.*))?$/;
-const HEAD_RE = /^(#{1,6})\s+(.*)$/;
-const DATEH_RE = /\[\[(\d{4})-(\d{2})-(\d{2})\]\]/;
-const TIME_RE = /(\d{1,2}):(\d{2})(?::(\d{2}))?/;
-const FENCE_RE = /^\s*(`{3,}|~~~)/;
-const indentOf = ln => { // tab 按 2 空格计(与 Python 版一致)
- const expanded = ln.replace(/\t/g, " ");
- return expanded.length - expanded.replace(/^ +/, "").length;
-};
-
-function cleanMatch(ln) {
- let s = ln.replace(/`[^`\n]*`/g, "");
- s = s.replace(/%%[\s\S]*?%%/g, "");
- s = s.replace(/<!--[\s\S]*?-->/g, "");
- return s;
-}
-function vdate(y, m, d) {
- const dt = new Date(y, m - 1, d);
- return (dt.getFullYear() === y && dt.getMonth() === m - 1 && dt.getDate() === d) ? dt : null;
-}
-function dateFromName(name) { // 文件名含 YYYY-MM-DD,或日记命名 26.0805 -> 2026-08-05
- const m1 = name.match(/(\d{4})-(\d{2})-(\d{2})/);
- if (m1) {
- const d1 = vdate(+m1[1], +m1[2], +m1[3]);
- if (d1) return d1;
- }
- const m = name.match(/^(\d{2})\.(\d{2})(\d{2})(?:\D|$)/);
- return m ? vdate(2000 + +m[1], +m[2], +m[3]) : null;
-}
-function parseFrontmatter(text) {
- const lines = text.split("\n");
- if (!lines.length || lines[0].trim() !== "---") return { tags: new Set(), date: null, bodyStart: 0 };
- const n = Math.min(lines.length, 200);
- for (let i = 1; i < n; i++) {
- const t = lines[i].trim();
- if (t === "---" || t === "...") {
- const block = lines.slice(1, i).join("\n");
- const tags = new Set();
- const im = block.match(/^tags:\s*\[(.*?)\]/m);
- if (im) {
- for (const x0 of im[1].split(",")) {
- const x = x0.trim().replace(/^["']|["']$/g, "").replace(/^#/, "").trim();
- if (x) tags.add(x);
- }
- } else {
- const bm = block.match(/^tags:\s*\n((?:[ \t]+-[ \t]+.*\n?)+)/m);
- if (bm) for (const ln of bm[1].split("\n")) {
- const m2 = ln.match(/^\s*-\s*["']?([\w\u4e00-\u9fff/\-]+)["']?\s*$/);
- if (m2) tags.add(m2[1]);
- }
- }
- let fdate = null;
- for (const key of ["date", "created", "published"]) {
- const dm = block.match(new RegExp("^" + key + ":\\s*[\"']?(\\d{4})-(\\d{2})-(\\d{2})", "m"));
- if (dm) { fdate = vdate(+dm[1], +dm[2], +dm[3]); break; }
- }
- return { tags, date: fdate, bodyStart: i + 1 };
- }
- }
- return { tags: new Set(), date: null, bodyStart: 0 };
-}
-const stripTag = s => String(s).replace(TAG_RE, "").replace(/[ \t]{2,}/g, " ").trim();
-
-function scanText(text, note, relpath, fileDate, mtimeDate) {
- const fm = parseFrontmatter(text);
- if (fm.tags.has("tagfeed") || fm.tags.has("tagfeed-live")) return []; // 跳过 tagfeed 系统页(静态聚合页/实时页)
- const lines = text.split("\n");
- const entries = [], seen = new Set();
- let order = 0, inFence = false, inPct = false, inHtml = false;
- let curHead = null, curHeadRaw = null, curDateHead = null;
-
- const entryDate = () => curDateHead || fileDate || fm.date || mtimeDate;
- const anchorOf = () => (curDateHead ? null : curHeadRaw);
- const breadcrumb = () => {
- if (curHead && !DATEH_RE.test(curHead)) {
- const t = curHead.replace(ANY_TAG, "").replace(/\s+/g, " ").trim();
- if (t) return t;
- }
- return null;
- };
- function addBlock(keyIdx, block) {
- const key = relpath + "|" + keyIdx;
- if (seen.has(key) || !block.length) return;
- seen.add(key);
- const d = entryDate();
- if (!d) return;
- let t = null;
- const tm = block[0][1].slice(0, 30).match(TIME_RE);
- if (tm) t = [+tm[1], +tm[2], +(tm[3] || 0)];
- order++;
- entries.push({ date: d, time: t, order, relpath, note,
- breadcrumb: breadcrumb(), lines: block,
- pageLevel: false, notes: [note], anchor: anchorOf() });
- }
- function collectChildren(i, parentIndent) {
- const kids = [], raw = [];
- let j = i + 1;
- while (j < lines.length) {
- const ln = lines[j];
- if (ln.trim() === "") {
- let k = j + 1;
- while (k < lines.length && lines[k].trim() === "") k++;
- if (k < lines.length && LIST_RE.test(lines[k]) && indentOf(lines[k]) > parentIndent) { j = k; continue; }
- break;
- }
- const mi = ln.match(LIST_RE);
- if (mi && indentOf(ln) > parentIndent) {
- raw.push([indentOf(ln), (mi[3] || "").replace(/\s+$/, "")]);
- j++;
- } else break;
- }
- for (const [ind, txt] of raw) kids.push([Math.floor((ind - parentIndent) / 2), txt]);
- return kids;
- }
-
- for (let i = fm.bodyStart; i < lines.length; i++) {
- const ln = lines[i];
- if (FENCE_RE.test(ln)) { inFence = !inFence; continue; }
- if (inFence) continue;
- if (inPct) { if (ln.includes("%%")) inPct = false; continue; }
- if (inHtml) { if (ln.includes("-->")) inHtml = false; continue; }
- const stripped = ln.trim();
- if (stripped.startsWith("%%") && !stripped.slice(2).includes("%%")) { inPct = true; continue; }
- if (stripped.startsWith("<!--") && !stripped.includes("-->")) { inHtml = true; continue; }
-
- const hm = ln.match(HEAD_RE);
- if (hm) {
- const htext = hm[2];
- const dh = htext.match(DATEH_RE);
- if (dh) { curDateHead = vdate(+dh[1], +dh[2], +dh[3]); curHead = null; curHeadRaw = null; }
- else { curHead = htext; curHeadRaw = htext.trim(); }
- if (TAG_RE.test(cleanMatch(htext))) {
- const sec = [[0, "**" + stripTag(htext) + "**"]];
- let j = i + 1;
- const level = hm[1].length;
- let count = 0;
- while (j < lines.length && count < 300) {
- const sl = lines[j];
- const shm = sl.match(HEAD_RE);
- if (shm && shm[1].length <= level) break;
- if (FENCE_RE.test(sl)) break;
- if (sl.trim()) {
- const smi = sl.match(LIST_RE);
- if (smi) sec.push([1 + Math.floor(indentOf(sl) / 2), (smi[3] || "").replace(/\s+$/, "")]);
- else sec.push([1, sl.trim()]);
- count++;
- }
- j++;
- }
- addBlock(i, sec);
- }
- continue;
- }
-
- const clean = cleanMatch(ln);
- if (!TAG_RE.test(clean)) continue;
-
- const mi = ln.match(LIST_RE);
- if (mi) {
- const ind = indentOf(ln);
- const itemText = mi[3] || "";
- const cleaned = stripTag(itemText);
- const kids = collectChildren(i, ind);
- addBlock(i, cleaned ? [[0, cleaned], ...kids] : [[0, itemText.trim()], ...kids]);
- continue;
- }
-
- // 独立标签行:向下吸收 list items,或向上归属到紧邻的块
- if (ln.replace(ANY_TAG, "").trim() === "" || ln.replace(ANY_TAG, "").trim() === "%%") {
- let sec = [];
- let j = i + 1;
- while (j < lines.length) {
- const mj = lines[j].match(LIST_RE);
- if (mj) { sec.push([indentOf(lines[j]), (mj[3] || "").replace(/\s+$/, "")]); j++; }
- else break;
- }
- let keyIdx = i;
- if (!sec.length) {
- const up = [];
- let k = i - 1, bulletIdx = null;
- while (k >= fm.bodyStart) {
- const bk = lines[k];
- if (bk.trim() === "" || HEAD_RE.test(bk) || FENCE_RE.test(bk)) break;
- up.push(k);
- if (LIST_RE.test(bk)) { bulletIdx = k; break; }
- k--;
- }
- if (bulletIdx !== null) {
- const inds = [];
- for (let bl = bulletIdx; bl < i; bl++) {
- const mb = lines[bl].match(LIST_RE);
- if (mb) { inds.push(indentOf(lines[bl])); sec.push([indentOf(lines[bl]), (mb[3] || "").replace(/\s+$/, "")]); }
- else sec.push([(inds.length ? inds[inds.length - 1] + 2 : 0), lines[bl].trim()]);
- }
- keyIdx = bulletIdx;
- } else if (up.length) {
- sec = up.slice().reverse().map(k2 => [0, lines[k2].trim()]);
- } else {
- // 兜底:标签行紧挨在某个标题下方(中间只有空行)→ 捕获该标题下的整个小节
- let h = i - 1;
- while (h >= fm.bodyStart && lines[h].trim() === "") h--;
- const hmk = h >= fm.bodyStart ? lines[h].match(HEAD_RE) : null;
- if (hmk) {
- const level = hmk[1].length;
- const title = hmk[2].replace(ANY_TAG, "").replace(/\s+/g, " ").trim();
- const sec2 = title ? [[0, "**" + title + "**"]] : [];
- let j2 = i + 1, count2 = 0;
- while (j2 < lines.length && count2 < 300) {
- const sl = lines[j2];
- const shm = sl.match(HEAD_RE);
- if (shm && shm[1].length <= level) break;
- if (FENCE_RE.test(sl)) break;
- if (sl.trim()) {
- const smi = sl.match(LIST_RE);
- if (smi) sec2.push([1 + Math.floor(indentOf(sl) / 2), (smi[3] || "").replace(/\s+$/, "")]);
- else sec2.push([1, sl.trim().replace(/^>\s*/, "")]);
- count2++;
- }
- j2++;
- }
- if (sec2.length) sec = sec2;
- }
- }
- }
- if (sec.length) {
- const mi0 = Math.min(...sec.map(x => x[0]));
- addBlock(keyIdx, sec.map(([ind, t]) => [Math.floor((ind - mi0) / 2), t]));
- }
- continue;
- }
-
- // 普通段落带标签:吸收到空行为止
- const para = [stripTag(clean)];
- let j = i + 1;
- while (j < lines.length) {
- const nl = lines[j];
- if (nl.trim() === "" || HEAD_RE.test(nl) || LIST_RE.test(nl) || FENCE_RE.test(nl)) break;
- para.push(nl.trim());
- j++;
- }
- addBlock(i, [[0, para.filter(p => p).join(" ")]]);
- }
-
- // frontmatter 标签 → 页面级条目(附正文开头预览)
- if (TAG_LIST.some(t => fm.tags.has(t))) {
- const d = fm.date || fileDate || mtimeDate;
- if (d) {
- order++;
- // 收集正文文本至 PREV_CHARS:超过上限的长文在渲染时截断并附「查看全文」链接
- const prev = [];
- let prevTotal = 0, prevCount = 0;
- for (let k = fm.bodyStart; k < lines.length && prevCount < 200; k++) {
- const s = lines[k].trim();
- if (!s || s.startsWith("#") || s === "---") continue;
- const clean = s.replace(ANY_TAG, "");
- if (!clean) continue;
- prev.push(clean);
- prevTotal += clean.length;
- prevCount++;
- if (prevTotal > PREV_CHARS) break; // 超过上限:已足够判断,停止收集
- }
- const preview = prev.join(" ");
- entries.push({ date: d, time: null, order, relpath, note, breadcrumb: null,
- lines: preview ? [[0, preview]] : [],
- pageLevel: true, notes: [note], anchor: null });
- }
- }
- return entries;
-}
-
-// ---------------- 扫描整个 vault ----------------
-const currentPath = (dv.current() && dv.current().file) ? dv.current().file.path : null;
-let all = [];
-for (const f of app.vault.getMarkdownFiles()) {
- if (f.path === currentPath || f.path.endsWith(".excalidraw.md")) continue;
- if (SKIP_DIRS.some(d => f.path.startsWith(d + "/"))) continue;
- if (f.path.split("/").some(seg => seg.startsWith("."))) continue; // 排除所有隐藏文件/文件夹(.开头)
- if (excludedPaths.some(p => f.path === p || f.path.startsWith(p.endsWith("/") ? p : p + "/"))) continue;
- let text;
- try { text = await app.vault.cachedRead(f); } catch (e) { continue; }
- if (text.slice(0, 1000).includes(MARKER)) continue; // 跳过 tagfeed 生成的静态页
- all = all.concat(scanText(text, f.basename, f.path, dateFromName(f.basename), new Date(f.stat.mtime)));
-}
-
-// ---------------- 去重:同日期+同内容合并,来源并排 ----------------
-const timeCmp = (a, b) => (a[0] - b[0]) || (a[1] - b[1]) || (a[2] - b[2]);
-const mergedMap = new Map();
-for (const e of all) {
- const fp = e.date.getTime() + "|" + e.lines.map(x => x[0] + ":" + x[1]).join("\n");
- const m = mergedMap.get(fp);
- if (m) {
- if (!m.notes.includes(e.note)) m.notes.push(e.note);
- if (e.time && (!m.time || timeCmp(e.time, m.time) < 0)) m.time = e.time;
- // 若先扫到的来源没有面包屑/锚点,而后面的有,则采用后者(与扫描顺序无关,结果确定)
- if (!m.breadcrumb && e.breadcrumb) {
- m.breadcrumb = e.breadcrumb;
- m.anchor = e.anchor;
- m.relpath = e.relpath;
- m.note = e.note;
- }
- } else mergedMap.set(fp, e);
-}
-const entries = [...mergedMap.values()];
-
-// ---------------- 渲染:按日期倒序 ----------------
-const byDate = new Map();
-for (const e of entries) {
- const k = e.date.getTime();
- if (!byDate.has(k)) byDate.set(k, []);
- byDate.get(k).push(e);
-}
-const dates = [...byDate.keys()].sort((a, b) => b - a);
-const noteSet = new Set();
-for (const e of entries) for (const n of e.notes) noteSet.add(n);
-
-const out = [];
-out.push("> **" + entries.length + "** 个内容块 | **" + noteSet.size + "** 篇笔记 | 按日期倒序 | 随笔记变动自动刷新");
-
-const pad2 = x => String(x).padStart(2, "0");
-const fmtDate = d => d.getFullYear() + "-" + pad2(d.getMonth() + 1) + "-" + pad2(d.getDate());
-const invTime = t => t.map(x => pad2(99 - x)).join("");
-
-for (const dk of dates) {
- const d = new Date(dk);
- out.push("## 📅 " + fmtDate(d) + " · " + WEEKDAYS[d.getDay() === 0 ? 6 : d.getDay() - 1]);
- out.push("");
- const group = byDate.get(dk);
-
- const sub = new Map(); // (笔记+面包屑) -> entries
- for (const e of group) {
- const key = e.relpath + "|" + (e.breadcrumb || "");
- if (!sub.has(key)) sub.set(key, []);
- sub.get(key).push(e);
- }
- const subKey = ([key, es]) => {
- const times = es.filter(e => e.time).map(e => e.time);
- const best = times.length ? times.reduce((a, b) => (timeCmp(a, b) > 0 ? a : b)) : null;
- return (best ? "0" : "1") + (best ? invTime(best) : "000000") + key;
- };
-
- for (const [key, es] of [...sub.entries()].sort((a, b) => (subKey(a) < subKey(b) ? -1 : subKey(a) > subKey(b) ? 1 : 0))) {
- const noteList = [];
- for (const e of es) for (const n of e.notes) if (!noteList.includes(n)) noteList.push(n);
- noteList.sort();
- const crumb = es.map(e => e.breadcrumb).find(b => b) || null;
- out.push("- 🔗 " + noteList.map(n => "[[" + n + "]]").join(" · ") + (crumb ? " · " + crumb : ""));
- const sorted = es.slice().sort((a, b) => {
- const ka = (a.time ? "0" : "1") + (a.time ? invTime(a.time) : "") + String(a.order).padStart(6, "0");
- const kb = (b.time ? "0" : "1") + (b.time ? invTime(b.time) : "") + String(b.order).padStart(6, "0");
- return ka < kb ? -1 : ka > kb ? 1 : 0;
- });
- for (const e of sorted) {
- let bl = e.lines, truncated = false;
- const totalChars = bl.reduce((s, x) => s + x[1].length, 0);
- if (totalChars > PREV_CHARS) { // 只有超过上限才截断;上限内全文展示
- truncated = true;
- const shown = [];
- let chars = 0;
- for (const [lvl, txt] of bl) {
- if (chars + txt.length > PREV_CHARS) {
- if (PREV_CHARS - chars > 0) shown.push([lvl, txt.slice(0, PREV_CHARS - chars) + "…"]);
- break;
- }
- shown.push([lvl, txt]);
- chars += txt.length;
- }
- bl = shown;
- }
- for (const [lvl, txt] of bl) out.push("\t".repeat(lvl + 1) + "- " + txt);
- if (truncated) {
- const target = e.note + (e.anchor ? "#" + e.anchor : "");
- const label = e.pageLevel ? "页面级标签 · 查看全文" : "查看全文";
- out.push("\t- [[" + target + "|↗ " + label + "]]");
- }
- }
- out.push("");
- }
- out.push("");
-}
-dv.paragraph(out.join("\n"));
-
-} catch (err) {
- dv.paragraph("> ❌ tagfeed-live 执行失败:" + err.message);
- console.error("tagfeed-live:", err);
-}
-
-```
diff --git "a/00index/\346\240\207\347\255\276\350\201\232\345\220\210/1.6\347\216\251\350\275\257\344\273\266/\346\223\215\344\275\234\347\263\273\347\273\237/ios.md" "b/00index/\346\240\207\347\255\276\350\201\232\345\220\210/1.6\347\216\251\350\275\257\344\273\266/\346\223\215\344\275\234\347\263\273\347\273\237/ios.md"
index 4fa3fa1..99788d3 100644
--- "a/00index/\346\240\207\347\255\276\350\201\232\345\220\210/1.6\347\216\251\350\275\257\344\273\266/\346\223\215\344\275\234\347\263\273\347\273\237/ios.md"
+++ "b/00index/\346\240\207\347\255\276\350\201\232\345\220\210/1.6\347\216\251\350\275\257\344\273\266/\346\223\215\344\275\234\347\263\273\347\273\237/ios.md"
@@ -15,5 +15,5 @@
## 📅 2026-07-27 · 周一
-- 🔗 [[26.0727]] · [[Memos-2026-07]] · MemoDaily
+- 🔗 [[2026-07-27]] · [[Memos-2026-07]] · MemoDaily
- 20:28:28 这就是 LaudableApps —— 一个专门收集“全球最美iOS应用”的线上艺术馆 #ui-好设计
diff --git "a/01GTD/26.07\346\263\250\351\224\200\344\270\252\344\275\223\346\210\267/26.0714-172251\346\263\250\351\224\200\351\276\231\345\215\216\344\270\252\344\275\223\346\210\267.md" "b/01GTD/26.07\346\263\250\351\224\200\344\270\252\344\275\223\346\210\267/2026-07-14-172251\346\263\250\351\224\200\351\276\231\345\215\216\344\270\252\344\275\223\346\210\267.md"
similarity index 100%
rename from "01GTD/26.07\346\263\250\351\224\200\344\270\252\344\275\223\346\210\267/26.0714-172251\346\263\250\351\224\200\351\276\231\345\215\216\344\270\252\344\275\223\346\210\267.md"
rename to "01GTD/26.07\346\263\250\351\224\200\344\270\252\344\275\223\346\210\267/2026-07-14-172251\346\263\250\351\224\200\351\276\231\345\215\216\344\270\252\344\275\223\346\210\267.md"
diff --git "a/01GTD/26.07\346\263\250\351\224\200\344\270\252\344\275\223\346\210\267/Drawing 26.0714_\346\263\250\351\224\200\351\276\231\345\215\216\344\270\252\344\275\223\346\210\267.excalidraw.md" "b/01GTD/26.07\346\263\250\351\224\200\344\270\252\344\275\223\346\210\267/Drawing 26.0714_\346\263\250\351\224\200\351\276\231\345\215\216\344\270\252\344\275\223\346\210\267.excalidraw.md"
index c881b54..d557383 100644
--- "a/01GTD/26.07\346\263\250\351\224\200\344\270\252\344\275\223\346\210\267/Drawing 26.0714_\346\263\250\351\224\200\351\276\231\345\215\216\344\270\252\344\275\223\346\210\267.excalidraw.md"
+++ "b/01GTD/26.07\346\263\250\351\224\200\344\270\252\344\275\223\346\210\267/Drawing 26.0714_\346\263\250\351\224\200\351\276\231\345\215\216\344\270\252\344\275\223\346\210\267.excalidraw.md"
@@ -62,7 +62,7 @@
-其他-逾期补申报 ^v4g5hsdO
## Embedded Files
-8c80caa30f663843e8cd3bf94df340b3c5600dd1: [[26.0714-172251注销龙华个体户]]
+8c80caa30f663843e8cd3bf94df340b3c5600dd1: [[2026-07-14-172251注销龙华个体户]]
0a25aa24929585276dd5e18b9538d43d372f68ff: [[Pasted Image 20260723151208_190.png]]
diff --git "a/01GTD/2603-\345\207\272\347\247\237\345\230\211\346\266\246/26.0323_141214_.md" "b/01GTD/2603-\345\207\272\347\247\237\345\230\211\346\266\246/2026-03-23_141214_.md"
similarity index 100%
rename from "01GTD/2603-\345\207\272\347\247\237\345\230\211\346\266\246/26.0323_141214_.md"
rename to "01GTD/2603-\345\207\272\347\247\237\345\230\211\346\266\246/2026-03-23_141214_.md"
diff --git "a/01GTD/2603-\345\207\272\347\247\237\345\230\211\346\266\246/26.0322\347\273\231\344\272\272\345\217\221\345\233\276-\345\230\211\346\266\246 2304/26.0323_135156_.md" "b/01GTD/2603-\345\207\272\347\247\237\345\230\211\346\266\246/26.0322\347\273\231\344\272\272\345\217\221\345\233\276-\345\230\211\346\266\246 2304/2026-03-23_135156_.md"
similarity index 100%
rename from "01GTD/2603-\345\207\272\347\247\237\345\230\211\346\266\246/26.0322\347\273\231\344\272\272\345\217\221\345\233\276-\345\230\211\346\266\246 2304/26.0323_135156_.md"
rename to "01GTD/2603-\345\207\272\347\247\237\345\230\211\346\266\246/26.0322\347\273\231\344\272\272\345\217\221\345\233\276-\345\230\211\346\266\246 2304/2026-03-23_135156_.md"
diff --git "a/02DS/AI-vibe-coding/case1-mac-app-launcher/X-\345\255\230\346\241\243/X-\351\234\200\346\261\202\346\217\217\350\277\260/26.0513-01-\345\210\227\350\241\250\350\247\206\345\233\276\345\242\236\345\212\240\346\240\207\347\255\276\347\232\204\346\213\226\345\212\250\346\216\222\345\272\217.md" "b/02DS/AI-vibe-coding/case1-mac-app-launcher/X-\345\255\230\346\241\243/X-\351\234\200\346\261\202\346\217\217\350\277\260/2026-05-13-01-\345\210\227\350\241\250\350\247\206\345\233\276\345\242\236\345\212\240\346\240\207\347\255\276\347\232\204\346\213\226\345\212\250\346\216\222\345\272\217.md"
similarity index 100%
rename from "02DS/AI-vibe-coding/case1-mac-app-launcher/X-\345\255\230\346\241\243/X-\351\234\200\346\261\202\346\217\217\350\277\260/26.0513-01-\345\210\227\350\241\250\350\247\206\345\233\276\345\242\236\345\212\240\346\240\207\347\255\276\347\232\204\346\213\226\345\212\250\346\216\222\345\272\217.md"
rename to "02DS/AI-vibe-coding/case1-mac-app-launcher/X-\345\255\230\346\241\243/X-\351\234\200\346\261\202\346\217\217\350\277\260/2026-05-13-01-\345\210\227\350\241\250\350\247\206\345\233\276\345\242\236\345\212\240\346\240\207\347\255\276\347\232\204\346\213\226\345\212\250\346\216\222\345\272\217.md"
diff --git "a/02DS/Ob/\346\217\222\344\273\266Notechain/26.0814-2228-Notechain.md" "b/02DS/Ob/\346\217\222\344\273\266Notechain/2026-08-14-2228-Notechain.md"
similarity index 100%
rename from "02DS/Ob/\346\217\222\344\273\266Notechain/26.0814-2228-Notechain.md"
rename to "02DS/Ob/\346\217\222\344\273\266Notechain/2026-08-14-2228-Notechain.md"
diff --git "a/02DS/VIbeCoding\350\277\233\351\230\266-\351\230\205\350\257\273\350\265\204\346\226\231/05-\344\270\223\351\242\230-UI-\350\247\206\350\247\211\350\256\276\350\256\241\350\203\275\345\212\233/UI-\350\256\276\350\256\241\350\247\204\350\214\203/26.0716-1220-\346\226\260\347\202\271\345\255\220-\346\231\272\350\203\275\346\217\220\347\202\274UIKIT.md" "b/02DS/VIbeCoding\350\277\233\351\230\266-\351\230\205\350\257\273\350\265\204\346\226\231/05-\344\270\223\351\242\230-UI-\350\247\206\350\247\211\350\256\276\350\256\241\350\203\275\345\212\233/UI-\350\256\276\350\256\241\350\247\204\350\214\203/2026-07-16-1220-\346\226\260\347\202\271\345\255\220-\346\231\272\350\203\275\346\217\220\347\202\274UIKIT.md"
similarity index 100%
rename from "02DS/VIbeCoding\350\277\233\351\230\266-\351\230\205\350\257\273\350\265\204\346\226\231/05-\344\270\223\351\242\230-UI-\350\247\206\350\247\211\350\256\276\350\256\241\350\203\275\345\212\233/UI-\350\256\276\350\256\241\350\247\204\350\214\203/26.0716-1220-\346\226\260\347\202\271\345\255\220-\346\231\272\350\203\275\346\217\220\347\202\274UIKIT.md"
rename to "02DS/VIbeCoding\350\277\233\351\230\266-\351\230\205\350\257\273\350\265\204\346\226\231/05-\344\270\223\351\242\230-UI-\350\247\206\350\247\211\350\256\276\350\256\241\350\203\275\345\212\233/UI-\350\256\276\350\256\241\350\247\204\350\214\203/2026-07-16-1220-\346\226\260\347\202\271\345\255\220-\346\231\272\350\203\275\346\217\220\347\202\274UIKIT.md"
diff --git a/X0.Diary/26.0322.md b/X0.Diary/2026-03-22.md
similarity index 89%
rename from X0.Diary/26.0322.md
rename to X0.Diary/2026-03-22.md
index 10919d6..60262a6 100755
--- a/X0.Diary/26.0322.md
+++ b/X0.Diary/2026-03-22.md
@@ -1,6 +1,7 @@
---
words:
2026-04-05: 159
+ 2026-08-25: 168
---
# GTD
@@ -15,7 +16,7 @@
# Misc
-- ~~~~
+
# ddup
@@ -27,6 +28,8 @@
- CD进入文件
- 适用下面的命令进行合并
+
+#### #命令行 合并声音文件和视频文件
```bash
ffmpeg -y -i 2.mp4 -i 2.m4a -c:v copy -c:a copy -shortest 合并.mp4
```
\ No newline at end of file
diff --git a/X0.Diary/26.0323.md b/X0.Diary/2026-03-23.md
similarity index 100%
rename from X0.Diary/26.0323.md
rename to X0.Diary/2026-03-23.md
diff --git a/X0.Diary/26.0324.md b/X0.Diary/2026-03-24.md
similarity index 100%
rename from X0.Diary/26.0324.md
rename to X0.Diary/2026-03-24.md
diff --git a/X0.Diary/26.0326/26.0326.md b/X0.Diary/2026-03-26/2026-03-26.md
similarity index 100%
rename from X0.Diary/26.0326/26.0326.md
rename to X0.Diary/2026-03-26/2026-03-26.md
diff --git a/X0.Diary/26.0326/Drawing 26.0326.excalidraw.md b/X0.Diary/2026-03-26/Drawing 26.0326.excalidraw.md
similarity index 100%
rename from X0.Diary/26.0326/Drawing 26.0326.excalidraw.md
rename to X0.Diary/2026-03-26/Drawing 26.0326.excalidraw.md
diff --git a/X0.Diary/26.0401.md b/X0.Diary/2026-04-01.md
similarity index 100%
rename from X0.Diary/26.0401.md
rename to X0.Diary/2026-04-01.md
diff --git a/X0.Diary/26.0405.md b/X0.Diary/2026-04-05.md
similarity index 100%
rename from X0.Diary/26.0405.md
rename to X0.Diary/2026-04-05.md
diff --git a/X0.Diary/26.0509.md b/X0.Diary/2026-05-09.md
similarity index 100%
rename from X0.Diary/26.0509.md
rename to X0.Diary/2026-05-09.md
diff --git a/X0.Diary/26.0523.md b/X0.Diary/2026-05-23.md
similarity index 100%
rename from X0.Diary/26.0523.md
rename to X0.Diary/2026-05-23.md
diff --git a/X0.Diary/26.0524.md b/X0.Diary/2026-05-24.md
similarity index 100%
rename from X0.Diary/26.0524.md
rename to X0.Diary/2026-05-24.md
diff --git a/X0.Diary/26.0526.md b/X0.Diary/2026-05-26.md
similarity index 100%
rename from X0.Diary/26.0526.md
rename to X0.Diary/2026-05-26.md
diff --git a/X0.Diary/26.0530.md b/X0.Diary/2026-05-30.md
similarity index 100%
rename from X0.Diary/26.0530.md
rename to X0.Diary/2026-05-30.md
diff --git a/X0.Diary/26.0625.md b/X0.Diary/2026-06-25.md
similarity index 100%
rename from X0.Diary/26.0625.md
rename to X0.Diary/2026-06-25.md
diff --git "a/X0.Diary/26.0712-\345\220\215\350\257\215\350\247\243\351\207\212-npx.md" "b/X0.Diary/2026-07-12-\345\220\215\350\257\215\350\247\243\351\207\212-npx.md"
similarity index 100%
rename from "X0.Diary/26.0712-\345\220\215\350\257\215\350\247\243\351\207\212-npx.md"
rename to "X0.Diary/2026-07-12-\345\220\215\350\257\215\350\247\243\351\207\212-npx.md"
diff --git a/X0.Diary/26.0714.md b/X0.Diary/2026-07-14.md
similarity index 100%
rename from X0.Diary/26.0714.md
rename to X0.Diary/2026-07-14.md
diff --git a/X0.Diary/26.0715.md b/X0.Diary/2026-07-15.md
similarity index 100%
rename from X0.Diary/26.0715.md
rename to X0.Diary/2026-07-15.md
diff --git a/X0.Diary/26.0716.md b/X0.Diary/2026-07-16.md
similarity index 100%
rename from X0.Diary/26.0716.md
rename to X0.Diary/2026-07-16.md
diff --git a/X0.Diary/26.0717.md b/X0.Diary/2026-07-17.md
similarity index 100%
rename from X0.Diary/26.0717.md
rename to X0.Diary/2026-07-17.md
diff --git a/X0.Diary/26.0718.md b/X0.Diary/2026-07-18.md
similarity index 100%
rename from X0.Diary/26.0718.md
rename to X0.Diary/2026-07-18.md
diff --git a/X0.Diary/26.0720.md b/X0.Diary/2026-07-20.md
similarity index 100%
rename from X0.Diary/26.0720.md
rename to X0.Diary/2026-07-20.md
diff --git a/X0.Diary/26.0721.md b/X0.Diary/2026-07-21.md
similarity index 71%
rename from X0.Diary/26.0721.md
rename to X0.Diary/2026-07-21.md
index 47460a8..cd04695 100644
--- a/X0.Diary/26.0721.md
+++ b/X0.Diary/2026-07-21.md
@@ -5,7 +5,7 @@
# Misc
- - #名词解释 [[26.0712-名词解释-npx]]
+ - #名词解释 [[2026-07-12-名词解释-npx]]
- ![[工程管理#^clippedframe=superpower以及相关方案的对比|26.0722-0032-研究superpower]]
diff --git a/X0.Diary/26.0722.md b/X0.Diary/2026-07-22.md
similarity index 100%
rename from X0.Diary/26.0722.md
rename to X0.Diary/2026-07-22.md
diff --git a/X0.Diary/26.0723.md b/X0.Diary/2026-07-23.md
similarity index 100%
rename from X0.Diary/26.0723.md
rename to X0.Diary/2026-07-23.md
diff --git a/X0.Diary/26.0727.md b/X0.Diary/2026-07-27.md
similarity index 100%
rename from X0.Diary/26.0727.md
rename to X0.Diary/2026-07-27.md
diff --git a/X0.Diary/26.0728.md b/X0.Diary/2026-07-28.md
similarity index 100%
rename from X0.Diary/26.0728.md
rename to X0.Diary/2026-07-28.md
diff --git a/X0.Diary/26.0731.md b/X0.Diary/2026-07-31.md
similarity index 100%
rename from X0.Diary/26.0731.md
rename to X0.Diary/2026-07-31.md
diff --git a/X0.Diary/26.0803.md b/X0.Diary/2026-08-03.md
similarity index 100%
rename from X0.Diary/26.0803.md
rename to X0.Diary/2026-08-03.md
diff --git a/X0.Diary/26.0804.md b/X0.Diary/2026-08-04.md
similarity index 100%
rename from X0.Diary/26.0804.md
rename to X0.Diary/2026-08-04.md
diff --git a/X0.Diary/26.0805.md b/X0.Diary/2026-08-05.md
similarity index 100%
rename from X0.Diary/26.0805.md
rename to X0.Diary/2026-08-05.md
diff --git a/X0.Diary/26.0806.md b/X0.Diary/2026-08-06.md
similarity index 100%
rename from X0.Diary/26.0806.md
rename to X0.Diary/2026-08-06.md
diff --git a/X0.Diary/26.0807.md b/X0.Diary/2026-08-07.md
similarity index 100%
rename from X0.Diary/26.0807.md
rename to X0.Diary/2026-08-07.md
diff --git a/X0.Diary/26.0808.md b/X0.Diary/2026-08-08.md
similarity index 100%
rename from X0.Diary/26.0808.md
rename to X0.Diary/2026-08-08.md
diff --git a/X0.Diary/26.0809.md b/X0.Diary/2026-08-09.md
similarity index 100%
rename from X0.Diary/26.0809.md
rename to X0.Diary/2026-08-09.md
diff --git a/X0.Diary/26.0810.md b/X0.Diary/2026-08-10.md
similarity index 100%
rename from X0.Diary/26.0810.md
rename to X0.Diary/2026-08-10.md
diff --git a/X0.Diary/26.0811.md b/X0.Diary/2026-08-11.md
similarity index 100%
rename from X0.Diary/26.0811.md
rename to X0.Diary/2026-08-11.md
diff --git a/X0.Diary/26.0812.md b/X0.Diary/2026-08-12.md
similarity index 100%
rename from X0.Diary/26.0812.md
rename to X0.Diary/2026-08-12.md
diff --git a/X0.Diary/26.0813.md b/X0.Diary/2026-08-13.md
similarity index 100%
rename from X0.Diary/26.0813.md
rename to X0.Diary/2026-08-13.md
diff --git a/X0.Diary/26.0814.md b/X0.Diary/2026-08-14.md
similarity index 97%
rename from X0.Diary/26.0814.md
rename to X0.Diary/2026-08-14.md
index 07e6078..964bba1 100644
--- a/X0.Diary/26.0814.md
+++ b/X0.Diary/2026-08-14.md
@@ -52,7 +52,7 @@
- 03:52 ✏️ [[小音箱-声阔A31X1C_SC_UG_V2_20260120.pdf]]
- 📝 **笔记** · 14 项
- 22:39 ✏️ [[插件Notechain]]
- - 22:31 ✏️ [[26.0814-2228-Notechain]]
+ - 22:31 ✏️ [[2026-08-14-2228-Notechain]]
- 21:14 ✏️ [[2608-13-211819-需要对mindr]]
- 21:14 ✏️ [[2608-07-145945-将iPhonei]]
- 21:14 ✏️ [[2608-10-000943-关于浪妹APP智]]
diff --git a/X0.Diary/26.0818.md b/X0.Diary/2026-08-18.md
similarity index 100%
rename from X0.Diary/26.0818.md
rename to X0.Diary/2026-08-18.md
diff --git a/X0.Diary/26.0819.md b/X0.Diary/2026-08-19.md
similarity index 100%
rename from X0.Diary/26.0819.md
rename to X0.Diary/2026-08-19.md
diff --git a/X0.Diary/26.0820.md b/X0.Diary/2026-08-20.md
similarity index 100%
rename from X0.Diary/26.0820.md
rename to X0.Diary/2026-08-20.md
diff --git a/X0.Diary/26.0821.md b/X0.Diary/2026-08-21.md
similarity index 100%
rename from X0.Diary/26.0821.md
rename to X0.Diary/2026-08-21.md
diff --git a/X0.Diary/26.0822.md b/X0.Diary/2026-08-22.md
similarity index 100%
rename from X0.Diary/26.0822.md
rename to X0.Diary/2026-08-22.md
diff --git a/X0.Diary/26.0823.md b/X0.Diary/2026-08-23.md
similarity index 100%
rename from X0.Diary/26.0823.md
rename to X0.Diary/2026-08-23.md
diff --git a/X0.Diary/26.0824.md b/X0.Diary/2026-08-24.md
similarity index 100%
rename from X0.Diary/26.0824.md
rename to X0.Diary/2026-08-24.md
diff --git a/X0.Diary/26.0825.md b/X0.Diary/2026-08-25.md
similarity index 96%
rename from X0.Diary/26.0825.md
rename to X0.Diary/2026-08-25.md
index d2aa05c..4277392 100644
--- a/X0.Diary/26.0825.md
+++ b/X0.Diary/2026-08-25.md
@@ -1,6 +1,6 @@
---
words:
- 2026-08-25: 1010
+ 2026-08-25: 1009
---
# 26.0825
@@ -9,6 +9,7 @@
## GTD
+[list2table]
- 工作流建设备忘 #工作流 #ob
- [x] [[01全局自动化索引与整理逻辑]]
- #软件研究 #音频处理
@@ -74,7 +75,7 @@
- 15:03 ✏️ [[00index.canvas生成原理说明]]
- 14:58 ✏️ [[官网做完别只放截图:Codex + Remotion 做了一条 24 秒产品演示]]
- 14:58 ✏️ [[达利欧最新长文2026像极了1929]]
- - 14:53 ✏️ [[26.0814-2228-Notechain]]
+ - 14:53 ✏️ [[2026-08-14-2228-Notechain]]
- 14:52 ✏️ [[ignis-让原版 Obsidian 随处可用-浏览器访问自动同步]]
- 14:45 ✏️ [[2026-08-25-本机ob本仓库git启用备忘]]
- 14:42 ✏️ [[2026-08-25-开源项目openconnector介绍]]
@@ -83,7 +84,7 @@
- 13:47 ✏️ [[02DS/02copilot/skills/json-canvas/SKILL|SKILL]]
- 12:58 ✏️ [[2026-08-25-命运-麦家的观点]]
- 11:38 ✏️ [[2026-08-23-尺蠖效应]]
- - 11:31 ✏️ [[26.0716-1220-新点子-智能提炼UIKIT]]
+ - 11:31 ✏️ [[2026-07-16-1220-新点子-智能提炼UIKIT]]
- 11:11 ✏️ [[安装记录]]
- 11:11 ✏️ [[2.0805安装录音转写Titanvault]]
- 11:01 ✏️ [[Everything You Need to Ace Computer Science and Coding in One Big Fat Notebook]]
diff --git "a/X0.Diary/26.0320/26.0320_150838_clash \350\256\276\347\275\256\345\244\207\345\277\230.md" "b/X0.Diary/26.0320/2026-03-20_150838_clash \350\256\276\347\275\256\345\244\207\345\277\230.md"
similarity index 100%
rename from "X0.Diary/26.0320/26.0320_150838_clash \350\256\276\347\275\256\345\244\207\345\277\230.md"
rename to "X0.Diary/26.0320/2026-03-20_150838_clash \350\256\276\347\275\256\345\244\207\345\277\230.md"
diff --git "a/X0.Diary/26.0321/26.0321-\345\222\214\344\272\247\347\240\224\345\274\200\344\274\232\350\256\250\350\256\272\345\267\245\344\275\234.md" "b/X0.Diary/26.0321/2026-03-21-\345\222\214\344\272\247\347\240\224\345\274\200\344\274\232\350\256\250\350\256\272\345\267\245\344\275\234.md"
similarity index 100%
rename from "X0.Diary/26.0321/26.0321-\345\222\214\344\272\247\347\240\224\345\274\200\344\274\232\350\256\250\350\256\272\345\267\245\344\275\234.md"
rename to "X0.Diary/26.0321/2026-03-21-\345\222\214\344\272\247\347\240\224\345\274\200\344\274\232\350\256\250\350\256\272\345\267\245\344\275\234.md"
diff --git "a/X0.Diary/26.0321/26.0321-\344\275\223\351\252\214 ijoy/26.0321-\344\275\223\351\252\214ijoy\345\271\266\345\201\232\347\254\224\350\256\260.md" "b/X0.Diary/26.0321/26.0321-\344\275\223\351\252\214 ijoy/2026-03-21-\344\275\223\351\252\214ijoy\345\271\266\345\201\232\347\254\224\350\256\260.md"
similarity index 100%
rename from "X0.Diary/26.0321/26.0321-\344\275\223\351\252\214 ijoy/26.0321-\344\275\223\351\252\214ijoy\345\271\266\345\201\232\347\254\224\350\256\260.md"
rename to "X0.Diary/26.0321/26.0321-\344\275\223\351\252\214 ijoy/2026-03-21-\344\275\223\351\252\214ijoy\345\271\266\345\201\232\347\254\224\350\256\260.md"
diff --git a/X1.Knomo/_knomo-system/indexes/memo-index-2026-07.json b/X1.Knomo/_knomo-system/indexes/memo-index-2026-07.json
index 9038ec9..048e1ad 100644
--- a/X1.Knomo/_knomo-system/indexes/memo-index-2026-07.json
+++ b/X1.Knomo/_knomo-system/indexes/memo-index-2026-07.json
@@ -1,7 +1,7 @@
{
"schemaVersion": 2,
"period": "2026-07",
- "updatedAt": "2026-08-24T16:35:57.420Z",
+ "updatedAt": "2026-08-25T08:22:02.013Z",
"memos": {
"2026071521161717": {
"id": "2026071521161717",
@@ -239,6 +239,203 @@
"lineNumberHint": 20,
"lastSyncedAt": "2026-08-13T15:13:30.980Z"
}
+ },
+ "2026071821071958": {
+ "id": "2026071821071958",
+ "createdAt": "2026-07-18T21:07:19.000+08:00",
+ "updatedAt": "2026-07-18T21:07:19.000+08:00",
+ "contentSnapshot": "接下来要做几件事。做一个网站,就像奇迹秀一样,把常用的各种类型的软件都整理起来。然后,前期我一个人维护,但是后续把它做成豆瓣式的体验,这样的话,就可以大家来维护它。然后顺便着推自己的软件。 #ideatodo ",
+ "contentHash": "fnv1a-0ea1a067",
+ "status": "active",
+ "syncStatus": "synced",
+ "source": "daily_scan",
+ "version": 1,
+ "tags": [
+ "ideatodo"
+ ],
+ "links": [],
+ "images": [],
+ "references": [],
+ "sourceMemoId": null,
+ "issue": null,
+ "lastMarkdownSyncAt": "2026-08-25T08:21:35.650Z",
+ "lastMarkdownSyncSource": "file_watch",
+ "dailyRef": {
+ "path": "X0.Diary/2026-07-18.md",
+ "heading": "# MemoDaily",
+ "sectionType": "heading",
+ "lastKnownBlock": "- 21:07:19 接下来要做几件事。做一个网站,就像奇迹秀一样,把常用的各种类型的软件都整理起来。然后,前期我一个人维护,但是后续把它做成豆瓣式的体验,这样的话,就可以大家来维护它。然后顺便着推自己的软件。 #ideatodo ",
+ "lastKnownHash": "fnv1a-306ba93c",
+ "lineNumberHint": 8,
+ "lastSyncedAt": "2026-08-25T08:21:35.650Z"
+ },
+ "monthlyRef": {
+ "path": "X1.Knomo/Memos-2026-07.md",
+ "dateHeading": "## [[2026-07-18]]",
+ "lastKnownBlock": "- 21:07:19 接下来要做几件事。做一个网站,就像奇迹秀一样,把常用的各种类型的软件都整理起来。然后,前期我一个人维护,但是后续把它做成豆瓣式的体验,这样的话,就可以大家来维护它。然后顺便着推自己的软件。 #ideatodo ",
+ "lastKnownHash": "fnv1a-306ba93c",
+ "lineNumberHint": 17,
+ "lastSyncedAt": "2026-08-25T08:21:36.217Z"
+ }
+ },
+ "2026071821074922": {
+ "id": "2026071821074922",
+ "createdAt": "2026-07-18T21:07:49.000+08:00",
+ "updatedAt": "2026-07-18T21:07:49.000+08:00",
+ "contentSnapshot": "然后做两个视频号。一个视频号的定位是?分享软件的。另一个视频号是专门谈音乐的。 #ideatodo ",
+ "contentHash": "fnv1a-76523841",
+ "status": "active",
+ "syncStatus": "synced",
+ "source": "daily_scan",
+ "version": 1,
+ "tags": [
+ "ideatodo"
+ ],
+ "links": [],
+ "images": [],
+ "references": [],
+ "sourceMemoId": null,
+ "issue": null,
+ "lastMarkdownSyncAt": "2026-08-25T08:21:36.916Z",
+ "lastMarkdownSyncSource": "file_watch",
+ "dailyRef": {
+ "path": "X0.Diary/2026-07-18.md",
+ "heading": "# MemoDaily",
+ "sectionType": "heading",
+ "lastKnownBlock": "- 21:07:49 然后做两个视频号。一个视频号的定位是?分享软件的。另一个视频号是专门谈音乐的。 #ideatodo ",
+ "lastKnownHash": "fnv1a-2059a48f",
+ "lineNumberHint": 9,
+ "lastSyncedAt": "2026-08-25T08:21:36.916Z"
+ },
+ "monthlyRef": {
+ "path": "X1.Knomo/Memos-2026-07.md",
+ "dateHeading": "## [[2026-07-18]]",
+ "lastKnownBlock": "- 21:07:49 然后做两个视频号。一个视频号的定位是?分享软件的。另一个视频号是专门谈音乐的。 #ideatodo ",
+ "lastKnownHash": "fnv1a-2059a48f",
+ "lineNumberHint": 18,
+ "lastSyncedAt": "2026-08-25T08:21:37.577Z"
+ }
+ },
+ "2026071722355167": {
+ "id": "2026071722355167",
+ "createdAt": "2026-07-17T22:35:51.000+08:00",
+ "updatedAt": "2026-07-17T22:35:51.000+08:00",
+ "contentSnapshot": "obsidian-打通微信--www.bijitongbu.site #已解决 #收费 #微信小程序",
+ "contentHash": "fnv1a-c61deba4",
+ "status": "active",
+ "syncStatus": "synced",
+ "source": "daily_scan",
+ "version": 1,
+ "tags": [
+ "已解决",
+ "收费",
+ "微信小程序"
+ ],
+ "links": [],
+ "images": [],
+ "references": [],
+ "sourceMemoId": null,
+ "issue": null,
+ "lastMarkdownSyncAt": "2026-08-25T08:21:49.662Z",
+ "lastMarkdownSyncSource": "file_watch",
+ "dailyRef": {
+ "path": "X0.Diary/2026-07-17.md",
+ "heading": "# MemoDaily",
+ "sectionType": "heading",
+ "lastKnownBlock": "- 22:35:51 obsidian-打通微信--www.bijitongbu.site #已解决 #收费 #微信小程序",
+ "lastKnownHash": "fnv1a-5a495a43",
+ "lineNumberHint": 5,
+ "lastSyncedAt": "2026-08-25T08:21:49.662Z"
+ },
+ "monthlyRef": {
+ "path": "X1.Knomo/Memos-2026-07.md",
+ "dateHeading": "## [[2026-07-17]]",
+ "lastKnownBlock": "- 22:35:51 obsidian-打通微信--www.bijitongbu.site #已解决 #收费 #微信小程序",
+ "lastKnownHash": "fnv1a-5a495a43",
+ "lineNumberHint": 14,
+ "lastSyncedAt": "2026-08-25T08:21:53.180Z"
+ }
+ },
+ "2026072720282866": {
+ "id": "2026072720282866",
+ "createdAt": "2026-07-27T20:28:28.000+08:00",
+ "updatedAt": "2026-07-27T20:28:28.000+08:00",
+ "contentSnapshot": "这就是 LaudableApps —— 一个专门收集“全球最美iOS应用”的线上艺术馆 #ios #ui-好设计 ",
+ "contentHash": "fnv1a-66ae4900",
+ "status": "active",
+ "syncStatus": "synced",
+ "source": "daily_scan",
+ "version": 1,
+ "tags": [
+ "ios",
+ "ui-好设计"
+ ],
+ "links": [],
+ "images": [],
+ "references": [],
+ "sourceMemoId": null,
+ "issue": null,
+ "lastMarkdownSyncAt": "2026-08-25T08:21:59.551Z",
+ "lastMarkdownSyncSource": "file_watch",
+ "dailyRef": {
+ "path": "X0.Diary/2026-07-27.md",
+ "heading": "# MemoDaily",
+ "sectionType": "heading",
+ "lastKnownBlock": "- 20:28:28 这就是 LaudableApps —— 一个专门收集“全球最美iOS应用”的线上艺术馆 #ios #ui-好设计 ",
+ "lastKnownHash": "fnv1a-48245145",
+ "lineNumberHint": 5,
+ "lastSyncedAt": "2026-08-25T08:21:59.551Z"
+ },
+ "monthlyRef": {
+ "path": "X1.Knomo/Memos-2026-07.md",
+ "dateHeading": "## [[2026-07-27]]",
+ "lastKnownBlock": "- 20:28:28 这就是 LaudableApps —— 一个专门收集“全球最美iOS应用”的线上艺术馆 #ios #ui-好设计 ",
+ "lastKnownHash": "fnv1a-48245145",
+ "lineNumberHint": 24,
+ "lastSyncedAt": "2026-08-25T08:22:00.085Z"
+ }
+ },
+ "2026072201491478": {
+ "id": "2026072201491478",
+ "createdAt": "2026-07-22T01:49:14.000+08:00",
+ "updatedAt": "2026-07-22T01:49:14.000+08:00",
+ "contentSnapshot": "视频中提到的AI工具hand-drawn-styles的GitHub地址是:https://github.com/threerocks/hand-drawn-styles",
+ "contentHash": "fnv1a-362e5d96",
+ "status": "active",
+ "syncStatus": "synced",
+ "source": "daily_scan",
+ "version": 1,
+ "tags": [],
+ "links": [
+ {
+ "target": "https://github.com/threerocks/hand-drawn-styles",
+ "displayText": null,
+ "syntax": "url"
+ }
+ ],
+ "images": [],
+ "references": [],
+ "sourceMemoId": null,
+ "issue": null,
+ "lastMarkdownSyncAt": "2026-08-25T08:22:02.013Z",
+ "lastMarkdownSyncSource": "file_watch",
+ "dailyRef": {
+ "path": "X0.Diary/2026-07-22.md",
+ "heading": "# MemoDaily",
+ "sectionType": "heading",
+ "lastKnownBlock": "- 01:49:14 视频中提到的AI工具hand-drawn-styles的GitHub地址是:https://github.com/threerocks/hand-drawn-styles",
+ "lastKnownHash": "fnv1a-ac17bf7e",
+ "lineNumberHint": 163,
+ "lastSyncedAt": "2026-08-25T08:22:02.013Z"
+ },
+ "monthlyRef": {
+ "path": "X1.Knomo/Memos-2026-07.md",
+ "dateHeading": "## [[2026-07-22]]",
+ "lastKnownBlock": "- 01:49:14 视频中提到的AI工具hand-drawn-styles的GitHub地址是:https://github.com/threerocks/hand-drawn-styles",
+ "lastKnownHash": "fnv1a-ac17bf7e",
+ "lineNumberHint": 21,
+ "lastSyncedAt": "2026-08-25T08:22:02.013Z"
+ }
}
}
}
diff --git a/X1.Knomo/_knomo-system/indexes/memo-index-2026-08.json b/X1.Knomo/_knomo-system/indexes/memo-index-2026-08.json
index dfb3b06..8e47ba0 100644
--- a/X1.Knomo/_knomo-system/indexes/memo-index-2026-08.json
+++ b/X1.Knomo/_knomo-system/indexes/memo-index-2026-08.json
@@ -1,7 +1,7 @@
{
"schemaVersion": 2,
"period": "2026-08",
- "updatedAt": "2026-08-25T06:46:25.137Z",
+ "updatedAt": "2026-08-25T08:26:23.116Z",
"memos": {
"2026080414154132": {
"id": "2026080414154132",
@@ -2275,6 +2275,2204 @@
"lineNumberHint": 103,
"lastSyncedAt": "2026-08-25T06:46:25.135Z"
}
+ },
+ "2026082002590804": {
+ "id": "2026082002590804",
+ "createdAt": "2026-08-20T02:59:08.000+08:00",
+ "updatedAt": "2026-08-20T02:59:08.000+08:00",
+ "contentSnapshot": "#悖论 朱熹反感甚至反对搞科举",
+ "contentHash": "fnv1a-142d78c6",
+ "status": "active",
+ "syncStatus": "synced",
+ "source": "daily_scan",
+ "version": 1,
+ "tags": [
+ "悖论"
+ ],
+ "links": [],
+ "images": [],
+ "references": [],
+ "sourceMemoId": null,
+ "issue": null,
+ "lastMarkdownSyncAt": "2026-08-25T08:21:54.692Z",
+ "lastMarkdownSyncSource": "file_watch",
+ "dailyRef": {
+ "path": "X0.Diary/2026-08-20.md",
+ "heading": "# MemoDaily",
+ "sectionType": "heading",
+ "lastKnownBlock": "- 02:59:08 #悖论 朱熹反感甚至反对搞科举",
+ "lastKnownHash": "fnv1a-393e9c39",
+ "lineNumberHint": 30,
+ "lastSyncedAt": "2026-08-25T08:21:54.692Z"
+ },
+ "monthlyRef": {
+ "path": "X1.Knomo/Memos-2026-08.md",
+ "dateHeading": "## [[2026-08-20]]",
+ "lastKnownBlock": "- 02:59:08 #悖论 朱熹反感甚至反对搞科举",
+ "lastKnownHash": "fnv1a-393e9c39",
+ "lineNumberHint": 73,
+ "lastSyncedAt": "2026-08-25T08:21:54.704Z"
+ }
+ },
+ "2026080414154179": {
+ "id": "2026080414154179",
+ "createdAt": "2026-08-04T14:15:41.000+08:00",
+ "updatedAt": "2026-08-04T14:15:41.000+08:00",
+ "contentSnapshot": "环吡酮胺,环吡酮胺,环吡酮胺,我用它来治疗 #灰指甲 ,已经快治好了,这个东西非常便宜,效果不错,是目前渗透力最强的单用药物。前一次治疗灰指甲,是用酸,醋酸和柠檬酸,已经完全治好。治疗灰指甲的诀窍是,用温水泡软,然后用玻璃纳米锉,把病甲尽量的磨掉,磨到感觉有点微疼为止,然后上药。上面这些,都不是重点,重点是,我用 环吡酮胺,把皮肤赘生物,瘊子,治好了。7月9号到货,今天22号,瘊子已经好了80%,只剩一点点了。方法就是把 环吡酮胺乳膏,涂在瘊子上,一天至少三次。意思就是,环吡酮胺,可以有效快速的治疗 癣,灰指甲,瘊子肉,疣,等等这些皮肤病。这个东西可以强力的破坏细胞壁,对细菌真菌有强烈的杀灭作用,而且渗透力强。 作者:liutaopx 链接:https://www.zhihu.com/question/2061451775761240887/answer/2063353188615558976 来源:知乎 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。",
+ "contentHash": "fnv1a-cba96796",
+ "status": "active",
+ "syncStatus": "synced",
+ "source": "daily_scan",
+ "version": 1,
+ "tags": [
+ "灰指甲"
+ ],
+ "links": [
+ {
+ "target": "https://www.zhihu.com/question/2061451775761240887/answer/2063353188615558976",
+ "displayText": null,
+ "syntax": "url"
+ }
+ ],
+ "images": [],
+ "references": [],
+ "sourceMemoId": null,
+ "issue": null,
+ "lastMarkdownSyncAt": "2026-08-25T08:21:56.480Z",
+ "lastMarkdownSyncSource": "file_watch",
+ "dailyRef": {
+ "path": "X0.Diary/2026-08-04.md",
+ "heading": "# MemoDaily",
+ "sectionType": "heading",
+ "lastKnownBlock": "- 14:15:41 环吡酮胺,环吡酮胺,环吡酮胺,我用它来治疗 #灰指甲 ,已经快治好了,这个东西非常便宜,效果不错,是目前渗透力最强的单用药物。前一次治疗灰指甲,是用酸,醋酸和柠檬酸,已经完全治好。治疗灰指甲的诀窍是,用温水泡软,然后用玻璃纳米锉,把病甲尽量的磨掉,磨到感觉有点微疼为止,然后上药。上面这些,都不是重点,重点是,我用 环吡酮胺,把皮肤赘生物,瘊子,治好了。7月9号到货,今天22号,瘊子已经好了80%,只剩一点点了。方法就是把 环吡酮胺乳膏,涂在瘊子上,一天至少三次。意思就是,环吡酮胺,可以有效快速的治疗 癣,灰指甲,瘊子肉,疣,等等这些皮肤病。这个东西可以强力的破坏细胞壁,对细菌真菌有强烈的杀灭作用,而且渗透力强。 作者:liutaopx 链接:https://www.zhihu.com/question/2061451775761240887/answer/2063353188615558976 来源:知乎 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。",
+ "lastKnownHash": "fnv1a-c4fa16a1",
+ "lineNumberHint": 66,
+ "lastSyncedAt": "2026-08-25T08:21:56.480Z"
+ },
+ "monthlyRef": {
+ "path": "X1.Knomo/Memos-2026-08.md",
+ "dateHeading": "## [[2026-08-04]]",
+ "lastKnownBlock": "- 14:15:41 环吡酮胺,环吡酮胺,环吡酮胺,我用它来治疗 #灰指甲 ,已经快治好了,这个东西非常便宜,效果不错,是目前渗透力最强的单用药物。前一次治疗灰指甲,是用酸,醋酸和柠檬酸,已经完全治好。治疗灰指甲的诀窍是,用温水泡软,然后用玻璃纳米锉,把病甲尽量的磨掉,磨到感觉有点微疼为止,然后上药。上面这些,都不是重点,重点是,我用 环吡酮胺,把皮肤赘生物,瘊子,治好了。7月9号到货,今天22号,瘊子已经好了80%,只剩一点点了。方法就是把 环吡酮胺乳膏,涂在瘊子上,一天至少三次。意思就是,环吡酮胺,可以有效快速的治疗 癣,灰指甲,瘊子肉,疣,等等这些皮肤病。这个东西可以强力的破坏细胞壁,对细菌真菌有强烈的杀灭作用,而且渗透力强。 作者:liutaopx 链接:https://www.zhihu.com/question/2061451775761240887/answer/2063353188615558976 来源:知乎 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。",
+ "lastKnownHash": "fnv1a-c4fa16a1",
+ "lineNumberHint": 15,
+ "lastSyncedAt": "2026-08-25T08:21:57.146Z"
+ }
+ },
+ "2026082417052782": {
+ "id": "2026082417052782",
+ "createdAt": "2026-08-24T17:05:27.000+08:00",
+ "updatedAt": "2026-08-24T17:05:27.000+08:00",
+ "contentSnapshot": "有很多重要的成对的概念在逻辑的最底层发生作用:如1.名(义)与实(际),如权力与义务。积极的和消极的(如两个自由:我能做什么的自由,和我能不做什么的自由)。 #底层逻辑 \n理论与现实, 理想与现实,谎言与真相,问题与答案(经常问题是 对的, 但答案是错的)。",
+ "contentHash": "fnv1a-0d3d0fc1",
+ "status": "active",
+ "syncStatus": "synced",
+ "source": "daily_scan",
+ "version": 1,
+ "tags": [
+ "底层逻辑"
+ ],
+ "links": [],
+ "images": [],
+ "references": [],
+ "sourceMemoId": null,
+ "issue": null,
+ "lastMarkdownSyncAt": "2026-08-25T08:22:01.276Z",
+ "lastMarkdownSyncSource": "file_watch",
+ "dailyRef": {
+ "path": "X0.Diary/2026-08-24.md",
+ "heading": "# MemoDaily",
+ "sectionType": "heading",
+ "lastKnownBlock": "- 17:05:27 有很多重要的成对的概念在逻辑的最底层发生作用:如1.名(义)与实(际),如权力与义务。积极的和消极的(如两个自由:我能做什么的自由,和我能不做什么的自由)。 #底层逻辑 \n\t理论与现实, 理想与现实,谎言与真相,问题与答案(经常问题是 对的, 但答案是错的)。",
+ "lastKnownHash": "fnv1a-84dad275",
+ "lineNumberHint": 94,
+ "lastSyncedAt": "2026-08-25T08:22:01.276Z"
+ },
+ "monthlyRef": {
+ "path": "X1.Knomo/Memos-2026-08.md",
+ "dateHeading": "## [[2026-08-24]]",
+ "lastKnownBlock": "- 17:05:27 有很多重要的成对的概念在逻辑的最底层发生作用:如1.名(义)与实(际),如权力与义务。积极的和消极的(如两个自由:我能做什么的自由,和我能不做什么的自由)。 #底层逻辑 \n\t理论与现实, 理想与现实,谎言与真相,问题与答案(经常问题是 对的, 但答案是错的)。",
+ "lastKnownHash": "fnv1a-84dad275",
+ "lineNumberHint": 89,
+ "lastSyncedAt": "2026-08-25T08:22:01.278Z"
+ }
+ },
+ "2026082417325387": {
+ "id": "2026082417325387",
+ "createdAt": "2026-08-24T17:32:53.000+08:00",
+ "updatedAt": "2026-08-24T17:32:53.000+08:00",
+ "contentSnapshot": "#话题 《可怕的法家》。其可怕在于,对老百姓是非常不友好的。比如其弱民、贫民、愚民的主张。我们当下中国的走向就是这个方向,所以有很大的现实意义。",
+ "contentHash": "fnv1a-a404ae64",
+ "status": "active",
+ "syncStatus": "synced",
+ "source": "daily_scan",
+ "version": 1,
+ "tags": [
+ "话题"
+ ],
+ "links": [],
+ "images": [],
+ "references": [],
+ "sourceMemoId": null,
+ "issue": null,
+ "lastMarkdownSyncAt": "2026-08-25T08:22:01.281Z",
+ "lastMarkdownSyncSource": "file_watch",
+ "dailyRef": {
+ "path": "X0.Diary/2026-08-24.md",
+ "heading": "# MemoDaily",
+ "sectionType": "heading",
+ "lastKnownBlock": "- 17:32:53 #话题 《可怕的法家》。其可怕在于,对老百姓是非常不友好的。比如其弱民、贫民、愚民的主张。我们当下中国的走向就是这个方向,所以有很大的现实意义。",
+ "lastKnownHash": "fnv1a-f68474c0",
+ "lineNumberHint": 96,
+ "lastSyncedAt": "2026-08-25T08:22:01.281Z"
+ },
+ "monthlyRef": {
+ "path": "X1.Knomo/Memos-2026-08.md",
+ "dateHeading": "## [[2026-08-24]]",
+ "lastKnownBlock": "- 17:32:53 #话题 《可怕的法家》。其可怕在于,对老百姓是非常不友好的。比如其弱民、贫民、愚民的主张。我们当下中国的走向就是这个方向,所以有很大的现实意义。",
+ "lastKnownHash": "fnv1a-f68474c0",
+ "lineNumberHint": 91,
+ "lastSyncedAt": "2026-08-25T08:22:01.281Z"
+ }
+ },
+ "2026082417402133": {
+ "id": "2026082417402133",
+ "createdAt": "2026-08-24T17:40:21.000+08:00",
+ "updatedAt": "2026-08-24T17:40:21.000+08:00",
+ "contentSnapshot": "#话题 社会主义--从社会理想到理想社会的一场漫长的天国想象。",
+ "contentHash": "fnv1a-e0e07b05",
+ "status": "active",
+ "syncStatus": "synced",
+ "source": "daily_scan",
+ "version": 1,
+ "tags": [
+ "话题"
+ ],
+ "links": [],
+ "images": [],
+ "references": [],
+ "sourceMemoId": null,
+ "issue": null,
+ "lastMarkdownSyncAt": "2026-08-25T08:22:01.284Z",
+ "lastMarkdownSyncSource": "file_watch",
+ "dailyRef": {
+ "path": "X0.Diary/2026-08-24.md",
+ "heading": "# MemoDaily",
+ "sectionType": "heading",
+ "lastKnownBlock": "- 17:40:21 #话题 社会主义--从社会理想到理想社会的一场漫长的天国想象。",
+ "lastKnownHash": "fnv1a-08a22337",
+ "lineNumberHint": 97,
+ "lastSyncedAt": "2026-08-25T08:22:01.284Z"
+ },
+ "monthlyRef": {
+ "path": "X1.Knomo/Memos-2026-08.md",
+ "dateHeading": "## [[2026-08-24]]",
+ "lastKnownBlock": "- 17:40:21 #话题 社会主义--从社会理想到理想社会的一场漫长的天国想象。",
+ "lastKnownHash": "fnv1a-08a22337",
+ "lineNumberHint": 92,
+ "lastSyncedAt": "2026-08-25T08:22:01.454Z"
+ }
+ },
+ "2026082418122326": {
+ "id": "2026082418122326",
+ "createdAt": "2026-08-24T18:12:23.000+08:00",
+ "updatedAt": "2026-08-24T18:12:23.000+08:00",
+ "contentSnapshot": "另外两个成对的概念是。问题与答案有意思的是?经常问题是对的,但答案是错的。 #成对的概念 ",
+ "contentHash": "fnv1a-cc4d2d3f",
+ "status": "active",
+ "syncStatus": "synced",
+ "source": "daily_scan",
+ "version": 1,
+ "tags": [
+ "成对的概念"
+ ],
+ "links": [],
+ "images": [],
+ "references": [],
+ "sourceMemoId": null,
+ "issue": null,
+ "lastMarkdownSyncAt": "2026-08-25T08:22:01.473Z",
+ "lastMarkdownSyncSource": "file_watch",
+ "dailyRef": {
+ "path": "X0.Diary/2026-08-24.md",
+ "heading": "# MemoDaily",
+ "sectionType": "heading",
+ "lastKnownBlock": "- 18:12:23 另外两个成对的概念是。问题与答案有意思的是?经常问题是对的,但答案是错的。 #成对的概念 ",
+ "lastKnownHash": "fnv1a-3137a7ab",
+ "lineNumberHint": 98,
+ "lastSyncedAt": "2026-08-25T08:22:01.473Z"
+ },
+ "monthlyRef": {
+ "path": "X1.Knomo/Memos-2026-08.md",
+ "dateHeading": "## [[2026-08-24]]",
+ "lastKnownBlock": "- 18:12:23 另外两个成对的概念是。问题与答案有意思的是?经常问题是对的,但答案是错的。 #成对的概念 ",
+ "lastKnownHash": "fnv1a-3137a7ab",
+ "lineNumberHint": 93,
+ "lastSyncedAt": "2026-08-25T08:22:01.474Z"
+ }
+ },
+ "2026082418122476": {
+ "id": "2026082418122476",
+ "createdAt": "2026-08-24T18:12:24.000+08:00",
+ "updatedAt": "2026-08-24T18:12:24.000+08:00",
+ "contentSnapshot": "理论与现实,理想与现实。谎言与真相。 #成对的概念 ",
+ "contentHash": "fnv1a-526f689e",
+ "status": "active",
+ "syncStatus": "synced",
+ "source": "daily_scan",
+ "version": 1,
+ "tags": [
+ "成对的概念"
+ ],
+ "links": [],
+ "images": [],
+ "references": [],
+ "sourceMemoId": null,
+ "issue": null,
+ "lastMarkdownSyncAt": "2026-08-25T08:22:01.476Z",
+ "lastMarkdownSyncSource": "file_watch",
+ "dailyRef": {
+ "path": "X0.Diary/2026-08-24.md",
+ "heading": "# MemoDaily",
+ "sectionType": "heading",
+ "lastKnownBlock": "- 18:12:24 理论与现实,理想与现实。谎言与真相。 #成对的概念 ",
+ "lastKnownHash": "fnv1a-48c5f621",
+ "lineNumberHint": 99,
+ "lastSyncedAt": "2026-08-25T08:22:01.476Z"
+ },
+ "monthlyRef": {
+ "path": "X1.Knomo/Memos-2026-08.md",
+ "dateHeading": "## [[2026-08-24]]",
+ "lastKnownBlock": "- 18:12:24 理论与现实,理想与现实。谎言与真相。 #成对的概念 ",
+ "lastKnownHash": "fnv1a-48c5f621",
+ "lineNumberHint": 94,
+ "lastSyncedAt": "2026-08-25T08:22:01.476Z"
+ }
+ },
+ "2026082418403400": {
+ "id": "2026082418403400",
+ "createdAt": "2026-08-24T18:40:34.000+08:00",
+ "updatedAt": "2026-08-24T18:40:34.000+08:00",
+ "contentSnapshot": "#基本概念 #excalidraw 插入的文本类型3+1种:图片、pdf、markdown,任意文档。插入的方式3种:链接、图象模式、交互模式。",
+ "contentHash": "fnv1a-a0b9ce6d",
+ "status": "active",
+ "syncStatus": "synced",
+ "source": "daily_scan",
+ "version": 1,
+ "tags": [
+ "基本概念",
+ "excalidraw"
+ ],
+ "links": [],
+ "images": [],
+ "references": [],
+ "sourceMemoId": null,
+ "issue": null,
+ "lastMarkdownSyncAt": "2026-08-25T08:22:01.478Z",
+ "lastMarkdownSyncSource": "file_watch",
+ "dailyRef": {
+ "path": "X0.Diary/2026-08-24.md",
+ "heading": "# MemoDaily",
+ "sectionType": "heading",
+ "lastKnownBlock": "- 18:40:34 #基本概念 #excalidraw 插入的文本类型3+1种:图片、pdf、markdown,任意文档。插入的方式3种:链接、图象模式、交互模式。",
+ "lastKnownHash": "fnv1a-c3f23cc6",
+ "lineNumberHint": 100,
+ "lastSyncedAt": "2026-08-25T08:22:01.478Z"
+ },
+ "monthlyRef": {
+ "path": "X1.Knomo/Memos-2026-08.md",
+ "dateHeading": "## [[2026-08-24]]",
+ "lastKnownBlock": "- 18:40:34 #基本概念 #excalidraw 插入的文本类型3+1种:图片、pdf、markdown,任意文档。插入的方式3种:链接、图象模式、交互模式。",
+ "lastKnownHash": "fnv1a-c3f23cc6",
+ "lineNumberHint": 95,
+ "lastSyncedAt": "2026-08-25T08:22:01.478Z"
+ }
+ },
+ "2026082421210548": {
+ "id": "2026082421210548",
+ "createdAt": "2026-08-24T21:21:05.000+08:00",
+ "updatedAt": "2026-08-24T21:21:05.000+08:00",
+ "contentSnapshot": "taglauncher下一步的一个发展方向。把软件的豆瓣(大家参与共建)和软件的hao123(或者类似奇迹秀、阿虚的储物间)--这样的事结合起来搞。 #ideatodo #taglauncher ",
+ "contentHash": "fnv1a-fd44f718",
+ "status": "active",
+ "syncStatus": "synced",
+ "source": "daily_scan",
+ "version": 1,
+ "tags": [
+ "ideatodo",
+ "taglauncher"
+ ],
+ "links": [],
+ "images": [],
+ "references": [],
+ "sourceMemoId": null,
+ "issue": null,
+ "lastMarkdownSyncAt": "2026-08-25T08:22:01.481Z",
+ "lastMarkdownSyncSource": "file_watch",
+ "dailyRef": {
+ "path": "X0.Diary/2026-08-24.md",
+ "heading": "# MemoDaily",
+ "sectionType": "heading",
+ "lastKnownBlock": "- 21:21:05 taglauncher下一步的一个发展方向。把软件的豆瓣(大家参与共建)和软件的hao123(或者类似奇迹秀、阿虚的储物间)--这样的事结合起来搞。 #ideatodo #taglauncher ",
+ "lastKnownHash": "fnv1a-d3a3cb32",
+ "lineNumberHint": 101,
+ "lastSyncedAt": "2026-08-25T08:22:01.481Z"
+ },
+ "monthlyRef": {
+ "path": "X1.Knomo/Memos-2026-08.md",
+ "dateHeading": "## [[2026-08-24]]",
+ "lastKnownBlock": "- 21:21:05 taglauncher下一步的一个发展方向。把软件的豆瓣(大家参与共建)和软件的hao123(或者类似奇迹秀、阿虚的储物间)--这样的事结合起来搞。 #ideatodo #taglauncher ",
+ "lastKnownHash": "fnv1a-d3a3cb32",
+ "lineNumberHint": 96,
+ "lastSyncedAt": "2026-08-25T08:22:01.481Z"
+ }
+ },
+ "2026082421301111": {
+ "id": "2026082421301111",
+ "createdAt": "2026-08-24T21:30:11.000+08:00",
+ "updatedAt": "2026-08-24T21:30:11.000+08:00",
+ "contentSnapshot": "成对的重要概念还有:在国家治理层面(对皇上而言),国家和地方,在国家层面,还有朝廷和内廷、前朝和后朝这些成对的因素在博弈。 #政治 #成对的概念\n",
+ "contentHash": "fnv1a-8514ccd6",
+ "status": "active",
+ "syncStatus": "synced",
+ "source": "daily_scan",
+ "version": 1,
+ "tags": [
+ "政治",
+ "成对的概念"
+ ],
+ "links": [],
+ "images": [],
+ "references": [],
+ "sourceMemoId": null,
+ "issue": null,
+ "lastMarkdownSyncAt": "2026-08-25T08:22:01.483Z",
+ "lastMarkdownSyncSource": "file_watch",
+ "dailyRef": {
+ "path": "X0.Diary/2026-08-24.md",
+ "heading": "# MemoDaily",
+ "sectionType": "heading",
+ "lastKnownBlock": "- 21:30:11 成对的重要概念还有:在国家治理层面(对皇上而言),国家和地方,在国家层面,还有朝廷和内廷、前朝和后朝这些成对的因素在博弈。 #政治 #成对的概念\n\t",
+ "lastKnownHash": "fnv1a-6c679856",
+ "lineNumberHint": 102,
+ "lastSyncedAt": "2026-08-25T08:22:01.483Z"
+ },
+ "monthlyRef": {
+ "path": "X1.Knomo/Memos-2026-08.md",
+ "dateHeading": "## [[2026-08-24]]",
+ "lastKnownBlock": "- 21:30:11 成对的重要概念还有:在国家治理层面(对皇上而言),国家和地方,在国家层面,还有朝廷和内廷、前朝和后朝这些成对的因素在博弈。 #政治 #成对的概念\n\t",
+ "lastKnownHash": "fnv1a-6c679856",
+ "lineNumberHint": 98,
+ "lastSyncedAt": "2026-08-25T08:22:01.484Z"
+ }
+ },
+ "2026081000094322": {
+ "id": "2026081000094322",
+ "createdAt": "2026-08-10T00:09:43.000+08:00",
+ "updatedAt": "2026-08-10T00:09:43.000+08:00",
+ "contentSnapshot": "关于浪妹APP智商提高,有记忆力的问题,因为它不是在电脑上用,所以,Hermes的记忆的那些经验,能否在手机端针对每个用户去复刻,这是一个问题,可能要动数据库结构,明天早上起来跟AI好好讨论一下",
+ "contentHash": "fnv1a-ecd928cd",
+ "status": "active",
+ "syncStatus": "synced",
+ "source": "daily_scan",
+ "version": 1,
+ "tags": [],
+ "links": [],
+ "images": [],
+ "references": [],
+ "sourceMemoId": null,
+ "issue": null,
+ "lastMarkdownSyncAt": "2026-08-25T08:22:01.658Z",
+ "lastMarkdownSyncSource": "file_watch",
+ "dailyRef": {
+ "path": "X0.Diary/2026-08-10.md",
+ "heading": "# MemoDaily",
+ "sectionType": "heading",
+ "lastKnownBlock": "- 00:09:43 关于浪妹APP智商提高,有记忆力的问题,因为它不是在电脑上用,所以,Hermes的记忆的那些经验,能否在手机端针对每个用户去复刻,这是一个问题,可能要动数据库结构,明天早上起来跟AI好好讨论一下",
+ "lastKnownHash": "fnv1a-348289ba",
+ "lineNumberHint": 50,
+ "lastSyncedAt": "2026-08-25T08:22:01.658Z"
+ },
+ "monthlyRef": {
+ "path": "X1.Knomo/Memos-2026-08.md",
+ "dateHeading": "## [[2026-08-10]]",
+ "lastKnownBlock": "- 00:09:43 关于浪妹APP智商提高,有记忆力的问题,因为它不是在电脑上用,所以,Hermes的记忆的那些经验,能否在手机端针对每个用户去复刻,这是一个问题,可能要动数据库结构,明天早上起来跟AI好好讨论一下",
+ "lastKnownHash": "fnv1a-348289ba",
+ "lineNumberHint": 26,
+ "lastSyncedAt": "2026-08-25T08:22:01.658Z"
+ }
+ },
+ "2026082510582151": {
+ "id": "2026082510582151",
+ "createdAt": "2026-08-25T10:58:21.000+08:00",
+ "updatedAt": "2026-08-25T08:26:23.115Z",
+ "contentSnapshot": "#法家 法家思想(韩非子),其核心是强调中央集权、严格的法律约束和对个体行为的精细化管理",
+ "contentHash": "fnv1a-1918da11",
+ "status": "active",
+ "syncStatus": "monthly_failed",
+ "source": "daily_scan",
+ "version": 1,
+ "tags": [
+ "法家"
+ ],
+ "links": [],
+ "images": [],
+ "references": [],
+ "sourceMemoId": null,
+ "issue": {
+ "type": "monthly_block_ambiguous",
+ "code": "monthly_archive_block_ambiguous",
+ "detectedAt": "2026-08-25T08:26:23.115Z",
+ "message": "Multiple memo blocks may match under the monthly archive date heading, so Knomo cannot update the archive automatically."
+ },
+ "lastMarkdownSyncAt": "2026-08-25T08:26:23.115Z",
+ "lastMarkdownSyncSource": "file_watch",
+ "dailyRef": {
+ "path": "X0.Diary/2026-08-25.md",
+ "heading": "# MemoDaily",
+ "sectionType": "heading",
+ "lastKnownBlock": "- 10:58:21 #法家 法家思想(韩非子),其核心是强调中央集权、严格的法律约束和对个体行为的精细化管理",
+ "lastKnownHash": "fnv1a-57605bbd",
+ "lineNumberHint": 105,
+ "lastSyncedAt": "2026-08-25T08:26:23.114Z"
+ },
+ "monthlyRef": {
+ "path": "X1.Knomo/Memos-2026-08.md",
+ "dateHeading": "## [[2026-08-25]]",
+ "lastKnownBlock": "- 10:58:21 #法家 法家思想(韩非子),其核心是强调中央集权、严格的法律约束和对个体行为的精细化管理",
+ "lastKnownHash": "fnv1a-57605bbd",
+ "lineNumberHint": null,
+ "lastSyncedAt": null
+ }
+ },
+ "2026082119215390": {
+ "id": "2026082119215390",
+ "createdAt": "2026-08-21T19:21:53.000+08:00",
+ "updatedAt": "2026-08-21T19:21:53.000+08:00",
+ "contentSnapshot": "在AI时代,读书方式应该有变化,效率提高。我把要读的几本书扔进去,我问问题,他回答,帮我整理脉络。当我想了解某个细节时,再定位到书的原始章节,再仔细读一遍,我觉得这种学习方式会比较高效。要借助work. Buddy这一类AI工具,把学习、整理直至输出文章的流程搭起来。再把自动发表文章的流程搭起来,先整文章,后续再去整视频。",
+ "contentHash": "fnv1a-55157083",
+ "status": "active",
+ "syncStatus": "synced",
+ "source": "daily_scan",
+ "version": 1,
+ "tags": [],
+ "links": [],
+ "images": [],
+ "references": [],
+ "sourceMemoId": null,
+ "issue": null,
+ "lastMarkdownSyncAt": "2026-08-25T08:22:02.184Z",
+ "lastMarkdownSyncSource": "file_watch",
+ "dailyRef": {
+ "path": "X0.Diary/2026-08-21.md",
+ "heading": "# MemoDaily",
+ "sectionType": "heading",
+ "lastKnownBlock": "- 19:21:53 在AI时代,读书方式应该有变化,效率提高。我把要读的几本书扔进去,我问问题,他回答,帮我整理脉络。当我想了解某个细节时,再定位到书的原始章节,再仔细读一遍,我觉得这种学习方式会比较高效。要借助work. Buddy这一类AI工具,把学习、整理直至输出文章的流程搭起来。再把自动发表文章的流程搭起来,先整文章,后续再去整视频。",
+ "lastKnownHash": "fnv1a-13ea2e2d",
+ "lineNumberHint": 32,
+ "lastSyncedAt": "2026-08-25T08:22:02.184Z"
+ },
+ "monthlyRef": {
+ "path": "X1.Knomo/Memos-2026-08.md",
+ "dateHeading": "## [[2026-08-21]]",
+ "lastKnownBlock": "- 19:21:53 在AI时代,读书方式应该有变化,效率提高。我把要读的几本书扔进去,我问问题,他回答,帮我整理脉络。当我想了解某个细节时,再定位到书的原始章节,再仔细读一遍,我觉得这种学习方式会比较高效。要借助work. Buddy这一类AI工具,把学习、整理直至输出文章的流程搭起来。再把自动发表文章的流程搭起来,先整文章,后续再去整视频。",
+ "lastKnownHash": "fnv1a-13ea2e2d",
+ "lineNumberHint": 76,
+ "lastSyncedAt": "2026-08-25T08:22:02.185Z"
+ }
+ },
+ "2026081214563139": {
+ "id": "2026081214563139",
+ "createdAt": "2026-08-12T14:56:31.000+08:00",
+ "updatedAt": "2026-08-12T14:56:31.000+08:00",
+ "contentSnapshot": "[ #开源 项目] #UI (https://mp.weixin.qq.com/s/SUkqkxhoUPJ8OW_Gt7W3oA),提升UI-506个Prompt图案库让AI画图变稳定最近刷到一个开源项目,看完直接 star 了。 苍何,熟悉AI圈的朋友不陌生——前阿里高级开发,Microsoft MVP。他做了个叫 ==awesome-gpt-image-2== 的项目,解决一个问题:GPT-Image-2 画图时, #Prompt 怎么写才能又稳又准? 思路很有意思。不是教你写散文式 Prompt,而是==把 Prompt 变成代码==——拆成<font color=#ff0000>主体、光照、材质、布局</font>这些零件,每个都有明确参数。像搭乐高,选模板、调参数、出图,不再每次从零写。 506 个案例,覆盖 12 个品类。UI、信息图、海报、电商、品牌、建筑、摄影、插画……常见场景都有模板。 最实用的是 Agent Skill。装到 Claude Code 或 Codex 里,说一句需求,Agent 自动调风格库生成。风格一致,不再第一张像海报,第二张像截图。 GitHub 搜 freestylefly/awesome-gpt-image-2,MIT 开源。画廊:gpt-image2.canghe.ai",
+ "contentHash": "fnv1a-e2ce1c98",
+ "status": "active",
+ "syncStatus": "synced",
+ "source": "daily_scan",
+ "version": 1,
+ "tags": [
+ "开源",
+ "UI",
+ "Prompt"
+ ],
+ "links": [
+ {
+ "target": "https://mp.weixin.qq.com/s/SUkqkxhoUPJ8OW_Gt7W3oA",
+ "displayText": null,
+ "syntax": "url"
+ }
+ ],
+ "images": [],
+ "references": [],
+ "sourceMemoId": null,
+ "issue": null,
+ "lastMarkdownSyncAt": "2026-08-25T08:22:02.363Z",
+ "lastMarkdownSyncSource": "file_watch",
+ "dailyRef": {
+ "path": "X0.Diary/2026-08-12.md",
+ "heading": "# MemoDaily",
+ "sectionType": "heading",
+ "lastKnownBlock": "- 14:56:31 [ #开源 项目] #UI (https://mp.weixin.qq.com/s/SUkqkxhoUPJ8OW_Gt7W3oA),提升UI-506个Prompt图案库让AI画图变稳定最近刷到一个开源项目,看完直接 star 了。 苍何,熟悉AI圈的朋友不陌生——前阿里高级开发,Microsoft MVP。他做了个叫 ==awesome-gpt-image-2== 的项目,解决一个问题:GPT-Image-2 画图时, #Prompt 怎么写才能又稳又准? 思路很有意思。不是教你写散文式 Prompt,而是==把 Prompt 变成代码==——拆成<font color=#ff0000>主体、光照、材质、布局</font>这些零件,每个都有明确参数。像搭乐高,选模板、调参数、出图,不再每次从零写。 506 个案例,覆盖 12 个品类。UI、信息图、海报、电商、品牌、建筑、摄影、插画……常见场景都有模板。 最实用的是 Agent Skill。装到 Claude Code 或 Codex 里,说一句需求,Agent 自动调风格库生成。风格一致,不再第一张像海报,第二张像截图。 GitHub 搜 freestylefly/awesome-gpt-image-2,MIT 开源。画廊:gpt-image2.canghe.ai",
+ "lastKnownHash": "fnv1a-fabbaa35",
+ "lineNumberHint": 74,
+ "lastSyncedAt": "2026-08-25T08:22:02.363Z"
+ },
+ "monthlyRef": {
+ "path": "X1.Knomo/Memos-2026-08.md",
+ "dateHeading": "## [[2026-08-12]]",
+ "lastKnownBlock": "- 14:56:31 [ #开源 项目] #UI (https://mp.weixin.qq.com/s/SUkqkxhoUPJ8OW_Gt7W3oA),提升UI-506个Prompt图案库让AI画图变稳定最近刷到一个开源项目,看完直接 star 了。 苍何,熟悉AI圈的朋友不陌生——前阿里高级开发,Microsoft MVP。他做了个叫 ==awesome-gpt-image-2== 的项目,解决一个问题:GPT-Image-2 画图时, #Prompt 怎么写才能又稳又准? 思路很有意思。不是教你写散文式 Prompt,而是==把 Prompt 变成代码==——拆成<font color=#ff0000>主体、光照、材质、布局</font>这些零件,每个都有明确参数。像搭乐高,选模板、调参数、出图,不再每次从零写。 506 个案例,覆盖 12 个品类。UI、信息图、海报、电商、品牌、建筑、摄影、插画……常见场景都有模板。 最实用的是 Agent Skill。装到 Claude Code 或 Codex 里,说一句需求,Agent 自动调风格库生成。风格一致,不再第一张像海报,第二张像截图。 GitHub 搜 freestylefly/awesome-gpt-image-2,MIT 开源。画廊:gpt-image2.canghe.ai",
+ "lastKnownHash": "fnv1a-fabbaa35",
+ "lineNumberHint": 29,
+ "lastSyncedAt": "2026-08-25T08:22:02.363Z"
+ }
+ },
+ "2026082217595327": {
+ "id": "2026082217595327",
+ "createdAt": "2026-08-22T17:59:53.000+08:00",
+ "updatedAt": "2026-08-22T17:59:53.000+08:00",
+ "contentSnapshot": "#做饭 肉去腥味的关键猪肉-八角 牛肉-胡椒 羊肉-草寇 鸡肉-花椒",
+ "contentHash": "fnv1a-ba0ba7b9",
+ "status": "active",
+ "syncStatus": "synced",
+ "source": "daily_scan",
+ "version": 1,
+ "tags": [
+ "做饭"
+ ],
+ "links": [],
+ "images": [],
+ "references": [],
+ "sourceMemoId": null,
+ "issue": null,
+ "lastMarkdownSyncAt": "2026-08-25T08:22:02.540Z",
+ "lastMarkdownSyncSource": "file_watch",
+ "dailyRef": {
+ "path": "X0.Diary/2026-08-22.md",
+ "heading": "# MemoDaily",
+ "sectionType": "heading",
+ "lastKnownBlock": "- 17:59:53 #做饭 肉去腥味的关键猪肉-八角 牛肉-胡椒 羊肉-草寇 鸡肉-花椒",
+ "lastKnownHash": "fnv1a-0f56cbfe",
+ "lineNumberHint": 73,
+ "lastSyncedAt": "2026-08-25T08:22:02.540Z"
+ },
+ "monthlyRef": {
+ "path": "X1.Knomo/Memos-2026-08.md",
+ "dateHeading": "## [[2026-08-22]]",
+ "lastKnownBlock": "- 17:59:53 #做饭 肉去腥味的关键猪肉-八角 牛肉-胡椒 羊肉-草寇 鸡肉-花椒",
+ "lastKnownHash": "fnv1a-0f56cbfe",
+ "lineNumberHint": 79,
+ "lastSyncedAt": "2026-08-25T08:22:02.541Z"
+ }
+ },
+ "2026082316193495": {
+ "id": "2026082316193495",
+ "createdAt": "2026-08-23T16:19:34.000+08:00",
+ "updatedAt": "2026-08-23T16:19:34.000+08:00",
+ "contentSnapshot": "刚才和谢大宝坐着。她说你看咱两现在住一起,可以一起聊天做播客,然后话题往下聊,先是就选题(聊什么)聊了会,最后话题聊到很多人做,但并不怎么样甚至做不下去的话题,我意识到“其实是无法变现赚不到钱从而没有(坚持做下去的)动力”\n#聊天 #可行性- 20:28:41 #写作点子 童话,人看着是人,其实只剩个壳了,其实不是人。",
+ "contentHash": "fnv1a-f95e8132",
+ "status": "active",
+ "syncStatus": "synced",
+ "source": "daily_scan",
+ "version": 1,
+ "tags": [
+ "聊天",
+ "可行性-",
+ "写作点子"
+ ],
+ "links": [],
+ "images": [],
+ "references": [],
+ "sourceMemoId": null,
+ "issue": null,
+ "lastMarkdownSyncAt": "2026-08-25T08:22:02.718Z",
+ "lastMarkdownSyncSource": "file_watch",
+ "dailyRef": {
+ "path": "X0.Diary/2026-08-23.md",
+ "heading": "# MemoDaily",
+ "sectionType": "heading",
+ "lastKnownBlock": "- 16:19:34 刚才和谢大宝坐着。她说你看咱两现在住一起,可以一起聊天做播客,然后话题往下聊,先是就选题(聊什么)聊了会,最后话题聊到很多人做,但并不怎么样甚至做不下去的话题,我意识到“其实是无法变现赚不到钱从而没有(坚持做下去的)动力”\n\t#聊天 #可行性- 20:28:41 #写作点子 童话,人看着是人,其实只剩个壳了,其实不是人。",
+ "lastKnownHash": "fnv1a-be059ffc",
+ "lineNumberHint": 180,
+ "lastSyncedAt": "2026-08-25T08:22:02.718Z"
+ },
+ "monthlyRef": {
+ "path": "X1.Knomo/Memos-2026-08.md",
+ "dateHeading": "## [[2026-08-23]]",
+ "lastKnownBlock": "- 16:19:34 刚才和谢大宝坐着。她说你看咱两现在住一起,可以一起聊天做播客,然后话题往下聊,先是就选题(聊什么)聊了会,最后话题聊到很多人做,但并不怎么样甚至做不下去的话题,我意识到“其实是无法变现赚不到钱从而没有(坚持做下去的)动力”\n\t#聊天 #可行性- 20:28:41 #写作点子 童话,人看着是人,其实只剩个壳了,其实不是人。",
+ "lastKnownHash": "fnv1a-be059ffc",
+ "lineNumberHint": 82,
+ "lastSyncedAt": "2026-08-25T08:22:02.718Z"
+ }
+ },
+ "2026082320324543": {
+ "id": "2026082320324543",
+ "createdAt": "2026-08-23T20:32:45.000+08:00",
+ "updatedAt": "2026-08-23T20:32:45.000+08:00",
+ "contentSnapshot": " #ai工具 #知识库 github项目,nashsu,能够自动把本地文档变成wiki,而且有自动编译,自动生长的特性。 #wiki #他山之石",
+ "contentHash": "fnv1a-38c4ca52",
+ "status": "active",
+ "syncStatus": "synced",
+ "source": "daily_scan",
+ "version": 1,
+ "tags": [
+ "ai工具",
+ "知识库",
+ "wiki",
+ "他山之石"
+ ],
+ "links": [],
+ "images": [],
+ "references": [],
+ "sourceMemoId": null,
+ "issue": null,
+ "lastMarkdownSyncAt": "2026-08-25T08:22:02.721Z",
+ "lastMarkdownSyncSource": "file_watch",
+ "dailyRef": {
+ "path": "X0.Diary/2026-08-23.md",
+ "heading": "# MemoDaily",
+ "sectionType": "heading",
+ "lastKnownBlock": "- 20:32:45 #ai工具 #知识库 github项目,nashsu,能够自动把本地文档变成wiki,而且有自动编译,自动生长的特性。 #wiki #他山之石",
+ "lastKnownHash": "fnv1a-1c0d95b5",
+ "lineNumberHint": 182,
+ "lastSyncedAt": "2026-08-25T08:22:02.721Z"
+ },
+ "monthlyRef": {
+ "path": "X1.Knomo/Memos-2026-08.md",
+ "dateHeading": "## [[2026-08-23]]",
+ "lastKnownBlock": "- 20:32:45 #ai工具 #知识库 github项目,nashsu,能够自动把本地文档变成wiki,而且有自动编译,自动生长的特性。 #wiki #他山之石",
+ "lastKnownHash": "fnv1a-1c0d95b5",
+ "lineNumberHint": 84,
+ "lastSyncedAt": "2026-08-25T08:22:02.722Z"
+ }
+ },
+ "2026082320340202": {
+ "id": "2026082320340202",
+ "createdAt": "2026-08-23T20:34:02.000+08:00",
+ "updatedAt": "2026-08-23T20:34:02.000+08:00",
+ "contentSnapshot": "Kaneo 是一款开源项目管理工具,核心理念只有一句话:All you need. Nothing you don't. 它不追求功能堆砌,而是让每个功能都真正解决问题,让团队专注于产品构建本身 #开源 #项目管理 #软件研究",
+ "contentHash": "fnv1a-02873924",
+ "status": "active",
+ "syncStatus": "synced",
+ "source": "daily_scan",
+ "version": 1,
+ "tags": [
+ "开源",
+ "项目管理",
+ "软件研究"
+ ],
+ "links": [],
+ "images": [],
+ "references": [],
+ "sourceMemoId": null,
+ "issue": null,
+ "lastMarkdownSyncAt": "2026-08-25T08:22:02.724Z",
+ "lastMarkdownSyncSource": "file_watch",
+ "dailyRef": {
+ "path": "X0.Diary/2026-08-23.md",
+ "heading": "# MemoDaily",
+ "sectionType": "heading",
+ "lastKnownBlock": "- 20:34:02 Kaneo 是一款开源项目管理工具,核心理念只有一句话:All you need. Nothing you don't. 它不追求功能堆砌,而是让每个功能都真正解决问题,让团队专注于产品构建本身 #开源 #项目管理 #软件研究",
+ "lastKnownHash": "fnv1a-7fd3e36a",
+ "lineNumberHint": 183,
+ "lastSyncedAt": "2026-08-25T08:22:02.724Z"
+ },
+ "monthlyRef": {
+ "path": "X1.Knomo/Memos-2026-08.md",
+ "dateHeading": "## [[2026-08-23]]",
+ "lastKnownBlock": "- 20:34:02 Kaneo 是一款开源项目管理工具,核心理念只有一句话:All you need. Nothing you don't. 它不追求功能堆砌,而是让每个功能都真正解决问题,让团队专注于产品构建本身 #开源 #项目管理 #软件研究",
+ "lastKnownHash": "fnv1a-7fd3e36a",
+ "lineNumberHint": 85,
+ "lastSyncedAt": "2026-08-25T08:22:02.724Z"
+ }
+ },
+ "2026082321411304": {
+ "id": "2026082321411304",
+ "createdAt": "2026-08-23T21:41:13.000+08:00",
+ "updatedAt": "2026-08-23T21:41:13.000+08:00",
+ "contentSnapshot": "#软件研究 #GTD Calflow可以一试,看起来不错,主要是日历视图看起来很不错",
+ "contentHash": "fnv1a-59847b5e",
+ "status": "active",
+ "syncStatus": "synced",
+ "source": "daily_scan",
+ "version": 1,
+ "tags": [
+ "软件研究",
+ "GTD"
+ ],
+ "links": [],
+ "images": [],
+ "references": [],
+ "sourceMemoId": null,
+ "issue": null,
+ "lastMarkdownSyncAt": "2026-08-25T08:22:02.726Z",
+ "lastMarkdownSyncSource": "file_watch",
+ "dailyRef": {
+ "path": "X0.Diary/2026-08-23.md",
+ "heading": "# MemoDaily",
+ "sectionType": "heading",
+ "lastKnownBlock": "- 21:41:13 #软件研究 #GTD Calflow可以一试,看起来不错,主要是日历视图看起来很不错",
+ "lastKnownHash": "fnv1a-710f82eb",
+ "lineNumberHint": 184,
+ "lastSyncedAt": "2026-08-25T08:22:02.726Z"
+ },
+ "monthlyRef": {
+ "path": "X1.Knomo/Memos-2026-08.md",
+ "dateHeading": "## [[2026-08-23]]",
+ "lastKnownBlock": "- 21:41:13 #软件研究 #GTD Calflow可以一试,看起来不错,主要是日历视图看起来很不错",
+ "lastKnownHash": "fnv1a-710f82eb",
+ "lineNumberHint": 86,
+ "lastSyncedAt": "2026-08-25T08:22:02.727Z"
+ }
+ },
+ "2026080713281720": {
+ "id": "2026080713281720",
+ "createdAt": "2026-08-07T13:28:17.000+08:00",
+ "updatedAt": "2026-08-07T13:28:17.000+08:00",
+ "contentSnapshot": " 《年会2》直接说了个大实话—— #### 任何人类组织的运转基本原理,就是高层吸血底层来奶中层,让中层鞭策底层为高层效力办事。 最终会不会崩盘,考验的是高层的操盘能力、中层的执行能力和底层的忍耐能力。 ",
+ "contentHash": "fnv1a-77023847",
+ "status": "active",
+ "syncStatus": "synced",
+ "source": "daily_scan",
+ "version": 1,
+ "tags": [],
+ "links": [],
+ "images": [],
+ "references": [],
+ "sourceMemoId": null,
+ "issue": null,
+ "lastMarkdownSyncAt": "2026-08-25T08:22:02.905Z",
+ "lastMarkdownSyncSource": "file_watch",
+ "dailyRef": {
+ "path": "X0.Diary/2026-08-07.md",
+ "heading": "# MemoDaily",
+ "sectionType": "heading",
+ "lastKnownBlock": "- 13:28:17 《年会2》直接说了个大实话—— #### 任何人类组织的运转基本原理,就是高层吸血底层来奶中层,让中层鞭策底层为高层效力办事。 最终会不会崩盘,考验的是高层的操盘能力、中层的执行能力和底层的忍耐能力。 ",
+ "lastKnownHash": "fnv1a-f2f5bdb8",
+ "lineNumberHint": 22,
+ "lastSyncedAt": "2026-08-25T08:22:02.905Z"
+ },
+ "monthlyRef": {
+ "path": "X1.Knomo/Memos-2026-08.md",
+ "dateHeading": "## [[2026-08-07]]",
+ "lastKnownBlock": "- 13:28:17 《年会2》直接说了个大实话—— #### 任何人类组织的运转基本原理,就是高层吸血底层来奶中层,让中层鞭策底层为高层效力办事。 最终会不会崩盘,考验的是高层的操盘能力、中层的执行能力和底层的忍耐能力。 ",
+ "lastKnownHash": "fnv1a-f2f5bdb8",
+ "lineNumberHint": 18,
+ "lastSyncedAt": "2026-08-25T08:22:02.906Z"
+ }
+ },
+ "2026080714594591": {
+ "id": "2026080714594591",
+ "createdAt": "2026-08-07T14:59:45.000+08:00",
+ "updatedAt": "2026-08-07T14:59:45.000+08:00",
+ "contentSnapshot": "#软件 #投屏 将 iPhone、iPad、Mac 投屏至安卓盒子上github.com/jqssun/android-airplay-server第一个面向 Android 的开源 AirPlay 接收器实现,支持镜像、视频和音频,基于UxPlay。它能将你的安卓设备变成兼容 AirPlay 的显示器和扬声器。,然后使用 iPhone、iPad、Mac 上自带的投屏功能,就能将视频、音频,或者设备画面直接传送过去。AirPlay 在苹果系设备间非常非常稳,就是不知道这款的稳定性如何。",
+ "contentHash": "fnv1a-beea5ae5",
+ "status": "active",
+ "syncStatus": "synced",
+ "source": "daily_scan",
+ "version": 1,
+ "tags": [
+ "软件",
+ "投屏"
+ ],
+ "links": [],
+ "images": [],
+ "references": [],
+ "sourceMemoId": null,
+ "issue": null,
+ "lastMarkdownSyncAt": "2026-08-25T08:22:02.909Z",
+ "lastMarkdownSyncSource": "file_watch",
+ "dailyRef": {
+ "path": "X0.Diary/2026-08-07.md",
+ "heading": "# MemoDaily",
+ "sectionType": "heading",
+ "lastKnownBlock": "- 14:59:45 #软件 #投屏 将 iPhone、iPad、Mac 投屏至安卓盒子上github.com/jqssun/android-airplay-server第一个面向 Android 的开源 AirPlay 接收器实现,支持镜像、视频和音频,基于UxPlay。它能将你的安卓设备变成兼容 AirPlay 的显示器和扬声器。,然后使用 iPhone、iPad、Mac 上自带的投屏功能,就能将视频、音频,或者设备画面直接传送过去。AirPlay 在苹果系设备间非常非常稳,就是不知道这款的稳定性如何。",
+ "lastKnownHash": "fnv1a-aaad638a",
+ "lineNumberHint": 24,
+ "lastSyncedAt": "2026-08-25T08:22:02.909Z"
+ },
+ "monthlyRef": {
+ "path": "X1.Knomo/Memos-2026-08.md",
+ "dateHeading": "## [[2026-08-07]]",
+ "lastKnownBlock": "- 14:59:45 #软件 #投屏 将 iPhone、iPad、Mac 投屏至安卓盒子上github.com/jqssun/android-airplay-server第一个面向 Android 的开源 AirPlay 接收器实现,支持镜像、视频和音频,基于UxPlay。它能将你的安卓设备变成兼容 AirPlay 的显示器和扬声器。,然后使用 iPhone、iPad、Mac 上自带的投屏功能,就能将视频、音频,或者设备画面直接传送过去。AirPlay 在苹果系设备间非常非常稳,就是不知道这款的稳定性如何。",
+ "lastKnownHash": "fnv1a-aaad638a",
+ "lineNumberHint": 20,
+ "lastSyncedAt": "2026-08-25T08:22:02.909Z"
+ }
+ },
+ "2026081321181981": {
+ "id": "2026081321181981",
+ "createdAt": "2026-08-13T21:18:19.000+08:00",
+ "updatedAt": "2026-08-13T21:18:19.000+08:00",
+ "contentSnapshot": "需要对mindraw 做一个 #容错 性设计,在文件夹里面直接对excalidraw改名之后从外面就打不开了。现在有这个问题 #需求",
+ "contentHash": "fnv1a-e1a284e9",
+ "status": "active",
+ "syncStatus": "synced",
+ "source": "daily_scan",
+ "version": 1,
+ "tags": [
+ "容错",
+ "需求"
+ ],
+ "links": [],
+ "images": [],
+ "references": [],
+ "sourceMemoId": null,
+ "issue": null,
+ "lastMarkdownSyncAt": "2026-08-25T08:22:03.085Z",
+ "lastMarkdownSyncSource": "file_watch",
+ "dailyRef": {
+ "path": "X0.Diary/2026-08-13.md",
+ "heading": "# MemoDaily",
+ "sectionType": "heading",
+ "lastKnownBlock": "- 21:18:19 需要对mindraw 做一个 #容错 性设计,在文件夹里面直接对excalidraw改名之后从外面就打不开了。现在有这个问题 #需求",
+ "lastKnownHash": "fnv1a-2a296ef4",
+ "lineNumberHint": 50,
+ "lastSyncedAt": "2026-08-25T08:22:03.085Z"
+ },
+ "monthlyRef": {
+ "path": "X1.Knomo/Memos-2026-08.md",
+ "dateHeading": "## [[2026-08-13]]",
+ "lastKnownBlock": "- 21:18:19 需要对mindraw 做一个 #容错 性设计,在文件夹里面直接对excalidraw改名之后从外面就打不开了。现在有这个问题 #需求",
+ "lastKnownHash": "fnv1a-2a296ef4",
+ "lineNumberHint": 32,
+ "lastSyncedAt": "2026-08-25T08:22:03.086Z"
+ }
+ },
+ "2026080815240508": {
+ "id": "2026080815240508",
+ "createdAt": "2026-08-08T15:24:05.000+08:00",
+ "updatedAt": "2026-08-08T15:24:05.000+08:00",
+ "contentSnapshot": "我要把让我痛苦的就感觉迟滞,做不下去的事情都今天都列出来,然后从中消灭几个让我难受的点",
+ "contentHash": "fnv1a-7be95ce0",
+ "status": "active",
+ "syncStatus": "synced",
+ "source": "daily_scan",
+ "version": 1,
+ "tags": [],
+ "links": [],
+ "images": [],
+ "references": [],
+ "sourceMemoId": null,
+ "issue": null,
+ "lastMarkdownSyncAt": "2026-08-25T08:22:03.266Z",
+ "lastMarkdownSyncSource": "file_watch",
+ "dailyRef": {
+ "path": "X0.Diary/2026-08-08.md",
+ "heading": "# MemoDaily",
+ "sectionType": "heading",
+ "lastKnownBlock": "- 15:24:05 我要把让我痛苦的就感觉迟滞,做不下去的事情都今天都列出来,然后从中消灭几个让我难受的点",
+ "lastKnownHash": "fnv1a-16407bc4",
+ "lineNumberHint": 89,
+ "lastSyncedAt": "2026-08-25T08:22:03.266Z"
+ },
+ "monthlyRef": {
+ "path": "X1.Knomo/Memos-2026-08.md",
+ "dateHeading": "## [[2026-08-08]]",
+ "lastKnownBlock": "- 15:24:05 我要把让我痛苦的就感觉迟滞,做不下去的事情都今天都列出来,然后从中消灭几个让我难受的点",
+ "lastKnownHash": "fnv1a-16407bc4",
+ "lineNumberHint": 23,
+ "lastSyncedAt": "2026-08-25T08:22:03.266Z"
+ }
+ },
+ "2026081913593121": {
+ "id": "2026081913593121",
+ "createdAt": "2026-08-19T13:59:31.000+08:00",
+ "updatedAt": "2026-08-19T13:59:31.000+08:00",
+ "contentSnapshot": "做公众账号特别好的一个主题是穿越迷雾或飞越迷雾",
+ "contentHash": "fnv1a-c94fea4f",
+ "status": "active",
+ "syncStatus": "synced",
+ "source": "daily_scan",
+ "version": 1,
+ "tags": [],
+ "links": [],
+ "images": [],
+ "references": [],
+ "sourceMemoId": null,
+ "issue": null,
+ "lastMarkdownSyncAt": "2026-08-25T08:22:03.444Z",
+ "lastMarkdownSyncSource": "file_watch",
+ "dailyRef": {
+ "path": "X0.Diary/2026-08-19.md",
+ "heading": "# MemoDaily",
+ "sectionType": "heading",
+ "lastKnownBlock": "- 13:59:31 做公众账号特别好的一个主题是穿越迷雾或飞越迷雾",
+ "lastKnownHash": "fnv1a-4809ae46",
+ "lineNumberHint": 42,
+ "lastSyncedAt": "2026-08-25T08:22:03.444Z"
+ },
+ "monthlyRef": {
+ "path": "X1.Knomo/Memos-2026-08.md",
+ "dateHeading": "## [[2026-08-19]]",
+ "lastKnownBlock": "- 13:59:31 做公众账号特别好的一个主题是穿越迷雾或飞越迷雾",
+ "lastKnownHash": "fnv1a-4809ae46",
+ "lineNumberHint": 35,
+ "lastSyncedAt": "2026-08-25T08:22:03.444Z"
+ }
+ },
+ "2026081914000892": {
+ "id": "2026081914000892",
+ "createdAt": "2026-08-19T14:00:08.000+08:00",
+ "updatedAt": "2026-08-19T14:00:08.000+08:00",
+ "contentSnapshot": "这个世界有很多纷纷扰扰,互相矛盾,互相在这矛盾的背后,持矛盾观点的人互相攻击,互相踩踏是吵闹,甚至发展到战争,这些的背后,其实为什么会有这么多冲突的观点,矛盾的观点,这就是我们所说的迷雾 #穿越迷雾 ",
+ "contentHash": "fnv1a-4c61a65c",
+ "status": "active",
+ "syncStatus": "synced",
+ "source": "daily_scan",
+ "version": 1,
+ "tags": [
+ "穿越迷雾"
+ ],
+ "links": [],
+ "images": [],
+ "references": [],
+ "sourceMemoId": null,
+ "issue": null,
+ "lastMarkdownSyncAt": "2026-08-25T08:22:03.447Z",
+ "lastMarkdownSyncSource": "file_watch",
+ "dailyRef": {
+ "path": "X0.Diary/2026-08-19.md",
+ "heading": "# MemoDaily",
+ "sectionType": "heading",
+ "lastKnownBlock": "- 14:00:08 这个世界有很多纷纷扰扰,互相矛盾,互相在这矛盾的背后,持矛盾观点的人互相攻击,互相踩踏是吵闹,甚至发展到战争,这些的背后,其实为什么会有这么多冲突的观点,矛盾的观点,这就是我们所说的迷雾 #穿越迷雾 ",
+ "lastKnownHash": "fnv1a-ba41b59e",
+ "lineNumberHint": 43,
+ "lastSyncedAt": "2026-08-25T08:22:03.447Z"
+ },
+ "monthlyRef": {
+ "path": "X1.Knomo/Memos-2026-08.md",
+ "dateHeading": "## [[2026-08-19]]",
+ "lastKnownBlock": "- 14:00:08 这个世界有很多纷纷扰扰,互相矛盾,互相在这矛盾的背后,持矛盾观点的人互相攻击,互相踩踏是吵闹,甚至发展到战争,这些的背后,其实为什么会有这么多冲突的观点,矛盾的观点,这就是我们所说的迷雾 #穿越迷雾 ",
+ "lastKnownHash": "fnv1a-ba41b59e",
+ "lineNumberHint": 36,
+ "lastSyncedAt": "2026-08-25T08:22:03.448Z"
+ }
+ },
+ "2026081914003279": {
+ "id": "2026081914003279",
+ "createdAt": "2026-08-19T14:00:32.000+08:00",
+ "updatedAt": "2026-08-19T14:00:32.000+08:00",
+ "contentSnapshot": "乘开心,对于真相的本质要能看清楚,就要从三百六零五六个角度从里到外解剖得开。宏观的看,换角度的看,才能显下的模型是最好的一个。预言",
+ "contentHash": "fnv1a-339f8a83",
+ "status": "active",
+ "syncStatus": "synced",
+ "source": "daily_scan",
+ "version": 1,
+ "tags": [],
+ "links": [],
+ "images": [],
+ "references": [],
+ "sourceMemoId": null,
+ "issue": null,
+ "lastMarkdownSyncAt": "2026-08-25T08:22:03.451Z",
+ "lastMarkdownSyncSource": "file_watch",
+ "dailyRef": {
+ "path": "X0.Diary/2026-08-19.md",
+ "heading": "# MemoDaily",
+ "sectionType": "heading",
+ "lastKnownBlock": "- 14:00:32 乘开心,对于真相的本质要能看清楚,就要从三百六零五六个角度从里到外解剖得开。宏观的看,换角度的看,才能显下的模型是最好的一个。预言",
+ "lastKnownHash": "fnv1a-60ef4ad8",
+ "lineNumberHint": 44,
+ "lastSyncedAt": "2026-08-25T08:22:03.451Z"
+ },
+ "monthlyRef": {
+ "path": "X1.Knomo/Memos-2026-08.md",
+ "dateHeading": "## [[2026-08-19]]",
+ "lastKnownBlock": "- 14:00:32 乘开心,对于真相的本质要能看清楚,就要从三百六零五六个角度从里到外解剖得开。宏观的看,换角度的看,才能显下的模型是最好的一个。预言",
+ "lastKnownHash": "fnv1a-60ef4ad8",
+ "lineNumberHint": 37,
+ "lastSyncedAt": "2026-08-25T08:22:03.452Z"
+ }
+ },
+ "2026081914004495": {
+ "id": "2026081914004495",
+ "createdAt": "2026-08-19T14:00:44.000+08:00",
+ "updatedAt": "2026-08-19T14:00:44.000+08:00",
+ "contentSnapshot": "看这个世界的最重要的角度有哪些呢?有政治、经济、文化、社会、语言、哲学",
+ "contentHash": "fnv1a-c4094a4f",
+ "status": "active",
+ "syncStatus": "synced",
+ "source": "daily_scan",
+ "version": 1,
+ "tags": [],
+ "links": [],
+ "images": [],
+ "references": [],
+ "sourceMemoId": null,
+ "issue": null,
+ "lastMarkdownSyncAt": "2026-08-25T08:22:03.456Z",
+ "lastMarkdownSyncSource": "file_watch",
+ "dailyRef": {
+ "path": "X0.Diary/2026-08-19.md",
+ "heading": "# MemoDaily",
+ "sectionType": "heading",
+ "lastKnownBlock": "- 14:00:44 看这个世界的最重要的角度有哪些呢?有政治、经济、文化、社会、语言、哲学",
+ "lastKnownHash": "fnv1a-d3ffc7e9",
+ "lineNumberHint": 45,
+ "lastSyncedAt": "2026-08-25T08:22:03.456Z"
+ },
+ "monthlyRef": {
+ "path": "X1.Knomo/Memos-2026-08.md",
+ "dateHeading": "## [[2026-08-19]]",
+ "lastKnownBlock": "- 14:00:44 看这个世界的最重要的角度有哪些呢?有政治、经济、文化、社会、语言、哲学",
+ "lastKnownHash": "fnv1a-d3ffc7e9",
+ "lineNumberHint": 38,
+ "lastSyncedAt": "2026-08-25T08:22:03.457Z"
+ }
+ },
+ "2026081914005231": {
+ "id": "2026081914005231",
+ "createdAt": "2026-08-19T14:00:52.000+08:00",
+ "updatedAt": "2026-08-19T14:00:52.000+08:00",
+ "contentSnapshot": "以上是最重要的几个类别",
+ "contentHash": "fnv1a-5fd1b2d9",
+ "status": "active",
+ "syncStatus": "synced",
+ "source": "daily_scan",
+ "version": 1,
+ "tags": [],
+ "links": [],
+ "images": [],
+ "references": [],
+ "sourceMemoId": null,
+ "issue": null,
+ "lastMarkdownSyncAt": "2026-08-25T08:22:03.459Z",
+ "lastMarkdownSyncSource": "file_watch",
+ "dailyRef": {
+ "path": "X0.Diary/2026-08-19.md",
+ "heading": "# MemoDaily",
+ "sectionType": "heading",
+ "lastKnownBlock": "- 14:00:52 以上是最重要的几个类别",
+ "lastKnownHash": "fnv1a-8f4e4e6c",
+ "lineNumberHint": 46,
+ "lastSyncedAt": "2026-08-25T08:22:03.459Z"
+ },
+ "monthlyRef": {
+ "path": "X1.Knomo/Memos-2026-08.md",
+ "dateHeading": "## [[2026-08-19]]",
+ "lastKnownBlock": "- 14:00:52 以上是最重要的几个类别",
+ "lastKnownHash": "fnv1a-8f4e4e6c",
+ "lineNumberHint": 39,
+ "lastSyncedAt": "2026-08-25T08:22:03.459Z"
+ }
+ },
+ "2026081914011143": {
+ "id": "2026081914011143",
+ "createdAt": "2026-08-19T14:01:11.000+08:00",
+ "updatedAt": "2026-08-19T14:01:11.000+08:00",
+ "contentSnapshot": "如何抽丝剥茧地说清楚? #穿越迷雾",
+ "contentHash": "fnv1a-4dcb8aef",
+ "status": "active",
+ "syncStatus": "synced",
+ "source": "daily_scan",
+ "version": 1,
+ "tags": [
+ "穿越迷雾"
+ ],
+ "links": [],
+ "images": [],
+ "references": [],
+ "sourceMemoId": null,
+ "issue": null,
+ "lastMarkdownSyncAt": "2026-08-25T08:22:03.462Z",
+ "lastMarkdownSyncSource": "file_watch",
+ "dailyRef": {
+ "path": "X0.Diary/2026-08-19.md",
+ "heading": "# MemoDaily",
+ "sectionType": "heading",
+ "lastKnownBlock": "- 14:01:11 如何抽丝剥茧地说清楚? #穿越迷雾",
+ "lastKnownHash": "fnv1a-1961ee2e",
+ "lineNumberHint": 47,
+ "lastSyncedAt": "2026-08-25T08:22:03.462Z"
+ },
+ "monthlyRef": {
+ "path": "X1.Knomo/Memos-2026-08.md",
+ "dateHeading": "## [[2026-08-19]]",
+ "lastKnownBlock": "- 14:01:11 如何抽丝剥茧地说清楚? #穿越迷雾",
+ "lastKnownHash": "fnv1a-1961ee2e",
+ "lineNumberHint": 40,
+ "lastSyncedAt": "2026-08-25T08:22:03.463Z"
+ }
+ },
+ "2026081914022808": {
+ "id": "2026081914022808",
+ "createdAt": "2026-08-19T14:02:28.000+08:00",
+ "updatedAt": "2026-08-19T14:02:28.000+08:00",
+ "contentSnapshot": "为什么翡翠某人是中华民族最大的祸患和罪人? 恢复帝制是走向现代化过程中最可怕的倒退。应该论证一下帝制为什么是最不可靠的一种制度。它会特别容易造成社会的大动荡。",
+ "contentHash": "fnv1a-0ba2fa5e",
+ "status": "active",
+ "syncStatus": "synced",
+ "source": "daily_scan",
+ "version": 1,
+ "tags": [],
+ "links": [],
+ "images": [],
+ "references": [],
+ "sourceMemoId": null,
+ "issue": null,
+ "lastMarkdownSyncAt": "2026-08-25T08:22:03.465Z",
+ "lastMarkdownSyncSource": "file_watch",
+ "dailyRef": {
+ "path": "X0.Diary/2026-08-19.md",
+ "heading": "# MemoDaily",
+ "sectionType": "heading",
+ "lastKnownBlock": "- 14:02:28 为什么翡翠某人是中华民族最大的祸患和罪人? 恢复帝制是走向现代化过程中最可怕的倒退。应该论证一下帝制为什么是最不可靠的一种制度。它会特别容易造成社会的大动荡。",
+ "lastKnownHash": "fnv1a-8ecac7c0",
+ "lineNumberHint": 48,
+ "lastSyncedAt": "2026-08-25T08:22:03.465Z"
+ },
+ "monthlyRef": {
+ "path": "X1.Knomo/Memos-2026-08.md",
+ "dateHeading": "## [[2026-08-19]]",
+ "lastKnownBlock": "- 14:02:28 为什么翡翠某人是中华民族最大的祸患和罪人? 恢复帝制是走向现代化过程中最可怕的倒退。应该论证一下帝制为什么是最不可靠的一种制度。它会特别容易造成社会的大动荡。",
+ "lastKnownHash": "fnv1a-8ecac7c0",
+ "lineNumberHint": 41,
+ "lastSyncedAt": "2026-08-25T08:22:03.466Z"
+ }
+ },
+ "2026081914092073": {
+ "id": "2026081914092073",
+ "createdAt": "2026-08-19T14:09:20.000+08:00",
+ "updatedAt": "2026-08-19T14:09:20.000+08:00",
+ "contentSnapshot": "任何复杂的理论术语都可以划归到一个大白话。去解释",
+ "contentHash": "fnv1a-5b11abba",
+ "status": "active",
+ "syncStatus": "synced",
+ "source": "daily_scan",
+ "version": 1,
+ "tags": [],
+ "links": [],
+ "images": [],
+ "references": [],
+ "sourceMemoId": null,
+ "issue": null,
+ "lastMarkdownSyncAt": "2026-08-25T08:22:03.468Z",
+ "lastMarkdownSyncSource": "file_watch",
+ "dailyRef": {
+ "path": "X0.Diary/2026-08-19.md",
+ "heading": "# MemoDaily",
+ "sectionType": "heading",
+ "lastKnownBlock": "- 14:09:20 任何复杂的理论术语都可以划归到一个大白话。去解释",
+ "lastKnownHash": "fnv1a-7b16367d",
+ "lineNumberHint": 49,
+ "lastSyncedAt": "2026-08-25T08:22:03.468Z"
+ },
+ "monthlyRef": {
+ "path": "X1.Knomo/Memos-2026-08.md",
+ "dateHeading": "## [[2026-08-19]]",
+ "lastKnownBlock": "- 14:09:20 任何复杂的理论术语都可以划归到一个大白话。去解释",
+ "lastKnownHash": "fnv1a-7b16367d",
+ "lineNumberHint": 42,
+ "lastSyncedAt": "2026-08-25T08:22:03.469Z"
+ }
+ },
+ "2026081914095720": {
+ "id": "2026081914095720",
+ "createdAt": "2026-08-19T14:09:57.000+08:00",
+ "updatedAt": "2026-08-19T14:09:57.000+08:00",
+ "contentSnapshot": "当然了,有一些术语是必要的去理解一些思想,因为任何术语都是一种非常巧妙的高度精炼的总结。对某一个面向某一种真相的揭示,语言还是我们,就像语言是我们必须依赖的东西一样。",
+ "contentHash": "fnv1a-3aa8fe5f",
+ "status": "active",
+ "syncStatus": "synced",
+ "source": "daily_scan",
+ "version": 1,
+ "tags": [],
+ "links": [],
+ "images": [],
+ "references": [],
+ "sourceMemoId": null,
+ "issue": null,
+ "lastMarkdownSyncAt": "2026-08-25T08:22:03.472Z",
+ "lastMarkdownSyncSource": "file_watch",
+ "dailyRef": {
+ "path": "X0.Diary/2026-08-19.md",
+ "heading": "# MemoDaily",
+ "sectionType": "heading",
+ "lastKnownBlock": "- 14:09:57 当然了,有一些术语是必要的去理解一些思想,因为任何术语都是一种非常巧妙的高度精炼的总结。对某一个面向某一种真相的揭示,语言还是我们,就像语言是我们必须依赖的东西一样。",
+ "lastKnownHash": "fnv1a-3e2df238",
+ "lineNumberHint": 50,
+ "lastSyncedAt": "2026-08-25T08:22:03.472Z"
+ },
+ "monthlyRef": {
+ "path": "X1.Knomo/Memos-2026-08.md",
+ "dateHeading": "## [[2026-08-19]]",
+ "lastKnownBlock": "- 14:09:57 当然了,有一些术语是必要的去理解一些思想,因为任何术语都是一种非常巧妙的高度精炼的总结。对某一个面向某一种真相的揭示,语言还是我们,就像语言是我们必须依赖的东西一样。",
+ "lastKnownHash": "fnv1a-3e2df238",
+ "lineNumberHint": 43,
+ "lastSyncedAt": "2026-08-25T08:22:03.473Z"
+ }
+ },
+ "2026081914110820": {
+ "id": "2026081914110820",
+ "createdAt": "2026-08-19T14:11:08.000+08:00",
+ "updatedAt": "2026-08-19T14:11:08.000+08:00",
+ "contentSnapshot": "如何引发别人的兴趣呢?可以巧妙地用别人熟知的故事。还巧妙的是去用别人熟知的电视剧电影里面的桥段来作为话题点,一点点地去展开",
+ "contentHash": "fnv1a-8f5af882",
+ "status": "active",
+ "syncStatus": "synced",
+ "source": "daily_scan",
+ "version": 1,
+ "tags": [],
+ "links": [],
+ "images": [],
+ "references": [],
+ "sourceMemoId": null,
+ "issue": null,
+ "lastMarkdownSyncAt": "2026-08-25T08:22:03.475Z",
+ "lastMarkdownSyncSource": "file_watch",
+ "dailyRef": {
+ "path": "X0.Diary/2026-08-19.md",
+ "heading": "# MemoDaily",
+ "sectionType": "heading",
+ "lastKnownBlock": "- 14:11:08 如何引发别人的兴趣呢?可以巧妙地用别人熟知的故事。还巧妙的是去用别人熟知的电视剧电影里面的桥段来作为话题点,一点点地去展开",
+ "lastKnownHash": "fnv1a-8711f4a2",
+ "lineNumberHint": 51,
+ "lastSyncedAt": "2026-08-25T08:22:03.475Z"
+ },
+ "monthlyRef": {
+ "path": "X1.Knomo/Memos-2026-08.md",
+ "dateHeading": "## [[2026-08-19]]",
+ "lastKnownBlock": "- 14:11:08 如何引发别人的兴趣呢?可以巧妙地用别人熟知的故事。还巧妙的是去用别人熟知的电视剧电影里面的桥段来作为话题点,一点点地去展开",
+ "lastKnownHash": "fnv1a-8711f4a2",
+ "lineNumberHint": 44,
+ "lastSyncedAt": "2026-08-25T08:22:03.476Z"
+ }
+ },
+ "2026081914112708": {
+ "id": "2026081914112708",
+ "createdAt": "2026-08-19T14:11:27.000+08:00",
+ "updatedAt": "2026-08-19T14:11:27.000+08:00",
+ "contentSnapshot": "需要把思想画成总图,在总图里面找切入点去展开,最后慢慢地就去引导大家认识总图",
+ "contentHash": "fnv1a-201be061",
+ "status": "active",
+ "syncStatus": "synced",
+ "source": "daily_scan",
+ "version": 1,
+ "tags": [],
+ "links": [],
+ "images": [],
+ "references": [],
+ "sourceMemoId": null,
+ "issue": null,
+ "lastMarkdownSyncAt": "2026-08-25T08:22:03.478Z",
+ "lastMarkdownSyncSource": "file_watch",
+ "dailyRef": {
+ "path": "X0.Diary/2026-08-19.md",
+ "heading": "# MemoDaily",
+ "sectionType": "heading",
+ "lastKnownBlock": "- 14:11:27 需要把思想画成总图,在总图里面找切入点去展开,最后慢慢地就去引导大家认识总图",
+ "lastKnownHash": "fnv1a-7f3a6a5c",
+ "lineNumberHint": 52,
+ "lastSyncedAt": "2026-08-25T08:22:03.478Z"
+ },
+ "monthlyRef": {
+ "path": "X1.Knomo/Memos-2026-08.md",
+ "dateHeading": "## [[2026-08-19]]",
+ "lastKnownBlock": "- 14:11:27 需要把思想画成总图,在总图里面找切入点去展开,最后慢慢地就去引导大家认识总图",
+ "lastKnownHash": "fnv1a-7f3a6a5c",
+ "lineNumberHint": 45,
+ "lastSyncedAt": "2026-08-25T08:22:03.479Z"
+ }
+ },
+ "2026081914121538": {
+ "id": "2026081914121538",
+ "createdAt": "2026-08-19T14:12:15.000+08:00",
+ "updatedAt": "2026-08-19T14:12:15.000+08:00",
+ "contentSnapshot": "可以不执着于说当下的时事,因为当下时事的走向它是不确定的。但是历史它就是。固定的东西,所以更好解释",
+ "contentHash": "fnv1a-3d716717",
+ "status": "active",
+ "syncStatus": "synced",
+ "source": "daily_scan",
+ "version": 1,
+ "tags": [],
+ "links": [],
+ "images": [],
+ "references": [],
+ "sourceMemoId": null,
+ "issue": null,
+ "lastMarkdownSyncAt": "2026-08-25T08:22:03.482Z",
+ "lastMarkdownSyncSource": "file_watch",
+ "dailyRef": {
+ "path": "X0.Diary/2026-08-19.md",
+ "heading": "# MemoDaily",
+ "sectionType": "heading",
+ "lastKnownBlock": "- 14:12:15 可以不执着于说当下的时事,因为当下时事的走向它是不确定的。但是历史它就是。固定的东西,所以更好解释",
+ "lastKnownHash": "fnv1a-92f8e6be",
+ "lineNumberHint": 53,
+ "lastSyncedAt": "2026-08-25T08:22:03.482Z"
+ },
+ "monthlyRef": {
+ "path": "X1.Knomo/Memos-2026-08.md",
+ "dateHeading": "## [[2026-08-19]]",
+ "lastKnownBlock": "- 14:12:15 可以不执着于说当下的时事,因为当下时事的走向它是不确定的。但是历史它就是。固定的东西,所以更好解释",
+ "lastKnownHash": "fnv1a-92f8e6be",
+ "lineNumberHint": 46,
+ "lastSyncedAt": "2026-08-25T08:22:03.482Z"
+ }
+ },
+ "2026081914123738": {
+ "id": "2026081914123738",
+ "createdAt": "2026-08-19T14:12:37.000+08:00",
+ "updatedAt": "2026-08-19T14:12:37.000+08:00",
+ "contentSnapshot": "观点故事,然后历史事实好,可以古今中外的说,趣味性也强",
+ "contentHash": "fnv1a-48d67917",
+ "status": "active",
+ "syncStatus": "synced",
+ "source": "daily_scan",
+ "version": 1,
+ "tags": [],
+ "links": [],
+ "images": [],
+ "references": [],
+ "sourceMemoId": null,
+ "issue": null,
+ "lastMarkdownSyncAt": "2026-08-25T08:22:03.486Z",
+ "lastMarkdownSyncSource": "file_watch",
+ "dailyRef": {
+ "path": "X0.Diary/2026-08-19.md",
+ "heading": "# MemoDaily",
+ "sectionType": "heading",
+ "lastKnownBlock": "- 14:12:37 观点故事,然后历史事实好,可以古今中外的说,趣味性也强",
+ "lastKnownHash": "fnv1a-dd83f87e",
+ "lineNumberHint": 54,
+ "lastSyncedAt": "2026-08-25T08:22:03.486Z"
+ },
+ "monthlyRef": {
+ "path": "X1.Knomo/Memos-2026-08.md",
+ "dateHeading": "## [[2026-08-19]]",
+ "lastKnownBlock": "- 14:12:37 观点故事,然后历史事实好,可以古今中外的说,趣味性也强",
+ "lastKnownHash": "fnv1a-dd83f87e",
+ "lineNumberHint": 47,
+ "lastSyncedAt": "2026-08-25T08:22:03.486Z"
+ }
+ },
+ "2026081914132233": {
+ "id": "2026081914132233",
+ "createdAt": "2026-08-19T14:13:22.000+08:00",
+ "updatedAt": "2026-08-19T14:13:22.000+08:00",
+ "contentSnapshot": "除了影视剧的桥段,当下的热点事实、热点事件也可以作为切入点",
+ "contentHash": "fnv1a-263c60ba",
+ "status": "active",
+ "syncStatus": "synced",
+ "source": "daily_scan",
+ "version": 1,
+ "tags": [],
+ "links": [],
+ "images": [],
+ "references": [],
+ "sourceMemoId": null,
+ "issue": null,
+ "lastMarkdownSyncAt": "2026-08-25T08:22:03.490Z",
+ "lastMarkdownSyncSource": "file_watch",
+ "dailyRef": {
+ "path": "X0.Diary/2026-08-19.md",
+ "heading": "# MemoDaily",
+ "sectionType": "heading",
+ "lastKnownBlock": "- 14:13:22 除了影视剧的桥段,当下的热点事实、热点事件也可以作为切入点",
+ "lastKnownHash": "fnv1a-3ac17d1c",
+ "lineNumberHint": 55,
+ "lastSyncedAt": "2026-08-25T08:22:03.490Z"
+ },
+ "monthlyRef": {
+ "path": "X1.Knomo/Memos-2026-08.md",
+ "dateHeading": "## [[2026-08-19]]",
+ "lastKnownBlock": "- 14:13:22 除了影视剧的桥段,当下的热点事实、热点事件也可以作为切入点",
+ "lastKnownHash": "fnv1a-3ac17d1c",
+ "lineNumberHint": 48,
+ "lastSyncedAt": "2026-08-25T08:22:03.490Z"
+ }
+ },
+ "2026081918505106": {
+ "id": "2026081918505106",
+ "createdAt": "2026-08-19T18:50:51.000+08:00",
+ "updatedAt": "2026-08-19T18:50:51.000+08:00",
+ "contentSnapshot": "可以做一个你不知道的事系列,比如你不知道,儒家其实是非常反对科举制的,虽然科举制的考试考的内容全部都是儒家的经典,表面上好像都是。",
+ "contentHash": "fnv1a-fee9d438",
+ "status": "active",
+ "syncStatus": "synced",
+ "source": "daily_scan",
+ "version": 1,
+ "tags": [],
+ "links": [],
+ "images": [],
+ "references": [],
+ "sourceMemoId": null,
+ "issue": null,
+ "lastMarkdownSyncAt": "2026-08-25T08:22:03.493Z",
+ "lastMarkdownSyncSource": "file_watch",
+ "dailyRef": {
+ "path": "X0.Diary/2026-08-19.md",
+ "heading": "# MemoDaily",
+ "sectionType": "heading",
+ "lastKnownBlock": "- 18:50:51 可以做一个你不知道的事系列,比如你不知道,儒家其实是非常反对科举制的,虽然科举制的考试考的内容全部都是儒家的经典,表面上好像都是。",
+ "lastKnownHash": "fnv1a-f3beff5f",
+ "lineNumberHint": 56,
+ "lastSyncedAt": "2026-08-25T08:22:03.493Z"
+ },
+ "monthlyRef": {
+ "path": "X1.Knomo/Memos-2026-08.md",
+ "dateHeading": "## [[2026-08-19]]",
+ "lastKnownBlock": "- 18:50:51 可以做一个你不知道的事系列,比如你不知道,儒家其实是非常反对科举制的,虽然科举制的考试考的内容全部都是儒家的经典,表面上好像都是。",
+ "lastKnownHash": "fnv1a-f3beff5f",
+ "lineNumberHint": 49,
+ "lastSyncedAt": "2026-08-25T08:22:03.493Z"
+ }
+ },
+ "2026081918511799": {
+ "id": "2026081918511799",
+ "createdAt": "2026-08-19T18:51:17.000+08:00",
+ "updatedAt": "2026-08-19T18:51:17.000+08:00",
+ "contentSnapshot": "儒家其实是法家的爹。嗯,虽然儿子烦老子。",
+ "contentHash": "fnv1a-25b802dc",
+ "status": "active",
+ "syncStatus": "synced",
+ "source": "daily_scan",
+ "version": 1,
+ "tags": [],
+ "links": [],
+ "images": [],
+ "references": [],
+ "sourceMemoId": null,
+ "issue": null,
+ "lastMarkdownSyncAt": "2026-08-25T08:22:03.496Z",
+ "lastMarkdownSyncSource": "file_watch",
+ "dailyRef": {
+ "path": "X0.Diary/2026-08-19.md",
+ "heading": "# MemoDaily",
+ "sectionType": "heading",
+ "lastKnownBlock": "- 18:51:17 儒家其实是法家的爹。嗯,虽然儿子烦老子。",
+ "lastKnownHash": "fnv1a-c581684c",
+ "lineNumberHint": 57,
+ "lastSyncedAt": "2026-08-25T08:22:03.496Z"
+ },
+ "monthlyRef": {
+ "path": "X1.Knomo/Memos-2026-08.md",
+ "dateHeading": "## [[2026-08-19]]",
+ "lastKnownBlock": "- 18:51:17 儒家其实是法家的爹。嗯,虽然儿子烦老子。",
+ "lastKnownHash": "fnv1a-c581684c",
+ "lineNumberHint": 50,
+ "lastSyncedAt": "2026-08-25T08:22:03.497Z"
+ }
+ },
+ "2026081918531480": {
+ "id": "2026081918531480",
+ "createdAt": "2026-08-19T18:53:14.000+08:00",
+ "updatedAt": "2026-08-19T18:53:14.000+08:00",
+ "contentSnapshot": "很多很深刻但你可能没有深思过的道理。描述的现象是对的,所给的答案是完全错误的--骗子骗人经常利用这个套路。",
+ "contentHash": "fnv1a-30dc7464",
+ "status": "active",
+ "syncStatus": "synced",
+ "source": "daily_scan",
+ "version": 1,
+ "tags": [],
+ "links": [],
+ "images": [],
+ "references": [],
+ "sourceMemoId": null,
+ "issue": null,
+ "lastMarkdownSyncAt": "2026-08-25T08:22:03.500Z",
+ "lastMarkdownSyncSource": "file_watch",
+ "dailyRef": {
+ "path": "X0.Diary/2026-08-19.md",
+ "heading": "# MemoDaily",
+ "sectionType": "heading",
+ "lastKnownBlock": "- 18:53:14 很多很深刻但你可能没有深思过的道理。描述的现象是对的,所给的答案是完全错误的--骗子骗人经常利用这个套路。",
+ "lastKnownHash": "fnv1a-1a79da23",
+ "lineNumberHint": 58,
+ "lastSyncedAt": "2026-08-25T08:22:03.500Z"
+ },
+ "monthlyRef": {
+ "path": "X1.Knomo/Memos-2026-08.md",
+ "dateHeading": "## [[2026-08-19]]",
+ "lastKnownBlock": "- 18:53:14 很多很深刻但你可能没有深思过的道理。描述的现象是对的,所给的答案是完全错误的--骗子骗人经常利用这个套路。",
+ "lastKnownHash": "fnv1a-1a79da23",
+ "lineNumberHint": 51,
+ "lastSyncedAt": "2026-08-25T08:22:03.500Z"
+ }
+ },
+ "2026081918554858": {
+ "id": "2026081918554858",
+ "createdAt": "2026-08-19T18:55:48.000+08:00",
+ "updatedAt": "2026-08-19T18:55:48.000+08:00",
+ "contentSnapshot": "以真带假,以小带大——骗子的常见骗术的核心。",
+ "contentHash": "fnv1a-a2e9678a",
+ "status": "active",
+ "syncStatus": "synced",
+ "source": "daily_scan",
+ "version": 1,
+ "tags": [],
+ "links": [],
+ "images": [],
+ "references": [],
+ "sourceMemoId": null,
+ "issue": null,
+ "lastMarkdownSyncAt": "2026-08-25T08:22:03.504Z",
+ "lastMarkdownSyncSource": "file_watch",
+ "dailyRef": {
+ "path": "X0.Diary/2026-08-19.md",
+ "heading": "# MemoDaily",
+ "sectionType": "heading",
+ "lastKnownBlock": "- 18:55:48 以真带假,以小带大——骗子的常见骗术的核心。",
+ "lastKnownHash": "fnv1a-4cd3ab32",
+ "lineNumberHint": 59,
+ "lastSyncedAt": "2026-08-25T08:22:03.504Z"
+ },
+ "monthlyRef": {
+ "path": "X1.Knomo/Memos-2026-08.md",
+ "dateHeading": "## [[2026-08-19]]",
+ "lastKnownBlock": "- 18:55:48 以真带假,以小带大——骗子的常见骗术的核心。",
+ "lastKnownHash": "fnv1a-4cd3ab32",
+ "lineNumberHint": 52,
+ "lastSyncedAt": "2026-08-25T08:22:03.504Z"
+ }
+ },
+ "2026081919003007": {
+ "id": "2026081919003007",
+ "createdAt": "2026-08-19T19:00:30.000+08:00",
+ "updatedAt": "2026-08-19T19:00:30.000+08:00",
+ "contentSnapshot": "小命题趣味点是从你不知道的事,这种就可以,但是也有一些大面题,贯穿古今中外的一个国家该怎么搞。",
+ "contentHash": "fnv1a-53f47b5c",
+ "status": "active",
+ "syncStatus": "synced",
+ "source": "daily_scan",
+ "version": 1,
+ "tags": [],
+ "links": [],
+ "images": [],
+ "references": [],
+ "sourceMemoId": null,
+ "issue": null,
+ "lastMarkdownSyncAt": "2026-08-25T08:22:03.507Z",
+ "lastMarkdownSyncSource": "file_watch",
+ "dailyRef": {
+ "path": "X0.Diary/2026-08-19.md",
+ "heading": "# MemoDaily",
+ "sectionType": "heading",
+ "lastKnownBlock": "- 19:00:30 小命题趣味点是从你不知道的事,这种就可以,但是也有一些大面题,贯穿古今中外的一个国家该怎么搞。",
+ "lastKnownHash": "fnv1a-00a6ec12",
+ "lineNumberHint": 60,
+ "lastSyncedAt": "2026-08-25T08:22:03.507Z"
+ },
+ "monthlyRef": {
+ "path": "X1.Knomo/Memos-2026-08.md",
+ "dateHeading": "## [[2026-08-19]]",
+ "lastKnownBlock": "- 19:00:30 小命题趣味点是从你不知道的事,这种就可以,但是也有一些大面题,贯穿古今中外的一个国家该怎么搞。",
+ "lastKnownHash": "fnv1a-00a6ec12",
+ "lineNumberHint": 53,
+ "lastSyncedAt": "2026-08-25T08:22:03.508Z"
+ }
+ },
+ "2026081919004641": {
+ "id": "2026081919004641",
+ "createdAt": "2026-08-19T19:00:46.000+08:00",
+ "updatedAt": "2026-08-19T19:00:46.000+08:00",
+ "contentSnapshot": "一个国家的制度怎么设计是政治话题,政治思想史里面有各种答案。一个国家的经济怎么搞?这是经济学的话题。",
+ "contentHash": "fnv1a-ebcc878a",
+ "status": "active",
+ "syncStatus": "synced",
+ "source": "daily_scan",
+ "version": 1,
+ "tags": [],
+ "links": [],
+ "images": [],
+ "references": [],
+ "sourceMemoId": null,
+ "issue": null,
+ "lastMarkdownSyncAt": "2026-08-25T08:22:03.511Z",
+ "lastMarkdownSyncSource": "file_watch",
+ "dailyRef": {
+ "path": "X0.Diary/2026-08-19.md",
+ "heading": "# MemoDaily",
+ "sectionType": "heading",
+ "lastKnownBlock": "- 19:00:46 一个国家的制度怎么设计是政治话题,政治思想史里面有各种答案。一个国家的经济怎么搞?这是经济学的话题。",
+ "lastKnownHash": "fnv1a-d1e8e8a7",
+ "lineNumberHint": 61,
+ "lastSyncedAt": "2026-08-25T08:22:03.511Z"
+ },
+ "monthlyRef": {
+ "path": "X1.Knomo/Memos-2026-08.md",
+ "dateHeading": "## [[2026-08-19]]",
+ "lastKnownBlock": "- 19:00:46 一个国家的制度怎么设计是政治话题,政治思想史里面有各种答案。一个国家的经济怎么搞?这是经济学的话题。",
+ "lastKnownHash": "fnv1a-d1e8e8a7",
+ "lineNumberHint": 54,
+ "lastSyncedAt": "2026-08-25T08:22:03.511Z"
+ }
+ },
+ "2026081919025493": {
+ "id": "2026081919025493",
+ "createdAt": "2026-08-19T19:02:54.000+08:00",
+ "updatedAt": "2026-08-19T19:02:54.000+08:00",
+ "contentSnapshot": "理想很美好,现实很骨感,这个在历史上反复出现。理论(可行)是一回事,现实(可行)又是另一回事儿。",
+ "contentHash": "fnv1a-8cd948a8",
+ "status": "active",
+ "syncStatus": "synced",
+ "source": "daily_scan",
+ "version": 1,
+ "tags": [],
+ "links": [],
+ "images": [],
+ "references": [],
+ "sourceMemoId": null,
+ "issue": null,
+ "lastMarkdownSyncAt": "2026-08-25T08:22:03.514Z",
+ "lastMarkdownSyncSource": "file_watch",
+ "dailyRef": {
+ "path": "X0.Diary/2026-08-19.md",
+ "heading": "# MemoDaily",
+ "sectionType": "heading",
+ "lastKnownBlock": "- 19:02:54 理想很美好,现实很骨感,这个在历史上反复出现。理论(可行)是一回事,现实(可行)又是另一回事儿。",
+ "lastKnownHash": "fnv1a-51558f3a",
+ "lineNumberHint": 62,
+ "lastSyncedAt": "2026-08-25T08:22:03.514Z"
+ },
+ "monthlyRef": {
+ "path": "X1.Knomo/Memos-2026-08.md",
+ "dateHeading": "## [[2026-08-19]]",
+ "lastKnownBlock": "- 19:02:54 理想很美好,现实很骨感,这个在历史上反复出现。理论(可行)是一回事,现实(可行)又是另一回事儿。",
+ "lastKnownHash": "fnv1a-51558f3a",
+ "lineNumberHint": 55,
+ "lastSyncedAt": "2026-08-25T08:22:03.514Z"
+ }
+ },
+ "2026081919294686": {
+ "id": "2026081919294686",
+ "createdAt": "2026-08-19T19:29:46.000+08:00",
+ "updatedAt": "2026-08-19T19:29:46.000+08:00",
+ "contentSnapshot": "发夹。可不是你想象的那个法家最狗血的法家。其残暴和疯狂超乎想象。起来。荒的主要人物的命运程度超乎想象。",
+ "contentHash": "fnv1a-4338222b",
+ "status": "active",
+ "syncStatus": "synced",
+ "source": "daily_scan",
+ "version": 1,
+ "tags": [],
+ "links": [],
+ "images": [],
+ "references": [],
+ "sourceMemoId": null,
+ "issue": null,
+ "lastMarkdownSyncAt": "2026-08-25T08:22:03.517Z",
+ "lastMarkdownSyncSource": "file_watch",
+ "dailyRef": {
+ "path": "X0.Diary/2026-08-19.md",
+ "heading": "# MemoDaily",
+ "sectionType": "heading",
+ "lastKnownBlock": "- 19:29:46 发夹。可不是你想象的那个法家最狗血的法家。其残暴和疯狂超乎想象。起来。荒的主要人物的命运程度超乎想象。",
+ "lastKnownHash": "fnv1a-dd0fc61f",
+ "lineNumberHint": 63,
+ "lastSyncedAt": "2026-08-25T08:22:03.517Z"
+ },
+ "monthlyRef": {
+ "path": "X1.Knomo/Memos-2026-08.md",
+ "dateHeading": "## [[2026-08-19]]",
+ "lastKnownBlock": "- 19:29:46 发夹。可不是你想象的那个法家最狗血的法家。其残暴和疯狂超乎想象。起来。荒的主要人物的命运程度超乎想象。",
+ "lastKnownHash": "fnv1a-dd0fc61f",
+ "lineNumberHint": 56,
+ "lastSyncedAt": "2026-08-25T08:22:03.518Z"
+ }
+ },
+ "2026081919352977": {
+ "id": "2026081919352977",
+ "createdAt": "2026-08-19T19:35:29.000+08:00",
+ "updatedAt": "2026-08-19T19:35:29.000+08:00",
+ "contentSnapshot": "深刻的道理往往是隐蔽的道理。隐蔽的原因就是因为其藏得太深。佛教和道家都有很多特别深刻的话。因为隐蔽,所以难发现,远离(浮在表面 容易发现的)常识。",
+ "contentHash": "fnv1a-beffc340",
+ "status": "active",
+ "syncStatus": "synced",
+ "source": "daily_scan",
+ "version": 1,
+ "tags": [],
+ "links": [],
+ "images": [],
+ "references": [],
+ "sourceMemoId": null,
+ "issue": null,
+ "lastMarkdownSyncAt": "2026-08-25T08:22:03.521Z",
+ "lastMarkdownSyncSource": "file_watch",
+ "dailyRef": {
+ "path": "X0.Diary/2026-08-19.md",
+ "heading": "# MemoDaily",
+ "sectionType": "heading",
+ "lastKnownBlock": "- 19:35:29 深刻的道理往往是隐蔽的道理。隐蔽的原因就是因为其藏得太深。佛教和道家都有很多特别深刻的话。因为隐蔽,所以难发现,远离(浮在表面 容易发现的)常识。",
+ "lastKnownHash": "fnv1a-ca9061c0",
+ "lineNumberHint": 64,
+ "lastSyncedAt": "2026-08-25T08:22:03.521Z"
+ },
+ "monthlyRef": {
+ "path": "X1.Knomo/Memos-2026-08.md",
+ "dateHeading": "## [[2026-08-19]]",
+ "lastKnownBlock": "- 19:35:29 深刻的道理往往是隐蔽的道理。隐蔽的原因就是因为其藏得太深。佛教和道家都有很多特别深刻的话。因为隐蔽,所以难发现,远离(浮在表面 容易发现的)常识。",
+ "lastKnownHash": "fnv1a-ca9061c0",
+ "lineNumberHint": 57,
+ "lastSyncedAt": "2026-08-25T08:22:03.521Z"
+ }
+ },
+ "2026081919363546": {
+ "id": "2026081919363546",
+ "createdAt": "2026-08-19T19:36:35.000+08:00",
+ "updatedAt": "2026-08-19T19:36:35.000+08:00",
+ "contentSnapshot": "祸害宋志刚的源头竟然在2000年前的韩非子身上。因为户籍制度和收容制度都是法家当年所主张出来的。",
+ "contentHash": "fnv1a-4a35732d",
+ "status": "active",
+ "syncStatus": "synced",
+ "source": "daily_scan",
+ "version": 1,
+ "tags": [],
+ "links": [],
+ "images": [],
+ "references": [],
+ "sourceMemoId": null,
+ "issue": null,
+ "lastMarkdownSyncAt": "2026-08-25T08:22:03.525Z",
+ "lastMarkdownSyncSource": "file_watch",
+ "dailyRef": {
+ "path": "X0.Diary/2026-08-19.md",
+ "heading": "# MemoDaily",
+ "sectionType": "heading",
+ "lastKnownBlock": "- 19:36:35 祸害宋志刚的源头竟然在2000年前的韩非子身上。因为户籍制度和收容制度都是法家当年所主张出来的。",
+ "lastKnownHash": "fnv1a-a2958443",
+ "lineNumberHint": 65,
+ "lastSyncedAt": "2026-08-25T08:22:03.525Z"
+ },
+ "monthlyRef": {
+ "path": "X1.Knomo/Memos-2026-08.md",
+ "dateHeading": "## [[2026-08-19]]",
+ "lastKnownBlock": "- 19:36:35 祸害宋志刚的源头竟然在2000年前的韩非子身上。因为户籍制度和收容制度都是法家当年所主张出来的。",
+ "lastKnownHash": "fnv1a-a2958443",
+ "lineNumberHint": 58,
+ "lastSyncedAt": "2026-08-25T08:22:03.525Z"
+ }
+ },
+ "2026081919383070": {
+ "id": "2026081919383070",
+ "createdAt": "2026-08-19T19:38:30.000+08:00",
+ "updatedAt": "2026-08-19T19:38:30.000+08:00",
+ "contentSnapshot": "有些东西你乍一听觉得挺喜欢的,等你了解了真相,你就不喜欢了。",
+ "contentHash": "fnv1a-980d7a2c",
+ "status": "active",
+ "syncStatus": "synced",
+ "source": "daily_scan",
+ "version": 1,
+ "tags": [],
+ "links": [],
+ "images": [],
+ "references": [],
+ "sourceMemoId": null,
+ "issue": null,
+ "lastMarkdownSyncAt": "2026-08-25T08:22:03.528Z",
+ "lastMarkdownSyncSource": "file_watch",
+ "dailyRef": {
+ "path": "X0.Diary/2026-08-19.md",
+ "heading": "# MemoDaily",
+ "sectionType": "heading",
+ "lastKnownBlock": "- 19:38:30 有些东西你乍一听觉得挺喜欢的,等你了解了真相,你就不喜欢了。",
+ "lastKnownHash": "fnv1a-fd88bc11",
+ "lineNumberHint": 66,
+ "lastSyncedAt": "2026-08-25T08:22:03.528Z"
+ },
+ "monthlyRef": {
+ "path": "X1.Knomo/Memos-2026-08.md",
+ "dateHeading": "## [[2026-08-19]]",
+ "lastKnownBlock": "- 19:38:30 有些东西你乍一听觉得挺喜欢的,等你了解了真相,你就不喜欢了。",
+ "lastKnownHash": "fnv1a-fd88bc11",
+ "lineNumberHint": 59,
+ "lastSyncedAt": "2026-08-25T08:22:03.529Z"
+ }
+ },
+ "2026081919384335": {
+ "id": "2026081919384335",
+ "createdAt": "2026-08-19T19:38:43.000+08:00",
+ "updatedAt": "2026-08-19T19:38:43.000+08:00",
+ "contentSnapshot": "就像有些食物,你看着挺好看的,结果咬一口,结果发现极难吃。",
+ "contentHash": "fnv1a-2fe77fb0",
+ "status": "active",
+ "syncStatus": "synced",
+ "source": "daily_scan",
+ "version": 1,
+ "tags": [],
+ "links": [],
+ "images": [],
+ "references": [],
+ "sourceMemoId": null,
+ "issue": null,
+ "lastMarkdownSyncAt": "2026-08-25T08:22:03.532Z",
+ "lastMarkdownSyncSource": "file_watch",
+ "dailyRef": {
+ "path": "X0.Diary/2026-08-19.md",
+ "heading": "# MemoDaily",
+ "sectionType": "heading",
+ "lastKnownBlock": "- 19:38:43 就像有些食物,你看着挺好看的,结果咬一口,结果发现极难吃。",
+ "lastKnownHash": "fnv1a-26b2a845",
+ "lineNumberHint": 67,
+ "lastSyncedAt": "2026-08-25T08:22:03.532Z"
+ },
+ "monthlyRef": {
+ "path": "X1.Knomo/Memos-2026-08.md",
+ "dateHeading": "## [[2026-08-19]]",
+ "lastKnownBlock": "- 19:38:43 就像有些食物,你看着挺好看的,结果咬一口,结果发现极难吃。",
+ "lastKnownHash": "fnv1a-26b2a845",
+ "lineNumberHint": 60,
+ "lastSyncedAt": "2026-08-25T08:22:03.532Z"
+ }
+ },
+ "2026081919385473": {
+ "id": "2026081919385473",
+ "createdAt": "2026-08-19T19:38:54.000+08:00",
+ "updatedAt": "2026-08-19T19:38:54.000+08:00",
+ "contentSnapshot": "法家就是这么一个思想流派。",
+ "contentHash": "fnv1a-d4583c5c",
+ "status": "active",
+ "syncStatus": "synced",
+ "source": "daily_scan",
+ "version": 1,
+ "tags": [],
+ "links": [],
+ "images": [],
+ "references": [],
+ "sourceMemoId": null,
+ "issue": null,
+ "lastMarkdownSyncAt": "2026-08-25T08:22:03.535Z",
+ "lastMarkdownSyncSource": "file_watch",
+ "dailyRef": {
+ "path": "X0.Diary/2026-08-19.md",
+ "heading": "# MemoDaily",
+ "sectionType": "heading",
+ "lastKnownBlock": "- 19:38:54 法家就是这么一个思想流派。",
+ "lastKnownHash": "fnv1a-caccc8dd",
+ "lineNumberHint": 68,
+ "lastSyncedAt": "2026-08-25T08:22:03.535Z"
+ },
+ "monthlyRef": {
+ "path": "X1.Knomo/Memos-2026-08.md",
+ "dateHeading": "## [[2026-08-19]]",
+ "lastKnownBlock": "- 19:38:54 法家就是这么一个思想流派。",
+ "lastKnownHash": "fnv1a-caccc8dd",
+ "lineNumberHint": 61,
+ "lastSyncedAt": "2026-08-25T08:22:03.536Z"
+ }
+ },
+ "2026081919390533": {
+ "id": "2026081919390533",
+ "createdAt": "2026-08-19T19:39:05.000+08:00",
+ "updatedAt": "2026-08-19T19:39:05.000+08:00",
+ "contentSnapshot": "这世间太多事情都不是表面上看起来的那样。",
+ "contentHash": "fnv1a-8b8cfc18",
+ "status": "active",
+ "syncStatus": "synced",
+ "source": "daily_scan",
+ "version": 1,
+ "tags": [],
+ "links": [],
+ "images": [],
+ "references": [],
+ "sourceMemoId": null,
+ "issue": null,
+ "lastMarkdownSyncAt": "2026-08-25T08:22:03.539Z",
+ "lastMarkdownSyncSource": "file_watch",
+ "dailyRef": {
+ "path": "X0.Diary/2026-08-19.md",
+ "heading": "# MemoDaily",
+ "sectionType": "heading",
+ "lastKnownBlock": "- 19:39:05 这世间太多事情都不是表面上看起来的那样。",
+ "lastKnownHash": "fnv1a-61bc31b6",
+ "lineNumberHint": 69,
+ "lastSyncedAt": "2026-08-25T08:22:03.539Z"
+ },
+ "monthlyRef": {
+ "path": "X1.Knomo/Memos-2026-08.md",
+ "dateHeading": "## [[2026-08-19]]",
+ "lastKnownBlock": "- 19:39:05 这世间太多事情都不是表面上看起来的那样。",
+ "lastKnownHash": "fnv1a-61bc31b6",
+ "lineNumberHint": 62,
+ "lastSyncedAt": "2026-08-25T08:22:03.539Z"
+ }
+ },
+ "2026081919393342": {
+ "id": "2026081919393342",
+ "createdAt": "2026-08-19T19:39:33.000+08:00",
+ "updatedAt": "2026-08-19T19:39:33.000+08:00",
+ "contentSnapshot": "我们为什么要研究这些理论。其实就是为了破除这些假象。(印象,幻象,表象)",
+ "contentHash": "fnv1a-4c634e1c",
+ "status": "active",
+ "syncStatus": "synced",
+ "source": "daily_scan",
+ "version": 1,
+ "tags": [],
+ "links": [],
+ "images": [],
+ "references": [],
+ "sourceMemoId": null,
+ "issue": null,
+ "lastMarkdownSyncAt": "2026-08-25T08:22:03.543Z",
+ "lastMarkdownSyncSource": "file_watch",
+ "dailyRef": {
+ "path": "X0.Diary/2026-08-19.md",
+ "heading": "# MemoDaily",
+ "sectionType": "heading",
+ "lastKnownBlock": "- 19:39:33 我们为什么要研究这些理论。其实就是为了破除这些假象。(印象,幻象,表象)",
+ "lastKnownHash": "fnv1a-70c91f21",
+ "lineNumberHint": 70,
+ "lastSyncedAt": "2026-08-25T08:22:03.543Z"
+ },
+ "monthlyRef": {
+ "path": "X1.Knomo/Memos-2026-08.md",
+ "dateHeading": "## [[2026-08-19]]",
+ "lastKnownBlock": "- 19:39:33 我们为什么要研究这些理论。其实就是为了破除这些假象。(印象,幻象,表象)",
+ "lastKnownHash": "fnv1a-70c91f21",
+ "lineNumberHint": 63,
+ "lastSyncedAt": "2026-08-25T08:22:03.543Z"
+ }
+ },
+ "2026081919415104": {
+ "id": "2026081919415104",
+ "createdAt": "2026-08-19T19:41:51.000+08:00",
+ "updatedAt": "2026-08-19T19:41:51.000+08:00",
+ "contentSnapshot": "道家表面上离治理国家这种挺远的,但是实际上却是历史上最好用的治国理论。",
+ "contentHash": "fnv1a-cf9a4625",
+ "status": "active",
+ "syncStatus": "synced",
+ "source": "daily_scan",
+ "version": 1,
+ "tags": [],
+ "links": [],
+ "images": [],
+ "references": [],
+ "sourceMemoId": null,
+ "issue": null,
+ "lastMarkdownSyncAt": "2026-08-25T08:22:03.546Z",
+ "lastMarkdownSyncSource": "file_watch",
+ "dailyRef": {
+ "path": "X0.Diary/2026-08-19.md",
+ "heading": "# MemoDaily",
+ "sectionType": "heading",
+ "lastKnownBlock": "- 19:41:51 道家表面上离治理国家这种挺远的,但是实际上却是历史上最好用的治国理论。",
+ "lastKnownHash": "fnv1a-69259cfb",
+ "lineNumberHint": 71,
+ "lastSyncedAt": "2026-08-25T08:22:03.546Z"
+ },
+ "monthlyRef": {
+ "path": "X1.Knomo/Memos-2026-08.md",
+ "dateHeading": "## [[2026-08-19]]",
+ "lastKnownBlock": "- 19:41:51 道家表面上离治理国家这种挺远的,但是实际上却是历史上最好用的治国理论。",
+ "lastKnownHash": "fnv1a-69259cfb",
+ "lineNumberHint": 64,
+ "lastSyncedAt": "2026-08-25T08:22:03.546Z"
+ }
+ },
+ "2026081919421446": {
+ "id": "2026081919421446",
+ "createdAt": "2026-08-19T19:42:14.000+08:00",
+ "updatedAt": "2026-08-19T19:42:14.000+08:00",
+ "contentSnapshot": "深刻的理论往往效果更持久,准确的概率更大。适用的范围更广。",
+ "contentHash": "fnv1a-95f70dbb",
+ "status": "active",
+ "syncStatus": "synced",
+ "source": "daily_scan",
+ "version": 1,
+ "tags": [],
+ "links": [],
+ "images": [],
+ "references": [],
+ "sourceMemoId": null,
+ "issue": null,
+ "lastMarkdownSyncAt": "2026-08-25T08:22:03.549Z",
+ "lastMarkdownSyncSource": "file_watch",
+ "dailyRef": {
+ "path": "X0.Diary/2026-08-19.md",
+ "heading": "# MemoDaily",
+ "sectionType": "heading",
+ "lastKnownBlock": "- 19:42:14 深刻的理论往往效果更持久,准确的概率更大。适用的范围更广。",
+ "lastKnownHash": "fnv1a-73f1a0b9",
+ "lineNumberHint": 72,
+ "lastSyncedAt": "2026-08-25T08:22:03.549Z"
+ },
+ "monthlyRef": {
+ "path": "X1.Knomo/Memos-2026-08.md",
+ "dateHeading": "## [[2026-08-19]]",
+ "lastKnownBlock": "- 19:42:14 深刻的理论往往效果更持久,准确的概率更大。适用的范围更广。",
+ "lastKnownHash": "fnv1a-73f1a0b9",
+ "lineNumberHint": 65,
+ "lastSyncedAt": "2026-08-25T08:22:03.550Z"
+ }
+ },
+ "2026081919432872": {
+ "id": "2026081919432872",
+ "createdAt": "2026-08-19T19:43:28.000+08:00",
+ "updatedAt": "2026-08-19T19:43:28.000+08:00",
+ "contentSnapshot": "儒家分为古儒家和新儒家,新儒家是得兼容秦制的。这件事从董仲舒开始",
+ "contentHash": "fnv1a-7fafd97a",
+ "status": "active",
+ "syncStatus": "synced",
+ "source": "daily_scan",
+ "version": 1,
+ "tags": [],
+ "links": [],
+ "images": [],
+ "references": [],
+ "sourceMemoId": null,
+ "issue": null,
+ "lastMarkdownSyncAt": "2026-08-25T08:22:03.554Z",
+ "lastMarkdownSyncSource": "file_watch",
+ "dailyRef": {
+ "path": "X0.Diary/2026-08-19.md",
+ "heading": "# MemoDaily",
+ "sectionType": "heading",
+ "lastKnownBlock": "- 19:43:28 儒家分为古儒家和新儒家,新儒家是得兼容秦制的。这件事从董仲舒开始",
+ "lastKnownHash": "fnv1a-b4646d76",
+ "lineNumberHint": 73,
+ "lastSyncedAt": "2026-08-25T08:22:03.554Z"
+ },
+ "monthlyRef": {
+ "path": "X1.Knomo/Memos-2026-08.md",
+ "dateHeading": "## [[2026-08-19]]",
+ "lastKnownBlock": "- 19:43:28 儒家分为古儒家和新儒家,新儒家是得兼容秦制的。这件事从董仲舒开始",
+ "lastKnownHash": "fnv1a-b4646d76",
+ "lineNumberHint": 66,
+ "lastSyncedAt": "2026-08-25T08:22:03.555Z"
+ }
+ },
+ "2026081919434461": {
+ "id": "2026081919434461",
+ "createdAt": "2026-08-19T19:43:44.000+08:00",
+ "updatedAt": "2026-08-19T19:43:44.000+08:00",
+ "contentSnapshot": "古儒,或者说原始儒家,崇周仇秦,崇拜周制,仇恨秦制",
+ "contentHash": "fnv1a-67d2ffbc",
+ "status": "active",
+ "syncStatus": "synced",
+ "source": "daily_scan",
+ "version": 1,
+ "tags": [],
+ "links": [],
+ "images": [],
+ "references": [],
+ "sourceMemoId": null,
+ "issue": null,
+ "lastMarkdownSyncAt": "2026-08-25T08:22:03.558Z",
+ "lastMarkdownSyncSource": "file_watch",
+ "dailyRef": {
+ "path": "X0.Diary/2026-08-19.md",
+ "heading": "# MemoDaily",
+ "sectionType": "heading",
+ "lastKnownBlock": "- 19:43:44 古儒,或者说原始儒家,崇周仇秦,崇拜周制,仇恨秦制",
+ "lastKnownHash": "fnv1a-141c6ede",
+ "lineNumberHint": 74,
+ "lastSyncedAt": "2026-08-25T08:22:03.558Z"
+ },
+ "monthlyRef": {
+ "path": "X1.Knomo/Memos-2026-08.md",
+ "dateHeading": "## [[2026-08-19]]",
+ "lastKnownBlock": "- 19:43:44 古儒,或者说原始儒家,崇周仇秦,崇拜周制,仇恨秦制",
+ "lastKnownHash": "fnv1a-141c6ede",
+ "lineNumberHint": 67,
+ "lastSyncedAt": "2026-08-25T08:22:03.559Z"
+ }
+ },
+ "2026081919442375": {
+ "id": "2026081919442375",
+ "createdAt": "2026-08-19T19:44:23.000+08:00",
+ "updatedAt": "2026-08-19T19:44:23.000+08:00",
+ "contentSnapshot": "别看三纲五常,三纲在五常的前面,但实际上先有五常,后有的三纲。五常是孔孟时代就有的,三纲才是董仲舒那时候才开始有的。。而且三纲根本不是儒家的理论,其实它是法家的理论.",
+ "contentHash": "fnv1a-068ee5d3",
+ "status": "active",
+ "syncStatus": "synced",
+ "source": "daily_scan",
+ "version": 1,
+ "tags": [],
+ "links": [],
+ "images": [],
+ "references": [],
+ "sourceMemoId": null,
+ "issue": null,
+ "lastMarkdownSyncAt": "2026-08-25T08:22:03.561Z",
+ "lastMarkdownSyncSource": "file_watch",
+ "dailyRef": {
+ "path": "X0.Diary/2026-08-19.md",
+ "heading": "# MemoDaily",
+ "sectionType": "heading",
+ "lastKnownBlock": "- 19:44:23 别看三纲五常,三纲在五常的前面,但实际上先有五常,后有的三纲。五常是孔孟时代就有的,三纲才是董仲舒那时候才开始有的。。而且三纲根本不是儒家的理论,其实它是法家的理论.",
+ "lastKnownHash": "fnv1a-cff7b0b1",
+ "lineNumberHint": 75,
+ "lastSyncedAt": "2026-08-25T08:22:03.561Z"
+ },
+ "monthlyRef": {
+ "path": "X1.Knomo/Memos-2026-08.md",
+ "dateHeading": "## [[2026-08-19]]",
+ "lastKnownBlock": "- 19:44:23 别看三纲五常,三纲在五常的前面,但实际上先有五常,后有的三纲。五常是孔孟时代就有的,三纲才是董仲舒那时候才开始有的。。而且三纲根本不是儒家的理论,其实它是法家的理论.",
+ "lastKnownHash": "fnv1a-cff7b0b1",
+ "lineNumberHint": 68,
+ "lastSyncedAt": "2026-08-25T08:22:03.562Z"
+ }
+ },
+ "2026081919585519": {
+ "id": "2026081919585519",
+ "createdAt": "2026-08-19T19:58:55.000+08:00",
+ "updatedAt": "2026-08-19T19:58:55.000+08:00",
+ "contentSnapshot": "悖论 你以为语言是帮助人沟通的,但人与人沟通过程中,最高的墙恰恰是语言。",
+ "contentHash": "fnv1a-4a48a2ab",
+ "status": "active",
+ "syncStatus": "synced",
+ "source": "daily_scan",
+ "version": 1,
+ "tags": [],
+ "links": [],
+ "images": [],
+ "references": [],
+ "sourceMemoId": null,
+ "issue": null,
+ "lastMarkdownSyncAt": "2026-08-25T08:22:03.565Z",
+ "lastMarkdownSyncSource": "file_watch",
+ "dailyRef": {
+ "path": "X0.Diary/2026-08-19.md",
+ "heading": "# MemoDaily",
+ "sectionType": "heading",
+ "lastKnownBlock": "- 19:58:55 悖论 你以为语言是帮助人沟通的,但人与人沟通过程中,最高的墙恰恰是语言。",
+ "lastKnownHash": "fnv1a-936381cf",
+ "lineNumberHint": 76,
+ "lastSyncedAt": "2026-08-25T08:22:03.565Z"
+ },
+ "monthlyRef": {
+ "path": "X1.Knomo/Memos-2026-08.md",
+ "dateHeading": "## [[2026-08-19]]",
+ "lastKnownBlock": "- 19:58:55 悖论 你以为语言是帮助人沟通的,但人与人沟通过程中,最高的墙恰恰是语言。",
+ "lastKnownHash": "fnv1a-936381cf",
+ "lineNumberHint": 69,
+ "lastSyncedAt": "2026-08-25T08:22:03.565Z"
+ }
+ },
+ "2026081920021926": {
+ "id": "2026081920021926",
+ "createdAt": "2026-08-19T20:02:19.000+08:00",
+ "updatedAt": "2026-08-19T20:02:19.000+08:00",
+ "contentSnapshot": "这应该是共产主义社会最主流的新闻--",
+ "contentHash": "fnv1a-a06049eb",
+ "status": "active",
+ "syncStatus": "synced",
+ "source": "daily_scan",
+ "version": 1,
+ "tags": [],
+ "links": [],
+ "images": [],
+ "references": [],
+ "sourceMemoId": null,
+ "issue": null,
+ "lastMarkdownSyncAt": "2026-08-25T08:22:03.568Z",
+ "lastMarkdownSyncSource": "file_watch",
+ "dailyRef": {
+ "path": "X0.Diary/2026-08-19.md",
+ "heading": "# MemoDaily",
+ "sectionType": "heading",
+ "lastKnownBlock": "- 20:02:19 这应该是共产主义社会最主流的新闻--",
+ "lastKnownHash": "fnv1a-b1923914",
+ "lineNumberHint": 77,
+ "lastSyncedAt": "2026-08-25T08:22:03.568Z"
+ },
+ "monthlyRef": {
+ "path": "X1.Knomo/Memos-2026-08.md",
+ "dateHeading": "## [[2026-08-19]]",
+ "lastKnownBlock": "- 20:02:19 这应该是共产主义社会最主流的新闻--",
+ "lastKnownHash": "fnv1a-b1923914",
+ "lineNumberHint": 70,
+ "lastSyncedAt": "2026-08-25T08:22:03.569Z"
+ }
}
}
}
diff --git a/X2.Archived/scripts/sync_0inbox.py b/X2.Archived/scripts/sync_0inbox.py
index 7b6887d..42d1fd0 100644
--- a/X2.Archived/scripts/sync_0inbox.py
+++ b/X2.Archived/scripts/sync_0inbox.py
@@ -26,9 +26,11 @@
扫描范围(用户指定)
--------------------
- 顶层黑名单 EXCLUDE_TOP 整棵子树不扫:X2.Archived / 00index / X1.Knomo / X.Attachment / P3.bobo
+- 嵌套黑名单 EXCLUDE_PATHS 整棵子树不扫:02DS/02copilot(Copilot 对话日志)、
+ 02DS/01dril-book(读书笔记库);两者均「永不从其中取内容生成 tagfeed」。
- 仅扫描后缀 SCAN_EXT:.md .markdown .excalidraw .canvas;其它后缀一律跳过
-- markdown 只抽标准 `tags:` frontmatter 与正文 #tag;topic/category/type 等
- 元数据字段不再当作标签(避免 copilot 对话、读书笔记的元数据被误建页)
+- markdown 只抽标准 `tags:` frontmatter 与正文 #tag;topic/category/keywords/type 等
+ 元数据字段绝不当作标签(避免 copilot 对话、读书笔记的元数据被误建页)
幂等 & 安全
----------
@@ -142,9 +144,10 @@
SCAN_EXT = {".md", ".markdown", ".excalidraw", ".canvas"}
EXCLUDE_TOP = {"X2.Archived", "00index", "X1.Knomo", "X.Attachment", "P3.bobo"}
# 嵌套路径排除(只排除指定子树,不影响其同级目录)。
-# 例:02DS/02copilot 是 Obsidian Copilot 的对话日志,其 topic: 字段与聊天正文
-# 不应被当作标签扫描(与 tagfeed 模板的 excludedPaths 保持一致)。
-EXCLUDE_PATHS = {"02DS/02copilot"}
+# 02DS/02copilot:Obsidian Copilot 对话日志,topic: 字段与聊天正文不应被当作标签扫描。
+# 02DS/01dril-book:读书笔记库,其正文/元数据亦不希望进入 tagfeed 体系。
+# 两者「永不从其中取内容」(与 tagfeed 模板的 excludedPaths 保持一致)。
+EXCLUDE_PATHS = {"02DS/02copilot", "02DS/01dril-book"}
INBOX_DIR = "0inbox"
EXCLUDE_DIRS = {".obsidian", ".trash", ".git", ".agents", ".claude",
".copilot", ".opencode", ".smart-env", ".workbuddy"}
@@ -206,11 +209,9 @@
tok = tok.strip().lstrip("#")
if tok:
tags.add(tok)
- for val in fm.values():
- for tok in split_fm_value(val):
- mm = TAG_RE.search(str(tok))
- if mm:
- tags.add(mm.group(1))
+ # 注意:不再遍历所有 frontmatter 值去抽取 #tag。
+ # 否则 topic/category/keywords 等元数据字段里出现的 # 文本
+ # 会被误判为标签,违背「这类字段不生成 tagfeed 页」的约定。
in_fence = False
for line in body.splitlines():
if line.lstrip().startswith("```"):
@@ -258,7 +259,7 @@
"> 本页由 DataviewJS 实时计算,笔记有变动时自动刷新。\n\n"
"```dataviewjs\n"
"const targetTag = \"__TARGET_TAG__\";\n"
- "const excludedPaths = [\"X2.Archived\", \"X0.Clippings\", \"X1.Knomo\", \"02DS/02copilot\", \".agents\", \".claude\", \".copilot\", \".opencode\", \".smart-env\", \".workbuddy\", \".trash\"];\n"
+ "const excludedPaths = [\"X2.Archived\", \"X0.Clippings\", \"X1.Knomo\", \"02DS/02copilot\", \"02DS/01dril-book\", \".agents\", \".claude\", \".copilot\", \".opencode\", \".smart-env\", \".workbuddy\", \".trash\"];\n"
"dv.paragraph(\"#\" + targetTag + \" 聚合占位\");\n"
"```\n"
)
--
Gitblit v1.9.3