fix(export): render themed note metadata in PDF snapshots
This commit is contained in:
@@ -38,3 +38,23 @@ it('an aborted snapshot never requests backend resources',async()=>{
|
||||
await expect(preparePdfSnapshot('text','note',{theme_id:'light',include_title:false,page_size:'A4'},controller.signal)).rejects.toMatchObject({name:'AbortError'})
|
||||
expect(apiClient.post).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('renders snapshot metadata with editor theme scopes and excludes YAML from the body',async()=>{
|
||||
const markdown='---\ntitle: "<Current & title>"\ntags: ["<tag>", "未保存标签"]\n---\n# Body'
|
||||
const html=await preparePdfSnapshot(markdown,'filename',{theme_id:'light',include_title:false,page_size:'A4'})
|
||||
const doc=new DOMParser().parseFromString(html,'text/html')
|
||||
const metadata=doc.querySelector('.milkdown-host > .note-metadata')!
|
||||
expect(metadata.querySelector('h1')?.textContent).toBe('<Current & title>')
|
||||
expect([...metadata.querySelectorAll('.metadata-tag')].map(el=>el.textContent)).toEqual(['<tag>','未保存标签'])
|
||||
expect([...metadata.querySelectorAll('*')].every(el=>el.hasAttribute('data-v-editor'))).toBe(true)
|
||||
expect(metadata.querySelector('button,input,form')).toBeNull()
|
||||
expect(renderMarkdown).toHaveBeenCalledWith('# Body',expect.anything())
|
||||
expect(apiClient.post).toHaveBeenCalledWith('/api/exports/preview-resources',expect.objectContaining({source:expect.objectContaining({markdown:'# Body'})}))
|
||||
})
|
||||
it('does not invent a metadata bar for plain notes or unsupported frontmatter',async()=>{
|
||||
for(const source of ['plain text','---\ntitle: [invalid]\n---\nbody']) {
|
||||
const html=await preparePdfSnapshot(source,'filename',{theme_id:'light',include_title:true,page_size:'A4'})
|
||||
expect(html).not.toContain('<section class="note-metadata"')
|
||||
expect(renderMarkdown).toHaveBeenCalledWith(source,expect.anything())
|
||||
}
|
||||
})
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { apiClient } from './apiClient'
|
||||
import { renderMarkdown } from '@/utils/markdown'
|
||||
import { splitNoteMetadata } from '@/utils/noteMetadata'
|
||||
import { t } from '@/i18n'
|
||||
import { mermaidThemeVariables } from './mermaidService'
|
||||
import { useThemeStore } from '@/stores/theme'
|
||||
import { useMarkdownPreferencesStore } from '@/stores/markdownPreferences'
|
||||
@@ -71,9 +73,14 @@ export async function preparePdfSnapshot(markdown:string,title:string,options:Op
|
||||
const headingStyle=Object.entries(heading.cssVariables).map(([key,value])=>`${key}:${value}`).join(';')
|
||||
const customHeading=heading.preferences.custom
|
||||
const dark=theme.isDark
|
||||
const resources=await apiClient.post<Resources>('/api/exports/preview-resources',{format:'pdf',source:{type:'markdown',markdown,file_path:filePath},options})
|
||||
const metadata=splitNoteMetadata(markdown)
|
||||
const body=metadata?.body ?? markdown
|
||||
const scope=scopeAttributes(VisualMarkdownEditor)
|
||||
// Match the editor DOM and scoped styles, with read-only metadata controls.
|
||||
const metadataHtml=metadata ? `<section class="note-metadata"${scope} aria-label="${escape(t('笔记属性','Note properties'))}"><span class="metadata-caption"${scope}>${escape(t('笔记属性','Note properties'))}</span>${metadata.title ? `<h1${scope}>${escape(metadata.title)}</h1>` : ''}<div class="metadata-tags"${scope}><span class="metadata-label"${scope}>${escape(t('标签','Tags'))}</span>${metadata.tags.map(tag=>`<span class="metadata-tag"${scope}><span${scope}>${escape(tag)}</span></span>`).join('')}</div></section>` : ''
|
||||
const resources=await apiClient.post<Resources>('/api/exports/preview-resources',{format:'pdf',source:{type:'markdown',markdown:body,file_path:filePath},options})
|
||||
signal?.throwIfAborted()
|
||||
const rendered=await renderMarkdown(markdown,{themeId:options.theme_id,theme:dark?'dark':'light',preferences,pdf:{mermaidVariables:diagramVariables,plot:async source=>{
|
||||
const rendered=await renderMarkdown(body,{themeId:options.theme_id,theme:dark?'dark':'light',preferences,pdf:{mermaidVariables:diagramVariables,plot:async source=>{
|
||||
const plot=resources.plots.find(p=>p.source.trim()===source.trim()); if(!plot?.svg)throw Error(plot?.warnings.join('; ')||'函数图像无法导出');return plot
|
||||
}}})
|
||||
const fragment=new DOMParser().parseFromString(rendered,'text/html')
|
||||
@@ -101,6 +108,5 @@ export async function preparePdfSnapshot(markdown:string,title:string,options:Op
|
||||
fragment.querySelectorAll('.markdown-code-toolbar button,.diagram-controls').forEach(e=>e.remove())
|
||||
const css=(await Promise.all(styles.map(s=>embedCss(s.css,s.base,signal)))).join('\n')
|
||||
signal?.throwIfAborted()
|
||||
const scope=scopeAttributes(VisualMarkdownEditor)
|
||||
return `<!doctype html><html${htmlAttrs}><head><meta charset="utf-8"><title>${escape(title)}</title><style>${css.replace(/<\/style/gi,'<\\/style')}\n:root{${rootVariables}}\n${printRules}</style></head><body${bodyAttrs}><div class="visual-editor pdf-document"${scope} ${customHeading?'data-heading-style="custom"':''} style="${escape(headingStyle)}"><div class="milkdown-host"${scope}><div class="milkdown"><article class="ProseMirror markdown-content" data-code-wrap="${preferences.wrapCode}" data-line-numbers="${preferences.lineNumbers}" style="--markdown-code-indent:${preferences.indent}">${options.include_title?`<h1>${escape(title)}</h1>`:''}${fragment.body.innerHTML}</article></div></div></div></body></html>`
|
||||
return `<!doctype html><html${htmlAttrs}><head><meta charset="utf-8"><title>${escape(title)}</title><style>${css.replace(/<\/style/gi,'<\\/style')}\n:root{${rootVariables}}\n${printRules}</style></head><body${bodyAttrs}><div class="visual-editor pdf-document"${scope} ${customHeading?'data-heading-style="custom"':''} style="${escape(headingStyle)}"><div class="milkdown-host"${scope}>${metadataHtml}<div class="milkdown"><article class="ProseMirror markdown-content" data-code-wrap="${preferences.wrapCode}" data-line-numbers="${preferences.lineNumbers}" style="--markdown-code-indent:${preferences.indent}">${options.include_title?`<h1>${escape(title)}</h1>`:''}${fragment.body.innerHTML}</article></div></div></div></body></html>`
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user