| | |
| | | 扫描范围(用户指定) |
| | | -------------------- |
| | | - 顶层黑名单 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 同时抽 frontmatter(tags + 关键词字段)与正文 #tag |
| | | - markdown 只抽标准 `tags:` frontmatter 与正文 #tag;topic/category/keywords/type 等 |
| | | 元数据字段绝不当作标签(避免 copilot 对话、读书笔记的元数据被误建页) |
| | | |
| | | 幂等 & 安全 |
| | | ---------- |
| | |
| | | TAG_RE = re.compile(r"(?<![\w/])#([A-Za-z0-9_\u4e00-\u9fff][\w\u4e00-\u9fff/\-]*)") |
| | | SCAN_EXT = {".md", ".markdown", ".excalidraw", ".canvas"} |
| | | EXCLUDE_TOP = {"X2.Archived", "00index", "X1.Knomo", "X.Attachment", "P3.bobo"} |
| | | # 嵌套路径排除(只排除指定子树,不影响其同级目录)。 |
| | | # 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"} |
| | | |
| | | FM_KEYWORD_KEYS = { |
| | | "aliases", "alias", "keywords", "keyword", "topic", "topics", |
| | | "category", "categories", "subject", "area", "areaOfFocus", |
| | | "people", "person", "project", "projects", "type", "tags", |
| | | } |
| | | # 只把标准 Obsidian 标签字段 `tags:` 当作标签;不再把 topic/category/subject/ |
| | | # type/keywords/aliases 等元数据字段误读为标签(否则 copilot 对话的 topic:、 |
| | | # 读书笔记的 type: 等会被当成标签疯狂建页)。行内 #标签 仍正常扫描。 |
| | | FM_KEYWORD_KEYS = {"tags"} |
| | | |
| | | |
| | | def split_fm_value(v) -> list: |
| | |
| | | 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("```"): |
| | |
| | | "> 本页由 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" |
| | | ) |
| | |
| | | all_tags = {} |
| | | for root, dirs, files in os.walk(vault): |
| | | dirs[:] = [d for d in dirs if d not in EXCLUDE_DIRS and not d.startswith(".")] |
| | | top = os.path.relpath(root, vault).split(os.sep)[0] |
| | | rel = os.path.relpath(root, vault) |
| | | top = rel.split(os.sep)[0] |
| | | if top in EXCLUDE_TOP: |
| | | dirs[:] = [] |
| | | continue |
| | | # 嵌套路径排除(如 02DS/02copilot) |
| | | if any(rel == p or rel.startswith(p + os.sep) for p in EXCLUDE_PATHS): |
| | | dirs[:] = [] |
| | | continue |
| | | for fn in files: |
| | | if fn.startswith("."): |
| | | continue |