PronounsPage/components/Profile.vue

289 lines
12 KiB
Vue

<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="`/${$config.contact.team.route}`" 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>
<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(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="typeof s.el.pronoun === 'string' ? s.el.pronoun : s.el.pronoun.name(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 + profile.customEvents.length > 0 && !isStatic" 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 && !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" />
<p v-if="!isStatic && profile.lastUpdate" class="text-muted small text-center">
<T>profile.lastUpdate</T><T>quotation.colon</T>
{{ $date($ulidTime(profile.lastUpdate)) }}
</p>
</section>
</div>
</template>
<script>
import spelling from '../plugins/spelling.ts';
import mainPronoun from '../plugins/mainPronoun.ts';
import { calendar } from '../src/calendar/calendar.ts';
import { buildFlags } from '../src/flags.ts';
import { buildImageUrl } from '../src/helpers.ts';
export default spelling.extend(mainPronoun).extend({
props: {
user: { required: true },
profile: { required: true },
terms: { default: null },
isStatic: { type: Boolean },
expandLinks: { type: Boolean },
},
data() {
return {
allFlags: buildFlags(this.$config.locale),
glue: ` ${this.$t('pronouns.or')} `,
minAge: this.$config.ageLimit || 13,
normaliseUrl: (url) => {
try {
return new URL(url).toString();
} catch {
return null;
}
},
year: calendar.getCurrentYear(),
};
},
computed: {
countFlags() {
return this.profile.flags.length + this.profile.customFlags.length;
},
manyFlagsLayout() {
return this.countFlags > 36 || this.countFlags === 0 || !this.hasDescriptionColumn;
},
hasDescriptionColumn() {
return this.profile.age && this.profile.age > this.minAge ||
this.profile.description.trim().length ||
this.profile.team ||
this.profile.timezone
;
},
mainRowCount() {
let c = 0;
if (this.profile.names.length) {
c++;
}
if (this.profile.pronouns.length && this.$config.pronouns.enabled) {
c++;
}
if (this.profile.links.length) {
c++;
}
return c;
},
usedOpinions() {
return new Set([
...this.profile.names.map((r) => r.opinion),
...this.profile.pronouns.map((r) => r.opinion),
...this.profile.words.flat().map((c) => c.values)
.flat()
.map((r) => r.opinion),
]);
},
},
methods: { buildImageUrl },
});
</script>
<style lang="scss" scoped>
.avatar {
width: 100%;
max-width: 5rem;
max-height: 5rem;
}
.mw-50 {
min-width: 50%;
}
</style>