mirror of
https://gitlab.com/PronounsPage/PronounsPage.git
synced 2025-08-05 03:57:03 -04:00
324 lines
14 KiB
Vue
324 lines
14 KiB
Vue
<script setup lang="ts">
|
|
import { useNuxtApp } from 'nuxt/app';
|
|
|
|
import useConfig from '~/composables/useConfig.ts';
|
|
import useMainPronoun from '~/composables/useMainPronoun.ts';
|
|
import useSpelling from '~/composables/useSpelling.ts';
|
|
import type { TermsEntryRaw } from '~/src/classes.ts';
|
|
import { loadCalendar, loadPronounLibrary } from '~/src/data.ts';
|
|
import { buildFlags } from '~/src/flags.ts';
|
|
import { buildImageUrl } from '~/src/helpers.ts';
|
|
import type { Profile, UserWithProfiles } from '~/src/profile.ts';
|
|
|
|
const props = withDefaults(defineProps<{
|
|
user: UserWithProfiles;
|
|
profile: Partial<Profile>;
|
|
terms?: TermsEntryRaw[] | null;
|
|
isStatic?: boolean;
|
|
expandLinks?: boolean;
|
|
}>(), {
|
|
terms: null,
|
|
});
|
|
|
|
const { $translator: translator } = useNuxtApp();
|
|
const { convertName } = useSpelling();
|
|
const config = useConfig();
|
|
|
|
const pronounLibrary = await loadPronounLibrary(config);
|
|
|
|
const { mainPronoun, pronounOpinions } = useMainPronoun(pronounLibrary, toRef(props, 'profile'), translator);
|
|
|
|
const normaliseUrl = (url: string): string | null => {
|
|
try {
|
|
return new URL(url).toString();
|
|
} catch {
|
|
return null;
|
|
}
|
|
};
|
|
|
|
const allFlags = buildFlags(config.locale);
|
|
const minAge = config.ageLimit || 13;
|
|
const year = (await loadCalendar()).getCurrentYear();
|
|
|
|
const countFlags = computed(() => {
|
|
return (props.profile.flags?.length ?? 0) + (props.profile.customFlags?.length ?? 0);
|
|
});
|
|
const manyFlagsLayout = computed(() => {
|
|
return countFlags.value > 36 || countFlags.value === 0 || !hasDescriptionColumn.value;
|
|
});
|
|
const hasDescriptionColumn = computed(() => {
|
|
return (props.profile.age && props.profile.age > minAge) ||
|
|
props.profile.description?.trim().length ||
|
|
props.user.team ||
|
|
props.profile.timezone
|
|
;
|
|
});
|
|
const mainRowCount = computed(() => {
|
|
let c = 0;
|
|
if (props.profile.names?.length) {
|
|
c++;
|
|
}
|
|
if (props.profile.pronouns?.length && config.pronouns.enabled) {
|
|
c++;
|
|
}
|
|
if (props.profile.links?.length) {
|
|
c++;
|
|
}
|
|
return c;
|
|
});
|
|
const usedOpinions = computed(() => {
|
|
return new Set([
|
|
...(props.profile.names ?? []).map((r) => r.opinion),
|
|
...(props.profile.pronouns ?? []).map((r) => r.opinion),
|
|
...(props.profile.words ?? []).flat().map((c) => c.values)
|
|
.flat()
|
|
.map((r) => r.opinion),
|
|
]);
|
|
});
|
|
</script>
|
|
|
|
<template>
|
|
<div>
|
|
<div class="mb-3 d-flex justify-content-between flex-column flex-md-row">
|
|
<div class="mw-50">
|
|
<div class="text-nowrap d-flex align-items-center">
|
|
<Avatar :user="user" class="me-3" />
|
|
<div>
|
|
<h2>
|
|
@{{ user.username }}
|
|
</h2>
|
|
<p v-if="user.team || profile.teamName || profile.footerName" class="mb-2">
|
|
<nuxt-link :to="{ name: 'team' }" class="badge bg-primary text-white">
|
|
<Icon v="collective-logo.svg" class="inverted" />
|
|
<T>contact.team.member</T>
|
|
</nuxt-link>
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="flex-grow-1 text-lg-end">
|
|
<slot></slot>
|
|
</div>
|
|
</div>
|
|
|
|
<template v-if="profile.access">
|
|
<section class="row">
|
|
<div v-if="hasDescriptionColumn" :class="['col-12', manyFlagsLayout ? '' : 'col-lg-6']">
|
|
<div v-if="profile.description" class="mb-3">
|
|
<p v-for="line in profile.description.split('\n')" class="mb-1" style="word-break: break-word;">
|
|
<Spelling escape :text="line" :markdown="profile.markdown" />
|
|
</p>
|
|
</div>
|
|
<p v-if="profile.age && profile.age >= minAge">
|
|
<Icon v="birthday-cake" />
|
|
<T v-if="$te('profile.age')">profile.age</T><T v-else>profile.birthday</T><T>quotation.colon</T>
|
|
{{ profile.age }}
|
|
</p>
|
|
<Timezone v-if="profile.timezone" :value="profile.timezone" :is-static="isStatic" />
|
|
</div>
|
|
|
|
<div v-if="profile.flags?.length || profile.customFlags?.length" :class="['col-12', manyFlagsLayout ? '' : 'col-lg-6']">
|
|
<ClientOnly>
|
|
<ExpandableList
|
|
:values="[...(profile.flags ?? []).filter(flag => allFlags[flag]), ...profile.customFlags]"
|
|
:limit="32"
|
|
:reduced-limit="8"
|
|
class="list-inline"
|
|
item-class="list-inline-item p-1"
|
|
:is-static="isStatic"
|
|
:expand="expandLinks"
|
|
>
|
|
<template #default="s">
|
|
<Flag
|
|
v-if="typeof s.el === 'string'"
|
|
:termkey="allFlags[s.el].display"
|
|
:name="$translateForPronoun(allFlags[s.el].display, mainPronoun)"
|
|
:alt="$t(`flags_alt.${s.el.replace(/'/g, '*').replace(/ /g, '_')}`)"
|
|
:img="`/flags/${s.el}.png`"
|
|
:terms="terms || []"
|
|
:asterisk="allFlags[s.el].asterisk"
|
|
/>
|
|
<Flag
|
|
v-else
|
|
:termkey="s.el.name"
|
|
:name="s.el.name"
|
|
:alt="s.el.alt || ''"
|
|
:img="buildImageUrl($config.public.cloudfront, s.el.value, 'flag')"
|
|
:terms="terms || []"
|
|
custom
|
|
:description="s.el.description"
|
|
:customlink="s.el.link"
|
|
/>
|
|
</template>
|
|
</ExpandableList>
|
|
</ClientOnly>
|
|
</div>
|
|
</section>
|
|
|
|
<section class="row">
|
|
<div v-if="profile.names?.length" :class="['col-6', mainRowCount === 3 ? 'col-lg-4' : 'col-lg-6']">
|
|
<h3>
|
|
<Icon v="signature" />
|
|
<T>profile.names</T>
|
|
</h3>
|
|
|
|
<ExpandableList :values="profile.names" :limit="16" class="list-unstyled" :is-static="isStatic" :expand="expandLinks">
|
|
<template #default="s">
|
|
<Opinion
|
|
:word="convertName(s.el.value)"
|
|
:opinion="s.el.opinion"
|
|
:escape="false"
|
|
:markdown="profile.markdown"
|
|
:pronunciation="s.el.pronunciation"
|
|
:link="config.locale === 'tok' && config.pronouns.enabled ? `${config.pronouns.prefix}/${s.el.value}` : null"
|
|
:custom-opinions="profile.opinions"
|
|
/>
|
|
</template>
|
|
</ExpandableList>
|
|
</div>
|
|
<div
|
|
v-if="profile.pronouns?.length && config.pronouns.enabled"
|
|
:class="['col-6', mainRowCount === 3 ? 'col-lg-4' : 'col-lg-6']"
|
|
>
|
|
<h3>
|
|
<Icon v="tags" />
|
|
<T>profile.pronouns</T>
|
|
</h3>
|
|
|
|
<ExpandableList :values="pronounOpinions" :limit="16" class="list-unstyled" :is-static="isStatic" :expand="expandLinks">
|
|
<template #default="s">
|
|
<Opinion
|
|
:word="s.el.short.options.join(s.el.short.glue)"
|
|
:opinion="s.el.opinion"
|
|
:link="`${config.pronouns.prefix || ''}/${s.el.link}`"
|
|
:custom-opinions="profile.opinions"
|
|
/>
|
|
</template>
|
|
</ExpandableList>
|
|
</div>
|
|
<div v-if="profile.links?.length" :class="['col-12', mainRowCount === 3 ? 'col-lg-4' : 'col-lg-6']">
|
|
<h3>
|
|
<Icon v="link" />
|
|
<T>profile.links</T>
|
|
</h3>
|
|
|
|
<ExpandableList :values="profile.links" :limit="16" class="list-unstyled" :is-static="isStatic" :expand="expandLinks">
|
|
<template #default="s">
|
|
<ProfileLink
|
|
:link="s.el"
|
|
:expand="isStatic"
|
|
:verified-links="profile.verifiedLinks || {}"
|
|
:metadata="profile.linksMetadata?.[normaliseUrl(s.el) ?? '']"
|
|
/>
|
|
</template>
|
|
</ExpandableList>
|
|
</div>
|
|
</section>
|
|
|
|
<section v-if="(profile.words ?? []).map(w => w.values.length).reduce((a, b) => a + b, 0) > 0" class="clearfix">
|
|
<h3>
|
|
<Icon v="scroll-old" />
|
|
<T>profile.words</T>
|
|
</h3>
|
|
|
|
<div class="row">
|
|
<template v-for="column in profile.words">
|
|
<div v-if="column.values.length" class="col-6 col-lg-3">
|
|
<h4 v-if="column.header" class="h6">
|
|
<Spelling :text="column.header" :markdown="profile.markdown" />
|
|
</h4>
|
|
|
|
<ExpandableList
|
|
:values="column.values"
|
|
:limit="16"
|
|
class="list-unstyled"
|
|
:is-static="isStatic"
|
|
:expand="expandLinks"
|
|
>
|
|
<template #default="s">
|
|
<Opinion
|
|
:word="s.el.value"
|
|
:opinion="s.el.opinion"
|
|
:custom-opinions="profile.opinions"
|
|
:markdown="profile.markdown"
|
|
/>
|
|
</template>
|
|
</ExpandableList>
|
|
</div>
|
|
</template>
|
|
</div>
|
|
</section>
|
|
|
|
<section v-if="(profile.events?.length ?? 0) + (profile.customEvents?.length ?? 0) > 0 && !isStatic && year" class="clearfix">
|
|
<h3>
|
|
<Icon v="calendar" />
|
|
<T>profile.calendar.header</T>
|
|
</h3>
|
|
|
|
<PersonalCalendar :year="year" :events="[...profile.events, ...profile.customEvents]" />
|
|
</section>
|
|
|
|
<section v-if="(profile.circle?.length ?? 0) > 0 && !isStatic" class="clearfix">
|
|
<h3>
|
|
<Icon v="heart-circle" />
|
|
<T>profile.circles.header</T>
|
|
</h3>
|
|
<div class="row">
|
|
<div v-for="connection in profile.circle" class="col-12 col-lg-4 pt-2 pb-2">
|
|
<Avatar :user="connection" :src="connection.avatar" class="float-start me-2" dsize="4rem" />
|
|
<h4>
|
|
<LocaleLink :link="`/@${connection.username}`" :locale="connection.locale">
|
|
@{{ connection.username }}
|
|
</LocaleLink>
|
|
<Tooltip v-if="connection.circleMutual" :text="$t('profile.circles.mutual')" class="small">
|
|
<Icon v="shield-check" set="s" />
|
|
</Tooltip>
|
|
</h4>
|
|
<p>{{ connection.relationship }}</p>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<section>
|
|
<OpinionLegend :custom="profile.opinions" :used="usedOpinions" />
|
|
<ul v-if="!isStatic" class="list-inline text-muted small text-center">
|
|
<li v-if="profile.id" class="list-inline-item">
|
|
<T>profile.creation</T><T>quotation.colon</T>
|
|
{{ $date($ulidTime(profile.id)) }}
|
|
</li>
|
|
<li v-if="profile.lastUpdate" class="list-inline-item">
|
|
<T>profile.lastUpdate</T><T>quotation.colon</T>
|
|
{{ $date($ulidTime(profile.lastUpdate)) }}
|
|
</li>
|
|
</ul>
|
|
</section>
|
|
</template>
|
|
<section v-else class="py-5">
|
|
<div class="alert alert-info text-center">
|
|
<p class="h5">
|
|
<Icon v="info-circle" />
|
|
<T>profile.visibility.accessDenied</T>
|
|
</p>
|
|
<p class="mb-0 pt-2">
|
|
<nuxt-link :to="{ name: 'user' }" class="btn btn-primary">
|
|
<Icon v="sign-in-alt" />
|
|
<T>user.login.action</T>
|
|
</nuxt-link>
|
|
</p>
|
|
</div>
|
|
</section>
|
|
</div>
|
|
</template>
|
|
|
|
<style lang="scss" scoped>
|
|
.avatar {
|
|
width: 100%;
|
|
max-width: 5rem;
|
|
max-height: 5rem;
|
|
}
|
|
|
|
.mw-50 {
|
|
min-width: 50%;
|
|
}
|
|
</style>
|