Переработал обработчик иконок для гиперссылок

This commit is contained in:
Иван Кузьменко 2025-07-05 05:57:27 +03:00
parent 418892b3a4
commit df54426a75
5 changed files with 143 additions and 104 deletions

View file

@ -1,57 +1,11 @@
<script lang="ts">
import { tryGetIcon } from '$lib/util/IconResolver';
import HoverIcon from './HoverIcon.svelte';
export let href: string;
let className: string = '';
export { className as class };
export let customIcon: string | null = null;
let url: URL;
let host: string;
const icons: Record<string, string> = {
none: 'material-symbols:link',
'steamcommunity.com': 'simple-icons:steam',
'twitter.com': 'simple-icons:x',
'x.com': 'simple-icons:x',
'github.com': 'simple-icons:github',
'youtube.com': 'simple-icons:youtube',
'itch.io': 'simple-icons:itchdotio',
'discord.gg': 'simple-icons:discord',
'gamebanana.com': 'simple-icons:gamebanana',
// https://хамяк.рф
'xn--80auf8a2c.xn--p1ai': 'fluent-emoji-high-contrast:hamster',
'teasanctuary.ru': '/icons/tea-sanctuary.svg',
'hl.teasanctuary.ru': '/icons/half-life.svg',
localhost: '/icons/tea-sanctuary.svg',
email: 'material-symbols:alternate-email'
};
function getHost(url: URL) {
const isEmail = url.href.startsWith('mailto:');
const hostname = isEmail ? 'email' : url.hostname;
// const split = hostname.split('.');
// const name = split[Math.max(split.length - 2, 0)];
// if (split.length > 1) return `${name}.${split[split.length - 1]}`;
// return name;
return hostname;
}
function tryGetIcon(link: string) {
try {
url = new URL(link);
host = getHost(url).replace(/\.com$/, '');
} catch {
return icons['none'];
}
let domain = getHost(url).toLocaleLowerCase();
let key = Object.keys(icons).find((key) => key == domain);
if (key == null) return icons['none'];
return icons[key];
}
</script>
<a
@ -60,7 +14,7 @@
target="_blank"
>
<div class="shrink-0 rounded-l-xl bg-slate-800 p-2">
<HoverIcon src={customIcon ?? tryGetIcon(href)} class="text-sm uppercase" text={host} />
<HoverIcon src={customIcon ?? tryGetIcon(href)} class="text-sm uppercase" text={href} />
</div>
<div
class="flex shrink-0 grow flex-nowrap items-center justify-center rounded-r-xl bg-slate-100 p-2 text-2xl text-nowrap text-slate-950"

View file

@ -1,6 +1,7 @@
<script lang="ts">
import { MediaQuery } from 'svelte/reactivity';
import HoverIcon from './HoverIcon.svelte';
import { tryGetIcon } from '$lib/util/IconResolver';
export let href: string;
let className: string = '';
@ -8,60 +9,13 @@
export let customIcon: string | null = null;
const sm = new MediaQuery('width >= 40rem', false);
let url: URL;
let host: string;
const icons: Record<string, string> = {
none: 'material-symbols:link',
'steamcommunity.com': 'simple-icons:steam',
'twitter.com': 'simple-icons:x',
'x.com': 'simple-icons:x',
'github.com': 'simple-icons:github',
'youtube.com': 'simple-icons:youtube',
'itch.io': 'simple-icons:itchdotio',
'discord.gg': 'simple-icons:discord',
'gamebanana.com': 'simple-icons:gamebanana',
// https://хамяк.рф
'xn--80auf8a2c.xn--p1ai': 'fluent-emoji-high-contrast:hamster',
'teasanctuary.ru': '/icons/tea-sanctuary.svg',
'hl.teasanctuary.ru': '/icons/half-life.svg',
localhost: '/icons/tea-sanctuary.svg',
email: 'material-symbols:alternate-email'
};
function getHost(url: URL) {
const isEmail = url.href.startsWith('mailto:');
const hostname = isEmail ? 'email' : url.hostname;
// const split = hostname.split('.');
// const name = split[Math.max(split.length - 2, 0)];
// if (split.length > 1) return `${name}.${split[split.length - 1]}`;
// return name;
return hostname;
}
function tryGetIcon(link: string) {
try {
url = new URL(link);
host = getHost(url).replace(/\.com$/, '');
} catch {
return icons['none'];
}
let domain = getHost(url).toLocaleLowerCase();
let key = Object.keys(icons).find((key) => key == domain);
if (key == null) return icons['none'];
return icons[key];
}
</script>
<a {href} class="{className} inline-block group" target="_blank">
<span class="inline-block {sm.current ? 'size-8 rounded-xl p-1' : 'size-6 rounded-sm p-0.5'} bg-emerald-800 align-bottom transition-all group-hover:scale-110">
<HoverIcon
src={customIcon ?? tryGetIcon(href)}
text={host}
text={href}
size={sm.current ? 24 : 20}
/>
</span>

View file

@ -0,0 +1,65 @@
const icons: Record<string, string> = {
none: 'material-symbols:link',
'steamcommunity.com': 'simple-icons:steam',
'twitter.com': 'simple-icons:x',
'x.com': 'simple-icons:x',
'github.com': 'simple-icons:github',
'youtube.com': 'simple-icons:youtube',
'itch.io': 'simple-icons:itchdotio',
'discord.gg': 'simple-icons:discord',
'gamebanana.com': 'simple-icons:gamebanana',
'bsky.app': 'simple-icons:bluesky',
'bsky.social': 'simple-icons:bluesky',
// https://хамяк.рф
'xn--80auf8a2c.xn--p1ai': 'fluent-emoji-high-contrast:hamster',
'teasanctuary.ru': '/icons/tea-sanctuary-white.svg',
'hl.teasanctuary.ru': '/icons/half-life.svg',
'git.teasanctuary.ru': 'devicon-plain:git',
localhost: '/icons/tea-sanctuary-white.svg',
email: 'material-symbols:alternate-email'
};
// Особые случаи, когда одним доменом второго уровня не ограничишься (например, randomtrash.itch.io)
const specialResolvers: Record<string, (url: URL) => string> = {
'teasanctuary.ru': (url) => {
// Домены третьего уровня и выше
const prefix = url.hostname.split('.').toReversed();
prefix.shift();
prefix.shift();
if (prefix[0] === "hl") {
return 'hl.teasanctuary.ru';
}
if (prefix[0] === "git" || url.pathname.split('/')[1] == "git") {
return 'git.teasanctuary.ru';
}
return 'teasanctuary.ru';
},
// Игнорируем имя пользователя
'bsky.app': (url) => 'bsky.app',
'bsky.social': (url) => 'bsky.social',
'itch.io': (url) => 'itch.io'
}
function getIconFromUrl(url: URL): string | undefined {
const href = url.href;
if (href.startsWith('mailto:'))
return 'email';
const hostname = url.hostname;
const secondLevel = hostname.match(/(([A-Za-z0-9\-])+\.([A-Za-z0-9\-])+)$/)?.at(0) ?? '';
if (specialResolvers[secondLevel])
return icons[specialResolvers[secondLevel](url)];
return icons[hostname];
}
export function tryGetIcon(link: string): string {
let url: URL;
try {
url = new URL(link);
} catch {
return icons['none'];
}
return getIconFromUrl(url) ?? icons['none'];
}

View file

@ -72,18 +72,14 @@
<SocialButton class="w-60 shrink-0" href="https://github.com/TeaSanctuary/">
GitHub
</SocialButton>
<SocialButton
class="w-60 shrink-0"
href="https://teasanctuary.ru/git"
customIcon="devicon-plain:git"
>
<SocialButton class="w-60 shrink-0" href="https://teasanctuary.ru/git">
Наш Git
</SocialButton>
<SocialButton class="w-60 shrink-0" href="https://hl.teasanctuary.ru">
Сервер HLDM
</SocialButton>
</div>
<SocialButton class="w-60 opacity-0 hover:opacity-100" href="https://хамяк.рф">
<SocialButton class="w-60 opacity-1 hover:opacity-100" href="https://хамяк.рф">
хамяк)
</SocialButton>
</div>
@ -111,8 +107,8 @@
<div class="text-justify">
Наша главная страсть &mdash; это, конечно, видеоигры. Мы часто участвуем в так называемых
"гейм джемах" &mdash; конкурсах на разработку игр. Наши игры вы можете оценить здесь:
<SocialHyperlink customIcon="simple-icons:itchdotio" href="https://randomtrash.itch.io">RandomTrash</SocialHyperlink>
<SocialHyperlink customIcon="simple-icons:itchdotio" href="https://friendlywithmeat.itch.io/">FriendlyWithMeat</SocialHyperlink>.
<SocialHyperlink href="https://randomtrash.itch.io">RandomTrash</SocialHyperlink>
<SocialHyperlink href="https://friendlywithmeat.itch.io/">FriendlyWithMeat</SocialHyperlink>.
Также мы ведём работу над нашим первым полноценным игровым проектом.
Следите за новостями в нашем
<SocialHyperlink href={PUBLIC_TS_DISCORD}>сообществе</SocialHyperlink>!

View file

@ -0,0 +1,70 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
width="196"
height="196"
viewBox="0 0 196 196"
fill="none"
version="1.1"
id="svg5"
sodipodi:docname="tea-sanctuary.svg"
inkscape:version="1.4 (86a8ad7, 2024-10-11)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs5" />
<sodipodi:namedview
id="namedview5"
pagecolor="#ffffff"
bordercolor="#000000"
borderopacity="0.25"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
inkscape:zoom="4.1581633"
inkscape:cx="98"
inkscape:cy="98"
inkscape:window-width="1920"
inkscape:window-height="1017"
inkscape:window-x="-8"
inkscape:window-y="-8"
inkscape:window-maximized="1"
inkscape:current-layer="svg5" />
<path
d="M48.9959 95.5296C30.3718 101.356 18.375 110.495 18.375 120.769C18.375 138.368 53.5652 152.635 96.9792 152.635C140.393 152.635 175.583 138.368 175.583 120.769C175.583 110.495 163.586 101.356 144.966 95.5296"
stroke="#f8fafc"
stroke-width="6"
stroke-linecap="round"
stroke-linejoin="round"
id="path1" />
<path
d="M18.375 121.626C18.375 139.225 37.4932 161.99 96.9792 161.99C156.465 161.99 175.583 138.368 175.583 120.769"
stroke="#f8fafc"
stroke-width="6"
stroke-linecap="round"
stroke-linejoin="round"
id="path2" />
<path
d="M37.4932 53.0425C37.4932 78.0121 54.1532 126.804 96.9792 126.804C139.805 126.804 156.461 78.0121 156.461 53.0466"
stroke="#f8fafc"
stroke-width="6"
stroke-linecap="round"
stroke-linejoin="round"
id="path3" />
<path
d="M96.9792 72.0831C129.83 72.0831 156.461 63.5602 156.461 53.0466C156.461 42.533 129.83 34.0101 96.9792 34.0101C64.1282 34.0101 37.4973 42.533 37.4973 53.0466C37.4973 63.5602 64.1282 72.0831 96.9792 72.0831Z"
stroke="#f8fafc"
stroke-width="6"
stroke-linecap="round"
stroke-linejoin="round"
id="path4" />
<path
d="M153.746 58.7428C146.788 50.8048 124.007 44.9861 96.9792 44.9861C69.9516 44.9861 47.1707 50.8048 40.2086 58.7469M147.6 90.3478C172.264 94.7578 177.625 80.7234 177.625 70.1639C177.625 59.6044 173.574 53.0466 164.248 53.0466C162.724 53.0522 161.221 53.4017 159.851 54.0691C158.48 54.7366 157.278 55.7047 156.335 56.9013"
stroke="#f8fafc"
stroke-width="6"
stroke-linecap="round"
stroke-linejoin="round"
id="path5" />
</svg>

After

Width:  |  Height:  |  Size: 2.6 KiB