itube-specs 0.0.1052 → 0.0.1053

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/SITE_UPDATES.md CHANGED
@@ -11,6 +11,21 @@
11
11
  коммиты до бампа); `npm run patch` сам проставит ей номер версии. Формат пункта: что сделать и где,
12
12
  кому именно (всем / сайтам с N), что будет, если не сделать.
13
13
 
14
+ ## 0.0.1053 — 2026-10-26
15
+
16
+ - **Всем — к сведению:** нативка в конце сеток листингов и похожих (`AdvNativeBottom` в `view/video`, `view/videos`,
17
+ `view/model-videos`, страницах категории, канала, поиска) больше не ленивая. Контейнер приезжает в серверной
18
+ разметке, и сеть заполняет его до гидрации, а ленивая гидрация пересобирала уже заполненный iframe — креатив
19
+ терялся, слот оставался пустым (найдено на hdsex-remaster). Делать ничего не надо.
20
+ - **Сайтам со своими сортировками на страницах на page-composables:** у `useCategoryPage`, `useChannelPage`,
21
+ `useModelPage` новая опция `sortOptions` (по умолчанию `sortItemsDefault`) — по ней ключи меты и проверка `?sort`.
22
+ Без неё свои значения `?sort` такой страницы — 404. Образец — коммит hdsex-remaster с этим изменением.
23
+ - **Сайтам с частотным капом преролла (hdsex-remaster):** в `usePlayerSiteHooks()` новый хук `onAdStart()` — старт
24
+ рекламы в плеере (например, поставить куку капа). Остальным — ничего.
25
+ - **Сайтам с боковой колонкой моделей у плеера (hdsex-remaster):** `appConfig.videoPage.hideAsideModelChips: true` —
26
+ с 1024px не дублировать чипсой над плеером модель, у которой есть блок в колонке. По умолчанию `false`, ничего не
27
+ меняется.
28
+
14
29
  ## 0.0.1052 — 2026-10-22
15
30
 
16
31
  - **Всем — к сведению:** мусорные адреса листингов теперь 404. `?page` не натуральное число (`0`, `-1`, `abc`, `01`) —
package/app.config.ts CHANGED
@@ -39,6 +39,8 @@ export default defineAppConfig({
39
39
  webcamsBlock: false,
40
40
  /** Сколько моделей из хвоста списка уходит во врезки похожих, когда блок Live Cams включён. */
41
41
  webcamInserts: 8,
42
+ /** С 1024px прятать чипсы моделей над плеером, у которых есть блок в боковой колонке (не дублировать ссылку). */
43
+ hideAsideModelChips: false,
42
44
  /** Мета ролика как на старых движках: description — до трёх фраз перехода (недостающие — из тегов), без них —
43
45
  * категории, склейка ключами `meta.videos-slug.tags_and` / `tags_tail`; в title доступен `{thirdText}` — название
44
46
  * в Title Case. Выключено — description из описания ролика. */
@@ -14,6 +14,7 @@
14
14
  lazy
15
15
  load-on="placeholder"
16
16
  @ended="emit('end')"
17
+ @adstart="siteHooks.onAdStart?.()"
17
18
  @advancerequest="onAdvanceRequest"
18
19
  @sourcechange="onSourceChange"
19
20
  @menurequest="onMenuRequest"
@@ -38,10 +38,8 @@
38
38
  priority
39
39
  >
40
40
  <template #grid-end>
41
- <LazyAdvNativeBottom
42
- v-if="featureFlags.AdsNativeEnabled"
43
- hydrate-on-visible
44
- />
41
+ <!-- не ленивая: ленивая гидрация пересобирала заполненный сетью iframe, креатив терялся -->
42
+ <AdvNativeBottom v-if="featureFlags.AdsNativeEnabled"/>
45
43
  </template>
46
44
  </GridVideos>
47
45
  <div
@@ -29,6 +29,7 @@
29
29
  v-for="(model, index) in data.models"
30
30
  :key="index"
31
31
  :item="getModelChips(model)"
32
+ :class="{'view-video__model-chips': hasAsideBlock(model.title)}"
32
33
  is-link
33
34
  />
34
35
  </template>
@@ -75,11 +76,10 @@
75
76
  />
76
77
  </template>
77
78
 
79
+ <!-- Без ленивой гидрации: контейнер приезжает в серверной разметке и сеть заполняет его до гидрации,
80
+ а ленивая гидрация пересобирала уже заполненный iframe — креатив терялся, слот оставался пустым. -->
78
81
  <template #grid-end>
79
- <LazyAdvNativeBottom
80
- v-if="featureFlags.AdsNativeEnabled"
81
- hydrate-on-visible
82
- />
82
+ <AdvNativeBottom v-if="featureFlags.AdsNativeEnabled"/>
83
83
  </template>
84
84
  </LazyGridVideos>
85
85
  </section>
@@ -126,7 +126,7 @@
126
126
 
127
127
  <script setup lang="ts">
128
128
  import type { IChipsItem, IModelCard, IStripcashModel, IVideoCard, IVideoData, PaginatedResponse } from '../../types';
129
- import { convertModelToRoundCard } from '../../runtime';
129
+ import { convertModelToRoundCard, convertString } from '../../runtime';
130
130
  import { VideosApiService } from '../../services/api/videos.service';
131
131
  import type { AsyncDataRequestStatus } from '#app';
132
132
 
@@ -232,6 +232,15 @@ const topChips = computed(() => {
232
232
  const topChipsStrings = computed(() => topChips.value?.map(item => item.value));
233
233
 
234
234
  /** Портрет в чипсе — квадратный кроп по лицу; своего у видео нет, его подмешивает BFF. */
235
+ // `videoPage.hideAsideModelChips`: с 1024px чипса модели не нужна тем, у кого есть блок в боковой колонке плеера
236
+ // (`useFetchAsideModels`). Снимок, а не реактивное чтение: список заполняется эффектом, и реактивный класс
237
+ // пересчитывался бы во время гидрации — разметка расходилась бы с серверной.
238
+ const asideModelSlugs = new Set(videoPage.hideAsideModelChips ? useAsideModelSlugs().value : []);
239
+
240
+ function hasAsideBlock(title: string): boolean {
241
+ return asideModelSlugs.has(convertString().toSlug(title));
242
+ }
243
+
235
244
  const placeholderOf = useModelPlaceholder();
236
245
 
237
246
  function getModelChips(model: IVideoData['models'][number]): IChipsItem {
@@ -283,6 +292,12 @@ function getModelChips(model: IVideoData['models'][number]): IChipsItem {
283
292
  margin-top: 32px;
284
293
  }
285
294
 
295
+ .view-video__model-chips {
296
+ @include from-br(sd) {
297
+ display: none;
298
+ }
299
+ }
300
+
286
301
  .view-video__popular-models {
287
302
  margin-top: 32px;
288
303
  }
@@ -36,10 +36,8 @@
36
36
  priority
37
37
  >
38
38
  <template #grid-end>
39
- <LazyAdvNativeBottom
40
- v-if="featureFlags.AdsNativeEnabled"
41
- hydrate-on-visible
42
- />
39
+ <!-- не ленивая: ленивая гидрация пересобирала заполненный сетью iframe, креатив терялся -->
40
+ <AdvNativeBottom v-if="featureFlags.AdsNativeEnabled"/>
43
41
  </template>
44
42
 
45
43
  <LazySectionSwiperCards
@@ -1,7 +1,7 @@
1
1
  import { CategoriesApiService } from '../../services/api/categories.service';
2
2
  import { VideosApiService } from '../../services/api/videos.service';
3
3
  import { sortItemsDefault } from '../../lib';
4
- import type { IBreadcrumbItem, ICardInfo, IChipsItem } from '../../types';
4
+ import type { IBreadcrumbItem, ICardInfo, IChipsItem, INavLinkItem } from '../../types';
5
5
 
6
6
  /** Слаги категорий, у которых не показываем связанные поисковые фразы. */
7
7
  const DISALLOWED_SLUGS = ['18', '19'];
@@ -35,7 +35,11 @@ export async function useCategoryPageData() {
35
35
  */
36
36
  export function useCategoryPage(
37
37
  pageData: Awaited<ReturnType<typeof useCategoryPageData>>,
38
- options: { metaKeys?: boolean } = {},
38
+ options: {
39
+ metaKeys?: boolean
40
+ /** Сортировки страницы (по умолчанию `sortItemsDefault`): по ним ключи меты и проверка `?sort` (чужой — 404). */
41
+ sortOptions?: INavLinkItem[]
42
+ } = {},
39
43
  ) {
40
44
  const { categoryInfo, data, status, relatedData } = pageData;
41
45
  const { t } = useI18n();
@@ -52,14 +56,16 @@ export function useCategoryPage(
52
56
  useStatsPage(() => ({ rotatorID: 'category', categories: slug.value ? [slug.value] : [] }));
53
57
 
54
58
  // с metaKeys `?sort` проверяет useMeta
55
- if (!options.metaKeys) useSortGuard(sortItemsDefault);
59
+ const sortOptions = options.sortOptions || sortItemsDefault;
60
+
61
+ if (!options.metaKeys) useSortGuard(sortOptions);
56
62
 
57
63
  const keysMeta = options.metaKeys
58
64
  ? useMeta({
59
65
  t,
60
66
  pageKey: useGetPureRouteName(),
61
67
  brandName: useAppConfig().brandName,
62
- sortOptions: sortItemsDefault,
68
+ sortOptions,
63
69
  text: title,
64
70
  nicheSafeText: title,
65
71
  seo: () => ({
@@ -4,7 +4,7 @@ import type { Language } from '../../runtime';
4
4
  import { sortItemsDefault } from '../../lib';
5
5
  import { ChannelsApiService } from '../../services/api/channels.service';
6
6
  import { VideosApiService } from '../../services/api/videos.service';
7
- import type { IBreadcrumbItem, IChipsItem, IPopularTags } from '../../types';
7
+ import type { IBreadcrumbItem, IChipsItem, IPopularTags, INavLinkItem } from '../../types';
8
8
 
9
9
  /**
10
10
  * Данные страницы канала: карточка канала, видео, популярные теги. Только запросы — их можно запускать в
@@ -37,7 +37,12 @@ export async function useChannelPageData() {
37
37
  */
38
38
  export function useChannelPage(
39
39
  pageData: Awaited<ReturnType<typeof useChannelPageData>>,
40
- options: { metaSort?: MaybeRefOrGetter<string>, metaSecondText?: Ref<string> } = {},
40
+ options: {
41
+ metaSort?: MaybeRefOrGetter<string>
42
+ metaSecondText?: Ref<string>
43
+ /** Сортировки страницы (по умолчанию `sortItemsDefault`): по ним ключи меты и проверка `?sort` (чужой — 404). */
44
+ sortOptions?: INavLinkItem[]
45
+ } = {},
41
46
  ) {
42
47
  const { cardInfo, status, channelTagsData } = pageData;
43
48
  const route = useRoute();
@@ -50,7 +55,7 @@ export function useChannelPage(
50
55
  t,
51
56
  pageKey: useGetPureRouteName(),
52
57
  brandName: useAppConfig().brandName,
53
- sortOptions: sortItemsDefault,
58
+ sortOptions: options.sortOptions || sortItemsDefault,
54
59
  sort: options.metaSort,
55
60
  text: metaTitle,
56
61
  secondText: options.metaSecondText,
@@ -4,7 +4,7 @@ import type { Language } from '../../runtime';
4
4
  import { sortItemsDefault } from '../../lib';
5
5
  import { ModelsApiService } from '../../services/api/models.service';
6
6
  import { VideosApiService } from '../../services/api/videos.service';
7
- import type { IBreadcrumbItem } from '../../types';
7
+ import type { IBreadcrumbItem, INavLinkItem } from '../../types';
8
8
 
9
9
  /**
10
10
  * Данные страницы модели: видео и карточка модели. Только запросы — их можно запускать в `Promise.all` вместе
@@ -34,7 +34,12 @@ export async function useModelPageData() {
34
34
  */
35
35
  export function useModelPage(
36
36
  pageData: Awaited<ReturnType<typeof useModelPageData>>,
37
- options: { metaSort?: MaybeRefOrGetter<string>, metaSecondText?: Ref<string> } = {},
37
+ options: {
38
+ metaSort?: MaybeRefOrGetter<string>
39
+ metaSecondText?: Ref<string>
40
+ /** Сортировки страницы (по умолчанию `sortItemsDefault`): по ним ключи меты и проверка `?sort` (чужой — 404). */
41
+ sortOptions?: INavLinkItem[]
42
+ } = {},
38
43
  ) {
39
44
  const { status, cardInfo } = pageData;
40
45
  const route = useRoute();
@@ -48,7 +53,7 @@ export function useModelPage(
48
53
  t,
49
54
  pageKey: useGetPureRouteName(),
50
55
  brandName: useAppConfig().brandName,
51
- sortOptions: sortItemsDefault,
56
+ sortOptions: options.sortOptions || sortItemsDefault,
52
57
  sort: options.metaSort,
53
58
  text: metaTitle,
54
59
  secondText: options.metaSecondText,
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "itube-specs",
3
3
  "type": "module",
4
- "version": "0.0.1052",
4
+ "version": "0.0.1053",
5
5
  "main": "./nuxt.config.ts",
6
6
  "types": "./types/index.d.ts",
7
7
  "scripts": {
@@ -33,10 +33,8 @@
33
33
  :class="{'_loading': status === 'pending'}"
34
34
  >
35
35
  <template #grid-end>
36
- <LazyAdvNativeBottom
37
- v-if="featureFlags.AdsNativeEnabled"
38
- hydrate-on-visible
39
- />
36
+ <!-- не ленивая: ленивая гидрация пересобирала заполненный сетью iframe, креатив терялся -->
37
+ <AdvNativeBottom v-if="featureFlags.AdsNativeEnabled"/>
40
38
  </template>
41
39
  <template #default>
42
40
  <UiExpandRow
@@ -41,10 +41,8 @@
41
41
  priority
42
42
  >
43
43
  <template #grid-end>
44
- <LazyAdvNativeBottom
45
- v-if="featureFlags.AdsNativeEnabled"
46
- hydrate-on-visible
47
- />
44
+ <!-- не ленивая: ленивая гидрация пересобирала заполненный сетью iframe, креатив терялся -->
45
+ <AdvNativeBottom v-if="featureFlags.AdsNativeEnabled"/>
48
46
  </template>
49
47
  <template #default>
50
48
  <LazySectionSwiperCards
@@ -78,10 +78,8 @@
78
78
  priority
79
79
  >
80
80
  <template #grid-end>
81
- <LazyAdvNativeBottom
82
- v-if="featureFlags.AdsNativeEnabled"
83
- hydrate-on-visible
84
- />
81
+ <!-- не ленивая: ленивая гидрация пересобирала заполненный сетью iframe, креатив терялся -->
82
+ <AdvNativeBottom v-if="featureFlags.AdsNativeEnabled"/>
85
83
  <UiViewAll
86
84
  v-if="videosData.items.length > MAXIMUM_VIDEOS || videosData.total > MAXIMUM_VIDEOS"
87
85
  :to="`/search/${slug}/videos`"
@@ -14,4 +14,6 @@ export interface IPlayerSiteHooks {
14
14
  }
15
15
  /** Плеер смонтирован (заглушка) и готов (ядро) — вызывается оба раза. */
16
16
  onReady?: () => void
17
+ /** Началась реклама (преролл): например, отметить частотный кап показа. */
18
+ onAdStart?: () => void
17
19
  }