mirror of
https://github.com/doocs/md.git
synced 2024-11-24 19:10:34 +08:00
refactor: update renderer (#346)
This commit is contained in:
parent
b94750384a
commit
a65c86e2ee
15
index.html
15
index.html
@ -3,23 +3,14 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
|
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
|
||||||
<meta
|
<meta name="keywords" content="md,markdown,markdown-editor,wechat,official-account,yanglbme,doocs" />
|
||||||
name="keywords"
|
<meta name="description" content="Wechat Markdown Editor | 一款高度简洁的微信 Markdown 编辑器" />
|
||||||
content="md,markdown,markdown-editor,wechat,official-account,yanglbme,doocs"
|
|
||||||
/>
|
|
||||||
<meta
|
|
||||||
name="description"
|
|
||||||
content="Wechat Markdown Editor | 一款高度简洁的微信 Markdown 编辑器"
|
|
||||||
/>
|
|
||||||
<meta
|
<meta
|
||||||
name="viewport"
|
name="viewport"
|
||||||
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0"
|
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0"
|
||||||
/>
|
/>
|
||||||
<title>微信 Markdown 编辑器 | Doocs 开源社区</title>
|
<title>微信 Markdown 编辑器 | Doocs 开源社区</title>
|
||||||
<link
|
<link rel="shortcut icon" href="https://cdn-doocs.oss-cn-shenzhen.aliyuncs.com/gh/doocs/md/images/favicon.png" />
|
||||||
rel="shortcut icon"
|
|
||||||
href="https://cdn-doocs.oss-cn-shenzhen.aliyuncs.com/gh/doocs/md/images/favicon.png"
|
|
||||||
/>
|
|
||||||
<link
|
<link
|
||||||
rel="apple-touch-icon-precomposed"
|
rel="apple-touch-icon-precomposed"
|
||||||
href="https://cdn-doocs.oss-cn-shenzhen.aliyuncs.com/gh/doocs/md/images/1648303220922-7e14aefa-816e-44c1-8604-ade709ca1c69.png"
|
href="https://cdn-doocs.oss-cn-shenzhen.aliyuncs.com/gh/doocs/md/images/1648303220922-7e14aefa-816e-44c1-8604-ade709ca1c69.png"
|
||||||
|
@ -574,8 +574,8 @@ function uploadImage(params) {
|
|||||||
:deep(.CodeMirror) {
|
:deep(.CodeMirror) {
|
||||||
border: 1px solid #eee;
|
border: 1px solid #eee;
|
||||||
height: 300px !important;
|
height: 300px !important;
|
||||||
font-family: 'Fira Mono', 'DejaVu Sans Mono', Menlo, Consolas,
|
font-family: 'Fira Mono', 'DejaVu Sans Mono', Menlo, Consolas, 'Liberation Mono', Monaco, 'Lucida Console',
|
||||||
'Liberation Mono', Monaco, 'Lucida Console', monospace !important;
|
monospace !important;
|
||||||
line-height: 20px;
|
line-height: 20px;
|
||||||
|
|
||||||
.CodeMirror-scroll {
|
.CodeMirror-scroll {
|
||||||
|
@ -87,8 +87,9 @@ export const useStore = defineStore(`store`, () => {
|
|||||||
// 更新编辑器
|
// 更新编辑器
|
||||||
const editorRefresh = () => {
|
const editorRefresh = () => {
|
||||||
codeThemeChange()
|
codeThemeChange()
|
||||||
|
const renderer = wxRenderer
|
||||||
const renderer = wxRenderer.getRenderer(isCiteStatus.value)
|
renderer.reset()
|
||||||
|
renderer.setOptions({ status: isCiteStatus.value, legend: legend.value })
|
||||||
marked.setOptions({ renderer })
|
marked.setOptions({ renderer })
|
||||||
let outputTemp = marked.parse(editor.value.getValue(0))
|
let outputTemp = marked.parse(editor.value.getValue(0))
|
||||||
|
|
||||||
|
@ -2,77 +2,85 @@ import { Renderer, marked } from 'marked'
|
|||||||
import hljs from 'highlight.js'
|
import hljs from 'highlight.js'
|
||||||
import markedKatex from 'marked-katex-extension'
|
import markedKatex from 'marked-katex-extension'
|
||||||
|
|
||||||
marked.use(markedKatex({
|
marked.use(
|
||||||
|
markedKatex({
|
||||||
throwOnError: false,
|
throwOnError: false,
|
||||||
output: `html`,
|
output: `html`,
|
||||||
nonStandard: true,
|
nonStandard: true,
|
||||||
}))
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
class WxRenderer {
|
class WxRenderer extends Renderer {
|
||||||
constructor(opts) {
|
constructor(opts) {
|
||||||
|
super()
|
||||||
this.opts = opts
|
this.opts = opts
|
||||||
let footnotes = []
|
this.footnotes = []
|
||||||
let footnoteIndex = 0
|
this.footnoteIndex = 0
|
||||||
let styleMapping = new Map()
|
this.styleMapping = this.buildTheme(opts.theme)
|
||||||
|
}
|
||||||
|
|
||||||
const merge = (base, extend) => Object.assign({}, base, extend)
|
reset = () => {
|
||||||
|
this.footnotes = []
|
||||||
|
this.footnoteIndex = 0
|
||||||
|
}
|
||||||
|
|
||||||
this.buildTheme = (themeTpl) => {
|
merge = (base, extend) => ({ ...base, ...extend })
|
||||||
const mapping = {}
|
|
||||||
const base = merge(themeTpl.BASE, {
|
buildTheme = (themeTpl) => {
|
||||||
|
const base = this.merge(themeTpl.BASE, {
|
||||||
'font-family': this.opts.fonts,
|
'font-family': this.opts.fonts,
|
||||||
'font-size': this.opts.size,
|
'font-size': this.opts.size,
|
||||||
})
|
})
|
||||||
for (const ele in themeTpl.inline) {
|
|
||||||
if (Object.prototype.hasOwnProperty.call(themeTpl.inline, ele)) {
|
const mapping = {
|
||||||
const style = themeTpl.inline[ele]
|
...Object.fromEntries(
|
||||||
mapping[ele] = merge(themeTpl.BASE, style)
|
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 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
|
return mapping
|
||||||
}
|
}
|
||||||
|
|
||||||
const getStyles = (tokenName, addition) => {
|
getStyles = (tokenName, addition = ``) => {
|
||||||
const arr = []
|
const dict = this.styleMapping[tokenName]
|
||||||
const dict = styleMapping[tokenName]
|
|
||||||
if (!dict)
|
if (!dict)
|
||||||
return ``
|
return ``
|
||||||
for (const key in dict) {
|
const styles = Object.entries(dict)
|
||||||
arr.push(`${key}:${dict[key]}`)
|
.map(([key, value]) => `${key}:${value}`)
|
||||||
}
|
.join(`;`)
|
||||||
return `style="${arr.join(`;`) + (addition || ``)}"`
|
return `style="${styles}${addition}"`
|
||||||
}
|
}
|
||||||
|
|
||||||
const addFootnote = (title, link) => {
|
addFootnote = (title, link) => {
|
||||||
footnotes.push([++footnoteIndex, title, link])
|
this.footnotes.push([++this.footnoteIndex, title, link])
|
||||||
return footnoteIndex
|
return this.footnoteIndex
|
||||||
}
|
}
|
||||||
|
|
||||||
this.buildFootnotes = () => {
|
buildFootnotes = () => {
|
||||||
const footnoteArray = footnotes.map((x) => {
|
if (!this.footnotes.length)
|
||||||
if (x[1] === x[2]) {
|
|
||||||
return `<code style="font-size: 90%; opacity: 0.6;">[${x[0]}]</code>: <i>${x[1]}</i><br/>`
|
|
||||||
}
|
|
||||||
return `<code style="font-size: 90%; opacity: 0.6;">[${x[0]}]</code> ${x[1]}: <i>${x[2]}</i><br/>`
|
|
||||||
})
|
|
||||||
if (!footnoteArray.length) {
|
|
||||||
return ``
|
return ``
|
||||||
}
|
const footnoteArray = this.footnotes
|
||||||
return `<h4 ${getStyles(`h4`)}>引用链接</h4><p ${getStyles(
|
.map(([index, title, link]) =>
|
||||||
|
link === title
|
||||||
|
? `<code style="font-size: 90%; opacity: 0.6;">[${index}]</code>: <i>${title}</i><br/>`
|
||||||
|
: `<code style="font-size: 90%; opacity: 0.6;">[${index}]</code> ${title}: <i>${link}</i><br/>`,
|
||||||
|
)
|
||||||
|
.join(`\n`)
|
||||||
|
return `<h4 ${this.getStyles(`h4`)}>引用链接</h4><p ${this.getStyles(
|
||||||
`footnotes`,
|
`footnotes`,
|
||||||
)}>${footnoteArray.join(`\n`)}</p>`
|
)}>${footnoteArray}</p>`
|
||||||
}
|
}
|
||||||
|
|
||||||
this.buildAddition = () => {
|
buildAddition = () => `
|
||||||
return `
|
|
||||||
<style>
|
<style>
|
||||||
.preview-wrapper pre::before {
|
.preview-wrapper pre::before {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
@ -88,56 +96,38 @@ class WxRenderer {
|
|||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
`
|
`
|
||||||
|
|
||||||
|
setOptions = (newOpts) => {
|
||||||
|
this.opts = this.merge(this.opts, newOpts)
|
||||||
|
this.styleMapping = this.buildTheme(this.opts.theme)
|
||||||
}
|
}
|
||||||
|
|
||||||
this.setOptions = (newOpts) => {
|
heading = (text, level) => {
|
||||||
this.opts = merge(this.opts, newOpts)
|
const tag = `h${level}`
|
||||||
|
return `<${tag} ${this.getStyles(tag)}>${text}</${tag}>`
|
||||||
}
|
}
|
||||||
|
|
||||||
this.hasFootnotes = () => footnotes.length !== 0
|
paragraph = (text) => {
|
||||||
|
const isFigureImage = text.includes(`<figure`) && text.includes(`<img`)
|
||||||
this.getRenderer = (status) => {
|
const isEmpty = text.trim() === ``
|
||||||
footnotes = []
|
return isFigureImage ? text : isEmpty ? `` : `<p ${this.getStyles(`p`)}>${text}</p>`
|
||||||
footnoteIndex = 0
|
|
||||||
|
|
||||||
styleMapping = this.buildTheme(this.opts.theme)
|
|
||||||
const renderer = new Renderer()
|
|
||||||
|
|
||||||
renderer.heading = (text, level) => {
|
|
||||||
switch (level) {
|
|
||||||
case 1:
|
|
||||||
return `<h1 ${getStyles(`h1`)}>${text}</h1>`
|
|
||||||
case 2:
|
|
||||||
return `<h2 ${getStyles(`h2`)}>${text}</h2>`
|
|
||||||
case 3:
|
|
||||||
return `<h3 ${getStyles(`h3`)}>${text}</h3>`
|
|
||||||
default:
|
|
||||||
return `<h4 ${getStyles(`h4`)}>${text}</h4>`
|
|
||||||
}
|
|
||||||
}
|
|
||||||
renderer.paragraph = (text) => {
|
|
||||||
if (text.includes(`<figure`) && text.includes(`<img`)) {
|
|
||||||
return text
|
|
||||||
}
|
|
||||||
return text.replace(/ /g, ``) === ``
|
|
||||||
? ``
|
|
||||||
: `<p ${getStyles(`p`)}>${text}</p>`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
renderer.blockquote = (text) => {
|
blockquote = (text) => {
|
||||||
text = text.replace(/<p.*?>/g, `<p ${getStyles(`blockquote_p`)}>`)
|
text = text.replace(/<p.*?>/g, `<p ${this.getStyles(`blockquote_p`)}>`)
|
||||||
return `<blockquote ${getStyles(`blockquote`)}>${text}</blockquote>`
|
return `<blockquote ${this.getStyles(`blockquote`)}>${text}</blockquote>`
|
||||||
}
|
}
|
||||||
renderer.code = (text, lang = ``) => {
|
|
||||||
|
code = (text, lang = ``) => {
|
||||||
if (lang.startsWith(`mermaid`)) {
|
if (lang.startsWith(`mermaid`)) {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
window.mermaid?.run()
|
window.mermaid?.run()
|
||||||
}, 0)
|
}, 0)
|
||||||
return `<center><pre class="mermaid">${text}</pre></center>`
|
return `<center><pre class="mermaid">${text}</pre></center>`
|
||||||
}
|
}
|
||||||
lang = lang.split(` `)[0]
|
const langText = lang.split(` `)[0]
|
||||||
lang = hljs.getLanguage(lang) ? lang : `plaintext`
|
const language = hljs.getLanguage(langText) ? langText : `plaintext`
|
||||||
text = hljs.highlight(text, { language: lang }).value
|
text = hljs.highlight(text, { language }).value
|
||||||
text = text
|
text = text
|
||||||
.replace(/\r\n/g, `<br/>`)
|
.replace(/\r\n/g, `<br/>`)
|
||||||
.replace(/\n/g, `<br/>`)
|
.replace(/\n/g, `<br/>`)
|
||||||
@ -145,87 +135,78 @@ class WxRenderer {
|
|||||||
return str.replace(/\s/g, ` `)
|
return str.replace(/\s/g, ` `)
|
||||||
})
|
})
|
||||||
|
|
||||||
return `<pre class="hljs code__pre" ${getStyles(
|
return `<pre class="hljs code__pre" ${this.getStyles(
|
||||||
`code_pre`,
|
`code_pre`,
|
||||||
)}><code class="language-${lang}" ${getStyles(
|
)}><code class="language-${lang}" ${this.getStyles(
|
||||||
`code`,
|
`code`,
|
||||||
)}>${text}</code></pre>`
|
)}>${text}</code></pre>`
|
||||||
}
|
}
|
||||||
renderer.codespan = (text, _) =>
|
|
||||||
`<code ${getStyles(`codespan`)}>${text}</code>`
|
|
||||||
renderer.listitem = text =>
|
|
||||||
`<li ${getStyles(`listitem`)}><span><%s/></span>${text}</li>`
|
|
||||||
|
|
||||||
renderer.list = (text, ordered, _) => {
|
codespan = text => `<code ${this.getStyles(`codespan`)}>${text}</code>`
|
||||||
text = text.replace(/<\/*p .*?>/g, ``).replace(/<\/*p>/g, ``)
|
|
||||||
|
listitem = text => `<li ${this.getStyles(`listitem`)}><span><%s/></span>${text}</li>`
|
||||||
|
|
||||||
|
list = (text, ordered) => {
|
||||||
|
text = text.replace(/<\/*p.*?>/g, ``).replace(/<\/*p>/g, ``)
|
||||||
|
|
||||||
const segments = text.split(`<%s/>`)
|
const segments = text.split(`<%s/>`)
|
||||||
|
|
||||||
if (!ordered) {
|
if (!ordered) {
|
||||||
text = segments.join(`• `)
|
return `<ul ${this.getStyles(`ul`)}>${segments.join(`• `)}</ul>`
|
||||||
return `<ul ${getStyles(`ul`)}>${text}</ul>`
|
|
||||||
}
|
|
||||||
text = segments[0]
|
|
||||||
for (let i = 1; i < segments.length; i++) {
|
|
||||||
text = `${text + i}. ${segments[i]}`
|
|
||||||
}
|
|
||||||
return `<ol ${getStyles(`ol`)}>${text}</ol>`
|
|
||||||
}
|
|
||||||
renderer.image = (href, title, text) => {
|
|
||||||
const createSubText = (s) => {
|
|
||||||
if (!s) {
|
|
||||||
return ``
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return `<figcaption ${getStyles(`figcaption`)}>${s}</figcaption>`
|
const orderedText = segments.map((segment, i) => (i > 0 ? `${i}. ` : ``) + segment).join(``)
|
||||||
|
return `<ol ${this.getStyles(`ol`)}>${orderedText}</ol>`
|
||||||
}
|
}
|
||||||
const transform = (title, alt) => {
|
|
||||||
const legend = localStorage.getItem(`legend`)
|
image = (href, title, text) => {
|
||||||
switch (legend) {
|
const createSubText = s => s ? `<figcaption ${this.getStyles(`figcaption`)}>${s}</figcaption>` : ``
|
||||||
case `alt`:
|
const transform = {
|
||||||
return alt
|
'alt': () => text,
|
||||||
case `title`:
|
'title': () => title,
|
||||||
return title
|
'alt-title': () => text || title,
|
||||||
case `alt-title`:
|
'title-alt': () => title || text,
|
||||||
return alt || title
|
}[this.opts.legend] || (() => ``)
|
||||||
case `title-alt`:
|
|
||||||
return title || alt
|
const subText = createSubText(transform())
|
||||||
default:
|
const figureStyles = this.getStyles(`figure`)
|
||||||
return ``
|
const imgStyles = this.getStyles(`image`)
|
||||||
}
|
|
||||||
}
|
|
||||||
const subText = createSubText(transform(title, text))
|
|
||||||
const figureStyles = getStyles(`figure`)
|
|
||||||
const imgStyles = getStyles(`image`)
|
|
||||||
return `<figure ${figureStyles}><img ${imgStyles} src="${href}" title="${title}" alt="${text}"/>${subText}</figure>`
|
return `<figure ${figureStyles}><img ${imgStyles} src="${href}" title="${title}" alt="${text}"/>${subText}</figure>`
|
||||||
}
|
}
|
||||||
renderer.link = (href, title, text) => {
|
|
||||||
|
link = (href, title, text) => {
|
||||||
if (href.startsWith(`https://mp.weixin.qq.com`)) {
|
if (href.startsWith(`https://mp.weixin.qq.com`)) {
|
||||||
return `<a href="${href}" title="${title || text}" ${getStyles(
|
return `<a href="${href}" title="${title || text}" ${this.getStyles(
|
||||||
`wx_link`,
|
`wx_link`,
|
||||||
)}>${text}</a>`
|
)}>${text}</a>`
|
||||||
}
|
}
|
||||||
if (href === text) {
|
if (href === text)
|
||||||
return text
|
return text
|
||||||
|
if (this.opts.status) {
|
||||||
|
const ref = this.addFootnote(title || text, href)
|
||||||
|
return `<span ${this.getStyles(
|
||||||
|
`link`,
|
||||||
|
)}>${text}<sup>[${ref}]</sup></span>`
|
||||||
}
|
}
|
||||||
if (status) {
|
return `<span ${this.getStyles(`link`)}>${text}</span>`
|
||||||
const ref = addFootnote(title || text, href)
|
|
||||||
return `<span ${getStyles(`link`)}>${text}<sup>[${ref}]</sup></span>`
|
|
||||||
}
|
|
||||||
return `<span ${getStyles(`link`)}>${text}</span>`
|
|
||||||
}
|
|
||||||
renderer.strong = text =>
|
|
||||||
`<strong ${getStyles(`strong`)}>${text}</strong>`
|
|
||||||
renderer.em = text =>
|
|
||||||
`<span style="font-style: italic;">${text}</span>`
|
|
||||||
renderer.table = (header, body) =>
|
|
||||||
`<section style="padding:0 8px;"><table class="preview-table"><thead ${getStyles(
|
|
||||||
`thead`,
|
|
||||||
)}>${header}</thead><tbody>${body}</tbody></table></section>`
|
|
||||||
renderer.tablecell = (text, _) =>
|
|
||||||
`<td ${getStyles(`td`)}>${text}</td>`
|
|
||||||
renderer.hr = () => `<hr ${getStyles(`hr`)}>`
|
|
||||||
return renderer
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
strong = text => `<strong ${this.getStyles(`strong`)}>${text}</strong>`
|
||||||
|
|
||||||
|
em = text => `<span style="font-style: italic;">${text}</span>`
|
||||||
|
|
||||||
|
table = (header, body) => `
|
||||||
|
<section style="padding:0 8px;">
|
||||||
|
<table class="preview-table">
|
||||||
|
<thead ${this.getStyles(`thead`)}>${header}</thead>
|
||||||
|
<tbody>${body}</tbody>
|
||||||
|
</table>
|
||||||
|
</section>`
|
||||||
|
|
||||||
|
tablecell = text => `<td ${this.getStyles(`td`)}>${text}</td>`
|
||||||
|
|
||||||
|
hr = () => `<hr ${this.getStyles(`hr`)}/>`
|
||||||
}
|
}
|
||||||
|
|
||||||
export default WxRenderer
|
export default WxRenderer
|
||||||
|
@ -170,7 +170,6 @@ function beforeUpload(file) {
|
|||||||
|
|
||||||
// 图片上传结束
|
// 图片上传结束
|
||||||
function uploaded(imageUrl) {
|
function uploaded(imageUrl) {
|
||||||
console.log(`图片上传之后: `, imageUrl)
|
|
||||||
if (!imageUrl) {
|
if (!imageUrl) {
|
||||||
ElMessage.error(`上传图片未知异常`)
|
ElMessage.error(`上传图片未知异常`)
|
||||||
return
|
return
|
||||||
@ -182,27 +181,27 @@ function uploaded(imageUrl) {
|
|||||||
// 将 Markdown 形式的 URL 插入编辑框光标所在位置
|
// 将 Markdown 形式的 URL 插入编辑框光标所在位置
|
||||||
toRaw(store.editor).replaceSelection(`\n${markdownImage}\n`, cursor)
|
toRaw(store.editor).replaceSelection(`\n${markdownImage}\n`, cursor)
|
||||||
ElMessage.success(`图片上传成功`)
|
ElMessage.success(`图片上传成功`)
|
||||||
// formatContent()
|
|
||||||
// onEditorRefresh()
|
|
||||||
}
|
}
|
||||||
function uploadImage(file, cb) {
|
function uploadImage(file, cb) {
|
||||||
isImgLoading.value = true
|
isImgLoading.value = true
|
||||||
|
|
||||||
toBase64(file)
|
toBase64(file)
|
||||||
.then((base64Content) => {
|
.then(base64Content => fileApi.fileUpload(base64Content, file))
|
||||||
fileApi
|
|
||||||
.fileUpload(base64Content, file)
|
|
||||||
.then((url) => {
|
.then((url) => {
|
||||||
console.log(url)
|
console.log(url)
|
||||||
cb ? cb(url) : uploaded(url)
|
if (cb) {
|
||||||
})
|
cb(url)
|
||||||
.catch((err) => {
|
}
|
||||||
ElMessage.error(err.message)
|
else {
|
||||||
})
|
uploaded(url)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
ElMessage.error(err.message)
|
ElMessage.error(err.message)
|
||||||
})
|
})
|
||||||
|
.finally(() => {
|
||||||
isImgLoading.value = false
|
isImgLoading.value = false
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const changeTimer = ref(0)
|
const changeTimer = ref(0)
|
||||||
|
Loading…
Reference in New Issue
Block a user