itube-specs 0.0.760 → 0.0.762

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 (58) hide show
  1. package/composables/use-noindex.ts +11 -0
  2. package/package.json +1 -1
  3. package/components/auth/s-auth-icon.vue +0 -20
  4. package/components/auth/s-auth-login.vue +0 -120
  5. package/components/auth/s-auth-popup.vue +0 -69
  6. package/components/auth/s-auth-recovery.vue +0 -104
  7. package/components/auth/s-auth-register.vue +0 -176
  8. package/components/cards/s-video-mini-card.vue +0 -62
  9. package/components/grids/s-grid-categories.vue +0 -20
  10. package/components/grids/s-grid-channels.vue +0 -23
  11. package/components/grids/s-grid-models.vue +0 -25
  12. package/components/grids/s-grid-playlists.vue +0 -21
  13. package/components/grids/s-grid-videos.vue +0 -68
  14. package/components/page-components/s-breadcrumbs.vue +0 -44
  15. package/components/page-components/s-expand-row.vue +0 -113
  16. package/components/page-components/s-filter-button.vue +0 -41
  17. package/components/page-components/s-filter-chips.vue +0 -34
  18. package/components/page-components/s-filter-page.vue +0 -178
  19. package/components/page-components/s-filter-popup.vue +0 -155
  20. package/components/page-components/s-filter-slider.vue +0 -145
  21. package/components/page-components/s-filter-videos-chips.vue +0 -105
  22. package/components/page-components/s-filter.vue +0 -357
  23. package/components/page-components/s-footer-models.vue +0 -28
  24. package/components/page-components/s-info-grid.vue +0 -89
  25. package/components/page-components/s-info-socials.vue +0 -33
  26. package/components/page-components/s-like.vue +0 -121
  27. package/components/page-components/s-model-filters.vue +0 -235
  28. package/components/page-components/s-navigation-links.vue +0 -63
  29. package/components/page-components/s-pagination.vue +0 -214
  30. package/components/page-components/s-report.vue +0 -277
  31. package/components/page-components/s-section-title.vue +0 -35
  32. package/components/page-components/s-share.vue +0 -145
  33. package/components/playlist/s-playlist-add.vue +0 -299
  34. package/components/playlist/s-playlist-delete-video.vue +0 -79
  35. package/components/playlist/s-playlist-edit.vue +0 -215
  36. package/components/playlist/s-playlist-input.vue +0 -88
  37. package/components/playlist/s-playlist-private-toggle.vue +0 -20
  38. package/components/ui/s-avatar.vue +0 -33
  39. package/components/ui/s-button.vue +0 -51
  40. package/components/ui/s-checkbox.vue +0 -57
  41. package/components/ui/s-chips.vue +0 -142
  42. package/components/ui/s-count.vue +0 -17
  43. package/components/ui/s-dropdown.vue +0 -152
  44. package/components/ui/s-icon.vue +0 -19
  45. package/components/ui/s-img.vue +0 -59
  46. package/components/ui/s-input.vue +0 -181
  47. package/components/ui/s-label.vue +0 -20
  48. package/components/ui/s-link.vue +0 -46
  49. package/components/ui/s-notification.vue +0 -72
  50. package/components/ui/s-popup.vue +0 -119
  51. package/components/ui/s-radio.vue +0 -56
  52. package/components/ui/s-select.vue +0 -105
  53. package/components/ui/s-slider.vue +0 -67
  54. package/components/ui/s-snackbar.vue +0 -27
  55. package/components/ui/s-timestamp.vue +0 -59
  56. package/components/ui/s-toggle.vue +0 -30
  57. package/components/ui/s-tooltip.vue +0 -57
  58. package/components/video/s-video-autoplay.vue +0 -29
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Помечает страницу как noindex для поисковиков (ссылки при этом проходятся — follow).
3
+ * Для приватных/служебных страниц и результатов внутреннего поиска.
4
+ */
5
+ export function useNoindex() {
6
+ useHead({
7
+ meta: [
8
+ { name: 'robots', content: 'noindex, follow' },
9
+ ],
10
+ });
11
+ }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "itube-specs",
3
3
  "type": "module",
4
- "version": "0.0.760",
4
+ "version": "0.0.762",
5
5
  "main": "./nuxt.config.ts",
6
6
  "types": "./types/index.d.ts",
7
7
  "scripts": {
@@ -1,20 +0,0 @@
1
- <template>
2
- <div class="s-auth-icon">
3
- <SIcon
4
- class="s-auth-icon__icon"
5
- :name="icon"
6
- size="24"
7
- />
8
- </div>
9
- </template>
10
-
11
- <script setup lang="ts">
12
- const props = withDefaults(defineProps<{
13
- icon?: string
14
- }>(), {
15
- icon: 'user-filled'
16
- })
17
- </script>
18
-
19
- <style scoped lang="scss">
20
- </style>
@@ -1,120 +0,0 @@
1
- <template>
2
- <div
3
- class="s-auth-login"
4
- :class="{'_loading': loading}"
5
- >
6
-
7
- <SAuthIcon class="s-auth-login__icon"/>
8
-
9
- <SInput
10
- v-model="form.username"
11
- :label="t('auth.username')"
12
- :placeholder="t('auth.username')"
13
- hide-label
14
- name="login-name"
15
- :error="error.username"
16
- @update:error="(val: boolean) => error.username = val"
17
- />
18
- <SInput
19
- v-model="form.password_hash"
20
- :label="t('auth.password')"
21
- :placeholder="t('auth.password')"
22
- hide-label
23
- type="password"
24
- :error="error.password_hash"
25
- @update:error="(val: boolean) => error.password_hash = val"
26
- />
27
-
28
- <div class="s-auth-login__buttons">
29
- <SButton
30
- class="s-auth-login__login"
31
- wide
32
- size="l"
33
- :disabled="loading"
34
- theme="primary"
35
- @click="login"
36
- >{{ t('auth.log_in') }}
37
- </SButton>
38
- <button
39
- class="s-auth-login__forgot"
40
- type="button"
41
- @click="onForgotClick"
42
- >
43
- {{ t('auth.forgot') }}
44
- </button>
45
-
46
- <p class="s-auth-login__back">
47
- {{ t('register_text') }}
48
- <button
49
- class="s-auth-login__back-button"
50
- type="button"
51
- @click="onCreateClick"
52
- >
53
- {{ t('auth.sign_up') }}
54
- </button>
55
- </p>
56
- </div>
57
- </div>
58
- </template>
59
-
60
- <script setup lang="ts">
61
- import { validatePassword, validateUsername } from '../../runtime';
62
- import { AuthorizationApiService } from '~/services/api/authorization.service';
63
-
64
- const { t } = useI18n();
65
-
66
- const form = ref({
67
- username: '',
68
- password_hash: '',
69
- });
70
-
71
- const error = ref({
72
- username: false,
73
- password_hash: false,
74
- });
75
-
76
- const emit = defineEmits<{
77
- (eventName: 'forgot'): void
78
- (eventName: 'create'): void
79
- }>();
80
-
81
- function onForgotClick() {
82
- emit('forgot');
83
- }
84
-
85
- function onCreateClick() {
86
- emit('create');
87
- }
88
-
89
- const loading = ref(false);
90
-
91
- const { setErrorState, showError, showSuccess, resetSnackbar } = useSnackbar();
92
-
93
- async function login() {
94
- resetSnackbar();
95
- error.value.username = false;
96
- error.value.password_hash = false;
97
-
98
- if (!validateUsername(form.value.username)) {
99
- error.value.username = true;
100
- showError('error_username');
101
- }
102
- if (!validatePassword(form.value.password_hash)) {
103
- error.value.password_hash = true;
104
- showError('error_password');
105
- }
106
-
107
- if (!error.value.username && !error.value.password_hash) {
108
- try {
109
- loading.value = true;
110
- await useUser(AuthorizationApiService).login(form.value);
111
- useAuthPopup().closeAuthPopup();
112
- showSuccess('Welcome back!');
113
- } catch (error) {
114
- setErrorState(error);
115
- } finally {
116
- loading.value = false;
117
- }
118
- }
119
- }
120
- </script>
@@ -1,69 +0,0 @@
1
- <template>
2
- <div v-if="isAuthPopupOpen" class="s-auth-popup__decorate"/>
3
- <transition mode="out-in">
4
- <SPopup
5
- v-if="currentStep === EAuthSteps.Registration && isAuthPopupOpen"
6
- v-model="isAuthPopupOpen"
7
- transparent-backdrop
8
- :back="isBackShow"
9
- class="s-auth-popup"
10
- @back="currentStep -= 1"
11
- >
12
- <template #title>{{ $t('auth.create_new') }}</template>
13
- <SAuthRegister
14
- :additional-text="additionalText"
15
- @login="onLoginClick"
16
- />
17
- </SPopup>
18
- </transition>
19
-
20
- <transition mode="out-in">
21
- <SPopup
22
- v-if="currentStep === EAuthSteps.Login && isAuthPopupOpen"
23
- v-model="isAuthPopupOpen"
24
- transparent-backdrop
25
- :back="isBackShow"
26
- class="s-auth-popup"
27
- @back="currentStep -= 1"
28
- >
29
- <template #title>{{ $t('auth.welcome_back') }}</template>
30
- <SAuthLogin @forgot="onRecoveryClick" @create="currentStep -= 1"/>
31
- </SPopup>
32
- </transition>
33
-
34
- <transition mode="out-in">
35
- <SPopup
36
- v-if="currentStep === EAuthSteps.Recovery && isAuthPopupOpen"
37
- v-model="isAuthPopupOpen"
38
- transparent-backdrop
39
- :back="isBackShow"
40
- class="s-auth-popup"
41
- @back="currentStep -= 1"
42
- >
43
- <template #title>{{ $t('auth.recovery_title')}}</template>
44
- <SAuthRecovery @login="onLoginClick"/>
45
- </SPopup>
46
- </transition>
47
- </template>
48
-
49
- <script setup lang="ts">
50
- import { EAuthSteps } from '../../runtime';
51
-
52
- const { isAuthPopupOpen, currentStep, additionalText } = useAuthPopup()
53
-
54
- const backShow = ref(false)
55
-
56
- function onLoginClick(): void {
57
- currentStep.value = EAuthSteps.Login
58
- backShow.value = true
59
- }
60
-
61
- function onRecoveryClick(): void {
62
- currentStep.value = EAuthSteps.Recovery
63
- backShow.value = true
64
- }
65
-
66
- const isBackShow = computed(() => {
67
- return currentStep.value !== EAuthSteps.Registration && backShow.value
68
- })
69
- </script>
@@ -1,104 +0,0 @@
1
- <template>
2
- <div
3
- class="s-auth-recovery"
4
- :class="{'_loading': loading}"
5
- >
6
-
7
- <SAuthIcon
8
- class="s-auth-recovery__icon"
9
- icon="envelope"
10
- />
11
-
12
- <p class="s-auth-recovery__text">{{ t('auth.recovery_text') }}</p>
13
- <SInput
14
- v-model="form.email"
15
- :label="$t('email')"
16
- :placeholder="$t('email')"
17
- hide-label
18
- inputmode="email"
19
- autocomplete="email"
20
- :error="error.email"
21
- @update:error="(val: boolean) => error.email = val"
22
- />
23
-
24
- <SButton
25
- class="s-auth-recovery__button"
26
- wide
27
- size="l"
28
- :disabled="loading"
29
- theme="primary"
30
- @click="submit"
31
- >{{ t('auth.get_recovery') }}
32
- </SButton>
33
- <button
34
- class="s-auth-recovery__login"
35
- type="button"
36
- @click="onLoginClick"
37
- >{{ t('auth.back_to_login') }}
38
- </button>
39
- </div>
40
- </template>
41
-
42
- <script setup lang="ts">
43
- import type { IPasswordForm } from '../../types';
44
- import { validateEmail } from '../../runtime';
45
- import { AuthorizationApiService } from '~/services/api/authorization.service';
46
-
47
- const { initRecaptcha, getRecaptchaToken } = useRecaptcha();
48
-
49
- const { t } = useI18n();
50
-
51
- const form = ref({
52
- email: '',
53
- token: '',
54
- } as IPasswordForm)
55
-
56
- const error = ref({
57
- email: false,
58
- });
59
-
60
- const emit = defineEmits<{
61
- (eventName: 'login'): void
62
- }>()
63
-
64
- function onLoginClick() {
65
- emit('login')
66
- }
67
-
68
- const loading = ref(false)
69
-
70
- const { setErrorState, showError, showSuccess, resetSnackbar } = useSnackbar();
71
-
72
- // Загружаем reCAPTCHA при открытии формы (onMounted) — даёт Google время собрать данные о поведении
73
- onMounted(async () => {
74
- try {
75
- await initRecaptcha();
76
- console.log('reCAPTCHA готов для этой формы');
77
- } catch (err) {
78
- console.error('Ошибка инициализации reCAPTCHA:', err);
79
- }
80
- });
81
-
82
- async function submit() {
83
- resetSnackbar()
84
- error.value.email = false;
85
-
86
- if (!validateEmail(form.value.email)) {
87
- error.value.email = true;
88
- showError('error_email');
89
- return;
90
- }
91
-
92
- try {
93
- loading.value = true;
94
- form.value.token = await getRecaptchaToken('password_reset');
95
- await useUser(AuthorizationApiService).password(form.value);
96
- useAuthPopup().closeAuthPopup();
97
- showSuccess('Check your email');
98
- } catch (error) {
99
- setErrorState(error);
100
- } finally {
101
- loading.value = false;
102
- }
103
- }
104
- </script>
@@ -1,176 +0,0 @@
1
- <template>
2
- <ClientOnly>
3
- <div
4
- class="s-auth-register"
5
- :class="{'_loading': loading}"
6
- >
7
- <SAuthIcon class="s-auth-register__icon" />
8
- <p
9
- v-if="additionalText"
10
- class="s-auth-register__text"
11
- >{{ t(additionalText) }}
12
- </p>
13
- <SInput
14
- v-model="form.email"
15
- :placeholder="t('email')"
16
- :label="t('email')"
17
- hide-label
18
- inputmode="email"
19
- autocomplete="email"
20
- :error="error.email"
21
- @update:error="(val: boolean) => error.email = val"
22
- />
23
- <SInput
24
- v-model="form.username"
25
- :label="t('login')"
26
- :placeholder="t('login')"
27
- hide-label
28
- name="login-name"
29
- :error="error.username"
30
- @update:error="(val: boolean) => error.username = val"
31
- />
32
- <SInput
33
- v-model="form.password_hash"
34
- :label="t('auth.password')"
35
- :placeholder="t('auth.password')"
36
- hide-label
37
- type="password"
38
- :error="error.password_hash"
39
- autocomplete="new-password"
40
- @update:error="(val: boolean) => error.password_hash = val"
41
- />
42
-
43
- <div class="s-auth-register__buttons">
44
- <SButton
45
- :disabled="loading"
46
- wide
47
- size="l"
48
- theme="primary"
49
- @click="submit"
50
- >{{ t('auth.sign_up') }}
51
- </SButton>
52
- <p class="s-auth-register__login">
53
- {{ t('login_text') }}
54
- <button
55
- class="s-auth-register__login-button"
56
- type="button"
57
- @click="onLoginClick">
58
- {{ t('auth.log_in') }}
59
- </button>
60
- </p>
61
- </div>
62
-
63
- <p class="s-auth-register__agreement">{{ t('auth.agree_begin') }}
64
- <NuxtLink
65
- class="s-auth-register__agreement-link"
66
- to="/terms"
67
- target="_blank"
68
- >{{ t('auth.agree_terms') }}
69
- </NuxtLink>
70
- {{ t('auth.agree_and') }}
71
- <NuxtLink
72
- class="s-auth-register__agreement-link"
73
- to="/policy"
74
- target="_blank"
75
- >{{ t('auth.agree_policy') }}
76
- </NuxtLink>
77
- </p>
78
- </div>
79
- </ClientOnly>
80
- </template>
81
-
82
- <script setup lang="ts">
83
- import type { IRegistrateForm } from '../../types';
84
- import { validateEmail, validatePassword, validateUsername } from '../../runtime';
85
- import { AuthorizationApiService } from '~/services/api/authorization.service';
86
-
87
- const { initRecaptcha, getRecaptchaToken } = useRecaptcha();
88
-
89
- defineProps<{
90
- additionalText?: string;
91
- }>()
92
-
93
- const { t } = useI18n();
94
-
95
- const form = ref({
96
- username: '',
97
- email: '',
98
- password_hash: '',
99
- token: '',
100
- } as IRegistrateForm)
101
-
102
- const error = ref({
103
- username: false,
104
- email: false,
105
- password_hash: false,
106
- })
107
-
108
- const emit = defineEmits<{
109
- (eventName: 'login'): void
110
- }>()
111
-
112
- function onLoginClick() {
113
- emit('login')
114
- }
115
-
116
- const loading = ref(false);
117
-
118
- const { setErrorState, showError, showSuccess, resetSnackbar } = useSnackbar();
119
- // Загружаем reCAPTCHA при открытии формы (onMounted) — даёт Google время собрать данные о поведении
120
- onMounted(async () => {
121
- try {
122
- await initRecaptcha();
123
- console.log('reCAPTCHA готов для этой формы');
124
- } catch (err) {
125
- console.error('Ошибка инициализации reCAPTCHA:', err);
126
- }
127
- });
128
-
129
- function validateForm(): boolean {
130
- const { username, email, password_hash } = form.value;
131
-
132
- const errors = {
133
- username: !validateUsername(username),
134
- email: !validateEmail(email),
135
- password_hash: !validatePassword(password_hash),
136
- };
137
-
138
- if (!validateEmail(email)) showError('error_email');
139
- if (!validatePassword(password_hash)) showError('error_password');
140
- if (!validateUsername(username)) showError('error_username');
141
-
142
- error.value = errors;
143
-
144
- return !Object.values(errors).some(Boolean);
145
- }
146
-
147
- async function submit() {
148
- resetSnackbar();
149
- if (!validateForm()) return;
150
-
151
- try {
152
- loading.value = true;
153
-
154
- // Уникальное действие для каждой формы
155
- // здесь 'register', в других формах — 'password_reset' или 'contact_form'
156
- form.value.token = await getRecaptchaToken('register');
157
-
158
- await useUser(AuthorizationApiService).register(form.value);
159
-
160
- // Создаем сразу Favorites плейлист, возможно костыль убрать когда будет нормальная логика с беком
161
- // const { FAVORITES_KEY } = useFavorites();
162
- // await PlaylistsApiService.postPlaylist(FAVORITES_KEY, EPlaylistType.Private);
163
-
164
- useAuthPopup().closeAuthPopup();
165
- showSuccess('Registration success');
166
- } catch (err) {
167
- setErrorState(err);
168
- console.error('Ошибка reCAPTCHA или регистрации:', err);
169
- } finally {
170
- loading.value = false;
171
- }
172
- }
173
- </script>
174
-
175
- <style scoped>
176
- </style>
@@ -1,62 +0,0 @@
1
- <template>
2
- <component
3
- :is="component"
4
- class="s-video-mini-card"
5
- :title="card.title"
6
- :to="link ? generateLink(`/playlists/${prefix}/${card.id}`) : null"
7
- >
8
- <SImg
9
- class="s-video-mini-card__img"
10
- sizes="140px"
11
- :src="currentPoster"
12
- width="1600"
13
- height="900"
14
- :alt="card.title"
15
- @error="onPosterError"
16
- />
17
- <div class="s-video-mini-card__wrapper">
18
- <p class="s-video-mini-card__title _truncate">{{ card.title }}</p>
19
- <span v-if="card.channelName" class="s-video-mini-card__channel _truncate">{{ card.channelName }}</span>
20
- <span class="s-video-mini-card__duration">
21
- <SIcon name="time" size="12" />
22
- {{ duration }}</span>
23
- </div>
24
- </component>
25
- </template>
26
-
27
- <script setup lang="ts">
28
- import type { IVideoCard } from '../../types';
29
- import { getDuration, ThumbSize } from '../../runtime';
30
-
31
- const props = defineProps<{
32
- card: IVideoCard
33
- link?: boolean
34
- prefix?: string
35
- }>()
36
-
37
- const posterError = ref(false)
38
-
39
- const duration = computed(() => {
40
- return getDuration(props.card.duration)
41
- })
42
-
43
- const component = computed(() => {
44
- if (props.link) {
45
- return resolveComponent('NuxtLink');
46
- }
47
- return 'div';
48
- })
49
-
50
- const { generateLink } = useGenerateLink();
51
-
52
- const posterPlaceholder = '/img/placeholder.webp'
53
-
54
- const currentPoster = computed(() => {
55
- if (posterError.value) return posterPlaceholder
56
- return props.card.thumbUrls?.webp?.[ThumbSize.Small] || posterPlaceholder
57
- })
58
-
59
- function onPosterError() {
60
- posterError.value = true
61
- }
62
- </script>
@@ -1,20 +0,0 @@
1
- <template>
2
- <div class="s-grid-categories">
3
- <SCategoryCard
4
- v-for="(item, index) in categories"
5
- :key="item.guid"
6
- :card="item"
7
- :priority="priority && index === 0"
8
- />
9
- <slot/>
10
- </div>
11
- </template>
12
-
13
- <script setup lang="ts">
14
- import type { ICategoryCard } from '../../types';
15
-
16
- defineProps<{
17
- categories: Array<ICategoryCard>,
18
- priority?: boolean
19
- }>();
20
- </script>
@@ -1,23 +0,0 @@
1
- <template>
2
- <div
3
- id="anchor"
4
- class="s-grid-channels"
5
- >
6
- <SChannelCard
7
- v-for="(item, index) in items"
8
- :key="item.guid"
9
- class="s-grid-channels__card"
10
- :card="item"
11
- :priority="priority && index === 0"
12
- />
13
- </div>
14
- </template>
15
-
16
- <script setup lang="ts">
17
- import type { IChannelCard } from '../../types';
18
-
19
- defineProps<{
20
- items: IChannelCard[]
21
- priority?: boolean
22
- }>();
23
- </script>
@@ -1,25 +0,0 @@
1
- <template>
2
- <div
3
- class="s-grid-models"
4
- :class="{'--footer': footer}"
5
- >
6
- <SModelCard
7
- v-for="(item, index) in items"
8
- :key="`model-card-${item.guid}`"
9
- class="s-grid-models__item"
10
- :card="item"
11
- :loading="index > 5 ? 'lazy' : 'eager'"
12
- :priority="priority && index === 0"
13
- />
14
- </div>
15
- </template>
16
-
17
- <script setup lang="ts">
18
- import type { IModelCard } from '../../types';
19
-
20
- defineProps<{
21
- items: IModelCard[]
22
- footer?: boolean
23
- priority?: boolean
24
- }>();
25
- </script>
@@ -1,21 +0,0 @@
1
- <template>
2
- <div class="s-grid-playlists">
3
- <SPlaylistCard
4
- v-for="(item, index) in items"
5
- :key="`user-playlist-${index}`"
6
- :card="item"
7
- :top-playlists="topPlaylists"
8
- :priority="priority && index === 0"
9
- />
10
- </div>
11
- </template>
12
-
13
- <script setup lang="ts">
14
- import type { IPlaylistCard } from '../../types';
15
-
16
- defineProps<{
17
- items: IPlaylistCard[]
18
- topPlaylists?: boolean
19
- priority?: boolean
20
- }>();
21
- </script>