panatgithub_AnkiHeadingSync/docs/2026-04-18PLAN5.md
Dusk 960b87103d feat(deck): add configurable deck resolution modes
中文: 新增 deck 模式选择、文件级 deck 解析、文件夹映射、模板插入与 warning 汇总

English: Add deck mode selection, file-level deck parsing, folder mapping, template insertion, and warning aggregation
2026-04-18 20:14:29 +08:00

7.8 KiB
Raw Blame History

模块 5Deck 模式选择与文件级 / 文件夹级 Deck 解析

Summary

本模块的目标是把 deck 相关功能做成“用户可选择、可理解、可插入模板”的产品形态,而不是隐式启用多套 deck 策略。

用户看到的 deck 体系固定为三层:

  1. 始终存在 defaultDeck,作为兜底方案
  2. 可选开启“文件级自定义牌组”
  3. 可选开启一种高级文件夹映射模式

最终优先级固定为:

文件级显式 deck > 文件夹映射 deck > defaultDeck

本轮按最新确认的决策执行:

  • 旧卡 deck 行为:执行命令时按最新代码规则执行
  • YAML key与用户输入的识别名一致默认值为 TARGET DECK
  • 默认模板:obsidian::filename
  • 模板变量:只支持裸变量 filename

需求描述

1. 默认模式:只使用 defaultDeck

  • 插件始终有 defaultDeck
  • 在其它 deck 功能都未开启时,所有卡都进入 defaultDeck
  • 这是最简单的兜底方案

2. 基本选项:开启文件级自定义牌组

开启后,设置页提供以下配置:

  • 牌组识别名
    • 默认值:TARGET DECK
    • 同时作用于 YAML key 和正文标记
  • 默认牌组模板
    • 默认值:obsidian::filename
  • 插入位置
    • 单选:YAML正文

开启后插件具备两种能力:

  1. 解析已有文件级 deck 声明
  2. 提供“向当前文件插入 deck 模板”的操作

3. 高级选项:开启文件夹映射

高级选项是互斥单选,固定三态:

  • 关闭
  • 文件夹级映射
  • 文件夹及文件名级映射

语义固定为:

  • 关闭
    • 不启用文件夹映射
  • 文件夹级映射
    • 例:数学/第一章/第一节.md -> 数学::第一章
  • 文件夹及文件名级映射
    • 例:数学/第一章/第一节.md -> 数学::第一章::第一节

设置页必须显示这两种模式的说明和示例。

Key Changes

配置与设置页

新增或调整 PluginSettings

  • defaultDeck: string
  • fileDeckEnabled: boolean
  • fileDeckMarker: string
    • 默认 TARGET DECK
  • fileDeckTemplate: string
    • 默认 obsidian::filename
  • fileDeckInsertLocation: "yaml" | "body"
  • folderDeckMode: "off" | "folder" | "folder-and-file"

defaultDeck 继续必填。

设置页固定展示四块内容:

  1. Default Deck
    • 保留现有输入框
    • 继续作为必填兜底 deck
  2. 文件级自定义牌组
    • 开关
    • 识别名输入框
    • 默认牌组模板输入框
    • 插入位置单选
    • “向当前文件插入 deck 模板”的操作入口
  3. 高级:文件夹映射
    • 单选:关闭 / 文件夹级映射 / 文件夹及文件名级映射
    • 带说明文案和示例
  4. 最终优先级说明
    • 明文显示:文件级 > 文件夹映射 > 默认 deck

文件级 deck 解析规则

文件级显式 deck 支持两类来源:

  • YAML
  • 正文

两类来源统一使用用户设置的 fileDeckMarker 作为识别名。默认情况下:

  • YAML keyTARGET DECK
  • 正文标记:TARGET DECK

正文支持两种语法:

TARGET DECK
数学::第一章
TARGET DECK: 数学::第一章

YAML 在默认情况下写作:

TARGET DECK: 数学::第一章

规则固定为:

  • YAML key 与正文 marker 使用同一个用户配置值
  • 正文只取第一个合法声明
  • fenced code block 内的正文 deck 声明必须忽略
  • YAML 与正文同时存在且不同:
    • 继续同步
    • 以 YAML 为准
    • 产出结构化 warning

模板插入规则

新增“向当前文件插入 deck 模板”的操作。

模板规则固定:

  • 默认模板:obsidian::filename
  • 第一版只支持一个变量:filename
  • filename 展开为当前文件名,不含 .md

插入行为固定:

  • 当插入位置是 yaml
    • 在 frontmatter 中写入 <fileDeckMarker>: <展开后的模板值>
    • 若没有 frontmatter则创建 frontmatter
  • 当插入位置是 body
    • 在正文靠前位置插入单行声明
    • 格式:<fileDeckMarker>: <展开后的模板值>

本轮不支持更多模板 DSL不支持 [[filename]]

文件夹映射规则

文件夹映射只在 folderDeckMode !== "off" 时参与解析。

  • folder
    • 仅使用父文件夹路径
  • folder-and-file
    • 使用父文件夹路径 + 当前文件名

规范化规则固定:

  • / 转成 ::
  • trim 首尾空格
  • 去掉空层级
  • 结果为空则视为无效
  • 文件夹名或文件名中若包含 ::,产出 warning并放弃该映射结果
  • 放弃后回退到 defaultDeck

最终解析规则

最终顺序固定:

  1. 文件级显式 deck
  2. 文件夹映射 deck
  3. defaultDeck

附加规则:

  • 文件级 deck 关闭时:
    • 不解析 YAML
    • 不解析正文
    • 只走文件夹映射或默认 deck
  • 文件夹映射关闭时:
    • 只走文件级或默认 deck

同步语义

本轮按最新决策执行:旧卡按现有代码规则执行。

这意味着:

  • toCreate
    • 使用最终 resolvedDeck
    • 若 deck 不存在,先 ensureDecks
    • addNotes
  • toUpdate
    • 保持当前实现行为
    • deck 是否触发 update / 是否触发 changeDeck,按现有代码规则保留
  • 本模块不额外改变旧卡 deck 迁移逻辑
  • 本模块的重点是新增 deck 配置、解析与模板插入能力

当前主链路接入点

本轮必须基于当前 manual-sync 主链路,不落到旧的 legacy 链路:

  1. CardIndexingService
    • 提取文件级 deck 原始线索
  2. RenderConfigService 或新增的 DeckResolutionService
    • 解析最终 resolvedDeck
  3. DiffPlannerService
    • 保持当前旧卡 deck 行为,不在本模块内重写
  4. ManualSyncService
    • 汇总 warnings
  5. AnkiBatchExecutor
    • 新卡使用 resolvedDeck

Warning 结构

新增结构化 warning

  • deck_conflict_yaml_body
  • deck_multiple_body_declarations
  • deck_invalid_folder_segment
  • deck_fallback_default

结果对象中必须包含:

  • warnings: DeckResolutionWarning[]

UI 层可以额外展示 notice但 warning 不能只存在于 notice。

Test Plan

设置与模板

  • 默认加载时:
    • fileDeckEnabled = false
    • fileDeckMarker = "TARGET DECK"
    • fileDeckTemplate = "obsidian::filename"
    • fileDeckInsertLocation = "body"
    • folderDeckMode = "off"
  • 设置页能正确保存和回显上述字段
  • 模板插入时:
    • YAML 模式能写入 <marker>: <deck>
    • 正文模式能写入 <marker>: <deck>
    • filename 正确展开

文件级 deck 解析

  • 只存在 YAML 时正确解析
  • 只存在正文单行语法时正确解析
  • 只存在正文双行语法时正确解析
  • marker 改名后YAML 和正文都按新 marker 识别
  • fenced code block 内正文声明被忽略
  • 多个正文声明时只取第一个并 warning
  • YAML 与正文冲突时取 YAML 并 warning

文件夹映射

  • folder 模式下:
    • 数学/第一章/第一节.md -> 数学::第一章
  • folder-and-file 模式下:
    • 数学/第一章/第一节.md -> 数学::第一章::第一节
  • 根目录文件回退到 defaultDeck
  • 路径片段包含 :: 时 warning并回退到 default

最终解析优先级

  • 文件级 deck 覆盖文件夹映射和 default
  • 无文件级时,文件夹映射覆盖 default
  • 文件级关闭时,只走文件夹映射或 default
  • 文件夹映射关闭时,只走文件级或 default

同步链路

  • 新卡创建使用 resolvedDeck
  • deck 不存在时先 ensureDecks
  • warnings 能沿链路出现在最终 sync result
  • 旧卡行为不被本模块额外改变,保持当前实现回归通过

Assumptions

  • 本轮是 deck 配置与解析能力增强,不重构旧卡 deck 更新语义。
  • YAML key 与正文 marker 使用同一个用户配置值。
  • 模板语法只支持裸变量 filename
  • 文件夹映射模式互斥单选。
  • 不实现 folder->deck 手工映射表,不实现多模板 DSL不实现标题级 deck 覆盖。