From 22254b6d3cce3f066011f77876c83fabbd00a102 Mon Sep 17 00:00:00 2001 From: yanglbme Date: Thu, 22 Aug 2024 16:45:11 +0800 Subject: [PATCH 1/2] refactor: update renderer --- index.html | 15 +- .../CodemirrorEditor/UploadImgDialog.vue | 4 +- src/stores/index.js | 5 +- src/utils/wx-renderer.js | 370 ++++++++---------- src/views/CodemirrorEditor.vue | 27 +- 5 files changed, 183 insertions(+), 238 deletions(-) diff --git a/index.html b/index.html index d0105fc02..2876afd1c 100644 --- a/index.html +++ b/index.html @@ -3,23 +3,14 @@ - - + + 微信 Markdown 编辑器 | Doocs 开源社区 - + { // 更新编辑器 const editorRefresh = () => { codeThemeChange() - - const renderer = wxRenderer.getRenderer(isCiteStatus.value) + const renderer = wxRenderer + renderer.reset() + renderer.setOptions({ status: isCiteStatus.value }) marked.setOptions({ renderer }) let outputTemp = marked.parse(editor.value.getValue(0)) diff --git a/src/utils/wx-renderer.js b/src/utils/wx-renderer.js index 9a4b39d5e..ec947ca19 100644 --- a/src/utils/wx-renderer.js +++ b/src/utils/wx-renderer.js @@ -2,230 +2,184 @@ 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 { +marked.use( + markedKatex({ + throwOnError: false, + output: `html`, + nonStandard: true, + }), +) + +class WxRenderer extends Renderer { constructor(opts) { + super() this.opts = opts - let footnotes = [] - let footnoteIndex = 0 - let styleMapping = new Map() - - const merge = (base, extend) => Object.assign({}, base, extend) + this.footnotes = [] + this.footnoteIndex = 0 + this.styleMapping = this.buildTheme(opts.theme) + } - this.buildTheme = (themeTpl) => { - const mapping = {} - const base = merge(themeTpl.BASE, { - 'font-family': this.opts.fonts, - 'font-size': this.opts.size, - }) - for (const ele in themeTpl.inline) { - if (Object.prototype.hasOwnProperty.call(themeTpl.inline, ele)) { - const style = themeTpl.inline[ele] - mapping[ele] = merge(themeTpl.BASE, style) - } - } + reset = () => { + this.footnotes = [] + this.footnoteIndex = 0 + } - const base_block = merge(base, {}) - for (const ele in themeTpl.block) { - if (Object.prototype.hasOwnProperty.call(themeTpl.block, ele)) { - const style = themeTpl.block[ele] - mapping[ele] = merge(base_block, style) - } - } - return mapping + 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), + ]), + ), } - const getStyles = (tokenName, addition) => { - const arr = [] - const dict = styleMapping[tokenName] - if (!dict) - return `` - for (const key in dict) { - arr.push(`${key}:${dict[key]}`) - } - return `style="${arr.join(`;`) + (addition || ``)}"` - } + return mapping + } - const addFootnote = (title, link) => { - footnotes.push([++footnoteIndex, title, link]) - return footnoteIndex - } + 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}"` + } - this.buildFootnotes = () => { - const footnoteArray = footnotes.map((x) => { - if (x[1] === x[2]) { - return `[${x[0]}]: ${x[1]}
` - } - return `[${x[0]}] ${x[1]}: ${x[2]}
` - }) - if (!footnoteArray.length) { - return `` + 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 `

引用链接

${footnoteArray}

` + } + + buildAddition = () => ` + + ` - this.buildAddition = () => { - return ` - - ` - } + setOptions = (newOpts) => { + this.opts = this.merge(this.opts, newOpts) + this.styleMapping = this.buildTheme(this.opts.theme) + } + + heading = (text, level) => { + const tag = `h${level}` + return `<${tag} ${this.getStyles(tag)}>${text}` + } + + paragraph = text => + text.includes(``) + ? text + : `

${text}

` + + blockquote = (text) => { + text = text.replace(//g, `

`) + return `

${text}
` + } - this.setOptions = (newOpts) => { - this.opts = merge(this.opts, newOpts) + 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` + text = hljs.highlight(text, { language }).value + text = text + .replace(/\r\n/g, `
`) + .replace(/\n/g, `
`) + .replace(/(>[^<]+)|(^[^<]+)/g, (str) => { + return str.replace(/\s/g, ` `) + }) - this.hasFootnotes = () => footnotes.length !== 0 - - this.getRenderer = (status) => { - footnotes = [] - footnoteIndex = 0 - - styleMapping = this.buildTheme(this.opts.theme) - const renderer = new Renderer() - - renderer.heading = (text, level) => { - switch (level) { - case 1: - return `

${text}

` - case 2: - return `

${text}

` - case 3: - return `

${text}

` - default: - return `

${text}

` - } - } - renderer.paragraph = (text) => { - if (text.includes(`${text}

` - } + return `
${text}
` + } - renderer.blockquote = (text) => { - text = text.replace(//g, `

`) - return `

${text}
` - } - renderer.code = (text, lang = ``) => { - if (lang.startsWith(`mermaid`)) { - setTimeout(() => { - window.mermaid?.run() - }, 0) - return `
${text}
` - } - lang = lang.split(` `)[0] - lang = hljs.getLanguage(lang) ? lang : `plaintext` - text = hljs.highlight(text, { language: lang }).value - text = text - .replace(/\r\n/g, `
`) - .replace(/\n/g, `
`) - .replace(/(>[^<]+)|(^[^<]+)/g, (str) => { - return str.replace(/\s/g, ` `) - }) - - return `
${text}
` - } - renderer.codespan = (text, _) => - `${text}` - renderer.listitem = text => - `
  • <%s/>${text}
  • ` - - renderer.list = (text, ordered, _) => { - text = text.replace(/<\/*p .*?>/g, ``).replace(/<\/*p>/g, ``) - const segments = text.split(`<%s/>`) - if (!ordered) { - text = segments.join(`• `) - return `
      ${text}
    ` - } - text = segments[0] - for (let i = 1; i < segments.length; i++) { - text = `${text + i}. ${segments[i]}` - } - return `
      ${text}
    ` - } - renderer.image = (href, title, text) => { - const createSubText = (s) => { - if (!s) { - return `` - } - - return `
    ${s}
    ` - } - const transform = (title, alt) => { - const legend = localStorage.getItem(`legend`) - switch (legend) { - case `alt`: - return alt - case `title`: - return title - case `alt-title`: - return alt || title - case `title-alt`: - return title || alt - default: - return `` - } - } - const subText = createSubText(transform(title, text)) - const figureStyles = getStyles(`figure`) - const imgStyles = getStyles(`image`) - return `
    ${text}${subText}
    ` - } - renderer.link = (href, title, text) => { - if (href.startsWith(`https://mp.weixin.qq.com`)) { - return `${text}` - } - if (href === text) { - return text - } - if (status) { - const ref = addFootnote(title || text, href) - return `${text}[${ref}]` - } - return `${text}` - } - renderer.strong = text => - `${text}` - renderer.em = text => - `${text}` - renderer.table = (header, body) => - `
    ${header}${body}
    ` - renderer.tablecell = (text, _) => - `${text}` - renderer.hr = () => `
    ` - return renderer + image = (href, title, text) => { + const subText = `
    ${ + title || text + }
    ` + 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 `${text}` } + + strong = text => `${text}` + + em = text => `${text}` + + table = (header, body) => ` +
    + + ${header} + ${body} +
    +
    ` + + tablecell = text => `${text}` + + hr = () => `
    ` } export default WxRenderer diff --git a/src/views/CodemirrorEditor.vue b/src/views/CodemirrorEditor.vue index 4429c0140..8b596755b 100644 --- a/src/views/CodemirrorEditor.vue +++ b/src/views/CodemirrorEditor.vue @@ -170,7 +170,6 @@ function beforeUpload(file) { // 图片上传结束 function uploaded(imageUrl) { - console.log(`图片上传之后: `, imageUrl) if (!imageUrl) { ElMessage.error(`上传图片未知异常`) return @@ -182,27 +181,27 @@ function uploaded(imageUrl) { // 将 Markdown 形式的 URL 插入编辑框光标所在位置 toRaw(store.editor).replaceSelection(`\n${markdownImage}\n`, cursor) ElMessage.success(`图片上传成功`) - // formatContent() - // onEditorRefresh() } function uploadImage(file, cb) { isImgLoading.value = true + toBase64(file) - .then((base64Content) => { - fileApi - .fileUpload(base64Content, file) - .then((url) => { - console.log(url) - cb ? cb(url) : uploaded(url) - }) - .catch((err) => { - ElMessage.error(err.message) - }) + .then(base64Content => fileApi.fileUpload(base64Content, file)) + .then((url) => { + console.log(url) + if (cb) { + cb(url) + } + else { + uploaded(url) + } }) .catch((err) => { ElMessage.error(err.message) }) - isImgLoading.value = false + .finally(() => { + isImgLoading.value = false + }) } const changeTimer = ref(0) From 83fabaf18b289a751ec797ea2d06207c7b1e49bb Mon Sep 17 00:00:00 2001 From: yanglbme Date: Thu, 22 Aug 2024 19:15:19 +0800 Subject: [PATCH 2/2] fix: style --- src/stores/index.js | 2 +- src/utils/wx-renderer.js | 49 +++++++++++++++++++++++++++++++--------- 2 files changed, 39 insertions(+), 12 deletions(-) diff --git a/src/stores/index.js b/src/stores/index.js index df7a3b197..a25744bc6 100644 --- a/src/stores/index.js +++ b/src/stores/index.js @@ -89,7 +89,7 @@ export const useStore = defineStore(`store`, () => { codeThemeChange() const renderer = wxRenderer renderer.reset() - renderer.setOptions({ status: isCiteStatus.value }) + renderer.setOptions({ status: isCiteStatus.value, legend: legend.value }) marked.setOptions({ renderer }) let outputTemp = marked.parse(editor.value.getValue(0)) diff --git a/src/utils/wx-renderer.js b/src/utils/wx-renderer.js index ec947ca19..16108e18e 100644 --- a/src/utils/wx-renderer.js +++ b/src/utils/wx-renderer.js @@ -107,10 +107,11 @@ class WxRenderer extends Renderer { return `<${tag} ${this.getStyles(tag)}>${text}` } - paragraph = text => - text.includes(``) - ? text - : `

    ${text}

    ` + paragraph = (text) => { + const isFigureImage = text.includes(`${text}

    ` + } blockquote = (text) => { text = text.replace(//g, `

    `) @@ -141,13 +142,37 @@ class WxRenderer extends Renderer { )}>${text}` } + codespan = text => `${text}` + + listitem = text => `

  • <%s/>${text}
  • ` + + list = (text, ordered) => { + text = text.replace(/<\/*p.*?>/g, ``).replace(/<\/*p>/g, ``) + + const segments = text.split(`<%s/>`) + + if (!ordered) { + return `
      ${segments.join(`• `)}
    ` + } + + const orderedText = segments.map((segment, i) => (i > 0 ? `${i}. ` : ``) + segment).join(``) + return `
      ${orderedText}
    ` + } + image = (href, title, text) => { - const subText = `
    ${ - title || text - }
    ` - return `
    ${text}${subText}
    ` + 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) => { @@ -160,7 +185,9 @@ class WxRenderer extends Renderer { return text if (this.opts.status) { const ref = this.addFootnote(title || text, href) - return `${text}[${ref}]` + return `${text}[${ref}]` } return `${text}` }