itube-specs 0.0.994 → 0.0.996
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/scss/themes/dark/_theme.scss +14 -10
- package/assets/scss/themes/light/_theme.scss +15 -10
- package/components/info/grid.vue +2 -2
- package/components/info/main.vue +11 -265
- package/components/info/model.vue +475 -0
- package/components/view/model-videos.vue +1 -3
- package/package.json +1 -1
- package/runtime/utils/cleaners/__tests__/clean-model-info.test.ts +9 -1
- package/runtime/utils/cleaners/clean-model-info.ts +3 -1
- package/types/card-info.d.ts +3 -0
|
@@ -814,19 +814,23 @@ $info-grid-item-warning: $color-red-400;
|
|
|
814
814
|
$info-grid-item-icon: $color-zinc-400;
|
|
815
815
|
$info-grid-plain-text: $color-zinc-400;
|
|
816
816
|
|
|
817
|
-
// ─── Info
|
|
818
|
-
$info-border: $color-zinc-800-50;
|
|
819
|
-
$info-bg: $color-zinc-900-50;
|
|
817
|
+
// ─── Info ───
|
|
820
818
|
$info-img-bg: $color-zinc-800;
|
|
821
819
|
$info-img-border: $color-zinc-700;
|
|
822
820
|
$info-description: $color-zinc-400;
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
$info-
|
|
826
|
-
$info-
|
|
827
|
-
$info-
|
|
828
|
-
$info-
|
|
829
|
-
$info-
|
|
821
|
+
|
|
822
|
+
// ─── Info model ───
|
|
823
|
+
$info-model-border: $color-zinc-800-50;
|
|
824
|
+
$info-model-bg: $color-zinc-900-50;
|
|
825
|
+
$info-model-img-bg: $color-zinc-800;
|
|
826
|
+
$info-model-description: $color-zinc-400;
|
|
827
|
+
$info-model-footer-border: $color-zinc-800-50;
|
|
828
|
+
$info-model-more-text: $color-zinc-500;
|
|
829
|
+
$info-model-more-text--hover: $color-zinc-300;
|
|
830
|
+
$info-model-also: $color-zinc-400;
|
|
831
|
+
$info-model-also-title: $color-zinc-500;
|
|
832
|
+
$info-model-born: $color-zinc-400;
|
|
833
|
+
$info-model-born-icon: $color-zinc-500;
|
|
830
834
|
|
|
831
835
|
// ─── Info socials ───
|
|
832
836
|
$info-socials-bg: $color-zinc-800;
|
|
@@ -15,6 +15,7 @@ $button-variant-overrides: (
|
|
|
15
15
|
filter-reset: muted,
|
|
16
16
|
playlist-button: secondary,
|
|
17
17
|
info-button: secondary,
|
|
18
|
+
info-model-button: secondary,
|
|
18
19
|
interface-button: secondary,
|
|
19
20
|
);
|
|
20
21
|
|
|
@@ -840,19 +841,23 @@ $info-grid-item-warning: $color-red-400;
|
|
|
840
841
|
$info-grid-item-icon: $color-gray-400;
|
|
841
842
|
$info-grid-plain-text: $color-gray-600;
|
|
842
843
|
|
|
843
|
-
// ─── Info
|
|
844
|
-
$info-border: transparent;
|
|
845
|
-
$info-bg: $color-gray-200;
|
|
844
|
+
// ─── Info ───
|
|
846
845
|
$info-img-bg: $color-gray-300;
|
|
847
846
|
$info-img-border: transparent;
|
|
848
847
|
$info-description: $color-gray-600;
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
$info-
|
|
852
|
-
$info-
|
|
853
|
-
$info-
|
|
854
|
-
$info-
|
|
855
|
-
$info-
|
|
848
|
+
|
|
849
|
+
// ─── Info model ───
|
|
850
|
+
$info-model-border: transparent;
|
|
851
|
+
$info-model-bg: $color-gray-200;
|
|
852
|
+
$info-model-img-bg: $color-gray-300;
|
|
853
|
+
$info-model-description: $color-gray-600;
|
|
854
|
+
$info-model-footer-border: $color-gray-300;
|
|
855
|
+
$info-model-more-text: $color-gray-600;
|
|
856
|
+
$info-model-more-text--hover: $color-gray-600;
|
|
857
|
+
$info-model-also: $color-gray-600;
|
|
858
|
+
$info-model-also-title: $color-gray-600;
|
|
859
|
+
$info-model-born: $color-gray-600;
|
|
860
|
+
$info-model-born-icon: $color-gray-600;
|
|
856
861
|
|
|
857
862
|
// ─── Info socials ───
|
|
858
863
|
$info-socials-bg: $color-white;
|
package/components/info/grid.vue
CHANGED
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
>
|
|
44
44
|
<!-- Флаг декоративный: страна названа в `value.title` ниже -->
|
|
45
45
|
<img
|
|
46
|
-
v-if="item.name === 'nationality_iso_codes' && isoCodes"
|
|
46
|
+
v-if="item.name === 'nationality_iso_codes' && isoCodes?.[vIndex]"
|
|
47
47
|
:src="`/icons/flags/${isoCodes[vIndex]}.svg`"
|
|
48
48
|
class="info-grid__item-flag"
|
|
49
49
|
width="20"
|
|
@@ -71,7 +71,7 @@ import type { IGroupedParameter, IGroupedParameterItem } from '../../types';
|
|
|
71
71
|
|
|
72
72
|
defineProps<{
|
|
73
73
|
groups: IGroupedParameter[]
|
|
74
|
-
isoCodes?: string
|
|
74
|
+
isoCodes?: string[]
|
|
75
75
|
}>();
|
|
76
76
|
|
|
77
77
|
const PLAIN_GROUP_ITEM_NAMES = ['turnons'];
|
package/components/info/main.vue
CHANGED
|
@@ -1,11 +1,6 @@
|
|
|
1
|
-
<!--
|
|
1
|
+
<!-- Инфо-карточка канала/категории: аватар, заголовок, описание. Модель — `InfoModel` -->
|
|
2
2
|
<template>
|
|
3
|
-
<div
|
|
4
|
-
:class="{
|
|
5
|
-
'--model': isModel,
|
|
6
|
-
}"
|
|
7
|
-
class="info-main"
|
|
8
|
-
>
|
|
3
|
+
<div class="info-main">
|
|
9
4
|
<div
|
|
10
5
|
:class="{
|
|
11
6
|
'--img': card.img,
|
|
@@ -13,7 +8,7 @@
|
|
|
13
8
|
class="info-main__content"
|
|
14
9
|
>
|
|
15
10
|
<UiImg
|
|
16
|
-
v-if="card.img
|
|
11
|
+
v-if="card.img"
|
|
17
12
|
:src="currentImage || placeholder"
|
|
18
13
|
:avif-src="currentAvifSrc"
|
|
19
14
|
:alt="title"
|
|
@@ -54,89 +49,19 @@
|
|
|
54
49
|
</div>
|
|
55
50
|
</div>
|
|
56
51
|
|
|
57
|
-
<InfoSocials
|
|
58
|
-
v-if="socialItems && socialItems.length > 0"
|
|
59
|
-
:items="socialItems"
|
|
60
|
-
:known-icons="knownIcons"
|
|
61
|
-
class="info-main__social"
|
|
62
|
-
icon-size="16"
|
|
63
|
-
/>
|
|
64
|
-
<p
|
|
65
|
-
v-if="card.akaNames"
|
|
66
|
-
class="info-main__also"
|
|
67
|
-
>
|
|
68
|
-
<span class="info-main__also-title">{{ $t('also_known_as') }} </span>
|
|
69
|
-
<span class="info-main__also-text">{{ card.akaNames }}</span>
|
|
70
|
-
</p>
|
|
71
|
-
<p
|
|
72
|
-
v-if="card.birthSummary"
|
|
73
|
-
class="info-main__born"
|
|
74
|
-
>
|
|
75
|
-
<UiIcon
|
|
76
|
-
class="info-main__born-title-icon"
|
|
77
|
-
name="geo"
|
|
78
|
-
size="14"
|
|
79
|
-
/>
|
|
80
|
-
<span class="info-main__born-text">{{ card.birthSummary }}</span>
|
|
81
|
-
<!-- Национальность нигде больше не подписана — флаг единственный её носитель,
|
|
82
|
-
поэтому в `alt` идёт название страны, а не ISO-код. -->
|
|
83
|
-
<img
|
|
84
|
-
v-for="(item, index) in card.nationalityIsoCodes"
|
|
85
|
-
:key="`nationalityIsoCodes-${index}`"
|
|
86
|
-
:src="`/icons/flags/${item}.svg`"
|
|
87
|
-
:alt="isoCodeToCountryName(item, locale)"
|
|
88
|
-
class="info-main__born-flag"
|
|
89
|
-
width="20"
|
|
90
|
-
height="14"
|
|
91
|
-
>
|
|
92
|
-
</p>
|
|
93
52
|
<p
|
|
94
53
|
v-if="cardDescription"
|
|
95
54
|
class="info-main__description"
|
|
96
55
|
>{{ cardDescription }}</p>
|
|
97
56
|
</div>
|
|
98
57
|
|
|
99
|
-
<div
|
|
100
|
-
v-if="card.parameters && card.parameters.length > 0"
|
|
101
|
-
class="info-main__parameters"
|
|
102
|
-
>
|
|
103
|
-
<div
|
|
104
|
-
v-if="isParameterShow"
|
|
105
|
-
class="info-main__parameters-wrapper"
|
|
106
|
-
>
|
|
107
|
-
<InfoGrid
|
|
108
|
-
:groups="groupedParameters"
|
|
109
|
-
:iso-codes="card.nationalityIsoCodes"
|
|
110
|
-
/>
|
|
111
|
-
</div>
|
|
112
|
-
<div class="info-main__parameters-wrapper">
|
|
113
|
-
<button
|
|
114
|
-
class="info-main__parameters-button"
|
|
115
|
-
type="button"
|
|
116
|
-
@click="showParameters"
|
|
117
|
-
>
|
|
118
|
-
{{ isParameterShow ? t('show_less') : t('show_more') }}
|
|
119
|
-
<UiIcon
|
|
120
|
-
:class="{'--more': isParameterShow}"
|
|
121
|
-
class="info-main__parameters-button-icon"
|
|
122
|
-
name="angle-down"
|
|
123
|
-
size="12"
|
|
124
|
-
/>
|
|
125
|
-
</button>
|
|
126
|
-
</div>
|
|
127
|
-
</div>
|
|
128
|
-
|
|
129
58
|
<ShareMain v-model="isShareOpen">
|
|
130
59
|
<template
|
|
131
60
|
v-if="type"
|
|
132
61
|
#card
|
|
133
62
|
>
|
|
134
|
-
<CardModelMini
|
|
135
|
-
v-if="isModel"
|
|
136
|
-
:card="shareModelCard"
|
|
137
|
-
/>
|
|
138
63
|
<CardChannelMini
|
|
139
|
-
v-
|
|
64
|
+
v-if="type === 'channel'"
|
|
140
65
|
:card="shareChannelCard"
|
|
141
66
|
/>
|
|
142
67
|
<CardCategoryMini
|
|
@@ -152,44 +77,27 @@
|
|
|
152
77
|
import { AuthSteps } from '../../runtime';
|
|
153
78
|
import type {
|
|
154
79
|
ICardInfo,
|
|
155
|
-
IGroupedParameter,
|
|
156
|
-
ILinkItem,
|
|
157
|
-
IModelCard,
|
|
158
80
|
IChannelCard,
|
|
159
81
|
ICategoryCard
|
|
160
82
|
} from '../../types';
|
|
161
83
|
import { CSS_BREAKPOINTS } from '../../config/css-breakpoints';
|
|
162
84
|
import { AuthorizationApiService } from '../../services/api/authorization.service';
|
|
163
|
-
import { groupParametersByGroup } from '../../utils/info-main/group-parameters-by-group';
|
|
164
|
-
import { isoCodeToCountryName } from '../../utils/iso-code-to-country-name';
|
|
165
85
|
|
|
166
|
-
const { t
|
|
86
|
+
const { t } = useI18n();
|
|
167
87
|
|
|
168
88
|
const props = defineProps<{
|
|
169
89
|
card: ICardInfo
|
|
170
90
|
title?: string
|
|
171
|
-
type?: '
|
|
91
|
+
type?: 'channel' | 'category'
|
|
172
92
|
videosCount?: number
|
|
173
93
|
}>();
|
|
174
94
|
|
|
175
|
-
const groupedParameters = computed<IGroupedParameter[]>(() =>
|
|
176
|
-
groupParametersByGroup(props.card.parameters ?? []),
|
|
177
|
-
);
|
|
178
|
-
|
|
179
|
-
const isModel = computed(() => props.type === 'model');
|
|
180
|
-
|
|
181
95
|
const featureFlags = useRuntimeConfig().public.featureFlags;
|
|
182
96
|
|
|
183
97
|
const isShareOpen = ref(false);
|
|
184
98
|
|
|
185
99
|
const { isAuthorized } = useUser(AuthorizationApiService);
|
|
186
100
|
const { openAuthPopup } = useAuthPopup();
|
|
187
|
-
const isParameterShow = ref(false);
|
|
188
|
-
|
|
189
|
-
function showParameters() {
|
|
190
|
-
isParameterShow.value = !isParameterShow.value;
|
|
191
|
-
}
|
|
192
|
-
|
|
193
101
|
const authPopupText = 'register_first';
|
|
194
102
|
|
|
195
103
|
/** Заглушка вместо ручки подписки: состояние живёт только в компоненте, на бэк ничего не уходит */
|
|
@@ -203,53 +111,16 @@ function onFollowClick() {
|
|
|
203
111
|
}
|
|
204
112
|
}
|
|
205
113
|
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
channel: '/img/logo-placeholder.svg',
|
|
209
|
-
};
|
|
210
|
-
/** У канала заглушка — svg, avif-пары у неё нет. */
|
|
211
|
-
const avifPlaceholders: Record<string, string | undefined> = {
|
|
212
|
-
model: '/img/model-placeholder-square.avif',
|
|
213
|
-
};
|
|
214
|
-
const placeholderType = computed(() => props.type || 'channel');
|
|
215
|
-
const placeholder = computed(() => placeholders[ placeholderType.value ]);
|
|
114
|
+
/** Заглушка — svg, avif-пары у неё нет. */
|
|
115
|
+
const placeholder = '/img/logo-placeholder.svg';
|
|
216
116
|
const imageError = ref(false);
|
|
217
117
|
const isFallback = computed(() => imageError.value || !props.card.img);
|
|
218
|
-
const currentImage = computed(() => (isFallback.value ? placeholder
|
|
118
|
+
const currentImage = computed(() => (isFallback.value ? placeholder : props.card.img));
|
|
219
119
|
|
|
220
120
|
/** `.info-main__img` — 80px до sm, 96px дальше. `auto` впереди — см. `UiImg`. */
|
|
221
121
|
const IMG_SIZES = `auto, (max-width: ${CSS_BREAKPOINTS.sm - 1}px) 80px, 96px`;
|
|
222
122
|
|
|
223
|
-
const currentAvifSrc = computed(() => (
|
|
224
|
-
isFallback.value ? avifPlaceholders[ placeholderType.value ] : props.card.imgAvifUrl
|
|
225
|
-
));
|
|
226
|
-
|
|
227
|
-
function getParameter(name: string) {
|
|
228
|
-
return props.card.parameters?.find(p => p.name === name)?.values?.[ 0 ]?.name;
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
/**
|
|
232
|
-
* `IModelCard.country` — пара «код файла флага + читаемое имя», как её отдаёт
|
|
233
|
-
* cleanModelCard. Раньше сюда уезжал целиком массив `nationalityIsoCodes`: у модели с
|
|
234
|
-
* двумя национальностями путь к флагу выходил `/icons/flags/us,gb.svg`, а у модели без
|
|
235
|
-
* национальностей пустой массив проходил `v-if` и давал битую картинку. Берём первый код.
|
|
236
|
-
*/
|
|
237
|
-
const shareCountry = computed(() => {
|
|
238
|
-
const iso = props.card.nationalityIsoCodes?.[0] || '';
|
|
239
|
-
if (!iso) return undefined;
|
|
240
|
-
|
|
241
|
-
return { name: iso, title: isoCodeToCountryName(iso, locale.value) };
|
|
242
|
-
});
|
|
243
|
-
|
|
244
|
-
const shareModelCard = computed<IModelCard>(() => ({
|
|
245
|
-
title: props.card.title,
|
|
246
|
-
videosCount: props.card.videosCount,
|
|
247
|
-
thumbUrl: props.card.img || '',
|
|
248
|
-
guid: props.card.guid || '',
|
|
249
|
-
parameters: [],
|
|
250
|
-
country: shareCountry.value,
|
|
251
|
-
age: getParameter('age'),
|
|
252
|
-
}));
|
|
123
|
+
const currentAvifSrc = computed(() => (isFallback.value ? undefined : props.card.imgAvifUrl));
|
|
253
124
|
|
|
254
125
|
const shareChannelCard = computed<IChannelCard>(() => ({
|
|
255
126
|
guid: props.card.guid || '',
|
|
@@ -270,28 +141,12 @@ const shareCategoryCard = computed<ICategoryCard>(() => ({
|
|
|
270
141
|
videosCount: props.card.videosCount,
|
|
271
142
|
}));
|
|
272
143
|
|
|
273
|
-
const knownIcons = ['facebook', 'instagram', 'tiktok', 'twitter', 'x', 'youtube', 'snapchat'];
|
|
274
|
-
|
|
275
144
|
const cardDescription = computed(() => props.card?.description);
|
|
276
145
|
|
|
277
146
|
const title = computed(() => props.title || props.card?.title);
|
|
278
|
-
|
|
279
|
-
const socialItems = computed<ILinkItem[]>(() => {
|
|
280
|
-
return Object.entries(props.card?.social || {}).map(([key, value]: [string, string]) => ({
|
|
281
|
-
name: value,
|
|
282
|
-
title: key,
|
|
283
|
-
icon: key,
|
|
284
|
-
}));
|
|
285
|
-
});
|
|
286
147
|
</script>
|
|
287
148
|
|
|
288
149
|
<style lang="scss">
|
|
289
|
-
.info-main.--model {
|
|
290
|
-
border: 1px solid $info-border;
|
|
291
|
-
border-radius: $radius-xl;
|
|
292
|
-
background-color: $info-bg;
|
|
293
|
-
}
|
|
294
|
-
|
|
295
150
|
.info-main__content {
|
|
296
151
|
display: grid;
|
|
297
152
|
column-gap: 16px;
|
|
@@ -299,16 +154,6 @@ const socialItems = computed<ILinkItem[]>(() => {
|
|
|
299
154
|
align-items: start;
|
|
300
155
|
}
|
|
301
156
|
|
|
302
|
-
.info-main.--model .info-main__content {
|
|
303
|
-
padding: 16px;
|
|
304
|
-
grid-template-columns: auto 1fr;
|
|
305
|
-
|
|
306
|
-
@include from-br(sm) {
|
|
307
|
-
padding: 20px;
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
}
|
|
311
|
-
|
|
312
157
|
.info-main__content.--img {
|
|
313
158
|
grid-template-columns: auto 1fr;
|
|
314
159
|
}
|
|
@@ -330,15 +175,10 @@ const socialItems = computed<ILinkItem[]>(() => {
|
|
|
330
175
|
|
|
331
176
|
width: var(--size);
|
|
332
177
|
height: var(--size);
|
|
333
|
-
grid-row: 1/
|
|
178
|
+
grid-row: 1/3;
|
|
334
179
|
}
|
|
335
180
|
}
|
|
336
181
|
|
|
337
|
-
.info-main__wrapper {
|
|
338
|
-
display: grid;
|
|
339
|
-
align-content: start;
|
|
340
|
-
}
|
|
341
|
-
|
|
342
182
|
.info-main__header-wrapper {
|
|
343
183
|
@include to-br(sm) {
|
|
344
184
|
display: flex;
|
|
@@ -385,11 +225,6 @@ const socialItems = computed<ILinkItem[]>(() => {
|
|
|
385
225
|
flex-shrink: 0;
|
|
386
226
|
}
|
|
387
227
|
|
|
388
|
-
.info-main__social {
|
|
389
|
-
margin-top: 8px;
|
|
390
|
-
grid-column: 2;
|
|
391
|
-
}
|
|
392
|
-
|
|
393
228
|
.info-main__description {
|
|
394
229
|
@include font-sm;
|
|
395
230
|
|
|
@@ -402,95 +237,6 @@ const socialItems = computed<ILinkItem[]>(() => {
|
|
|
402
237
|
}
|
|
403
238
|
}
|
|
404
239
|
|
|
405
|
-
.--avatar .info-main__description {
|
|
406
|
-
@include from-br(sm) {
|
|
407
|
-
grid-column: 2;
|
|
408
|
-
}
|
|
409
|
-
}
|
|
410
|
-
|
|
411
|
-
.info-main__parameters-wrapper {
|
|
412
|
-
padding: 20px;
|
|
413
|
-
border-top: 1px solid $info-parameters-border;
|
|
414
|
-
|
|
415
|
-
@include from-br(sm) {
|
|
416
|
-
grid-column: 1/-1;
|
|
417
|
-
}
|
|
418
|
-
}
|
|
419
|
-
|
|
420
|
-
.info-main__parameters-button {
|
|
421
|
-
@include font-sm;
|
|
422
|
-
|
|
423
|
-
display: flex;
|
|
424
|
-
margin: 0 auto;
|
|
425
|
-
align-items: center;
|
|
426
|
-
flex-wrap: nowrap;
|
|
427
|
-
column-gap: 4px;
|
|
428
|
-
color: $info-parameters-text;
|
|
429
|
-
|
|
430
|
-
@include hover(color) {
|
|
431
|
-
color: $info-parameters-text--hover;
|
|
432
|
-
}
|
|
433
|
-
}
|
|
434
|
-
|
|
435
|
-
.info-main__parameters-button-icon {
|
|
436
|
-
transition: var(--transition) transform;
|
|
437
|
-
}
|
|
438
|
-
|
|
439
|
-
.info-main__parameters-button-icon.--more {
|
|
440
|
-
transform: rotate(180deg);
|
|
441
|
-
}
|
|
442
|
-
|
|
443
|
-
.info-main__also {
|
|
444
|
-
@include font-sm;
|
|
445
|
-
|
|
446
|
-
color: $info-parameters-also;
|
|
447
|
-
margin-top: 16px;
|
|
448
|
-
|
|
449
|
-
@include to-br(sm) {
|
|
450
|
-
grid-column: 1/-1;
|
|
451
|
-
}
|
|
452
|
-
}
|
|
453
|
-
|
|
454
|
-
.info-main__also-title {
|
|
455
|
-
color: $info-parameters-also-title;
|
|
456
|
-
}
|
|
457
|
-
|
|
458
|
-
.info-main__born {
|
|
459
|
-
@include font-sm;
|
|
460
|
-
|
|
461
|
-
color: $info-parameters-born;
|
|
462
|
-
margin-top: 8px;
|
|
463
|
-
|
|
464
|
-
@include to-br(sm) {
|
|
465
|
-
grid-column: 1/-1;
|
|
466
|
-
}
|
|
467
|
-
}
|
|
468
|
-
|
|
469
|
-
.info-main__born-title {
|
|
470
|
-
display: flex;
|
|
471
|
-
align-items: center;
|
|
472
|
-
flex-wrap: nowrap;
|
|
473
|
-
column-gap: 6px;
|
|
474
|
-
}
|
|
475
|
-
|
|
476
|
-
.info-main__born-title-icon {
|
|
477
|
-
display: inline-block;
|
|
478
|
-
margin-right: 6px;
|
|
479
|
-
color: $info-parameters-born-icon;
|
|
480
|
-
vertical-align: middle;
|
|
481
|
-
}
|
|
482
|
-
|
|
483
|
-
.info-main__born-text {
|
|
484
|
-
display: inline;
|
|
485
|
-
vertical-align: middle;
|
|
486
|
-
}
|
|
487
|
-
|
|
488
|
-
.info-main__born-flag {
|
|
489
|
-
display: inline;
|
|
490
|
-
margin-left: 6px;
|
|
491
|
-
vertical-align: middle;
|
|
492
|
-
}
|
|
493
|
-
|
|
494
240
|
.info-main__header-button {
|
|
495
241
|
@include button-variant(info-button);
|
|
496
242
|
}
|
|
@@ -0,0 +1,475 @@
|
|
|
1
|
+
<!-- Инфо-карточка модели: портрет 3:4, псевдонимы, соцсети, шапка с действиями, рождение, описание, параметры с «Show more» -->
|
|
2
|
+
<template>
|
|
3
|
+
<section class="info-model">
|
|
4
|
+
<div class="info-model__content">
|
|
5
|
+
<div class="info-model__aside">
|
|
6
|
+
<UiImg
|
|
7
|
+
:src="currentImage"
|
|
8
|
+
:avif-src="currentAvifSrc"
|
|
9
|
+
:alt="card.title"
|
|
10
|
+
:sizes="IMG_SIZES"
|
|
11
|
+
class="info-model__img"
|
|
12
|
+
width="240"
|
|
13
|
+
height="320"
|
|
14
|
+
priority
|
|
15
|
+
@error="imageError = true"
|
|
16
|
+
/>
|
|
17
|
+
<p
|
|
18
|
+
v-if="card.akaNames"
|
|
19
|
+
class="info-model__also"
|
|
20
|
+
>
|
|
21
|
+
<span class="info-model__also-title">{{ t('also_known_as') }} </span>
|
|
22
|
+
<span class="info-model__also-text">{{ card.akaNames }}</span>
|
|
23
|
+
</p>
|
|
24
|
+
<InfoSocials
|
|
25
|
+
v-if="socialItems.length > 0"
|
|
26
|
+
:items="socialItems"
|
|
27
|
+
:known-icons="KNOWN_ICONS"
|
|
28
|
+
class="info-model__social"
|
|
29
|
+
icon-size="16"
|
|
30
|
+
/>
|
|
31
|
+
</div>
|
|
32
|
+
|
|
33
|
+
<header class="info-model__header">
|
|
34
|
+
<h1 class="info-model__title">{{ card.title }}</h1>
|
|
35
|
+
<div class="info-model__actions">
|
|
36
|
+
<UiCount
|
|
37
|
+
class="info-model__count"
|
|
38
|
+
icon="video-camera"
|
|
39
|
+
>{{ $n(videosCount || card.videosCount, 'compact') }}</UiCount>
|
|
40
|
+
<UiBtn
|
|
41
|
+
:aria-label="t('share')"
|
|
42
|
+
:title="t('share')"
|
|
43
|
+
theme="muted"
|
|
44
|
+
size="s"
|
|
45
|
+
class="info-model__share"
|
|
46
|
+
@click="isShareOpen = true"
|
|
47
|
+
>
|
|
48
|
+
<UiIcon
|
|
49
|
+
name="share"
|
|
50
|
+
size="16"
|
|
51
|
+
/>
|
|
52
|
+
<span class="info-model__share-text">{{ t('share') }}</span>
|
|
53
|
+
</UiBtn>
|
|
54
|
+
<UiFollowBtn
|
|
55
|
+
v-if="featureFlags.FeatureFollowEnabled"
|
|
56
|
+
:following="isFollowing"
|
|
57
|
+
:name="card.title"
|
|
58
|
+
class="info-model__follow"
|
|
59
|
+
size="s"
|
|
60
|
+
@toggle="onFollowClick"
|
|
61
|
+
/>
|
|
62
|
+
</div>
|
|
63
|
+
</header>
|
|
64
|
+
|
|
65
|
+
<p
|
|
66
|
+
v-if="card.birthSummary"
|
|
67
|
+
class="info-model__born"
|
|
68
|
+
>
|
|
69
|
+
<UiIcon
|
|
70
|
+
class="info-model__born-icon"
|
|
71
|
+
name="geo"
|
|
72
|
+
size="14"
|
|
73
|
+
/>
|
|
74
|
+
<span class="info-model__born-text">{{ card.birthSummary }}</span>
|
|
75
|
+
<!-- Национальность нигде больше не подписана — флаг единственный её носитель,
|
|
76
|
+
поэтому в `alt` идёт название страны, а не ISO-код. -->
|
|
77
|
+
<img
|
|
78
|
+
v-for="(item, index) in card.nationalityIsoCodes"
|
|
79
|
+
:key="`nationalityIsoCodes-${index}`"
|
|
80
|
+
:src="`/icons/flags/${item}.svg`"
|
|
81
|
+
:alt="isoCodeToCountryName(item, locale)"
|
|
82
|
+
class="info-model__born-flag"
|
|
83
|
+
width="20"
|
|
84
|
+
height="14"
|
|
85
|
+
>
|
|
86
|
+
</p>
|
|
87
|
+
|
|
88
|
+
<p
|
|
89
|
+
v-if="card.description"
|
|
90
|
+
:class="{'_truncate': !isExpanded}"
|
|
91
|
+
class="info-model__description"
|
|
92
|
+
>{{ card.description }}</p>
|
|
93
|
+
|
|
94
|
+
<InfoGrid
|
|
95
|
+
v-if="mainGroups.length > 0"
|
|
96
|
+
:groups="mainGroups"
|
|
97
|
+
:iso-codes="card.nationalityIsoCodes"
|
|
98
|
+
class="info-model__parameters"
|
|
99
|
+
/>
|
|
100
|
+
|
|
101
|
+
<InfoGrid
|
|
102
|
+
v-if="extraGroups.length > 0"
|
|
103
|
+
:class="{'--collapsed': !isExpanded}"
|
|
104
|
+
:groups="extraGroups"
|
|
105
|
+
:iso-codes="card.nationalityIsoCodes"
|
|
106
|
+
class="info-model__parameters-extra"
|
|
107
|
+
/>
|
|
108
|
+
</div>
|
|
109
|
+
|
|
110
|
+
<footer
|
|
111
|
+
v-if="hasMore"
|
|
112
|
+
class="info-model__footer"
|
|
113
|
+
>
|
|
114
|
+
<button
|
|
115
|
+
:aria-expanded="isExpanded"
|
|
116
|
+
class="info-model__more"
|
|
117
|
+
type="button"
|
|
118
|
+
@click="isExpanded = !isExpanded"
|
|
119
|
+
>
|
|
120
|
+
{{ isExpanded ? t('show_less') : t('show_more') }}
|
|
121
|
+
<UiIcon
|
|
122
|
+
:class="{'--expanded': isExpanded}"
|
|
123
|
+
class="info-model__more-icon"
|
|
124
|
+
name="angle-down"
|
|
125
|
+
size="12"
|
|
126
|
+
/>
|
|
127
|
+
</button>
|
|
128
|
+
</footer>
|
|
129
|
+
|
|
130
|
+
<ShareMain v-model="isShareOpen">
|
|
131
|
+
<template #card>
|
|
132
|
+
<CardModelMini :card="shareModelCard"/>
|
|
133
|
+
</template>
|
|
134
|
+
</ShareMain>
|
|
135
|
+
</section>
|
|
136
|
+
</template>
|
|
137
|
+
|
|
138
|
+
<script setup lang="ts">
|
|
139
|
+
import { AuthSteps } from '../../runtime';
|
|
140
|
+
import type { ICardInfo, IGroupedParameter, ILinkItem, IModelCard } from '../../types';
|
|
141
|
+
import { CSS_BREAKPOINTS } from '../../config/css-breakpoints';
|
|
142
|
+
import { AuthorizationApiService } from '../../services/api/authorization.service';
|
|
143
|
+
import { groupParametersByGroup } from '../../utils/info-main/group-parameters-by-group';
|
|
144
|
+
import { isoCodeToCountryName } from '../../utils/iso-code-to-country-name';
|
|
145
|
+
|
|
146
|
+
const props = defineProps<{
|
|
147
|
+
card: ICardInfo
|
|
148
|
+
videosCount?: number
|
|
149
|
+
}>();
|
|
150
|
+
|
|
151
|
+
const { t, locale } = useI18n();
|
|
152
|
+
|
|
153
|
+
const featureFlags = useRuntimeConfig().public.featureFlags;
|
|
154
|
+
|
|
155
|
+
const KNOWN_ICONS = ['facebook', 'instagram', 'tiktok', 'twitter', 'x', 'youtube', 'snapchat'];
|
|
156
|
+
|
|
157
|
+
const PLACEHOLDER = '/img/model-placeholder-portrait.webp';
|
|
158
|
+
const PLACEHOLDER_AVIF = '/img/model-placeholder-portrait.avif';
|
|
159
|
+
|
|
160
|
+
/** `.info-model__img` — 160px до sm, 240px дальше. `auto` впереди — см. `UiImg`. */
|
|
161
|
+
const IMG_SIZES = `auto, (max-width: ${CSS_BREAKPOINTS.sm - 1}px) 160px, 240px`;
|
|
162
|
+
|
|
163
|
+
const imageError = ref(false);
|
|
164
|
+
const isFallback = computed(() => imageError.value || !props.card.portraitImg);
|
|
165
|
+
const currentImage = computed(() => (isFallback.value ? PLACEHOLDER : props.card.portraitImg));
|
|
166
|
+
const currentAvifSrc = computed(() => (isFallback.value ? PLACEHOLDER_AVIF : props.card.portraitAvifUrl));
|
|
167
|
+
|
|
168
|
+
const socialItems = computed<ILinkItem[]>(() => (
|
|
169
|
+
Object.entries(props.card.social || {}).map(([key, value]) => ({
|
|
170
|
+
name: value || '',
|
|
171
|
+
title: key,
|
|
172
|
+
icon: key,
|
|
173
|
+
}))
|
|
174
|
+
));
|
|
175
|
+
|
|
176
|
+
const groupedParameters = computed<IGroupedParameter[]>(() =>
|
|
177
|
+
groupParametersByGroup(props.card.parameters ?? []),
|
|
178
|
+
);
|
|
179
|
+
|
|
180
|
+
/** Первая группа видна всегда; вторая свёрнутой видна только с sm, остальные — после «Show more». */
|
|
181
|
+
const mainGroups = computed(() => groupedParameters.value.slice(0, 1));
|
|
182
|
+
|
|
183
|
+
const isExpanded = ref(false);
|
|
184
|
+
|
|
185
|
+
const extraGroups = computed(() => (
|
|
186
|
+
isExpanded.value ? groupedParameters.value.slice(1) : groupedParameters.value.slice(1, 2)
|
|
187
|
+
));
|
|
188
|
+
|
|
189
|
+
const hasMore = computed(() => groupedParameters.value.length > 1 || Boolean(props.card.description));
|
|
190
|
+
|
|
191
|
+
const isShareOpen = ref(false);
|
|
192
|
+
|
|
193
|
+
const { isAuthorized } = useUser(AuthorizationApiService);
|
|
194
|
+
const { openAuthPopup } = useAuthPopup();
|
|
195
|
+
|
|
196
|
+
/** Заглушка вместо ручки подписки: состояние живёт только в компоненте, на бэк ничего не уходит */
|
|
197
|
+
const isFollowing = ref(false);
|
|
198
|
+
|
|
199
|
+
function onFollowClick() {
|
|
200
|
+
if (isAuthorized.value) {
|
|
201
|
+
isFollowing.value = !isFollowing.value;
|
|
202
|
+
} else {
|
|
203
|
+
openAuthPopup(AuthSteps.Login, 'register_first');
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function getParameter(name: string) {
|
|
208
|
+
return props.card.parameters?.find(p => p.name === name)?.values?.[ 0 ]?.name;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* `IModelCard.country` — пара «код файла флага + читаемое имя», как её отдаёт
|
|
213
|
+
* cleanModelCard. Берём первый код: у модели с двумя национальностями целый массив дал бы
|
|
214
|
+
* путь `/icons/flags/us,gb.svg`, а пустой массив — битую картинку.
|
|
215
|
+
*/
|
|
216
|
+
const shareCountry = computed(() => {
|
|
217
|
+
const iso = props.card.nationalityIsoCodes?.[ 0 ] || '';
|
|
218
|
+
if (!iso) return undefined;
|
|
219
|
+
|
|
220
|
+
return { name: iso, title: isoCodeToCountryName(iso, locale.value) };
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
const shareModelCard = computed<IModelCard>(() => ({
|
|
224
|
+
title: props.card.title,
|
|
225
|
+
videosCount: props.card.videosCount,
|
|
226
|
+
thumbUrl: props.card.img || '',
|
|
227
|
+
guid: props.card.guid || '',
|
|
228
|
+
parameters: [],
|
|
229
|
+
country: shareCountry.value,
|
|
230
|
+
age: getParameter('age'),
|
|
231
|
+
}));
|
|
232
|
+
</script>
|
|
233
|
+
|
|
234
|
+
<style lang="scss">
|
|
235
|
+
.info-model {
|
|
236
|
+
border: 1px solid $info-model-border;
|
|
237
|
+
border-radius: $radius-xl;
|
|
238
|
+
background-color: $info-model-bg;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
.info-model__content {
|
|
242
|
+
display: grid;
|
|
243
|
+
grid-template-columns: 160px minmax(0, 1fr);
|
|
244
|
+
grid-template-rows: auto 1fr;
|
|
245
|
+
grid-template-areas:
|
|
246
|
+
'aside header'
|
|
247
|
+
'aside parameters'
|
|
248
|
+
'born born'
|
|
249
|
+
'description description'
|
|
250
|
+
'extra extra';
|
|
251
|
+
column-gap: 16px;
|
|
252
|
+
padding: 16px;
|
|
253
|
+
|
|
254
|
+
@include from-br(sm) {
|
|
255
|
+
grid-template-columns: 240px minmax(0, 1fr);
|
|
256
|
+
grid-template-rows: auto auto auto auto 1fr;
|
|
257
|
+
grid-template-areas:
|
|
258
|
+
'aside header'
|
|
259
|
+
'aside born'
|
|
260
|
+
'aside description'
|
|
261
|
+
'aside parameters'
|
|
262
|
+
'aside extra';
|
|
263
|
+
padding: 20px;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
.info-model__aside {
|
|
268
|
+
grid-area: aside;
|
|
269
|
+
align-self: start;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
.info-model__img {
|
|
273
|
+
display: block;
|
|
274
|
+
width: 100%;
|
|
275
|
+
height: auto;
|
|
276
|
+
aspect-ratio: 3/4;
|
|
277
|
+
border-radius: $radius-lg;
|
|
278
|
+
object-fit: cover;
|
|
279
|
+
background-color: $info-model-img-bg;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
.info-model__also {
|
|
283
|
+
@include font-xs;
|
|
284
|
+
|
|
285
|
+
margin-top: 8px;
|
|
286
|
+
color: $info-model-also;
|
|
287
|
+
line-height: 1.625;
|
|
288
|
+
|
|
289
|
+
@include from-br(sm) {
|
|
290
|
+
@include font-sm;
|
|
291
|
+
|
|
292
|
+
line-height: 1.625;
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
.info-model__also-title {
|
|
297
|
+
color: $info-model-also-title;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
.info-model__social {
|
|
301
|
+
margin-top: 8px;
|
|
302
|
+
|
|
303
|
+
@include from-br(sm) {
|
|
304
|
+
margin-top: 12px;
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
.info-model__header {
|
|
309
|
+
display: grid;
|
|
310
|
+
grid-area: header;
|
|
311
|
+
row-gap: 8px;
|
|
312
|
+
margin-bottom: 8px;
|
|
313
|
+
|
|
314
|
+
@include from-br(sm) {
|
|
315
|
+
display: flex;
|
|
316
|
+
flex-wrap: wrap;
|
|
317
|
+
align-items: center;
|
|
318
|
+
column-gap: 12px;
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
.info-model__title {
|
|
323
|
+
@include font-base;
|
|
324
|
+
|
|
325
|
+
min-width: 0;
|
|
326
|
+
font-weight: 700;
|
|
327
|
+
line-height: 1.25;
|
|
328
|
+
text-transform: uppercase;
|
|
329
|
+
overflow-wrap: break-word;
|
|
330
|
+
|
|
331
|
+
@include from-br(sm) {
|
|
332
|
+
@include font-lg;
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
.info-model__actions {
|
|
337
|
+
display: flex;
|
|
338
|
+
align-items: center;
|
|
339
|
+
gap: 8px;
|
|
340
|
+
|
|
341
|
+
@include from-br(sm) {
|
|
342
|
+
display: contents;
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
.info-model__count {
|
|
347
|
+
padding: 7px 12px;
|
|
348
|
+
flex-shrink: 0;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
.info-model__share {
|
|
352
|
+
@include button-variant(info-model-button);
|
|
353
|
+
|
|
354
|
+
@include to-br(sm) {
|
|
355
|
+
width: 36px;
|
|
356
|
+
padding: 0;
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
.info-model__share-text {
|
|
361
|
+
@include to-br(sm) {
|
|
362
|
+
display: none;
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
.info-model__follow {
|
|
367
|
+
margin-inline-start: auto;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
.info-model__born {
|
|
371
|
+
@include font-sm;
|
|
372
|
+
|
|
373
|
+
grid-area: born;
|
|
374
|
+
margin-top: 20px;
|
|
375
|
+
color: $info-model-born;
|
|
376
|
+
|
|
377
|
+
@include from-br(sm) {
|
|
378
|
+
margin-top: 0;
|
|
379
|
+
margin-bottom: 12px;
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
.info-model__born-icon {
|
|
384
|
+
display: inline-block;
|
|
385
|
+
margin-right: 6px;
|
|
386
|
+
color: $info-model-born-icon;
|
|
387
|
+
vertical-align: middle;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
.info-model__born-text {
|
|
391
|
+
vertical-align: middle;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
.info-model__born-flag {
|
|
395
|
+
display: inline;
|
|
396
|
+
margin-left: 6px;
|
|
397
|
+
vertical-align: middle;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
.info-model__description {
|
|
401
|
+
@include font-sm;
|
|
402
|
+
|
|
403
|
+
--line-clamp: 2;
|
|
404
|
+
|
|
405
|
+
grid-area: description;
|
|
406
|
+
margin-top: 16px;
|
|
407
|
+
line-height: 1.625;
|
|
408
|
+
color: $info-model-description;
|
|
409
|
+
|
|
410
|
+
@include from-br(sm) {
|
|
411
|
+
margin-top: 0;
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
.info-model__parameters {
|
|
416
|
+
grid-area: parameters;
|
|
417
|
+
align-self: start;
|
|
418
|
+
margin-top: 16px;
|
|
419
|
+
|
|
420
|
+
@include from-br(sm) {
|
|
421
|
+
margin-top: 20px;
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
.info-model__parameters .info-grid__item-title {
|
|
426
|
+
@include to-br(sm) {
|
|
427
|
+
display: none;
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
.info-model__parameters .info-grid__item:not(.--link) .info-grid__item-value {
|
|
432
|
+
@include to-br(sm) {
|
|
433
|
+
border-left: none;
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
.info-model__parameters-extra {
|
|
438
|
+
grid-area: extra;
|
|
439
|
+
margin-top: 20px;
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
.info-model__parameters-extra.--collapsed {
|
|
443
|
+
@include to-br(sm) {
|
|
444
|
+
display: none;
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
.info-model__footer {
|
|
449
|
+
display: flex;
|
|
450
|
+
justify-content: center;
|
|
451
|
+
padding: 12px 20px;
|
|
452
|
+
border-top: 1px solid $info-model-footer-border;
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
.info-model__more {
|
|
456
|
+
@include font-sm;
|
|
457
|
+
|
|
458
|
+
display: flex;
|
|
459
|
+
align-items: center;
|
|
460
|
+
column-gap: 6px;
|
|
461
|
+
color: $info-model-more-text;
|
|
462
|
+
|
|
463
|
+
@include hover(color) {
|
|
464
|
+
color: $info-model-more-text--hover;
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
.info-model__more-icon {
|
|
469
|
+
transition: var(--transition) transform;
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
.info-model__more-icon.--expanded {
|
|
473
|
+
transform: rotate(180deg);
|
|
474
|
+
}
|
|
475
|
+
</style>
|
|
@@ -1,14 +1,12 @@
|
|
|
1
1
|
<!-- Страница видео модели: инфо, теги, грид видео, пагинация, похожие -->
|
|
2
2
|
<template>
|
|
3
3
|
<div class="view-model-videos _page _container">
|
|
4
|
-
<
|
|
4
|
+
<InfoModel
|
|
5
5
|
v-if="cardInfo && isFirstPage && !isFilteredPage"
|
|
6
6
|
:key="`info-${cardInfo.title}`"
|
|
7
7
|
:card="cardInfo"
|
|
8
|
-
:title="title"
|
|
9
8
|
:videos-count="infoVideosCount"
|
|
10
9
|
class="view-model-videos__info"
|
|
11
|
-
type="model"
|
|
12
10
|
/>
|
|
13
11
|
|
|
14
12
|
<UiSectionTitle
|
package/package.json
CHANGED
|
@@ -9,7 +9,12 @@ describe('cleanModelInfo', () => {
|
|
|
9
9
|
social: '@model',
|
|
10
10
|
videos_count: 50,
|
|
11
11
|
video_thumb_urls: { webp: { '320x180': 'https://img.com/s.webp' } },
|
|
12
|
-
portraits: {
|
|
12
|
+
portraits: {
|
|
13
|
+
dark: {
|
|
14
|
+
square: { width: 256, height: 256, webp: 'https://cdn/sq.webp', avif: 'https://cdn/sq.avif' },
|
|
15
|
+
tall: { width: 480, height: 640, webp: 'https://cdn/tall.webp', avif: 'https://cdn/tall.avif' },
|
|
16
|
+
},
|
|
17
|
+
},
|
|
13
18
|
parameters: 'height: 170',
|
|
14
19
|
long_description: 'long desc',
|
|
15
20
|
bio: 'bio text',
|
|
@@ -31,6 +36,8 @@ describe('cleanModelInfo', () => {
|
|
|
31
36
|
expect(result.videosCount).toBe(50);
|
|
32
37
|
expect(result.img).toBe('https://cdn/sq.webp');
|
|
33
38
|
expect(result.imgAvifUrl).toBe('https://cdn/sq.avif');
|
|
39
|
+
expect(result.portraitImg).toBe('https://cdn/tall.webp');
|
|
40
|
+
expect(result.portraitAvifUrl).toBe('https://cdn/tall.avif');
|
|
34
41
|
expect(result.parameters).toBe('height: 170');
|
|
35
42
|
expect(result.fullDescription).toBe('long desc');
|
|
36
43
|
expect(result.relatedNames).toEqual(['Model B']);
|
|
@@ -52,6 +59,7 @@ describe('cleanModelInfo', () => {
|
|
|
52
59
|
expect(result.social).toBeUndefined();
|
|
53
60
|
expect(result.videosCount).toBe(0);
|
|
54
61
|
expect(result.img).toBe('');
|
|
62
|
+
expect(result.portraitImg).toBe('');
|
|
55
63
|
expect(result.parameters).toEqual([]);
|
|
56
64
|
expect(result.fullDescription).toBe('');
|
|
57
65
|
expect(result.relatedNames).toEqual([]);
|
|
@@ -3,7 +3,7 @@ import { pickPortraitVariant } from '../pick-portrait-variant';
|
|
|
3
3
|
|
|
4
4
|
/** Нормализует сырые данные страницы модели к типу ICardInfo. */
|
|
5
5
|
export const cleanModelInfo = (card: IRawModelInfo, theme?: string): ICardInfo => {
|
|
6
|
-
//
|
|
6
|
+
// `tall` — в шапку страницы, `square` — в мини-карточку шаринга и круглые аватары.
|
|
7
7
|
const portrait = pickPortraitVariant(card.portraits, theme);
|
|
8
8
|
|
|
9
9
|
return {
|
|
@@ -13,6 +13,8 @@ export const cleanModelInfo = (card: IRawModelInfo, theme?: string): ICardInfo =
|
|
|
13
13
|
videosCount: card.videos_count || 0,
|
|
14
14
|
img: portrait?.square?.webp || '',
|
|
15
15
|
imgAvifUrl: portrait?.square?.avif,
|
|
16
|
+
portraitImg: portrait?.tall?.webp || '',
|
|
17
|
+
portraitAvifUrl: portrait?.tall?.avif,
|
|
16
18
|
parameters: card.parameters || [],
|
|
17
19
|
fullDescription: card.long_description || '',
|
|
18
20
|
relatedNames: card.relatedNames || [],
|
package/types/card-info.d.ts
CHANGED
|
@@ -24,6 +24,9 @@ export interface ICardInfo {
|
|
|
24
24
|
social?: {
|
|
25
25
|
[key: string]: string | undefined;
|
|
26
26
|
};
|
|
27
|
+
/** Портретный кроп 3:4 для шапки страницы модели; `img` остаётся квадратным. */
|
|
28
|
+
portraitImg?: string;
|
|
29
|
+
portraitAvifUrl?: string;
|
|
27
30
|
parameters?: IParameterModel[]
|
|
28
31
|
relatedNames?: string[]
|
|
29
32
|
akaNames: string;
|