itube-specs 0.0.814 → 0.0.816

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 (42) hide show
  1. package/nuxt.config.ts +5 -1
  2. package/package.json +2 -1
  3. package/pages/2257.vue +71 -0
  4. package/pages/categories/[slug].vue +246 -0
  5. package/pages/categories/all.vue +100 -0
  6. package/pages/categories/index.vue +180 -0
  7. package/pages/categories/letter/[slug].vue +92 -0
  8. package/pages/categories.vue +44 -0
  9. package/pages/channels/[channel].vue +216 -0
  10. package/pages/channels/index.vue +148 -0
  11. package/pages/channels.vue +6 -0
  12. package/pages/confirm/registration/[token].vue +88 -0
  13. package/pages/contacts.vue +368 -0
  14. package/pages/dmca.vue +129 -0
  15. package/pages/embed/[spot].vue +23 -0
  16. package/pages/favorites.vue +30 -0
  17. package/pages/index.vue +91 -0
  18. package/pages/models/[slug]/channel/[channel].vue +98 -0
  19. package/pages/models/[slug]/index.vue +100 -0
  20. package/pages/models/[slug]/tag/[tag].vue +95 -0
  21. package/pages/models/index.vue +87 -0
  22. package/pages/models/letter/[slug].vue +61 -0
  23. package/pages/models.vue +6 -0
  24. package/pages/playlists/[playlistId]/[slug].vue +136 -0
  25. package/pages/playlists/index.vue +154 -0
  26. package/pages/playlists.vue +6 -0
  27. package/pages/policy.vue +190 -0
  28. package/pages/search/[slug]/index.vue +266 -0
  29. package/pages/search/[slug]/models.vue +63 -0
  30. package/pages/search/[slug]/videos.vue +65 -0
  31. package/pages/tag/[tag].vue +80 -0
  32. package/pages/terms.vue +473 -0
  33. package/pages/user/profile.vue +252 -0
  34. package/pages/user/recovery-password.vue +82 -0
  35. package/pages/user/settings.vue +36 -0
  36. package/pages/user.vue +36 -0
  37. package/pages/videos/[slug].vue +161 -0
  38. package/server/api/site/languages.get.ts +14 -0
  39. package/server/middleware/locale-guard.ts +23 -0
  40. package/server/utils/__tests__/is-locale-blocked.test.ts +38 -0
  41. package/server/utils/is-locale-blocked.ts +31 -0
  42. package/services/api/authorization.service.ts +1 -1
package/nuxt.config.ts CHANGED
@@ -26,7 +26,11 @@ export default defineNuxtConfig({
26
26
  runtimeConfig: {
27
27
  public: {
28
28
  featureFlags: {},
29
- adsConfig: {}
29
+ adsConfig: {},
30
+ locales: {
31
+ codes: [],
32
+ default: 'en'
33
+ }
30
34
  }
31
35
  },
32
36
  });
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "itube-specs",
3
3
  "type": "module",
4
- "version": "0.0.814",
4
+ "version": "0.0.816",
5
5
  "main": "./nuxt.config.ts",
6
6
  "types": "./types/index.d.ts",
7
7
  "scripts": {
@@ -55,6 +55,7 @@
55
55
  "middleware/",
56
56
  "plugins/",
57
57
  "server/",
58
+ "pages/",
58
59
  "nuxt.config.ts"
59
60
  ],
60
61
  "devDependencies": {
package/pages/2257.vue ADDED
@@ -0,0 +1,71 @@
1
+ <template>
2
+ <div class="document__container _container">
3
+ <div class="document">
4
+ <section>
5
+ <h1>{{ h1 }}</h1>
6
+ <p>
7
+ For the purposes of 18 U.S. Code § 2257, the operator of the {{ brandName }} website is not a “Producer,”
8
+ either primary or secondary, of any of the content found on {{ brandName }}. The operator’s activities,
9
+ with respect to such content, are limited to the transmission, storage, retrieval, hosting and/or formatting of
10
+ depictions posted by third party users, on areas of the website under the user's control.
11
+ </p>
12
+
13
+ <p>
14
+ For § 2257 records for any content found on the {{ brandName }} website, please direct your request to the
15
+ respective uploader, whether a website, a user, a studio or a producer which provided the content (“Uploader”).
16
+ </p>
17
+
18
+ <p>
19
+ {{ brandName }} abides by the following procedures to ensure compliance:
20
+ </p>
21
+
22
+ <ul>
23
+ <li>
24
+ All Uploaders must be over eighteen (18) years of age to upload,
25
+ share and sell content on {{ brandName }};
26
+ </li>
27
+
28
+ <li>
29
+ When uploading, Uploader must verify the content; assure
30
+ he/she/they are 18 years of age; certify that he/she/they keep records of the models in the content and that
31
+ they are over 18 years of age;
32
+ </li>
33
+
34
+ <li>
35
+ the uploaded content does not violate {{ brandName }}’s
36
+ <a href="/terms">Terms of Service
37
+ </a>
38
+ , and any of its related policies.
39
+ </li>
40
+ </ul>
41
+
42
+ <p>
43
+ For further assistance and/or information in finding the content’s Uploader, please contact {{ brandName }}’s
44
+ compliance department at
45
+ <a
46
+ :href="`mailto:${emails.support}`"
47
+ class="email"
48
+ >{{ emails.support }}</a>
49
+ .
50
+ </p>
51
+
52
+ <p>
53
+ {{ brandName }} allows content to be reported as inappropriate. Should any content be reported as illegal,
54
+ unlawful,
55
+ harassing, harmful, offensive or various other reasons, {{ brandName }} shall remove it without delay.
56
+ </p>
57
+ </section>
58
+ </div>
59
+ </div>
60
+ </template>
61
+
62
+ <script setup lang="ts">
63
+ const { t } = useI18n();
64
+ const pagePureName = useGetPureRouteName();
65
+ const { brandName, emails } = useAppConfig();
66
+ const { meta, h1 } = useMeta(t, pagePureName, brandName);
67
+
68
+ watchEffect(() => {
69
+ useHead(meta.value);
70
+ });
71
+ </script>
@@ -0,0 +1,246 @@
1
+ <template>
2
+ <div class="category _page _container">
3
+ <UiBreadcrumbs
4
+ :items="breadcrumbs"
5
+ class="_breadcrumbs-info"
6
+ />
7
+
8
+ <InfoMain
9
+ v-if="categoryInfoResult && isFirstPage"
10
+ :key="`info-${categoryInfo?.header}`"
11
+ :card="categoryInfoResult"
12
+ type="category"
13
+ class="category__info"
14
+ />
15
+
16
+ <UiExpandRow
17
+ v-if="relatedCategories && relatedCategories.length && relatedCategories.length > 0"
18
+ :items="relatedCategories"
19
+ :pre-text="`${$t('related_categories')}:`"
20
+ class="category__related-categories"
21
+ />
22
+
23
+ <LazySectionToolbar
24
+ class="category__options"
25
+ hydrate-on-idle
26
+ />
27
+
28
+ <GridVideos
29
+ v-if="data && data.items && data.items?.length > 0"
30
+ id="anchor"
31
+ :items="data.items"
32
+ priority
33
+ :class="{'_loading': status === 'pending'}"
34
+ >
35
+ <template #grid-end>
36
+ <LazyAdvNativeBottom
37
+ v-if="featureFlags.AdsNativeEnabled"
38
+ hydrate-on-visible
39
+ />
40
+ </template>
41
+ <template #default>
42
+ <UiExpandRow
43
+ v-if="isChipsShow && relatedSearch && relatedSearch.length && relatedSearch.length > 0 && statusRelated !== 'pending'"
44
+ :items="relatedSearch"
45
+ :pre-text="`${$t('related_searches')}: `"
46
+ class="category__related-searches"
47
+ />
48
+ <div
49
+ v-else-if="statusRelated === 'pending'"
50
+ class="category__related-loading _loading"
51
+ />
52
+ <LazySectionSwiperCards
53
+ hydrate-on-visible
54
+ v-if="modelsByCategory && modelsByCategory.items && modelsByCategory.items.length > 0"
55
+ :items="modelsRoundCards"
56
+ :title="$t('category_top_models_title', {name: title})"
57
+ class="category__models"
58
+ placeholder-avatar="/img/model-placeholder.webp"
59
+ />
60
+ </template>
61
+ </GridVideos>
62
+ <div
63
+ v-else-if="status === 'pending'"
64
+ class="category__loading _loading"
65
+ />
66
+ <UiEmptyState v-else/>
67
+
68
+ <UiPagination
69
+ v-if="data"
70
+ arrow-icon
71
+ :key="`pagination-${data.allPages}`"
72
+ :total="data.allPages"
73
+ class="category__pagination _pagination"
74
+ />
75
+
76
+ <LazyUiFullDescr
77
+ v-if="isFirstPage && categoryInfo?.fullDescription"
78
+ :text="categoryInfo.fullDescription"
79
+ hydrate-on-visible
80
+ />
81
+ </div>
82
+ </template>
83
+
84
+ <script setup lang="ts">
85
+ import { CategoriesApiService } from '../../services/api/categories.service';
86
+ import { VideosApiService } from '../../services/api/videos.service';
87
+ import { ModelsApiService } from '../../services/api/models.service';
88
+ import { convertModelToRoundCard } from '../../runtime';
89
+ import type { ICardInfo } from '../../types';
90
+ import { computed, unref } from 'vue';
91
+
92
+ const featureFlags = useRuntimeConfig().public.featureFlags;
93
+
94
+ const { t } = useI18n();
95
+
96
+ const route = useRoute();
97
+ const disallowedSlugs = ['18', '19'];
98
+
99
+ const slug = useSlug();
100
+
101
+ const [
102
+ { data: categoryInfo },
103
+ { data, status },
104
+ { data: relatedData, status: statusRelated },
105
+ { data: modelsByCategory },
106
+ ] = await Promise.all([
107
+ useFetchCategoriesByName(CategoriesApiService.getCategoriesByName),
108
+ useFetchVideosByCategories(VideosApiService.getListByNicheAndCategory),
109
+ useFetchCategoriesRelated(CategoriesApiService.getRelatedCategories),
110
+ useFetchModelsByCategory(ModelsApiService.getListByCategory, 100),
111
+ ]);
112
+
113
+ const isChipsShow = computed(() => !disallowedSlugs.some((item) => slug.value.includes(item)));
114
+
115
+ const relatedSearchRaw = computed(() => {
116
+ if (!isChipsShow.value) return null;
117
+ return relatedData.value || null;
118
+ });
119
+
120
+ const modelsRoundCards = computed(() =>
121
+ modelsByCategory.value?.items.map(convertModelToRoundCard) || []
122
+ );
123
+
124
+ const categoryInfoResult = computed(() => ({
125
+ title: categoryInfo.value?.header,
126
+ description: categoryInfo.value?.description,
127
+ videosCount: categoryInfo.value?.videosCount,
128
+ guid: categoryInfo.value?.guid,
129
+ miniCardTitle: categoryInfo.value?.title,
130
+ } as ICardInfo));
131
+
132
+ const relatedSearch = computed(() => relatedSearchRaw.value?.items?.map(item => ({
133
+ title: item.query,
134
+ value: item.query,
135
+ prefix: 'search',
136
+ })) || []);
137
+
138
+ const relatedCategories = computed(() => categoryInfo.value?.related);
139
+
140
+ const title = computed(() => categoryInfo.value?.title || '');
141
+ const metaTitle = computed(() => categoryInfo.value?.metaTitle || '');
142
+ const brandName = computed(() => useAppConfig().brandName);
143
+ const pageNumber = computed(() => route.query?.[ 'page' ] ? Number(route.query[ 'page' ]) : 1);
144
+ const pageText = computed(() => unref(pageNumber) === 1 ? '' : ` ${t('page')} ${unref(pageNumber)}`);
145
+
146
+ watchEffect(() => {
147
+ const categoryTitle = `${metaTitle.value}${pageText.value} | ${brandName.value}`;
148
+ const categoryDescription = `${categoryInfo.value?.metaDescription || ''}${pageText.value}`;
149
+
150
+ useHead({
151
+ title: categoryTitle,
152
+ meta: [
153
+ { name: 'description', content: categoryDescription },
154
+ { property: 'og:title', content: categoryTitle },
155
+ { property: 'og:description', content: categoryDescription },
156
+ { name: 'twitter:title', content: categoryTitle },
157
+ { name: 'twitter:description', content: categoryDescription },
158
+ ],
159
+ });
160
+
161
+ if (
162
+ status.value === 'success' &&
163
+ (!data.value || !categoryInfo.value)
164
+ ) {
165
+ throw createError({ statusCode: 404, statusMessage: 'Not Found' });
166
+ }
167
+ });
168
+
169
+ const isFirstPage = computed(() => !route.query[ 'page' ] || route.query[ 'page' ] === '1');
170
+
171
+ const breadcrumbs = [
172
+ {
173
+ name: `${t('navigation.main')}`,
174
+ to: '/',
175
+ },
176
+ {
177
+ name: `${t('navigation.categories')}`,
178
+ to: '/categories',
179
+ },
180
+ {
181
+ name: title.value,
182
+ },
183
+ ];
184
+ </script>
185
+
186
+ <style lang="scss">
187
+ .category__options {
188
+ margin-bottom: 24px;
189
+ }
190
+
191
+ .category__options:has(.ui-expand-row) {
192
+ margin-bottom: 16px;
193
+ }
194
+
195
+ .category__loading {
196
+ height: 100%;
197
+ min-height: 30vh;
198
+ }
199
+
200
+ .category__models {
201
+ margin-top: 40px;
202
+ }
203
+
204
+ .category__info {
205
+ margin-bottom: 24px;
206
+ }
207
+
208
+ .category__related-loading {
209
+ --size: 20px;
210
+
211
+ height: 24px;
212
+
213
+ @include from-br(sm) {
214
+ height: 32px;
215
+ }
216
+ }
217
+
218
+ .category__models,
219
+ .category__related-searches {
220
+ grid-column: 1/-1;
221
+ grid-row: 2;
222
+ margin-block: 8px;
223
+ }
224
+
225
+ .category__related-searches + .category__models {
226
+ grid-row: 3;
227
+ }
228
+
229
+ .category__top {
230
+ display: grid;
231
+ row-gap: 8px;
232
+ margin-top: auto;
233
+
234
+ @include from-br(sm) {
235
+ row-gap: 16px;
236
+ }
237
+ }
238
+
239
+ .category__related-categories {
240
+ margin-bottom: 24px;
241
+ }
242
+
243
+ .category__pagination {
244
+ margin-top: 32px;
245
+ }
246
+ </style>
@@ -0,0 +1,100 @@
1
+ <template>
2
+ <div class="all _page _container">
3
+ <UiBreadcrumbs
4
+ :items="breadcrumbs"
5
+ class="all__breadcrumbs _breadcrumbs"
6
+ />
7
+ <UiSectionTitle
8
+ :title="h1"
9
+ :count="totalCount"
10
+ class="_title"
11
+ icon="grid"
12
+ />
13
+
14
+ <div class="_nav-filter">
15
+ <CategoryNav
16
+ v-if="categoriesGroupByLetterChips"
17
+ :items="categoriesGroupByLetterChips as IChipsItem[]"
18
+ />
19
+ <div
20
+ v-else
21
+ class="categories__loading _loading"
22
+ />
23
+ </div>
24
+
25
+ <LazyCategoryByLetter
26
+ v-if="categoriesGroupByLetter"
27
+ :categories="categoriesGroupByLetter as Record<string,ICategoryCard[]>"
28
+ hydrate-on-visible
29
+ />
30
+
31
+ <LazySectionSeoText
32
+ :text="t('all_footer_text')"
33
+ links-link="all_footer_links"
34
+ hydrate-on-visible
35
+ />
36
+
37
+ <section
38
+ v-if="dataModels"
39
+ class="all__popular-models"
40
+ >
41
+ <LazySectionModels
42
+ :data-items="dataModels.items"
43
+ :title="t('categories_popular_models')"
44
+ hydrate-on-visible
45
+ />
46
+ </section>
47
+ </div>
48
+ </template>
49
+
50
+ <script setup lang="ts">
51
+ import type { IBreadcrumbItem, ICategoryCard, IChipsItem } from '../../types';
52
+ import { ModelsApiService } from '../../services/api/models.service';
53
+ import { CategoriesApiService } from '../../services/api/categories.service';
54
+ import { AsyncData } from '../../runtime';
55
+
56
+ const pageName = useRoute().name;
57
+
58
+ const { t } = useI18n();
59
+
60
+ const [
61
+ { data: dataModels },
62
+ { data: categoriesGroupByLetterChips },
63
+ { data: categoriesGroupByLetter },
64
+ ] = await Promise.all([
65
+ useFetchTopModels(ModelsApiService.getTop, 25, AsyncData.TopModels, String(pageName)),
66
+ useFetchCategoriesGroupByLetter(AsyncData.GroupsCategoriesByLetterChips, CategoriesApiService.getListGroupedByLetterChips),
67
+ useFetchCategoriesGroupByLetter(AsyncData.GroupsCategoriesByLetter, CategoriesApiService.getListGroupedByLetter),
68
+ ]);
69
+
70
+ const pagePureName = useGetPureRouteName();
71
+ const { meta, h1 } = useMeta(t, pagePureName, useAppConfig().brandName);
72
+
73
+ watchEffect(() => {
74
+ useHead(meta.value);
75
+ });
76
+
77
+ const totalCount = computed(() =>
78
+ Object.values(categoriesGroupByLetter.value || {}).reduce((sum, arr) => sum + arr.length, 0)
79
+ );
80
+
81
+ const breadcrumbs = [
82
+ {
83
+ name: `${t('navigation.main')}`,
84
+ to: '/',
85
+ },
86
+ {
87
+ name: `${t('navigation.categories')}`,
88
+ to: '/categories',
89
+ },
90
+ {
91
+ name: `${t('all_categories')}`,
92
+ },
93
+ ] as IBreadcrumbItem[];
94
+ </script>
95
+
96
+ <style lang="scss">
97
+ .all__popular-models {
98
+ margin-top: 32px;
99
+ }
100
+ </style>
@@ -0,0 +1,180 @@
1
+ <template>
2
+ <div class="grouped _page _container">
3
+ <UiBreadcrumbs
4
+ :items="breadcrumbs"
5
+ class="_breadcrumbs"
6
+ />
7
+
8
+ <UiSectionTitle
9
+ :title="h1"
10
+ :count="totalCount"
11
+ class="all__title _title"
12
+ icon="grid"
13
+ />
14
+
15
+ <div class="_nav-filter">
16
+ <CategoryNav
17
+ v-if="categoriesGroupByLetter"
18
+ :items="categoriesGroupByLetter as IChipsItem[]"
19
+ hide-on-mobile
20
+ />
21
+ <div
22
+ v-else
23
+ class="categories__loading _loading"
24
+ />
25
+ </div>
26
+
27
+ <div
28
+ v-if="groupsCategories && groupsCategories.length > 0"
29
+ class="grouped__category-wrapper"
30
+ >
31
+ <CategoryGroup
32
+ v-for="(item, index) in groupsCategories"
33
+ :key="`group-category-${index}`"
34
+ :items="item?.categories"
35
+ :title="item.title"
36
+ :icon="item.icon"
37
+ class="grouped__category-section"
38
+ />
39
+ <UiLink
40
+ :to="generateLink('/categories/all')"
41
+ class="grouped__category-all"
42
+ theme="secondary"
43
+ >{{ t('show_all') }}
44
+ </UiLink>
45
+ </div>
46
+
47
+ <SectionSeoText
48
+ :text="t('grouped_footer_text')"
49
+ links-link="grouped_footer_links"
50
+ />
51
+ </div>
52
+ </template>
53
+
54
+ <script setup lang="ts">
55
+ import type { IBreadcrumbItem, IChipsItem } from '../../types';
56
+ import { CategoriesApiService } from '../../services/api/categories.service';
57
+ import { AsyncData } from '../../runtime';
58
+
59
+ const [
60
+ { data: categoriesGroupByLetter },
61
+ { data: groupsCategories },
62
+ ] = await Promise.all([
63
+ useFetchCategoriesGroupByLetter(AsyncData.GroupsCategoriesByLetterChips, CategoriesApiService.getListGroupedByLetterChips),
64
+ useFetchGroupsCategories(CategoriesApiService.getGroupsCategories),
65
+ ]);
66
+
67
+ const { t } = useI18n();
68
+
69
+ const { generateLink } = useGenerateLink();
70
+
71
+ const pagePureName = useGetPureRouteName();
72
+ const { meta, h1 } = useMeta(t, pagePureName, useAppConfig().brandName);
73
+
74
+ watchEffect(() => {
75
+ useHead(meta.value);
76
+ });
77
+
78
+ const totalCount = computed(() =>
79
+ groupsCategories.value?.reduce((sum, item) => sum + (item.categories?.length || 0), 0) || 0
80
+ );
81
+
82
+ const breadcrumbs = [
83
+ {
84
+ name: `${t('navigation.main')}`,
85
+ to: '/',
86
+ },
87
+ {
88
+ name: `${t('categories')}`,
89
+ },
90
+ ] as IBreadcrumbItem[];
91
+ </script>
92
+
93
+ <style lang="scss">
94
+ .grouped__popular {
95
+ display: grid;
96
+ row-gap: 8px;
97
+
98
+ @include from-br(sm) {
99
+ row-gap: 16px;
100
+ }
101
+ }
102
+
103
+ .grouped__popular-models {
104
+ display: flex;
105
+ column-gap: 4px;
106
+ align-items: center;
107
+ }
108
+
109
+ .grouped__popular-highlight {
110
+ @include from-br(sm) {
111
+ padding-right: 4px;
112
+ border-right: 1px solid $grouped-highlight-border;
113
+ }
114
+ }
115
+
116
+ .grouped__options-separate {
117
+ display: block;
118
+ width: 1px;
119
+ height: 24px;
120
+ background-color: $grouped-separator;
121
+
122
+ @include from-br(sm) {
123
+ height: 32px;
124
+ }
125
+ }
126
+
127
+ .grouped__loading {
128
+ --size: 24px;
129
+
130
+ min-height: 32px;
131
+ }
132
+
133
+ .grouped__category-icon {
134
+ color: $grouped-category-icon;
135
+ }
136
+
137
+ .grouped__category-wrapper {
138
+ display: grid;
139
+ row-gap: 16px;
140
+ }
141
+
142
+ .grouped__category-all {
143
+ justify-self: center;
144
+ margin-block: 16px;
145
+
146
+ @include from-br(sm) {
147
+ grid-column: 1/-1;
148
+ }
149
+ }
150
+
151
+ .grouped__footer-text {
152
+ @include font-sm;
153
+
154
+ color: $grouped-footer-text;
155
+ margin-top: 32px;
156
+ }
157
+
158
+ .grouped__category-section {
159
+ padding-bottom: 16px;
160
+ border-bottom: 1px solid $grouped-section-border;
161
+ }
162
+
163
+ .grouped__category-section:first-child {
164
+ padding-top: 0;
165
+ }
166
+
167
+ .grouped__category-section:last-child {
168
+ border-bottom: none;
169
+ }
170
+
171
+ .grouped__footer-models {
172
+ display: grid;
173
+ row-gap: 8px;
174
+ padding-top: 8px;
175
+
176
+ @include from-br(sm) {
177
+ row-gap: 16px;
178
+ }
179
+ }
180
+ </style>