fix(export): 相对导出三道保险(E3 三轮)

未保存文档禁用相对模式并说明原因;保存对话框默认打开到文档所在
文件夹;导出后按 statFile 核查正文图片在文档旁是否存在,缺失弹窗报数,
不再静默裂图。
This commit is contained in:
Muse
2026-11-14 11:55:31 +08:00
parent a38409662c
commit 2ddd12082e

View File

@@ -824,16 +824,48 @@ export default function App() {
}) })
}, [docOverrides, ui.docTheme, tauriMode, resolveExportImage]) }, [docOverrides, ui.docTheme, tauriMode, resolveExportImage])
/** 相对导出后核查:正文引用的本地图片是否真实存在于文档旁,缺失数返回给提示。 */
const countMissingImages = useCallback(async (docPath: string, content: string): Promise<number> => {
const srcs = new Set<string>()
for (const m of content.matchAll(/!\[[^\]]*\]\(([^)\s]+)/g)) srcs.add(m[1])
let missing = 0
for (const src of srcs) {
if (/^(https?:|data:)/i.test(src)) continue
let decoded = src
try { decoded = decodeURIComponent(src) } catch { /* 保持原样 */ }
try {
if (!(await statFile(joinPath(dirOf(docPath), decoded))).exists) missing++
} catch { missing++ }
}
return missing
}, [])
const exportHtmlFile = useCallback(async (imageMode: ImageMode) => { const exportHtmlFile = useCallback(async (imageMode: ImageMode) => {
if (!tab) return if (!tab) return
setExportOpen(false) setExportOpen(false)
try { try {
if (tauriMode) { if (tauriMode) {
// 先定落点再生成:相对模式要按 HTML 文件所在目录重写图片路径(验收 E3) // 相对模式的前提是文档已落盘:没有磁盘路径就无从定位图片(验收 E3 三轮)
const target = await pickSaveHtmlFile(`${tab.title}.html`) if (imageMode === 'relative' && !tab.path) {
setError('当前文档尚未保存到磁盘,相对路径模式无法定位图片;请先保存文档,或改用「图片内嵌」模式导出')
return
}
// 先定落点再生成:相对模式要按 HTML 文件所在目录重写图片路径(验收 E3);
// 保存对话框默认打开到文档所在文件夹,同目录导出时相对路径天然成立
const defaultTarget = imageMode === 'relative' && tab.path
? joinPath(dirOf(tab.path), `${tab.title}.html`)
: `${tab.title}.html`
const target = await pickSaveHtmlFile(defaultTarget)
if (!target) return if (!target) return
const html = await buildExportHtml(tab, imageMode, { exportDir: dirOf(target) }) const html = await buildExportHtml(tab, imageMode, { exportDir: dirOf(target) })
await writeTextFile(target, html, false) await writeTextFile(target, html, false)
if (imageMode === 'relative' && tab.path) {
const missing = await countMissingImages(tab.path, tab.content)
if (missing > 0) {
flash(`已导出 HTML,但有 ${missing} 张图片在文档旁未找到,相对链接会失效(先确认编辑器里图片是否正常显示)`)
return
}
}
flash(`已导出 HTML:${baseName(target)}`) flash(`已导出 HTML:${baseName(target)}`)
} else { } else {
const html = await buildExportHtml(tab, imageMode) const html = await buildExportHtml(tab, imageMode)
@@ -841,7 +873,7 @@ export default function App() {
flash('已导出 HTML(浏览器下载)') flash('已导出 HTML(浏览器下载)')
} }
} catch (e) { setError(String(e)) } } catch (e) { setError(String(e)) }
}, [tab, buildExportHtml, tauriMode, flash]) }, [tab, buildExportHtml, tauriMode, flash, countMissingImages])
const copyHtml = useCallback(async () => { const copyHtml = useCallback(async () => {
if (!tab) return if (!tab) return