feat: improve code block parsing
parent
2bf4f8913a
commit
8dd91002d7
|
@ -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'
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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"
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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"
|
||||||
>
|
>
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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 '@vueuse/core'
|
||||||
<pre lang=\\"ts\\">
|
|
||||||
import { useMouse, usePreferredDark } from '@vueuse/core'
|
|
||||||
// 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>
|
||||||
"
|
"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
|
|
@ -33,15 +33,27 @@ describe('content-rich', () => {
|
||||||
const { formatted } = await render('<p>Testing code block</p><p>```ts<br />import { useMouse, usePreferredDark } from '@vueuse/core'</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 '@vueuse/core'</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,
|
||||||
|
|
Loading…
Reference in New Issue