feat: show tag hover card when hovering cursor on hashtag links (#2621)

Co-authored-by: userquin <userquin@gmail.com>
zio/stable
TAKAHASHI Shuuji 2024-03-05 01:45:25 +09:00 committed by GitHub
parent 0fa87f71a4
commit e44833b18a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 120 additions and 28 deletions

View File

@ -0,0 +1,45 @@
<script setup lang="ts">
import type { mastodon } from 'masto'
defineOptions({
inheritAttrs: false,
})
const { tagName, disabled } = defineProps<{
tagName?: string
disabled?: boolean
}>()
const tag = ref<mastodon.v1.Tag>()
const tagHover = ref()
const hovered = useElementHover(tagHover)
watch(hovered, (newHovered) => {
if (newHovered && tagName) {
fetchTag(tagName).then((t) => {
tag.value = t
})
}
})
const userSettings = useUserSettings()
</script>
<template>
<span ref="tagHover">
<VMenu
v-if="!disabled && !getPreferences(userSettings, 'hideTagHoverCard')"
placement="bottom-start"
:delay="{ show: 500, hide: 100 }"
v-bind="$attrs"
:close-on-content-click="false"
>
<slot />
<template #popper>
<TagCardSkeleton v-if="!tag" />
<TagCard v-else :tag="tag" />
</template>
</VMenu>
<slot v-else />
</span>
</template>

View File

@ -1,9 +1,7 @@
<script lang="ts" setup> <script setup lang="ts">
import type { mastodon } from 'masto' import type { mastodon } from 'masto'
const { const { tag } = defineProps<{
tag,
} = defineProps<{
tag: mastodon.v1.Tag tag: mastodon.v1.Tag
}>() }>()
@ -32,13 +30,14 @@ function go(evt: MouseEvent | KeyboardEvent) {
<template> <template>
<div <div
block p4 hover:bg-active flex justify-between cursor-pointer block p4 hover:bg-active flex justify-between cursor-pointer flex-gap-2
@click="onclick" @click="onclick"
@keydown.enter="onclick" @keydown.enter="onclick"
> >
<div flex flex-gap-2>
<TagActionButton :tag="tag" />
<div> <div>
<h4 flex items-center text-size-base leading-normal font-medium line-clamp-1 break-all ws-pre-wrap> <h4 flex items-center text-size-base leading-normal font-medium line-clamp-1 break-all ws-pre-wrap>
<TagActionButton :tag="tag" />
<bdi> <bdi>
<span>#</span> <span>#</span>
<span hover:underline>{{ tag.name }}</span> <span hover:underline>{{ tag.name }}</span>
@ -46,6 +45,7 @@ function go(evt: MouseEvent | KeyboardEvent) {
</h4> </h4>
<CommonTrending v-if="tag.history" :history="tag.history" text-sm text-secondary line-clamp-1 ws-pre-wrap break-all /> <CommonTrending v-if="tag.history" :history="tag.history" text-sm text-secondary line-clamp-1 ws-pre-wrap break-all />
</div> </div>
</div>
<div v-if="tag.history" flex items-center> <div v-if="tag.history" flex items-center>
<CommonTrendingCharts :history="tag.history" /> <CommonTrendingCharts :history="tag.history" />
</div> </div>

View File

@ -1,5 +1,5 @@
<template> <template>
<div p4 flex justify-between> <div p4 flex justify-between gap-4>
<div flex="~ col 1 gap-2"> <div flex="~ col 1 gap-2">
<div flex class="skeleton-loading-bg" h-5 w-30 rounded /> <div flex class="skeleton-loading-bg" h-5 w-30 rounded />
<div flex class="skeleton-loading-bg" h-4 w-45 rounded /> <div flex class="skeleton-loading-bg" h-4 w-45 rounded />

View File

@ -93,6 +93,23 @@ export async function fetchAccountByHandle(acct: string): Promise<mastodon.v1.Ac
return promise return promise
} }
export function fetchTag(tagName: string, force = false): Promise<mastodon.v1.Tag> {
const server = currentServer.value
const userId = currentUser.value?.account.id
const key = `${server}:${userId}:tag:${tagName}`
const cached = cache.get(key)
if (cached && !force)
return Promise.resolve(cached)
const promise = useMastoClient().v1.tags.$select(tagName).fetch()
.then((tag) => {
cacheTag(tag)
return tag
})
cache.set(key, promise)
return promise
}
export function useAccountById(id?: string | null) { export function useAccountById(id?: string | null) {
return useAsyncState(() => fetchAccountById(id), null).state return useAsyncState(() => fetchAccountById(id), null).state
} }
@ -113,3 +130,8 @@ export function cacheAccount(account: mastodon.v1.Account, server = currentServe
setCached(`${server}:${userId}:account:${account.id}`, account, override) setCached(`${server}:${userId}:account:${account.id}`, account, override)
setCached(`${server}:${userId}:account:${userAcct}`, account, override) setCached(`${server}:${userId}:account:${userAcct}`, account, override)
} }
export function cacheTag(tag: mastodon.v1.Tag, server = currentServer.value, override?: boolean) {
const userId = currentUser.value?.account.id
setCached(`${server}:${userId}:tag:${tag.name}`, tag, override)
}

View File

@ -10,6 +10,7 @@ import Emoji from '~/components/emoji/Emoji.vue'
import ContentCode from '~/components/content/ContentCode.vue' import ContentCode from '~/components/content/ContentCode.vue'
import ContentMentionGroup from '~/components/content/ContentMentionGroup.vue' import ContentMentionGroup from '~/components/content/ContentMentionGroup.vue'
import AccountHoverWrapper from '~/components/account/AccountHoverWrapper.vue' import AccountHoverWrapper from '~/components/account/AccountHoverWrapper.vue'
import TagHoverWrapper from '~/components/account/TagHoverWrapper.vue'
function getTextualAstComponents(astChildren: Node[]): string { function getTextualAstComponents(astChildren: Node[]): string {
return astChildren return astChildren
@ -128,11 +129,13 @@ function handleMention(el: Node) {
addBdiNode(el) addBdiNode(el)
return h(AccountHoverWrapper, { handle, class: 'inline-block' }, () => nodeToVNode(el)) return h(AccountHoverWrapper, { handle, class: 'inline-block' }, () => nodeToVNode(el))
} }
const matchTag = href.match(TagLinkRE) const matchTag = href.match(TagLinkRE)
if (matchTag) { if (matchTag) {
const [, , name] = matchTag const [, , tagName] = matchTag
addBdiNode(el) addBdiNode(el)
el.attributes.href = `/${currentServer.value}/tags/${name}` el.attributes.href = `/${currentServer.value}/tags/${tagName}`
return h(TagHoverWrapper, { tagName, class: 'inline-block' }, () => nodeToVNode(el))
} }
} }
} }

View File

@ -16,6 +16,7 @@ export interface PreferencesSettings {
hideTranslation: boolean hideTranslation: boolean
hideUsernameEmojis: boolean hideUsernameEmojis: boolean
hideAccountHoverCard: boolean hideAccountHoverCard: boolean
hideTagHoverCard: boolean
hideNews: boolean hideNews: boolean
grayscaleMode: boolean grayscaleMode: boolean
enableAutoplay: boolean enableAutoplay: boolean
@ -70,6 +71,7 @@ export const DEFAULT__PREFERENCES_SETTINGS: PreferencesSettings = {
hideTranslation: false, hideTranslation: false,
hideUsernameEmojis: false, hideUsernameEmojis: false,
hideAccountHoverCard: false, hideAccountHoverCard: false,
hideTagHoverCard: false,
hideNews: false, hideNews: false,
grayscaleMode: false, grayscaleMode: false,
enableAutoplay: true, enableAutoplay: true,

View File

@ -536,6 +536,7 @@
"hide_follower_count": "Hide following/follower count", "hide_follower_count": "Hide following/follower count",
"hide_news": "Hide news", "hide_news": "Hide news",
"hide_reply_count": "Hide reply count", "hide_reply_count": "Hide reply count",
"hide_tag_hover_card": "Hide tag hover card",
"hide_translation": "Hide translation", "hide_translation": "Hide translation",
"hide_username_emojis": "Hide username emojis", "hide_username_emojis": "Hide username emojis",
"hide_username_emojis_description": "Hides emojis from usernames in timelines. Emojis will still be visible in their profiles.", "hide_username_emojis_description": "Hides emojis from usernames in timelines. Emojis will still be visible in their profiles.",

View File

@ -536,6 +536,7 @@
"hide_follower_count": "Ocultar número de seguidores", "hide_follower_count": "Ocultar número de seguidores",
"hide_news": "Ocultar noticias", "hide_news": "Ocultar noticias",
"hide_reply_count": "Ocultar número de respuestas", "hide_reply_count": "Ocultar número de respuestas",
"hide_tag_hover_card": "Ocultar tarjeta flotante de etiqueta",
"hide_translation": "Ocultar traducción", "hide_translation": "Ocultar traducción",
"hide_username_emojis": "Ocultar emojis en el nombre de usuario", "hide_username_emojis": "Ocultar emojis en el nombre de usuario",
"hide_username_emojis_description": "Oculta los emojis de los nombres de usuarios en la línea de tiempo. Los emojis permanecerán visibles en sus perfiles.", "hide_username_emojis_description": "Oculta los emojis de los nombres de usuarios en la línea de tiempo. Los emojis permanecerán visibles en sus perfiles.",

View File

@ -27,6 +27,12 @@ const userSettings = useUserSettings()
> >
{{ $t('settings.preferences.hide_account_hover_card') }} {{ $t('settings.preferences.hide_account_hover_card') }}
</SettingsToggleItem> </SettingsToggleItem>
<SettingsToggleItem
:checked="getPreferences(userSettings, 'hideTagHoverCard')"
@click="togglePreferences('hideTagHoverCard')"
>
{{ $t('settings.preferences.hide_tag_hover_card') }}
</SettingsToggleItem>
<SettingsToggleItem <SettingsToggleItem
:checked="getPreferences(userSettings, 'enableAutoplay')" :checked="getPreferences(userSettings, 'enableAutoplay')"
:disabled="getPreferences(userSettings, 'enableDataSaving')" :disabled="getPreferences(userSettings, 'enableDataSaving')"

View File

@ -180,11 +180,18 @@ exports[`content-rich > handles html within code blocks 1`] = `
exports[`content-rich > hashtag adds bdi 1`] = ` exports[`content-rich > hashtag adds bdi 1`] = `
"<p> "<p>
Testing bdi is added Testing bdi is added
<a <span
><VMenu
placement="bottom-start"
class="inline-block"
close-on-content-click="false"
><a
class="mention hashtag" class="mention hashtag"
rel="nofollow noopener noreferrer" rel="nofollow noopener noreferrer"
to="/m.webtoo.ls/tags/turkey" to="/m.webtoo.ls/tags/turkey"
><bdi>#<span>turkey</span></bdi></a ><bdi>#<span>turkey</span></bdi></a
></VMenu
></span
> >
</p> </p>
<p></p> <p></p>
@ -194,12 +201,17 @@ exports[`content-rich > hashtag adds bdi 1`] = `
exports[`content-rich > hashtag doesn't add 2 bdi 1`] = ` exports[`content-rich > hashtag doesn't add 2 bdi 1`] = `
"<p> "<p>
Testing bdi not added Testing bdi not added
<a <span
><VMenu
placement="bottom-start"
class="inline-block"
close-on-content-click="false"
><a
class="mention hashtag" class="mention hashtag"
rel="nofollow noopener noreferrer" rel="nofollow noopener noreferrer"
to="/m.webtoo.ls/tags/turkey" to="/m.webtoo.ls/tags/turkey"
><bdi></bdi ><bdi></bdi></a></VMenu
></a> ></span>
</p> </p>
<p></p> <p></p>
" "