itube-specs 0.0.899 → 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.
Files changed (40) hide show
  1. package/assets/icons/common/clock-filled.svg +1 -0
  2. package/assets/scss/mixins/_breakpoints.scss +3 -2
  3. package/assets/scss/themes/dark/_theme.scss +17 -23
  4. package/assets/scss/themes/light/_theme.scss +17 -23
  5. package/assets/scss/vars/_palette.scss +1 -0
  6. package/components/card/category.vue +4 -8
  7. package/components/card/dropdown/index.vue +47 -9
  8. package/components/card/dropdown/trigger.vue +1 -0
  9. package/components/card/model.vue +1 -1
  10. package/components/card/playlist.vue +15 -6
  11. package/components/card/round.vue +3 -3
  12. package/components/card/video/actions.vue +123 -0
  13. package/components/card/video/index.vue +149 -30
  14. package/components/filter/selects.vue +1 -1
  15. package/components/info/grid.vue +1 -1
  16. package/components/layout/header-user.vue +1 -1
  17. package/components/library/feed.vue +65 -24
  18. package/components/library/follows.vue +8 -44
  19. package/components/library/item.vue +4 -4
  20. package/components/player/like.vue +1 -1
  21. package/components/player/playlist.vue +2 -2
  22. package/components/report/main.vue +20 -5
  23. package/components/ui/checkbox.vue +1 -1
  24. package/components/ui/dropdown.vue +1 -1
  25. package/components/ui/input.vue +2 -2
  26. package/components/ui/radio.vue +1 -1
  27. package/components/ui/toggle.vue +1 -1
  28. package/composables/use-report-popup.ts +26 -3
  29. package/composables/use-video-collections.ts +30 -0
  30. package/lib/scheme/profile-navigation-items.ts +1 -1
  31. package/package.json +1 -1
  32. package/pages/history.vue +12 -36
  33. package/pages/library/categories.vue +2 -1
  34. package/pages/library/channels.vue +1 -1
  35. package/pages/library/index.vue +3 -3
  36. package/pages/library/models.vue +4 -4
  37. package/stubs/library-follows.ts +19 -16
  38. package/types/library-feed.d.ts +6 -0
  39. package/types/video-card.d.ts +4 -0
  40. package/components/card/history.vue +0 -359
@@ -1,359 +0,0 @@
1
- <!-- Карточка истории просмотров: постер с полосой прогресса, канал и время последнего просмотра -->
2
- <template>
3
- <div class="card-history">
4
- <NuxtLink
5
- :to="link"
6
- class="card-history__poster-wrapper"
7
- @mouseenter.passive="startPreviewByHover(previewId)"
8
- @mouseleave.passive="stopPreviewByHover(previewId)"
9
- @touchstart.passive="startPreviewByTouch(previewId)"
10
- >
11
- <UiImg
12
- :src="currentPoster"
13
- :urls="card.thumbUrls"
14
- :loading="loading"
15
- :priority="priority"
16
- :sizes="`(max-width: ${CSS_BREAKPOINTS.lg}px) 180px, 480px`"
17
- :alt="card.title"
18
- class="card-history__poster"
19
- width="480"
20
- height="270"
21
- @error="posterError = true"
22
- />
23
- <UiVideoPreview
24
- v-if="isPreviewShow && card.previewUrl"
25
- :url="card.previewUrl"
26
- />
27
- <UiLabel
28
- v-if="card.quality"
29
- :text="`${card.quality}p`"
30
- class="card-history__resolution"
31
- theme="yellow"
32
- />
33
- <UiLabel
34
- v-if="card.duration"
35
- :text="duration"
36
- class="card-history__duration"
37
- theme="black"
38
- />
39
- <span
40
- :style="{ '--card-history-progress': `${progress}%` }"
41
- :aria-label="t('watched_percent', { percent: progress })"
42
- :aria-valuenow="progress"
43
- class="card-history__progress"
44
- role="progressbar"
45
- aria-valuemin="0"
46
- aria-valuemax="100"
47
- />
48
- </NuxtLink>
49
-
50
- <div class="card-history__wrapper">
51
- <component
52
- :is="channelComponent"
53
- :to="channelLink"
54
- class="card-history__logo-wrapper"
55
- >
56
- <UiImg
57
- v-if="card.channelAvatar"
58
- :src="card.channelAvatar"
59
- :alt="card.channelName"
60
- :loading="loading"
61
- class="card-history__channel-logo"
62
- width="32"
63
- height="32"
64
- sizes="32px"
65
- />
66
- <span
67
- v-else
68
- class="card-history__user"
69
- >U</span>
70
- </component>
71
-
72
- <NuxtLink
73
- :to="link"
74
- :title="card.title"
75
- class="card-history__title _truncate"
76
- >{{ card.title }}
77
- </NuxtLink>
78
-
79
- <p class="card-history__meta">
80
- <NuxtLink
81
- v-if="card.channelName"
82
- :to="channelLink"
83
- class="card-history__channel _truncate"
84
- >{{ card.channelName }}</NuxtLink>
85
- <span
86
- :class="{'--remaining': remaining}"
87
- class="card-history__watched"
88
- >
89
- <UiIcon
90
- name="clock"
91
- size="12"
92
- />
93
- {{ remaining ? remainingText : watchedAgo }}
94
- </span>
95
- </p>
96
-
97
- <CardDropdownTrigger
98
- :card="videoCard"
99
- class="card-history__dropdown"
100
- show-remove
101
- show-share
102
- @share="emit('share', videoCard)"
103
- @remove="emit('remove', card)"
104
- />
105
- </div>
106
- </div>
107
- </template>
108
-
109
- <script setup lang="ts">
110
- import type { HistoryStubCard } from '../../stubs/history';
111
- import type { IVideoCard } from '../../types';
112
- import { convertString, getDuration } from '../../runtime';
113
- import { CSS_BREAKPOINTS } from '../../config/css-breakpoints';
114
-
115
- const props = defineProps<{
116
- card: HistoryStubCard
117
- loading?: 'eager' | 'lazy'
118
- priority?: boolean
119
- /** Развилка мета-строки: вместо «когда смотрели» — сколько осталось до конца («Продолжить просмотр») */
120
- remaining?: boolean
121
- }>();
122
-
123
- const emit = defineEmits<{
124
- (e: 'remove', card: HistoryStubCard): void
125
- (e: 'share', card: IVideoCard): void
126
- }>();
127
-
128
- const { t, locale } = useI18n();
129
-
130
- const { generateLink } = useGenerateLink();
131
-
132
- const NuxtLinkComponent = resolveComponent('NuxtLink');
133
-
134
- const channelComponent = computed(() => props.card.channelName ? NuxtLinkComponent : 'span');
135
-
136
- const posterError = ref(false);
137
-
138
- const currentPoster = computed(() => {
139
- if (posterError.value || !props.card.thumbUrls) return '/img/placeholder.webp';
140
- return undefined;
141
- });
142
-
143
- const previewId = useId();
144
- const { activePreviewId, startPreviewByHover, stopPreviewByHover, startPreviewByTouch } = useVideoPreview();
145
-
146
- const isPreviewShow = computed(() => activePreviewId.value === previewId);
147
-
148
- const duration = computed(() => getDuration(props.card.duration));
149
-
150
- const progress = computed(() => Math.min(100, Math.max(0, Math.round(props.card.progress))));
151
-
152
- const watchedAgo = computed(() => useFormatTimeAgo(props.card.watchedAt, locale.value, true));
153
-
154
- const remainingText = computed(() => {
155
- const left = Math.ceil((props.card.duration * (100 - progress.value)) / 100 / 60);
156
- return t('minutes_left', { count: Math.max(left, 1) });
157
- });
158
-
159
- const link = computed(() => generateLink(`/videos/${props.card.id}`));
160
-
161
- const channelLink = computed(() => props.card.channelName
162
- ? generateLink(`/channels/${convertString().toSlug(props.card.channelName)}`)
163
- : undefined);
164
-
165
- /** Дропдаун и репорт работают с обычной карточкой видео — переиспользуем её контракт. */
166
- const videoCard = computed<IVideoCard>(() => ({
167
- guid: props.card.guid,
168
- id: props.card.id,
169
- title: props.card.title,
170
- duration: props.card.duration,
171
- quality: props.card.quality,
172
- channelName: props.card.channelName,
173
- channelAvatar: props.card.channelAvatar,
174
- thumbUrls: props.card.thumbUrls,
175
- }));
176
-
177
- </script>
178
-
179
- <style lang="scss">
180
- $inset-label: 8px;
181
- $progress-height: 4px;
182
- $dropdown-column: 18px;
183
-
184
- .card-history {
185
- @include video-card;
186
-
187
- row-gap: 0;
188
- background-color: $history-card-bg;
189
- border: 1px solid $history-card-border;
190
- border-radius: $radius-xl;
191
-
192
- @include to-br(xs) {
193
- touch-action: manipulation;
194
- }
195
-
196
- @include hover(border-color, box-shadow) {
197
- border-color: $history-card-border--hover;
198
- box-shadow: $card-ring-shadow--hover;
199
- }
200
- }
201
-
202
- .card-history__poster-wrapper {
203
- @include video-card-poster;
204
-
205
- border-radius: calc($radius-xl - 1px) calc($radius-xl - 1px) 0 0;
206
- border-bottom: 1px solid $history-card-poster-border;
207
- }
208
-
209
- .card-history__poster {
210
- width: 100%;
211
- height: auto;
212
- object-fit: cover;
213
- aspect-ratio: 16/9;
214
- }
215
-
216
- .card-history__duration,
217
- .card-history__resolution {
218
- position: absolute;
219
- }
220
-
221
- .card-history__resolution {
222
- top: $inset-label;
223
- right: $inset-label;
224
- }
225
-
226
- .card-history__duration {
227
- bottom: $inset-label + $progress-height;
228
- left: $inset-label;
229
- }
230
-
231
- /* Превью кроет постер целиком ($z-index-plus) — полоса прогресса должна остаться поверх него */
232
- .card-history__progress {
233
- position: absolute;
234
- right: 0;
235
- bottom: 0;
236
- left: 0;
237
- z-index: $z-index-loader;
238
- height: $progress-height;
239
- background-color: $history-card-progress-bg;
240
-
241
- &::after {
242
- content: '';
243
- display: block;
244
- width: var(--card-history-progress, 0%);
245
- height: 100%;
246
- background-color: $history-card-progress-fill;
247
- }
248
- }
249
-
250
- .card-history__wrapper {
251
- display: grid;
252
- column-gap: 8px;
253
- grid-template-columns: 32px 1fr $dropdown-column;
254
- align-items: start;
255
- padding: 12px;
256
- color: $history-card-secondary;
257
- background-color: $history-card-wrapper-bg;
258
- border-radius: 0 0 calc($radius-xl - 1px) calc($radius-xl - 1px);
259
- }
260
-
261
- .card-history__logo-wrapper {
262
- grid-row: 1/3;
263
- grid-column: 1;
264
- }
265
-
266
- .card-history__channel-logo {
267
- border-radius: $radius-full;
268
- }
269
-
270
- .card-history__user {
271
- @include font-xs;
272
-
273
- width: 32px;
274
- height: 32px;
275
- display: grid;
276
- place-content: center;
277
- font-weight: 700;
278
- text-transform: uppercase;
279
- color: $history-card-user-text;
280
- background-color: $history-card-user-bg;
281
- border-radius: $radius-full;
282
- }
283
-
284
- .card-history__title {
285
- @include font-sm;
286
-
287
- --line-clamp: 2;
288
-
289
- grid-row: 1;
290
- grid-column: 2;
291
- color: $history-card-title;
292
-
293
- @include hover(color) {
294
- color: $history-card-title--hover;
295
- }
296
- }
297
-
298
- .card-history__meta {
299
- @include font-xs;
300
-
301
- display: flex;
302
- align-items: center;
303
- grid-row: 2;
304
- grid-column: 2;
305
- margin-top: 6px;
306
- font-weight: 400;
307
- color: $history-card-secondary;
308
- }
309
-
310
- .card-history__channel {
311
- color: $history-card-secondary;
312
- }
313
-
314
- .card-history__channel + .card-history__watched::before {
315
- content: '·';
316
- margin-inline: 6px;
317
- }
318
-
319
- .card-history__watched {
320
- display: flex;
321
- align-items: center;
322
- column-gap: 4px;
323
- flex-shrink: 0;
324
- }
325
-
326
- .card-history__watched.--remaining {
327
- color: $history-card-remaining;
328
- }
329
-
330
- .card-history__dropdown {
331
- grid-row: 1/3;
332
- grid-column: 3;
333
- align-self: start;
334
- color: $history-card-dropdown-icon;
335
-
336
- @include hover(color) {
337
- color: $history-card-dropdown-icon--hover;
338
- }
339
- }
340
-
341
- @include to-br(xs) {
342
- .card-history__logo-wrapper {
343
- display: none;
344
- }
345
-
346
- .card-history__wrapper {
347
- grid-template-columns: 1fr $dropdown-column;
348
- }
349
-
350
- .card-history__title,
351
- .card-history__meta {
352
- grid-column: 1;
353
- }
354
-
355
- .card-history__dropdown {
356
- grid-column: 2;
357
- }
358
- }
359
- </style>