Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Merged
merged 30 commits into from
Mar 4, 2024
Merged
Show file tree
Hide file tree
Changes from 22 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
385fa0f
feat: show tag hover card when hovering cursor on hashtag links
shuuji3 Feb 25, 2024
0d3ec43
text: update snapshot as `TagHoverWrapper` adds one `<span>` wrapper
shuuji3 Feb 25, 2024
381d166
fix: avoid aggressive tag fetch call by moving `fetchTag()` to `TagCa…
shuuji3 Feb 25, 2024
32b1151
fix: adjust tag hovercard layout
shuuji3 Feb 25, 2024
de8e26a
test: update vitest snapshot
shuuji3 Feb 25, 2024
f7c376a
chore: avoid fetching card until visible
userquin Feb 25, 2024
9b3184d
Revert "test: update vitest snapshot"
shuuji3 Feb 25, 2024
1019773
fix: remove unnecessary async
shuuji3 Feb 25, 2024
c1bce74
feat: improve tag card layout
shuuji3 Feb 25, 2024
acd15de
test: set `test.pool` = `forks` to fix pending test issue
shuuji3 Feb 25, 2024
3ed696f
fix: store a resolved promise when resolving cache result
userquin Feb 25, 2024
3219d65
fix(cache): cache fetch promises until resolved
userquin Feb 25, 2024
4bb6bdb
fix(cache): cache fetch promises until resolved also for account by id
userquin Feb 25, 2024
23bc2e9
chore: don't store promise, return the promise when requested
userquin Feb 25, 2024
9b8056f
chore: .
userquin Feb 25, 2024
89f0a47
chore: rever account hover changes
userquin Feb 25, 2024
0056238
chore: revert fetch promises
userquin Feb 25, 2024
b84c7f0
chore: remove pool forks
userquin Feb 25, 2024
2215703
chore: don't fetch when running tests
userquin Feb 25, 2024
da7dddd
chore: return resolved promise when cached instead the cached
userquin Feb 25, 2024
35cc206
chore: include `hide_account_and_tag_hover_card` translation for es-ES
userquin Feb 25, 2024
0f05142
chore: cleanup tag hover wrapper
userquin Feb 25, 2024
d19770d
feat: split `hideAccountHoverCard` and `hideTagHoverCard` settings
shuuji3 Feb 26, 2024
f6b2471
feat: fetch tag cache just once after hovering
shuuji3 Feb 26, 2024
db070ee
test: update vitest snapshot
shuuji3 Feb 27, 2024
8364abc
fix: check hovered value in watch and fetch tag cache each mouse hove…
shuuji3 Feb 27, 2024
d327c60
feat: show TagCardSkeleton during loading tag data
shuuji3 Feb 28, 2024
f67be19
refactor: use `useElementHover` for hover state detection
shuuji3 Feb 28, 2024
95fdea6
fix: revert show delay to initial 500ms included by mistake
shuuji3 Feb 28, 2024
9b9d74b
chore: update spanish entries
userquin Feb 28, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion components/account/AccountHoverWrapper.vue
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ const userSettings = useUserSettings()

<template>
<span ref="hoverCard">
<VMenu v-if="!disabled && account && !getPreferences(userSettings, 'hideAccountHoverCard')" placement="bottom-start" :delay="{ show: 500, hide: 100 }" v-bind="$attrs" :close-on-content-click="false">
<VMenu v-if="!disabled && account && !getPreferences(userSettings, 'hideAccountAndTagHoverCard')" placement="bottom-start" :delay="{ show: 500, hide: 100 }" v-bind="$attrs" :close-on-content-click="false">
<slot />
<template #popper>
<AccountHoverCard v-if="account" :account="account" />
Expand Down
68 changes: 68 additions & 0 deletions components/account/TagHoverWrapper.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
<script setup lang="ts">
import type { mastodon } from 'masto'
import { fetchTag } from '~/composables/cache'

type WatcherType = [tag?: mastodon.v1.Tag, tagName?: string, v?: boolean]

defineOptions({
inheritAttrs: false,
})

const props = defineProps<{
tag?: mastodon.v1.Tag
tagName?: string
disabled?: boolean
}>()

const hoverCard = ref()
const targetIsVisible = ref(false)
const tag = ref<mastodon.v1.Tag | null | undefined>(props.tag)

useIntersectionObserver(
hoverCard,
([{ intersectionRatio }]) => {
targetIsVisible.value = intersectionRatio > 0.1
},
)

watch(
() => [props.tag, props.tagName, targetIsVisible.value] satisfies WatcherType,
([newTag, newTagName, newVisible], oldProps) => {
if (newTag) {
tag.value = newTag
return
}

if (!newVisible || process.test)
return

if (newTagName) {
const [_oldTag, oldTagName, _oldVisible] = oldProps ?? [undefined, undefined, false]
if (!oldTagName || newTagName !== oldTagName || !tag.value) {
fetchTag(newTagName).then((t) => {
if (newTagName === props.tagName)
tag.value = t
})
}

return
}

tag.value = undefined
}, { immediate: true, flush: 'post' },
)

const userSettings = useUserSettings()
</script>

<template>
<span ref="hoverCard">
<VMenu v-if="!disabled && tag && !getPreferences(userSettings, 'hideAccountAndTagHoverCard')" placement="bottom-start" :delay="{ show: 500, hide: 100 }" v-bind="$attrs" :close-on-content-click="false">
<slot />
<template #popper>
<TagCard v-if="tag" :tag="tag" />
</template>
</VMenu>
<slot v-else />
</span>
</template>
28 changes: 14 additions & 14 deletions components/tag/TagCard.vue
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
<script lang="ts" setup>
<script setup lang="ts">
import type { mastodon } from 'masto'

const {
tag,
} = defineProps<{
const { tag } = defineProps<{
tag: mastodon.v1.Tag
}>()

Expand Down Expand Up @@ -32,19 +30,21 @@ function go(evt: MouseEvent | KeyboardEvent) {

<template>
<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"
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This gap also improves the layout of the hashtag page in Explore tab for very long hashtag names.

Screenshot from 2024-02-25 20-21-16

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think for long tags the start should be top aligned

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd like the tag to always be aligned with the text below. I proposed having the star to the right, but I see now that it isn't a good idea. Maybe we should have the star always at the top left, and then the tag and the text below to the right of it. Or the star could be at the right of the graph, kind of like the Follow button in the notification cards for people following you

Copy link
Member Author

@shuuji3 shuuji3 Feb 25, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, I tried another layout by making the follow button an independent block and placing it on the left. This ensures both texts are aligned and more robust with a long hashtag.

For Hovercard, items-center looks lined up better than the top. For Hashtag, both look fine.

@patak-dev Which position do you prefer if we use this layout?

Hovercard

Screenshot from 2024-02-25 21-19-41

align center (items-center)

Screenshot from 2024-02-25 21-19-06
Screenshot from 2024-02-25 21-10-25

align top (items-start)

Screenshot from 2024-02-25 21-18-56
Screenshot from 2024-02-25 21-11-57

Hashtag page

Screenshot from 2024-02-25 21-44-05
Screenshot from 2024-02-25 21-43-37

@keydown.enter="onclick"
>
<div>
<h4 flex items-center text-size-base leading-normal font-medium line-clamp-1 break-all ws-pre-wrap>
<TagActionButton :tag="tag" />
<bdi>
<span>#</span>
<span hover:underline>{{ tag.name }}</span>
</bdi>
</h4>
<CommonTrending v-if="tag.history" :history="tag.history" text-sm text-secondary line-clamp-1 ws-pre-wrap break-all />
<div flex flex-gap-2>
<TagActionButton :tag="tag" />
<div>
<h4 flex items-center text-size-base leading-normal font-medium line-clamp-1 break-all ws-pre-wrap>
<bdi>
<span>#</span>
<span hover:underline>{{ tag.name }}</span>
</bdi>
</h4>
<CommonTrending v-if="tag.history" :history="tag.history" text-sm text-secondary line-clamp-1 ws-pre-wrap break-all />
</div>
</div>
<div v-if="tag.history" flex items-center>
<CommonTrendingCharts :history="tag.history" />
Expand Down
22 changes: 22 additions & 0 deletions composables/cache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,23 @@ export async function fetchAccountByHandle(acct: string): Promise<mastodon.v1.Ac
return account
}

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) {
return useAsyncState(() => fetchAccountById(id), null).state
}
Expand All @@ -111,3 +128,8 @@ export function cacheAccount(account: mastodon.v1.Account, server = currentServe
setCached(`${server}:${userId}:account:${account.id}`, 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)
}
7 changes: 5 additions & 2 deletions composables/content-render.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import Emoji from '~/components/emoji/Emoji.vue'
import ContentCode from '~/components/content/ContentCode.vue'
import ContentMentionGroup from '~/components/content/ContentMentionGroup.vue'
import AccountHoverWrapper from '~/components/account/AccountHoverWrapper.vue'
import TagHoverWrapper from '~/components/account/TagHoverWrapper.vue'

function getTextualAstComponents(astChildren: Node[]): string {
return astChildren
Expand Down Expand Up @@ -128,11 +129,13 @@ function handleMention(el: Node) {
addBdiNode(el)
return h(AccountHoverWrapper, { handle, class: 'inline-block' }, () => nodeToVNode(el))
}

const matchTag = href.match(TagLinkRE)
if (matchTag) {
const [, , name] = matchTag
const [, , tagName] = matchTag
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))
}
}
}
Expand Down
4 changes: 2 additions & 2 deletions composables/settings/definition.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export interface PreferencesSettings {
hideFollowerCount: boolean
hideTranslation: boolean
hideUsernameEmojis: boolean
hideAccountHoverCard: boolean
hideAccountAndTagHoverCard: boolean
patak-dev marked this conversation as resolved.
Show resolved Hide resolved
hideNews: boolean
grayscaleMode: boolean
enableAutoplay: boolean
Expand Down Expand Up @@ -69,7 +69,7 @@ export const DEFAULT__PREFERENCES_SETTINGS: PreferencesSettings = {
hideFollowerCount: false,
hideTranslation: false,
hideUsernameEmojis: false,
hideAccountHoverCard: false,
hideAccountAndTagHoverCard: false,
hideNews: false,
grayscaleMode: false,
enableAutoplay: true,
Expand Down
2 changes: 1 addition & 1 deletion locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -529,7 +529,7 @@
"github_cards": "GitHub Cards",
"github_cards_description": "When a GitHub link is posted, an accessible HTML card using the social graph meta is displayed instead of the social image.",
"grayscale_mode": "Grayscale mode",
"hide_account_hover_card": "Hide account hover card",
"hide_account_and_tag_hover_card": "Hide account and tag hover card",
"hide_alt_indi_on_posts": "Hide alt indicator on posts",
"hide_boost_count": "Hide boost count",
"hide_favorite_count": "Hide favorite count",
Expand Down
2 changes: 1 addition & 1 deletion locales/es.json
Original file line number Diff line number Diff line change
Expand Up @@ -519,7 +519,7 @@
"github_cards": "Tarjetas GitHub",
"github_cards_description": "Cuando se publica un enlace de GitHub, se muestra una tarjeta HTML accesible que usa el meta del gráfico social en lugar de la imagen social.",
"grayscale_mode": "Modo escala de grises",
"hide_account_hover_card": "Ocultar tarjeta flotante de cuenta",
"hide_account_and_tag_hover_card": "Ocultar tarjetas flotantes de cuenta y etiqueta",
"hide_alt_indi_on_posts": "Ocultar indicador ALT en publicaciones",
"hide_boost_count": "Ocultar contador de retoots",
"hide_favorite_count": "Ocultar número de publicaciones favoritas",
Expand Down
6 changes: 3 additions & 3 deletions pages/settings/preferences/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,10 @@ const userSettings = useUserSettings()
{{ $t('settings.preferences.hide_alt_indi_on_posts') }}
</SettingsToggleItem>
<SettingsToggleItem
:checked="getPreferences(userSettings, 'hideAccountHoverCard')"
@click="togglePreferences('hideAccountHoverCard')"
:checked="getPreferences(userSettings, 'hideAccountAndTagHoverCard')"
@click="togglePreferences('hideAccountAndTagHoverCard')"
>
{{ $t('settings.preferences.hide_account_hover_card') }}
{{ $t('settings.preferences.hide_account_and_tag_hover_card') }}
</SettingsToggleItem>
<SettingsToggleItem
:checked="getPreferences(userSettings, 'enableAutoplay')"
Expand Down
25 changes: 14 additions & 11 deletions tests/nuxt/__snapshots__/content-rich.test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -180,11 +180,13 @@ exports[`content-rich > handles html within code blocks 1`] = `
exports[`content-rich > hashtag adds bdi 1`] = `
"<p>
Testing bdi is added
<a
class="mention hashtag"
rel="nofollow noopener noreferrer"
to="/m.webtoo.ls/tags/turkey"
><bdi>#<span>turkey</span></bdi></a
<span
><a
class="mention hashtag"
rel="nofollow noopener noreferrer"
to="/m.webtoo.ls/tags/turkey"
><bdi>#<span>turkey</span></bdi></a
></span
>
</p>
<p></p>
Expand All @@ -194,12 +196,13 @@ exports[`content-rich > hashtag adds bdi 1`] = `
exports[`content-rich > hashtag doesn't add 2 bdi 1`] = `
"<p>
Testing bdi not added
<a
class="mention hashtag"
rel="nofollow noopener noreferrer"
to="/m.webtoo.ls/tags/turkey"
><bdi></bdi
></a>
<span
><a
class="mention hashtag"
rel="nofollow noopener noreferrer"
to="/m.webtoo.ls/tags/turkey"
><bdi></bdi></a
></span>
</p>
<p></p>
"
Expand Down
Loading