mirror of
https://gitlab.com/PronounsPage/PronounsPage.git
synced 2025-09-24 05:05:20 -04:00
Merge branch 'seo' into 'main'
seo improvements See merge request PronounsPage/PronounsPage!515
This commit is contained in:
commit
acca283120
28
app.vue
28
app.vue
@ -2,15 +2,13 @@
|
||||
import { useNuxtApp, useRuntimeConfig } from 'nuxt/app';
|
||||
import { useHead, useSeoMeta } from '@unhead/vue';
|
||||
import useConfig from '~/composables/useConfig.ts';
|
||||
import { getDefaultSeo } from '~/composables/useSimpleHead.ts';
|
||||
|
||||
const { $translator: translator } = useNuxtApp();
|
||||
const runtimeConfig = useRuntimeConfig();
|
||||
const config = useConfig();
|
||||
|
||||
const title = translator.translate('title');
|
||||
const description = translator.translate('description');
|
||||
const keywords = (translator.translate<string[]>('seo.keywords') || []).join(', ');
|
||||
const banner = `${runtimeConfig.public.baseUrl}/api/banner/zaimki.png`;
|
||||
const defaultSeo = getDefaultSeo(translator, runtimeConfig);
|
||||
const colour = '#C71585';
|
||||
|
||||
useHead({
|
||||
@ -26,25 +24,25 @@ useHead({
|
||||
});
|
||||
|
||||
useSeoMeta({
|
||||
title,
|
||||
title: defaultSeo.title,
|
||||
charset: 'utf-8',
|
||||
description,
|
||||
keywords,
|
||||
description: defaultSeo.description,
|
||||
keywords: defaultSeo.keywords,
|
||||
viewport: 'width=device-width, initial-scale=1',
|
||||
appleMobileWebAppTitle: title,
|
||||
appleMobileWebAppTitle: defaultSeo.title,
|
||||
themeColor: colour,
|
||||
|
||||
ogType: 'article',
|
||||
ogTitle: title,
|
||||
ogDescription: description,
|
||||
ogSiteName: title,
|
||||
ogImage: banner,
|
||||
ogTitle: defaultSeo.title,
|
||||
ogDescription: defaultSeo.description,
|
||||
ogSiteName: defaultSeo.title,
|
||||
ogImage: defaultSeo.banner,
|
||||
|
||||
twitterCard: 'summary_large_image',
|
||||
twitterTitle: title,
|
||||
twitterDescription: description,
|
||||
twitterTitle: defaultSeo.title,
|
||||
twitterDescription: defaultSeo.description,
|
||||
twitterSite: runtimeConfig.public.baseUrl,
|
||||
twitterImage: banner,
|
||||
twitterImage: defaultSeo.banner,
|
||||
});
|
||||
</script>
|
||||
|
||||
|
@ -52,7 +52,7 @@ export default defineComponent({
|
||||
},
|
||||
setup() {
|
||||
const { convertName } = useSpelling();
|
||||
const { data: authors } = useFetch<ContactAuthor[]>('/api/admin/list/footer');
|
||||
const { data: authors } = useFetch<ContactAuthor[]>('/api/admin/list/footer', { lazy: true });
|
||||
return {
|
||||
config: useConfig(),
|
||||
convertName,
|
||||
|
@ -1,40 +1,40 @@
|
||||
<template>
|
||||
<span v-if="config.calendar?.enabled">
|
||||
<component
|
||||
:is="event.level === EventLevel.CustomDay ? 'span' : 'nuxt-link'"
|
||||
v-if="range"
|
||||
:to="`/${config.calendar.route}/${eventDays[0]}`"
|
||||
class="badge bg-primary text-white"
|
||||
>
|
||||
<T v-if="month" :params="{ day: event.getRange(year) }">calendar.dates.{{ eventDays[0].month }}</T>
|
||||
<template v-else>{{ event.getRange(year) }}</template>
|
||||
</component>
|
||||
<Tooltip v-if="event.level === EventLevel.CustomDay" :text="$t('profile.calendar.customEvents.disclaimer')">
|
||||
<Icon v="user" />
|
||||
</Tooltip>
|
||||
<component
|
||||
:is="event.level === EventLevel.CustomDay ? 'span' : 'nuxt-link'"
|
||||
v-if="range"
|
||||
:to="`/${config.calendar?.route}/${eventDays[0]}`"
|
||||
class="badge bg-primary text-white"
|
||||
>
|
||||
<T v-if="month" :params="{ day: event.getRange(year) }">calendar.dates.{{ eventDays[0].month }}</T>
|
||||
<template v-else>
|
||||
<Flag v-if="event.flag" :alt="$t(`flags_alt.${event.flag.replace(/'/g, '*').replace(/ /g, '_')}`) || ''" :img="`/flags/${event.flag}.png`" />
|
||||
<Icon v-else v="arrow-circle-right" />
|
||||
{{ event.getRange(year) }}
|
||||
</template>
|
||||
<T v-if="$te(`calendar.events.${eventName}`, true)" :params="{ param: eventParam }">calendar.events.{{ eventName }}</T>
|
||||
<LinkedText v-else :text="eventName" />
|
||||
<a
|
||||
v-if="ics && event.level !== EventLevel.CustomDay"
|
||||
:href="`/api/queer-calendar-${config.locale}-${year}-${event.getUuid($config.public.baseUrl)}.ics`"
|
||||
class="small"
|
||||
:aria-label="`${$t('crud.download')} .ics`"
|
||||
:title="`${$t('crud.download')} .ics`"
|
||||
>
|
||||
<Icon v="calendar-plus" />
|
||||
</a>
|
||||
<small v-if="event.localCalendar">({{ event.localCalendar }})</small>
|
||||
<a v-if="addButton" href="#" class="text-success" @click.prevent="$emit('add', eventName)">
|
||||
<Icon v="plus-circle" hover />
|
||||
</a>
|
||||
<a v-if="removeButton" href="#" class="text-danger" @click.prevent="$emit('delete', eventName)">
|
||||
<Icon v="minus-circle" hover />
|
||||
</a>
|
||||
</span>
|
||||
</component>
|
||||
<Tooltip v-if="event.level === EventLevel.CustomDay" :text="$t('profile.calendar.customEvents.disclaimer')">
|
||||
<Icon v="user" />
|
||||
</Tooltip>
|
||||
<template v-else>
|
||||
<Flag v-if="event.flag" :alt="$t(`flags_alt.${event.flag.replace(/'/g, '*').replace(/ /g, '_')}`) || ''" :img="`/flags/${event.flag}.png`" />
|
||||
<Icon v-else v="arrow-circle-right" />
|
||||
</template>
|
||||
<T v-if="$te(`calendar.events.${eventName}`, true)" :params="{ param: eventParam }">calendar.events.{{ eventName }}</T>
|
||||
<LinkedText v-else :text="eventName" />
|
||||
<a
|
||||
v-if="ics && event.level !== EventLevel.CustomDay"
|
||||
:href="`/api/queer-calendar-${config.locale}-${year}-${event.getUuid($config.public.baseUrl)}.ics`"
|
||||
class="small"
|
||||
:aria-label="`${$t('crud.download')} .ics`"
|
||||
:title="`${$t('crud.download')} .ics`"
|
||||
>
|
||||
<Icon v="calendar-plus" />
|
||||
</a>
|
||||
<small v-if="event.localCalendar">({{ event.localCalendar }})</small>
|
||||
<a v-if="addButton" href="#" class="text-success" @click.prevent="$emit('add', eventName)">
|
||||
<Icon v="plus-circle" hover />
|
||||
</a>
|
||||
<a v-if="removeButton" href="#" class="text-danger" @click.prevent="$emit('delete', eventName)">
|
||||
<Icon v="minus-circle" hover />
|
||||
</a>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
|
@ -1,23 +1,21 @@
|
||||
<template>
|
||||
<span>
|
||||
<span v-for="(part, index) in exampleParts">
|
||||
<strong v-if="part.variable"><Morpheme
|
||||
:pronoun="pronoun"
|
||||
:morpheme="part.str"
|
||||
:counter="counter"
|
||||
:prepend="getPrepend(index)"
|
||||
:append="getAppend(index)"
|
||||
/></strong>
|
||||
<span v-else><Spelling :text="getPrepend(index) + part.str + getAppend(index)" /></span>
|
||||
</span>
|
||||
<small v-if="link">
|
||||
(<nuxt-link :to="`/${pronoun.canonicalName}`"><Spelling escape :text="pronoun.canonicalName" /></nuxt-link>)
|
||||
</small>
|
||||
<Pronunciation
|
||||
v-if="pronunciation && pronoun.pronounceable && example.toPronunciationString(pronoun)"
|
||||
:pronunciation="example.toPronunciationString(pronoun) as string"
|
||||
/>
|
||||
</span>
|
||||
<template v-for="(part, index) in exampleParts" :key="index">
|
||||
<strong v-if="part.variable"><Morpheme
|
||||
:pronoun="pronoun"
|
||||
:morpheme="part.str"
|
||||
:counter="counter"
|
||||
:prepend="getPrepend(index)"
|
||||
:append="getAppend(index)"
|
||||
/></strong>
|
||||
<Spelling v-else :text="getPrepend(index) + part.str + getAppend(index)" />
|
||||
</template>
|
||||
<small v-if="link">
|
||||
(<nuxt-link :to="`/${pronoun.canonicalName}`"><Spelling escape :text="pronoun.canonicalName" /></nuxt-link>)
|
||||
</small>
|
||||
<Pronunciation
|
||||
v-if="pronunciation && pronoun.pronounceable && example.toPronunciationString(pronoun)"
|
||||
:pronunciation="example.toPronunciationString(pronoun) as string"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
|
@ -201,7 +201,7 @@ import { useMainStore } from '../store/index.ts';
|
||||
export default {
|
||||
setup() {
|
||||
const translationModeVisibleCookie = useCookie('translationModeVisible', sessionCookieSetting);
|
||||
const versionBackend = useFetch('/api/version', { server: false });
|
||||
const versionBackend = useFetch('/api/version', { server: false, lazy: true });
|
||||
return {
|
||||
config: useConfig(),
|
||||
store: useMainStore(),
|
||||
|
@ -18,7 +18,7 @@
|
||||
<Avatar v-if="link.avatar" :user="link.avatar" dsize="1.6rem" />
|
||||
<Icon v-else :v="link.icon" :size="1.6" />
|
||||
<br>
|
||||
<span class="text-nowrap"><Spelling :text="link.text" /></span>
|
||||
<Spelling class="text-nowrap" :text="link.text" />
|
||||
</template>
|
||||
</PotentiallyExternalLink>
|
||||
</template>
|
||||
|
@ -26,7 +26,7 @@ export default defineComponent({
|
||||
text = escapeHtml(text);
|
||||
}
|
||||
if (!text) {
|
||||
return h('span');
|
||||
return h('span', this.$attrs);
|
||||
}
|
||||
|
||||
let isLink = false;
|
||||
@ -40,10 +40,10 @@ export default defineComponent({
|
||||
return h(Icon, { v: buffer });
|
||||
}
|
||||
|
||||
const bufferNode = h('span', { innerHTML: this.handleSpelling(buffer) });
|
||||
const attrs = { ...this.$attrs, innerHTML: this.handleSpelling(buffer) };
|
||||
|
||||
if (!isLink) {
|
||||
return bufferNode;
|
||||
return h('span', attrs);
|
||||
}
|
||||
|
||||
linkBuffer = linkBuffer.replace(/≡/g, '='); // meh workaround, i know…
|
||||
@ -59,23 +59,17 @@ export default defineComponent({
|
||||
) {
|
||||
return h(
|
||||
'a',
|
||||
{ href: linkBuffer, target: '_blank', rel: 'noopener' },
|
||||
[bufferNode],
|
||||
{ ...attrs, href: linkBuffer, target: '_blank', rel: 'noopener' },
|
||||
);
|
||||
}
|
||||
|
||||
if (linkBuffer.indexOf('#') === 0) {
|
||||
return h(
|
||||
'a',
|
||||
{ href: linkBuffer },
|
||||
[bufferNode],
|
||||
);
|
||||
return h('a', { ...attrs, href: linkBuffer });
|
||||
}
|
||||
|
||||
return h(
|
||||
NuxtLink,
|
||||
{ to: linkBuffer || `/${this.config.nouns.route}#${this.handleSpelling(buffer)}` },
|
||||
() => [bufferNode],
|
||||
{ ...attrs, to: linkBuffer || `/${this.config.nouns.route}#${this.handleSpelling(buffer)}` },
|
||||
);
|
||||
};
|
||||
const addChild = (): void => {
|
||||
@ -127,7 +121,7 @@ export default defineComponent({
|
||||
}
|
||||
addChild();
|
||||
|
||||
return h('span', children);
|
||||
return children;
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
@ -48,11 +48,14 @@ export default defineComponent({
|
||||
async setup() {
|
||||
const runtimeConfig = useRuntimeConfig();
|
||||
|
||||
const { data: stats } = await useFetch<{ overall: { users: number } }>('/api/admin/stats-public');
|
||||
const { data: stats } = await useFetch<{ overall: { users: number } }>(
|
||||
'/api/admin/stats-public',
|
||||
{ lazy: true },
|
||||
);
|
||||
|
||||
return {
|
||||
stats,
|
||||
selectedDay: storeToRefs(useMainStore()).selectedDay,
|
||||
celebrate1M: stats.value && stats.value.overall.users >= 1_000_000 && stats.value.overall.users < 1_005_000,
|
||||
calendar: buildCalendar(runtimeConfig.public.baseUrl),
|
||||
};
|
||||
},
|
||||
@ -67,6 +70,9 @@ export default defineComponent({
|
||||
$tRefs(): Refs {
|
||||
return this.$refs as unknown as Refs;
|
||||
},
|
||||
celebrate1M(): boolean {
|
||||
return this.stats !== null && this.stats.overall.users >= 1_000_000 && this.stats.overall.users < 1_005_000;
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
selectedDay() {
|
||||
|
@ -26,7 +26,7 @@ export default {
|
||||
emphasise: { type: Boolean },
|
||||
},
|
||||
async setup() {
|
||||
const { data: moderation } = await useFetch('/api/admin/moderation', { server: false });
|
||||
const { data: moderation } = await useFetch('/api/admin/moderation', { server: false, lazy: true });
|
||||
return {
|
||||
moderation,
|
||||
};
|
||||
|
@ -1,23 +1,21 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="main-asides d-flex flex-column flex-xxl-row justify-content-center align-items-center align-items-xxl-stretch">
|
||||
<aside v-if="!wide" class="aside-left p-0 p-xxl-3">
|
||||
<slot name="aside-left">
|
||||
<AdPlaceholder :phkey="['aside-left', null]" class="d-none d-xxl-block" />
|
||||
</slot>
|
||||
</aside>
|
||||
<main :class="[wide ? 'wide' : '']">
|
||||
<slot></slot>
|
||||
</main>
|
||||
<aside v-if="!wide" class="aside-right">
|
||||
<slot name="aside-right">
|
||||
<AdPlaceholder :phkey="['aside-right', null]" />
|
||||
</slot>
|
||||
</aside>
|
||||
</div>
|
||||
<div class="d-flex justify-content-center">
|
||||
<main><slot name="below"></slot></main>
|
||||
</div>
|
||||
<div class="main-asides d-flex flex-column flex-xxl-row justify-content-center align-items-center align-items-xxl-stretch">
|
||||
<aside v-if="!wide" class="aside-left p-0 p-xxl-3">
|
||||
<slot name="aside-left">
|
||||
<AdPlaceholder :phkey="['aside-left', null]" class="d-none d-xxl-block" />
|
||||
</slot>
|
||||
</aside>
|
||||
<main :class="[wide ? 'wide' : '']">
|
||||
<slot></slot>
|
||||
</main>
|
||||
<aside v-if="!wide" class="aside-right">
|
||||
<slot name="aside-right">
|
||||
<AdPlaceholder :phkey="['aside-right', null]" />
|
||||
</slot>
|
||||
</aside>
|
||||
</div>
|
||||
<div class="d-flex justify-content-center">
|
||||
<main><slot name="below"></slot></main>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
@ -1,14 +1,12 @@
|
||||
<template>
|
||||
<div>
|
||||
<div ref="svg" :class="border ? 'border' : ''" v-html="svg"></div>
|
||||
<button v-if="download && svg" class="btn btn-outline-primary btn-sm m-3" :disabled="generating" @click.prevent="generateDownload">
|
||||
<Spinner v-if="generating" />
|
||||
<template v-else>
|
||||
<Icon v="download" />
|
||||
<T>user.qr.download</T>
|
||||
</template>
|
||||
</button>
|
||||
</div>
|
||||
<div ref="svg" :class="border ? 'border' : ''" v-bind="$attrs" v-html="svg"></div>
|
||||
<button v-if="download && svg" class="btn btn-outline-primary btn-sm m-3" :disabled="generating" @click.prevent="generateDownload">
|
||||
<Spinner v-if="generating" />
|
||||
<template v-else>
|
||||
<Icon v="download" />
|
||||
<T>user.qr.download</T>
|
||||
</template>
|
||||
</button>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
@ -207,7 +207,7 @@ interface Data {
|
||||
export default defineComponent({
|
||||
async setup() {
|
||||
const dialogue = useDialogue();
|
||||
const { data: keys } = await useFetch('/api/sources/keys');
|
||||
const { data: keys } = await useFetch('/api/sources/keys', { lazy: true, default: () => [] });
|
||||
|
||||
return {
|
||||
config: useConfig(),
|
||||
|
@ -26,7 +26,7 @@ export default defineComponent({
|
||||
text = safeInlineMarkdown(text);
|
||||
}
|
||||
|
||||
return h('span', { innerHTML: this.handleSpelling(text) });
|
||||
return h('span', { ...this.$attrs, innerHTML: this.handleSpelling(text) });
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
@ -71,7 +71,7 @@
|
||||
<script>
|
||||
export default {
|
||||
setup() {
|
||||
const { data: stats } = useFetch('/api/admin/stats-public');
|
||||
const { data: stats } = useFetch('/api/admin/stats-public', { lazy: true });
|
||||
return {
|
||||
stats,
|
||||
};
|
||||
|
@ -1,13 +1,14 @@
|
||||
<template>
|
||||
<component :is="Array.isArray(txt) ? 'div' : 'span'" :class="[translationMode ? 't-translation-mode' : '', modified ? 't-modified' : '']" @click="clicked">
|
||||
<template v-if="Array.isArray(txt)">
|
||||
<p v-for="p in txt">
|
||||
<Icon v-if="icon" :v="icon" /><LinkedText :text="p" />
|
||||
<template v-if="Array.isArray(txt)">
|
||||
<div :class="classes" @click="clicked">
|
||||
<p v-for="(paragraph, i) in txt" :key="i">
|
||||
<Icon v-if="icon" :v="icon" /><LinkedText :text="paragraph" />
|
||||
</p>
|
||||
</template><template v-else>
|
||||
<Icon v-if="icon" :v="icon" /><LinkedText :text="txt" />
|
||||
</template>
|
||||
</component>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else>
|
||||
<Icon v-if="icon" :v="icon" /><LinkedText :text="txt" :class="classes" @click="clicked" />
|
||||
</template>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
@ -47,6 +48,9 @@ export default {
|
||||
? this.$translator.applyParams(this.translationChanges[this.key], this.params || {})
|
||||
: this.$translator.translate(this.key, this.params || {});
|
||||
},
|
||||
classes() {
|
||||
return [this.translationMode ? 't-translation-mode' : '', this.modified ? 't-modified' : ''];
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
async clicked(e) {
|
||||
@ -87,7 +91,7 @@ export default {
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
<style lang="scss">
|
||||
@import "assets/variables";
|
||||
|
||||
.t-translation-mode {
|
||||
|
@ -130,7 +130,7 @@ interface Data {
|
||||
export default defineComponent({
|
||||
async setup() {
|
||||
const dialogue = useDialogue();
|
||||
const { data: keys } = await useFetch('/api/terms/keys');
|
||||
const { data: keys } = await useFetch('/api/terms/keys', { lazy: true, default: () => [] });
|
||||
|
||||
return {
|
||||
config: useConfig(),
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { useRuntimeConfig } from 'nuxt/app';
|
||||
import type { RuntimeConfig } from 'nuxt/schema';
|
||||
import { unref } from 'vue';
|
||||
import { useSeoMeta } from '@unhead/vue';
|
||||
import type { MaybeRef } from 'vue';
|
||||
@ -6,24 +7,35 @@ import type { UseSeoMetaInput } from '@unhead/vue';
|
||||
import { clearLinkedText } from '../src/helpers.ts';
|
||||
import type { Translator } from '../src/translator';
|
||||
|
||||
export const getDefaultSeo = (translator: Translator, runtimeConfig: RuntimeConfig) => ({
|
||||
title: translator.translate('title'),
|
||||
description: translator.translate('description'),
|
||||
keywords: (translator.translate<string[]>('seo.keywords') || []).join(', '),
|
||||
banner: `${runtimeConfig.public.baseUrl}/api/banner/zaimki.png`,
|
||||
});
|
||||
|
||||
export interface HeadParams {
|
||||
title?: MaybeRef<string | null>;
|
||||
description?: MaybeRef<string | null>;
|
||||
banner?: MaybeRef<string | null>;
|
||||
noindex?: boolean;
|
||||
keywords?: string[];
|
||||
keywords?: MaybeRef<string[]>;
|
||||
}
|
||||
|
||||
const DESCRIPTION_MAX_WORDCOUNT = 24;
|
||||
|
||||
export default (
|
||||
{ title, description, banner, noindex = false, keywords }: HeadParams,
|
||||
translator: Translator,
|
||||
): void => {
|
||||
const runtimeConfig = useRuntimeConfig();
|
||||
const defaultSeo = getDefaultSeo(translator, runtimeConfig);
|
||||
const seo: UseSeoMetaInput = {};
|
||||
|
||||
const seoTitle = () => {
|
||||
let titleUnwrapped = unref(title);
|
||||
if (!titleUnwrapped) {
|
||||
return translator.translate('title');
|
||||
return defaultSeo.title;
|
||||
}
|
||||
titleUnwrapped = titleUnwrapped.replace(/'/g, '\'');
|
||||
titleUnwrapped = titleUnwrapped.replace(/<\/?[^>]+(>|$)/g, ''); // html tags
|
||||
@ -38,11 +50,14 @@ export default (
|
||||
const seoDescription = () => {
|
||||
let descriptionUnwrapped = unref(description);
|
||||
if (!descriptionUnwrapped) {
|
||||
return undefined;
|
||||
return defaultSeo.description;
|
||||
}
|
||||
descriptionUnwrapped = clearLinkedText(descriptionUnwrapped);
|
||||
descriptionUnwrapped = `${descriptionUnwrapped.split(' ').slice(0, 24)
|
||||
.join(' ')}…`;
|
||||
const words = descriptionUnwrapped.split(' ');
|
||||
if (words.length > DESCRIPTION_MAX_WORDCOUNT) {
|
||||
descriptionUnwrapped = `${words.slice(0, DESCRIPTION_MAX_WORDCOUNT)
|
||||
.join(' ')}…`;
|
||||
}
|
||||
return descriptionUnwrapped;
|
||||
};
|
||||
seo.description = seoDescription;
|
||||
@ -52,7 +67,7 @@ export default (
|
||||
const seoImage = () => {
|
||||
let bannerUnwrapped = unref(banner);
|
||||
if (!bannerUnwrapped) {
|
||||
return undefined;
|
||||
return defaultSeo.banner;
|
||||
}
|
||||
bannerUnwrapped = bannerUnwrapped.replace(/^\//, '');
|
||||
if (!bannerUnwrapped.startsWith('https://')) {
|
||||
@ -67,10 +82,11 @@ export default (
|
||||
seo.robots = 'noindex';
|
||||
}
|
||||
|
||||
if (keywords) {
|
||||
const mergedKeywords = (translator.get<string[]>('seo.keywords', false, false, false) || []).concat(keywords);
|
||||
seo.keywords = mergedKeywords.join(', ');
|
||||
}
|
||||
seo.keywords = () => {
|
||||
const keywordsUnwrapped = unref(keywords) ?? [];
|
||||
const mergedKeywords = defaultSeo.keywords.split(', ').concat(keywordsUnwrapped);
|
||||
return mergedKeywords.join(', ');
|
||||
};
|
||||
|
||||
useSeoMeta(seo);
|
||||
};
|
||||
|
@ -344,12 +344,12 @@ export default defineComponent({
|
||||
const flags = buildFlags(config.locale);
|
||||
useSimpleHead({
|
||||
title: `@${username.value}`,
|
||||
description: profile.value ? profile.value.description : null,
|
||||
description: computed(() => profile.value ? profile.value.description : null),
|
||||
banner: `api/banner/@${username.value}.png`,
|
||||
noindex: true,
|
||||
keywords: profile.value
|
||||
keywords: computed(() => profile.value
|
||||
? profile.value.flags.map((flag) => translateForPronoun(flags[flag].display, mainPronoun.value))
|
||||
: undefined,
|
||||
: undefined),
|
||||
}, translator);
|
||||
|
||||
await user;
|
||||
|
@ -107,7 +107,7 @@ export default {
|
||||
}
|
||||
useSimpleHead({
|
||||
title: `${translator.translate('pronouns.intro')}: ${short}`.trim(),
|
||||
banner: `api/banner/${translator.translate('pronouns.any.short')}.png`,
|
||||
banner: `api/banner${route.path.replace(/\/$/, '')}.png`,
|
||||
}, translator);
|
||||
return {
|
||||
config,
|
||||
|
@ -35,11 +35,13 @@ export default {
|
||||
});
|
||||
|
||||
const { $translator: translator } = useNuxtApp();
|
||||
const route = useRoute();
|
||||
const config = useConfig();
|
||||
|
||||
useSimpleHead({
|
||||
title: config.pronouns.null.description,
|
||||
description: config.pronouns.null.history,
|
||||
banner: `api/banner${route.path.replace(/\/$/, '')}.png`,
|
||||
}, translator);
|
||||
|
||||
return {
|
||||
|
@ -36,10 +36,12 @@ export default {
|
||||
});
|
||||
|
||||
const { $translator: translator } = useNuxtApp();
|
||||
const route = useRoute();
|
||||
const config = useConfig();
|
||||
useSimpleHead({
|
||||
title: config.pronouns.mirror.name,
|
||||
description: config.pronouns.mirror.description,
|
||||
banner: `api/banner${route.path.replace(/\/$/, '')}.png`,
|
||||
}, translator);
|
||||
return {
|
||||
config,
|
||||
|
@ -172,7 +172,7 @@ export default {
|
||||
}, translator);
|
||||
}
|
||||
|
||||
const { data: sources } = useFetch('/api/sources');
|
||||
const { data: sources } = useFetch('/api/sources', { lazy: true });
|
||||
|
||||
return {
|
||||
config,
|
||||
|
Loading…
x
Reference in New Issue
Block a user