forked from Mirrors/elk
feat: show tag hover card when hovering cursor on hashtag links (#2621)
Co-authored-by: userquin <userquin@gmail.com>
This commit is contained in:
parent
0fa87f71a4
commit
e44833b18a
10 changed files with 120 additions and 28 deletions
45
components/account/TagHoverWrapper.vue
Normal file
45
components/account/TagHoverWrapper.vue
Normal 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>
|
|
@ -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>
|
||||||
|
|
|
@ -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 />
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
|
|
|
@ -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))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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.",
|
||||||
|
|
|
@ -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.",
|
||||||
|
|
|
@ -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')"
|
||||||
|
|
|
@ -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>
|
||||||
"
|
"
|
||||||
|
|
Loading…
Reference in a new issue