feat: improve code block parsing

zio/stable
Anthony Fu 2022-11-27 14:16:02 +08:00
parent 2bf4f8913a
commit 8dd91002d7
8 changed files with 39 additions and 17 deletions

View File

@ -1,4 +1,5 @@
<script setup lang="ts"> <script setup lang="ts">
// @ts-expect-error missing types
import { DynamicScroller } from 'vue-virtual-scroller' import { DynamicScroller } from 'vue-virtual-scroller'
import 'vue-virtual-scroller/dist/vue-virtual-scroller.css' import 'vue-virtual-scroller/dist/vue-virtual-scroller.css'
import type { Paginator } from 'masto' import type { Paginator } from 'masto'

View File

@ -11,7 +11,10 @@
<NavSideItem text="Conversations" to="/conversations" icon="i-ri:at-line" /> <NavSideItem text="Conversations" to="/conversations" icon="i-ri:at-line" />
<NavSideItem text="Favourites" to="/favourites" icon="i-ri:heart-3-line" /> <NavSideItem text="Favourites" to="/favourites" icon="i-ri:heart-3-line" />
<NavSideItem text="Bookmarks" to="/bookmarks" icon="i-ri:bookmark-line " /> <NavSideItem text="Bookmarks" to="/bookmarks" icon="i-ri:bookmark-line " />
<NavSideItem text="Lists" :to="getAccountPath(currentUser.account)" icon="i-ri:list-check-2-line"> <NavSideItem
:text="currentUser.account.displayName || 'Profile'"
:to="getAccountPath(currentUser.account)" icon="i-ri:list-check-2-line"
>
<template #icon> <template #icon>
<AccountAvatar :account="currentUser.account" h="1.2em" /> <AccountAvatar :account="currentUser.account" h="1.2em" />
</template> </template>

View File

@ -6,12 +6,13 @@ const { status, withAction = true } = defineProps<{
withAction?: boolean withAction?: boolean
}>() }>()
const { translation } = useTranslation(status) const { translation } = useTranslation(status)
const content = $computed(() => translation.visible ? translation.text : status.content) const content = $computed(() => translation.visible ? translation.text : status.content)
</script> </script>
<template> <template>
<div class="status-body" whitespace-pre-wrap break-words :class="{ 'with-action': withAction }"> <div class="status-body" whitespace-pre-wrap break-words :class="{ 'with-action': withAction }">
<ContentRich v-if="content" <ContentRich
v-if="content"
:content="content" :content="content"
:emojis="status.emojis" :emojis="status.emojis"
/> />

View File

@ -60,9 +60,10 @@ export function contentToVNode(
return `:${name}:` return `:${name}:`
}) })
// handle code frames // handle code frames
.replace(/<p>(```|~~~)([\w]*)([\s\S]+?)\1/g, (_1, _2, lang, raw) => { .replace(/>(```|~~~)([\s\S]+?)\1/g, (_1, _2, raw) => {
const code = htmlToText(`<p>${raw}</p>`) const plain = htmlToText(raw)
return `<custom-code lang="${lang?.trim().toLowerCase() || ''}" code="${encodeURIComponent(code)}"></custom-code>` const [lang, ...code] = plain.split('\n')
return `><custom-code lang="${lang?.trim().toLowerCase() || ''}" code="${encodeURIComponent(code.join('\n'))}" />`
}) })
const tree = parseFragment(content) const tree = parseFragment(content)

View File

@ -23,7 +23,6 @@
<UserSignInEntry v-if="!currentUser" /> <UserSignInEntry v-if="!currentUser" />
<VDropdown <VDropdown
v-if="currentUser" v-if="currentUser"
:triggers="['click', 'focus']"
:distance="0" :distance="0"
placement="bottom-end" placement="bottom-end"
> >

View File

@ -69,7 +69,7 @@ html {
} }
.code-block { .code-block {
--at-apply: bg-code text-0.9rem p3 rounded overflow-auto leading-1.6em; --at-apply: bg-code text-0.9rem p3 mt-2 rounded overflow-auto leading-1.6em;
.shiki { .shiki {
background: transparent !important; background: transparent !important;

View File

@ -1,15 +1,20 @@
// Vitest Snapshot v1 // Vitest Snapshot v1
exports[`content-rich > code frame 1`] = ` exports[`content-rich > code frame 1`] = `
"<p>Testing code block</p> "<p>Testing code block</p><p><pre lang=\\"ts\\">import { useMouse, usePreferredDark } from &#39;@vueuse/core&#39;
<pre lang=\\"ts\\">
import { useMouse, usePreferredDark } from &#39;@vueuse/core&#39;
// tracks mouse position // tracks mouse position
const { x, y } = useMouse() const { x, y } = useMouse()
// is the user prefers dark theme // is the user prefers dark theme
const isDark = usePreferredDark()</pre const isDark = usePreferredDark()</pre></p>"
> `;
<p></p>
exports[`content-rich > code frame 2 1`] = `
"<p>
<span class=\\"h-card\\"><a class=\\"u-url mention\\" to=\\"/@antfu@mas.to\\"></a></span>
Testing<br />
<pre lang=\\"ts\\">const a = hello</pre>
</p>
" "
`; `;

View File

@ -33,15 +33,27 @@ describe('content-rich', () => {
const { formatted } = await render('<p>Testing code block</p><p>```ts<br />import { useMouse, usePreferredDark } from &#39;@vueuse/core&#39;</p><p>// tracks mouse position<br />const { x, y } = useMouse()</p><p>// is the user prefers dark theme<br />const isDark = usePreferredDark()<br />```</p>') const { formatted } = await render('<p>Testing code block</p><p>```ts<br />import { useMouse, usePreferredDark } from &#39;@vueuse/core&#39;</p><p>// tracks mouse position<br />const { x, y } = useMouse()</p><p>// is the user prefers dark theme<br />const isDark = usePreferredDark()<br />```</p>')
expect(formatted).toMatchSnapshot() expect(formatted).toMatchSnapshot()
}) })
it('code frame 2', async () => {
const { formatted } = await render('<p><span class=\"h-card\"><a href=\"https://mas.to/@antfu\" class=\"u-url mention\">@<span>antfu</span></a></span> Testing<br />```ts<br />const a = hello<br />```</p>')
expect(formatted).toMatchSnapshot()
})
}) })
async function render(content: string, emojis?: Record<string, Emoji>) { async function render(content: string, emojis?: Record<string, Emoji>) {
const vnode = contentToVNode(content, emojis) const vnode = contentToVNode(content, emojis)
const html = (await renderToString(vnode)) const html = (await renderToString(vnode))
.replace(/<!--[\[\]]-->/g, '') .replace(/<!--[\[\]]-->/g, '')
const formatted = format(html, { let formatted = ''
parser: 'html',
}) try {
formatted = format(html, {
parser: 'html',
})
}
catch (e) {
formatted = html
}
return { return {
vnode, vnode,