【已解决】关于新建笔记时如何设置弹窗要求输入笔记名?


遇到的问题

我使用了插件Template创建了简单的模板,如下

---
title: "<% tp.file.title %>"
date: "<% tp.date.now("YYYY-MM-DD HH:mm") %>"
author: Rekutosei
tags: 

---

# "<% tp.file.title %>"

但是目前我使用Ob新建笔记时
它默认帮我使用了“未命名”、“未命名1”、“未命名2”这些笔记名
导致title属性值派不上用场。

后来我看到网友的图:

Ta在新建笔记时,Ob会先要求输入笔记名称。

这与我的需求刚好符合,但是我不知道如何设置才能做到像Ta这样

嗯,虽然看上去有点偏题,不过也许你可以看看这个?可以用一个快捷键直接同步这三个位置的标题 新插件:Obsidian File Title Updater,在文件名、前置属性(frontmatter)和笔记的一级标题之间同步标题

1 个赞

谢谢你。

这个插件我尝试过了,不是很方便,它需要创建笔记文件后,再使用命令执行这个插件来统一“文件名、title属性、一级标题”


不过就在刚才我找到解决办法了
CSDN找到的攻略
但是这篇文章被锁VIP了,游客看不了


大致原理还是利用Template插件,在模板开头插入一段JS
提示用户输入一个文件名,并将当前文件重命名为用户输入的名字

---
<%* const newFileName = await tp.system.prompt("Enter a name for this file:"); if (newFileName) { await tp.file.rename(newFileName); } %>
title:  "<% newFileName %>"
date: "<% tp.date.now("YYYY-MM-DD HH:mm") %>"
author: Rekutosei
tags: 

---

# <% newFileName %>

我写了一个,你可以看一下,可能有些个性化的文字需要改一下

---
<%*
/* ========== 配置区 - 可扩展的常量配置 ========== */
const CONFIG = {
    AUTHOR: "lspzc",
    BASE_FOLDER: "/md",
    EXCLUDE_FOLDERS: new Set(["papers", "attachments"]),
    FILE_EXT: ".md"
};

/* ========== 核心功能函数 ========== */

/* 获取分层文件夹 */
async function getHierarchicalFolders() {
    let currentPath = CONFIG.BASE_FOLDER;
    let finalPath = "";

    while (true) {
        const { folders } = await app.vault.adapter.list(currentPath);
        const validFolders = folders
          .map(fullPath => fullPath.split("/").pop())
          .filter(folder => !CONFIG.EXCLUDE_FOLDERS.has(folder.toLowerCase()));
        
        const options = [];
        if (currentPath !== CONFIG.BASE_FOLDER) options.push("🔙");
        options.push(...validFolders, "✔️");
        
        const chosen = await tp.system.suggester(
          options,
          options,
          false,
          `选择目录 📁 当前路径: ${currentPath.replace(CONFIG.BASE_FOLDER, "根目录")}`
        );
        
        if (!chosen) return null;
        
        if (chosen === "✔️") {
          finalPath = currentPath.replace(CONFIG.BASE_FOLDER + "/", "");
          break;
        } else if (chosen === "🔙") {
          currentPath = currentPath.split("/").slice(0, -1).join("/");
        } else {
          currentPath = `${currentPath}/${chosen}`;
        }
    }
  return finalPath;
}

/* 判读文件是否存在 */
async function isFileExists(folderPath, fileName) {
  const fullPath = `${CONFIG.BASE_FOLDER}/${folderPath}/${fileName}${CONFIG.FILE_EXT}`.replace(/\/+/g, "/");
  return await app.vault.adapter.exists(fullPath);
}

/* 获取唯一文件名 */
async function getUniqueFileName(chosenFolder) {
  while (true) {
    const titleName = await tp.system.prompt("请输入笔记标题 ✏️", "未命名笔记");
    if (!titleName) return false;
    if (!(await isFileExists(chosenFolder, titleName))) return titleName;
    new Notice(`⚠️ "${titleName}" 已存在,请换用其他名称`);
  }
}

/* 新增文件夹流程 */
async function createNewFolder() {
  const basePath = await getHierarchicalFolders();
  if (!basePath) return null;

  while (true) {
    const folderName = await tp.system.prompt("请输入新文件夹名称 📁", "新文件夹");
    if (!folderName) return null;

    const fullPath = `${CONFIG.BASE_FOLDER}/${basePath}/${folderName}`.replace(/\/+/g, "/");
    
    if (!(await app.vault.adapter.exists(fullPath))) {
      await app.vault.createFolder(fullPath);
      return `${basePath}/${folderName}`;
    }
    new Notice(`⚠️ 文件夹 "${folderName}" 已存在,请换用其他名称`);
  }
}

/* 主流程 */
try {
    // 判断使用现有目录还是新建目录
    let isNewFolder;
    try {
        isNewFolder = await tp.system.suggester(
        ["使用现有目录", "创建新文件夹"],
        [false, true],
        true,
        "选择文件夹操作 🗃️"
        );
    } catch (error) {
        new Notice("🛑 操作已取消");
        return;
    }
    
    // 获取新建笔记的路径
    let chosenFolder;
    if (isNewFolder) {
        // 新建目录,调用新建目录方法
        chosenFolder = await createNewFolder();
    } else {
        // 选择原有目录,调用获取分层文件夹方法
        chosenFolder = await getHierarchicalFolders();
    }
    if (!chosenFolder) {
        new Notice("🛑 操作已取消");
        return;
    }
    
    // 获取唯一笔记标题
    const titleName = await getUniqueFileName(chosenFolder);
    if (!titleName) {
        new Notice("🛑 操作已取消");
        return;
    }
    
    // 获取笔记属性
    try {
        // 收集Frontmatter
        // 笔记状态,如果用户取消输入则退出
        const state = await tp.system.suggester(
          ["进行中","待办/未开始","持续更新","已完成"],
          ["doing","todo","update","done"],
          true,
          "选择笔记状态 🔄"
        );
        // 是否添加标题序号,如果用户取消输入则退出
        const isAdd = await tp.system.suggester(
            ["添加标题编号", "不添加编号"],
            [true, false],
            true,
            "标题编号设置 🔢"
        );
        
        // 构建 Frontmatter
        tp.frontmatter = { 
        author: CONFIG.AUTHOR,
        tags: "",
        state,
        ...(isAdd && { "number headings": "auto, first-level 1, max 6, _.1.1" })
        };
    } catch (error) {
        new Notice("🛑 操作已取消");
        return;
    }
    
    // 移动文件
    await tp.file.move(`${CONFIG.BASE_FOLDER}/${chosenFolder}/${titleName}`);
} catch (error) {
    new Notice(`❌ 操作异常: ${error.message}`);
}
-%>
author: <% tp.frontmatter.author %>
tags: <% tp.frontmatter.tags %>  
state: <% tp.frontmatter.state %>  
<%* if (tp.frontmatter["number headings"]) { -%>
number headings: <% tp.frontmatter["number headings"] %>
<%* } -%>
---

<% tp.file.cursor() %>
1 个赞

感谢你提供如此完整的解决方案 :+1:

你的方案确实非常强大,功能很丰富,比如路径选择、唯一性检查等等,还有很强的可配置性,看得出来是下了很大功夫的。

不过对于我这种刚接触 Template 插件的新手来说,理解起来还是有点挑战 :joy:

我觉得可以把两种方式结合起来:日常记录用我上面提到的简单版提高效率,正式写文档时用你的高级流程来规范管理。

再次感谢你的贡献!