用户反馈
用户从钉钉文档复制大量图文到 Nowen Note 后,发现正文中的图片仍然引用钉钉远程 URL,没有保存到 Nowen 附件系统。
结果:
- 局域网打开笔记时图片仍要请求钉钉外链;
- 页面加载明显变慢;
- 外部链接可能需要登录、带防盗链或未来失效;
- 用户希望有开关,并希望“粘贴时默认把图片保存为附件”。
目标分支:release/v1.5.0
当前代码现状
1. 后端已经具备安全的远程图片转存接口
backend/src/routes/remote-image-import.ts 已有:
POST /api/attachments/import-remote-image
会把远程图片下载并保存为当前 note 的附件。
2. 前端也已有 remoteImageLocalizer.ts
该模块明确写着:
粘贴图文时,自动把远程图片下载并上传到 nowen-note 的文件管理/附件系统,然后替换正文链接。
并已经实现:
shouldLocalizeUrl()
extractRemoteImageUrls()
localizeRemoteImage()
localizeRemoteImages()
replaceRemoteUrlsInHtml()
但当前主编辑器粘贴链路并没有真正调用批量 localizeRemoteImages()。
3. Tiptap 当前只支持“选中一张网络图片后手动转存”
TiptapEditor.tsx 中目前只有选中 image node 后:
api.attachments.importRemoteImage(
currentNote.id,
originalSrc,
"image-action",
)
即:用户必须发现远程图,再逐张手动操作。
4. 当前账号级 UserPreferences 没有远程图片粘贴策略
现有 UserPreferences 只有编辑器、字号、阅读密度、目录等字段,没有类似:
所以目前没有用户可配置的开关。
产品建议
这类笔记软件的核心价值是“内容长期可控”。对于钉钉、飞书、网页、公众号等来源,保留第三方图片 URL 风险较大。
建议新增账号级策略:
remoteImagePasteMode:
| "localize" // 自动转存为附件
| "ask" // 每次粘贴时询问
| "keep-remote" // 保留原链接
默认值建议:
即新用户默认把外部图片保存到 Nowen 附件系统。
设置文案建议:
粘贴外部图片
● 自动保存为附件(推荐)
○ 每次询问
○ 保留原始链接
自动保存可避免钉钉、飞书、网页图片过期或访问缓慢,但会占用附件存储空间。
建议实现
P0:Tiptap 粘贴 HTML 自动转存远程图片
当剪贴板包含:
并且 remoteImagePasteMode === "localize" 时:
读取 clipboard HTML
→ sanitize
→ extractRemoteImageUrls
→ 并发/限流 importRemoteImage
→ 替换成 /api/attachments/<id>
→ 再插入 ProseMirror
不要先把远程 URL 永久写进正文再后台悄悄替换,避免保存竞态。
但为了交互不卡死,可以显示轻量进度:
P0:失败时单图降级,不阻断整次粘贴
例如 10 张图:
应:
- 8 张换成本地附件;
- 2 张保留原 URL;
- toast 提示“8 张已保存,2 张保留原链接”。
不能因为一张钉钉图 403 就让整段文字粘贴失败。
P0:Markdown 编辑器也保持一致
对于:

粘贴时同样根据 preference 转存并替换 URL。
P1:ask 模式
粘贴检测到远程图片时弹一次选择:
检测到 15 张外部图片
[保存为附件] [保留原链接]
可增加:
P1:并发和性能控制
不要串行一张张下载导致大段内容等待很久。
建议:
- 最大并发 3~5;
- 相同 URL 去重;
- 单张失败独立处理;
- 复用后端现有去重能力;
- 粘贴过程中保持编辑器 selection/async insert anchor;
- 切换笔记时不得把结果插入错误 note。
P1:外链来源兼容
重点覆盖:
- 钉钉文档
- 飞书文档
- 普通网页
- 微信/公众号网页
- 带 query 参数的 CDN 图片
远程抓取仍必须沿用服务端安全下载策略,防 SSRF;不要改成 renderer 直接 fetch 任意 URL。
P1:历史笔记入口
现有 remote-image-localization 后台任务继续保留,用于:
新 preference 只解决“以后粘贴默认本地化”。
数据与安全要求
- 不允许
file://、内网 SSRF 等危险 URL 绕过后端校验
- 附件保存成功后正文使用稳定
/api/attachments/<id> 引用
- 不持久化临时 signed URL
- 同一粘贴中重复 URL 只下载一次
- 本地附件 URL 不重复本地化
- 失败不破坏原始文字/排版
- Undo/Redo 行为正常
验收标准
涉及范围
frontend/src/lib/userPreferenceAccountCache.ts
frontend/src/hooks/useUserPreferences.tsx
frontend/src/components/SettingsModal.tsx
frontend/src/components/TiptapEditor.tsx
frontend/src/components/MarkdownEditorImpl.tsx
frontend/src/lib/remoteImageLocalizer.ts
backend/src/routes/remote-image-import.ts
- 相关粘贴/远程图片测试
建议优先级:P1(数据长期可用性 + 局域网性能)
用户反馈
用户从钉钉文档复制大量图文到 Nowen Note 后,发现正文中的图片仍然引用钉钉远程 URL,没有保存到 Nowen 附件系统。
结果:
目标分支:
release/v1.5.0当前代码现状
1. 后端已经具备安全的远程图片转存接口
backend/src/routes/remote-image-import.ts已有:会把远程图片下载并保存为当前 note 的附件。
2. 前端也已有
remoteImageLocalizer.ts该模块明确写着:
并已经实现:
但当前主编辑器粘贴链路并没有真正调用批量
localizeRemoteImages()。3. Tiptap 当前只支持“选中一张网络图片后手动转存”
TiptapEditor.tsx中目前只有选中 image node 后:即:用户必须发现远程图,再逐张手动操作。
4. 当前账号级 UserPreferences 没有远程图片粘贴策略
现有
UserPreferences只有编辑器、字号、阅读密度、目录等字段,没有类似:pasteRemoteImageMode所以目前没有用户可配置的开关。
产品建议
这类笔记软件的核心价值是“内容长期可控”。对于钉钉、飞书、网页、公众号等来源,保留第三方图片 URL 风险较大。
建议新增账号级策略:
默认值建议:
"localize"即新用户默认把外部图片保存到 Nowen 附件系统。
设置文案建议:
建议实现
P0:Tiptap 粘贴 HTML 自动转存远程图片
当剪贴板包含:
并且
remoteImagePasteMode === "localize"时:不要先把远程 URL 永久写进正文再后台悄悄替换,避免保存竞态。
但为了交互不卡死,可以显示轻量进度:
P0:失败时单图降级,不阻断整次粘贴
例如 10 张图:
应:
不能因为一张钉钉图 403 就让整段文字粘贴失败。
P0:Markdown 编辑器也保持一致
对于:
粘贴时同样根据 preference 转存并替换 URL。
P1:
ask模式粘贴检测到远程图片时弹一次选择:
可增加:
P1:并发和性能控制
不要串行一张张下载导致大段内容等待很久。
建议:
P1:外链来源兼容
重点覆盖:
远程抓取仍必须沿用服务端安全下载策略,防 SSRF;不要改成 renderer 直接 fetch 任意 URL。
P1:历史笔记入口
现有 remote-image-localization 后台任务继续保留,用于:
新 preference 只解决“以后粘贴默认本地化”。
数据与安全要求
file://、内网 SSRF 等危险 URL 绕过后端校验/api/attachments/<id>引用验收标准
ask/keep-remote模式行为正确/api/attachments/...的图片不重复导入涉及范围
frontend/src/lib/userPreferenceAccountCache.tsfrontend/src/hooks/useUserPreferences.tsxfrontend/src/components/SettingsModal.tsxfrontend/src/components/TiptapEditor.tsxfrontend/src/components/MarkdownEditorImpl.tsxfrontend/src/lib/remoteImageLocalizer.tsbackend/src/routes/remote-image-import.ts建议优先级:P1(数据长期可用性 + 局域网性能)