1
0

feat: support custom emoji

This commit is contained in:
Anthony Fu 2022-11-21 15:14:07 +08:00
parent 193d1cf5c5
commit cefecb16a0
6 changed files with 48 additions and 26 deletions

View File

@ -14,9 +14,7 @@ defineProps<{
</NuxtLink> </NuxtLink>
</div> </div>
<NuxtLink flex flex-col :to="`/@${account.acct}`"> <NuxtLink flex flex-col :to="`/@${account.acct}`">
<h4 font-bold> <CommonRichContent font-bold :content="account.displayName" />
{{ account.displayName }}
</h4>
<p op35 text-sm> <p op35 text-sm>
@{{ account.acct }} @{{ account.acct }}
</p> </p>

View File

@ -9,6 +9,6 @@ defineProps<{
<template> <template>
<a :href="`/@${account.acct}`" flex gap-2 font-bold items-center> <a :href="`/@${account.acct}`" flex gap-2 font-bold items-center>
<img :src="account.avatar" class="w-5 h-5 rounded"> <img :src="account.avatar" class="w-5 h-5 rounded">
{{ account.displayName }} <CommonRichContent :content="account.displayName" />
</a> </a>
</template> </template>

View File

@ -1,3 +1,5 @@
import type { Emoji } from 'masto'
export default defineComponent({ export default defineComponent({
props: { props: {
content: { content: {
@ -6,6 +8,15 @@ export default defineComponent({
}, },
}, },
setup(props) { setup(props) {
return () => contentToVNode(props.content) const emojis = shallowRef<Record<string, Emoji>>({})
onMounted(() => {
const { server } = useAppCookies()
const { serverInfos } = useClientState()
if (server.value)
emojis.value = serverInfos.value[server.value].customEmojis || {}
})
return () => h('div', { class: 'rich-content' }, contentToVNode(props.content, undefined, emojis.value))
}, },
}) })

View File

@ -11,23 +11,3 @@ const { status } = defineProps<{
<CommonRichContent :content="status.content" /> <CommonRichContent :content="status.content" />
</div> </div>
</template> </template>
<style lang="postcss">
.status-body {
a {
--at-apply: text-primary hover:underline;
.invisible {
--at-apply: hidden;
}
.ellipsis {
--at-apply: truncate overflow-hidden ws-nowrap;
}
}
b {
--at-apply: font-bold;
}
p {
--at-apply: my-2;
}
}
</style>

View File

@ -1,3 +1,4 @@
import type { Emoji } from 'masto'
import type { DefaultTreeAdapterMap } from 'parse5' import type { DefaultTreeAdapterMap } from 'parse5'
import { parseFragment } from 'parse5' import { parseFragment } from 'parse5'
import type { VNode } from 'vue' import type { VNode } from 'vue'
@ -33,7 +34,14 @@ export function defaultHandle(el: Element) {
export function contentToVNode( export function contentToVNode(
content: string, content: string,
handle: (node: Element) => Element | undefined | null | void = defaultHandle, handle: (node: Element) => Element | undefined | null | void = defaultHandle,
customEmojis: Record<string, Emoji> = {},
): VNode { ): VNode {
content = content.replace(/:([\w-]+?):/g, (_, name) => {
const emoji = customEmojis[name]
if (emoji)
return `<img src="${emoji.url}" alt="${name}" class="custom-emoji" />`
return `:${name}:`
})
const tree = parseFragment(content) const tree = parseFragment(content)
return h(Fragment, tree.childNodes.map(n => treeToVNode(n, handle))) return h(Fragment, tree.childNodes.map(n => treeToVNode(n, handle)))
} }

View File

@ -27,4 +27,29 @@
/* Force vertical scrollbar to be always visible to avoid layout shift while loading the content */ /* Force vertical scrollbar to be always visible to avoid layout shift while loading the content */
html { html {
overflow-y: scroll; overflow-y: scroll;
} }
.custom-emoji {
display: inline-block;
height: 1.2em;
width: 1.2em;
vertical-align: middle;
}
.rich-content {
a {
--at-apply: text-primary hover:underline;
.invisible {
--at-apply: hidden;
}
.ellipsis {
--at-apply: truncate overflow-hidden ws-nowrap;
}
}
b {
--at-apply: font-bold;
}
p {
--at-apply: my-2;
}
}