mirror of
https://gitlab.com/PronounsPage/PronounsPage.git
synced 2025-08-05 03:57:03 -04:00
165 lines
5.3 KiB
Vue
165 lines
5.3 KiB
Vue
<script setup lang="ts">
|
|
import useConfig from '../composables/useConfig.ts';
|
|
import { randomNumber, sleep } from '../src/helpers.ts';
|
|
|
|
import type { TermsEntryRaw } from '~/src/classes.ts';
|
|
|
|
const props = defineProps<{
|
|
termkey?: string;
|
|
name?: string;
|
|
alt: string;
|
|
img: string;
|
|
description?: string;
|
|
customlink?: string;
|
|
terms?: TermsEntryRaw[];
|
|
custom?: boolean;
|
|
asterisk?: boolean;
|
|
style?: string;
|
|
}>();
|
|
|
|
const { $isGranted: isGranted, $isSafari: isSafari } = useNuxtApp();
|
|
const config = useConfig();
|
|
|
|
const missing = ref(false);
|
|
const imgSrc = ref('data:image/svg+xml,<svg xmlns=\'http://www.w3.org/2000/svg\' width=\'26\' height=\'16\'><rect width=\'100%\' height=\'100%\' fill=\'transparent\'/></svg>');
|
|
|
|
const link = computed(() => {
|
|
if (props.customlink) {
|
|
return props.customlink;
|
|
}
|
|
|
|
if (!config.terminology.enabled ||
|
|
!(config.terminology.published || isGranted('terms'))) {
|
|
return null;
|
|
}
|
|
|
|
let fallback = null;
|
|
|
|
const options = [];
|
|
if (props.termkey) {
|
|
options.push(props.termkey);
|
|
if (props.termkey.endsWith('amorous')) {
|
|
options.push(props.termkey.replace('amorous', 'amory'));
|
|
}
|
|
}
|
|
if (props.name) {
|
|
options.push(props.name);
|
|
}
|
|
|
|
for (const term of props.terms || []) {
|
|
const keys = [...termKeys(term)];
|
|
for (const option of options) {
|
|
// exact match
|
|
for (const key of keys) {
|
|
if (key.toLowerCase() === option.toLowerCase()) {
|
|
return key;
|
|
}
|
|
}
|
|
|
|
// fallback
|
|
for (const key of keys) {
|
|
if (key.toLowerCase().includes(option.toLowerCase()) &&
|
|
!key.includes('+') && // joined identities are likely to be a false match
|
|
(fallback === null || key.length < fallback.length) // take shortest matching
|
|
) {
|
|
fallback = key;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return fallback;
|
|
});
|
|
|
|
onMounted(async () => {
|
|
if (isSafari()) {
|
|
await sleep(randomNumber(200, 800)); // spread out less important requests, it seems to cause issues on safari
|
|
}
|
|
imgSrc.value = props.img;
|
|
});
|
|
|
|
function* termKeys(term: TermsEntryRaw) {
|
|
if (term.key) {
|
|
yield term.key;
|
|
}
|
|
if (term.term) {
|
|
for (const p of term.term.split('|')) {
|
|
yield p;
|
|
}
|
|
}
|
|
if (term.original) {
|
|
for (const p of term.original.split('|')) {
|
|
yield p;
|
|
}
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<template>
|
|
<span class="flag-wrapper">
|
|
<a
|
|
v-if="link"
|
|
:href="link.startsWith('http') ? link : `/${config.terminology.route}?filter=${link.toLowerCase()}`"
|
|
target="_blank"
|
|
rel="noopener"
|
|
:title="alt"
|
|
>
|
|
<img v-if="!missing" :src="imgSrc" :alt :class="['flag-mini rounded', style]" @error="missing = true">
|
|
<LocaleLink v-else locale="en" link="/blog/missing-flags" class="text-danger"><Icon v="exclamation-circle" /></LocaleLink>
|
|
<Spelling escape :text="name" /><sup v-if="custom" class="text-muted"><small><Icon v="user" /></small></sup><sup v-if="asterisk" class="text-muted"><small>*</small></sup>
|
|
</a>
|
|
<span v-else :title="alt">
|
|
<img v-if="!missing" :src="imgSrc" :alt :class="['flag-mini rounded', style]" @error="missing = true">
|
|
<LocaleLink v-else locale="en" link="/blog/missing-flags" class="text-danger"><Icon v="exclamation-circle" /></LocaleLink>
|
|
<Spelling escape :text="name" /><sup v-if="custom" class="text-muted"><small><Icon v="user" /></small></sup><sup v-if="asterisk" class="text-muted"><small>*</small></sup>
|
|
</span>
|
|
<span class="flag-preview bg-white rouded p-2 border">
|
|
<img v-if="!missing" :src="imgSrc" :alt="alt" :class="['rounded', style]" @error="missing = true">
|
|
<LocaleLink v-else locale="en" link="/blog/missing-flags" class="text-danger"><Icon v="exclamation-circle" /></LocaleLink>
|
|
<span v-if="asterisk" class="alert alert-warning small d-block-force mt-2 mb-0 p-2">
|
|
*
|
|
<T>profile.flagsAsterisk</T>
|
|
</span>
|
|
<span v-if="custom" class="alert alert-warning small d-block-force mt-2 mb-0 p-2">
|
|
<Icon v="user" />
|
|
<T>profile.flagsCustomWarning</T>
|
|
</span>
|
|
<span v-if="name && description" class="d-block-force alert p-2"><strong>{{ name }}</strong> — {{ description }}</span>
|
|
<span v-if="alt" class="d-block-force alert p-2 small mb-0 text-muted"><T>crud.alt</T><T>quotation.colon</T> {{ alt }}</span>
|
|
</span>
|
|
</span>
|
|
</template>
|
|
|
|
<style lang="scss" scoped>
|
|
.flag-mini {
|
|
height: 1rem;
|
|
}
|
|
|
|
.flag-wrapper {
|
|
position: relative;
|
|
|
|
.flag-preview {
|
|
position: absolute;
|
|
top: 1.5em;
|
|
left: 0;
|
|
z-index: 999;
|
|
display: none;
|
|
img {
|
|
max-height: 128px;
|
|
}
|
|
}
|
|
|
|
text-align: left;
|
|
font-weight: normal;
|
|
|
|
&:hover {
|
|
.flag-preview {
|
|
display: block;
|
|
span {
|
|
white-space: normal;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
</style>
|