我给自己写了一个本地 Markdown 知识库:双链、全文搜索与 UI 全自定义实录

举报
yd_294377644 发表于 2026/10/23 15:35:25 2026/10/23
【摘要】 市面上的笔记软件要么强推云端、要么双链语法学习成本高、要么 UI 不合我意。我攒了一堆 Markdown 笔记却找不到顺手工具,于是自己写了一个**本地优先、支持双链回溯、全文搜索、UI 全自定义**的知识库。这篇博客记录从需求到落地的全过程:技术选型、双链实现、搜索方案、UI 定制心得,以及那些自己写一遍才明白的细节。

目录

  1. 缘起:为什么我不想要任何云端笔记软件
  2. 需求拆解:从"能记笔记"到"能找到笔记"
  3. 技术选型:浏览器 + 一个文件夹就够了
  4. 核心实现一:双链与反向引用
  5. 核心实现二:本地全文搜索
  6. UI 定制:知识库也是"自己的房间"
  7. 三个让我卡了一阵的坑
  8. 结尾:工具型软件的"长期主义"

1. 缘起:为什么我不想要任何云端笔记软件

我有几百篇 Markdown 笔记,散落在一个文件夹里——读书笔记、技术复盘、灵感片段、项目文档。它们都是我的,但我经常找不到。

试过几个主流方案,体验都让我皱眉:

  • 云端笔记要登录、要同步、要担心哪天收费策略变了;
  • 双链笔记软件功能强大,但语法学习成本高,UI 也改不动;
  • 代码编辑器 + 文件树能看,但没有"反向链接"“全文搜索”"关系图"这些知识管理该有的能力。

我的诉求其实很朴素:让一堆 Markdown 文件,像一个知识库那样能被检索、能被关联、能被我自己定义样子。既然现成的都不顺手,那就自己写一个。

2. 需求拆解:从"能记笔记"到"能找到笔记"

和写任何工具一样,先把"做一个知识库"拆成可验收清单:

诉求(人话) 拆解后(可验收)
能看笔记 选文件夹 → 列出所有 .md → 点击渲染预览
能编辑 内置编辑器,保存即写回原文件
能互链 [[笔记名]] 语法可跳转,并能查看"哪些笔记引用了当前这篇"
能搜索 全文搜索,支持高亮关键词、按修改时间排序
能看关系 简单的关系图:节点是笔记,边是引用
UI 我说了算 整体视觉风格、编辑/预览布局可自定义

这张表决定了知识库的骨架。功能不多,但每一项都做到位——这是我对所有"自己用的工具"的一贯要求。

3. 技术选型:浏览器 + 一个文件夹就够了

我犹豫过要不要上 Electron 或 Tauri,但仔细想需求——我不需要系统级文件监听、不需要托盘、不需要原生能力,浏览器 + File System Access API 完全够用:

能力 浏览器方案
选文件夹 showDirectoryPicker()(File System Access API)
读文件 file.getFile() + text()
写回 createWritable() + write()
渲染 Markdown marked 或 markdown-it
全文搜索 自建内存索引,几百篇规模毫秒级

整个知识库就是一个 HTML 文件,双击就能开。没有安装包、没有更新提示、没有后台进程——文件还是文件,软件只是"看文件的一个视角"。

心得:选型时先问"我到底需要什么",而不是"什么技术更高级"。本地小工具的最高境界是双击即用、关掉即走。

4. 核心实现一:双链与反向引用

双链的核心是两件事:正向跳转和反向引用。

正向跳转:渲染 Markdown 时,把 [[笔记名]] 替换成指向该笔记的链接:

html = html.replace(/\[\[(.+?)\]\]/g, (_, name) => {
    const target = notes.find(n => n.title === name);
    return target
        ? `<a class="wikilink" data-id="${target.id}">${name}</a>`
        : `<span class="wikilink-broken">${name}</span>`;
});

反向引用:对每篇笔记,扫描所有其他笔记里出现的 [[本篇名]],建立反向索引:

const backlinks = notes.filter(n =>
    n.content.includes(`[[${currentNote.title}]]`)
);

两个细节值得说:

  1. 断链要可视化。链接指向不存在的笔记时,用不同样式标红——一眼看出"我引用了一篇还没写的笔记",这本身就是知识管理的提示;
  2. 反向引用是知识库的灵魂。正向链接谁都会加,但"哪些笔记提到了我"才是发现隐性关联的关键,这是普通文件夹给不了的能力。

5. 核心实现二:本地全文搜索

几百篇笔记的全文搜索,不需要 Elasticsearch,一个内存索引就够:

// 建索引:每篇笔记的标题 + 正文分词后存进 Map
const index = new Map();
for (const note of notes) {
    const tokens = tokenize(note.title + ' ' + note.content);
    for (const token of tokens) {
        if (!index.has(token)) index.set(token, new Set());
        index.get(token).add(note.id);
    }
}

// 查询:取所有 token 命中的笔记求交集
function search(query) {
    const tokens = tokenize(query);
    return tokens
        .map(t => index.get(t) || new Set())
        .reduce((a, b) => new Set([...a].filter(x => b.has(x))));
}

几个让搜索"好用"的细节:

  • 标题命中权重更高,排序时优先返回标题匹配的笔记;
  • 预览片段:结果里显示关键词前后各 30 字的上下文,并高亮关键词;
  • 支持中文:中文不分词,直接按字符建索引,避免引入分词库的复杂度。

心得:搜索体验的好坏,不在"能不能搜到",而在"搜到后能不能一眼判断要不要点进去"。预览片段和高亮,比搜索算法本身更影响体验。

6. UI 定制:知识库也是"自己的房间"

本地工具最大的特权,是 UI 完全我说了算。我给自己定了几个商业软件不会这么做的设计选择:

  • 三栏布局:文件树 / 编辑器 / 预览,比例可拖拽,默认 2:3:3;
  • 关系图是入口,不是装饰。首页不是文件列表,而是笔记关系图——打开就看见"我的知识长什么样";
  • 暗色为主,跟随系统。长时间读写笔记,暗色更护眼;
  • 每篇笔记顶部显示反向引用数。一眼看出"这篇被多少其他笔记引用",是被引用多的核心笔记还是孤立片段。

这些设计在商业产品里几乎不可能出现——它们太"个人化"了。但本地工具的意义,恰恰就是让软件贴合你的思维习惯,而不是让你迁就软件的功能预设。

7. 三个让我卡了一阵的坑

坑一:File System Access API 的权限持久化。 每次刷新都要重新授权选文件夹,体验很差。解决办法是用 IndexedDB 把 directory handle 序列化存下来,下次启动直接恢复——浏览器允许这种"权限记忆",但需要用户之前授权过。

坑二:Markdown 渲染与双链的优先级。 marked 会把 [[xxx]] 里的方括号当特殊字符处理,导致双链语法被破坏。解决办法是在 Markdown 渲染之前先替换 [[xxx]] 为占位符,渲染后再替换回链接——顺序不能反。

坑三:大笔记的编辑卡顿。 一篇几千行的笔记,每次输入都重新渲染预览会卡。改成预览防抖(debounce 300ms) + 只渲染可视区域后丝滑。教训:任何"输入即渲染"的场景,防抖是必须的,不是可选的。

8. 结尾:工具型软件的"长期主义"

做完这个知识库,我对"工具型产品"有了一种更深的体会:

  1. 它和你一起成长。UI、快捷键、字段结构都能随你的思维习惯调整,软件不会"卡"在你的工作流之外;
  2. 它尊重你的数据。笔记就是硬盘里的 .md 文件,知识库只是"看它们的一个视角"——换工具也不丢数据;
  3. 它尊重你的注意力。没有推荐位、没有红点、没有"今日总结"弹窗,打开就是为了读写。

在这个所有软件都在抢用户时长的时代,一个"打开就做事、关掉就消失"的本地工具,反而成了一种奢侈。

如果你也有一堆 Markdown 笔记、也想要一个"自己的知识库",我强烈建议自己写一个。技术门槛比想象中低,一个 HTML 文件就够;真正难的是克制住加功能的冲动——不做社区、不做分享、不做"发现",就做一个老老实实的本地知识库。

工具小而准,价值往往不在多,而在"它永远在那里,永远贴合你"。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。