可编辑的浮窗与可导出的 Markdown Mindmap 导图 - 开发讨论 - Obsidian 中文论坛
插件是如何适配 Obsidian 的 PDF 导出功能的:
1. 主要实现逻辑
在 mmBlock.js 中可以看到关键实现:
module.exports = (plg, ob)=> {
const md2htmlText = require('./getText/md2htmlText.js')(app, ob)
const { genMM } = require('./genMM.js')(plg.app, ob)
const mmBlock = async (source, el, ctx)=> {
const fmRgx = new RegExp(String.raw`---\nmarkmap:\n height: (\d+)\n---\n`, '')
let height = 400
const md = source.replace(fmRgx, (m, p1)=> { height = p1; return '' })
el.style.height = `${height}px`
const text = await md2htmlText(md, ctx.sourcePath)
if (ctx.el.parentNode?.className == 'print') {
await genMM(el, text, ctx.sourcePath, height)
el.style.height = 'fit-content'
}
else setTimeout(async ()=> {
await genMM(el, text, ctx.sourcePath)
}, 100)
}
plg.registerMarkdownCodeBlockProcessor('markmap', mmBlock)
}主要步骤:
- 检测打印环境:
if (ctx.el.parentNode?.className == 'print') {通过检查父节点的 className 是否为 ‘print’ 来判断是否处于 PDF 导出环境
- 特殊处理导出场景:
await genMM(el, text, ctx.sourcePath, height)
el.style.height = 'fit-content'2. 导出图片处理
在 genMM.js 中:
const genMM = async (wrapper, htmlText, sourcePath, printHeight)=> {
wrapper.empty()
const svg = wrapper.createSvg('svg')
const lib = new Transformer(), { root } = lib.transform(htmlText)
const mm = Markmap.create(svg, mmJson.opts, root)
funcBtns(svg, sourcePath); await mm.fit()
if (printHeight) {
await mm.fit()
// seems markmap@0.18 requires calling fit() again before exporting a PDF
svg.replaceWith(await svg2img(svg))
}
else wrapper.append(customBar(mm))
}当检测到是打印环境时(printHeight 参数存在):
- 再次调用
mm.fit()确保思维导图尺寸正确 - 将 SVG 转换为图片并替换原 SVG 元素
3. SVG 转图片处理
在 svg2img.js 中:
const svg2img = async (svg)=> {
// 计算尺寸和缩放比例
const { width: w1, height: h1 } = svg.getBoundingClientRect()
const { width: w2, height: h2, x, y } = svg.getBBox()
, scale = 2 * 1e-2 * Math.max(w2, h2)
, ratio = h2 / w2
let width, height
// 保持宽高比
if (h1 / w1 < ratio) {
height = h1 * scale
width = height / ratio
} else {
width = w1 * scale
height = width * ratio
}
// 添加边距
const margin = h2 / 75
// 克隆 SVG 并添加样式
const svgClone = svg.cloneNode(!0)
svgClone.prepend(Object.assign(
document.createElement('style'),
{textContent: rule}
))
// 处理嵌入图片
await encodeImg2B64(svgClone)
// 转换为 base64 图片
const svgStr = unescape(encodeURI(
new XMLSerializer().serializeToString(svgClone)
))
const img = await b64b.loadNewImg(
`data:image/svg+xml;base64,${btoa(svgStr)}`
)
const b64 = b64b.getByDraw(
width, height, scale, img,
[-x + margin, -y + margin]
)
img.src = b64
return img
}主要步骤:
- 计算适当的尺寸和缩放比例
- 克隆 SVG 并添加必要的样式
- 处理嵌入的图片(转为 base64)
- 将 SVG 转换为 base64 格式的图片
- 创建新的图片元素并返回
4. 嵌入图片处理
const encodeImg2B64 = async (svg)=> {
const imgs = svg.querySelectorAll('img')
for (const img of imgs) {
const b64 = await b64b.getByUrl(img.src)
if (img.src.startsWith('blob:')) {
const newImg = await b64b.loadNewImg(b64)
newImg.style.cssText = img.style.cssText
newImg.style.width = '100%'
img.replaceWith(newImg)
}
else img.src = b64
}
}确保所有嵌入的图片都被转换为 base64 格式,以便在 PDF 中正确显示。
5. 样式处理
module.exports = `
svg.markmap {
--markmap-text-color: #222;
--markmap-highlight-bg: rgba(255, 208, 0, 0.4);
background-color: #fff;
}
.markmap-foreign.markmap-foreign {
& code {white-space: pre-wrap;}
& .copy-code-button {display: none;}
}
`为导出的 PDF 设置特定的样式,确保在打印时有良好的显示效果。
这样的设计确保了:
- 思维导图可以正确地导出到 PDF
- 保持了图片质量和清晰度
- 维持了正确的宽高比
- 处理了嵌入图片的兼容性
- 提供了适合打印的样式
解决translate(NaN,NaN)
可参考codeblock render blank · Issue #4 · PlayerMiller109/obsidian-markmap-fileviews · GitHub
Ob 的工作方式:以阅读模式打开时,不是在切换时才渲染编辑模式,而是与阅读模式同时。
如何得出这个判断:阅读模式的 ctx 有 promises[0];编辑模式相反。
此时编辑模式的 div 状态为 display: none
→ 初始化 markmap 时容器不可见
→ 容器的 width/height 被计算为 0,坐标计算为 NaN
When opened in reading mode, the editing mode is rendered simultaneously with the reading mode, rather than when switching to.
How to arrive at this judgment: In reading mode, the ctx has promises[0]; vice versa.
At this time, the state of the editing mode div is display: none.
→ The container is invisible when initializing the markmap.
→ The width/height of the container is calculated as 0, and the coordinates are calculated as NaN.
初步已解决:不再报错。
Preliminarily solved: Do not call mm.fit() for the editing mode when you open in reading mode. No longer reports an error.
进一步方案:找到切换模式的检测。
Further solution: Find the detection for mode switching.
- 目前 Ob 没有提供直接的方法实现。no direct method currently
- 急用可以考虑定时器监测。can use a timer for monitoring if urgent
- 我倾向于等待寻找更合适的方法。I tend to wait and look for a more suitable method.