itube-specs 0.0.885 → 0.0.889
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/assets/icons/common/clock.svg +1 -1
- package/assets/icons/common/heart-stroke.svg +1 -1
- package/components/card/{video/dropdown.vue → dropdown/index.vue} +4 -4
- package/components/card/dropdown/trigger.vue +54 -0
- package/components/card/history.vue +1 -16
- package/components/card/video/index.vue +3 -19
- package/components/player/playlist.vue +3 -2
- package/components/section/nav-links.vue +2 -2
- package/components/view/videos.vue +3 -0
- package/composables/fetch/use-fetch-videos.ts +5 -3
- package/composables/use-meta.ts +3 -2
- package/lib/index.ts +1 -0
- package/lib/scheme/sort-items-search.ts +22 -0
- package/package.json +1 -1
- package/pages/library.vue +2 -2
- package/pages/search/[slug]/index.vue +41 -30
- package/pages/search/[slug]/videos.vue +4 -3
- package/runtime/utils/__tests__/get-selected-query.test.ts +9 -0
- package/services/api/videos.service.ts +2 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24"
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 6v6l4 2"/><circle cx="12" cy="12" r="10"/></svg>
|
|
@@ -1 +1 @@
|
|
|
1
|
-
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24"
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-heart w-5 h-5" aria-hidden="true"><path d="M2 9.5a5.5 5.5 0 0 1 9.591-3.676.56.56 0 0 0 .818 0A5.49 5.49 0 0 1 22 9.5c0 2.29-1.5 4-3 5.5l-5.492 5.313a2 2 0 0 1-3 .019L5 15c-1.5-1.5-3-3.2-3-5.5"></path></svg>
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
<slot name="trigger">
|
|
11
11
|
<button
|
|
12
12
|
:aria-label="$t('open_menu')"
|
|
13
|
-
class="card-
|
|
13
|
+
class="card-dropdown__trigger"
|
|
14
14
|
type="button"
|
|
15
15
|
>
|
|
16
16
|
<UiIcon
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
<template #header>
|
|
24
24
|
<CardVideoMini
|
|
25
25
|
:card="card"
|
|
26
|
-
class="card-
|
|
26
|
+
class="card-dropdown__preview _to-md"
|
|
27
27
|
/>
|
|
28
28
|
</template>
|
|
29
29
|
<template #items="className">
|
|
@@ -139,7 +139,7 @@ defineExpose({ openDropdown });
|
|
|
139
139
|
</script>
|
|
140
140
|
|
|
141
141
|
<style lang="scss">
|
|
142
|
-
.card-
|
|
142
|
+
.card-dropdown__trigger {
|
|
143
143
|
position: relative;
|
|
144
144
|
|
|
145
145
|
&::after {
|
|
@@ -147,7 +147,7 @@ defineExpose({ openDropdown });
|
|
|
147
147
|
}
|
|
148
148
|
}
|
|
149
149
|
|
|
150
|
-
.card-
|
|
150
|
+
.card-dropdown__preview {
|
|
151
151
|
padding: 16px 32px 16px 16px;
|
|
152
152
|
border-bottom: 1px solid $video-card-dropdown-border;
|
|
153
153
|
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
<!-- Кнопка меню карточки: до первого клика в DOM только она, чанк самого меню грузится по требованию -->
|
|
2
|
+
<template>
|
|
3
|
+
<LazyCardDropdown
|
|
4
|
+
v-if="isMounted"
|
|
5
|
+
v-bind="{ ...$attrs, ...props }"
|
|
6
|
+
auto-open
|
|
7
|
+
/>
|
|
8
|
+
<button
|
|
9
|
+
v-else
|
|
10
|
+
v-bind="{ onClick: $attrs.onClick }"
|
|
11
|
+
:aria-label="t('open_menu')"
|
|
12
|
+
:class="[$attrs.class, 'card-dropdown__trigger']"
|
|
13
|
+
type="button"
|
|
14
|
+
@click="mountDropdown"
|
|
15
|
+
>
|
|
16
|
+
<UiIcon
|
|
17
|
+
name="dots-vertical"
|
|
18
|
+
size="18"
|
|
19
|
+
/>
|
|
20
|
+
</button>
|
|
21
|
+
</template>
|
|
22
|
+
|
|
23
|
+
<script setup lang="ts">
|
|
24
|
+
import type { IVideoCard } from '../../../types';
|
|
25
|
+
|
|
26
|
+
defineOptions({ inheritAttrs: false });
|
|
27
|
+
|
|
28
|
+
const props = defineProps<{
|
|
29
|
+
card: IVideoCard
|
|
30
|
+
showWatchLater?: boolean
|
|
31
|
+
showShare?: boolean
|
|
32
|
+
showReport?: boolean
|
|
33
|
+
showRemove?: boolean
|
|
34
|
+
}>();
|
|
35
|
+
|
|
36
|
+
const { t } = useI18n();
|
|
37
|
+
|
|
38
|
+
const isMounted = ref(false);
|
|
39
|
+
|
|
40
|
+
async function mountDropdown() {
|
|
41
|
+
await import('./index.vue');
|
|
42
|
+
isMounted.value = true;
|
|
43
|
+
}
|
|
44
|
+
</script>
|
|
45
|
+
|
|
46
|
+
<style lang="scss">
|
|
47
|
+
.card-dropdown__trigger {
|
|
48
|
+
position: relative;
|
|
49
|
+
|
|
50
|
+
&::after {
|
|
51
|
+
@include area-point;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
</style>
|
|
@@ -94,28 +94,14 @@
|
|
|
94
94
|
</span>
|
|
95
95
|
</p>
|
|
96
96
|
|
|
97
|
-
<
|
|
98
|
-
v-if="dropdownMounted"
|
|
97
|
+
<CardDropdownTrigger
|
|
99
98
|
:card="videoCard"
|
|
100
99
|
class="card-history__dropdown"
|
|
101
100
|
show-remove
|
|
102
101
|
show-share
|
|
103
|
-
auto-open
|
|
104
102
|
@share="emit('share', videoCard)"
|
|
105
103
|
@remove="emit('remove', card)"
|
|
106
104
|
/>
|
|
107
|
-
<button
|
|
108
|
-
v-else
|
|
109
|
-
:aria-label="t('open_menu')"
|
|
110
|
-
class="card-history__dropdown card-video-dropdown__trigger"
|
|
111
|
-
type="button"
|
|
112
|
-
@click="dropdownMounted = true"
|
|
113
|
-
>
|
|
114
|
-
<UiIcon
|
|
115
|
-
name="dots-vertical"
|
|
116
|
-
size="18"
|
|
117
|
-
/>
|
|
118
|
-
</button>
|
|
119
105
|
</div>
|
|
120
106
|
</div>
|
|
121
107
|
</template>
|
|
@@ -188,7 +174,6 @@ const videoCard = computed<IVideoCard>(() => ({
|
|
|
188
174
|
thumbUrls: props.card.thumbUrls,
|
|
189
175
|
}));
|
|
190
176
|
|
|
191
|
-
const dropdownMounted = ref(false);
|
|
192
177
|
</script>
|
|
193
178
|
|
|
194
179
|
<style lang="scss">
|
|
@@ -110,26 +110,12 @@
|
|
|
110
110
|
/>
|
|
111
111
|
</div>
|
|
112
112
|
</div>
|
|
113
|
-
<
|
|
114
|
-
v-if="dropdownMounted"
|
|
113
|
+
<CardDropdownTrigger
|
|
115
114
|
:card="card"
|
|
116
115
|
:show-watch-later="featureFlags.FeatureWatchLaterEnabled"
|
|
117
116
|
class="card-video__dropdown"
|
|
118
117
|
show-report
|
|
119
|
-
auto-open
|
|
120
118
|
/>
|
|
121
|
-
<button
|
|
122
|
-
v-else
|
|
123
|
-
:aria-label="t('open_menu')"
|
|
124
|
-
class="card-video__dropdown card-video-dropdown__trigger"
|
|
125
|
-
type="button"
|
|
126
|
-
@click="dropdownMounted = true"
|
|
127
|
-
>
|
|
128
|
-
<UiIcon
|
|
129
|
-
name="dots-vertical"
|
|
130
|
-
size="18"
|
|
131
|
-
/>
|
|
132
|
-
</button>
|
|
133
119
|
</div>
|
|
134
120
|
</div>
|
|
135
121
|
</template>
|
|
@@ -167,8 +153,6 @@ const { activePreviewId, startPreviewByHover, stopPreviewByHover, startPreviewBy
|
|
|
167
153
|
|
|
168
154
|
const isPreviewShow = computed(() => activePreviewId.value === previewId);
|
|
169
155
|
|
|
170
|
-
const dropdownMounted = ref(false);
|
|
171
|
-
|
|
172
156
|
const posterError = ref(false);
|
|
173
157
|
const avatarError = ref(false);
|
|
174
158
|
|
|
@@ -266,13 +250,13 @@ $layout-breakpoint: 250px;
|
|
|
266
250
|
}
|
|
267
251
|
}
|
|
268
252
|
|
|
269
|
-
.card-
|
|
253
|
+
.card-dropdown__trigger > .ui-icon {
|
|
270
254
|
@include from-br(sm) {
|
|
271
255
|
transition: var(--transition) transform;
|
|
272
256
|
}
|
|
273
257
|
}
|
|
274
258
|
|
|
275
|
-
.card-
|
|
259
|
+
.card-dropdown__trigger:is(:hover, :focus-visible) > .ui-icon {
|
|
276
260
|
@include from-br(sm) {
|
|
277
261
|
transform: scale($video-card-dropdown-size--hover);
|
|
278
262
|
}
|
|
@@ -104,7 +104,7 @@
|
|
|
104
104
|
class="player-playlist__card-mini"
|
|
105
105
|
link
|
|
106
106
|
/>
|
|
107
|
-
<
|
|
107
|
+
<CardDropdownTrigger
|
|
108
108
|
:top="topDropdownIndex === index"
|
|
109
109
|
:card="item"
|
|
110
110
|
:show-remove="isOwner"
|
|
@@ -426,7 +426,8 @@ function onFollowClick() {
|
|
|
426
426
|
}
|
|
427
427
|
|
|
428
428
|
.player-playlist__card:is(:hover, :focus-visible) .player-playlist__card-dropdown,
|
|
429
|
-
.player-playlist__card-dropdown.--open
|
|
429
|
+
.player-playlist__card-dropdown.--open,
|
|
430
|
+
.player-playlist__card-dropdown:focus-within {
|
|
430
431
|
@include from-br(sm) {
|
|
431
432
|
opacity: 1;
|
|
432
433
|
}
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
<UiLink
|
|
5
5
|
v-for="(item, index) in items"
|
|
6
6
|
:key="`sort-item-${index}`"
|
|
7
|
-
:to="item.
|
|
7
|
+
:to="item.key ? linkTo(item, index) : generateLink(item.value)"
|
|
8
8
|
:active="isActive(item, index)"
|
|
9
9
|
:size="size"
|
|
10
10
|
:theme="theme"
|
|
@@ -59,7 +59,7 @@ function isActive(item: INavLinkItem, index: number) {
|
|
|
59
59
|
return true;
|
|
60
60
|
}
|
|
61
61
|
|
|
62
|
-
if (item.key
|
|
62
|
+
if (item.key) {
|
|
63
63
|
return route.query[item.key?.toLowerCase() || ''] === String(item.value)?.toLowerCase();
|
|
64
64
|
}
|
|
65
65
|
|
|
@@ -30,6 +30,7 @@
|
|
|
30
30
|
icon="video-camera"
|
|
31
31
|
/>
|
|
32
32
|
<LazySectionToolbar
|
|
33
|
+
:sort-items="sortItems"
|
|
33
34
|
class="view-videos__options"
|
|
34
35
|
hydrate-on-idle
|
|
35
36
|
/>
|
|
@@ -89,6 +90,7 @@
|
|
|
89
90
|
import type {
|
|
90
91
|
IChipsItem,
|
|
91
92
|
IModelCard,
|
|
93
|
+
INavLinkItem,
|
|
92
94
|
IVideoCard,
|
|
93
95
|
PaginatedResponse
|
|
94
96
|
} from '../../types';
|
|
@@ -106,6 +108,7 @@ const props = defineProps<{
|
|
|
106
108
|
dataModels: PaginatedResponse<IModelCard> | null
|
|
107
109
|
topRandomCategories: IChipsItem[] | null
|
|
108
110
|
flagImg?: string
|
|
111
|
+
sortItems?: INavLinkItem[]
|
|
109
112
|
}>();
|
|
110
113
|
|
|
111
114
|
const { t } = useI18n();
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import { selectDurationItems, sortItemsDefault, selectAddedItems } from '../../lib';
|
|
2
2
|
import type { Language } from '../../runtime';
|
|
3
3
|
import { AsyncData, getSelectedQuery } from '../../runtime';
|
|
4
|
-
import type { IVideoCard, PaginatedResponse } from '../../types';
|
|
4
|
+
import type { INavLinkItem, IVideoCard, PaginatedResponse } from '../../types';
|
|
5
5
|
import { useRoute } from 'vue-router';
|
|
6
6
|
|
|
7
7
|
export const useFetchVideos = async (
|
|
8
|
-
apiMethod: (...args: any[]) => Promise<PaginatedResponse<IVideoCard
|
|
8
|
+
apiMethod: (...args: any[]) => Promise<PaginatedResponse<IVideoCard>>,
|
|
9
|
+
// defaultOrder: null — сортировка не уходит в запрос, пока её не выбрали явно (выдача поиска)
|
|
10
|
+
{ sortItems = sortItemsDefault, defaultOrder = '-country_order' }: { sortItems?: INavLinkItem[], defaultOrder?: string | null } = {}
|
|
9
11
|
) => {
|
|
10
12
|
const route = useRoute();
|
|
11
13
|
|
|
@@ -30,7 +32,7 @@ export const useFetchVideos = async (
|
|
|
30
32
|
['per-page']: perPage,
|
|
31
33
|
},
|
|
32
34
|
filters.value,
|
|
33
|
-
getSelectedQuery(route,
|
|
35
|
+
getSelectedQuery(route, sortItems) || defaultOrder || undefined
|
|
34
36
|
)(),
|
|
35
37
|
{
|
|
36
38
|
watch: [
|
package/composables/use-meta.ts
CHANGED
|
@@ -31,8 +31,9 @@ export function useMeta(t: (key: string, params?: Record<string, unknown>) => st
|
|
|
31
31
|
const sortType = computed(() => {
|
|
32
32
|
const values = sortOptions?.map(item => item.value);
|
|
33
33
|
const queryValue = String(route?.query?.['sort']);
|
|
34
|
-
|
|
35
|
-
|
|
34
|
+
// Дефолт — первая кнопка набора: у выдачи поиска это relevant, у остальных listing'ов trending
|
|
35
|
+
const defaultValue = sortOptions?.[0]?.value || 'trending';
|
|
36
|
+
return queryValue && values?.includes(queryValue) ? queryValue : defaultValue;
|
|
36
37
|
});
|
|
37
38
|
|
|
38
39
|
function getPath(key: string) {
|
package/lib/index.ts
CHANGED
|
@@ -11,6 +11,7 @@ export * from './scheme/sort-channels';
|
|
|
11
11
|
export * from './scheme/sort-models';
|
|
12
12
|
export * from './scheme/sort-playlists';
|
|
13
13
|
export * from './scheme/sort-items-default';
|
|
14
|
+
export * from './scheme/sort-items-search';
|
|
14
15
|
export * from './scheme/history-periods';
|
|
15
16
|
export * from './scheme/categories-navigation';
|
|
16
17
|
export * from './scheme/navigation-items';
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { INavLinkItem } from '../../types';
|
|
2
|
+
|
|
3
|
+
/** Сортировки выдачи поиска: релевантность (без order — порядок отдаёт бэкенд) + те же new/longest. */
|
|
4
|
+
export const sortItemsSearch: Array<INavLinkItem> = [
|
|
5
|
+
{
|
|
6
|
+
title: 'sort.relevant',
|
|
7
|
+
value: 'relevant',
|
|
8
|
+
key: 'sort',
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
title: 'sort.new',
|
|
12
|
+
value: 'new-videos',
|
|
13
|
+
key: 'sort',
|
|
14
|
+
query: '-created',
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
title: 'sort.longest',
|
|
18
|
+
value: 'longest',
|
|
19
|
+
key: 'sort',
|
|
20
|
+
query: '-duration',
|
|
21
|
+
},
|
|
22
|
+
];
|
package/package.json
CHANGED
package/pages/library.vue
CHANGED
|
@@ -176,7 +176,7 @@ const newTotal = computed(() => libraryStub.newModels + libraryStub.newChannels
|
|
|
176
176
|
const collections = computed<LibraryLink[]>(() => [
|
|
177
177
|
{
|
|
178
178
|
to: generateLink('/favorites'),
|
|
179
|
-
icon: 'heart',
|
|
179
|
+
icon: 'heart-stroke',
|
|
180
180
|
title: t('favorites'),
|
|
181
181
|
subtitle: t('plural.videos', libraryStub.favorites),
|
|
182
182
|
},
|
|
@@ -206,7 +206,7 @@ const collections = computed<LibraryLink[]>(() => [
|
|
|
206
206
|
const following = computed<LibraryLink[]>(() => [
|
|
207
207
|
{
|
|
208
208
|
to: generateLink('/models'),
|
|
209
|
-
icon: 'user
|
|
209
|
+
icon: 'user',
|
|
210
210
|
title: t('models'),
|
|
211
211
|
subtitle: t('plural.models', libraryStub.models),
|
|
212
212
|
badge: t('library.new_count', { count: libraryStub.newModels }),
|
|
@@ -17,34 +17,19 @@
|
|
|
17
17
|
class="search__result"
|
|
18
18
|
>
|
|
19
19
|
<div
|
|
20
|
-
v-if="
|
|
20
|
+
v-if="categoriesData && Object.keys(categoriesData)?.length"
|
|
21
21
|
class="search__result-section"
|
|
22
22
|
>
|
|
23
23
|
<UiSectionTitle
|
|
24
|
-
:title="$t('
|
|
25
|
-
:count="
|
|
26
|
-
icon="
|
|
24
|
+
:title="$t('categories')"
|
|
25
|
+
:count="Object.keys(categoriesData).length"
|
|
26
|
+
icon="grid"
|
|
27
27
|
title-tag="h2"
|
|
28
28
|
/>
|
|
29
|
-
<
|
|
30
|
-
:
|
|
29
|
+
<GridCategories
|
|
30
|
+
:categories="categoriesData"
|
|
31
31
|
priority
|
|
32
|
-
|
|
33
|
-
<template #grid-end>
|
|
34
|
-
<LazyAdvNativeBottom
|
|
35
|
-
v-if="featureFlags.AdsNativeEnabled"
|
|
36
|
-
hydrate-on-visible
|
|
37
|
-
/>
|
|
38
|
-
<NuxtLink
|
|
39
|
-
v-if="videosData.items.length > MAXIMUM_VIDEOS || videosData.total > MAXIMUM_VIDEOS"
|
|
40
|
-
:to="generateLink(`/search/${slug}/videos`)"
|
|
41
|
-
class="search__show-all"
|
|
42
|
-
>
|
|
43
|
-
<span class="search__show-all-number">+{{ videosData.total - MAXIMUM_VIDEOS }}</span>
|
|
44
|
-
{{ $t('show_more_videos') }}
|
|
45
|
-
</NuxtLink>
|
|
46
|
-
</template>
|
|
47
|
-
</GridVideos>
|
|
32
|
+
/>
|
|
48
33
|
</div>
|
|
49
34
|
|
|
50
35
|
<div
|
|
@@ -83,19 +68,34 @@
|
|
|
83
68
|
</div>
|
|
84
69
|
|
|
85
70
|
<div
|
|
86
|
-
v-if="
|
|
71
|
+
v-if="videosData && videosData.items?.length"
|
|
87
72
|
class="search__result-section"
|
|
88
73
|
>
|
|
89
74
|
<UiSectionTitle
|
|
90
|
-
:title="$t('
|
|
91
|
-
:count="
|
|
92
|
-
icon="
|
|
75
|
+
:title="$t('videos')"
|
|
76
|
+
:count="videosData.total"
|
|
77
|
+
icon="video-camera"
|
|
93
78
|
title-tag="h2"
|
|
94
79
|
/>
|
|
95
|
-
<
|
|
96
|
-
:
|
|
80
|
+
<GridVideos
|
|
81
|
+
:items="videosData.items.slice(0, MAXIMUM_VIDEOS)"
|
|
97
82
|
priority
|
|
98
|
-
|
|
83
|
+
>
|
|
84
|
+
<template #grid-end>
|
|
85
|
+
<LazyAdvNativeBottom
|
|
86
|
+
v-if="featureFlags.AdsNativeEnabled"
|
|
87
|
+
hydrate-on-visible
|
|
88
|
+
/>
|
|
89
|
+
<NuxtLink
|
|
90
|
+
v-if="videosData.items.length > MAXIMUM_VIDEOS || videosData.total > MAXIMUM_VIDEOS"
|
|
91
|
+
:to="generateLink(`/search/${slug}/videos`)"
|
|
92
|
+
class="search__show-all"
|
|
93
|
+
>
|
|
94
|
+
<span class="search__show-all-number">+{{ videosData.total - MAXIMUM_VIDEOS }}</span>
|
|
95
|
+
{{ $t('show_more_videos') }}
|
|
96
|
+
</NuxtLink>
|
|
97
|
+
</template>
|
|
98
|
+
</GridVideos>
|
|
99
99
|
</div>
|
|
100
100
|
</div>
|
|
101
101
|
<UiEmptyState
|
|
@@ -139,10 +139,21 @@ const [
|
|
|
139
139
|
useFetchRelatedSearch(SearchApiService.getPhrases),
|
|
140
140
|
]);
|
|
141
141
|
|
|
142
|
-
useNoindexThinListing(() => videosData.value?.items?.length);
|
|
143
142
|
const { t } = useI18n();
|
|
144
143
|
const { generateLink } = useGenerateLink();
|
|
145
144
|
|
|
145
|
+
const hasVideosResults = Boolean(videosData.value?.items?.length);
|
|
146
|
+
const hasModelsResults = Boolean(modelsData.value?.items?.length);
|
|
147
|
+
const hasCategoriesResults = Boolean(categoriesData.value && Object.keys(categoriesData.value).length);
|
|
148
|
+
|
|
149
|
+
if (hasModelsResults && !hasVideosResults && !hasCategoriesResults) {
|
|
150
|
+
await navigateTo(generateLink(`/search/${slug.value}/models`), { redirectCode: 302, replace: true });
|
|
151
|
+
} else if (hasVideosResults && !hasModelsResults && !hasCategoriesResults) {
|
|
152
|
+
await navigateTo(generateLink(`/search/${slug.value}/videos`), { redirectCode: 302, replace: true });
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
useNoindexThinListing(() => videosData.value?.items?.length);
|
|
156
|
+
|
|
146
157
|
const MAXIMUM_VIDEOS = 23;
|
|
147
158
|
const MAXIMUM_MODELS = 15;
|
|
148
159
|
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
:title="h1"
|
|
12
12
|
:data-models="null"
|
|
13
13
|
:top-random-categories="null"
|
|
14
|
+
:sort-items="sortItemsSearch"
|
|
14
15
|
hide-categories
|
|
15
16
|
/>
|
|
16
17
|
<div
|
|
@@ -24,7 +25,7 @@
|
|
|
24
25
|
import type { IBreadcrumbItem } from '../../../types';
|
|
25
26
|
import { convertString } from '../../../runtime';
|
|
26
27
|
import { VideosApiService } from '../../../services/api/videos.service';
|
|
27
|
-
import {
|
|
28
|
+
import { sortItemsSearch } from '../../../lib';
|
|
28
29
|
|
|
29
30
|
const slug = useSlug();
|
|
30
31
|
|
|
@@ -33,7 +34,7 @@ const { t } = useI18n();
|
|
|
33
34
|
const [
|
|
34
35
|
{ data: videosData, status: videosStatus },
|
|
35
36
|
] = await Promise.all([
|
|
36
|
-
useFetchVideos(VideosApiService.getSearchByNicheFiltered(slug.value)),
|
|
37
|
+
useFetchVideos(VideosApiService.getSearchByNicheFiltered(slug.value), { sortItems: sortItemsSearch, defaultOrder: null }),
|
|
37
38
|
]);
|
|
38
39
|
|
|
39
40
|
useNoindexThinListing(() => videosData.value?.items?.length);
|
|
@@ -41,7 +42,7 @@ useNoindexThinListing(() => videosData.value?.items?.length);
|
|
|
41
42
|
const pagePureName = useGetPureRouteName();
|
|
42
43
|
const metaTitle = computed(() => convertString().toCapitalize(slug.value));
|
|
43
44
|
|
|
44
|
-
const { meta, h1 } = useMeta(t, pagePureName, useAppConfig().brandName,
|
|
45
|
+
const { meta, h1 } = useMeta(t, pagePureName, useAppConfig().brandName, sortItemsSearch, metaTitle);
|
|
45
46
|
|
|
46
47
|
watchEffect(() => {
|
|
47
48
|
if (slug) {
|
|
@@ -17,6 +17,15 @@ describe('getSelectedQuery', () => {
|
|
|
17
17
|
expect(getSelectedQuery(route as any, items as any)).toBeUndefined();
|
|
18
18
|
});
|
|
19
19
|
|
|
20
|
+
it('элемент найден, но без query → undefined (сортировка не уходит в запрос)', () => {
|
|
21
|
+
const route = { query: { sort: 'relevant' } };
|
|
22
|
+
const items = [
|
|
23
|
+
{ value: 'relevant', key: 'sort' },
|
|
24
|
+
{ value: 'newest', key: 'sort', query: '-created' },
|
|
25
|
+
];
|
|
26
|
+
expect(getSelectedQuery(route as any, items as any)).toBeUndefined();
|
|
27
|
+
});
|
|
28
|
+
|
|
20
29
|
it('пустой items → undefined', () => {
|
|
21
30
|
const route = { query: { sort: 'popular' } };
|
|
22
31
|
expect(getSelectedQuery(route as any, [])).toBeUndefined();
|
|
@@ -59,7 +59,7 @@ export class VideosApiService {
|
|
|
59
59
|
lang: Language,
|
|
60
60
|
pagination: RequestPagination,
|
|
61
61
|
videosFilter: IRequestVideoFilter,
|
|
62
|
-
order
|
|
62
|
+
order?: string,
|
|
63
63
|
niche = NicheHelper.getSiteDefaultNiche(),
|
|
64
64
|
): Promise<PaginatedResponse<IVideoCard>> => {
|
|
65
65
|
return await ApiHelper.fetch(
|
|
@@ -74,7 +74,7 @@ export class VideosApiService {
|
|
|
74
74
|
},
|
|
75
75
|
params: {
|
|
76
76
|
...pagination,
|
|
77
|
-
order,
|
|
77
|
+
...(order ? { order } : {}),
|
|
78
78
|
},
|
|
79
79
|
body: {
|
|
80
80
|
query: phrase,
|