public: true TODO: true edit:

  • 2026-01-09

1. 准备

  • 私有仓库private
  • 公开仓库publicundefined
  • 发布脚本deploy.py
  • obsidian插件shell commander

2. 准备工作

  • 将deploy.py脚本放在public仓库根目录下
import os
import shutil
import subprocess
import sys       # <--- 确保导入了 sys
import json
import frontmatter

# =======================================================
# 👇【新增】加入这行代码,强制让 Python 输出 UTF-8,解决 emoji 报错
sys.stdout.reconfigure(encoding='utf-8')
# =======================================================

# ================= ⚙️ 配置区域 =================

# 1. 私有仓库(源)
PRIVATE_VAULT_PATH = r"D:\obsidian-gitsync\workspace"

# 2. 公开仓库(目标)
PUBLISH_REPO_PATH = r"D:\obsidian-gitsync\publish"

# 3. 文章子目录
TARGET_SUBDIR = "publish"

# 4. 记录同步状态的清单文件(存放在 Publish 仓库根目录,不会被发布)
MANIFEST_FILE = ".sync_manifest.json"

# ===============================================

def is_public(file_path):
    """读取文件 YAML Header"""
    try:
        post = frontmatter.load(file_path)
        return post.get('public') is True
    except Exception:
        return False

def load_manifest():
    """读取上次同步的文件列表"""
    manifest_path = os.path.join(PUBLISH_REPO_PATH, MANIFEST_FILE)
    if os.path.exists(manifest_path):
        try:
            with open(manifest_path, 'r', encoding='utf-8') as f:
                return set(json.load(f))
        except:
            return set()
    return set()

def save_manifest(file_list):
    """保存本次同步的文件列表"""
    manifest_path = os.path.join(PUBLISH_REPO_PATH, MANIFEST_FILE)
    with open(manifest_path, 'w', encoding='utf-8') as f:
        json.dump(list(file_list), f, indent=2, ensure_ascii=False)

def sync_files_safely():
    dest_base_dir = os.path.join(PUBLISH_REPO_PATH, TARGET_SUBDIR)
    
    # 1. 获取当前所有需要同步的文件 (Current State)
    # 存储的是相对于 TARGET_SUBDIR 的路径
    current_sync_files = set()
    
    print("📥 扫描私有仓库中标记为 Public 的文件...")
    
    # 临时字典用于存储源文件路径,方便后续复制
    # Key: 相对路径, Value: 绝对源路径
    files_to_copy = {}

    for root, dirs, files in os.walk(PRIVATE_VAULT_PATH):
        dirs[:] = [d for d in dirs if not d.startswith('.')]
        for file in files:
            if file.endswith(".md"):
                source_abs_path = os.path.join(root, file)
                if is_public(source_abs_path):
                    # 计算相对结构路径
                    rel_path = os.path.relpath(source_abs_path, PRIVATE_VAULT_PATH)
                    current_sync_files.add(rel_path)
                    files_to_copy[rel_path] = source_abs_path

    # 2. 读取上次的清单 (Previous State)
    previous_sync_files = load_manifest()

    # 3. 计算需要删除的文件 (上次有,这次没有的)
    files_to_delete = previous_sync_files - current_sync_files
    
    # 4. 执行删除 (只删除脚本自己产生过的旧文件)
    if files_to_delete:
        print(f"🧹 检测到 {len(files_to_delete)} 个文件需要被移除...")
        for rel_path in files_to_delete:
            full_path_to_delete = os.path.join(dest_base_dir, rel_path)
            if os.path.exists(full_path_to_delete):
                try:
                    os.remove(full_path_to_delete)
                    print(f"   ❌ 已移除旧文件: {rel_path}")
                except OSError as e:
                    print(f"   ⚠️ 移除失败: {rel_path}, {e}")
            
            # 尝试清理空文件夹 (可选)
            # 如果删除了文件导致文件夹为空,顺手删掉文件夹
            parent_dir = os.path.dirname(full_path_to_delete)
            if os.path.exists(parent_dir) and not os.listdir(parent_dir):
                try:
                    os.rmdir(parent_dir)
                    print(f"   📂 移除空目录: {parent_dir}")
                except:
                    pass

    # 5. 执行复制/更新 (覆盖写入)
    print(f"🚀 开始同步 {len(current_sync_files)} 个文件...")
    for rel_path, src_path in files_to_copy.items():
        dest_path = os.path.join(dest_base_dir, rel_path)
        
        # 确保目标目录存在
        os.makedirs(os.path.dirname(dest_path), exist_ok=True)
        
        # 复制文件
        shutil.copy2(src_path, dest_path)
        # print(f"   ✅ 同步: {rel_path}") # 日志太长可以注释掉

    # 6. 保存新的清单
    save_manifest(current_sync_files)
    print("💾 同步清单已更新。")
    
    return len(current_sync_files) + len(files_to_delete)

def git_push():
    # ... (Git 推送部分代码保持不变) ...
    print("\n🚀 正在检查 Git 状态...")
    os.chdir(PUBLISH_REPO_PATH)
    try:
        subprocess.run(["git", "add", "."], check=True)
        status = subprocess.run(["git", "status", "--porcelain"], capture_output=True, text=True)
        if not status.stdout.strip():
            print("☕️ 内容无变化,无需推送。")
            return
        subprocess.run(["git", "commit", "-m", "Auto deploy from Private Vault"], check=True)
        subprocess.run(["git", "push"], check=True)
        print("🌟 发布成功!")
    except subprocess.CalledProcessError as e:
        print(f"❌ Git 操作出错: {e}")

if __name__ == "__main__":
    if sync_files_safely() > 0:
        git_push()
    else:
        # 即使没有文件变动,如果有文件被删除了,sync_files_safely 也会返回 > 0
        # 只有在完全没有任何 Public 文件且没有删除操作时,才会到这里
        print("🔍 扫描完毕,未检测到变动。")
  • 在obsidian中安装第三方插件shell commander
  • 根据自己电脑上的路径添加py脚本和shell指令
    "C:\Users\xxx\AppData\Local\Programs\Python\Python313\python.exe" "D:\path\to\valut\deploy.py"
    

3. 工作流

  1. 私有仓库中编辑笔记 在yaml中添加public:true的标记
  2. 运行shell指令

public: true edit:

  • 2026-01-09

#1. 准备工作

  1. 图床管理软件
    • Picgo
    • Piclist(二次开发,功能更全)
  2. 图床存储
    • Github
    • Cloudfire R2

2. 流程

2.1. 图床管理

#TODO

2.2. 图床存储

2.2.1. Github

创建一个==public==仓库,

2.2.2. Cloudfire R2


tags: TODO: public: true

1. 准备工作

  • 安装并启用 Templater 插件。
  • 在库中创建一个文件夹(例如 _scripts)。
  • 设置 -> Templater -> Script files folder location 中,选中该文件夹。

2. 创建脚本

Scripts 文件夹中新建文件 add_mod_date.js,复制以下代码:

/**
 * 逻辑:如果今天的日期不在文件的 mod_history 属性中,则追加进去。
 */
async function add_mod_date(tp) {
    const file = tp.file.find_tfile(tp.file.path(true));
    const today = moment().format("YYYY-MM-DD");
    const propName = "edit"; 

    await tp.app.fileManager.processFrontMatter(file, (frontmatter) => {
        // 初始化或规范化数组
        if (frontmatter[propName] === undefined) {
            frontmatter[propName] = [];
        }
        if (!Array.isArray(frontmatter[propName])) {
            frontmatter[propName] = frontmatter[propName] ? [frontmatter[propName]] : [];
        }
        // 去重添加
        if (!frontmatter[propName].includes(today)) {
            frontmatter[propName].push(today);
            frontmatter[propName].sort(); // 可选:排序
            new Notice(`已记录修改日期: ${today}`);
        } else {
            // new Notice(`今天已记录过`); // 可选:静默模式可注释此行
        }
    });
}
module.exports = add_mod_date;

3. 创建调用模版

在你的模版文件夹中新建一个笔记(如 工具-更新修改历史.md),写入:

<% tp.user.add_mod_date(tp) %>

4. 绑定快捷键 (关键步骤)

由于模版默认不显示在快捷键列表,需先注册:

  1. 打开 设置 -> Templater
  2. 找到 Template Hotkeys 区域。
  3. 点击 Add new hotkey for template,选择刚才创建的 工具-更新修改历史.md,点击 + 号。
  4. 转到 设置 -> 快捷键 (Hotkeys)
  5. 搜索 Templater: Insert template,找到刚才注册的条目,设置快捷键(如 Alt+S)。

5. Dataview 汇总 (用于日记)

在你的 日记模版 中加入以下查询,即可自动列出历史记录包含该日记日期的文件:

TABLE file.mtime as "最后修改时间"
FROM ""
WHERE contains(mod_history, string(this.file.day))
AND file.name != this.file.name
SORT file.mtime desc

6. 使用流程

  1. 日常修改:修改某文件后,按下快捷键(Alt+S)。
  2. 自动记录:文件的 YAML 属性 mod_history 会自动增加今天的日期(如 2026-01-09)。
  3. 日记回顾:打开 2026-01-09 的日记,Dataview 会自动抓取所有在该日期打过卡的文件。