import { computed, toValue } from 'vue'; import type { MaybeRefOrGetter } from 'vue'; import { useRoute } from 'vue-router'; import type { INavLinkItem, IPageSeo } from '../types'; type MetaText = MaybeRefOrGetter; type MetaParams = { /** функция перевода (i18n) */ t: (key: string, params?: Record) => string /** ключ страницы в `meta.*` (например 'videos', 'models') */ pageKey: string /** название бренда для подстановки в мета */ brandName: string /** опции сортировки — задают уровень `meta...*` в ключах */ sortOptions?: INavLinkItem[] /** подстановка `{text}` — основной текст (slug, имя и т.д.) */ text?: MetaText /** подстановка `{secondText}` */ secondText?: MetaText /** подстановка `{thirdText}` */ thirdText?: MetaText /** подстановка `{fourText}` */ fourthText?: MetaText /** * Подменяет i18n-ключ заголовка (по умолчанию 'h1') — вызывается с фактической * сортировкой, потому что варианты заголовка есть не у каждой из них. * Нужен, когда подставляемого текста может не быть и в шаблоне остаётся * висячий предлог (`… Videos in ` без страны). */ h1Key?: (sortType: string) => string /** * Мета от API (поле `seo` в ответе листинга). Каждое непустое поле перебивает * свой i18n-шаблон; чего API не отдал — остаётся на i18n. Бренд бэкенд уже * подставляет сам, а номер страницы — нет, поэтому суффикс пагинации * дописываем здесь. */ seo?: MaybeRefOrGetter }; /** * Генерирует реактивные meta (title, description) и h1 с учётом сортировки, страницы и i18n-ключей. */ export function useMeta({ t, pageKey, brandName, sortOptions, text, secondText, thirdText, fourthText, h1Key, seo }: MetaParams) { const route = useRoute(); const sortType = computed(() => { const values = sortOptions?.map(item => item.value); const queryValue = String(route?.query?.['sort']); // Дефолт — первая кнопка набора: у выдачи поиска это relevant, у остальных listing'ов trending const defaultValue = sortOptions?.[0]?.value || 'trending'; return queryValue && values?.includes(queryValue) ? queryValue : defaultValue; }); const pageSuffix = computed(() => { const pageNumber = route.query?.['page'] ? Number(route.query['page']) : 1; return pageNumber === 1 ? '' : ` ${t('page')} ${pageNumber}`; }); function getPath(key: string) { const pageText = pageSuffix.value; return t( `meta.${pageKey}.${sortOptions && sortOptions.length > 0 ? `${sortType.value}.` : ''}${key}`, { text: toValue(text), secondText: toValue(secondText), thirdText: toValue(thirdText), fourText: toValue(fourthText), brandName: `| ${brandName}`, //черточка '|' нужна обязательно page: pageText, } ); } const apiSeo = computed(() => toValue(seo) || null); /** * Дописывает « Page N» так же, как это делают i18n-шаблоны — перед брендом, * который бэкенд уже приклеил через `|`. */ function withPageSuffix(value: string) { const suffix = pageSuffix.value; if (!suffix) return value; const brandIndex = value.lastIndexOf(' | '); return brandIndex === -1 ? `${value}${suffix}` : `${value.slice(0, brandIndex)}${suffix}${value.slice(brandIndex)}`; } const metaTitle = computed(() => { const apiTitle = apiSeo.value?.title; return apiTitle ? withPageSuffix(apiTitle) : getPath('title'); }); const h1 = computed(() => apiSeo.value?.h1 || getPath(h1Key?.(sortType.value) || 'h1')); const meta = computed(() => { const apiDescription = apiSeo.value?.description; const description = apiDescription ? `${apiDescription}${pageSuffix.value}` : getPath('meta_description'); const keywords = apiSeo.value?.keywords; return { title: metaTitle.value, meta: [ { name: 'description', content: description }, ...(keywords ? [{ name: 'keywords', content: keywords }] : []), { property: 'og:title', content: metaTitle.value }, { property: 'og:description', content: description }, { name: 'twitter:title', content: metaTitle.value }, { name: 'twitter:description', content: description }, ], }; }); return { meta, h1, }; }