diff --git a/components/account/AccountDisplayName.vue b/components/account/AccountDisplayName.vue
index eb9a232a..758dbca2 100644
--- a/components/account/AccountDisplayName.vue
+++ b/components/account/AccountDisplayName.vue
@@ -4,12 +4,15 @@ import type { mastodon } from 'masto'
defineProps<{
account: mastodon.v1.Account
}>()
+
+const userSettings = useUserSettings()
diff --git a/components/content/ContentRich.setup.ts b/components/content/ContentRich.setup.ts
index 867d0659..52bb3c3a 100644
--- a/components/content/ContentRich.setup.ts
+++ b/components/content/ContentRich.setup.ts
@@ -7,10 +7,12 @@ defineOptions({
const {
content,
emojis,
+ showEmojis = true,
markdown = true,
} = defineProps<{
content: string
emojis?: mastodon.v1.CustomEmoji[]
+ showEmojis?: boolean
markdown?: boolean
}>()
@@ -21,6 +23,7 @@ export default () => h(
{ class: 'content-rich', dir: 'auto' },
contentToVNode(content, {
emojis: emojisObject.value,
+ showEmojis,
markdown,
}),
)
diff --git a/composables/content-parse.ts b/composables/content-parse.ts
index 797323a1..012ede3c 100644
--- a/composables/content-parse.ts
+++ b/composables/content-parse.ts
@@ -8,6 +8,7 @@ import { emojiRegEx, getEmojiAttributes } from '../config/emojis'
export interface ContentParseOptions {
emojis?: Record
+ showEmojis?: boolean
mentions?: mastodon.v1.StatusMention[]
markdown?: boolean
replaceUnicodeEmoji?: boolean
@@ -81,6 +82,7 @@ export function parseMastodonHTML(
replaceUnicodeEmoji = true,
convertMentionLink = false,
collapseMentionLink = false,
+ showEmojis = true,
mentions,
status,
inReplyToStatus,
@@ -108,8 +110,16 @@ export function parseMastodonHTML(
...options.astTransforms || [],
]
- if (replaceUnicodeEmoji)
- transforms.push(transformUnicodeEmoji)
+ if (showEmojis) {
+ if (replaceUnicodeEmoji)
+ transforms.push(transformUnicodeEmoji)
+
+ transforms.push(replaceCustomEmoji(options.emojis ?? {}))
+ }
+ else {
+ transforms.push(removeUnicodeEmoji)
+ transforms.push(removeCustomEmoji(options.emojis ?? {}))
+ }
if (markdown)
transforms.push(transformMarkdown)
@@ -120,8 +130,6 @@ export function parseMastodonHTML(
if (convertMentionLink)
transforms.push(transformMentionLink)
- transforms.push(replaceCustomEmoji(options.emojis || {}))
-
transforms.push(transformParagraphs)
if (collapseMentionLink)
@@ -329,6 +337,25 @@ function filterHref() {
}
}
+function removeUnicodeEmoji(node: Node) {
+ if (node.type !== TEXT_NODE)
+ return node
+
+ let start = 0
+
+ const matches = [] as (string | Node)[]
+ findAndReplaceEmojisInText(emojiRegEx, node.value, (match, result) => {
+ matches.push(result.slice(start).trimEnd())
+ start = result.length + match.match.length
+ return undefined
+ })
+ if (matches.length === 0)
+ return node
+
+ matches.push(node.value.slice(start))
+ return matches.filter(Boolean)
+}
+
function transformUnicodeEmoji(node: Node) {
if (node.type !== TEXT_NODE)
return node
@@ -350,6 +377,28 @@ function transformUnicodeEmoji(node: Node) {
return matches.filter(Boolean)
}
+function removeCustomEmoji(customEmojis: Record): Transform {
+ return (node) => {
+ if (node.type !== TEXT_NODE)
+ return node
+
+ const split = node.value.split(/\s?:([\w-]+?):/g)
+ if (split.length === 1)
+ return node
+
+ return split.map((name, i) => {
+ if (i % 2 === 0)
+ return name
+
+ const emoji = customEmojis[name] as mastodon.v1.CustomEmoji
+ if (!emoji)
+ return `:${name}:`
+
+ return ''
+ }).filter(Boolean)
+ }
+}
+
function replaceCustomEmoji(customEmojis: Record): Transform {
return (node) => {
if (node.type !== TEXT_NODE)
diff --git a/composables/content-render.ts b/composables/content-render.ts
index 3d736f84..0b3c49d8 100644
--- a/composables/content-render.ts
+++ b/composables/content-render.ts
@@ -10,6 +10,14 @@ import ContentCode from '~/components/content/ContentCode.vue'
import ContentMentionGroup from '~/components/content/ContentMentionGroup.vue'
import AccountHoverWrapper from '~/components/account/AccountHoverWrapper.vue'
+function getTexualAstComponents(astChildren: Node[]): string {
+ return astChildren
+ .filter(({ type }) => type === TEXT_NODE)
+ .map(({ value }) => value)
+ .reduce((accumulator, current) => accumulator + current, '')
+ .trim()
+}
+
/**
* Raw HTML to VNodes
*/
@@ -17,7 +25,14 @@ export function contentToVNode(
content: string,
options?: ContentParseOptions,
): VNode {
- const tree = parseMastodonHTML(content, options)
+ let tree = parseMastodonHTML(content, options)
+
+ const textContents = getTexualAstComponents(tree.children)
+
+ // if the username only contains emojis, we should probably show the emojis anyway to avoid a blank name
+ if (!options?.showEmojis && textContents.length === 0)
+ tree = parseMastodonHTML(content, { ...options, showEmojis: true })
+
return h(Fragment, (tree.children as Node[] || []).map(n => treeToVNode(n)))
}
diff --git a/composables/settings/definition.ts b/composables/settings/definition.ts
index 90423afc..820a1b95 100644
--- a/composables/settings/definition.ts
+++ b/composables/settings/definition.ts
@@ -14,6 +14,7 @@ export interface PreferencesSettings {
hideFavoriteCount: boolean
hideFollowerCount: boolean
hideTranslation: boolean
+ hideUsernameEmojis: boolean
hideAccountHoverCard: boolean
grayscaleMode: boolean
enableAutoplay: boolean
@@ -72,6 +73,7 @@ export const DEFAULT__PREFERENCES_SETTINGS: PreferencesSettings = {
hideFavoriteCount: false,
hideFollowerCount: false,
hideTranslation: false,
+ hideUsernameEmojis: false,
hideAccountHoverCard: false,
grayscaleMode: false,
enableAutoplay: true,
diff --git a/locales/en.json b/locales/en.json
index 2adb6ae1..b694e3c1 100644
--- a/locales/en.json
+++ b/locales/en.json
@@ -406,6 +406,7 @@
"hide_follower_count": "Hide follower count",
"hide_reply_count": "Hide reply count",
"hide_translation": "Hide translation",
+ "hide_username_emojis": "Hide username emojis",
"label": "Preferences",
"title": "Experimental Features",
"user_picker": "User Picker",
diff --git a/pages/[[server]]/@[account]/index.vue b/pages/[[server]]/@[account]/index.vue
index 027ac8f5..faba9dca 100644
--- a/pages/[[server]]/@[account]/index.vue
+++ b/pages/[[server]]/@[account]/index.vue
@@ -11,6 +11,8 @@ const { t } = useI18n()
const { data: account, pending, refresh } = $(await useAsyncData(() => fetchAccountByHandle(accountName).catch(() => null), { immediate: process.client, default: () => shallowRef() }))
const relationship = $computed(() => account ? useRelationship(account).value : undefined)
+const userSettings = useUserSettings()
+
onReactivated(() => {
// Silently update data when reentering the page
// The user will see the previous content first, and any changes will be updated to the UI when the request is completed
@@ -21,7 +23,11 @@ onReactivated(() => {
-
+
diff --git a/pages/settings/preferences/index.vue b/pages/settings/preferences/index.vue
index 4e76edcb..3f63a2db 100644
--- a/pages/settings/preferences/index.vue
+++ b/pages/settings/preferences/index.vue
@@ -73,6 +73,12 @@ const userSettings = useUserSettings()
>
{{ $t('settings.preferences.hide_follower_count') }}
+
+ {{ $t("settings.preferences.hide_username_emojis") }}
+
{{ $t('settings.preferences.title') }}