itube-specs 0.0.895 → 0.0.897
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/components/card/video/index.vue +17 -2
- package/composables/use-meta.ts +67 -28
- package/package.json +1 -1
- package/pages/2257.vue +1 -1
- package/pages/categories/all.vue +1 -1
- package/pages/categories/index.vue +1 -1
- package/pages/categories/letter/[slug].vue +7 -1
- package/pages/channels/[channel].vue +8 -1
- package/pages/channels/index.vue +6 -1
- package/pages/confirm/registration/[token].vue +1 -1
- package/pages/contacts.vue +1 -1
- package/pages/dmca.vue +1 -1
- package/pages/index.vue +8 -10
- package/pages/models/[slug]/channel/[channel].vue +9 -1
- package/pages/models/[slug]/index.vue +9 -1
- package/pages/models/[slug]/tag/[tag].vue +10 -1
- package/pages/models/index.vue +7 -1
- package/pages/models/letter/[slug].vue +8 -1
- package/pages/playlists/[playlistId]/[slug].vue +9 -1
- package/pages/playlists/index.vue +6 -1
- package/pages/policy.vue +1 -1
- package/pages/search/[slug]/index.vue +7 -1
- package/pages/search/[slug]/models.vue +7 -1
- package/pages/search/[slug]/videos.vue +7 -1
- package/pages/tag/[tag].vue +8 -1
- package/pages/terms.vue +1 -1
- package/pages/user/profile.vue +1 -1
- package/pages/user/recovery-password.vue +1 -1
- package/pages/user/settings.vue +1 -1
- package/pages/videos/[slug].vue +8 -1
- package/runtime/utils/cleaners/__tests__/clean-model-info.test.ts +13 -0
- package/runtime/utils/cleaners/clean-model-info.ts +1 -0
- package/server/api/models/list-by-niche.post.ts +3 -1
- package/types/card-info.d.ts +2 -0
- package/types/index.d.ts +1 -0
- package/types/page-seo.d.ts +6 -0
- package/types/paginated-response.d.ts +3 -0
- package/types/raw/raw-model-info.d.ts +2 -1
|
@@ -150,6 +150,8 @@ const { generateLink } = useGenerateLink();
|
|
|
150
150
|
|
|
151
151
|
const pageName = useGetPureRouteName();
|
|
152
152
|
const isChannelPage = computed(() => pageName === 'channels-channel');
|
|
153
|
+
const isModelPage = computed(() => pageName.startsWith('models-slug'));
|
|
154
|
+
const isSearchPage = computed(() => pageName.startsWith('search-slug'));
|
|
153
155
|
|
|
154
156
|
const previewId = useId();
|
|
155
157
|
const { activePreviewId, startPreviewByHover, stopPreviewByHover, startPreviewByTouch } = useVideoPreview();
|
|
@@ -193,10 +195,23 @@ const channelLink = computed(() => props.card.channelName
|
|
|
193
195
|
const slug = useSlug();
|
|
194
196
|
|
|
195
197
|
const cardTags = computed(() => {
|
|
196
|
-
const
|
|
198
|
+
const query = slug.value.toLowerCase();
|
|
199
|
+
|
|
200
|
+
const models = props.card.models
|
|
201
|
+
? [ ...props.card.models ].filter(item => !isModelPage.value || item.toLowerCase() !== query)
|
|
202
|
+
: [];
|
|
203
|
+
|
|
204
|
+
const searchRank = (item: string) => {
|
|
205
|
+
if (!isSearchPage.value || !query) return 0;
|
|
206
|
+
const name = item.toLowerCase();
|
|
207
|
+
if (name === query) return 0;
|
|
208
|
+
return name.includes(query) ? 1 : 2;
|
|
209
|
+
};
|
|
210
|
+
|
|
197
211
|
const sortModels = models.sort((a, b) =>
|
|
198
|
-
a.localeCompare(b)
|
|
212
|
+
searchRank(a) - searchRank(b) || a.localeCompare(b)
|
|
199
213
|
);
|
|
214
|
+
|
|
200
215
|
return sortModels?.map(item => convertString().toChips(item, 'models'));
|
|
201
216
|
});
|
|
202
217
|
</script>
|
package/composables/use-meta.ts
CHANGED
|
@@ -1,9 +1,27 @@
|
|
|
1
|
-
import { computed,
|
|
2
|
-
import type {
|
|
1
|
+
import { computed, toValue } from 'vue';
|
|
2
|
+
import type { MaybeRefOrGetter } from 'vue';
|
|
3
3
|
import { useRoute } from 'vue-router';
|
|
4
|
-
import type { INavLinkItem } from '../types';
|
|
4
|
+
import type { INavLinkItem, IPageSeo } from '../types';
|
|
5
5
|
|
|
6
|
-
type
|
|
6
|
+
type MetaText = MaybeRefOrGetter<string | undefined>;
|
|
7
|
+
|
|
8
|
+
type MetaParams = {
|
|
9
|
+
/** функция перевода (i18n) */
|
|
10
|
+
t: (key: string, params?: Record<string, unknown>) => string
|
|
11
|
+
/** ключ страницы в `meta.*` (например 'videos', 'models') */
|
|
12
|
+
pageKey: string
|
|
13
|
+
/** название бренда для подстановки в мета */
|
|
14
|
+
brandName: string
|
|
15
|
+
/** опции сортировки — задают уровень `meta.<page>.<sort>.*` в ключах */
|
|
16
|
+
sortOptions?: INavLinkItem[]
|
|
17
|
+
/** подстановка `{text}` — основной текст (slug, имя и т.д.) */
|
|
18
|
+
text?: MetaText
|
|
19
|
+
/** подстановка `{secondText}` */
|
|
20
|
+
secondText?: MetaText
|
|
21
|
+
/** подстановка `{thirdText}` */
|
|
22
|
+
thirdText?: MetaText
|
|
23
|
+
/** подстановка `{fourText}` */
|
|
24
|
+
fourthText?: MetaText
|
|
7
25
|
/**
|
|
8
26
|
* Подменяет i18n-ключ заголовка (по умолчанию 'h1') — вызывается с фактической
|
|
9
27
|
* сортировкой, потому что варианты заголовка есть не у каждой из них.
|
|
@@ -11,21 +29,19 @@ type MetaOptions = {
|
|
|
11
29
|
* висячий предлог (`… Videos in ` без страны).
|
|
12
30
|
*/
|
|
13
31
|
h1Key?: (sortType: string) => string
|
|
32
|
+
/**
|
|
33
|
+
* Мета от API (поле `seo` в ответе листинга). Каждое непустое поле перебивает
|
|
34
|
+
* свой i18n-шаблон; чего API не отдал — остаётся на i18n. Бренд бэкенд уже
|
|
35
|
+
* подставляет сам, а номер страницы — нет, поэтому суффикс пагинации
|
|
36
|
+
* дописываем здесь.
|
|
37
|
+
*/
|
|
38
|
+
seo?: MaybeRefOrGetter<IPageSeo | null | undefined>
|
|
14
39
|
};
|
|
15
40
|
|
|
16
41
|
/**
|
|
17
42
|
* Генерирует реактивные meta (title, description) и h1 с учётом сортировки, страницы и i18n-ключей.
|
|
18
|
-
* @param t - функция перевода (i18n)
|
|
19
|
-
* @param page - ключ страницы для i18n (например 'videos', 'models')
|
|
20
|
-
* @param brandName - название бренда для подстановки в мета
|
|
21
|
-
* @param sortOptions - опции сортировки (если есть)
|
|
22
|
-
* @param text - основной текст (slug, имя и т.д.)
|
|
23
|
-
* @param secondText - второй подставляемый текст
|
|
24
|
-
* @param thirdText - третий подставляемый текст
|
|
25
|
-
* @param fourthText - четвёртый подставляемый текст
|
|
26
|
-
* @param options - уточнения (подмена ключа h1)
|
|
27
43
|
*/
|
|
28
|
-
export function useMeta(t
|
|
44
|
+
export function useMeta({ t, pageKey, brandName, sortOptions, text, secondText, thirdText, fourthText, h1Key, seo }: MetaParams) {
|
|
29
45
|
const route = useRoute();
|
|
30
46
|
|
|
31
47
|
const sortType = computed(() => {
|
|
@@ -36,37 +52,60 @@ export function useMeta(t: (key: string, params?: Record<string, unknown>) => st
|
|
|
36
52
|
return queryValue && values?.includes(queryValue) ? queryValue : defaultValue;
|
|
37
53
|
});
|
|
38
54
|
|
|
55
|
+
const pageSuffix = computed(() => {
|
|
56
|
+
const pageNumber = route.query?.['page'] ? Number(route.query['page']) : 1;
|
|
57
|
+
return pageNumber === 1 ? '' : ` ${t('page')} ${pageNumber}`;
|
|
58
|
+
});
|
|
59
|
+
|
|
39
60
|
function getPath(key: string) {
|
|
40
|
-
const
|
|
41
|
-
const secondTextValue = unref(secondText);
|
|
42
|
-
const thirdTextValue = unref(thirdText);
|
|
43
|
-
const fourTextValue = unref(fourthText);
|
|
44
|
-
const pageNumber = computed(() => route.query?.['page'] ? Number(route.query['page']) : 1);
|
|
45
|
-
const pageText = unref(pageNumber) === 1 ? null : ` ${t('page')} ${unref(pageNumber)}`;
|
|
61
|
+
const pageText = pageSuffix.value;
|
|
46
62
|
|
|
47
63
|
return t(
|
|
48
|
-
`meta.${
|
|
64
|
+
`meta.${pageKey}.${sortOptions && sortOptions.length > 0 ? `${sortType.value}.` : ''}${key}`,
|
|
49
65
|
{
|
|
50
|
-
text:
|
|
51
|
-
secondText:
|
|
52
|
-
thirdText:
|
|
53
|
-
fourText:
|
|
66
|
+
text: toValue(text),
|
|
67
|
+
secondText: toValue(secondText),
|
|
68
|
+
thirdText: toValue(thirdText),
|
|
69
|
+
fourText: toValue(fourthText),
|
|
54
70
|
brandName: `| ${brandName}`, //черточка '|' нужна обязательно
|
|
55
71
|
page: pageText,
|
|
56
72
|
}
|
|
57
73
|
);
|
|
58
74
|
}
|
|
59
75
|
|
|
60
|
-
const
|
|
61
|
-
|
|
76
|
+
const apiSeo = computed(() => toValue(seo) || null);
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Дописывает « Page N» так же, как это делают i18n-шаблоны — перед брендом,
|
|
80
|
+
* который бэкенд уже приклеил через `|`.
|
|
81
|
+
*/
|
|
82
|
+
function withPageSuffix(value: string) {
|
|
83
|
+
const suffix = pageSuffix.value;
|
|
84
|
+
if (!suffix) return value;
|
|
85
|
+
|
|
86
|
+
const brandIndex = value.lastIndexOf(' | ');
|
|
87
|
+
return brandIndex === -1
|
|
88
|
+
? `${value}${suffix}`
|
|
89
|
+
: `${value.slice(0, brandIndex)}${suffix}${value.slice(brandIndex)}`;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const metaTitle = computed(() => {
|
|
93
|
+
const apiTitle = apiSeo.value?.title;
|
|
94
|
+
return apiTitle ? withPageSuffix(apiTitle) : getPath('title');
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
const h1 = computed(() => apiSeo.value?.h1 || getPath(h1Key?.(sortType.value) || 'h1'));
|
|
62
98
|
|
|
63
99
|
const meta = computed(() => {
|
|
64
|
-
const
|
|
100
|
+
const apiDescription = apiSeo.value?.description;
|
|
101
|
+
const description = apiDescription ? `${apiDescription}${pageSuffix.value}` : getPath('meta_description');
|
|
102
|
+
const keywords = apiSeo.value?.keywords;
|
|
65
103
|
|
|
66
104
|
return {
|
|
67
105
|
title: metaTitle.value,
|
|
68
106
|
meta: [
|
|
69
107
|
{ name: 'description', content: description },
|
|
108
|
+
...(keywords ? [{ name: 'keywords', content: keywords }] : []),
|
|
70
109
|
{ property: 'og:title', content: metaTitle.value },
|
|
71
110
|
{ property: 'og:description', content: description },
|
|
72
111
|
{ name: 'twitter:title', content: metaTitle.value },
|
package/package.json
CHANGED
package/pages/2257.vue
CHANGED
|
@@ -64,7 +64,7 @@ const { t } = useI18n();
|
|
|
64
64
|
const { generateLink } = useGenerateLink();
|
|
65
65
|
const pagePureName = useGetPureRouteName();
|
|
66
66
|
const { brandName, emails } = useAppConfig();
|
|
67
|
-
const { meta, h1 } = useMeta(t, pagePureName, brandName);
|
|
67
|
+
const { meta, h1 } = useMeta({ t, pageKey: pagePureName, brandName });
|
|
68
68
|
|
|
69
69
|
watchEffect(() => {
|
|
70
70
|
useHead(meta.value);
|
package/pages/categories/all.vue
CHANGED
|
@@ -68,7 +68,7 @@ const [
|
|
|
68
68
|
]);
|
|
69
69
|
|
|
70
70
|
const pagePureName = useGetPureRouteName();
|
|
71
|
-
const { meta, h1 } = useMeta(t, pagePureName, useAppConfig().brandName);
|
|
71
|
+
const { meta, h1 } = useMeta({ t, pageKey: pagePureName, brandName: useAppConfig().brandName });
|
|
72
72
|
|
|
73
73
|
watchEffect(() => {
|
|
74
74
|
useHead(meta.value);
|
|
@@ -69,7 +69,7 @@ const { t } = useI18n();
|
|
|
69
69
|
const { generateLink } = useGenerateLink();
|
|
70
70
|
|
|
71
71
|
const pagePureName = useGetPureRouteName();
|
|
72
|
-
const { meta, h1 } = useMeta(t, pagePureName, useAppConfig().brandName);
|
|
72
|
+
const { meta, h1 } = useMeta({ t, pageKey: pagePureName, brandName: useAppConfig().brandName });
|
|
73
73
|
|
|
74
74
|
watchEffect(() => {
|
|
75
75
|
useHead(meta.value);
|
|
@@ -59,7 +59,13 @@ const slug = computed(() => route.params[ 'slug' ] as string || '');
|
|
|
59
59
|
|
|
60
60
|
const pagePureName = useGetPureRouteName();
|
|
61
61
|
const metaTitle = computed(() => convertString().toCapitalize(slug.value));
|
|
62
|
-
const { meta, h1 } = useMeta(
|
|
62
|
+
const { meta, h1 } = useMeta({
|
|
63
|
+
t,
|
|
64
|
+
pageKey: pagePureName,
|
|
65
|
+
brandName: useAppConfig().brandName,
|
|
66
|
+
sortOptions: [],
|
|
67
|
+
text: metaTitle,
|
|
68
|
+
});
|
|
63
69
|
|
|
64
70
|
watchEffect(() => {
|
|
65
71
|
useHead(meta.value);
|
|
@@ -117,7 +117,14 @@ const routeQueryCategories = computed(() => {
|
|
|
117
117
|
|
|
118
118
|
const {
|
|
119
119
|
meta
|
|
120
|
-
} = useMeta(
|
|
120
|
+
} = useMeta({
|
|
121
|
+
t,
|
|
122
|
+
pageKey: pageName,
|
|
123
|
+
brandName: useAppConfig().brandName,
|
|
124
|
+
sortOptions: sortItemsDefault,
|
|
125
|
+
text: metaTitle,
|
|
126
|
+
secondText: routeQueryCategories,
|
|
127
|
+
});
|
|
121
128
|
|
|
122
129
|
watchEffect(() => {
|
|
123
130
|
useHead(meta.value);
|
package/pages/channels/index.vue
CHANGED
|
@@ -102,7 +102,12 @@ const items = computed(() => {
|
|
|
102
102
|
});
|
|
103
103
|
|
|
104
104
|
const pagePureName = useGetPureRouteName();
|
|
105
|
-
const { meta, h1 } = useMeta(
|
|
105
|
+
const { meta, h1 } = useMeta({
|
|
106
|
+
t,
|
|
107
|
+
pageKey: pagePureName,
|
|
108
|
+
brandName: useAppConfig().brandName,
|
|
109
|
+
sortOptions: sortChannels,
|
|
110
|
+
});
|
|
106
111
|
|
|
107
112
|
watchEffect(() => {
|
|
108
113
|
useHead(meta.value);
|
|
@@ -61,7 +61,7 @@ onMounted(() => {
|
|
|
61
61
|
});
|
|
62
62
|
|
|
63
63
|
const pagePureName = useGetPureRouteName();
|
|
64
|
-
const { meta } = useMeta(t, pagePureName, useAppConfig().brandName);
|
|
64
|
+
const { meta } = useMeta({ t, pageKey: pagePureName, brandName: useAppConfig().brandName });
|
|
65
65
|
|
|
66
66
|
watchEffect(() => {
|
|
67
67
|
useHead(meta.value);
|
package/pages/contacts.vue
CHANGED
|
@@ -280,7 +280,7 @@ watch(() => route.query, (value) => {
|
|
|
280
280
|
const placeholder = computed(() => t('contacts.type_here'));
|
|
281
281
|
|
|
282
282
|
const pagePureName = useGetPureRouteName();
|
|
283
|
-
const { meta } = useMeta(t, pagePureName, useAppConfig().brandName);
|
|
283
|
+
const { meta } = useMeta({ t, pageKey: pagePureName, brandName: useAppConfig().brandName });
|
|
284
284
|
|
|
285
285
|
watchEffect(() => {
|
|
286
286
|
useHead(meta.value);
|
package/pages/dmca.vue
CHANGED
|
@@ -122,7 +122,7 @@ const { t } = useI18n();
|
|
|
122
122
|
const { generateLink } = useGenerateLink();
|
|
123
123
|
const pagePureName = useGetPureRouteName();
|
|
124
124
|
const { brandName, emails } = useAppConfig();
|
|
125
|
-
const { meta, h1 } = useMeta(t, pagePureName, brandName);
|
|
125
|
+
const { meta, h1 } = useMeta({ t, pageKey: pagePureName, brandName });
|
|
126
126
|
|
|
127
127
|
watchEffect(() => {
|
|
128
128
|
useHead(meta.value);
|
package/pages/index.vue
CHANGED
|
@@ -69,18 +69,16 @@ const isCountryShown = computed(() => !route.query['sort'] && !!clientCountry.na
|
|
|
69
69
|
|
|
70
70
|
const countryName = computed(() => (isCountryShown.value ? clientCountry.name.value : ''));
|
|
71
71
|
|
|
72
|
-
const { meta, h1 } = useMeta(
|
|
72
|
+
const { meta, h1 } = useMeta({
|
|
73
73
|
t,
|
|
74
|
-
pageName,
|
|
75
|
-
useAppConfig().brandName,
|
|
76
|
-
sortItemsDefault,
|
|
77
|
-
routeQueryCategories,
|
|
78
|
-
countryName,
|
|
79
|
-
undefined,
|
|
80
|
-
undefined,
|
|
74
|
+
pageKey: pageName,
|
|
75
|
+
brandName: useAppConfig().brandName,
|
|
76
|
+
sortOptions: sortItemsDefault,
|
|
77
|
+
text: routeQueryCategories,
|
|
78
|
+
secondText: countryName,
|
|
81
79
|
// Страну не определили — берём заголовок без неё, иначе останется висячий предлог.
|
|
82
|
-
|
|
83
|
-
);
|
|
80
|
+
h1Key: sortType => (sortType === 'trending' && !countryName.value ? 'h1_no_country' : 'h1'),
|
|
81
|
+
});
|
|
84
82
|
|
|
85
83
|
const countryFlagImg = computed(() => (isCountryShown.value ? clientCountry.flagSrc.value : undefined));
|
|
86
84
|
|
|
@@ -68,7 +68,15 @@ const channelRoute = String(route.params[ 'channel' ]);
|
|
|
68
68
|
const channelSlug = computed(() => convertString().fromSlug(channelRoute));
|
|
69
69
|
const text = computed(() => `${metaTitle.value} ${convertString().toCapitalize(channelSlug.value)}`);
|
|
70
70
|
|
|
71
|
-
const { meta, h1 } = useMeta(
|
|
71
|
+
const { meta, h1 } = useMeta({
|
|
72
|
+
t,
|
|
73
|
+
pageKey: pageName,
|
|
74
|
+
brandName: useAppConfig().brandName,
|
|
75
|
+
sortOptions: sortItemsDefault,
|
|
76
|
+
text,
|
|
77
|
+
secondText: routeQueryCategories,
|
|
78
|
+
seo: () => cardInfo.value?.seo,
|
|
79
|
+
});
|
|
72
80
|
|
|
73
81
|
const breadcrumbs: IBreadcrumbItem[] = [
|
|
74
82
|
{
|
|
@@ -74,7 +74,15 @@ const routeQueryCategories = computed(() => {
|
|
|
74
74
|
return '';
|
|
75
75
|
});
|
|
76
76
|
|
|
77
|
-
const { meta, h1 } = useMeta(
|
|
77
|
+
const { meta, h1 } = useMeta({
|
|
78
|
+
t,
|
|
79
|
+
pageKey: pageName,
|
|
80
|
+
brandName: useAppConfig().brandName,
|
|
81
|
+
sortOptions: sortItemsDefault,
|
|
82
|
+
text: metaTitle,
|
|
83
|
+
secondText: routeQueryCategories,
|
|
84
|
+
seo: () => cardInfo.value?.seo,
|
|
85
|
+
});
|
|
78
86
|
|
|
79
87
|
const breadcrumbs: IBreadcrumbItem[] = [
|
|
80
88
|
{
|
|
@@ -65,7 +65,16 @@ const routeQueryCategories = computed(() => {
|
|
|
65
65
|
return '';
|
|
66
66
|
});
|
|
67
67
|
|
|
68
|
-
const { meta, h1 } = useMeta(
|
|
68
|
+
const { meta, h1 } = useMeta({
|
|
69
|
+
t,
|
|
70
|
+
pageKey: pageName,
|
|
71
|
+
brandName: useAppConfig().brandName,
|
|
72
|
+
sortOptions: sortItemsDefault,
|
|
73
|
+
text: metaTitle,
|
|
74
|
+
secondText: routeQueryCategories,
|
|
75
|
+
thirdText: tagSlug,
|
|
76
|
+
seo: () => cardInfo.value?.seo,
|
|
77
|
+
});
|
|
69
78
|
|
|
70
79
|
const breadcrumbs: IBreadcrumbItem[] = [
|
|
71
80
|
{
|
package/pages/models/index.vue
CHANGED
|
@@ -69,7 +69,13 @@ if (models.value?.items) {
|
|
|
69
69
|
models.value.items = applyTestImages(models.value.items)!;
|
|
70
70
|
}
|
|
71
71
|
|
|
72
|
-
const { meta, h1 } = useMeta(
|
|
72
|
+
const { meta, h1 } = useMeta({
|
|
73
|
+
t,
|
|
74
|
+
pageKey: pagePureName,
|
|
75
|
+
brandName: useAppConfig().brandName,
|
|
76
|
+
sortOptions: sortModels,
|
|
77
|
+
seo: () => models.value?.seo,
|
|
78
|
+
});
|
|
73
79
|
|
|
74
80
|
watchEffect(() => {
|
|
75
81
|
useHead(meta.value);
|
|
@@ -39,7 +39,14 @@ const [
|
|
|
39
39
|
useFetchModelsByLetter(ModelsApiService.getListByNicheAndLetter),
|
|
40
40
|
]);
|
|
41
41
|
|
|
42
|
-
const { meta, h1 } = useMeta(
|
|
42
|
+
const { meta, h1 } = useMeta({
|
|
43
|
+
t,
|
|
44
|
+
pageKey: pagePureName,
|
|
45
|
+
brandName: useAppConfig().brandName,
|
|
46
|
+
sortOptions: sortModels,
|
|
47
|
+
text: metaTitle,
|
|
48
|
+
seo: () => models.value?.seo,
|
|
49
|
+
});
|
|
43
50
|
|
|
44
51
|
watchEffect(() => {
|
|
45
52
|
useHead(meta.value);
|
|
@@ -107,7 +107,15 @@ const pagePureName = useGetPureRouteName();
|
|
|
107
107
|
const title = computed(() => videoData.value?.title || '');
|
|
108
108
|
const description = computed(() => videoData.value?.description || '');
|
|
109
109
|
const playlistName = computed(() => playlistsData.value?.name);
|
|
110
|
-
const { meta } = useMeta(
|
|
110
|
+
const { meta } = useMeta({
|
|
111
|
+
t,
|
|
112
|
+
pageKey: pagePureName,
|
|
113
|
+
brandName: useAppConfig().brandName,
|
|
114
|
+
sortOptions: [],
|
|
115
|
+
text: title,
|
|
116
|
+
secondText: playlistName,
|
|
117
|
+
thirdText: description,
|
|
118
|
+
});
|
|
111
119
|
|
|
112
120
|
watchEffect(() => {
|
|
113
121
|
if (videoData.value?.title) {
|
|
@@ -120,7 +120,12 @@ watch(
|
|
|
120
120
|
const { t } = useI18n();
|
|
121
121
|
|
|
122
122
|
const pagePureName = useGetPureRouteName();
|
|
123
|
-
const { meta, h1 } = useMeta(
|
|
123
|
+
const { meta, h1 } = useMeta({
|
|
124
|
+
t,
|
|
125
|
+
pageKey: pagePureName,
|
|
126
|
+
brandName: useAppConfig().brandName,
|
|
127
|
+
sortOptions: sortPlaylists,
|
|
128
|
+
});
|
|
124
129
|
|
|
125
130
|
watchEffect(() => {
|
|
126
131
|
useHead(meta.value);
|
package/pages/policy.vue
CHANGED
|
@@ -182,7 +182,7 @@
|
|
|
182
182
|
const { t } = useI18n();
|
|
183
183
|
const pagePureName = useGetPureRouteName();
|
|
184
184
|
const { brandName, emails } = useAppConfig();
|
|
185
|
-
const { meta, h1 } = useMeta(t, pagePureName, brandName);
|
|
185
|
+
const { meta, h1 } = useMeta({ t, pageKey: pagePureName, brandName });
|
|
186
186
|
|
|
187
187
|
watchEffect(() => {
|
|
188
188
|
useHead(meta.value);
|
|
@@ -194,7 +194,13 @@ const relatedSearchesItems = computed(() => relatedSearch.value && typeof relate
|
|
|
194
194
|
const pagePureName = useGetPureRouteName();
|
|
195
195
|
const metaTitle = computed(() => convertString().toCapitalize(slug.value));
|
|
196
196
|
|
|
197
|
-
const { meta, h1 } = useMeta(
|
|
197
|
+
const { meta, h1 } = useMeta({
|
|
198
|
+
t,
|
|
199
|
+
pageKey: pagePureName,
|
|
200
|
+
brandName: useAppConfig().brandName,
|
|
201
|
+
sortOptions: [],
|
|
202
|
+
text: metaTitle,
|
|
203
|
+
});
|
|
198
204
|
|
|
199
205
|
watchEffect(() => {
|
|
200
206
|
if (slug) {
|
|
@@ -39,7 +39,13 @@ useNoindexThinListing(() => modelsData.value?.items?.length);
|
|
|
39
39
|
|
|
40
40
|
const metaTitle = computed(() => convertString().toCapitalize(slug.value));
|
|
41
41
|
|
|
42
|
-
const { meta, h1 } = useMeta(
|
|
42
|
+
const { meta, h1 } = useMeta({
|
|
43
|
+
t,
|
|
44
|
+
pageKey: pagePureName,
|
|
45
|
+
brandName: useAppConfig().brandName,
|
|
46
|
+
sortOptions: sortModels,
|
|
47
|
+
text: metaTitle,
|
|
48
|
+
});
|
|
43
49
|
|
|
44
50
|
watchEffect(() => {
|
|
45
51
|
if (slug) {
|
|
@@ -42,7 +42,13 @@ useNoindexThinListing(() => videosData.value?.items?.length);
|
|
|
42
42
|
const pagePureName = useGetPureRouteName();
|
|
43
43
|
const metaTitle = computed(() => convertString().toCapitalize(slug.value));
|
|
44
44
|
|
|
45
|
-
const { meta, h1 } = useMeta(
|
|
45
|
+
const { meta, h1 } = useMeta({
|
|
46
|
+
t,
|
|
47
|
+
pageKey: pagePureName,
|
|
48
|
+
brandName: useAppConfig().brandName,
|
|
49
|
+
sortOptions: sortItemsSearch,
|
|
50
|
+
text: metaTitle,
|
|
51
|
+
});
|
|
46
52
|
|
|
47
53
|
watchEffect(() => {
|
|
48
54
|
if (slug) {
|
package/pages/tag/[tag].vue
CHANGED
|
@@ -58,7 +58,14 @@ const routeQueryCategories = computed(() => {
|
|
|
58
58
|
return '';
|
|
59
59
|
});
|
|
60
60
|
|
|
61
|
-
const { meta, h1 } = useMeta(
|
|
61
|
+
const { meta, h1 } = useMeta({
|
|
62
|
+
t,
|
|
63
|
+
pageKey: pageName,
|
|
64
|
+
brandName: useAppConfig().brandName,
|
|
65
|
+
sortOptions: sortItemsDefault,
|
|
66
|
+
text: metaTitle,
|
|
67
|
+
secondText: routeQueryCategories,
|
|
68
|
+
});
|
|
62
69
|
|
|
63
70
|
watchEffect(() => {
|
|
64
71
|
if (slug.value) {
|
package/pages/terms.vue
CHANGED
|
@@ -465,7 +465,7 @@
|
|
|
465
465
|
const { t } = useI18n();
|
|
466
466
|
const pagePureName = useGetPureRouteName();
|
|
467
467
|
const { brandName } = useAppConfig();
|
|
468
|
-
const { meta, h1 } = useMeta(t, pagePureName, brandName);
|
|
468
|
+
const { meta, h1 } = useMeta({ t, pageKey: pagePureName, brandName });
|
|
469
469
|
|
|
470
470
|
watchEffect(() => {
|
|
471
471
|
useHead(meta.value);
|
package/pages/user/profile.vue
CHANGED
|
@@ -112,7 +112,7 @@ const items = computed<Array<IDataItem>>(() => {
|
|
|
112
112
|
});
|
|
113
113
|
|
|
114
114
|
const pagePureName = useGetPureRouteName();
|
|
115
|
-
const { meta } = useMeta(t, pagePureName, useAppConfig().brandName);
|
|
115
|
+
const { meta } = useMeta({ t, pageKey: pagePureName, brandName: useAppConfig().brandName });
|
|
116
116
|
|
|
117
117
|
watchEffect(() => {
|
|
118
118
|
useHead(meta.value);
|
|
@@ -54,7 +54,7 @@ function update() {
|
|
|
54
54
|
}
|
|
55
55
|
|
|
56
56
|
const pagePureName = useGetPureRouteName();
|
|
57
|
-
const { meta } = useMeta(t, pagePureName, useAppConfig().brandName);
|
|
57
|
+
const { meta } = useMeta({ t, pageKey: pagePureName, brandName: useAppConfig().brandName });
|
|
58
58
|
|
|
59
59
|
watchEffect(() => {
|
|
60
60
|
useHead(meta.value);
|
package/pages/user/settings.vue
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
const { t } = useI18n();
|
|
9
9
|
|
|
10
10
|
const pagePureName = useGetPureRouteName();
|
|
11
|
-
const { meta } = useMeta(t, pagePureName, useAppConfig().brandName);
|
|
11
|
+
const { meta } = useMeta({ t, pageKey: pagePureName, brandName: useAppConfig().brandName });
|
|
12
12
|
|
|
13
13
|
watchEffect(() => {
|
|
14
14
|
useHead(meta.value);
|
package/pages/videos/[slug].vue
CHANGED
|
@@ -60,7 +60,14 @@ useSetSeoOverride(() => ({
|
|
|
60
60
|
const title = computed(() => videoData.value?.title || '');
|
|
61
61
|
const description = computed(() => videoData.value?.description || '');
|
|
62
62
|
const pagePureName = useGetPureRouteName();
|
|
63
|
-
const { meta } = useMeta(
|
|
63
|
+
const { meta } = useMeta({
|
|
64
|
+
t,
|
|
65
|
+
pageKey: pagePureName,
|
|
66
|
+
brandName: useAppConfig().brandName,
|
|
67
|
+
sortOptions: [],
|
|
68
|
+
text: title,
|
|
69
|
+
secondText: description,
|
|
70
|
+
});
|
|
64
71
|
|
|
65
72
|
const webpThumbs = videoData.value?.thumbUrls?.webp;
|
|
66
73
|
const webpEntries = webpThumbs ? Object.entries(webpThumbs) : [];
|
|
@@ -16,6 +16,12 @@ describe('cleanModelInfo', () => {
|
|
|
16
16
|
aka_names: ['Alias A', 'Alias B'],
|
|
17
17
|
birth_summary: 'Born 1990, USA',
|
|
18
18
|
nationality_iso_codes: ['US', 'CA'],
|
|
19
|
+
seo: {
|
|
20
|
+
title: 'Model Name Videos | Brand',
|
|
21
|
+
description: 'meta desc',
|
|
22
|
+
h1: 'Model Name Porn Videos',
|
|
23
|
+
keywords: 'a, b',
|
|
24
|
+
},
|
|
19
25
|
};
|
|
20
26
|
const result = cleanModelInfo(raw as any);
|
|
21
27
|
expect(result.title).toBe('Model Name');
|
|
@@ -29,6 +35,12 @@ describe('cleanModelInfo', () => {
|
|
|
29
35
|
expect(result.akaNames).toBe('Alias A, Alias B');
|
|
30
36
|
expect(result.birthSummary).toBe('Born 1990, USA');
|
|
31
37
|
expect(result.nationalityIsoCodes).toEqual(['US', 'CA']);
|
|
38
|
+
expect(result.seo).toEqual({
|
|
39
|
+
title: 'Model Name Videos | Brand',
|
|
40
|
+
description: 'meta desc',
|
|
41
|
+
h1: 'Model Name Porn Videos',
|
|
42
|
+
keywords: 'a, b',
|
|
43
|
+
});
|
|
32
44
|
});
|
|
33
45
|
|
|
34
46
|
it('пустые данные → дефолты', () => {
|
|
@@ -44,5 +56,6 @@ describe('cleanModelInfo', () => {
|
|
|
44
56
|
expect(result.akaNames).toBe('');
|
|
45
57
|
expect(result.birthSummary).toBe('');
|
|
46
58
|
expect(result.nationalityIsoCodes).toEqual([]);
|
|
59
|
+
expect(result.seo).toBeNull();
|
|
47
60
|
});
|
|
48
61
|
});
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { cleanModelCard, getUrlWithProxiedParams } from '../../../runtime';
|
|
2
|
-
import type { IModelFilter, IRawModelCard, IModelCard } from '../../../types';
|
|
2
|
+
import type { IModelFilter, IRawModelCard, IModelCard, IPageSeo } from '../../../types';
|
|
3
3
|
|
|
4
4
|
export default defineEventHandler(async (event) => {
|
|
5
5
|
const apiUrl = process.env.NUXT_PUBLIC_API_DOMAIN;
|
|
@@ -17,6 +17,7 @@ export default defineEventHandler(async (event) => {
|
|
|
17
17
|
|
|
18
18
|
const items: IRawModelCard[] = rawData?.data?.items || [];
|
|
19
19
|
const filters: IModelFilter[] = rawData?.data?.filters || [];
|
|
20
|
+
const seo: IPageSeo | null = rawData?.data?.seo || null;
|
|
20
21
|
|
|
21
22
|
const cleanedItems: IModelCard[] = items && items.length > 0 ? items.map(cleanModelCard) : [];
|
|
22
23
|
|
|
@@ -26,5 +27,6 @@ export default defineEventHandler(async (event) => {
|
|
|
26
27
|
total: rawData?.data?.total,
|
|
27
28
|
items: cleanedItems,
|
|
28
29
|
filters: filters,
|
|
30
|
+
seo: seo,
|
|
29
31
|
};
|
|
30
32
|
});
|
package/types/card-info.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { IParameterModel } from './parameter-model';
|
|
2
2
|
import type { IChipsItem } from './chips-item';
|
|
3
|
+
import type { IPageSeo } from './page-seo';
|
|
3
4
|
|
|
4
5
|
export interface ICardInfo {
|
|
5
6
|
title: string;
|
|
@@ -9,6 +10,7 @@ export interface ICardInfo {
|
|
|
9
10
|
guid?: string;
|
|
10
11
|
fullDescription?: string
|
|
11
12
|
miniCardTitle?: string
|
|
13
|
+
seo?: IPageSeo | null;
|
|
12
14
|
|
|
13
15
|
// Fields specific to Channel
|
|
14
16
|
url?: string;
|
package/types/index.d.ts
CHANGED
|
@@ -52,6 +52,7 @@ export * from './chips-item.d.ts';
|
|
|
52
52
|
export * from './library-feed.d.ts';
|
|
53
53
|
export * from './nav-link-item.d.ts';
|
|
54
54
|
export * from './paginated-response.d.ts';
|
|
55
|
+
export * from './page-seo.d.ts';
|
|
55
56
|
export * from './request-pagination.d.ts';
|
|
56
57
|
export * from './tab-item.d.ts';
|
|
57
58
|
export * from './filter-scheme.d.ts';
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { IRawThumbUrls } from './raw-thumbs-urls';
|
|
2
|
-
import type { IParameterModel } from '~/types';
|
|
2
|
+
import type { IParameterModel, IPageSeo } from '~/types';
|
|
3
3
|
|
|
4
4
|
export interface IRawModelInfo {
|
|
5
5
|
guid: string;
|
|
@@ -41,4 +41,5 @@ export interface IRawModelInfo {
|
|
|
41
41
|
}[];
|
|
42
42
|
birth_summary: string;
|
|
43
43
|
nationality_iso_codes: string[];
|
|
44
|
+
seo?: IPageSeo;
|
|
44
45
|
}
|