Trilium 附件上传插件(nas_upload.js)功能说明与配置指南※
适用版本:TriliumNext 0.105.0(桌面版 Electron 验证通过,浏览器/服务器模式亦兼容) 用途:把笔记中拖入/粘贴的文件改存到第三方存储(飞牛 NAS、OpenList/AList、群晖 WebDAV 等),笔记内只保留可点击链接,避免大附件撑爆 Trilium 的 SQLite 数据库。

一、功能概览※
| 能力 | 说明 |
|---|---|
| 双后端可切换 | OpenList API 模式 / 通用 WebDAV 模式,改一个配置即可 |
| 三种拦截模式 | 全转存 / 按大小分流 / 仅手动按钮,按需选择 |
| 拖拽 & 粘贴拦截 | 文件拖进笔记、或粘贴文件时自动转存,不进数据库 |
| 浮动按钮手动上传 | 右下角圆形按钮,可多选文件,不拦截任何输入 |
| 自动插入链接 | 上传成功后光标处自动插入下载链接(CKEditor 官方 API,不破坏编辑器) |
| 自动复制剪贴板 | 上传完成自动复制链接,桌面版走 Electron 原生通道(不受手势限制) |
| 年月子目录 | 自动按 YYYY-MM 分目录,避免单目录文件堆积 |
| 防覆盖命名 | 文件名加时间戳前缀 |
| 429 限流自愈 | 服务端限流时自动退避重试 + 会话内目录缓存,减少重复请求 |
| 链接与协议分离 | 上传走 WebDAV 协议地址,返回给用户的是可点击的「文件页/直链」地址(自动去 /dav) |
二、安装方法※
- 在 Trilium 新建一条 Code 笔记,语言设为
application/javascript(mime =application/javascript)。 - 把 nas_upload.js 的全部内容粘贴进该笔记。
- 给这条笔记添加标签:
#run=frontendStartup。 - 重启 Trilium(菜单 → 重启),或按
Ctrl+Shift+R刷新。 - 控制台(F12)出现
[NAS上传]开头的日志即代表启动成功;右下角出现蓝色⬆按钮即代表脚本生效。
⚠️ 修改
CONFIG配置区后,必须重启 Trilium 才能生效(该脚本在 frontend 启动时加载一次)。
三、配置项详解(CONFIG)※
所有配置集中在文件顶部的 CONFIG 对象,按模块说明如下。
1. 全局开关※
backend: 'openlist' // 'openlist' | 'webdav',决定走哪套上传后端
mode: 'size' // 'all' | 'size' | 'manual',拦截策略(见下方表)
sizeThresholdKB: 1024 // mode='size' 时的大小阈值,单位 KB| mode 值 | 行为 |
|---|---|
'all' |
所有文件都转存到 NAS,Trilium 里只留链接 |
'size'(默认) |
按大小分流:单批中任一文件 ≥ 阈值则整批转存,否则走 Trilium 原生内联附件 |
'manual' |
不拦截拖拽/粘贴,只提供右下角按钮手动上传 |
2. OpenList 后端配置(backend: 'openlist' 时生效)※
openlist: {
baseUrl: 'http://192.168.1.100:5244', // OpenList 地址(不要带 /dav)
token: '', // 方式一:直接填令牌(后台→设置→其他→令牌)
username: 'admin', // 方式二:token 留空时填账密自动登录
password: '', // OpenList 登录密码
remotePath: '/trilium-attachments' // 上传目标目录(会自动创建)
}- 鉴权二选一:填
token则优先用 token;token留空则脚本用username/password调/api/auth/login换 token 并缓存到会话内。 - remotePath 注意:OpenList 根目录是虚拟的,目录名须以已挂载存储名开头,例如
/本地存储/trilium-attachments(登录首页看到的文件夹名即存储名)。直接写/trilium-attachments在根下建目录会 403。 - 上传成功后默认返回直链:
{baseUrl}/d{remotePath}/文件名(需目标目录对 guest 可读,否则 403)。
3. WebDAV 后端配置(backend: 'webdav' 时生效)※
webdav: {
baseUrl: 'http://192.168.1.100:5244/dav', // WebDAV 地址(OpenList 为 :5244/dav,飞牛/群晖为各自的 /dav)
username: 'admin',
password: '',
remotePath: '/trilium-attachments',
linkStyle: 'plain', // 'plain' | 'openlistDirect' | 'dav'
linkBaseUrl: '' // 可选,链接用的对外域名
}linkStyle 三选一(返回给用户的链接长相):
| 值 | 链接形式 | 说明 |
|---|---|---|
'plain'(默认) |
http://ip:5244/trilium-attachments/... |
自动去掉 /dav,打开 OpenList 文件页,最常用 |
'openlistDirect' |
http://ip:5244/d/trilium-attachments/... |
OpenList /d 直链,浏览器直接下载/内联 |
'dav' |
http://ip:5244/dav/trilium-attachments/... |
保留原始协议地址,浏览器打开会弹认证框,一般不用 |
linkBaseUrl:不填则自动用 baseUrl 去掉末尾 /dav;若用域名访问(如 https://openlist.nsoft.vip),在此显式填写域名,让返回的链接走公网域名而非局域网 IP。 |
4. 命名与目录※
filenameWithTime: true, // 文件名加时间戳前缀防覆盖,如 20260903_143251_报告.docx
monthlyDirs: true, // 按年月自动分子目录:/trilium-attachments/2026-09/xxx.png
linkAsFilename: true // 插入链接的文字:true=显示文件名,false=显示裸 URL四、使用方式※
自动转存(mode ≠ manual)※
- 拖拽:把文件拖进任意
text/code/render笔记正文,被脚本拦截后自动上传、插入链接。 - 粘贴:复制文件后
Ctrl+V粘贴进笔记,同样自动转存。
手动上传(任何 mode 都可用)※
- 点击右下角蓝色
⬆按钮 → 选文件(可多选)→ 自动上传并插入/复制链接。
上传后行为※
- 右下角 Toast 提示「上传中 → 已上传 N 个文件,链接已插入并复制」。
- 链接自动插入到当前光标处(多个文件之间补空格)。
- 链接文本自动复制到剪贴板(桌面版必成功)。
五、链接插入机制(四级降级,跨版本兼容)※
CKEditor 5 是 Model-View-DOM 三层架构,外部脚本绝不能直接操作 DOM(会导致 view 树与实际 DOM 失同步,鼠标悬停链接时 Link 插件查不到节点 → 报 hasClass of undefined 崩溃)。脚本因此做了运行时探测 + 多重降级:
editor.addLinkToEditor(href, title)—— 官方 API(v0.105/0.98 均存在)editor.addLink(href, title, true)——externalLink=true必须传,否则 href 会被加#变成内部笔记链接- 从返回对象里挖出 CKEditor 实例 → 走
model.change(writer.insertText(...))官方 model API - 从 DOM + React fiber 反查 CKEditor 实例(
__reactFiber$→ 沿fiber.return向上 60 层找stateNode)→ 仍走 model API
四层全部失败时降级为「复制到剪贴板 + 提示 Ctrl+V 手动粘贴」,并在控制台输出一次诊断(编辑器类型、可用方法名),方便定位版本差异。
六、剪贴板机制(三级通道)※
浏览器 navigator.clipboard.writeText() 与 document.execCommand('copy') 都需要用户手势,而上传是异步的,await 之后手势已过期 → 自动复制会静默失败。脚本的三级通道:
- Electron 原生 clipboard(桌面版最优):
require('electron').clipboard.writeText(),不受手势限制;typeof require !== 'function'保护浏览器模式自动跳过。 - Clipboard API →
execCommand依次兜底(浏览器模式,仅在仍有手势时生效)。 - 全失败 → 弹「手动复制」面板(用户点击按钮本身即手势,必成功),1.2 秒后自动消失。
七、429 限流处理与 OpenList 限流配置※
脚本侧自愈:
createdDirs会话级目录缓存:同一目录建过一次不再重复发 MKCOL/mkdir 建目录请求。getRetryDelay():优先读Retry-After头,否则线性退避1s→2s→3s→4s→5s(上限 8s),最多重试 5 次。- 多文件上传之间强制间隔 500ms,避免突发请求。 OpenList 服务端限流调高(若仍频繁 429):
- 管理后台 → 设置 → 流量:4 个限速项设为
-1(不限速)。 max_concurrency默认 64,按需调高。data/config.json里的rate_limit(可能默认 20 req/min)改大或注释掉。- 临时建议:把
backend切回'openlist'API 模式,请求量约少一半,且可能走不同的限流计数器。
八、WebDAV 权限 / 路径报错速查※
上传失败时脚本会按 HTTP 状态码给出中文提示并附服务端返回正文:
| 状态码 | 常见原因 | 排查 |
|---|---|---|
| 401 | 账号或密码错误 | 核对 username/password |
| 403 | 无写权限 | ①OpenList 用户未开「WebDAV 管理」权限(CanWebdavManage)②飞牛账号对共享目录无写权限 ③存储驱动只读 ④remotePath 不在已挂载存储下 |
| 404 | 路径不存在 | 检查 baseUrl 与 remotePath |
| 405 | 方法不允许 | 目录已存在但服务不允许覆盖(MKCOL 405 已按正常处理,非错误) |
| 409 | 父目录不存在 | 建目录被拒,检查上层路径 |
| 413 | 文件超过大小限制 | 服务端单文件上限 |
| 429 | 服务端限流 | 见第七节,已自动重试 5 次仍失败则稍后重试 |
| 507 | 磁盘空间不足 | 清 NAS 空间 |
调试技巧:
backend: 'webdav'时启动会自动跑PROPFIND自检,结果打在控制台,可快速区分「路径错」还是「权限错」。
九、常见问题(FAQ)※
Q:上传成功但笔记里链接点不动 / 鼠标划过报错?
A:说明走了错误的插入通道。确保脚本是最新版(四级降级 + model API),不要用 execCommand 直接插 DOM。
Q:提示「已复制」但 Ctrl+V 粘出旧内容?
A:浏览器模式异步复制无解,请用 Trilium 桌面版(走 Electron 原生 clipboard);或点弹出的「手动复制」按钮。
Q:OpenList 链接打开 403?
A:直链需目标目录对 guest 可读。若不想公开,把 linkStyle 设为 'plain'(文件页)或 'dav',或给目录开访客可读。
Q:改了配置没生效?
A:改 CONFIG 后必须重启 Trilium(#run=frontendStartup 只在启动时加载一次)。
Q:远程访问时浏览器报 CORS?
A:桌面版 Electron 一般不受 CORS 限制;浏览器/服务器模式访问需给 OpenList 开 CORS(或在同域下反代)。
Q:中文密码 / 中文目录怎么处理?
A:脚本用 btoa(unescape(encodeURIComponent(...))) 做 UTF-8 安全 base64;路径逐段 encodeURIComponent,中文目录/密码均支持。
十、卸载※
删除那条带 #run=frontendStartup 标签的 Code 笔记并重启 Trilium 即可;右下角按钮会随脚本卸载而消失。