itube-specs 0.0.195

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 (169) hide show
  1. package/README.md +121 -0
  2. package/components/cards/f-video-mini-card.vue +49 -0
  3. package/components/grids/f-grid-categories.vue +20 -0
  4. package/components/grids/f-grid-channels.vue +23 -0
  5. package/components/grids/f-grid-models.vue +25 -0
  6. package/components/grids/f-grid-playlists.vue +21 -0
  7. package/components/grids/f-grid-videos.vue +33 -0
  8. package/components/page-components/f-breadcrumbs.vue +44 -0
  9. package/components/page-components/f-chips-panel.vue +101 -0
  10. package/components/page-components/f-pagination.vue +206 -0
  11. package/components/page-components/f-report.vue +221 -0
  12. package/components/page-components/f-share.vue +96 -0
  13. package/components/page-components/f-sort.vue +57 -0
  14. package/components/page-components/f-videos-title.vue +20 -0
  15. package/components/ui/f-button.vue +50 -0
  16. package/components/ui/f-checkbox.vue +55 -0
  17. package/components/ui/f-chips.vue +116 -0
  18. package/components/ui/f-count.vue +12 -0
  19. package/components/ui/f-country.vue +26 -0
  20. package/components/ui/f-dropdown.vue +122 -0
  21. package/components/ui/f-icon.vue +19 -0
  22. package/components/ui/f-img.vue +46 -0
  23. package/components/ui/f-input.vue +162 -0
  24. package/components/ui/f-label.vue +20 -0
  25. package/components/ui/f-link.vue +33 -0
  26. package/components/ui/f-model-tag.vue +28 -0
  27. package/components/ui/f-notification.vue +77 -0
  28. package/components/ui/f-popup.vue +136 -0
  29. package/components/ui/f-radio.vue +56 -0
  30. package/components/ui/f-select.vue +88 -0
  31. package/components/ui/f-slider.vue +55 -0
  32. package/components/ui/f-snackbar.vue +47 -0
  33. package/components/ui/f-timestamp.vue +51 -0
  34. package/components/ui/f-toggle.vue +29 -0
  35. package/composables/use-antiadblock-domains.ts +20 -0
  36. package/composables/use-auth-popup.ts +25 -0
  37. package/composables/use-convert-query-categories.ts +7 -0
  38. package/composables/use-generate-link.ts +30 -0
  39. package/composables/use-get-pure-route-name.ts +5 -0
  40. package/composables/use-get-videos-filter-request.ts +30 -0
  41. package/composables/use-meta.ts +42 -0
  42. package/composables/use-playlist-edit.ts +36 -0
  43. package/composables/use-report-popup.ts +21 -0
  44. package/composables/use-seo-links.ts +87 -0
  45. package/composables/use-share-popup.ts +23 -0
  46. package/composables/use-snackbar.ts +52 -0
  47. package/composables/use-test-composable.ts +3 -0
  48. package/lib/alphabet-items.ts +2 -0
  49. package/lib/contact-forms-scheme.ts +98 -0
  50. package/lib/contacts/report-issue-items.ts +5 -0
  51. package/lib/contacts/report-malware-items.ts +6 -0
  52. package/lib/contacts/report-reasons-items.ts +12 -0
  53. package/lib/contacts/report-wrong-items.ts +6 -0
  54. package/lib/index.ts +7 -0
  55. package/lib/report-forms-scheme.ts +205 -0
  56. package/nuxt.config.ts +20 -0
  57. package/package.json +53 -0
  58. package/runtime/enums/async-data.ts +48 -0
  59. package/runtime/enums/auth-step.ts +5 -0
  60. package/runtime/enums/contacts-subjects.ts +7 -0
  61. package/runtime/enums/languages.ts +9 -0
  62. package/runtime/enums/niche.ts +6 -0
  63. package/runtime/enums/playlist-step.ts +5 -0
  64. package/runtime/enums/playlist-type.ts +4 -0
  65. package/runtime/enums/report-forms-subjects.ts +7 -0
  66. package/runtime/index.ts +51 -0
  67. package/runtime/utils/cleaners/clean-category-card.ts +9 -0
  68. package/runtime/utils/cleaners/clean-category-info.ts +9 -0
  69. package/runtime/utils/cleaners/clean-channel-card.ts +12 -0
  70. package/runtime/utils/cleaners/clean-channel-info.ts +13 -0
  71. package/runtime/utils/cleaners/clean-model-card.ts +9 -0
  72. package/runtime/utils/cleaners/clean-model-info.ts +11 -0
  73. package/runtime/utils/cleaners/clean-playlist-card.ts +16 -0
  74. package/runtime/utils/cleaners/clean-playlist-data.ts +15 -0
  75. package/runtime/utils/cleaners/clean-playlist-video.ts +12 -0
  76. package/runtime/utils/cleaners/clean-profile-data.ts +11 -0
  77. package/runtime/utils/cleaners/clean-user-playlists-card.ts +11 -0
  78. package/runtime/utils/cleaners/clean-video-card.ts +19 -0
  79. package/runtime/utils/cleaners/clean-video-data.ts +27 -0
  80. package/runtime/utils/compress-image.ts +27 -0
  81. package/runtime/utils/converters/convert-categories-to-chips.ts +13 -0
  82. package/runtime/utils/converters/convert-categories-to-footer.ts +11 -0
  83. package/runtime/utils/converters/convert-date-to-timestamp.ts +37 -0
  84. package/runtime/utils/converters/convert-model-card-to-chips.ts +13 -0
  85. package/runtime/utils/converters/convert-string.ts +56 -0
  86. package/runtime/utils/converters/group-categories-by-first-letter.ts +24 -0
  87. package/runtime/utils/converters/group-objects-by-first-letter.ts +16 -0
  88. package/runtime/utils/format-date.ts +12 -0
  89. package/runtime/utils/format-number.ts +12 -0
  90. package/runtime/utils/format-time-ago.ts +21 -0
  91. package/runtime/utils/get-duration.ts +17 -0
  92. package/runtime/utils/get-month.ts +22 -0
  93. package/runtime/utils/get-multiple-query.ts +26 -0
  94. package/runtime/utils/get-selected-query.ts +6 -0
  95. package/runtime/utils/is-mobile.ts +15 -0
  96. package/runtime/utils/normalize-url.ts +43 -0
  97. package/runtime/utils/on-backdrop-click.ts +5 -0
  98. package/runtime/utils/scroll-lock.ts +28 -0
  99. package/runtime/utils/server/abort-controller.ts +14 -0
  100. package/runtime/utils/server/api-helper.ts +41 -0
  101. package/runtime/utils/server/get-url-with-proxied-params.ts +6 -0
  102. package/runtime/utils/server/parse-api-error.ts +14 -0
  103. package/runtime/utils/server/server-api-helper.ts +28 -0
  104. package/runtime/utils/validate-email.ts +4 -0
  105. package/runtime/utils/validate-password.ts +3 -0
  106. package/runtime/utils/validate-phone.ts +4 -0
  107. package/runtime/utils/validate-username.ts +4 -0
  108. package/runtime/utils/video-data-add-model-icon.ts +20 -0
  109. package/runtime/utils/vtt-helper.ts +86 -0
  110. package/types/authorization-forms.d.ts +16 -0
  111. package/types/breadcrumb-item.d.ts +4 -0
  112. package/types/button-sizes.d.ts +1 -0
  113. package/types/button-themes.d.ts +1 -0
  114. package/types/card-info.d.ts +22 -0
  115. package/types/category-card.d.ts +8 -0
  116. package/types/change-email-form.d.ts +3 -0
  117. package/types/change-password-form.d.ts +4 -0
  118. package/types/channel-card.d.ts +10 -0
  119. package/types/chips-item.d.ts +8 -0
  120. package/types/contacts-form.d.ts +10 -0
  121. package/types/contacts-scheme.d.ts +14 -0
  122. package/types/country.d.ts +5 -0
  123. package/types/css-breakpoints.d.ts +1 -0
  124. package/types/filter-scheme.d.ts +37 -0
  125. package/types/fluid-player.d.ts +226 -0
  126. package/types/gender.d.ts +5 -0
  127. package/types/group-categories.d.ts +15 -0
  128. package/types/index.d.ts +59 -0
  129. package/types/input-types.d.ts +1 -0
  130. package/types/link-item.d.ts +6 -0
  131. package/types/model-card.d.ts +7 -0
  132. package/types/model-filter-payload.d.ts +4 -0
  133. package/types/model-filter.d.ts +15 -0
  134. package/types/model-group.d.ts +5 -0
  135. package/types/model-tag.d.ts +5 -0
  136. package/types/multi-suggest.d.ts +105 -0
  137. package/types/navigation-items.d.ts +10 -0
  138. package/types/paginated-response.d.ts +8 -0
  139. package/types/parameter-model.d.ts +14 -0
  140. package/types/playlist-card.d.ts +16 -0
  141. package/types/playlist-data.d.ts +15 -0
  142. package/types/playlist-info-type.d.ts +28 -0
  143. package/types/playlist-video-form.d.ts +9 -0
  144. package/types/profile-data.d.ts +9 -0
  145. package/types/raw/raw-category-card.d.ts +23 -0
  146. package/types/raw/raw-category-info.d.ts +23 -0
  147. package/types/raw/raw-channel-card.d.ts +29 -0
  148. package/types/raw/raw-channel-info.d.ts +29 -0
  149. package/types/raw/raw-model-card.d.ts +53 -0
  150. package/types/raw/raw-model-info.d.ts +54 -0
  151. package/types/raw/raw-playlist-card.d.ts +27 -0
  152. package/types/raw/raw-playlist-data.d.ts +29 -0
  153. package/types/raw/raw-playlist-user.d.ts +24 -0
  154. package/types/raw/raw-playlist-video.d.ts +18 -0
  155. package/types/raw/raw-profile-data.d.ts +22 -0
  156. package/types/raw/raw-video-card.d.ts +78 -0
  157. package/types/raw/raw-video-data.d.ts +53 -0
  158. package/types/recovery-password-form.d.ts +4 -0
  159. package/types/related-phrases.d.ts +6 -0
  160. package/types/report-form.d.ts +9 -0
  161. package/types/report-scheme.d.ts +21 -0
  162. package/types/request-filters.d.ts +13 -0
  163. package/types/request-pagination.d.ts +5 -0
  164. package/types/search-top-models.d.ts +6 -0
  165. package/types/select-item.d.ts +10 -0
  166. package/types/tab-item.d.ts +6 -0
  167. package/types/thumbs-urls.d.ts +13 -0
  168. package/types/video-card.d.ts +18 -0
  169. package/types/video-data.d.ts +36 -0
package/README.md ADDED
@@ -0,0 +1,121 @@
1
+ Порядок действий:
2
+ 1) `npm login` = логинимся к npm (bitbucket);
3
+ 2) `npm version patch` - патчит версию (можно и минорить или мажорить по ситуации);
4
+ 3) `npm publish --access public` - публикуем версию (или сразу `npm run public` - обьединяет два действия);
5
+ 4) ставим пакет в проекте;
6
+ 5) проекте самом в `nuxt.config.ts` добавляем;
7
+ ```bash
8
+ extends: [
9
+ process.env.LAYER_PATH || '@test/test'
10
+ ],
11
+ ```
12
+ 6) Если не первый раз то обновляем пакет `npm install @test/test@latest`, если пакет уже установлен можно в ручную поменять версию и `npm install` (если пакет недавно обновился, сразу npm install может не подхватить последнюю версию);
13
+ 7) `npx nuxi prepare`
14
+ 8) если есть проблемы с runtime запустить в пакете npm run dev он перегенерирует tsconfig и eslint.config.mjs
15
+ 9) и запускаем либо `npm run dev`
16
+
17
+ `nuxt.config.ts`
18
+
19
+ ```bash
20
+ import { fileURLToPath } from 'node:url'
21
+ import { defineNuxtConfig } from 'nuxt/config'
22
+
23
+ export default defineNuxtConfig({
24
+ modules: ['@nuxt/eslint'],
25
+ eslint: {
26
+ config: {
27
+ // Use the generated ESLint config for lint root project as well
28
+ rootDir: fileURLToPath(new URL('..', import.meta.url))
29
+ }
30
+ },
31
+ components: {
32
+ dirs: [
33
+ {
34
+ path: './components',
35
+ pathPrefix: false
36
+ }
37
+ ]
38
+ }
39
+ })
40
+ ```
41
+
42
+ `package.json` - версии nuxt && vue должны быть идентичны проекту
43
+ ```bash
44
+ {
45
+ "name": "@test/test", // свое
46
+ "type": "module",
47
+ "version": "0.0.1",
48
+ "main": "./nuxt.config.ts",
49
+ "types": "./types/index.d.ts",
50
+ "exports": {
51
+ ".": {
52
+ "types": "./types/index.d.ts",
53
+ "import": "./nuxt.config.ts",
54
+ "default": "./nuxt.config.ts"
55
+ },
56
+ "./types": {
57
+ "types": "./types/index.d.ts",
58
+ "default": "./types/index.d.ts"
59
+ },
60
+ "./runtime": {
61
+ "import": "./runtime/index.ts",
62
+ "default": "./runtime/index.ts"
63
+ },
64
+ "./*": "./*"
65
+ },
66
+ "files": [
67
+ "components/",
68
+ "composables/",
69
+ "types/",
70
+ "nuxt.config.ts"
71
+ ],
72
+ "devDependencies": {
73
+ "@nuxt/eslint": "latest",
74
+ "@types/node": "^20.19.19",
75
+ "eslint": "^9.37.0",
76
+ "nuxt": "3.17.6",
77
+ "typescript": "^5.9.3",
78
+ "vue": "3.5.17"
79
+ }
80
+ }
81
+ ```
82
+ Импорт typescript типов осуществляется за счет этих опций
83
+ ```
84
+ "types": "./types/index.d.ts",
85
+ "exports": {
86
+ ".": {
87
+ "types": "./types/index.d.ts",
88
+ "import": "./nuxt.config.ts",
89
+ "default": "./nuxt.config.ts"
90
+ },
91
+ "./types": {
92
+ "types": "./types/index.d.ts",
93
+ "default": "./types/index.d.ts"
94
+ },
95
+ "./runtime": {
96
+ "import": "./runtime/index.ts",
97
+ "default": "./runtime/index.ts"
98
+ },
99
+ "./*": "./*"
100
+ },
101
+ ```
102
+ Где:
103
+ - "types" указывает TypeScript, где лежит корневой файл с типами;
104
+ - "exports" сообщает Node и TS, какие модули доступны для импорта;
105
+ - "./types" позволяет писать коротко:
106
+
107
+ ```
108
+ import type { IUser } from '@test/test/types'
109
+ ```
110
+ Типы не импортируются автоматически, каждый тип в свою очередь экспортируется в index.d.ts (обязательно указывать расширение при эскпорте d.ts) общий файл откуда уже и берутся все типы в проект.
111
+
112
+ `tsconfig.json`
113
+
114
+ ```bash
115
+ {
116
+ "extends": "./.nuxt/tsconfig.json",
117
+ "compilerOptions": {
118
+ "types": ["node"]
119
+ }
120
+ }
121
+ ```
@@ -0,0 +1,49 @@
1
+ <template>
2
+ <component
3
+ class="f-video-mini-card"
4
+ :is="component"
5
+ :title="card.title"
6
+ :to="link ? generateLink(`/playlists/${prefix}/${card.id}`) : null"
7
+ >
8
+ <div class="f-video-mini-card__img-wrapper">
9
+ <FImg
10
+ class="f-video-mini-card__img"
11
+ sizes="96px"
12
+ :src="card.thumbUrl"
13
+ width="96"
14
+ height="64"
15
+ :alt="card.title"
16
+ />
17
+ <FLabel
18
+ class="f-video-mini-card__duration"
19
+ theme="black"
20
+ :text="duration"
21
+ ></FLabel>
22
+ </div>
23
+ <p class="f-video-mini-card__description _truncate">{{ card.title }}</p>
24
+ </component>
25
+ </template>
26
+
27
+ <script setup lang="ts">
28
+ import type { IVideoCard } from '../../types';
29
+ import { getDuration } from '../../runtime';
30
+
31
+ const props = defineProps<{
32
+ card: IVideoCard
33
+ link?: boolean
34
+ prefix?: string
35
+ }>()
36
+
37
+ const duration = computed(() => {
38
+ return getDuration(props.card.duration)
39
+ })
40
+
41
+ const component = computed(() => {
42
+ if (props.link) {
43
+ return resolveComponent('NuxtLink');
44
+ }
45
+ return 'div';
46
+ })
47
+
48
+ const { generateLink } = useGenerateLink();
49
+ </script>
@@ -0,0 +1,20 @@
1
+ <template>
2
+ <div class="f-grid-categories">
3
+ <FCategoryCard
4
+ v-for="(item, index) in categories"
5
+ :card="item"
6
+ :key="item.guid"
7
+ :priority="priority && index === 0"
8
+ />
9
+ <slot></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>
@@ -0,0 +1,23 @@
1
+ <template>
2
+ <div
3
+ class="f-grid-channels"
4
+ id="anchor"
5
+ >
6
+ <FChannelCard
7
+ class="f-grid-channels__card"
8
+ v-for="(item, index) in items"
9
+ :card="item"
10
+ :key="item.guid"
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>
@@ -0,0 +1,25 @@
1
+ <template>
2
+ <div
3
+ class="f-grid-models"
4
+ :class="{'--footer': footer}"
5
+ >
6
+ <FModelCard
7
+ v-for="(item, index) in items"
8
+ :key="`model-card-${item.guid}`"
9
+ class="f-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>
@@ -0,0 +1,21 @@
1
+ <template>
2
+ <div class="f-grid-playlists">
3
+ <FPlaylistCard
4
+ v-for="(item, index) in items"
5
+ :card="item"
6
+ :key="`user-playlist-${index}`"
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>
@@ -0,0 +1,33 @@
1
+ <template>
2
+ <div
3
+ class="f-grid-videos"
4
+ :class="[
5
+ {'--first-hidden': hideFirstRow},
6
+ ]"
7
+ >
8
+ <FVideoCard
9
+ v-for="(item, index) in items"
10
+ class="f-grid-videos__card"
11
+ :card="item"
12
+ :priority="priority && index === 0"
13
+ :loading="index > 3 || related ? 'lazy' : 'eager'"
14
+ :key="`video-${item.guid}`"
15
+ :top-chips="topChips"
16
+ :playlist-id="playlistId"
17
+ />
18
+ <slot></slot>
19
+ </div>
20
+ </template>
21
+
22
+ <script setup lang="ts">
23
+ import type { IVideoCard } from '../../types';
24
+
25
+ defineProps<{
26
+ items: Array<IVideoCard>
27
+ hideFirstRow?: boolean
28
+ priority?: boolean
29
+ related?: boolean
30
+ playlistId?: string
31
+ topChips?: string[]
32
+ }>()
33
+ </script>
@@ -0,0 +1,44 @@
1
+ <template>
2
+ <nav class="f-breadcrumbs" aria-label="Breadcrumb" role="navigation" itemscope itemtype="https://schema.org/BreadcrumbList">
3
+ <ol>
4
+ <li
5
+ v-for="(item, index) in resultItems"
6
+ :key="`breadcrumb-${index}`"
7
+ itemscope
8
+ itemprop="itemListElement"
9
+ itemtype="https://schema.org/ListItem"
10
+ >
11
+ <NuxtLink
12
+ :to="generateLink(item.to || '/')"
13
+ class="f-breadcrumbs__item"
14
+ itemprop="item"
15
+ >
16
+ <span itemprop="name" v-html="item.name" />
17
+ </NuxtLink>
18
+ <meta itemprop="position" :content="String(index + 1)" />
19
+ </li>
20
+ <li
21
+ itemscope
22
+ itemprop="itemListElement"
23
+ itemtype="https://schema.org/ListItem"
24
+ >
25
+ <span class="f-breadcrumbs__item" aria-current="page" itemprop="name" v-html="items[items.length - 1].name" />
26
+ <meta itemprop="position" :content="String(items.length)" />
27
+ </li>
28
+ </ol>
29
+ </nav>
30
+ </template>
31
+
32
+ <script setup lang="ts">
33
+ import type { IBreadcrumbItem } from '../../types';
34
+
35
+ const props = defineProps<{
36
+ items: IBreadcrumbItem[]
37
+ }>()
38
+
39
+ const { generateLink } = useGenerateLink();
40
+
41
+ const resultItems = computed(() => {
42
+ return props.items.slice(0, props.items.length - 1)
43
+ })
44
+ </script>
@@ -0,0 +1,101 @@
1
+ <template>
2
+ <div
3
+ class="f-chips-panel"
4
+ ref="containerRef"
5
+ :class="[
6
+ { '--expand-show': isExpandOpened },
7
+ { '--alphabet': alphabet },
8
+ { '--mobile-expand': mobileExpand },
9
+ ]"
10
+ >
11
+ <div
12
+ class="f-chips-panel__wrapper"
13
+ :class="[{ '--more-show': isElementsOverflow }]"
14
+ >
15
+ <slot name="prepend"></slot>
16
+ <slot>
17
+ <FChips
18
+ v-for="item in itemsResult"
19
+ :item="item"
20
+ :key="item.title"
21
+ :alphabet="alphabet"
22
+ is-link
23
+ :mini="mini"
24
+ />
25
+ </slot>
26
+ <div
27
+ v-if="!hideMore"
28
+ v-show="isElementsOverflow"
29
+ class="f-chips-panel__more-wrapper"
30
+ :class="{'--small': mini}"
31
+ >
32
+ <FChips
33
+ class="f-chips-panel__button --more"
34
+ :item="{
35
+ title: moreButtonName,
36
+ icon: isExpandOpened ? 'minus' : 'plus',
37
+ }"
38
+ :mini="mini"
39
+ @click="onMoreClick"
40
+ />
41
+ </div>
42
+ </div>
43
+ </div>
44
+ </template>
45
+
46
+ <script setup lang="ts">
47
+ import { IChipsItem } from '@arkadykid/test/types';
48
+
49
+ const props = defineProps<{
50
+ items?: IChipsItem[];
51
+ alphabet?: boolean;
52
+ hideMore?: boolean;
53
+ mini?: boolean;
54
+ mobileExpand?: boolean;
55
+ }>();
56
+
57
+ const { t } = useI18n()
58
+
59
+ const emit = defineEmits<{
60
+ (eventName: 'more', eventValue: boolean): void
61
+ }>()
62
+
63
+ const containerRef = ref();
64
+
65
+ const isExpandOpened = ref(false);
66
+
67
+ const MAXIMUM_BUTTONS = 20; // вычислено на глаз, приблизительно
68
+
69
+ const itemsResult = computed(() => {
70
+ return props.items?.slice(
71
+ 0,
72
+ !props.alphabet && !isExpandOpened.value
73
+ ? MAXIMUM_BUTTONS
74
+ : props.items.length,
75
+ );
76
+ });
77
+
78
+ const moreButtonName = computed(() => isExpandOpened.value ? t('less') : t('more'));
79
+
80
+ const isElementsOverflow = ref(false);
81
+
82
+ function checkIsElementOverflow() {
83
+ isElementsOverflow.value = !!(
84
+ containerRef.value &&
85
+ containerRef.value.scrollHeight > containerRef.value.clientHeight
86
+ );
87
+ }
88
+
89
+ function onMoreClick() {
90
+ isExpandOpened.value = !isExpandOpened.value
91
+ emit('more', isExpandOpened.value)
92
+ }
93
+
94
+ onMounted(() => {
95
+ checkIsElementOverflow();
96
+ });
97
+
98
+ watch(() => useRoute().query, () => {
99
+ setTimeout(() => checkIsElementOverflow(), 100)
100
+ })
101
+ </script>
@@ -0,0 +1,206 @@
1
+ <template>
2
+ <nav
3
+ v-if="length > 1"
4
+ class="f-pagination"
5
+ >
6
+ <div class="f-pagination__items">
7
+
8
+ <template v-if="length <= 6">
9
+ <NuxtLink
10
+ class="f-pagination__item"
11
+ :class="[
12
+ number === activeNumber && 'f-pagination__item--active',
13
+ theme && `f-button --${theme}`
14
+ ]"
15
+ v-for="number in length"
16
+ :key="`pagination-${number}`"
17
+ :to="linkTo(number)"
18
+ >
19
+ {{ number }}
20
+ </NuxtLink>
21
+ </template>
22
+
23
+ <template v-else>
24
+ <NuxtLink
25
+ class="f-pagination__item"
26
+ :class="[
27
+ 1 === activeNumber && 'f-pagination__item--active',
28
+ theme && `f-button --${theme}`
29
+ ]"
30
+ :to="linkTo(1)"
31
+ >1
32
+ </NuxtLink>
33
+ <span
34
+ v-if="activeNumber > 4"
35
+ class="f-pagination__item f-pagination__item--empty"
36
+ :class="[
37
+ theme && `f-button --${theme}`
38
+ ]"
39
+ >...</span>
40
+ <NuxtLink
41
+ v-else
42
+ class="f-pagination__item"
43
+ :class="[
44
+ 2 === activeNumber && 'f-pagination__item--active',
45
+ theme && `f-button --${theme}`
46
+ ]"
47
+ :to="linkTo(2)"
48
+ >2
49
+ </NuxtLink>
50
+ <NuxtLink
51
+ v-for="number in activeNumbers"
52
+ :class="[
53
+ number === activeNumber && 'f-pagination__item--active',
54
+ theme && `f-button --${theme}`
55
+ ]"
56
+ class="f-pagination__item"
57
+ :key="`pagination-${number}`"
58
+ :to="linkTo(number)"
59
+ >
60
+ {{ number }}
61
+ </NuxtLink>
62
+ <span
63
+ v-if="activeNumber < length - 3"
64
+ class="f-pagination__item f-pagination__item--empty"
65
+ :class="[
66
+ theme && `f-button --${theme}`
67
+ ]"
68
+ >...</span>
69
+ <NuxtLink
70
+ v-else
71
+ class="f-pagination__item"
72
+ :class="[
73
+ length - 1 === activeNumber && 'f-pagination__item--active',
74
+ theme && `f-button --${theme}`
75
+ ]"
76
+ :to="linkTo(length - 1)"
77
+ >{{ length - 1 }}
78
+ </NuxtLink>
79
+ <NuxtLink
80
+ class="f-pagination__item"
81
+ :class="[
82
+ length === activeNumber && 'f-pagination__item--active',
83
+ theme && `f-button --${theme}`
84
+ ]"
85
+ :to="linkTo(length)"
86
+ >
87
+ {{ length }}
88
+ </NuxtLink>
89
+ </template>
90
+ </div>
91
+ <FLink
92
+ wide
93
+ class="f-pagination__prev"
94
+ :to="linkTo(previousValue)"
95
+ :class="{'--disabled': activeNumber <= 1}"
96
+ >
97
+ <FIcon
98
+ v-if="arrowIcon"
99
+ name="arrow-left"
100
+ size="24"
101
+ />
102
+ {{ t('previous' )}}
103
+ </FLink>
104
+ <FLink
105
+ wide
106
+ class="f-pagination__next"
107
+ :class="{'--disabled': activeNumber >= length}"
108
+ :to="linkTo(nextValue)"
109
+ >
110
+ {{ t('next') }}
111
+ <FIcon
112
+ v-if="arrowIcon"
113
+ name="arrow-right"
114
+ size="24"
115
+ />
116
+ </FLink>
117
+ </nav>
118
+ </template>
119
+
120
+ <script setup lang="ts">
121
+ // не забыват проставлять key на компоненте для переинициализации и отслеживания props.total чтобы пересчитывалось правильное сео
122
+ import type { ButtonThemes } from '../../types';
123
+
124
+ const props = withDefaults(defineProps<{
125
+ prefix?: string
126
+ theme?: ButtonThemes
127
+ total: number
128
+ perPage?: number | string
129
+ limit?: number
130
+ arrowIcon?: boolean
131
+ }>(), {
132
+ prefix: 'page',
133
+ perPage: 48,
134
+ })
135
+
136
+ const {t} = useI18n();
137
+ const route = useRoute()
138
+
139
+ const length = computed(() => {
140
+ return props.limit ? Math.min(props.total, props.limit) : props.total;
141
+ })
142
+
143
+ const activeNumber = computed(() => {
144
+ const n = Number(route.query[props.prefix]);
145
+ return !n || n < 1 ? 1 : n;
146
+ })
147
+
148
+ const previousValue = computed(() => {
149
+ return activeNumber.value > 1 ? activeNumber.value - 1 : activeNumber.value
150
+ })
151
+
152
+ const nextValue = computed(() => {
153
+ return activeNumber.value !== length.value ? activeNumber.value + 1 : activeNumber.value
154
+ })
155
+
156
+ const activeNumbers = computed(() => {
157
+ if (activeNumber.value < 5) {
158
+ return [3, 4, 5]
159
+ } else if (activeNumber.value > length.value - 3) {
160
+ return [length.value - 4, length.value - 3, length.value - 2]
161
+ }
162
+ return [activeNumber.value - 1, activeNumber.value, activeNumber.value + 1]
163
+ })
164
+
165
+ watchEffect(() => {
166
+ if (activeNumber.value > length.value && props.total > 0) {
167
+ navigateTo(linkTo(length.value));
168
+ }
169
+ })
170
+
171
+ function linkTo(value: string | number) {
172
+ const safeValue = Math.max(1, Number(value));
173
+
174
+ // Если это первая страница — ведём в корень без query
175
+ if (safeValue === 1) {
176
+ return {
177
+ path: route.path
178
+ }
179
+ }
180
+
181
+ return {
182
+ query: {
183
+ ...route.query,
184
+ [props.prefix]: safeValue
185
+ }
186
+ }
187
+ }
188
+
189
+ watch(() => route.query['page'], (value) => {
190
+ if (value) {
191
+ document.documentElement.querySelector('#anchor')?.scrollIntoView({ behavior: 'smooth' });
192
+ }
193
+ })
194
+
195
+ const i18n = useI18n();
196
+ const localePath = useLocalePath();
197
+ const siteUrl = useAppConfig().siteUrl as string;
198
+ const { canonicalUrl, alternateLinks } = useSeoLinks(siteUrl, props.total === 1, i18n, localePath);
199
+
200
+ useHead({
201
+ link: computed(() => [
202
+ { rel: 'canonical', href: canonicalUrl.value, key: `canonical-${route.path}` },
203
+ ...alternateLinks.value,
204
+ ]),
205
+ });
206
+ </script>