- 桌面应用
【免费下载链接】QOwnNotes
QOwnNotes is a plain-text file notepad and todo-list manager with Markdown support and Nextcloud / ownCloud integration.
QOwnNotes 内置了一个面向 Homepage 仪表盘的本地 HTTP 建议接口(Bookmark Suggestion API),它复用 Web Companion 浏览器扩展同款的书签解析与索引逻辑,让 Homepage 的搜索框可以直接从你本地笔记中的书签生成联想建议。本文将从启用配置、HTTP 端点协议、书签匹配算法、Homepage 配置,到仓库自带的custom.js/custom.css前端整合方案,给出完整可落地的接入教程。
功能概述:一条命令连通「本地书签」与「Homepage 搜索」
QOwnNotes 以纯文本 Markdown 文件存储笔记,而书签正是以 Markdown 链接形式存在于笔记中。Homepage Suggestion API 的核心思路是:
- QOwnNotes 在本地启动一个仅绑定
127.0.0.1的 HTTP 服务; - Homepage(或其
custom.js前端脚本)向该服务请求GET /suggest?q=<关键字>; - QOwnNotes 扫描带
bookmarks标签的笔记、名为Bookmarks的笔记以及当前打开的笔记,解析出书签后返回 JSON 建议列表; - Homepage 将建议合并进自己的搜索联想框,点击后可直接打开书签 URL,而不是交给搜索引擎。
这意味着你的 Homepage 搜索框从此拥有了「个人书签搜索引擎」能力,数据完全保留在本机,无需上传任何云端。
在 QOwnNotes 中启用 Bookmark Suggestion API
设置入口
打开 QOwnNotes 的Settings -> Browser extension / command snippets(Web Companion 设置页),按以下步骤操作:
- 勾选Enable socket server(启用 Socket 服务),这是 Suggestion API 生效的前置开关——未启用时整个浏览器扩展框架区都是灰的;
- 在Bookmark suggestion API分组框中勾选Enable Homepage-compatible bookmark suggestions API;
- 设置一个监听端口(默认为
22224); - 可选:设置一个安全令牌(Security token)。
对应界面定义可以在 webcompanionsettingswidget.ui 中看到:端口使用QSpinBox(取值范围 1~65535,默认 22224),令牌使用QLineEdit(echoMode为 Password,最大长度 32),并附带三个工具按钮:显示令牌、复制到剪贴板、重新生成令牌。设置读取/存储逻辑位于 webcompanionsettingswidget.cpp。
底层参数说明
这些设置最终映射到SettingsService的配置键,由 websocketserverservice.cpp 读取:
| 配置键 | 默认值 | 说明 |
|---|---|---|
webSocketServerService/bookmarkSuggestionApiEnabled | false | 是否启用建议 API,对应 isBookmarkSuggestionApiEnabled() |
webSocketServerService/bookmarkSuggestionApiPort | 22224(Debug 构建为22225) | 监听端口,对应 getBookmarkSuggestionApiDefaultPort() |
webSocketServerService/bookmarkSuggestionApiToken | 自动生成 32 位随机串 | 访问令牌,通过CryptoService加密存储,见 getOrGenerateBookmarkSuggestionApiToken() |
几个值得注意的实现细节:
- 仅绑定本机回环地址:HTTP 服务通过
m_pHttpServer->listen(QHostAddress::LocalHost, port)启动(startSuggestionHttpServer()),不会暴露到局域网,外部机器无法直接访问。 - 令牌自动生成与加密:保存设置时若令牌为空,会自动生成一个 32 位随机字符串;令牌在设置文件中以加密形式保存(见 webcompanionsettingswidget.cpp)。同时代码保留了对旧版明文值的兼容回退逻辑,读取时会自动迁移为加密存储。
- 服务联动:
refreshServers()会在启用开关打开时启动 HTTP 服务、关闭时停止(websocketserverservice.cpp),与 WebSocket 服务相互独立、互不影响。
HTTP 端点协议详解
请求格式
- 端点:
GET /suggest - 查询参数:
| 参数 | 必选 | 说明 |
|---|---|---|
q | 是 | 搜索关键字,URL 编码后传入 |
limit | 否 | 返回建议条数上限,默认10,最小1,最大50 |
token | 否 | 安全令牌;仅在 QOwnNotes 中配置了令牌后必须携带 |
响应格式
响应为 JSON 数组,第一个元素是原始查询字符串,第二个元素是建议字符串数组:
["home", ["Homepage", "https://example.com/home", "Some Home Link"]]该结构的序列化逻辑在 Bookmark::homepageSuggestionResponseJson() 中实现:数组第一个元素为查询词,第二个元素为建议列表。
错误状态码与请求处理流程
请求处理在 processHttpRequest() 中完成,顺序如下:
- 请求行解析失败 → 返回
400 Bad Request; - 非
GET方法 → 返回405 Method Not Allowed; - 路径不是
/suggest→ 返回404 Not Found; token参数与配置的令牌不一致 → 返回401 Unauthorized(未配置令牌时则跳过该校验);limit参数非法时回退为10,超出 1~50 范围时被强制收敛到边界;- 正常时返回
200 OK与 JSON 响应体。
此外,所有响应都带有 CORS 头(Access-Control-Allow-Origin: *)、Cache-Control: no-store与Connection: close(见 httpResponse()),方便浏览器端脚本跨域请求。
性能优化:双层缓存
服务端实现了两级缓存(homepageSuggestionResponse()):
- 书签缓存:书签列表缓存 2500ms(2.5 秒)后失效重建,避免每次请求都重新解析所有笔记;
- 建议结果缓存:以
查询词|limit为键缓存生成好的 JSON 响应,缓存上限 200 条,超出后按插入顺序淘汰最旧条目。
书签来源与匹配算法(源码级解析)
书签从哪里来
getBookmarksForSuggestions()(websocketserverservice.cpp)按以下顺序汇总书签:
- 所有带有
bookmarks标签的笔记(标签名可在设置中修改,默认bookmarks,见 getBookmarksTag()); - 所有名为
Bookmarks的笔记(笔记名同样可配置,默认Bookmarks,见 getBookmarksNoteName()); - 当前正在编辑的笔记中的所有链接。
汇总前会做去重合并(相同 URL 的书签合并标签、名称与描述),然后整体缓存。
书签如何解析
Bookmark::parseBookmarks() 使用正则解析笔记文本,支持三类 Markdown 链接:
- 标准书签格式:
- [名称](http://链接) #标签1 #标签2 描述文字(行首为-或*); - 普通命名链接:
[名称](http://链接); - 尖括号链接:
<http://链接>。
其中 URL 必须以协议://形式出现(如https://、http://)。
建议匹配与排序
Bookmark::suggestionStrings() 实现了完整的匹配算法:
- 大小写不敏感:查询词与书签的名称、URL、标签、描述、原始 Markdown 文本统一转为小写后比较;
- 多词 token 匹配:查询词按空格拆分为多个 token,书签必须包含所有token 才算命中(token 顺序无关);
- 前缀优先排序:名称或 URL 以查询词/任一 token 开头的书签排在前面,其次才是仅包含子串的书签;
- 输出配对:命中书签会先输出名称、再输出 URL(测试用例 testBookmarkSuggestionsKeepNameUrlPairing 明确验证了「名称索引 < URL 索引」的顺序),这正是
custom.js建立「名称→URL」映射的前提; - 去重:大小写折叠后相同的建议只保留一条(testBookmarkSuggestionsDeduplication)。
仓库单元测试 test_notes.cpp 覆盖了前缀/子串匹配、去重、多 token 顺序无关、名称-URL 配对、元数据(标签/描述)参与匹配、空查询、limit 收敛(limit=100时强制为 50)以及响应 JSON 结构等全部关键行为,可作为你理解算法边界的参考。
配置 Homepage:让搜索框接收本地建议
搜索配置示例
在 Homepage 的配置中,为search块指定provider: custom,并将suggestionUrl指向本地 QOwnNotes 建议接口:
search: provider: custom url: https://example.com/search?q= suggestionUrl: http://127.0.0.1:22224/suggest?q= showSearchSuggestions: trueprovider: custom:告诉 Homepage 使用自定义搜索提供方;suggestionUrl:Homepage 会向该地址追加用户输入的查询词并发起联想请求,showSearchSuggestions: true启用联想框显示;- 若你在 QOwnNotes 中配置了安全令牌,请保持
suggestionUrl不变,改由custom.js中的QON_TOKEN变量在请求时自动追加&token=...,见下文。
需要修改哪个 Homepage 文件
在标准 Homepage 安装中,将search块加入以下任一文件:
settings.yaml(Homepage 全局设置)settings.yml(同上,取决于你的安装命名)- 容器中挂载为
/app/config/settings.yaml的文件
如果你的 Homepage 部署把配置拆分到多个文件,请编辑已经包含searchprovider 设置的那个文件,而不要新建重复配置。
自定义 Homepage 前端资源:合并建议与直接打开书签
仅配置suggestionUrl时,Homepage 会把建议当作普通搜索词。若希望建议直接打开书签 URL(而非跳转搜索引擎),需要使用仓库自带的定制前端资源。
资源位置
仓库在 docs/homepage 目录下提供了现成的定制示例:
- custom.js:核心整合脚本;
- custom.css:为 QOwnNotes 建议条目提供视觉区分样式;
- README.md:整合说明文档。
当你的 Homepage 部署支持加载custom.js与custom.css时,将这两个文件放入对应目录即可。
custom.js 的工作原理
custom.js是一个立即执行的前端脚本,逐条完成了以下工作(对应 docs/homepage/README.md 的说明):
- 拦截建议请求:重写
window.fetch,拦截 Homepage 的/api/search/searchSuggestion端点请求; - 合并建议:额外向 QOwnNotes 的
http://127.0.0.1:22224/suggest?q=<查询>&limit=<QON_LIMIT>发起请求,并把返回的建议与 Homepage 原生建议合并(去重后返回,QOwnNotes 建议排前); - 标记来源:为每条 QOwnNotes 建议添加
[QON]前缀(QON_LABEL),在界面上一眼可辨; - 建立名称→URL 映射:将「名称 + URL」成对出现的建议存入
suggestionToUrl映射表; - 拦截新窗口打开:重写
window.open,识别搜索引擎跳转 URL 并解码出原始书签地址,若命中映射表则直接以书签 URL 打开新标签页,避免把书签文本交给搜索引擎;带[QON]前缀的文本会被彻底拦截,绝不外泄给搜索服务; - 破除 4 条截断:通过改写
Response.prototype.json,拦截 Homepage 对建议列表「前 4 条」的客户端裁剪逻辑,让合并后的完整建议列表得以展示; - DOM 标记:通过
MutationObserver为匹配行添加qon-suggestionCSS 类,并处理键盘导航、滚动定位、URL 提示行等细节。
可调参数
custom.js顶部的常量即全部配置开关:
| 常量 | 默认值 | 说明 |
|---|---|---|
QON_URL | http://127.0.0.1:22224/suggest?q= | QOwnNotes 建议接口地址 |
QON_TOKEN | "" | 与 QOwnNotes 设置中相同的安全令牌;配置后请求会自动追加&token=... |
QON_LIMIT | 20 | 向 QOwnNotes 请求的建议条数(上限受服务端 50 限制) |
QON_LABEL | "[QON] " | 显示在建议前的标记文本 |
DEBUG | false | 设为true可输出[QON-MERGE]调试日志 |
custom.css 的样式效果
custom.css 为button.qon-suggestion添加绿色(#2f855a)左边框和文字颜色,并针对映射到 URL 的建议行(li.qon-li-has-url)在下方渲染一个小字号 URL 提示(.qon-url-hint),方便你确认即将打开的地址。
外部主机部署时的注意点
如果你的 Homepage 运行在与 QOwnNotes 不同的机器/容器上,必须:
- 将
custom.js与custom.css部署到承载 Homepage 的那台主机上,它才能跨机查询 QOwnNotes 的建议接口; - 使用 QOwnNotes 安全令牌时,确保
custom.js内的QON_TOKEN与 QOwnNotes 设置中的令牌一致。
安全边界与生产建议
由于书签数据可能包含敏感 URL,官方文档明确提示了以下安全边界:
- 默认仅限本机访问:服务只绑定
127.0.0.1,外部网络无法直接触达; - 必须使用安全令牌:令牌校验失败返回
401,未配置令牌时接口对所有本机进程开放;建议始终配置令牌; - 如需跨机访问:应在 QOwnNotes 之前放置反向代理,并用防火墙规则或 IP 白名单限制访问来源,不要直接将端口暴露到公网;
- 令牌加密存储:令牌通过
CryptoService加密后写入设置,避免明文落盘(相关键也被纳入 misc.cpp 的敏感键清单)。
验证与排错
启用并配置完成后,你可以直接在本机用curl验证接口:
# 不带令牌 curl "http://127.0.0.1:22224/suggest?q=home&limit=10" # 带令牌(替换为你的实际令牌) curl "http://127.0.0.1:22224/suggest?q=home&limit=10&token=YOUR_TOKEN"期望返回形如["home",["Homepage","https://example.com/home"]]的 JSON。若返回401,请核对custom.js中的QON_TOKEN与 QOwnNotes 设置中的令牌是否一致;若返回404,请确认请求路径为/suggest且 QOwnNotes 的启用开关与 Socket server 开关均已打开。更新custom.js/custom.css后,请对 Homepage 页面执行硬刷新,确保浏览器加载新版本脚本。
至此,你的 Homepage 搜索框已经与本地书签笔记打通:输入关键字即可获得来自 QOwnNotes 笔记的个人化联想建议,并直接打开对应书签,全程数据不出本机。
- 桌面应用
【免费下载链接】QOwnNotes
QOwnNotes is a plain-text file notepad and todo-list manager with Markdown support and Nextcloud / ownCloud integration.
相关推荐
QOwnNotes Homepage 书签建议 API:为 Homepage 搜索框接入本地书签自动补全
QOwnNotes Homepage 书签建议 API:为 Homepage 搜索框接入本地书签自动补全 QOwnNotes 提供了一个本地 HTTP 端点,把
桌面应用QOwnNotes Homepage 书签建议 API 接入指南:将本地书签注入 Homepage 搜索建议
QOwnNotes Homepage 书签建议 API 接入指南:将本地书签注入 Homepage 搜索建议 QOwnNotes 内置了一个仅绑定本机回环地址的
桌面应用QOwnNotes Homepage 书签建议 API 实战:把本地书签接入 Homepage 搜索建议
QOwnNotes Homepage 书签建议 API 实战:把本地书签接入 Homepage 搜索建议 QOwnNotes 除了常见的笔记与 Markdown
桌面应用
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考