feat: support custom emoji
parent
193d1cf5c5
commit
cefecb16a0
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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))
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
|
@ -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>
|
|
||||||
|
|
|
@ -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)))
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,3 +28,28 @@
|
||||||
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue