itube-specs 0.0.1001 → 0.0.1003
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/composables/use-generate-link.ts +6 -7
- package/composables/use-niches.ts +3 -1
- package/config/__tests__/niches.test.ts +27 -5
- package/config/niches.ts +37 -6
- package/package.json +1 -1
- package/public/img/model-placeholder/gay-dark-portrait.avif +0 -0
- package/public/img/model-placeholder/gay-dark-portrait.webp +0 -0
- package/public/img/model-placeholder/gay-light-portrait.avif +0 -0
- package/public/img/model-placeholder/gay-light-portrait.webp +0 -0
- package/public/img/model-placeholder/shemale-dark-portrait.avif +0 -0
- package/public/img/model-placeholder/shemale-dark-portrait.webp +0 -0
- package/public/img/model-placeholder/shemale-dark-square.avif +0 -0
- package/public/img/model-placeholder/shemale-dark-square.webp +0 -0
- package/public/img/model-placeholder/shemale-light-portrait.avif +0 -0
- package/public/img/model-placeholder/shemale-light-portrait.webp +0 -0
- package/public/img/model-placeholder/shemale-light-square.avif +0 -0
- package/public/img/model-placeholder/shemale-light-square.webp +0 -0
- package/public/img/model-placeholder/straight-dark-portrait.avif +0 -0
- package/public/img/model-placeholder/straight-dark-portrait.webp +0 -0
- package/public/img/model-placeholder/straight-dark-square.avif +0 -0
- package/public/img/model-placeholder/straight-dark-square.webp +0 -0
- package/public/img/model-placeholder/straight-light-portrait.avif +0 -0
- package/public/img/model-placeholder/straight-light-portrait.webp +0 -0
- package/public/img/model-placeholder/straight-light-square.avif +0 -0
- package/public/img/model-placeholder/straight-light-square.webp +0 -0
- package/utils/niche-helper.ts +11 -4
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Language, Niche } from '../runtime';
|
|
2
|
-
import { nicheSlug, type NicheSlugs } from '../config/niches';
|
|
2
|
+
import { nicheSlug, withNichePrefix, type NicheSlugs } from '../config/niches';
|
|
3
3
|
import { NicheHelper } from '../utils/niche-helper';
|
|
4
4
|
|
|
5
5
|
/** Возвращает generateLink — генерирует путь с учётом текущей ниши и локали. */
|
|
@@ -26,13 +26,12 @@ export const useGenerateLink = () => {
|
|
|
26
26
|
const nicheFreeRoots = (appConfig.nicheFreeRoots as string[] | undefined) || [];
|
|
27
27
|
const isNicheFree = nicheFreeRoots.some(root => cleanPath === root || cleanPath.startsWith(`${root}/`));
|
|
28
28
|
|
|
29
|
-
const withNiche =
|
|
30
|
-
!niche.value || niche.value === defaultNiche || isOneNiche || isNicheFree
|
|
31
|
-
? cleanPath
|
|
32
|
-
: `/${nicheSlug(niche.value, appConfig.nicheSlugs as NicheSlugs | undefined)}${cleanPath}`;
|
|
33
|
-
|
|
34
29
|
const locale = localeArg || undefined;
|
|
35
|
-
|
|
30
|
+
const localized = localePath(cleanPath, locale);
|
|
31
|
+
|
|
32
|
+
if (!niche.value || niche.value === defaultNiche || isOneNiche || isNicheFree) return localized;
|
|
33
|
+
|
|
34
|
+
return withNichePrefix(localized, `/${nicheSlug(niche.value, appConfig.nicheSlugs as NicheSlugs | undefined)}`, localePath('/', locale));
|
|
36
35
|
};
|
|
37
36
|
|
|
38
37
|
return { generateLink };
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { Niche } from '../runtime';
|
|
2
|
+
import { withNichePrefix } from '../config/niches';
|
|
2
3
|
import { NicheHelper } from '../utils/niche-helper';
|
|
3
4
|
|
|
4
5
|
/** Иконка из assets/icons/common под каждую нишу. */
|
|
@@ -40,7 +41,8 @@ export const useNiches = () => {
|
|
|
40
41
|
function changeNiche(name: Niche) {
|
|
41
42
|
if (name === current.value) return;
|
|
42
43
|
current.value = name;
|
|
43
|
-
|
|
44
|
+
const root = localePath('/');
|
|
45
|
+
window.location.href = withNichePrefix(root, NicheHelper.getNichePath(name), root);
|
|
44
46
|
}
|
|
45
47
|
|
|
46
48
|
return { niches, currentNiche, changeNiche };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { describe, it, expect } from 'vitest';
|
|
2
|
-
import { addNicheAliases, nicheFromPath, nicheFromSlug, nicheRoutePrefix, nicheSlug } from '../niches';
|
|
2
|
+
import { addNicheAliases, nicheFromPath, nicheFromSlug, nicheRoutePrefix, nicheSlug, nichePath, withNichePrefix } from '../niches';
|
|
3
3
|
|
|
4
4
|
const CONFIG = { niches: ['straight', 'gay', 'shemale'], defaultNiche: 'straight', contentRoots: ['/', '/videos', '/models'] };
|
|
5
5
|
|
|
@@ -19,9 +19,9 @@ describe('niches', () => {
|
|
|
19
19
|
];
|
|
20
20
|
addNicheAliases(pages, CONFIG);
|
|
21
21
|
|
|
22
|
-
expect(pages[0]?.alias).toEqual(['
|
|
23
|
-
expect(pages[1]?.alias).toEqual(['/v/:slug', '/:
|
|
24
|
-
expect(pages[2]?.alias).toEqual(['
|
|
22
|
+
expect(pages[0]?.alias).toEqual(['/gay', '/shemale']);
|
|
23
|
+
expect(pages[1]?.alias).toEqual(['/v/:slug', '/gay/videos/:slug', '/shemale/videos/:slug']);
|
|
24
|
+
expect(pages[2]?.alias).toEqual(['/gay/models', '/shemale/models']);
|
|
25
25
|
expect(pages[3]?.alias).toBeUndefined();
|
|
26
26
|
expect(pages[4]?.alias).toBeUndefined();
|
|
27
27
|
});
|
|
@@ -32,7 +32,29 @@ describe('niches', () => {
|
|
|
32
32
|
|
|
33
33
|
const pages = [{ path: '/videos/:slug' }];
|
|
34
34
|
addNicheAliases(pages, { ...CONFIG, slugs });
|
|
35
|
-
expect(pages[0]?.alias).toEqual(['/:
|
|
35
|
+
expect(pages[0]?.alias).toEqual(['/gays/videos/:slug', '/shemale/videos/:slug']);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('префикс пути ниши: слаг, у дефолтной и одной ниши пусто', () => {
|
|
39
|
+
const slugs = { gay: 'gays' };
|
|
40
|
+
expect(nichePath('gay', { ...CONFIG, slugs })).toBe('/gays');
|
|
41
|
+
expect(nichePath('shemale', { ...CONFIG, slugs })).toBe('/shemale');
|
|
42
|
+
expect(nichePath('straight', CONFIG)).toBe('');
|
|
43
|
+
expect(nichePath(undefined, CONFIG)).toBe('');
|
|
44
|
+
expect(nichePath('gay', { niches: ['gay'], defaultNiche: 'straight' })).toBe('');
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it('префикс ниши в локализованном пути — после языка', () => {
|
|
48
|
+
expect(withNichePrefix('/models', '/gays')).toBe('/gays/models');
|
|
49
|
+
expect(withNichePrefix('/', '/gays')).toBe('/gays');
|
|
50
|
+
expect(withNichePrefix('/?page=2', '/gays')).toBe('/gays?page=2');
|
|
51
|
+
expect(withNichePrefix('/ru/models', '/gays', '/ru')).toBe('/ru/gays/models');
|
|
52
|
+
expect(withNichePrefix('/ru', '/gays', '/ru')).toBe('/ru/gays');
|
|
53
|
+
expect(withNichePrefix('/ru?page=2', '/gays', '/ru')).toBe('/ru/gays?page=2');
|
|
54
|
+
expect(withNichePrefix('/rubber', '/gays', '/ru')).toBe('/gays/rubber');
|
|
55
|
+
expect(withNichePrefix('/models', '')).toBe('/models');
|
|
56
|
+
expect(withNichePrefix('https://ru.example.com/models', '/gays')).toBe('https://ru.example.com/gays/models');
|
|
57
|
+
expect(withNichePrefix('https://ru.example.com', '/gays')).toBe('https://ru.example.com/gays');
|
|
36
58
|
});
|
|
37
59
|
|
|
38
60
|
it('слаг ↔ ниша', () => {
|
package/config/niches.ts
CHANGED
|
@@ -79,19 +79,50 @@ function isUnderRoot(path: string, roots: readonly string[]): boolean {
|
|
|
79
79
|
|
|
80
80
|
/**
|
|
81
81
|
* Клонирует контентные разделы под нишевые префиксы — алиасом той же записи роута:
|
|
82
|
-
* `/videos/:slug` начинает отвечать и на `/gay/videos/:slug`.
|
|
83
|
-
*
|
|
82
|
+
* `/videos/:slug` начинает отвечать и на `/gay/videos/:slug`. Алиасы статичные, по одному на нишу:
|
|
83
|
+
* у алиаса vue-router требует те же параметры, что у оригинала, поэтому параметр `:niche` сюда нельзя.
|
|
84
|
+
* Сайту с одной нишей ничего не добавляет. Мутирует переданный список — так устроен хук `pages:extend`.
|
|
84
85
|
*/
|
|
85
86
|
export function addNicheAliases(pages: INicheAliasablePage[], config: INicheRouting): void {
|
|
86
|
-
const
|
|
87
|
-
if (!
|
|
87
|
+
const prefixes = prefixedNiches(config).map(niche => `/${nicheSlug(niche, config.slugs)}`);
|
|
88
|
+
if (!prefixes.length) return;
|
|
88
89
|
|
|
89
90
|
for (const page of pages) {
|
|
90
91
|
if (!page.path || !isUnderRoot(page.path, config.contentRoots)) continue;
|
|
91
92
|
|
|
92
|
-
const
|
|
93
|
+
const aliases = prefixes.map(prefix => (page.path === '/' ? prefix : `${prefix}${page.path}`));
|
|
93
94
|
const current = Array.isArray(page.alias) ? page.alias : page.alias ? [page.alias] : [];
|
|
94
95
|
|
|
95
|
-
page.alias = [...current,
|
|
96
|
+
page.alias = [...current, ...aliases];
|
|
96
97
|
}
|
|
97
98
|
}
|
|
99
|
+
|
|
100
|
+
/** Префикс пути ниши (`/gays`). У дефолтной ниши и у сайта с одной нишей — пусто. */
|
|
101
|
+
export function nichePath(niche: string | undefined, { niches, defaultNiche, slugs }: Pick<INicheRouting, 'niches' | 'defaultNiche' | 'slugs'>): string {
|
|
102
|
+
if (!niche || niches.length <= 1 || niche === defaultNiche) return '';
|
|
103
|
+
|
|
104
|
+
return `/${nicheSlug(niche, slugs)}`;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const ORIGIN_RE = /^https?:\/\/[^/?#]+/;
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Вставляет префикс ниши в уже локализованный путь — после языкового префикса: `/ru/models` → `/ru/gays/models`.
|
|
111
|
+
* Префикс ниши добавляется после `localePath`, а не до: на нишевом пути i18n находит роут по алиасу и
|
|
112
|
+
* пересобирает путь по имени роута — то есть без ниши (так же `switchLocalePath`). `localeRoot` —
|
|
113
|
+
* `localePath('/')` той же локали. Абсолютный URL (локали на поддоменах) — префикс в его путь.
|
|
114
|
+
*/
|
|
115
|
+
export function withNichePrefix(localizedPath: string, nichePath: string, localeRoot = '/'): string {
|
|
116
|
+
if (!nichePath) return localizedPath;
|
|
117
|
+
|
|
118
|
+
const origin = localizedPath.match(ORIGIN_RE)?.[0] || '';
|
|
119
|
+
if (origin) return `${origin}${withNichePrefix(localizedPath.slice(origin.length) || '/', nichePath, localeRoot)}`;
|
|
120
|
+
|
|
121
|
+
const localeRootPath = localeRoot.replace(ORIGIN_RE, '').replace(/\/$/, '');
|
|
122
|
+
const afterRoot = localizedPath.slice(localeRootPath.length);
|
|
123
|
+
const root = localeRootPath && localizedPath.startsWith(localeRootPath) && (!afterRoot || /^[/?#]/.test(afterRoot)) ? localeRootPath : '';
|
|
124
|
+
const rest = localizedPath.slice(root.length);
|
|
125
|
+
const tail = rest === '/' ? '' : rest.startsWith('/?') || rest.startsWith('/#') ? rest.slice(1) : rest;
|
|
126
|
+
|
|
127
|
+
return `${root}${nichePath}${tail}`;
|
|
128
|
+
}
|
package/package.json
CHANGED
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/utils/niche-helper.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Niche } from '../runtime';
|
|
2
|
-
import {
|
|
2
|
+
import { nichePath, type NicheSlugs } from '../config/niches';
|
|
3
3
|
|
|
4
4
|
/** Хелпер для работы с нишами */
|
|
5
5
|
export class NicheHelper {
|
|
@@ -21,10 +21,17 @@ export class NicheHelper {
|
|
|
21
21
|
* сайта с одной нишей — пусто.
|
|
22
22
|
*/
|
|
23
23
|
public static getNichePath(niche: Niche = NicheHelper.getCurrentNiche()): string {
|
|
24
|
+
return nichePath(niche, NicheHelper.getNicheRouting());
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** Ниши сайта, дефолтная и слаги — то, что нужно `nichePath` и прочим хелперам `itube-specs/config`. */
|
|
28
|
+
public static getNicheRouting(): { niches: Niche[], defaultNiche: Niche, slugs?: NicheSlugs } {
|
|
24
29
|
const appConfig = useAppConfig();
|
|
25
|
-
const isOneNiche = ((appConfig.niches as Niche[] | undefined) || []).length <= 1;
|
|
26
|
-
if (!niche || isOneNiche || niche === NicheHelper.getSiteDefaultNiche()) return '';
|
|
27
30
|
|
|
28
|
-
return
|
|
31
|
+
return {
|
|
32
|
+
niches: (appConfig.niches as Niche[] | undefined) || [],
|
|
33
|
+
defaultNiche: NicheHelper.getSiteDefaultNiche(),
|
|
34
|
+
slugs: appConfig.nicheSlugs as NicheSlugs | undefined,
|
|
35
|
+
};
|
|
29
36
|
}
|
|
30
37
|
}
|