Skip to content

Commit 5ef92e5

Browse files
committed
feat(links): adds hover title for external links
1 parent a4fb08c commit 5ef92e5

12 files changed

+127
-11
lines changed

src/components/common/buttons/TagLink.vue

+6-1
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,12 @@ const onClick = (e: MouseEvent) => {
2626
</script>
2727

2828
<template>
29-
<a :href="tag?.url" @click="onClick">
29+
<a
30+
:href="tag?.url"
31+
:title="tag?.title"
32+
:data-tag="JSON.stringify(tag)"
33+
@click="onClick"
34+
>
3035
<NTag
3136
class="tag"
3237
:class="{ meta: tag?.meta, link: !!tag?.url }"

src/components/common/buttons/TitleLink.vue

+5-1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,10 @@ defineProps({
99
required: false,
1010
default: NH2,
1111
},
12+
label: {
13+
type: String,
14+
required: false,
15+
},
1216
});
1317
1418
const emit = defineEmits<{
@@ -25,7 +29,7 @@ const onTitleClick = (e: MouseEvent) => {
2529
</script>
2630

2731
<template>
28-
<a class="anchor-link" @click="onTitleClick">
32+
<a class="anchor-link" :title="label" @click="onTitleClick">
2933
<component :is="component" class="content" :class="{ 'hover-link': !!$attrs.href }">
3034
<slot />
3135
</component>

src/components/common/list/use-list-scroll.ts

+11-5
Original file line numberDiff line numberDiff line change
@@ -81,14 +81,19 @@ export const getPosterQuery =
8181
} satisfies ImageQuery;
8282
};
8383

84-
const i18n = useI18n('common', 'tag');
84+
const i18n = useI18n('common');
85+
86+
const i18nEpisode = i18n('episode', 'common', 'tag');
87+
const i18nSeason = i18n('season', 'common', 'tag');
88+
const openInEpisode = i18n('open_episode_in_trakt', 'common', 'tooltip');
89+
const openInSeason = i18n('open_season_in_trakt', 'common', 'tooltip');
90+
8591
export const getTags = (item: Pick<ListScrollSourceItem, 'episode' | 'season'>, type: ListScrollItem['type']): ListScrollItem['tags'] => {
8692
const tags: ListScrollItem['tags'] = [];
8793
if (type === 'episode' && item.episode) {
8894
tags.push({
89-
label: `${i18n('season')} ${item.episode.season.toString().padStart(2, '0')} ${i18n('episode')} ${item.episode.number
90-
.toString()
91-
.padStart(2, '0')}`,
95+
label: `${i18nSeason} ${item.episode.season.toString().padStart(2, '0')} ${i18nEpisode} ${item.episode.number.toString().padStart(2, '0')}`,
96+
title: openInEpisode,
9297
type: 'warning',
9398
bordered: true,
9499
url: ResolveExternalLinks.search({
@@ -113,7 +118,8 @@ export const getTags = (item: Pick<ListScrollSourceItem, 'episode' | 'season'>,
113118
}
114119
} else if (type === 'season' && item.season) {
115120
tags.push({
116-
label: `Season ${item.season.number.toString().padStart(2, '0')}`,
121+
label: `${i18nSeason} ${item.season.number.toString().padStart(2, '0')}`,
122+
title: openInSeason,
117123
type: 'warning',
118124
url: ResolveExternalLinks.search({
119125
type: 'season',

src/components/views/panel/MoviePanel.vue

+10-1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import PanelPoster from '~/components/views/panel/PanelPoster.vue';
1212
import { ResolveExternalLinks } from '~/settings/external.links';
1313
import { useMovieStore } from '~/stores/data/movie.store';
1414
import { useExtensionSettingsStore } from '~/stores/settings/extension.store';
15+
import { useI18n } from '~/utils';
1516
import { deCapitalise } from '~/utils/string.utils';
1617
1718
const props = defineProps({
@@ -46,6 +47,8 @@ onUnmounted(() => {
4647
movie.value = undefined;
4748
});
4849
50+
const i18n = useI18n('movie', 'panel');
51+
4952
const title = computed(() => {
5053
if (!movie.value?.title) return;
5154
return deCapitalise(movie.value.title);
@@ -65,7 +68,13 @@ const { openTab } = useExtensionSettingsStore();
6568

6669
<template>
6770
<NFlex justify="center" align="center" vertical>
68-
<TitleLink v-if="title" class="show-title" :href="titleUrl" @on-click="openTab">
71+
<TitleLink
72+
v-if="title"
73+
class="show-title"
74+
:href="titleUrl"
75+
:title="i18n('open_in_trakt', 'common', 'tooltip')"
76+
@on-click="openTab"
77+
>
6978
{{ title }}
7079
</TitleLink>
7180
<NSkeleton v-else class="show-title-skeleton" style="width: 50dvh" round />

src/components/views/panel/PanelLinks.vue

+13
Original file line numberDiff line numberDiff line change
@@ -37,12 +37,22 @@ const { ids, mode, season, episode } = toRefs(props);
3737
3838
const i18n = useI18n('panel', 'detail');
3939
40+
const labelKey = computed(() => {
41+
const label = ['show', 'episode', 'season'].includes(mode.value)
42+
? `open_${mode.value}_in`
43+
: 'open_in';
44+
return (source: string) => {
45+
return i18n({ key: label, substitutions: [source] }, 'common', 'tooltip');
46+
};
47+
});
48+
4049
const links = computed(() => {
4150
if (!ids?.value) return;
4251
const _links: TagLink[] = [];
4352
if (ids.value.trakt) {
4453
_links.push({
4554
label: 'Trakt',
55+
title: labelKey.value('Trakt.tv'),
4656
url: ResolveExternalLinks.search({
4757
type: mode.value,
4858
source: 'trakt',
@@ -60,6 +70,7 @@ const links = computed(() => {
6070
if (ids.value.imdb) {
6171
_links.push({
6272
label: 'IMDb',
73+
title: labelKey.value('IMDb.com'),
6374
url: ResolveExternalLinks.imdb(ids.value.imdb),
6475
icon: IconIMDb,
6576
iconProps: {
@@ -73,6 +84,7 @@ const links = computed(() => {
7384
if (ids.value.tmdb) {
7485
_links.push({
7586
label: 'TMDb',
87+
title: labelKey.value('TheMovieDb.org'),
7688
url: ResolveExternalLinks.tmdb({
7789
id: ids.value.tmdb,
7890
type: mode.value,
@@ -90,6 +102,7 @@ const links = computed(() => {
90102
if (ids.value.tvdb) {
91103
_links.push({
92104
label: 'TVDb',
105+
title: labelKey.value('TheTVDB.com'),
93106
url: ResolveExternalLinks.tvdb(ids.value.tvdb, mode.value),
94107
icon: IconTVDb,
95108
iconProps: {

src/components/views/panel/PanelOverview.vue

+5
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,10 @@ defineProps({
1313
type: String,
1414
required: false,
1515
},
16+
label: {
17+
type: String,
18+
required: false,
19+
},
1620
overview: {
1721
type: String,
1822
required: false,
@@ -28,6 +32,7 @@ const { openTab } = useExtensionSettingsStore();
2832
v-if="title"
2933
class="title"
3034
:href="url"
35+
:title="label"
3136
:component="NH4"
3237
@on-click="openTab"
3338
>

src/components/views/panel/PersonPanel.vue

+10-1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import PersonPanelOverview from '~/components/views/panel/PersonPanelOverview.vu
1212
import { ResolveExternalLinks } from '~/settings/external.links';
1313
import { usePersonStore } from '~/stores/data/person.store';
1414
import { useExtensionSettingsStore } from '~/stores/settings/extension.store';
15+
import { useI18n } from '~/utils';
1516
import { deCapitalise } from '~/utils/string.utils';
1617
1718
const props = defineProps({
@@ -46,6 +47,8 @@ onUnmounted(() => {
4647
person.value = undefined;
4748
});
4849
50+
const i18n = useI18n('panel', 'person');
51+
4952
const title = computed(() => {
5053
if (!person.value?.name) return;
5154
return deCapitalise(person.value?.name);
@@ -65,7 +68,13 @@ const { openTab } = useExtensionSettingsStore();
6568

6669
<template>
6770
<NFlex justify="center" align="center" vertical>
68-
<TitleLink v-if="title" class="show-title" :href="titleUrl" @on-click="openTab">
71+
<TitleLink
72+
v-if="title"
73+
class="show-title"
74+
:href="titleUrl"
75+
:title="i18n('open_in_trakt', 'common', 'tooltip')"
76+
@on-click="openTab"
77+
>
6978
{{ title }}
7079
</TitleLink>
7180
<NSkeleton v-else class="show-title-skeleton" style="width: 50dvh" round />

src/components/views/panel/PersonPanelDetails.vue

+5
Original file line numberDiff line numberDiff line change
@@ -65,34 +65,39 @@ const socials = computed(() => {
6565
if (person.value?.homepage) {
6666
_socials.push({
6767
label: 'Homepage',
68+
title: i18n({ key: 'open', substitutions: ['home page'] }, 'common', 'tooltip'),
6869
url: person.value?.homepage,
6970
icon: IconExternalLinkRounded,
7071
});
7172
}
7273
if (person.value?.social_ids?.facebook) {
7374
_socials.push({
7475
label: 'Facebook',
76+
title: i18n({ key: 'open_in', substitutions: ['Facebook'] }, 'common', 'tooltip'),
7577
url: ResolveExternalLinks.facebook(person.value?.social_ids.facebook),
7678
icon: IconFacebook,
7779
});
7880
}
7981
if (person.value?.social_ids?.instagram) {
8082
_socials.push({
8183
label: 'Instagram',
84+
title: i18n({ key: 'open_in', substitutions: ['Instagram'] }, 'common', 'tooltip'),
8285
url: ResolveExternalLinks.instagram(person.value?.social_ids.instagram),
8386
icon: IconInstagram,
8487
});
8588
}
8689
if (person.value?.social_ids?.twitter) {
8790
_socials.push({
8891
label: 'Twitter',
92+
title: i18n({ key: 'open_in', substitutions: ['Twitter'] }, 'common', 'tooltip'),
8993
url: ResolveExternalLinks.twitter(person.value?.social_ids.twitter),
9094
icon: IconTwitter,
9195
});
9296
}
9397
if (person.value?.social_ids?.wikipedia) {
9498
_socials.push({
9599
label: 'Wikipedia',
100+
title: i18n({ key: 'open_in', substitutions: ['Wikipedia'] }, 'common', 'tooltip'),
96101
url: ResolveExternalLinks.wikipedia(person.value?.social_ids.wikipedia),
97102
icon: IconWikipedia,
98103
});

src/components/views/panel/ShowPanel.vue

+17-1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import ShowPanelPicker from '~/components/views/panel/ShowPanelPicker.vue';
1616
import { ResolveExternalLinks } from '~/settings/external.links';
1717
import { type ShowSeasons, useShowStore } from '~/stores/data/show.store';
1818
import { useExtensionSettingsStore } from '~/stores/settings/extension.store';
19+
import { useI18n } from '~/utils';
1920
import { deCapitalise } from '~/utils/string.utils';
2021
2122
const props = defineProps({
@@ -42,6 +43,8 @@ const { showId, seasonNumber, episodeNumber } = toRefs(props);
4243
4344
const { getShowProgress } = useShowStore();
4445
46+
const i18n = useI18n('panel', 'show');
47+
4548
const progress = computed(() => {
4649
if (!showId?.value) return;
4750
return getShowProgress(showId.value).value;
@@ -153,7 +156,13 @@ const { openTab } = useExtensionSettingsStore();
153156

154157
<template>
155158
<NFlex justify="center" align="center" vertical>
156-
<TitleLink v-if="title" class="show-title" :href="titleUrl" @on-click="openTab">
159+
<TitleLink
160+
v-if="title"
161+
class="show-title"
162+
:href="titleUrl"
163+
:title="i18n('open_show_in_trakt', 'common', 'tooltip')"
164+
@on-click="openTab"
165+
>
157166
{{ title }}
158167
</TitleLink>
159168
<NSkeleton v-else class="show-title-skeleton" style="width: 50dvh" round />
@@ -175,6 +184,13 @@ const { openTab } = useExtensionSettingsStore();
175184

176185
<ShowPanelPicker :seasons="seasons" :episodes="episodes" :mode="panelType" />
177186

187+
<ShowPanelPicker
188+
:mode="panelType"
189+
:seasons="seasons"
190+
:episodes="episodes"
191+
:progress="progress"
192+
/>
193+
178194
<ShowPanelOverview
179195
:episode="episode"
180196
:season="season"

src/components/views/panel/ShowPanelOverview.vue

+6-1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import type { TraktShowExtended } from '~/models/trakt/trakt-show.model';
99
1010
import PanelOverview from '~/components/views/panel/PanelOverview.vue';
1111
import { ResolveExternalLinks } from '~/settings/external.links';
12+
import { useI18n } from '~/utils';
1213
import { deCapitalise } from '~/utils/string.utils';
1314
1415
const props = defineProps({
@@ -33,6 +34,8 @@ const props = defineProps({
3334
3435
const { mode, episode, season, show } = toRefs(props);
3536
37+
const i18n = useI18n('panel', 'show', 'oerview');
38+
3639
const title = computed(() => {
3740
if (mode.value === 'show') {
3841
if (!show?.value) return;
@@ -74,6 +77,8 @@ const url = computed(() => {
7477
});
7578
});
7679
80+
const label = computed(() => i18n(`open_${mode.value}_in_trakt`, 'common', 'tooltip'));
81+
7782
const overview = computed(() => {
7883
if (mode.value === 'show') {
7984
if (!show?.value) return;
@@ -89,5 +94,5 @@ const overview = computed(() => {
8994
</script>
9095

9196
<template>
92-
<PanelOverview :title="title" :url="url" :overview="overview" />
97+
<PanelOverview :title="title" :label="label" :url="url" :overview="overview" />
9398
</template>

src/i18n/en/common/tooltip.json

+38
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
{
2+
"common__tooltip__open": {
3+
"message": "Open $1",
4+
"description": "Label for a link that opens a page in a new tab"
5+
},
6+
"common__tooltip__open_in": {
7+
"message": "Open in $1",
8+
"description": "Label for a link that opens a page in a new tab"
9+
},
10+
"common__tooltip__open_show_in": {
11+
"message": "Open show in $1",
12+
"description": "Label for a show link that opens a page in a new tab"
13+
},
14+
"common__tooltip__open_episode_in": {
15+
"message": "Open episode in $1",
16+
"description": "Label for a episode link that opens a page in a new tab"
17+
},
18+
"common__tooltip__open_season_in": {
19+
"message": "Open season in $1",
20+
"description": "Label for a season link that opens a page in a new tab"
21+
},
22+
"common__tooltip__open_in_trakt": {
23+
"message": "Open in Trakt.tv",
24+
"description": "Label for opening a trakt.tv link in a new tab."
25+
},
26+
"common__tooltip__open_show_in_trakt": {
27+
"message": "Open show in Trakt.tv",
28+
"description": "Label for opening a trakt.tv show link in a new tab."
29+
},
30+
"common__tooltip__open_episode_in_trakt": {
31+
"message": "Open episode in Trakt.tv",
32+
"description": "Label for opening a trakt.tv episode link in a new tab."
33+
},
34+
"common__tooltip__open_season_in_trakt": {
35+
"message": "Open season in Trakt.tv",
36+
"description": "Label for opening a trakt.tv season link in a new tab."
37+
}
38+
}

src/models/tag.model.ts

+1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import type { Component } from 'vue';
33

44
export type TagLink = TagProps & {
55
label: string;
6+
title?: string;
67
url?: string;
78
meta?: boolean;
89
icon?: Component;

0 commit comments

Comments
 (0)