chore: upgrade renderer lib (#355)

close #344
This commit is contained in:
Libin YANG 2024-08-27 19:47:02 +08:00 committed by GitHub
parent 832360419c
commit bd31da9e5f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
15 changed files with 521 additions and 604 deletions

965
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -34,7 +34,7 @@
"juice": "^8.0.0", "juice": "^8.0.0",
"katex": "^0.16.11", "katex": "^0.16.11",
"lucide-vue-next": "^0.428.0", "lucide-vue-next": "^0.428.0",
"marked": "^4.0.18", "marked": "^14.1.0",
"marked-katex-extension": "^5.1.1", "marked-katex-extension": "^5.1.1",
"minio": "7.1.3", "minio": "7.1.3",
"node-fetch": "^3.3.2", "node-fetch": "^3.3.2",

View File

@ -2,8 +2,6 @@
import { nextTick, reactive, ref } from 'vue' import { nextTick, reactive, ref } from 'vue'
import { storeToRefs } from 'pinia' import { storeToRefs } from 'pinia'
import { ElNotification } from 'element-plus' import { ElNotification } from 'element-plus'
import CodeMirror from 'codemirror'
import { Command } from 'lucide-vue-next'
import PostInfo from './PostInfo.vue' import PostInfo from './PostInfo.vue'
import FileDropdown from './FileDropdown.vue' import FileDropdown from './FileDropdown.vue'

View File

@ -10,7 +10,7 @@ import {
import { Check } from 'lucide-vue-next' import { Check } from 'lucide-vue-next'
import { cn } from '@/lib/utils' import { cn } from '@/lib/utils'
const props = defineProps<ContextMenuCheckboxItemProps & { class?: HTMLAttributes['class'] }>() const props = defineProps<ContextMenuCheckboxItemProps & { class?: HTMLAttributes[`class`] }>()
const emits = defineEmits<ContextMenuCheckboxItemEmits>() const emits = defineEmits<ContextMenuCheckboxItemEmits>()
const delegatedProps = computed(() => { const delegatedProps = computed(() => {
@ -30,7 +30,7 @@ const forwarded = useForwardPropsEmits(delegatedProps, emits)
props.class, props.class,
)" )"
> >
<span class="absolute left-2 flex h-3.5 w-3.5 items-center justify-center"> <span class="absolute left-2 h-3.5 w-3.5 flex items-center justify-center">
<ContextMenuItemIndicator> <ContextMenuItemIndicator>
<Check class="h-4 w-4" /> <Check class="h-4 w-4" />
</ContextMenuItemIndicator> </ContextMenuItemIndicator>

View File

@ -9,7 +9,7 @@ import {
} from 'radix-vue' } from 'radix-vue'
import { cn } from '@/lib/utils' import { cn } from '@/lib/utils'
const props = defineProps<ContextMenuContentProps & { class?: HTMLAttributes['class'] }>() const props = defineProps<ContextMenuContentProps & { class?: HTMLAttributes[`class`] }>()
const emits = defineEmits<ContextMenuContentEmits>() const emits = defineEmits<ContextMenuContentEmits>()
const delegatedProps = computed(() => { const delegatedProps = computed(() => {

View File

@ -8,7 +8,7 @@ import {
} from 'radix-vue' } from 'radix-vue'
import { cn } from '@/lib/utils' import { cn } from '@/lib/utils'
const props = defineProps<ContextMenuItemProps & { class?: HTMLAttributes['class'], inset?: boolean }>() const props = defineProps<ContextMenuItemProps & { class?: HTMLAttributes[`class`], inset?: boolean }>()
const emits = defineEmits<ContextMenuItemEmits>() const emits = defineEmits<ContextMenuItemEmits>()
const delegatedProps = computed(() => { const delegatedProps = computed(() => {

View File

@ -3,7 +3,7 @@ import { type HTMLAttributes, computed } from 'vue'
import { ContextMenuLabel, type ContextMenuLabelProps } from 'radix-vue' import { ContextMenuLabel, type ContextMenuLabelProps } from 'radix-vue'
import { cn } from '@/lib/utils' import { cn } from '@/lib/utils'
const props = defineProps<ContextMenuLabelProps & { class?: HTMLAttributes['class'], inset?: boolean }>() const props = defineProps<ContextMenuLabelProps & { class?: HTMLAttributes[`class`], inset?: boolean }>()
const delegatedProps = computed(() => { const delegatedProps = computed(() => {
const { class: _, ...delegated } = props const { class: _, ...delegated } = props

View File

@ -10,7 +10,7 @@ import {
import { Circle } from 'lucide-vue-next' import { Circle } from 'lucide-vue-next'
import { cn } from '@/lib/utils' import { cn } from '@/lib/utils'
const props = defineProps<ContextMenuRadioItemProps & { class?: HTMLAttributes['class'] }>() const props = defineProps<ContextMenuRadioItemProps & { class?: HTMLAttributes[`class`] }>()
const emits = defineEmits<ContextMenuRadioItemEmits>() const emits = defineEmits<ContextMenuRadioItemEmits>()
const delegatedProps = computed(() => { const delegatedProps = computed(() => {
@ -30,7 +30,7 @@ const forwarded = useForwardPropsEmits(delegatedProps, emits)
props.class, props.class,
)" )"
> >
<span class="absolute left-2 flex h-3.5 w-3.5 items-center justify-center"> <span class="absolute left-2 h-3.5 w-3.5 flex items-center justify-center">
<ContextMenuItemIndicator> <ContextMenuItemIndicator>
<Circle class="h-2 w-2 fill-current" /> <Circle class="h-2 w-2 fill-current" />
</ContextMenuItemIndicator> </ContextMenuItemIndicator>

View File

@ -6,7 +6,7 @@ import {
} from 'radix-vue' } from 'radix-vue'
import { cn } from '@/lib/utils' import { cn } from '@/lib/utils'
const props = defineProps<ContextMenuSeparatorProps & { class?: HTMLAttributes['class'] }>() const props = defineProps<ContextMenuSeparatorProps & { class?: HTMLAttributes[`class`] }>()
const delegatedProps = computed(() => { const delegatedProps = computed(() => {
const { class: _, ...delegated } = props const { class: _, ...delegated } = props

View File

@ -3,7 +3,7 @@ import type { HTMLAttributes } from 'vue'
import { cn } from '@/lib/utils' import { cn } from '@/lib/utils'
const props = defineProps<{ const props = defineProps<{
class?: HTMLAttributes['class'] class?: HTMLAttributes[`class`]
}>() }>()
</script> </script>

View File

@ -8,7 +8,7 @@ import {
} from 'radix-vue' } from 'radix-vue'
import { cn } from '@/lib/utils' import { cn } from '@/lib/utils'
const props = defineProps<DropdownMenuSubContentProps & { class?: HTMLAttributes['class'] }>() const props = defineProps<DropdownMenuSubContentProps & { class?: HTMLAttributes[`class`] }>()
const emits = defineEmits<DropdownMenuSubContentEmits>() const emits = defineEmits<DropdownMenuSubContentEmits>()
const delegatedProps = computed(() => { const delegatedProps = computed(() => {

View File

@ -8,7 +8,7 @@ import {
import { ChevronRight } from 'lucide-vue-next' import { ChevronRight } from 'lucide-vue-next'
import { cn } from '@/lib/utils' import { cn } from '@/lib/utils'
const props = defineProps<ContextMenuSubTriggerProps & { class?: HTMLAttributes['class'], inset?: boolean }>() const props = defineProps<ContextMenuSubTriggerProps & { class?: HTMLAttributes[`class`], inset?: boolean }>()
const delegatedProps = computed(() => { const delegatedProps = computed(() => {
const { class: _, ...delegated } = props const { class: _, ...delegated } = props

View File

@ -381,7 +381,7 @@ const graceTheme = mergeTheme(defaultTheme, {
}, },
thead: { thead: {
'background': `linear-gradient(45deg, rgba(0, 152, 116, 0.9), rgba(0, 192, 146, 0.9))`, 'background': `rgba(0, 0, 0, 0.05)`,
'color': `#fff`, 'color': `#fff`,
'font-weight': `bold`, 'font-weight': `bold`,
}, },

View File

@ -52,14 +52,19 @@ class WxRenderer extends Renderer {
getStyles = (tokenName, addition = ``) => { getStyles = (tokenName, addition = ``) => {
const dict = this.styleMapping[tokenName] const dict = this.styleMapping[tokenName]
if (!dict) if (!dict) {
return `` return ``
}
const styles = Object.entries(dict) const styles = Object.entries(dict)
.map(([key, value]) => `${key}:${value}`) .map(([key, value]) => `${key}:${value}`)
.join(`;`) .join(`;`)
return `style="${styles}${addition}"` return `style="${styles}${addition}"`
} }
styledContent = (styleLabel, content, label = styleLabel) => {
return `<${label} ${this.getStyles(styleLabel)}>${content}</${label}>`
}
addFootnote = (title, link) => { addFootnote = (title, link) => {
this.footnotes.push([++this.footnoteIndex, title, link]) this.footnotes.push([++this.footnoteIndex, title, link])
return this.footnoteIndex return this.footnoteIndex
@ -77,9 +82,8 @@ class WxRenderer extends Renderer {
: `<code style="font-size: 90%; opacity: 0.6;">[${index}]</code> ${title}: <i style="word-break: break-all">${link}</i><br/>`, : `<code style="font-size: 90%; opacity: 0.6;">[${index}]</code> ${title}: <i style="word-break: break-all">${link}</i><br/>`,
) )
.join(`\n`) .join(`\n`)
return `<h4 ${this.getStyles(`h4`)}>引用链接</h4><p ${this.getStyles(
`footnotes`, return this.styledContent(`h4`, `引用链接`) + this.styledContent(`footnotes`, footnoteArray, `p`)
)}>${footnoteArray}</p>`
} }
buildAddition = () => ` buildAddition = () => `
@ -104,23 +108,29 @@ class WxRenderer extends Renderer {
this.styleMapping = this.buildTheme(this.opts.theme) this.styleMapping = this.buildTheme(this.opts.theme)
} }
heading = (text, level) => { heading({ tokens, depth }) {
const tag = `h${level}` const text = this.parser.parseInline(tokens)
return `<${tag} ${this.getStyles(tag)}>${text}</${tag}>` const tag = `h${depth}`
return this.styledContent(tag, text)
} }
paragraph = (text) => { paragraph({ tokens }) {
const text = this.parser.parseInline(tokens)
const isFigureImage = text.includes(`<figure`) && text.includes(`<img`) const isFigureImage = text.includes(`<figure`) && text.includes(`<img`)
const isEmpty = text.trim() === `` const isEmpty = text.trim() === ``
return isFigureImage ? text : isEmpty ? `` : `<p ${this.getStyles(`p`)}>${text}</p>` if (isFigureImage || isEmpty) {
return text
}
return this.styledContent(`p`, text)
} }
blockquote = (text) => { blockquote({ tokens }) {
let text = this.parser.parse(tokens)
text = text.replace(/<p.*?>/g, `<p ${this.getStyles(`blockquote_p`)}>`) text = text.replace(/<p.*?>/g, `<p ${this.getStyles(`blockquote_p`)}>`)
return `<blockquote ${this.getStyles(`blockquote`)}>${text}</blockquote>` return this.styledContent(`blockquote`, text)
} }
code = (text, lang = ``) => { code({ text, lang }) {
if (lang.startsWith(`mermaid`)) { if (lang.startsWith(`mermaid`)) {
setTimeout(() => { setTimeout(() => {
window.mermaid?.run() window.mermaid?.run()
@ -129,86 +139,103 @@ class WxRenderer extends Renderer {
} }
const langText = lang.split(` `)[0] const langText = lang.split(` `)[0]
const language = hljs.getLanguage(langText) ? langText : `plaintext` const language = hljs.getLanguage(langText) ? langText : `plaintext`
text = hljs.highlight(text, { language }).value let highlighted = hljs.highlight(text, { language }).value
text = text highlighted = highlighted
.replace(/\r\n/g, `<br/>`) .replace(/\r\n/g, `<br/>`)
.replace(/\n/g, `<br/>`) .replace(/\n/g, `<br/>`)
.replace(/(>[^<]+)|(^[^<]+)/g, (str) => { .replace(/(>[^<]+)|(^[^<]+)/g, str => str.replace(/\s/g, `&nbsp;`))
return str.replace(/\s/g, `&nbsp;`)
})
return `<pre class="hljs code__pre" ${this.getStyles( return `<pre class="hljs code__pre" ${this.getStyles(
`code_pre`, `code_pre`,
)}><code class="language-${lang}" ${this.getStyles( )}><code class="language-${lang}" ${this.getStyles(
`code`, `code`,
)}>${text}</code></pre>` )}>${highlighted}</code></pre>`
} }
codespan = text => `<code ${this.getStyles(`codespan`)}>${text}</code>` codespan({ text }) {
return this.styledContent(`codespan`, text, `code`)
}
listitem = text => `<li ${this.getStyles(`listitem`)}><span><%s/></span>${text}</li>` listitem(tokens, prefix) {
return `<li ${this.getStyles(`listitem`)}>${prefix}${this.parser.parseInline(tokens)}</li>`
}
list = (text, ordered) => { list({ ordered, items }) {
text = text.replace(/<\/*p.*?>/g, ``).replace(/<\/*p>/g, ``) const listItems = []
for (let i = 0; i < items.length; i++) {
const segments = text.split(`<%s/>`) const { tokens } = items[i]
const prefix = ordered ? `${i + 1}. ` : ``
if (!ordered) { listItems.push(this.listitem(tokens, prefix))
return `<ul ${this.getStyles(`ul`)}>${segments.join(``)}</ul>`
} }
const label = ordered ? `ol` : `ul`
const orderedText = segments.map((segment, i) => (i > 0 ? `${i}. ` : ``) + segment).join(``) return this.styledContent(label, listItems.join(``))
return `<ol ${this.getStyles(`ol`)}>${orderedText}</ol>`
} }
image = (href, title, text) => { image({ href, title, text }) {
const createSubText = s => s ? `<figcaption ${this.getStyles(`figcaption`)}>${s}</figcaption>` : `` const createSubText = s =>
const transform = { s ? `<figcaption ${this.getStyles(`figcaption`)}>${s}</figcaption>` : ``
'alt': () => text, const transform
'title': () => title, = {
'alt-title': () => text || title, 'alt': () => text,
'title-alt': () => title || text, 'title': () => title,
}[this.opts.legend] || (() => ``) 'alt-title': () => text || title,
'title-alt': () => title || text,
}[this.opts.legend] || (() => ``)
const subText = createSubText(transform()) const subText = createSubText(transform())
const figureStyles = this.getStyles(`figure`) const figureStyles = this.getStyles(`figure`)
const imgStyles = this.getStyles(`image`) const imgStyles = this.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>`
} }
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}" ${this.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) { if (this.opts.status) {
const ref = this.addFootnote(title || text, href) const ref = this.addFootnote(title || text, href)
return `<span ${this.getStyles( return `<span ${this.getStyles(
`link`, `link`,
)}>${text}<sup>[${ref}]</sup></span>` )}>${text}<sup>[${ref}]</sup></span>`
} }
return `<span ${this.getStyles(`link`)}>${text}</span>` return this.styledContent(`link`, text, `span`)
} }
strong = text => `<strong ${this.getStyles(`strong`)}>${text}</strong>` strong({ text }) {
return this.styledContent(`strong`, text)
}
em = text => `<span style="font-style: italic;">${text}</span>` em({ text }) {
return `<span style="font-style: italic;">${text}</span>`
}
table = (header, body) => ` 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 `
<section style="padding:0 8px;"> <section style="padding:0 8px;">
<table class="preview-table"> <table class="preview-table">
<thead ${this.getStyles(`thead`)}>${header}</thead> <thead ${this.getStyles(`thead`)}>${headerRow}</thead>
<tbody>${body}</tbody> <tbody>${body}</tbody>
</table> </table>
</section>` </section>`
}
tablecell = text => `<td ${this.getStyles(`td`)}>${text}</td>` tablecell({ text }) {
return this.styledContent(`td`, text)
}
hr = () => `<hr ${this.getStyles(`hr`)}/>` hr(_) {
return this.styledContent(`hr`, ``)
}
} }
export default WxRenderer export default WxRenderer

View File

@ -15,21 +15,14 @@ import RunLoading from '@/components/RunLoading.vue'
import { import {
ContextMenu, ContextMenu,
ContextMenuCheckboxItem,
ContextMenuContent, ContextMenuContent,
ContextMenuItem, ContextMenuItem,
ContextMenuLabel,
ContextMenuRadioGroup,
ContextMenuRadioItem,
ContextMenuSeparator, ContextMenuSeparator,
ContextMenuShortcut, ContextMenuShortcut,
ContextMenuSub,
ContextMenuSubContent,
ContextMenuSubTrigger,
ContextMenuTrigger, ContextMenuTrigger,
} from '@/components/ui/context-menu' } from '@/components/ui/context-menu'
import { altKey, altSign, ctrlKey, ctrlSign, shiftKey, shiftSign } from '@/config' import { altKey, altSign, ctrlKey, shiftKey, shiftSign } from '@/config'
import { import {
checkImage, checkImage,