forked from Mirrors/elk
feat: hide redudant mentions (#1293)
This commit is contained in:
parent
da2f19fb23
commit
3132f4fdea
9 changed files with 186 additions and 30 deletions
|
@ -3,9 +3,11 @@ import type { mastodon } from 'masto'
|
||||||
|
|
||||||
const {
|
const {
|
||||||
status,
|
status,
|
||||||
|
newer,
|
||||||
withAction = true,
|
withAction = true,
|
||||||
} = defineProps<{
|
} = defineProps<{
|
||||||
status: mastodon.v1.Status | mastodon.v1.StatusEdit
|
status: mastodon.v1.Status | mastodon.v1.StatusEdit
|
||||||
|
newer?: mastodon.v1.Status
|
||||||
withAction?: boolean
|
withAction?: boolean
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
|
@ -20,13 +22,15 @@ const vnode = $computed(() => {
|
||||||
mentions: 'mentions' in status ? status.mentions : undefined,
|
mentions: 'mentions' in status ? status.mentions : undefined,
|
||||||
markdown: true,
|
markdown: true,
|
||||||
collapseMentionLink: !!('inReplyToId' in status && status.inReplyToId),
|
collapseMentionLink: !!('inReplyToId' in status && status.inReplyToId),
|
||||||
|
status: 'id' in status ? status : undefined,
|
||||||
|
inReplyToStatus: newer,
|
||||||
})
|
})
|
||||||
return vnode
|
return vnode
|
||||||
})
|
})
|
||||||
</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 }" relative>
|
||||||
<span
|
<span
|
||||||
v-if="status.content"
|
v-if="status.content"
|
||||||
class="content-rich line-compact" dir="auto"
|
class="content-rich line-compact" dir="auto"
|
||||||
|
|
|
@ -22,6 +22,7 @@ const props = withDefaults(
|
||||||
}>(),
|
}>(),
|
||||||
{ actions: true },
|
{ actions: true },
|
||||||
)
|
)
|
||||||
|
|
||||||
const userSettings = useUserSettings()
|
const userSettings = useUserSettings()
|
||||||
|
|
||||||
const status = $computed(() => {
|
const status = $computed(() => {
|
||||||
|
@ -183,7 +184,7 @@ const showReplyTo = $computed(() => !replyToMain && !directReply)
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Content -->
|
<!-- Content -->
|
||||||
<StatusContent :status="status" :context="context" mb2 :class="{ 'mt-2 mb1': isDM }" />
|
<StatusContent :status="status" :newer="newer" :context="context" mb2 :class="{ 'mt-2 mb1': isDM }" />
|
||||||
<StatusActions v-if="actions !== false" v-show="!userSettings.zenMode" :status="status" />
|
<StatusActions v-if="actions !== false" v-show="!userSettings.zenMode" :status="status" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -3,6 +3,7 @@ import type { mastodon } from 'masto'
|
||||||
|
|
||||||
const { status, context } = defineProps<{
|
const { status, context } = defineProps<{
|
||||||
status: mastodon.v1.Status
|
status: mastodon.v1.Status
|
||||||
|
newer?: mastodon.v1.Status
|
||||||
context?: mastodon.v2.FilterContext | 'details'
|
context?: mastodon.v2.FilterContext | 'details'
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
|
@ -25,7 +26,7 @@ const isFiltered = $computed(() => filterPhrase && (context && context !== 'deta
|
||||||
'ms--3.5 mt--1 ms--1': isDM && context !== 'details',
|
'ms--3.5 mt--1 ms--1': isDM && context !== 'details',
|
||||||
}"
|
}"
|
||||||
>
|
>
|
||||||
<StatusBody v-if="!isFiltered && status.sensitive && !status.spoilerText" :status="status" :with-action="!isDetails" :class="isDetails ? 'text-xl' : ''" />
|
<StatusBody v-if="!isFiltered && status.sensitive && !status.spoilerText" :status="status" :newer="newer" :with-action="!isDetails" :class="isDetails ? 'text-xl' : ''" />
|
||||||
<StatusSpoiler :enabled="status.sensitive || isFiltered" :filter="isFiltered">
|
<StatusSpoiler :enabled="status.sensitive || isFiltered" :filter="isFiltered">
|
||||||
<template v-if="filterPhrase" #spoiler>
|
<template v-if="filterPhrase" #spoiler>
|
||||||
<p>{{ `${$t('status.filter_hidden_phrase')}: ${filterPhrase}` }}</p>
|
<p>{{ `${$t('status.filter_hidden_phrase')}: ${filterPhrase}` }}</p>
|
||||||
|
@ -33,7 +34,7 @@ const isFiltered = $computed(() => filterPhrase && (context && context !== 'deta
|
||||||
<template v-else-if="status.spoilerText" #spoiler>
|
<template v-else-if="status.spoilerText" #spoiler>
|
||||||
<p>{{ status.spoilerText }}</p>
|
<p>{{ status.spoilerText }}</p>
|
||||||
</template>
|
</template>
|
||||||
<StatusBody v-if="!status.sensitive || status.spoilerText" :status="status" :with-action="!isDetails" :class="isDetails ? 'text-xl' : ''" />
|
<StatusBody v-if="!status.sensitive || status.spoilerText" :status="status" :newer="newer" :with-action="!isDetails" :class="isDetails ? 'text-xl' : ''" />
|
||||||
<StatusTranslation :status="status" />
|
<StatusTranslation :status="status" />
|
||||||
<StatusPoll v-if="status.poll" :status="status" />
|
<StatusPoll v-if="status.poll" :status="status" />
|
||||||
<StatusMedia
|
<StatusMedia
|
||||||
|
|
|
@ -3,6 +3,7 @@ import type { mastodon } from 'masto'
|
||||||
|
|
||||||
const props = withDefaults(defineProps<{
|
const props = withDefaults(defineProps<{
|
||||||
status: mastodon.v1.Status
|
status: mastodon.v1.Status
|
||||||
|
newer?: mastodon.v1.Status
|
||||||
command?: boolean
|
command?: boolean
|
||||||
actions?: boolean
|
actions?: boolean
|
||||||
}>(), {
|
}>(), {
|
||||||
|
@ -36,7 +37,7 @@ const isDM = $computed(() => status.visibility === 'direct')
|
||||||
<AccountInfo :account="status.account" />
|
<AccountInfo :account="status.account" />
|
||||||
</AccountHoverWrapper>
|
</AccountHoverWrapper>
|
||||||
</NuxtLink>
|
</NuxtLink>
|
||||||
<StatusContent :status="status" context="details" />
|
<StatusContent :status="status" :newer="newer" context="details" />
|
||||||
<div flex="~ gap-1" items-center text-secondary text-sm>
|
<div flex="~ gap-1" items-center text-secondary text-sm>
|
||||||
<div flex>
|
<div flex>
|
||||||
<div>{{ createdAt }}</div>
|
<div>{{ createdAt }}</div>
|
||||||
|
|
|
@ -14,6 +14,8 @@ export interface ContentParseOptions {
|
||||||
astTransforms?: Transform[]
|
astTransforms?: Transform[]
|
||||||
convertMentionLink?: boolean
|
convertMentionLink?: boolean
|
||||||
collapseMentionLink?: boolean
|
collapseMentionLink?: boolean
|
||||||
|
status?: mastodon.v1.Status
|
||||||
|
inReplyToStatus?: mastodon.v1.Status
|
||||||
}
|
}
|
||||||
|
|
||||||
const sanitizerBasicClasses = filterClasses(/^(h-\S*|p-\S*|u-\S*|dt-\S*|e-\S*|mention|hashtag|ellipsis|invisible)$/u)
|
const sanitizerBasicClasses = filterClasses(/^(h-\S*|p-\S*|u-\S*|dt-\S*|e-\S*|mention|hashtag|ellipsis|invisible)$/u)
|
||||||
|
@ -80,6 +82,8 @@ export function parseMastodonHTML(
|
||||||
convertMentionLink = false,
|
convertMentionLink = false,
|
||||||
collapseMentionLink = false,
|
collapseMentionLink = false,
|
||||||
mentions,
|
mentions,
|
||||||
|
status,
|
||||||
|
inReplyToStatus,
|
||||||
} = options
|
} = options
|
||||||
|
|
||||||
if (markdown) {
|
if (markdown) {
|
||||||
|
@ -121,7 +125,7 @@ export function parseMastodonHTML(
|
||||||
transforms.push(transformParagraphs)
|
transforms.push(transformParagraphs)
|
||||||
|
|
||||||
if (collapseMentionLink)
|
if (collapseMentionLink)
|
||||||
transforms.push(transformCollapseMentions())
|
transforms.push(transformCollapseMentions(status, inReplyToStatus))
|
||||||
|
|
||||||
return transformSync(parse(html), transforms)
|
return transformSync(parse(html), transforms)
|
||||||
}
|
}
|
||||||
|
@ -443,12 +447,22 @@ function transformParagraphs(node: Node): Node | Node[] {
|
||||||
return node
|
return node
|
||||||
}
|
}
|
||||||
|
|
||||||
function transformCollapseMentions() {
|
function isMention(node: Node) {
|
||||||
|
const child = node.children?.length === 1 ? node.children[0] : null
|
||||||
|
return Boolean(child?.name === 'a' && child.attributes.class?.includes('mention'))
|
||||||
|
}
|
||||||
|
|
||||||
|
function isSpacing(node: Node) {
|
||||||
|
return node.type === TEXT_NODE && !node.value.trim()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract the username from a known mention node
|
||||||
|
function getMentionHandle(node: Node): string | undefined {
|
||||||
|
return hrefToHandle(node.children?.[0].attributes.href) // node.children?.[0]?.children?.[0]?.attributes?.['data-id']
|
||||||
|
}
|
||||||
|
|
||||||
|
function transformCollapseMentions(status?: mastodon.v1.Status, inReplyToStatus?: mastodon.v1.Status): Transform {
|
||||||
let processed = false
|
let processed = false
|
||||||
function isMention(node: Node) {
|
|
||||||
const child = node.children?.length === 1 ? node.children[0] : null
|
|
||||||
return Boolean(child?.name === 'a' && child.attributes.class?.includes('mention'))
|
|
||||||
}
|
|
||||||
|
|
||||||
return (node: Node, root: Node): Node | Node[] => {
|
return (node: Node, root: Node): Node | Node[] => {
|
||||||
if (processed || node.parent !== root || !node.children)
|
if (processed || node.parent !== root || !node.children)
|
||||||
|
@ -456,12 +470,12 @@ function transformCollapseMentions() {
|
||||||
const mentions: (Node | undefined)[] = []
|
const mentions: (Node | undefined)[] = []
|
||||||
const children = node.children as Node[]
|
const children = node.children as Node[]
|
||||||
for (const child of children) {
|
for (const child of children) {
|
||||||
// metion
|
// mention
|
||||||
if (isMention(child)) {
|
if (isMention(child)) {
|
||||||
mentions.push(child)
|
mentions.push(child)
|
||||||
}
|
}
|
||||||
// spaces in between
|
// spaces in between
|
||||||
else if (child.type === TEXT_NODE && !child.value.trim()) {
|
else if (isSpacing(child)) {
|
||||||
mentions.push(child)
|
mentions.push(child)
|
||||||
}
|
}
|
||||||
// other content, stop collapsing
|
// other content, stop collapsing
|
||||||
|
@ -478,21 +492,55 @@ function transformCollapseMentions() {
|
||||||
if (mentions.length === 0)
|
if (mentions.length === 0)
|
||||||
return node
|
return node
|
||||||
|
|
||||||
|
let removeNextSpacing = false
|
||||||
|
const contextualMentions = mentions.filter((mention) => {
|
||||||
|
if (!mention)
|
||||||
|
return false
|
||||||
|
|
||||||
|
if (removeNextSpacing && isSpacing(mention)) {
|
||||||
|
removeNextSpacing = false
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isMention(mention) && inReplyToStatus) {
|
||||||
|
const mentionHandle = getMentionHandle(mention)
|
||||||
|
if (inReplyToStatus.account.acct === mentionHandle || inReplyToStatus.mentions.some(m => m.acct === mentionHandle))
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
|
||||||
|
const mentionsCount = contextualMentions.filter(m => m && isMention(m)).length
|
||||||
|
|
||||||
|
// We have a special case for single mentions that are part of a reply.
|
||||||
|
// We already have the replying to badge in this case or the status is connected to the previous one.
|
||||||
|
// This is needed because the status doesn't included the in Reply to handle, only the account id.
|
||||||
|
// But this covers the majority of cases.
|
||||||
|
const showMentions = !(mentionsCount === 0 || (mentionsCount === 1 && status?.inReplyToAccountId))
|
||||||
|
|
||||||
|
const contextualChildren = children.slice(mentions.length)
|
||||||
return {
|
return {
|
||||||
...node,
|
...node,
|
||||||
children: [h('mention-group', null, ...mentions.filter(Boolean)), ...children.slice(mentions.length)],
|
children: showMentions ? [h('mention-group', null, ...contextualMentions), ...contextualChildren] : contextualChildren,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function hrefToHandle(href: string): string | undefined {
|
||||||
|
const matchUser = href.match(UserLinkRE)
|
||||||
|
if (matchUser) {
|
||||||
|
const [, server, username] = matchUser
|
||||||
|
return `${username}@${server.replace(/(.+\.)(.+\..+)/, '$2')}`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function transformMentionLink(node: Node): string | Node | (string | Node)[] | null {
|
function transformMentionLink(node: Node): string | Node | (string | Node)[] | null {
|
||||||
if (node.name === 'a' && node.attributes.class?.includes('mention')) {
|
if (node.name === 'a' && node.attributes.class?.includes('mention')) {
|
||||||
const href = node.attributes.href
|
const href = node.attributes.href
|
||||||
if (href) {
|
if (href) {
|
||||||
const matchUser = href.match(UserLinkRE)
|
const handle = hrefToHandle(href)
|
||||||
if (matchUser) {
|
if (handle) {
|
||||||
const [, server, username] = matchUser
|
|
||||||
const handle = `${username}@${server.replace(/(.+\.)(.+\..+)/, '$2')}`
|
|
||||||
// convert to Tiptap mention node
|
// convert to Tiptap mention node
|
||||||
return h('span', { 'data-type': 'mention', 'data-id': handle }, handle)
|
return h('span', { 'data-type': 'mention', 'data-id': handle }, handle)
|
||||||
}
|
}
|
||||||
|
|
|
@ -68,18 +68,19 @@ onReactivated(() => {
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<MainContent back>
|
<MainContent back>
|
||||||
<template v-if="!pending">
|
<template v-if="!pending && !pendingContext">
|
||||||
<div v-if="status" xl:mt-4 border="b base" mb="50vh">
|
<div v-if="status" xl:mt-4 border="b base" mb="50vh">
|
||||||
<template v-for="comment of context?.ancestors" :key="comment.id">
|
<template v-for="comment, i of context?.ancestors" :key="comment.id">
|
||||||
<StatusCard
|
<StatusCard
|
||||||
:status="comment" :actions="comment.visibility !== 'direct'" context="account"
|
:status="comment" :actions="comment.visibility !== 'direct'" context="account"
|
||||||
:has-older="true" :has-newer="true"
|
:has-older="true" :newer="context?.ancestors[i - 1]"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<StatusDetails
|
<StatusDetails
|
||||||
ref="main"
|
ref="main"
|
||||||
:status="status"
|
:status="status"
|
||||||
|
:newer="context?.ancestors.at(-1)"
|
||||||
command
|
command
|
||||||
style="scroll-margin-top: 60px"
|
style="scroll-margin-top: 60px"
|
||||||
/>
|
/>
|
||||||
|
@ -105,7 +106,7 @@ onReactivated(() => {
|
||||||
:status="item"
|
:status="item"
|
||||||
context="account"
|
context="account"
|
||||||
:older="context?.descendants[index + 1]"
|
:older="context?.descendants[index + 1]"
|
||||||
:newer="context?.descendants[index - 1]"
|
:newer="index > 0 ? context?.descendants[index - 1] : status"
|
||||||
:has-newer="index === 0"
|
:has-newer="index === 0"
|
||||||
:main="status"
|
:main="status"
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -92,6 +92,25 @@ exports[`html-parse > empty > html 1`] = `""`;
|
||||||
|
|
||||||
exports[`html-parse > empty > text 1`] = `""`;
|
exports[`html-parse > empty > text 1`] = `""`;
|
||||||
|
|
||||||
|
exports[`html-parse > hide mentions in context > html 1`] = `
|
||||||
|
"<p>
|
||||||
|
<mention-group
|
||||||
|
><span class=\\"h-card\\"
|
||||||
|
><a
|
||||||
|
href=\\"/@haoqun@webtoo.ls\\"
|
||||||
|
class=\\"u-url mention\\"
|
||||||
|
rel=\\"nofollow noopener noreferrer\\"
|
||||||
|
target=\\"_blank\\"
|
||||||
|
>@<span>haoqun</span></a
|
||||||
|
></span
|
||||||
|
></mention-group
|
||||||
|
>Great to see this happening
|
||||||
|
</p>
|
||||||
|
"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`html-parse > hide mentions in context > text 1`] = `"@haoqunGreat to see this happening"`;
|
||||||
|
|
||||||
exports[`html-parse > html entities > html 1`] = `
|
exports[`html-parse > html entities > html 1`] = `
|
||||||
"<p>Hello <World />.</p>
|
"<p>Hello <World />.</p>
|
||||||
"
|
"
|
||||||
|
@ -142,3 +161,50 @@ exports[`html-parse > link + mention > html 1`] = `
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`html-parse > link + mention > text 1`] = `"Happy 🤗 we’re now using @vitest (migrated from chai+mocha) https://github.com/ayoayco/astro-reactive-library/pull/203"`;
|
exports[`html-parse > link + mention > text 1`] = `"Happy 🤗 we’re now using @vitest (migrated from chai+mocha) https://github.com/ayoayco/astro-reactive-library/pull/203"`;
|
||||||
|
|
||||||
|
exports[`html-parse > mentions without context > html 1`] = `
|
||||||
|
"<p>
|
||||||
|
<mention-group
|
||||||
|
><span class=\\"h-card\\"
|
||||||
|
><a
|
||||||
|
href=\\"/@haoqun@webtoo.ls\\"
|
||||||
|
class=\\"u-url mention\\"
|
||||||
|
rel=\\"nofollow noopener noreferrer\\"
|
||||||
|
target=\\"_blank\\"
|
||||||
|
>@<span>haoqun</span></a
|
||||||
|
></span
|
||||||
|
></mention-group
|
||||||
|
>Great to see this happening
|
||||||
|
</p>
|
||||||
|
"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`html-parse > mentions without context > text 1`] = `"@haoqunGreat to see this happening"`;
|
||||||
|
|
||||||
|
exports[`html-parse > show mentions in context > html 1`] = `
|
||||||
|
"<p>
|
||||||
|
<mention-group
|
||||||
|
><span class=\\"h-card\\"
|
||||||
|
><a
|
||||||
|
href=\\"/@haoqun@webtoo.ls\\"
|
||||||
|
class=\\"u-url mention\\"
|
||||||
|
rel=\\"nofollow noopener noreferrer\\"
|
||||||
|
target=\\"_blank\\"
|
||||||
|
>@<span>haoqun</span></a
|
||||||
|
></span
|
||||||
|
>
|
||||||
|
<span class=\\"h-card\\"
|
||||||
|
><a
|
||||||
|
href=\\"/@antfu@webtoo.ls\\"
|
||||||
|
class=\\"u-url mention\\"
|
||||||
|
rel=\\"nofollow noopener noreferrer\\"
|
||||||
|
target=\\"_blank\\"
|
||||||
|
>@<span>antfu</span></a
|
||||||
|
></span
|
||||||
|
></mention-group
|
||||||
|
>Great to see this happening
|
||||||
|
</p>
|
||||||
|
"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`html-parse > show mentions in context > text 1`] = `"@haoqun @antfuGreat to see this happening"`;
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
import { describe, expect, it, vi } from 'vitest'
|
import { describe, expect, it, vi } from 'vitest'
|
||||||
import { renderToString } from 'vue/server-renderer'
|
import { renderToString } from 'vue/server-renderer'
|
||||||
import { format } from 'prettier'
|
import { format } from 'prettier'
|
||||||
|
import type { mastodon } from 'masto'
|
||||||
import { contentToVNode } from '~/composables/content-render'
|
import { contentToVNode } from '~/composables/content-render'
|
||||||
import type { ContentParseOptions } from '~~/composables/content-parse'
|
import type { ContentParseOptions } from '~~/composables/content-parse'
|
||||||
|
|
||||||
|
@ -139,6 +140,37 @@ describe('content-rich', () => {
|
||||||
`)
|
`)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('hides collapsed mentions', async () => {
|
||||||
|
const { formatted } = await render('<p><span class="h-card"><a href="https://m.webtoo.ls/@elk" class="u-url mention" rel="nofollow noopener noreferrer" target="_blank">@<span>elk</span></a></span> content</p>', {
|
||||||
|
collapseMentionLink: true,
|
||||||
|
inReplyToStatus: { account: { acct: 'elk@webtoo.ls' }, mentions: [] as mastodon.v1.StatusMention[] } as mastodon.v1.Status,
|
||||||
|
})
|
||||||
|
expect(formatted).toMatchInlineSnapshot(`
|
||||||
|
"<p>content</p>
|
||||||
|
"
|
||||||
|
`)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('shows some collapsed mentions', async () => {
|
||||||
|
const { formatted } = await render('<p><span class="h-card"><a href="https://m.webtoo.ls/@elk" class="u-url mention" rel="nofollow noopener noreferrer" target="_blank">@<span>elk</span></a></span> <span class="h-card"><a href="https://m.webtoo.ls/@antfu" class="u-url mention" rel="nofollow noopener noreferrer" target="_blank">@<span>antfu</span></a></span> content</p>', {
|
||||||
|
collapseMentionLink: true,
|
||||||
|
inReplyToStatus: { account: { acct: 'elk@webtoo.ls' }, mentions: [] as mastodon.v1.StatusMention[] } as mastodon.v1.Status,
|
||||||
|
})
|
||||||
|
expect(formatted).toMatchInlineSnapshot(`
|
||||||
|
"<p>
|
||||||
|
<mention-group>
|
||||||
|
<span class=\\"h-card\\"
|
||||||
|
><a
|
||||||
|
class=\\"u-url mention\\"
|
||||||
|
rel=\\"nofollow noopener noreferrer\\"
|
||||||
|
to=\\"/m.webtoo.ls/@antfu\\"
|
||||||
|
></a></span></mention-group
|
||||||
|
>content
|
||||||
|
</p>
|
||||||
|
"
|
||||||
|
`)
|
||||||
|
})
|
||||||
|
|
||||||
it ('block with injected html, without language', async () => {
|
it ('block with injected html, without language', async () => {
|
||||||
const { formatted } = await render(`
|
const { formatted } = await render(`
|
||||||
<pre>
|
<pre>
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import type { mastodon } from 'masto'
|
|
||||||
import { describe, expect, it } from 'vitest'
|
import { describe, expect, it } from 'vitest'
|
||||||
import { format } from 'prettier'
|
import { format } from 'prettier'
|
||||||
import { render as renderTree } from 'ultrahtml'
|
import { render as renderTree } from 'ultrahtml'
|
||||||
|
import type { ContentParseOptions } from '~~/composables/content-parse'
|
||||||
|
|
||||||
describe('html-parse', () => {
|
describe('html-parse', () => {
|
||||||
it('empty', async () => {
|
it('empty', async () => {
|
||||||
|
@ -19,11 +19,13 @@ describe('html-parse', () => {
|
||||||
|
|
||||||
it('custom emoji', async () => {
|
it('custom emoji', async () => {
|
||||||
const { formatted, serializedText } = await render('Daniel Roe :nuxt:', {
|
const { formatted, serializedText } = await render('Daniel Roe :nuxt:', {
|
||||||
nuxt: {
|
emojis: {
|
||||||
shortcode: 'nuxt',
|
nuxt: {
|
||||||
url: 'https://media.webtoo.ls/custom_emojis/images/000/000/366/original/73330dfc9dda4078.png',
|
shortcode: 'nuxt',
|
||||||
staticUrl: 'https://media.webtoo.ls/custom_emojis/images/000/000/366/original/73330dfc9dda4078.png',
|
url: 'https://media.webtoo.ls/custom_emojis/images/000/000/366/original/73330dfc9dda4078.png',
|
||||||
visibleInPicker: true,
|
staticUrl: 'https://media.webtoo.ls/custom_emojis/images/000/000/366/original/73330dfc9dda4078.png',
|
||||||
|
visibleInPicker: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
expect(formatted).toMatchSnapshot('html')
|
expect(formatted).toMatchSnapshot('html')
|
||||||
|
@ -62,8 +64,8 @@ describe('html-parse', () => {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
async function render(input: string, emojis?: Record<string, mastodon.v1.CustomEmoji>) {
|
async function render(input: string, options?: ContentParseOptions) {
|
||||||
const tree = parseMastodonHTML(input, { emojis })
|
const tree = parseMastodonHTML(input, options)
|
||||||
const html = await renderTree(tree)
|
const html = await renderTree(tree)
|
||||||
let formatted = ''
|
let formatted = ''
|
||||||
const serializedText = treeToText(tree).trim()
|
const serializedText = treeToText(tree).trim()
|
||||||
|
|
Loading…
Reference in a new issue