itube-specs 0.0.898 → 0.0.902
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-filled.svg +1 -0
- package/assets/scss/mixins/_breakpoints.scss +3 -2
- package/assets/scss/themes/dark/_theme.scss +17 -26
- package/assets/scss/themes/light/_theme.scss +17 -26
- package/assets/scss/vars/_palette.scss +1 -0
- package/components/card/category.vue +4 -8
- package/components/card/dropdown/index.vue +47 -9
- package/components/card/dropdown/trigger.vue +1 -0
- package/components/card/model.vue +1 -1
- package/components/card/playlist.vue +15 -6
- package/components/card/round.vue +3 -3
- package/components/card/video/actions.vue +123 -0
- package/components/card/video/index.vue +149 -30
- package/components/filter/selects.vue +1 -1
- package/components/info/grid.vue +1 -1
- package/components/layout/header-user.vue +1 -1
- package/components/library/feed.vue +65 -24
- package/components/library/follows.vue +8 -44
- package/components/library/item.vue +4 -4
- package/components/player/like.vue +1 -1
- package/components/player/playlist.vue +2 -2
- package/components/report/main.vue +20 -5
- package/components/section/swiper-cards.vue +3 -0
- package/components/ui/checkbox.vue +1 -1
- package/components/ui/dropdown.vue +1 -1
- package/components/ui/input.vue +2 -2
- package/components/ui/radio.vue +1 -1
- package/components/ui/section-title.vue +21 -6
- package/components/ui/toggle.vue +1 -1
- package/composables/use-report-popup.ts +26 -3
- package/composables/use-video-collections.ts +30 -0
- package/lib/scheme/profile-navigation-items.ts +1 -28
- package/package.json +1 -1
- package/pages/following.vue +4 -0
- package/pages/history.vue +12 -36
- package/pages/library/categories.vue +2 -1
- package/pages/library/channels.vue +1 -1
- package/pages/library/index.vue +21 -52
- package/pages/library/models.vue +4 -4
- package/stubs/library-follows.ts +19 -16
- package/types/library-feed.d.ts +6 -0
- package/types/video-card.d.ts +4 -0
- package/components/card/history.vue +0 -359
|
@@ -6,6 +6,8 @@
|
|
|
6
6
|
{'--compact': compact},
|
|
7
7
|
{'--avatar': card.channelAvatar},
|
|
8
8
|
{'--no-channel': isChannelPage},
|
|
9
|
+
{'--progress': hasProgress},
|
|
10
|
+
{'--favorite': isFavoriteCard},
|
|
9
11
|
]"
|
|
10
12
|
class="card-video"
|
|
11
13
|
>
|
|
@@ -38,20 +40,31 @@
|
|
|
38
40
|
class="card-video__new"
|
|
39
41
|
theme="primary-gradient"
|
|
40
42
|
/>
|
|
41
|
-
<UiLabel
|
|
42
|
-
v-if="card.quality && parseInt(card.quality) >= 1080"
|
|
43
|
-
:text="card.quality"
|
|
44
|
-
class="card-video__resolution"
|
|
45
|
-
theme="yellow"
|
|
46
|
-
/>
|
|
47
43
|
<UiLabel
|
|
48
44
|
v-if="card.duration"
|
|
49
45
|
:text="duration"
|
|
50
46
|
class="card-video__duration"
|
|
51
47
|
theme="black"
|
|
52
48
|
/>
|
|
49
|
+
<span
|
|
50
|
+
v-if="hasProgress"
|
|
51
|
+
:style="{ '--card-video-progress': `${progress}%` }"
|
|
52
|
+
:aria-label="t('watched_percent', { percent: progress })"
|
|
53
|
+
:aria-valuenow="progress"
|
|
54
|
+
class="card-video__progress"
|
|
55
|
+
role="progressbar"
|
|
56
|
+
aria-valuemin="0"
|
|
57
|
+
aria-valuemax="100"
|
|
58
|
+
/>
|
|
53
59
|
</NuxtLink>
|
|
54
60
|
|
|
61
|
+
<CardVideoActions
|
|
62
|
+
v-if="showActions"
|
|
63
|
+
:card="card"
|
|
64
|
+
:show-favorites="canFavorite"
|
|
65
|
+
:show-watch-later="canWatchLater"
|
|
66
|
+
/>
|
|
67
|
+
|
|
55
68
|
<div class="card-video__wrapper">
|
|
56
69
|
<component
|
|
57
70
|
v-if="!mini"
|
|
@@ -82,7 +95,7 @@
|
|
|
82
95
|
>{{ card.title }}
|
|
83
96
|
</NuxtLink>
|
|
84
97
|
<component
|
|
85
|
-
v-if="!mini && !compact && card.channelName"
|
|
98
|
+
v-if="!mini && !compact && !history && card.channelName"
|
|
86
99
|
:is="component"
|
|
87
100
|
:to="channelLink"
|
|
88
101
|
class="card-video__channel-wrapper"
|
|
@@ -96,8 +109,28 @@
|
|
|
96
109
|
</template>
|
|
97
110
|
</span>
|
|
98
111
|
</component>
|
|
112
|
+
<p
|
|
113
|
+
v-if="history"
|
|
114
|
+
class="card-video__meta"
|
|
115
|
+
>
|
|
116
|
+
<NuxtLink
|
|
117
|
+
v-if="card.channelName"
|
|
118
|
+
:to="channelLink"
|
|
119
|
+
class="card-video__channel-title"
|
|
120
|
+
>{{ card.channelName }}</NuxtLink>
|
|
121
|
+
<span
|
|
122
|
+
:class="{'--remaining': remaining}"
|
|
123
|
+
class="card-video__watched"
|
|
124
|
+
>
|
|
125
|
+
<UiIcon
|
|
126
|
+
name="clock"
|
|
127
|
+
size="12"
|
|
128
|
+
/>
|
|
129
|
+
{{ remaining ? remainingText : watchedAgo }}
|
|
130
|
+
</span>
|
|
131
|
+
</p>
|
|
99
132
|
<div
|
|
100
|
-
v-if="!mini && !compact && cardTags && cardTags.length > 0"
|
|
133
|
+
v-if="!mini && !compact && !history && cardTags && cardTags.length > 0"
|
|
101
134
|
class="card-video__tags"
|
|
102
135
|
>
|
|
103
136
|
<div class="card-video__tags-wrapper">
|
|
@@ -113,9 +146,14 @@
|
|
|
113
146
|
</div>
|
|
114
147
|
<CardDropdownTrigger
|
|
115
148
|
:card="card"
|
|
116
|
-
:show-watch-later="
|
|
149
|
+
:show-watch-later="canWatchLater"
|
|
150
|
+
:show-favorites="canFavorite"
|
|
151
|
+
:show-report="!history"
|
|
152
|
+
:show-remove="history"
|
|
153
|
+
:show-share="history"
|
|
117
154
|
class="card-video__dropdown"
|
|
118
|
-
|
|
155
|
+
@share="emit('share', card)"
|
|
156
|
+
@remove="emit('remove', card)"
|
|
119
157
|
/>
|
|
120
158
|
</div>
|
|
121
159
|
</div>
|
|
@@ -138,9 +176,18 @@ const props = defineProps<{
|
|
|
138
176
|
playlistId?: string
|
|
139
177
|
priority?: boolean
|
|
140
178
|
playlist?: boolean
|
|
179
|
+
/** Тип «история»: мета-строка со временем просмотра вместо канала и тегов, меню с удалением и шарингом */
|
|
180
|
+
history?: boolean
|
|
181
|
+
/** Развилка мета-строки истории: вместо «когда смотрели» — сколько осталось до конца («Продолжить просмотр») */
|
|
182
|
+
remaining?: boolean
|
|
141
183
|
}>();
|
|
142
184
|
|
|
143
|
-
const
|
|
185
|
+
const emit = defineEmits<{
|
|
186
|
+
(e: 'remove', card: IVideoCard): void
|
|
187
|
+
(e: 'share', card: IVideoCard): void
|
|
188
|
+
}>();
|
|
189
|
+
|
|
190
|
+
const { t, locale } = useI18n();
|
|
144
191
|
|
|
145
192
|
const NuxtLinkComponent = resolveComponent('NuxtLink');
|
|
146
193
|
|
|
@@ -184,6 +231,27 @@ const duration = computed(() => {
|
|
|
184
231
|
return getDuration(props.card.duration);
|
|
185
232
|
});
|
|
186
233
|
|
|
234
|
+
const { isFavorite } = useVideoCollections();
|
|
235
|
+
|
|
236
|
+
const isFavoriteCard = computed(() => isFavorite(props.card.guid));
|
|
237
|
+
|
|
238
|
+
const canFavorite = computed(() => featureFlags.FeatureFavoritesEnabled);
|
|
239
|
+
const canWatchLater = computed(() => !props.history && featureFlags.FeatureWatchLaterEnabled);
|
|
240
|
+
const showActions = computed(() => !props.mini && (canFavorite.value || canWatchLater.value));
|
|
241
|
+
|
|
242
|
+
const hasProgress = computed(() => props.card.progress !== undefined);
|
|
243
|
+
|
|
244
|
+
const progress = computed(() => Math.min(100, Math.max(0, Math.round(props.card.progress ?? 0))));
|
|
245
|
+
|
|
246
|
+
const watchedAgo = computed(() => props.card.watchedAt === undefined
|
|
247
|
+
? ''
|
|
248
|
+
: useFormatTimeAgo(props.card.watchedAt, locale.value, true));
|
|
249
|
+
|
|
250
|
+
const remainingText = computed(() => {
|
|
251
|
+
const left = Math.ceil((props.card.duration * (100 - progress.value)) / 100 / 60);
|
|
252
|
+
return t('minutes_left', { count: Math.max(left, 1) });
|
|
253
|
+
});
|
|
254
|
+
|
|
187
255
|
const rootPath = computed(() => props.playlistId ? `playlists/${props.playlistId}` : 'videos');
|
|
188
256
|
|
|
189
257
|
const link = computed(() => generateLink(`/${rootPath.value}/${props.card[ featureFlags.FetchByUrlEnabled ? 'url' : 'id' ]}`));
|
|
@@ -219,6 +287,7 @@ const cardTags = computed(() => {
|
|
|
219
287
|
<style lang="scss">
|
|
220
288
|
$inset-label: 8px;
|
|
221
289
|
$layout-breakpoint: 250px;
|
|
290
|
+
$progress-height: 4px;
|
|
222
291
|
|
|
223
292
|
// Колонка под меню карточки фиксирована, а не auto: иначе ширина зависит от того, отрисовано меню
|
|
224
293
|
// или нет, и в момент подмены кнопки заголовок дёргается
|
|
@@ -227,6 +296,7 @@ $dropdown-column: 18px;
|
|
|
227
296
|
.card-video {
|
|
228
297
|
@include video-card;
|
|
229
298
|
|
|
299
|
+
position: relative;
|
|
230
300
|
container-type: inline-size;
|
|
231
301
|
|
|
232
302
|
@include to-br(xs) {
|
|
@@ -246,7 +316,6 @@ $dropdown-column: 18px;
|
|
|
246
316
|
}
|
|
247
317
|
|
|
248
318
|
.card-video__duration,
|
|
249
|
-
.card-video__resolution,
|
|
250
319
|
.card-video__new {
|
|
251
320
|
position: absolute;
|
|
252
321
|
}
|
|
@@ -256,6 +325,11 @@ $dropdown-column: 18px;
|
|
|
256
325
|
left: $inset-label;
|
|
257
326
|
}
|
|
258
327
|
|
|
328
|
+
/* Кнопка избранного занимает левый верхний угол постера — плашка «новое» встаёт правее неё */
|
|
329
|
+
.--favorite .card-video__new {
|
|
330
|
+
left: $inset-label * 2 + 36px;
|
|
331
|
+
}
|
|
332
|
+
|
|
259
333
|
.card-video__dropdown {
|
|
260
334
|
grid-column: 2;
|
|
261
335
|
grid-row: 1;
|
|
@@ -273,29 +347,45 @@ $dropdown-column: 18px;
|
|
|
273
347
|
}
|
|
274
348
|
|
|
275
349
|
.card-dropdown__trigger > .ui-icon {
|
|
276
|
-
@
|
|
350
|
+
@media (hover: hover) {
|
|
277
351
|
transition: var(--transition) transform;
|
|
278
352
|
}
|
|
279
353
|
}
|
|
280
354
|
|
|
281
355
|
.card-dropdown__trigger:is(:hover, :focus-visible) > .ui-icon {
|
|
282
|
-
@
|
|
356
|
+
@media (hover: hover) {
|
|
283
357
|
transform: scale($video-card-dropdown-size--hover);
|
|
284
358
|
}
|
|
285
359
|
}
|
|
286
360
|
|
|
287
|
-
.card-video__resolution {
|
|
288
|
-
top: $inset-label;
|
|
289
|
-
right: $inset-label;
|
|
290
|
-
background: $video-card-resolution-bg;
|
|
291
|
-
color: $video-card-resolution-text;
|
|
292
|
-
}
|
|
293
|
-
|
|
294
361
|
.card-video__duration {
|
|
295
362
|
bottom: $inset-label;
|
|
296
363
|
left: $inset-label;
|
|
297
364
|
}
|
|
298
365
|
|
|
366
|
+
.--progress .card-video__duration {
|
|
367
|
+
bottom: $inset-label + $progress-height;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
/* Превью кроет постер целиком ($z-index-plus) — полоса прогресса должна остаться поверх него */
|
|
371
|
+
.card-video__progress {
|
|
372
|
+
position: absolute;
|
|
373
|
+
right: 0;
|
|
374
|
+
bottom: 0;
|
|
375
|
+
left: 0;
|
|
376
|
+
z-index: $z-index-loader;
|
|
377
|
+
height: $progress-height;
|
|
378
|
+
background-color: $video-card-progress-bg;
|
|
379
|
+
|
|
380
|
+
&::after {
|
|
381
|
+
content: '';
|
|
382
|
+
display: block;
|
|
383
|
+
width: var(--card-video-progress, 0%);
|
|
384
|
+
height: 100%;
|
|
385
|
+
background-color: $video-card-progress-fill;
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
|
|
299
389
|
.card-video__wrapper {
|
|
300
390
|
display: grid;
|
|
301
391
|
grid-template-columns: 1fr $dropdown-column;
|
|
@@ -322,7 +412,8 @@ $dropdown-column: 18px;
|
|
|
322
412
|
grid-column: 2;
|
|
323
413
|
}
|
|
324
414
|
|
|
325
|
-
.--avatar .card-video__channel-wrapper
|
|
415
|
+
.--avatar .card-video__channel-wrapper,
|
|
416
|
+
.--avatar .card-video__meta {
|
|
326
417
|
grid-column: 2;
|
|
327
418
|
}
|
|
328
419
|
|
|
@@ -386,13 +477,13 @@ $dropdown-column: 18px;
|
|
|
386
477
|
grid-column: 1;
|
|
387
478
|
}
|
|
388
479
|
|
|
389
|
-
@
|
|
480
|
+
@media (hover: hover) {
|
|
390
481
|
transition: var(--transition) color;
|
|
391
482
|
}
|
|
392
483
|
}
|
|
393
484
|
|
|
394
485
|
.card-video:is(:hover, :focus-visible) .card-video__title {
|
|
395
|
-
@
|
|
486
|
+
@media (hover: hover) {
|
|
396
487
|
color: $video-card-text--hover;
|
|
397
488
|
}
|
|
398
489
|
}
|
|
@@ -413,6 +504,38 @@ $dropdown-column: 18px;
|
|
|
413
504
|
}
|
|
414
505
|
}
|
|
415
506
|
|
|
507
|
+
/* Мета-строка истории: «канал · когда смотрели / сколько осталось» на месте строки канала */
|
|
508
|
+
.card-video__meta {
|
|
509
|
+
@include font-xs;
|
|
510
|
+
|
|
511
|
+
display: flex;
|
|
512
|
+
align-items: center;
|
|
513
|
+
grid-row: 2;
|
|
514
|
+
margin: 6px 0 0;
|
|
515
|
+
font-weight: 400;
|
|
516
|
+
color: $video-card-secondary;
|
|
517
|
+
|
|
518
|
+
@include to-cq($layout-breakpoint) {
|
|
519
|
+
grid-column: 1;
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
.card-video__meta .card-video__channel-title + .card-video__watched::before {
|
|
524
|
+
content: '·';
|
|
525
|
+
margin-inline: 6px;
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
.card-video__watched {
|
|
529
|
+
display: flex;
|
|
530
|
+
align-items: center;
|
|
531
|
+
column-gap: 4px;
|
|
532
|
+
flex-shrink: 0;
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
.card-video__watched.--remaining {
|
|
536
|
+
color: $video-card-remaining;
|
|
537
|
+
}
|
|
538
|
+
|
|
416
539
|
.card-video__channel-logo {
|
|
417
540
|
border-radius: 50%;
|
|
418
541
|
}
|
|
@@ -425,12 +548,8 @@ $dropdown-column: 18px;
|
|
|
425
548
|
.card-video__logo-wrapper[href],
|
|
426
549
|
.card-video__channel-wrapper[href],
|
|
427
550
|
.card-video__title {
|
|
428
|
-
@include
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
&:is(:hover, :focus-visible) {
|
|
432
|
-
opacity: 0.9;
|
|
433
|
-
}
|
|
551
|
+
@include hover(opacity) {
|
|
552
|
+
opacity: 0.9;
|
|
434
553
|
}
|
|
435
554
|
}
|
|
436
555
|
|
|
@@ -347,7 +347,7 @@ function getSelectLabel(item: IModelFilter) {
|
|
|
347
347
|
}
|
|
348
348
|
|
|
349
349
|
.filter-selects__wrapper:is(:hover, :focus-visible) .filter-selects__select-button:not(.--active-select) {
|
|
350
|
-
@
|
|
350
|
+
@media (hover: hover) {
|
|
351
351
|
background: $filters-main-bg--hover;
|
|
352
352
|
}
|
|
353
353
|
}
|
package/components/info/grid.vue
CHANGED
|
@@ -168,7 +168,7 @@ function isWarning(value: string) {
|
|
|
168
168
|
}
|
|
169
169
|
|
|
170
170
|
.info-grid__item.--link:is(:hover, :focus-visible) .info-grid__item-value {
|
|
171
|
-
@
|
|
171
|
+
@media (hover: hover) {
|
|
172
172
|
background-color: $info-grid-item-value-link-bg--hover;
|
|
173
173
|
}
|
|
174
174
|
}
|
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
<!-- Блок подписки (модель / канал / категория): шапка с аватаром, тегами и счётчиками + лента роликов -->
|
|
2
2
|
<template>
|
|
3
3
|
<article class="library-feed">
|
|
4
|
-
<header
|
|
4
|
+
<header
|
|
5
|
+
:class="{ '--without-avatar': withoutAvatar }"
|
|
6
|
+
class="library-feed__header"
|
|
7
|
+
>
|
|
5
8
|
<NuxtLink
|
|
9
|
+
v-if="!withoutAvatar"
|
|
6
10
|
:to="item.to"
|
|
7
11
|
:title="item.title"
|
|
8
12
|
class="library-feed__avatar-link"
|
|
@@ -14,9 +18,9 @@
|
|
|
14
18
|
:loading="priority ? 'eager' : 'lazy'"
|
|
15
19
|
:priority="priority"
|
|
16
20
|
class="library-feed__avatar"
|
|
17
|
-
width="
|
|
18
|
-
height="
|
|
19
|
-
sizes="
|
|
21
|
+
width="64"
|
|
22
|
+
height="64"
|
|
23
|
+
sizes="64px"
|
|
20
24
|
@error="avatarError = true"
|
|
21
25
|
/>
|
|
22
26
|
<span
|
|
@@ -45,7 +49,7 @@
|
|
|
45
49
|
|
|
46
50
|
<span
|
|
47
51
|
v-if="item.newCount"
|
|
48
|
-
class="library-feed__badge"
|
|
52
|
+
class="library-feed__badge _from-sm"
|
|
49
53
|
>{{ t('library.new_count', { count: item.newCount }) }}</span>
|
|
50
54
|
</div>
|
|
51
55
|
|
|
@@ -57,10 +61,19 @@
|
|
|
57
61
|
micro
|
|
58
62
|
/>
|
|
59
63
|
|
|
64
|
+
<p
|
|
65
|
+
v-if="parametersText || ageText"
|
|
66
|
+
class="library-feed__params"
|
|
67
|
+
>{{ parametersText }}<span
|
|
68
|
+
v-if="ageText"
|
|
69
|
+
class="library-feed__age"
|
|
70
|
+
>{{ ageText }}</span></p>
|
|
71
|
+
|
|
60
72
|
<p class="library-feed__meta">
|
|
61
73
|
<span
|
|
62
74
|
v-for="(entry, index) in item.meta"
|
|
63
75
|
:key="`meta-${index}`"
|
|
76
|
+
:class="{ '_from-sm': entry.desktopOnly }"
|
|
64
77
|
class="library-feed__meta-item"
|
|
65
78
|
>
|
|
66
79
|
<UiIcon
|
|
@@ -137,6 +150,8 @@ const props = defineProps<{
|
|
|
137
150
|
item: ILibraryFeedItem
|
|
138
151
|
following?: boolean
|
|
139
152
|
priority?: boolean
|
|
153
|
+
/** У категорий аватарки нет вообще — колонка не рисуется, инициалы не подставляются */
|
|
154
|
+
withoutAvatar?: boolean
|
|
140
155
|
}>();
|
|
141
156
|
|
|
142
157
|
const emit = defineEmits<{
|
|
@@ -158,6 +173,14 @@ const initials = computed(() => props.item.title
|
|
|
158
173
|
.toUpperCase());
|
|
159
174
|
|
|
160
175
|
const videosText = computed(() => t('plural.videos', props.item.videosCount));
|
|
176
|
+
|
|
177
|
+
const parametersText = computed(() => (props.item.parameters ?? []).filter(Boolean).join(' · '));
|
|
178
|
+
|
|
179
|
+
/** Возраст идёт хвостом той же строки, поэтому разделитель печатается только когда параметры есть. */
|
|
180
|
+
const ageText = computed(() => {
|
|
181
|
+
if (!props.item.age) return '';
|
|
182
|
+
return parametersText.value ? ` · ${props.item.age}` : props.item.age;
|
|
183
|
+
});
|
|
161
184
|
</script>
|
|
162
185
|
|
|
163
186
|
<style lang="scss">
|
|
@@ -165,13 +188,11 @@ const videosText = computed(() => t('plural.videos', props.item.videosCount));
|
|
|
165
188
|
/* Растушёвка «ещё» у тегов уходит в фон карточки, а не страницы */
|
|
166
189
|
--expand-row-fade-end: #{$library-feed-fade};
|
|
167
190
|
|
|
168
|
-
padding: 16px;
|
|
169
|
-
background-color: $library-feed-bg;
|
|
170
|
-
border: 1px solid $library-feed-border;
|
|
171
|
-
border-radius: $radius-xl;
|
|
172
|
-
|
|
173
191
|
@include from-br(sm) {
|
|
174
192
|
padding: 20px;
|
|
193
|
+
border: 1px solid $library-feed-border;
|
|
194
|
+
background-color: $library-feed-bg;
|
|
195
|
+
border-radius: $radius-xl;
|
|
175
196
|
}
|
|
176
197
|
}
|
|
177
198
|
|
|
@@ -180,7 +201,7 @@ const videosText = computed(() => t('plural.videos', props.item.videosCount));
|
|
|
180
201
|
grid-template-columns: auto minmax(0, 1fr);
|
|
181
202
|
column-gap: 16px;
|
|
182
203
|
row-gap: 12px;
|
|
183
|
-
align-items:
|
|
204
|
+
align-items: center;
|
|
184
205
|
margin-bottom: 20px;
|
|
185
206
|
|
|
186
207
|
@include from-br(sm) {
|
|
@@ -188,24 +209,32 @@ const videosText = computed(() => t('plural.videos', props.item.videosCount));
|
|
|
188
209
|
}
|
|
189
210
|
}
|
|
190
211
|
|
|
212
|
+
.library-feed__header.--without-avatar {
|
|
213
|
+
grid-template-columns: minmax(0, 1fr);
|
|
214
|
+
|
|
215
|
+
@include from-br(sm) {
|
|
216
|
+
grid-template-columns: minmax(0, 1fr) auto;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
191
220
|
.library-feed__avatar {
|
|
192
|
-
width:
|
|
193
|
-
height:
|
|
221
|
+
width: 64px;
|
|
222
|
+
height: 64px;
|
|
194
223
|
object-fit: cover;
|
|
195
224
|
object-position: center 30%;
|
|
196
225
|
border-radius: $radius-full;
|
|
197
|
-
border: 2px solid $library-feed-avatar-border;
|
|
198
226
|
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
227
|
+
/* Кольцо рисуем тенью, а не бордером: при border-box рамка съедала бы 4px картинки */
|
|
228
|
+
box-shadow: 0 0 0 2px $library-feed-avatar-border;
|
|
229
|
+
|
|
230
|
+
@media (hover: hover) {
|
|
231
|
+
transition: var(--transition) box-shadow;
|
|
203
232
|
}
|
|
204
233
|
}
|
|
205
234
|
|
|
206
|
-
@
|
|
235
|
+
@media (hover: hover) {
|
|
207
236
|
.library-feed__avatar-link:is(:hover, :focus-visible) .library-feed__avatar {
|
|
208
|
-
|
|
237
|
+
box-shadow: 0 0 0 2px $library-feed-avatar-border--hover;
|
|
209
238
|
}
|
|
210
239
|
}
|
|
211
240
|
|
|
@@ -270,7 +299,18 @@ const videosText = computed(() => t('plural.videos', props.item.videosCount));
|
|
|
270
299
|
}
|
|
271
300
|
|
|
272
301
|
.library-feed__tags {
|
|
273
|
-
margin-top:
|
|
302
|
+
margin-top: 4px;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
.library-feed__params {
|
|
306
|
+
@include font-sm;
|
|
307
|
+
|
|
308
|
+
margin-top: 4px;
|
|
309
|
+
color: $library-feed-params;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
.library-feed__age {
|
|
313
|
+
color: $library-feed-age;
|
|
274
314
|
}
|
|
275
315
|
|
|
276
316
|
.library-feed__meta {
|
|
@@ -281,7 +321,7 @@ const videosText = computed(() => t('plural.videos', props.item.videosCount));
|
|
|
281
321
|
align-items: center;
|
|
282
322
|
column-gap: 8px;
|
|
283
323
|
row-gap: 4px;
|
|
284
|
-
margin-top:
|
|
324
|
+
margin-top: 2px;
|
|
285
325
|
color: $library-feed-meta;
|
|
286
326
|
}
|
|
287
327
|
|
|
@@ -291,9 +331,10 @@ const videosText = computed(() => t('plural.videos', props.item.videosCount));
|
|
|
291
331
|
column-gap: 6px;
|
|
292
332
|
}
|
|
293
333
|
|
|
294
|
-
|
|
334
|
+
/* Разделитель слева, а не справа: скрытый на мобиле счётчик не оставляет висячей точки */
|
|
335
|
+
.library-feed__meta-item:not(:first-child)::before {
|
|
295
336
|
content: '·';
|
|
296
|
-
margin-inline-
|
|
337
|
+
margin-inline-end: 2px;
|
|
297
338
|
color: $library-feed-meta-dot;
|
|
298
339
|
}
|
|
299
340
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
<!-- Список подписок «Библиотеки»: сортировка, блоки
|
|
1
|
+
<!-- Список подписок «Библиотеки»: сортировка, блоки лент -->
|
|
2
2
|
<template>
|
|
3
3
|
<div class="library-follows">
|
|
4
4
|
<UiSectionTitle
|
|
@@ -27,6 +27,7 @@
|
|
|
27
27
|
:item="item"
|
|
28
28
|
:following="isFollowing(item)"
|
|
29
29
|
:priority="index === 0"
|
|
30
|
+
:without-avatar="withoutAvatar"
|
|
30
31
|
@toggle-follow="onToggleFollow"
|
|
31
32
|
/>
|
|
32
33
|
</div>
|
|
@@ -35,15 +36,6 @@
|
|
|
35
36
|
v-else
|
|
36
37
|
without-reset
|
|
37
38
|
/>
|
|
38
|
-
|
|
39
|
-
<UiConfirm
|
|
40
|
-
v-model="isUnfollowOpen"
|
|
41
|
-
:title="t('library.unfollow_title', { name: pendingUnfollow?.title })"
|
|
42
|
-
:confirm-text="t('library.unfollow_confirm')"
|
|
43
|
-
@confirm="onUnfollowConfirm"
|
|
44
|
-
>
|
|
45
|
-
<p class="library-follows__confirm">{{ t('library.unfollow_text') }}</p>
|
|
46
|
-
</UiConfirm>
|
|
47
39
|
</div>
|
|
48
40
|
</template>
|
|
49
41
|
|
|
@@ -61,6 +53,8 @@ const props = defineProps<{
|
|
|
61
53
|
items: ILibraryFeedItem[]
|
|
62
54
|
title: string
|
|
63
55
|
icon: string
|
|
56
|
+
/** Список без аватарок — у категорий их нет (и инициалы вместо них тоже не рисуем) */
|
|
57
|
+
withoutAvatar?: boolean
|
|
64
58
|
}>();
|
|
65
59
|
|
|
66
60
|
const { t } = useI18n();
|
|
@@ -90,31 +84,10 @@ function isFollowing(item: ILibraryFeedItem) {
|
|
|
90
84
|
return !unfollowed.value.includes(item.guid);
|
|
91
85
|
}
|
|
92
86
|
|
|
93
|
-
/** Сущность, для которой открыт попап отписки. */
|
|
94
|
-
const pendingUnfollow = ref<ILibraryFeedItem | null>(null);
|
|
95
|
-
|
|
96
|
-
const isUnfollowOpen = computed({
|
|
97
|
-
get: () => pendingUnfollow.value !== null,
|
|
98
|
-
set: value => {
|
|
99
|
-
if (!value) pendingUnfollow.value = null;
|
|
100
|
-
},
|
|
101
|
-
});
|
|
102
|
-
|
|
103
|
-
/** Подписка — сразу, отписка — только через подтверждение: она сносит ленту обновлений. */
|
|
104
87
|
function onToggleFollow(item: ILibraryFeedItem) {
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
unfollowed.value = unfollowed.value.filter(guid => guid !== item.guid);
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
function onUnfollowConfirm() {
|
|
114
|
-
if (!pendingUnfollow.value) return;
|
|
115
|
-
|
|
116
|
-
unfollowed.value = [...unfollowed.value, pendingUnfollow.value.guid];
|
|
117
|
-
pendingUnfollow.value = null;
|
|
88
|
+
unfollowed.value = isFollowing(item)
|
|
89
|
+
? [...unfollowed.value, item.guid]
|
|
90
|
+
: unfollowed.value.filter(guid => guid !== item.guid);
|
|
118
91
|
}
|
|
119
92
|
</script>
|
|
120
93
|
|
|
@@ -128,20 +101,11 @@ function onUnfollowConfirm() {
|
|
|
128
101
|
flex-wrap: wrap;
|
|
129
102
|
align-items: center;
|
|
130
103
|
gap: 8px;
|
|
131
|
-
margin-bottom:
|
|
104
|
+
margin-bottom: 32px;
|
|
132
105
|
}
|
|
133
106
|
|
|
134
107
|
.library-follows__list {
|
|
135
108
|
display: grid;
|
|
136
109
|
row-gap: 24px;
|
|
137
110
|
}
|
|
138
|
-
|
|
139
|
-
.library-follows__confirm {
|
|
140
|
-
@include font-sm;
|
|
141
|
-
|
|
142
|
-
max-width: 400px;
|
|
143
|
-
margin: 0 auto;
|
|
144
|
-
text-align: center;
|
|
145
|
-
color: $page-library-confirm-text;
|
|
146
|
-
}
|
|
147
111
|
</style>
|
|
@@ -86,12 +86,12 @@ withDefaults(defineProps<{
|
|
|
86
86
|
border: 1px solid $library-item-icon-border;
|
|
87
87
|
border-radius: $radius-xl;
|
|
88
88
|
|
|
89
|
-
@
|
|
89
|
+
@media (hover: hover) {
|
|
90
90
|
transition: var(--transition) background-color, var(--transition) border-color;
|
|
91
91
|
}
|
|
92
92
|
}
|
|
93
93
|
|
|
94
|
-
@
|
|
94
|
+
@media (hover: hover) {
|
|
95
95
|
.library-item:is(:hover, :focus-visible) .library-item__icon {
|
|
96
96
|
background-color: $library-item-icon-bg--hover;
|
|
97
97
|
border-color: $library-item-icon-border--hover;
|
|
@@ -117,12 +117,12 @@ withDefaults(defineProps<{
|
|
|
117
117
|
font-weight: 600;
|
|
118
118
|
color: $library-item-title;
|
|
119
119
|
|
|
120
|
-
@
|
|
120
|
+
@media (hover: hover) {
|
|
121
121
|
transition: var(--transition) color;
|
|
122
122
|
}
|
|
123
123
|
}
|
|
124
124
|
|
|
125
|
-
@
|
|
125
|
+
@media (hover: hover) {
|
|
126
126
|
.library-item:is(:hover, :focus-visible) .library-item__title {
|
|
127
127
|
color: $library-item-title--hover;
|
|
128
128
|
}
|
|
@@ -414,7 +414,7 @@ function onFollowClick() {
|
|
|
414
414
|
align-self: center;
|
|
415
415
|
margin-left: auto;
|
|
416
416
|
|
|
417
|
-
@
|
|
417
|
+
@media (hover: hover) {
|
|
418
418
|
opacity: 0;
|
|
419
419
|
transition: var(--transition) opacity;
|
|
420
420
|
color: $video-playlist-card-dropdown-text;
|
|
@@ -428,7 +428,7 @@ function onFollowClick() {
|
|
|
428
428
|
.player-playlist__card:is(:hover, :focus-visible) .player-playlist__card-dropdown,
|
|
429
429
|
.player-playlist__card-dropdown.--open,
|
|
430
430
|
.player-playlist__card-dropdown:focus-within {
|
|
431
|
-
@
|
|
431
|
+
@media (hover: hover) {
|
|
432
432
|
opacity: 1;
|
|
433
433
|
}
|
|
434
434
|
}
|