itube-specs 0.0.951 → 0.0.952
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.
|
@@ -74,7 +74,7 @@
|
|
|
74
74
|
|
|
75
75
|
<span
|
|
76
76
|
v-if="model.goal"
|
|
77
|
-
:class="{'--visible':
|
|
77
|
+
:class="{'--visible': isGoalVisible}"
|
|
78
78
|
class="card-webcam__goal"
|
|
79
79
|
>
|
|
80
80
|
<span class="card-webcam__goal-text _truncate">
|
|
@@ -100,12 +100,15 @@
|
|
|
100
100
|
:niche-path="nichePath"
|
|
101
101
|
/>
|
|
102
102
|
|
|
103
|
+
<!-- Ховер по имени показывает только цель: превью — дело постера. -->
|
|
103
104
|
<a
|
|
104
105
|
:href="model.url"
|
|
105
106
|
class="card-webcam__name _truncate"
|
|
106
107
|
target="_blank"
|
|
107
108
|
rel="nofollow noopener sponsored"
|
|
108
109
|
@click="onClick"
|
|
110
|
+
@mouseenter="onNameEnter"
|
|
111
|
+
@mouseleave="onNameLeave"
|
|
109
112
|
>{{ model.name }}</a>
|
|
110
113
|
|
|
111
114
|
<span
|
|
@@ -315,6 +318,24 @@ function onLeave() {
|
|
|
315
318
|
previewSlot.release(previewId.value);
|
|
316
319
|
}
|
|
317
320
|
|
|
321
|
+
/**
|
|
322
|
+
* Цель стримера появляется в двух случаях: при превью (ховер по постеру) и просто
|
|
323
|
+
* при ховере по имени модели — там поток не нужен, а прочитать цель хочется.
|
|
324
|
+
*/
|
|
325
|
+
const isNameHovered = ref(false);
|
|
326
|
+
|
|
327
|
+
const isGoalVisible = computed(() => isPreviewActive.value || isNameHovered.value);
|
|
328
|
+
|
|
329
|
+
function onNameEnter() {
|
|
330
|
+
if (props.touchPreview) return;
|
|
331
|
+
|
|
332
|
+
isNameHovered.value = true;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
function onNameLeave() {
|
|
336
|
+
isNameHovered.value = false;
|
|
337
|
+
}
|
|
338
|
+
|
|
318
339
|
/**
|
|
319
340
|
* Тач: превью включает удержание тумбы — короткий тап должен открывать модель, а
|
|
320
341
|
* не запускать поток. Движение пальцем считаем скроллом и удержание отменяем.
|
|
@@ -118,6 +118,14 @@ const props = defineProps<{
|
|
|
118
118
|
/** Где живёт состояние: свой query-параметр `filter_<name>` (модели, вебкамы) или общая
|
|
119
119
|
* строка `categories=<группа>_<значение>` (каталог видео, синхронно с попапом фильтра). */
|
|
120
120
|
mode?: 'param' | 'categories'
|
|
121
|
+
/**
|
|
122
|
+
* Своя подпись ВЫБРАННОГО значения на кнопке селекта. Без пропса — обычное поведение
|
|
123
|
+
* (название выбранной опции). Строка — шаблон с `{value}` (например `{value} speaking`),
|
|
124
|
+
* функция получает фильтр и выбранную опцию и может вернуть пусто — тогда подпись
|
|
125
|
+
* стандартная. Нужна там, где выбранное значение читается фразой: в разделе вебкамов
|
|
126
|
+
* язык на кнопке и в чипсе выглядит как «Russian speaking».
|
|
127
|
+
*/
|
|
128
|
+
selectedLabel?: string | ((filter: IModelFilter, option: IModelFilterOptions) => string | undefined)
|
|
121
129
|
}>();
|
|
122
130
|
|
|
123
131
|
const route = useRoute();
|
|
@@ -300,7 +308,18 @@ function getSelectLabel(item: IModelFilter) {
|
|
|
300
308
|
const current = readQueryValue(item.name, route.query);
|
|
301
309
|
if (!current) return item.title;
|
|
302
310
|
const opt = item.options.find(o => o.name === current);
|
|
303
|
-
|
|
311
|
+
if (!opt) return item.title;
|
|
312
|
+
|
|
313
|
+
return overrideSelectedLabel(item, opt) || capitalize(opt.title);
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/** Подпись выбранного значения из пропса `selectedLabel`; пусто — берётся стандартная. */
|
|
317
|
+
function overrideSelectedLabel(item: IModelFilter, option: IModelFilterOptions) {
|
|
318
|
+
if (!props.selectedLabel) return '';
|
|
319
|
+
|
|
320
|
+
if (typeof props.selectedLabel === 'function') return props.selectedLabel(item, option) || '';
|
|
321
|
+
|
|
322
|
+
return props.selectedLabel.replace('{value}', capitalize(option.title));
|
|
304
323
|
}
|
|
305
324
|
|
|
306
325
|
// Количество бек считает не для всех опций (у синтетической «all» его нет) — скобки без числа не рисуем
|
|
@@ -53,6 +53,7 @@ query-параметрах `filter_*`. Формат провайдера при
|
|
|
53
53
|
<FilterSelects
|
|
54
54
|
v-if="filters.length"
|
|
55
55
|
:filters="filters"
|
|
56
|
+
:selected-label="selectedFilterLabel"
|
|
56
57
|
/>
|
|
57
58
|
</div>
|
|
58
59
|
</div>
|
|
@@ -114,11 +115,11 @@ query-параметрах `filter_*`. Формат провайдера при
|
|
|
114
115
|
</template>
|
|
115
116
|
|
|
116
117
|
<script setup lang="ts">
|
|
117
|
-
import type { IModelCard, IModelFilter, ISwitcherItem, IStripcashModel, IWebcamModelsResponse, PaginatedResponse } from '../../types';
|
|
118
|
+
import type { IModelCard, IModelFilter, IModelFilterOptions, ISwitcherItem, IStripcashModel, IWebcamModelsResponse, PaginatedResponse } from '../../types';
|
|
118
119
|
import { countWebcamFilters } from '../../utils/webcams/count-filters';
|
|
119
120
|
import { normalizeWebcamFacets } from '../../utils/webcams/normalize-facets';
|
|
120
121
|
import { toWebcamFilterPayload } from '../../utils/webcams/to-filter-payload';
|
|
121
|
-
import { WEBCAM_FILTER_ANY } from '../../utils/webcams/facets';
|
|
122
|
+
import { WEBCAM_FILTER_ANY, WEBCAM_LANGUAGE_FACET } from '../../utils/webcams/facets';
|
|
122
123
|
import { sortWebcams } from '../../lib';
|
|
123
124
|
|
|
124
125
|
const props = defineProps<{
|
|
@@ -181,6 +182,18 @@ const filters = computed<IModelFilter[]>(() => (currentFacets.value.length ? cur
|
|
|
181
182
|
group: { ...filter.group, title: translate(`webcams.filter_groups.${filter.group.name}`, filter.group.name) },
|
|
182
183
|
})));
|
|
183
184
|
|
|
185
|
+
/**
|
|
186
|
+
* Выбранный язык на кнопке селекта читается фразой — «Russian speaking», как в макете
|
|
187
|
+
* и как в чипсе облака фильтров. Остальные фильтры стандартные: у них значение и так
|
|
188
|
+
* самодостаточное («Украина», «18-24»). Ключа у сайта может не быть — тогда останется
|
|
189
|
+
* просто название языка.
|
|
190
|
+
*/
|
|
191
|
+
function selectedFilterLabel(filter: IModelFilter, option: IModelFilterOptions) {
|
|
192
|
+
if (filter.name !== WEBCAM_LANGUAGE_FACET || !te('webcams.speaking')) return '';
|
|
193
|
+
|
|
194
|
+
return t('webcams.speaking', { lang: option.title });
|
|
195
|
+
}
|
|
196
|
+
|
|
184
197
|
/** Модалка фильтра слоя ждёт ответ листинга — ей нужны только фасеты. */
|
|
185
198
|
const filterData = computed(() => ({ filters: filters.value }) as unknown as PaginatedResponse<IModelCard>);
|
|
186
199
|
|
|
@@ -3,11 +3,9 @@ import type { LocationQuery } from '#vue-router';
|
|
|
3
3
|
import { getMonth } from '../runtime';
|
|
4
4
|
import { buildChipParts } from '../utils/filter-chips/build-chip-parts';
|
|
5
5
|
import { LANG_TO_FLAG } from '../utils/webcams/lang-to-flag';
|
|
6
|
+
import { WEBCAM_LANGUAGE_FACET } from '../utils/webcams/facets';
|
|
6
7
|
import type { Ref } from 'vue';
|
|
7
8
|
|
|
8
|
-
/** Единственный фильтр-язык у нас в вебкамах: у моделей такого поля нет. */
|
|
9
|
-
const LANGUAGE_FILTER = 'language';
|
|
10
|
-
|
|
11
9
|
export function useFilterChipsItems(
|
|
12
10
|
filters: Ref<IModelFilter[]>,
|
|
13
11
|
route: { query: LocationQuery },
|
|
@@ -95,7 +93,7 @@ export function useFilterChipsItems(
|
|
|
95
93
|
* флага. Если у сайта ключа перевода ещё нет, остаётся название языка.
|
|
96
94
|
*/
|
|
97
95
|
const buildLanguageChip = (valueText: string) => {
|
|
98
|
-
const code = String(route.query[`filter_${
|
|
96
|
+
const code = String(route.query[`filter_${WEBCAM_LANGUAGE_FACET}`] || '').toLowerCase();
|
|
99
97
|
|
|
100
98
|
return {
|
|
101
99
|
title: t('webcams.speaking', { lang: valueText }) || valueText,
|
|
@@ -110,7 +108,7 @@ export function useFilterChipsItems(
|
|
|
110
108
|
const valueText = getValueText(item);
|
|
111
109
|
if (!valueText) return null;
|
|
112
110
|
return {
|
|
113
|
-
...(filter.name ===
|
|
111
|
+
...(filter.name === WEBCAM_LANGUAGE_FACET
|
|
114
112
|
? buildLanguageChip(valueText)
|
|
115
113
|
: buildChipParts(filter.title, valueText)),
|
|
116
114
|
value: item.map(
|
package/package.json
CHANGED
package/utils/webcams/facets.ts
CHANGED
|
@@ -24,6 +24,9 @@ export const WEBCAM_QUICK_FACETS = ['age', 'language', 'country', 'specifics'];
|
|
|
24
24
|
/** Значение селекта «любой» — слой по нему удаляет параметр из query. */
|
|
25
25
|
export const WEBCAM_FILTER_ANY = 'all';
|
|
26
26
|
|
|
27
|
+
/** Имя языкового фасета: у него подпись выбранного значения читается фразой («Russian speaking»). */
|
|
28
|
+
export const WEBCAM_LANGUAGE_FACET = 'language';
|
|
29
|
+
|
|
27
30
|
/** Группы фильтров: строка фильтров и всё остальное. */
|
|
28
31
|
export const WEBCAM_FILTER_GROUPS = {
|
|
29
32
|
quick: { name: 'quick', order: 0 },
|