diff --git a/index.html b/index.html index d0105fc..2876afd 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, 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 9a4b39d..16108e1 100644 --- a/src/utils/wx-renderer.js +++ b/src/utils/wx-renderer.js @@ -2,230 +2,211 @@ 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, -})) +marked.use( + markedKatex({ + throwOnError: false, + output: `html`, + nonStandard: true, + }), +) -class WxRenderer { +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.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) - } - } - - 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 - } - - 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 || ``)}"` - } - - const addFootnote = (title, link) => { - footnotes.push([++footnoteIndex, title, link]) - return footnoteIndex - } - - 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 `` - } - return `

引用链接

${footnoteArray.join(`\n`)}

` - } - - this.buildAddition = () => { - return ` - - ` - } - - this.setOptions = (newOpts) => { - this.opts = merge(this.opts, newOpts) - } - - 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}

` - } - - 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 = 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 - } + 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}"` + } + + 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 = () => ` + + ` + + 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) => { + const isFigureImage = text.includes(`${text}

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

    `) + return `

    ${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` + text = hljs.highlight(text, { language }).value + text = text + .replace(/\r\n/g, `
    `) + .replace(/\n/g, `
    `) + .replace(/(>[^<]+)|(^[^<]+)/g, (str) => { + return str.replace(/\s/g, ` `) + }) + + return `
    ${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 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 `${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 4429c01..8b59675 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)