import { Renderer, marked } from 'marked' import hljs from 'highlight.js' import markedKatex from 'marked-katex-extension' marked.use( markedKatex({ throwOnError: false, output: `html`, nonStandard: true, }), ) class WxRenderer extends Renderer { constructor(opts) { super() this.opts = opts this.footnotes = [] this.footnoteIndex = 0 this.styleMapping = this.buildTheme(opts.theme) } reset = () => { this.footnotes = [] this.footnoteIndex = 0 } merge = (base, extend) => ({ ...base, ...extend }) buildTheme = (themeTpl) => { const base = this.merge(themeTpl.BASE, { 'font-family': this.opts.fonts, 'font-size': this.opts.size, }) const mapping = { ...Object.fromEntries( Object.entries(themeTpl.inline).map(([ele, style]) => [ ele, this.merge(base, style), ]), ), ...Object.fromEntries( Object.entries(themeTpl.block).map(([ele, style]) => [ ele, this.merge(base, style), ]), ), } return mapping } getStyles = (tokenName, addition = ``) => { const dict = this.styleMapping[tokenName] if (!dict) { return `` } const styles = Object.entries(dict) .map(([key, value]) => `${key}:${value}`) .join(`;`) return `style="${styles}${addition}"` } styledContent = (styleLabel, content, label = styleLabel) => { return `<${label} ${this.getStyles(styleLabel)}>${content}` } addFootnote = (title, link) => { this.footnotes.push([++this.footnoteIndex, title, link]) return this.footnoteIndex } buildFootnotes = () => { if (!this.footnotes.length) { return `` } const footnoteArray = this.footnotes .map(([index, title, link]) => link === title ? `[${index}]: ${title}
` : `[${index}] ${title}: ${link}
`, ) .join(`\n`) return this.styledContent(`h4`, `引用链接`) + this.styledContent(`footnotes`, footnoteArray, `p`) } buildAddition = () => ` ` setOptions = (newOpts) => { this.opts = this.merge(this.opts, newOpts) this.styleMapping = this.buildTheme(this.opts.theme) } heading({ tokens, depth }) { const text = this.parser.parseInline(tokens) const tag = `h${depth}` return this.styledContent(tag, text) } paragraph({ tokens }) { const text = this.parser.parseInline(tokens) const isFigureImage = text.includes(`/g, `

`) return this.styledContent(`blockquote`, text) } code({ text, lang }) { if (lang.startsWith(`mermaid`)) { setTimeout(() => { window.mermaid?.run() }, 0) return `

${text}
` } const langText = lang.split(` `)[0] const language = hljs.getLanguage(langText) ? langText : `plaintext` let highlighted = hljs.highlight(text, { language }).value highlighted = highlighted .replace(/\r\n/g, `
`) .replace(/\n/g, `
`) .replace(/(>[^<]+)|(^[^<]+)/g, str => str.replace(/\s/g, ` `)) return `
${highlighted}
` } codespan({ text }) { return this.styledContent(`codespan`, text, `code`) } listitem(tokens, prefix) { return `
  • ${prefix}${this.parser.parseInline(tokens)}
  • ` } list({ ordered, items }) { const listItems = [] for (let i = 0; i < items.length; i++) { const { tokens } = items[i] const prefix = ordered ? `${i + 1}. ` : `• ` listItems.push(this.listitem(tokens, prefix)) } const label = ordered ? `ol` : `ul` return this.styledContent(label, listItems.join(``)) } image({ href, title, text }) { const createSubText = s => s ? `
    ${s}
    ` : `` const transform = { 'alt': () => text, 'title': () => title, 'alt-title': () => text || title, 'title-alt': () => title || text, }[this.opts.legend] || (() => ``) const subText = createSubText(transform()) const figureStyles = this.getStyles(`figure`) const imgStyles = this.getStyles(`image`) return `
    ${text}${subText}
    ` } link({ href, title, text }) { if (href.startsWith(`https://mp.weixin.qq.com`)) { return `${text}` } if (href === text) { return text } if (this.opts.status) { const ref = this.addFootnote(title || text, href) return `${text}[${ref}]` } return this.styledContent(`link`, text, `span`) } strong({ text }) { return this.styledContent(`strong`, text) } em({ text }) { return `${text}` } table({ header, rows }) { const headerRow = header.map(cell => this.styledContent(`td`, cell.text)).join(``) const body = rows.map((row) => { const rowContent = row.map(cell => this.styledContent(`td`, cell.text)).join(``) return this.styledContent(`tr`, rowContent) }).join(``) return `
    ${headerRow}${body}
    ` } tablecell({ text }) { return this.styledContent(`td`, text) } hr(_) { return this.styledContent(`hr`, ``) } } export default WxRenderer