@netgen/storyblok-siteaccess 0.1.0

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 (47) hide show
  1. package/README.md +350 -0
  2. package/dist/classes/Siteaccess.d.ts +82 -0
  3. package/dist/classes/Siteaccess.js +245 -0
  4. package/dist/classes/Siteaccess.js.map +1 -0
  5. package/dist/classes/SiteaccessResolver.d.ts +51 -0
  6. package/dist/classes/SiteaccessResolver.js +95 -0
  7. package/dist/classes/SiteaccessResolver.js.map +1 -0
  8. package/dist/classes/index.d.ts +2 -0
  9. package/dist/classes/index.js +3 -0
  10. package/dist/classes/index.js.map +1 -0
  11. package/dist/hooks/index.d.ts +1 -0
  12. package/dist/hooks/index.js +2 -0
  13. package/dist/hooks/index.js.map +1 -0
  14. package/dist/hooks/useSiteaccess.d.ts +9 -0
  15. package/dist/hooks/useSiteaccess.js +11 -0
  16. package/dist/hooks/useSiteaccess.js.map +1 -0
  17. package/dist/index.d.ts +5 -0
  18. package/dist/index.js +9 -0
  19. package/dist/index.js.map +1 -0
  20. package/dist/resolvers/client.d.ts +12 -0
  21. package/dist/resolvers/client.js +18 -0
  22. package/dist/resolvers/client.js.map +1 -0
  23. package/dist/resolvers/index.d.ts +3 -0
  24. package/dist/resolvers/index.js +4 -0
  25. package/dist/resolvers/index.js.map +1 -0
  26. package/dist/resolvers/server.d.ts +12 -0
  27. package/dist/resolvers/server.js +18 -0
  28. package/dist/resolvers/server.js.map +1 -0
  29. package/dist/resolvers/universal.d.ts +10 -0
  30. package/dist/resolvers/universal.js +11 -0
  31. package/dist/resolvers/universal.js.map +1 -0
  32. package/dist/types/augmentation.d.ts +12 -0
  33. package/dist/types/augmentation.js +2 -0
  34. package/dist/types/augmentation.js.map +1 -0
  35. package/dist/types/index.d.ts +2 -0
  36. package/dist/types/index.js +2 -0
  37. package/dist/types/index.js.map +1 -0
  38. package/dist/types/siteaccess.d.ts +91 -0
  39. package/dist/types/siteaccess.js +2 -0
  40. package/dist/types/siteaccess.js.map +1 -0
  41. package/dist/utils/index.d.ts +1 -0
  42. package/dist/utils/index.js +2 -0
  43. package/dist/utils/index.js.map +1 -0
  44. package/dist/utils/routing.d.ts +19 -0
  45. package/dist/utils/routing.js +45 -0
  46. package/dist/utils/routing.js.map +1 -0
  47. package/package.json +82 -0
package/README.md ADDED
@@ -0,0 +1,350 @@
1
+ # @netgen/storyblok-siteaccess
2
+
3
+ Generic siteaccess management for multi-market, multi-language Storyblok websites.
4
+
5
+ ## Features
6
+
7
+ - **Generic & Type-safe**: Works with any market/language structure using TypeScript generics
8
+ - **URL Generation**: Convert between Storyblok paths, URLs, and pathnames
9
+ - **Multi-market Support**: Handle multiple markets with different languages
10
+ - **Alternate Language Management**: Generate language/market alternatives for SEO
11
+ - **Framework Agnostic Core**: Works with any framework, with optional React hooks
12
+ - **Environment Management**: Support for different environments (dev, prod, etc.)
13
+ - **Next.js Integration**: Built-in utilities for Next.js routing and redirects
14
+ - **Internationalization**: Generate next-intl routing configurations automatically
15
+
16
+ ## Installation
17
+
18
+ ```bash
19
+ npm install @netgen/storyblok-siteaccess
20
+ ```
21
+
22
+ ## Basic Usage
23
+
24
+ ### 1. Define Your Types
25
+
26
+ ```typescript
27
+ // Your project types
28
+ export enum Market {
29
+ us = 'us',
30
+ uk = 'uk',
31
+ de = 'de'
32
+ }
33
+
34
+ export enum Language {
35
+ en = 'en',
36
+ de = 'de'
37
+ }
38
+
39
+ // Type your configurations
40
+ export type ProjectSiteaccessConfig = SiteaccessConfig<Market, Language>;
41
+ export type ProjectEnvironmentConfig = EnvironmentConfig<Market, Language>;
42
+ ```
43
+
44
+ ### 2. Create Configuration
45
+
46
+ ```typescript
47
+ import { SiteaccessResolver, Siteaccess } from '@netgen/storyblok-siteaccess';
48
+
49
+ const environmentConfig: ProjectEnvironmentConfig = {
50
+ siteaccess: [
51
+ {
52
+ host: 'example.com',
53
+ locale: 'en',
54
+ market: Market.us,
55
+ language: Language.en,
56
+ storyblokFolderPrefix: 'us',
57
+ storyblokVisualEditorPrefix: 'us',
58
+ },
59
+ {
60
+ host: 'example.de',
61
+ locale: 'de',
62
+ market: Market.de,
63
+ language: Language.de,
64
+ storyblokFolderPrefix: 'de',
65
+ storyblokVisualEditorPrefix: 'de',
66
+ }
67
+ ],
68
+ marketConfigs: {
69
+ [Market.us]: {
70
+ primaryLanguage: Language.en,
71
+ tosStoryblokPath: 'terms-of-service'
72
+ },
73
+ [Market.de]: {
74
+ primaryLanguage: Language.de,
75
+ tosStoryblokPath: 'nutzungsbedingungen'
76
+ }
77
+ },
78
+ siteinfoIds: {
79
+ [Market.us]: 'us-siteinfo-id',
80
+ [Market.de]: 'de-siteinfo-id'
81
+ },
82
+ partnersFolderStoryblokPrefix: 'partners',
83
+ hosts: [
84
+ { host: 'example.com', defaultLocale: 'en' },
85
+ { host: 'example.de', defaultLocale: 'de' }
86
+ ]
87
+ };
88
+
89
+ // Create resolver
90
+ const siteaccessResolver = new SiteaccessResolver(
91
+ environmentConfig.siteaccess,
92
+ environmentConfig
93
+ );
94
+ ```
95
+
96
+ ### 3. Use Siteaccess
97
+
98
+ ```typescript
99
+ // Resolve configuration
100
+ const config = siteaccessResolver.resolveByLocale({ locale: 'en' });
101
+
102
+ // Create siteaccess instance
103
+ const siteaccess = new Siteaccess(config, environmentConfig);
104
+
105
+ // Generate URLs
106
+ const url = siteaccess.getUrl({ storyblokPath: 'about' });
107
+ // Result: 'https://example.com/about'
108
+
109
+ const storyblokPath = siteaccess.getStoryblokPath({ pathname: '/about' });
110
+ // Result: 'us/about'
111
+ ```
112
+
113
+ ## React Integration
114
+
115
+ ### Basic Hook Usage
116
+
117
+ ```typescript
118
+ import { useSiteaccess } from '@netgen/storyblok-siteaccess';
119
+
120
+ function MyComponent() {
121
+ const siteaccess = useSiteaccess(siteaccessConfig, environmentConfig);
122
+
123
+ const aboutUrl = siteaccess.getUrl({ storyblokPath: 'about' });
124
+
125
+ return <a href={aboutUrl}>About Us</a>;
126
+ }
127
+ ```
128
+
129
+ ### Create Project-Specific Hook
130
+
131
+ ```typescript
132
+ import { createSiteaccessHook } from '@netgen/storyblok-siteaccess';
133
+
134
+ // Create your typed hook
135
+ export const useProjectSiteaccess = createSiteaccessHook<Market, Language>(
136
+ () => getCurrentSiteaccessConfig(),
137
+ () => environmentConfig
138
+ );
139
+
140
+ // Use in components
141
+ function MyComponent() {
142
+ const siteaccess = useProjectSiteaccess();
143
+ // Fully typed with your Market and Language enums
144
+ }
145
+ ```
146
+
147
+ ## Advanced Features
148
+
149
+ ### Market Alternates
150
+
151
+ ```typescript
152
+ const alternates = siteaccess.getMarketAlternates(
153
+ Object.values(Market), // All available markets
154
+ { storyblokAlternates: story.alternates }
155
+ );
156
+
157
+ // Result: Array of alternate URLs for different markets
158
+ ```
159
+
160
+ ### Language Validation
161
+
162
+ ```typescript
163
+ const isValid = siteaccess.isStoryLanguageTranslationValid(story);
164
+ // Checks if the current language has a valid translation
165
+ ```
166
+
167
+ ### Storyblok API Integration
168
+
169
+ ```typescript
170
+ const storyblokLanguage = siteaccess.getStoryblokLanguage();
171
+ // Returns 'default' for primary language, actual language code otherwise
172
+
173
+ const apiLanguage = siteaccess.getLanguageForStoryblok();
174
+ // Returns undefined for primary language, language code otherwise
175
+ ```
176
+
177
+ ## Client/Server Resolution
178
+
179
+ ### Next.js Example
180
+
181
+ ```typescript
182
+ import {
183
+ createLocaleBasedClientResolver,
184
+ createLocaleBasedServerResolver,
185
+ createUniversalSiteaccessResolver
186
+ } from '@netgen/storyblok-siteaccess';
187
+ import { useLocale } from 'next-intl';
188
+ import { getLocale } from 'next-intl/server';
189
+
190
+ // Create resolvers
191
+ const clientResolver = createLocaleBasedClientResolver(
192
+ useLocale,
193
+ (locale) => siteaccessResolver.resolveByLocale({ locale })
194
+ );
195
+
196
+ const serverResolver = createLocaleBasedServerResolver(
197
+ getLocale,
198
+ (locale) => siteaccessResolver.resolveByLocale({ locale })
199
+ );
200
+
201
+ const universalResolver = createUniversalSiteaccessResolver(
202
+ clientResolver,
203
+ serverResolver
204
+ );
205
+
206
+ // Use in your app
207
+ export const getCurrentSiteaccessConfig = {
208
+ client: universalResolver.client,
209
+ server: universalResolver.server
210
+ };
211
+ ```
212
+
213
+ ## API Reference
214
+
215
+ ### Classes
216
+
217
+ - **`Siteaccess<TMarket, TLanguage>`**: Main class for URL generation and path conversion
218
+ - **`SiteaccessResolver<TMarket, TLanguage>`**: Resolves siteaccess configurations based on various criteria
219
+
220
+ ### Types
221
+
222
+ - **`SiteaccessConfig<TMarket, TLanguage>`**: Configuration for a single siteaccess
223
+ - **`EnvironmentConfig<TMarket, TLanguage>`**: Complete environment configuration
224
+ - **`AlternateStory<TMarket, TLanguage>`**: Language/market alternate information
225
+ - **`SiteaccessInputType`**: Input union type for path conversion methods
226
+
227
+ ### Hooks
228
+
229
+ - **`useSiteaccess()`**: React hook for creating Siteaccess instances
230
+ - **`createSiteaccessHook()`**: Factory for creating project-specific hooks
231
+
232
+ ### Resolvers
233
+
234
+ - **`createClientSiteaccessResolver()`**: Create client-side resolver
235
+ - **`createServerSiteaccessResolver()`**: Create server-side resolver
236
+ - **`createUniversalSiteaccessResolver()`**: Create universal resolver
237
+
238
+ ## Migration from Project-Specific Implementation
239
+
240
+ 1. **Extract your types**: Move Market/Language enums to your project
241
+ 2. **Update imports**: Import from `@netgen/storyblok-siteaccess`
242
+ 3. **Add type parameters**: Add `<Market, Language>` to class instantiations
243
+ 4. **Update configurations**: Move environment configs to your project
244
+ 5. **Create typed hooks**: Use `createSiteaccessHook()` for React integration
245
+
246
+ ## Next.js Integration
247
+
248
+ ### Automatic Routing Configuration
249
+
250
+ Generate Next.js internationalization routing from your environment config:
251
+
252
+ ```typescript
253
+ import { generateNextIntlRouting } from '@netgen/storyblok-siteaccess';
254
+ import { defineRouting } from 'next-intl/routing';
255
+
256
+ // Generate routing configuration
257
+ const routingConfig = generateNextIntlRouting(environmentConfig, {
258
+ defaultLocale: 'en',
259
+ alternateLinks: false,
260
+ localeCookie: false,
261
+ localePrefix: { mode: 'always' }
262
+ });
263
+
264
+ export const routing = defineRouting(routingConfig);
265
+ ```
266
+
267
+ ### Storyblok Visual Editor Redirects
268
+
269
+ Generate redirects for Storyblok's visual editor:
270
+
271
+ ```typescript
272
+ import { generateStoryblokRedirects } from '@netgen/storyblok-siteaccess';
273
+
274
+ // Generate redirects
275
+ export const storyblokRedirects = generateStoryblokRedirects(environmentConfig, {
276
+ permanent: false
277
+ });
278
+
279
+ // Use in next.config.js
280
+ module.exports = {
281
+ async redirects() {
282
+ return storyblokRedirects;
283
+ }
284
+ };
285
+ ```
286
+
287
+ ### Utility Functions
288
+
289
+ ```typescript
290
+ import {
291
+ getLocales,
292
+ getHosts,
293
+ getDomainConfig,
294
+ isValidLocale,
295
+ getDefaultLocaleForHost
296
+ } from '@netgen/storyblok-siteaccess';
297
+
298
+ // Get all locales
299
+ const locales = getLocales(environmentConfig);
300
+
301
+ // Check if locale is valid
302
+ const isValid = isValidLocale(environmentConfig, 'en-US');
303
+
304
+ // Get domain configuration
305
+ const domainConfig = getDomainConfig(environmentConfig, 'example.com');
306
+ ```
307
+
308
+ ### Integration with next-intl
309
+
310
+ ```typescript
311
+ import { isValidLocale } from '@netgen/storyblok-siteaccess';
312
+ import { getRequestConfig } from 'next-intl/server';
313
+
314
+ export default getRequestConfig(async ({ requestLocale }) => {
315
+ const locale = await requestLocale;
316
+
317
+ // Validate locale using the package utility
318
+ if (!isValidLocale(environmentConfig, locale)) {
319
+ // Handle invalid locale
320
+ return { locale: 'en', messages: {} };
321
+ }
322
+
323
+ return {
324
+ locale,
325
+ messages: (await import(`./messages/${locale}.json`)).default
326
+ };
327
+ });
328
+ ```
329
+
330
+ ## API Reference
331
+
332
+ ### Utility Functions
333
+
334
+ - **`generateNextIntlRouting(config, options?)`**: Generate Next.js internationalization routing
335
+ - **`generateStoryblokRedirects(config, options?)`**: Generate Storyblok visual editor redirects
336
+ - **`getLocales(config)`**: Extract all unique locales
337
+ - **`getHosts(config)`**: Extract all unique hosts
338
+ - **`getDomainConfig(config, host)`**: Get domain configuration for specific host
339
+ - **`isValidLocale(config, locale)`**: Check if locale exists in configuration
340
+ - **`getDefaultLocaleForHost(config, host)`**: Get default locale for specific host
341
+
342
+ ### Types
343
+
344
+ - **`NextRoutingConfig`**: Next.js routing configuration interface
345
+ - **`NextRedirectConfig`**: Next.js redirect configuration interface
346
+ - **`NextDomainConfig`**: Next.js domain configuration interface
347
+
348
+ ## License
349
+
350
+ ISC
@@ -0,0 +1,82 @@
1
+ import type { ISbAlternateObject, ISbStoryData } from "@storyblok/react";
2
+ import type { AlternateStory, EnvironmentConfig, SiteaccessConfig, SiteaccessInputType } from "../types";
3
+ import { GenericSiteaccessResolver } from "./SiteaccessResolver";
4
+ import type { Market, Language } from "../types/augmentation";
5
+ /**
6
+ * Generic Siteaccess class
7
+ * Handles URL generation, path conversion, and alternate language management
8
+ */
9
+ export declare class Siteaccess<TMarket extends Market = Market, TLanguage extends Language = Language, TEnvironmentConfig extends EnvironmentConfig<TMarket, TLanguage> = EnvironmentConfig<TMarket, TLanguage>> {
10
+ markets: TMarket[];
11
+ languages: TLanguage[];
12
+ private siteaccessResolver;
13
+ static readonly storyblokDefaultLanguage = "default";
14
+ readonly host: string;
15
+ readonly uri?: string;
16
+ readonly locale: string;
17
+ readonly market: TMarket;
18
+ readonly language: TLanguage;
19
+ readonly storyblokVisualEditorPrefix: string;
20
+ readonly baseUrl: string;
21
+ readonly origin: string;
22
+ readonly storyblokFolderPrefix: string;
23
+ readonly siteaccessConfig: SiteaccessConfig<TMarket, TLanguage>;
24
+ environmentConfig: TEnvironmentConfig;
25
+ constructor(markets: TMarket[], languages: TLanguage[], siteaccessConfig: SiteaccessConfig<TMarket, TLanguage>, environmentConfig: TEnvironmentConfig, siteaccessResolver: GenericSiteaccessResolver<TMarket, TLanguage>);
26
+ /**
27
+ * Get the Storyblok language for API calls
28
+ * Returns 'default' for primary market language, actual language otherwise
29
+ */
30
+ getStoryblokLanguage(): string;
31
+ /**
32
+ * Convert input to Storyblok path
33
+ */
34
+ getStoryblokPath(input: SiteaccessInputType): string;
35
+ /**
36
+ * Convert input to pathname
37
+ */
38
+ getPathname(input: SiteaccessInputType): string;
39
+ /**
40
+ * Convert input to full URL
41
+ */
42
+ getUrl(input: SiteaccessInputType): string;
43
+ /**
44
+ * Get primary language for current market
45
+ * Requires environmentConfig to be provided
46
+ */
47
+ getMarketPrimaryLanguage(): TLanguage;
48
+ /**
49
+ * Get market alternates for a story
50
+ * Requires environmentConfig and all available markets to be provided
51
+ */
52
+ getMarketAlternates({ storyblokAlternates, }?: {
53
+ storyblokAlternates?: ISbAlternateObject[];
54
+ }): AlternateStory<TMarket, TLanguage>[];
55
+ /**
56
+ * Get language for Storyblok API calls (undefined for primary language)
57
+ */
58
+ getLanguageForStoryblok(): TLanguage | undefined;
59
+ /**
60
+ * Check if story language translation is valid
61
+ */
62
+ isStoryLanguageTranslationValid(story: ISbStoryData): boolean;
63
+ /**
64
+ * Get language alternates for a story
65
+ */
66
+ getLanguageAlternates(story: ISbStoryData, siteaccessResolver: GenericSiteaccessResolver<TMarket, TLanguage>): AlternateStory<TMarket, TLanguage>[];
67
+ /**
68
+ * Static utility: Strip leading and trailing slashes
69
+ */
70
+ static stripSlashes(path?: string): string;
71
+ /**
72
+ * Static utility: Join URL parts
73
+ */
74
+ static joinUrlParts(...parts: (string | undefined | null)[]): string;
75
+ private storyblokPathToLocation;
76
+ private locationToStoryblokPath;
77
+ private locationToUrl;
78
+ private urlToLocation;
79
+ private pathnameToLocation;
80
+ private locationToPathname;
81
+ private normalizeInput;
82
+ }
@@ -0,0 +1,245 @@
1
+ import { GenericSiteaccessResolver } from "./SiteaccessResolver";
2
+ /**
3
+ * Generic Siteaccess class
4
+ * Handles URL generation, path conversion, and alternate language management
5
+ */
6
+ export class Siteaccess {
7
+ markets;
8
+ languages;
9
+ siteaccessResolver;
10
+ static storyblokDefaultLanguage = "default";
11
+ host;
12
+ uri;
13
+ locale;
14
+ market;
15
+ language;
16
+ storyblokVisualEditorPrefix;
17
+ baseUrl;
18
+ origin;
19
+ storyblokFolderPrefix;
20
+ siteaccessConfig;
21
+ environmentConfig;
22
+ constructor(markets, languages, siteaccessConfig, environmentConfig, siteaccessResolver) {
23
+ this.markets = markets;
24
+ this.languages = languages;
25
+ this.siteaccessResolver = siteaccessResolver;
26
+ this.host = Siteaccess.stripSlashes(siteaccessConfig.host);
27
+ this.locale = Siteaccess.stripSlashes(siteaccessConfig.locale);
28
+ this.uri = Siteaccess.stripSlashes(siteaccessConfig.uri);
29
+ this.market = siteaccessConfig.market;
30
+ this.language = siteaccessConfig.language;
31
+ this.storyblokVisualEditorPrefix = Siteaccess.stripSlashes(siteaccessConfig.storyblokVisualEditorPrefix);
32
+ this.siteaccessConfig = siteaccessConfig;
33
+ this.origin = `https://${this.host}`;
34
+ this.baseUrl = `${this.origin}/${this.uri || ""}`;
35
+ this.storyblokFolderPrefix = Siteaccess.stripSlashes(siteaccessConfig.storyblokFolderPrefix);
36
+ this.environmentConfig = environmentConfig;
37
+ }
38
+ /**
39
+ * Get the Storyblok language for API calls
40
+ * Returns 'default' for primary market language, actual language otherwise
41
+ */
42
+ getStoryblokLanguage() {
43
+ const marketPrimaryLanguage = this.getMarketPrimaryLanguage();
44
+ const isCurrentLanguageMarketPrimary = marketPrimaryLanguage === this.language;
45
+ return isCurrentLanguageMarketPrimary
46
+ ? Siteaccess.storyblokDefaultLanguage
47
+ : String(this.language);
48
+ }
49
+ /**
50
+ * Convert input to Storyblok path
51
+ */
52
+ getStoryblokPath(input) {
53
+ const location = this.normalizeInput(input);
54
+ return this.locationToStoryblokPath(location);
55
+ }
56
+ /**
57
+ * Convert input to pathname
58
+ */
59
+ getPathname(input) {
60
+ const location = this.normalizeInput(input);
61
+ return this.locationToPathname(location);
62
+ }
63
+ /**
64
+ * Convert input to full URL
65
+ */
66
+ getUrl(input) {
67
+ const location = this.normalizeInput(input);
68
+ return this.locationToUrl(location);
69
+ }
70
+ /**
71
+ * Get primary language for current market
72
+ * Requires environmentConfig to be provided
73
+ */
74
+ getMarketPrimaryLanguage() {
75
+ const currentMarketConfig = this.environmentConfig.marketConfigs[this.market];
76
+ return currentMarketConfig.primaryLanguage;
77
+ }
78
+ /**
79
+ * Get market alternates for a story
80
+ * Requires environmentConfig and all available markets to be provided
81
+ */
82
+ getMarketAlternates({ storyblokAlternates = [], } = {}) {
83
+ return this.markets.map((market) => {
84
+ const storyblokAlternate = storyblokAlternates.find((alternate) => {
85
+ const alternateSiteaccess = this.siteaccessResolver.resolveByStoryblokPath({
86
+ storyblokPath: alternate.full_slug,
87
+ });
88
+ return alternateSiteaccess.market === market;
89
+ });
90
+ if (storyblokAlternate) {
91
+ const marketSiteaccessConfig = this.siteaccessResolver.resolveByMarket({
92
+ market,
93
+ });
94
+ const marketSiteaccess = new Siteaccess(this.markets, this.languages, marketSiteaccessConfig, this.environmentConfig, this.siteaccessResolver);
95
+ const url = marketSiteaccess.getUrl({
96
+ storyblokPath: storyblokAlternate.full_slug,
97
+ });
98
+ return {
99
+ language: this.language,
100
+ market,
101
+ url,
102
+ isCurrent: market === this.market,
103
+ locale: marketSiteaccess.locale,
104
+ };
105
+ }
106
+ const marketSiteaccessConfig = this.siteaccessResolver.resolveByMarket({
107
+ market,
108
+ });
109
+ const marketSiteaccess = new Siteaccess(this.markets, this.languages, marketSiteaccessConfig, this.environmentConfig, this.siteaccessResolver);
110
+ return {
111
+ language: marketSiteaccess.language,
112
+ market,
113
+ url: marketSiteaccess.baseUrl,
114
+ isCurrent: market === this.market,
115
+ locale: marketSiteaccess.locale,
116
+ };
117
+ });
118
+ }
119
+ /**
120
+ * Get language for Storyblok API calls (undefined for primary language)
121
+ */
122
+ getLanguageForStoryblok() {
123
+ const marketPrimaryLanguage = this.getMarketPrimaryLanguage();
124
+ const isCurrentLanguageMarketPrimary = marketPrimaryLanguage === this.language;
125
+ return isCurrentLanguageMarketPrimary ? undefined : this.language;
126
+ }
127
+ /**
128
+ * Check if story language translation is valid
129
+ */
130
+ isStoryLanguageTranslationValid(story) {
131
+ const validTranslatedLanguages = story.translated_slugs
132
+ ?.filter((variant) => !!variant.name)
133
+ .map((variant) => variant.lang) || [];
134
+ const marketPrimaryLanguage = this.getMarketPrimaryLanguage();
135
+ const validLanguages = [marketPrimaryLanguage, ...validTranslatedLanguages];
136
+ return validLanguages.includes(this.language);
137
+ }
138
+ /**
139
+ * Get language alternates for a story
140
+ */
141
+ getLanguageAlternates(story, siteaccessResolver) {
142
+ const translatedVariants = story.translated_slugs || [];
143
+ const siteaccessConfig = siteaccessResolver.resolveByStoryblokPath({
144
+ storyblokPath: story.full_slug || "",
145
+ });
146
+ const siteaccess = new Siteaccess(this.markets, this.languages, siteaccessConfig, this.environmentConfig, this.siteaccessResolver);
147
+ const defaultVariant = {
148
+ path: story.default_full_slug || "",
149
+ lang: siteaccess.getMarketPrimaryLanguage(),
150
+ name: story.name,
151
+ published: story.published || story.is_published || !!story.published_at,
152
+ };
153
+ const allLanguageVariants = [defaultVariant, ...translatedVariants];
154
+ const validatedLanguageVariants = allLanguageVariants
155
+ .map((variantInfo) => {
156
+ const isPublished = "published" in variantInfo && variantInfo.published;
157
+ const isCurrentLanguage = variantInfo.lang === siteaccess.language;
158
+ if ((!isPublished && !isCurrentLanguage) || !variantInfo.name)
159
+ return null;
160
+ const language = this.languages.find((language) => language === variantInfo.lang);
161
+ if (!language)
162
+ return null;
163
+ const languageSiteaccessConfig = siteaccessResolver.findMatchingSiteaccessConfig((siteaccessConfig) => {
164
+ const languageMatch = siteaccessConfig.language === language;
165
+ const normalizedStoryblokPath = Siteaccess.stripSlashes(variantInfo.path);
166
+ const storyblokPrefixMatch = normalizedStoryblokPath.startsWith(siteaccessConfig.storyblokFolderPrefix);
167
+ return languageMatch && storyblokPrefixMatch;
168
+ });
169
+ if (!languageSiteaccessConfig)
170
+ return null;
171
+ const languageSiteaccess = new Siteaccess(this.markets, this.languages, languageSiteaccessConfig, this.environmentConfig, this.siteaccessResolver);
172
+ const url = languageSiteaccess.getUrl({
173
+ storyblokPath: variantInfo.path,
174
+ });
175
+ return {
176
+ language,
177
+ isCurrent: variantInfo.lang === siteaccess.language,
178
+ url,
179
+ market: languageSiteaccess.market,
180
+ locale: languageSiteaccess.locale,
181
+ };
182
+ })
183
+ .filter((variant) => variant !== null);
184
+ return validatedLanguageVariants;
185
+ }
186
+ /**
187
+ * Static utility: Strip leading and trailing slashes
188
+ */
189
+ static stripSlashes(path) {
190
+ return path?.replace(/^\/+|\/+$/g, "") || "";
191
+ }
192
+ /**
193
+ * Static utility: Join URL parts
194
+ */
195
+ static joinUrlParts(...parts) {
196
+ return parts.filter(Boolean).join("/").replace(/\/+/g, "/");
197
+ }
198
+ // Private methods for internal path conversion
199
+ storyblokPathToLocation(storyblokPath) {
200
+ let result = storyblokPath;
201
+ if (this.storyblokVisualEditorPrefix &&
202
+ result.startsWith(this.storyblokVisualEditorPrefix)) {
203
+ result = result.slice(this.storyblokVisualEditorPrefix.length);
204
+ }
205
+ if (this.storyblokFolderPrefix &&
206
+ result.startsWith(this.storyblokFolderPrefix)) {
207
+ result = result.slice(this.storyblokFolderPrefix.length);
208
+ }
209
+ return Siteaccess.stripSlashes(result);
210
+ }
211
+ locationToStoryblokPath(location) {
212
+ return Siteaccess.joinUrlParts(this.storyblokFolderPrefix, location);
213
+ }
214
+ locationToUrl(location) {
215
+ const pathname = this.locationToPathname(location);
216
+ return `${this.origin}${pathname}`;
217
+ }
218
+ urlToLocation(url) {
219
+ const urlObject = new URL(url);
220
+ return this.pathnameToLocation(urlObject.pathname);
221
+ }
222
+ pathnameToLocation(pathname) {
223
+ const uriWithSlash = this.uri ? `/${this.uri}` : "";
224
+ return pathname.replace(uriWithSlash, "");
225
+ }
226
+ locationToPathname(location) {
227
+ return `/${Siteaccess.joinUrlParts(this.uri, location)}`;
228
+ }
229
+ normalizeInput(input) {
230
+ if (input.pathname !== undefined) {
231
+ const normalizedPathname = Siteaccess.stripSlashes(input.pathname);
232
+ return this.pathnameToLocation(normalizedPathname);
233
+ }
234
+ if (input.storyblokPath !== undefined) {
235
+ const normalizedStoryblokPath = Siteaccess.stripSlashes(input.storyblokPath);
236
+ return this.storyblokPathToLocation(normalizedStoryblokPath);
237
+ }
238
+ if (input.url !== undefined) {
239
+ const normalizedUrl = Siteaccess.stripSlashes(input.url);
240
+ return this.urlToLocation(normalizedUrl);
241
+ }
242
+ throw new Error("Must provide one of: pathname, storyblokPath, or url.");
243
+ }
244
+ }
245
+ //# sourceMappingURL=Siteaccess.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Siteaccess.js","sourceRoot":"","sources":["../../src/classes/Siteaccess.ts"],"names":[],"mappings":"AAOA,OAAO,EAAE,yBAAyB,EAAE,MAAM,sBAAsB,CAAC;AAGjE;;;GAGG;AACH,MAAM,OAAO,UAAU;IAwBZ;IACA;IAGC;IApBH,MAAM,CAAU,wBAAwB,GAAG,SAAS,CAAC;IAE5C,IAAI,CAAS;IACb,GAAG,CAAU;IACb,MAAM,CAAS;IACf,MAAM,CAAU;IAChB,QAAQ,CAAY;IACpB,2BAA2B,CAAS;IACpC,OAAO,CAAS;IAChB,MAAM,CAAS;IACf,qBAAqB,CAAS;IAC9B,gBAAgB,CAAuC;IAEhE,iBAAiB,CAAqB;IAE7C,YACS,OAAkB,EAClB,SAAsB,EAC7B,gBAAsD,EACtD,iBAAqC,EAC7B,kBAAiE;QAJlE,YAAO,GAAP,OAAO,CAAW;QAClB,cAAS,GAAT,SAAS,CAAa;QAGrB,uBAAkB,GAAlB,kBAAkB,CAA+C;QAEzE,IAAI,CAAC,IAAI,GAAG,UAAU,CAAC,YAAY,CAAC,gBAAgB,CAAC,IAAI,CAAC,CAAC;QAC3D,IAAI,CAAC,MAAM,GAAG,UAAU,CAAC,YAAY,CAAC,gBAAgB,CAAC,MAAM,CAAC,CAAC;QAC/D,IAAI,CAAC,GAAG,GAAG,UAAU,CAAC,YAAY,CAAC,gBAAgB,CAAC,GAAG,CAAC,CAAC;QACzD,IAAI,CAAC,MAAM,GAAG,gBAAgB,CAAC,MAAM,CAAC;QACtC,IAAI,CAAC,QAAQ,GAAG,gBAAgB,CAAC,QAAQ,CAAC;QAC1C,IAAI,CAAC,2BAA2B,GAAG,UAAU,CAAC,YAAY,CACxD,gBAAgB,CAAC,2BAA2B,CAC7C,CAAC;QACF,IAAI,CAAC,gBAAgB,GAAG,gBAAgB,CAAC;QACzC,IAAI,CAAC,MAAM,GAAG,WAAW,IAAI,CAAC,IAAI,EAAE,CAAC;QAErC,IAAI,CAAC,OAAO,GAAG,GAAG,IAAI,CAAC,MAAM,IAAI,IAAI,CAAC,GAAG,IAAI,EAAE,EAAE,CAAC;QAClD,IAAI,CAAC,qBAAqB,GAAG,UAAU,CAAC,YAAY,CAClD,gBAAgB,CAAC,qBAAqB,CACvC,CAAC;QAEF,IAAI,CAAC,iBAAiB,GAAG,iBAAiB,CAAC;IAC7C,CAAC;IAED;;;OAGG;IACH,oBAAoB;QAClB,MAAM,qBAAqB,GAAG,IAAI,CAAC,wBAAwB,EAAE,CAAC;QAC9D,MAAM,8BAA8B,GAClC,qBAAqB,KAAK,IAAI,CAAC,QAAQ,CAAC;QAE1C,OAAO,8BAA8B;YACnC,CAAC,CAAC,UAAU,CAAC,wBAAwB;YACrC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IAC5B,CAAC;IAED;;OAEG;IACH,gBAAgB,CAAC,KAA0B;QACzC,MAAM,QAAQ,GAAG,IAAI,CAAC,cAAc,CAAC,KAAK,CAAC,CAAC;QAC5C,OAAO,IAAI,CAAC,uBAAuB,CAAC,QAAQ,CAAC,CAAC;IAChD,CAAC;IAED;;OAEG;IACH,WAAW,CAAC,KAA0B;QACpC,MAAM,QAAQ,GAAG,IAAI,CAAC,cAAc,CAAC,KAAK,CAAC,CAAC;QAC5C,OAAO,IAAI,CAAC,kBAAkB,CAAC,QAAQ,CAAC,CAAC;IAC3C,CAAC;IAED;;OAEG;IACH,MAAM,CAAC,KAA0B;QAC/B,MAAM,QAAQ,GAAG,IAAI,CAAC,cAAc,CAAC,KAAK,CAAC,CAAC;QAC5C,OAAO,IAAI,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAC;IACtC,CAAC;IAED;;;OAGG;IACH,wBAAwB;QACtB,MAAM,mBAAmB,GACvB,IAAI,CAAC,iBAAiB,CAAC,aAAa,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QAEpD,OAAO,mBAAmB,CAAC,eAAe,CAAC;IAC7C,CAAC;IAED;;;OAGG;IACH,mBAAmB,CAAC,EAClB,mBAAmB,GAAG,EAAE,MAGtB,EAAE;QACJ,OAAO,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE;YACjC,MAAM,kBAAkB,GAAG,mBAAmB,CAAC,IAAI,CAAC,CAAC,SAAS,EAAE,EAAE;gBAChE,MAAM,mBAAmB,GACvB,IAAI,CAAC,kBAAkB,CAAC,sBAAsB,CAAC;oBAC7C,aAAa,EAAE,SAAS,CAAC,SAAS;iBACnC,CAAC,CAAC;gBACL,OAAO,mBAAmB,CAAC,MAAM,KAAK,MAAM,CAAC;YAC/C,CAAC,CAAC,CAAC;YAEH,IAAI,kBAAkB,EAAE,CAAC;gBACvB,MAAM,sBAAsB,GAAG,IAAI,CAAC,kBAAkB,CAAC,eAAe,CAAC;oBACrE,MAAM;iBACP,CAAC,CAAC;gBACH,MAAM,gBAAgB,GAAG,IAAI,UAAU,CACrC,IAAI,CAAC,OAAO,EACZ,IAAI,CAAC,SAAS,EACd,sBAAsB,EACtB,IAAI,CAAC,iBAAiB,EACtB,IAAI,CAAC,kBAAkB,CACxB,CAAC;gBACF,MAAM,GAAG,GAAG,gBAAgB,CAAC,MAAM,CAAC;oBAClC,aAAa,EAAE,kBAAkB,CAAC,SAAS;iBAC5C,CAAC,CAAC;gBAEH,OAAO;oBACL,QAAQ,EAAE,IAAI,CAAC,QAAQ;oBACvB,MAAM;oBACN,GAAG;oBACH,SAAS,EAAE,MAAM,KAAK,IAAI,CAAC,MAAM;oBACjC,MAAM,EAAE,gBAAgB,CAAC,MAAM;iBAChC,CAAC;YACJ,CAAC;YAED,MAAM,sBAAsB,GAAG,IAAI,CAAC,kBAAkB,CAAC,eAAe,CAAC;gBACrE,MAAM;aACP,CAAC,CAAC;YACH,MAAM,gBAAgB,GAAG,IAAI,UAAU,CACrC,IAAI,CAAC,OAAO,EACZ,IAAI,CAAC,SAAS,EACd,sBAAsB,EACtB,IAAI,CAAC,iBAAiB,EACtB,IAAI,CAAC,kBAAkB,CACxB,CAAC;YAEF,OAAO;gBACL,QAAQ,EAAE,gBAAgB,CAAC,QAAQ;gBACnC,MAAM;gBACN,GAAG,EAAE,gBAAgB,CAAC,OAAO;gBAC7B,SAAS,EAAE,MAAM,KAAK,IAAI,CAAC,MAAM;gBACjC,MAAM,EAAE,gBAAgB,CAAC,MAAM;aAChC,CAAC;QACJ,CAAC,CAAC,CAAC;IACL,CAAC;IAED;;OAEG;IACH,uBAAuB;QACrB,MAAM,qBAAqB,GAAG,IAAI,CAAC,wBAAwB,EAAE,CAAC;QAC9D,MAAM,8BAA8B,GAClC,qBAAqB,KAAK,IAAI,CAAC,QAAQ,CAAC;QAE1C,OAAO,8BAA8B,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC;IACpE,CAAC;IAED;;OAEG;IACH,+BAA+B,CAAC,KAAmB;QACjD,MAAM,wBAAwB,GAC5B,KAAK,CAAC,gBAAgB;YACpB,EAAE,MAAM,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC;aACpC,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,IAAiB,CAAC,IAAI,EAAE,CAAC;QAEvD,MAAM,qBAAqB,GAAG,IAAI,CAAC,wBAAwB,EAAE,CAAC;QAC9D,MAAM,cAAc,GAAG,CAAC,qBAAqB,EAAE,GAAG,wBAAwB,CAAC,CAAC;QAE5E,OAAO,cAAc,CAAC,QAAQ,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IAChD,CAAC;IAED;;OAEG;IACI,qBAAqB,CAC1B,KAAmB,EACnB,kBAAiE;QAEjE,MAAM,kBAAkB,GAAG,KAAK,CAAC,gBAAgB,IAAI,EAAE,CAAC;QAExD,MAAM,gBAAgB,GAAG,kBAAkB,CAAC,sBAAsB,CAAC;YACjE,aAAa,EAAE,KAAK,CAAC,SAAS,IAAI,EAAE;SACrC,CAAC,CAAC;QAEH,MAAM,UAAU,GAAG,IAAI,UAAU,CAC/B,IAAI,CAAC,OAAO,EACZ,IAAI,CAAC,SAAS,EACd,gBAAgB,EAChB,IAAI,CAAC,iBAAiB,EACtB,IAAI,CAAC,kBAAkB,CACxB,CAAC;QAEF,MAAM,cAAc,GAAG;YACrB,IAAI,EAAE,KAAK,CAAC,iBAAiB,IAAI,EAAE;YACnC,IAAI,EAAE,UAAU,CAAC,wBAAwB,EAAE;YAC3C,IAAI,EAAE,KAAK,CAAC,IAAI;YAChB,SAAS,EAAE,KAAK,CAAC,SAAS,IAAI,KAAK,CAAC,YAAY,IAAI,CAAC,CAAC,KAAK,CAAC,YAAY;SACzE,CAAC;QAEF,MAAM,mBAAmB,GAAG,CAAC,cAAc,EAAE,GAAG,kBAAkB,CAAC,CAAC;QACpE,MAAM,yBAAyB,GAAG,mBAAmB;aAClD,GAAG,CAAC,CAAC,WAAW,EAAE,EAAE;YACnB,MAAM,WAAW,GAAG,WAAW,IAAI,WAAW,IAAI,WAAW,CAAC,SAAS,CAAC;YACxE,MAAM,iBAAiB,GAAG,WAAW,CAAC,IAAI,KAAK,UAAU,CAAC,QAAQ,CAAC;YACnE,IAAI,CAAC,CAAC,WAAW,IAAI,CAAC,iBAAiB,CAAC,IAAI,CAAC,WAAW,CAAC,IAAI;gBAC3D,OAAO,IAAI,CAAC;YAEd,MAAM,QAAQ,GAAG,IAAI,CAAC,SAAS,CAAC,IAAI,CAClC,CAAC,QAAQ,EAAE,EAAE,CAAC,QAAQ,KAAK,WAAW,CAAC,IAAI,CAC5C,CAAC;YACF,IAAI,CAAC,QAAQ;gBAAE,OAAO,IAAI,CAAC;YAE3B,MAAM,wBAAwB,GAC5B,kBAAkB,CAAC,4BAA4B,CAC7C,CAAC,gBAAgB,EAAE,EAAE;gBACnB,MAAM,aAAa,GAAG,gBAAgB,CAAC,QAAQ,KAAK,QAAQ,CAAC;gBAE7D,MAAM,uBAAuB,GAAG,UAAU,CAAC,YAAY,CACrD,WAAW,CAAC,IAAI,CACjB,CAAC;gBAEF,MAAM,oBAAoB,GAAG,uBAAuB,CAAC,UAAU,CAC7D,gBAAgB,CAAC,qBAAqB,CACvC,CAAC;gBAEF,OAAO,aAAa,IAAI,oBAAoB,CAAC;YAC/C,CAAC,CACF,CAAC;YAEJ,IAAI,CAAC,wBAAwB;gBAAE,OAAO,IAAI,CAAC;YAE3C,MAAM,kBAAkB,GAAG,IAAI,UAAU,CACvC,IAAI,CAAC,OAAO,EACZ,IAAI,CAAC,SAAS,EACd,wBAAwB,EACxB,IAAI,CAAC,iBAAiB,EACtB,IAAI,CAAC,kBAAkB,CACxB,CAAC;YAEF,MAAM,GAAG,GAAG,kBAAkB,CAAC,MAAM,CAAC;gBACpC,aAAa,EAAE,WAAW,CAAC,IAAI;aAChC,CAAC,CAAC;YAEH,OAAO;gBACL,QAAQ;gBACR,SAAS,EAAE,WAAW,CAAC,IAAI,KAAK,UAAU,CAAC,QAAQ;gBACnD,GAAG;gBACH,MAAM,EAAE,kBAAkB,CAAC,MAAM;gBACjC,MAAM,EAAE,kBAAkB,CAAC,MAAM;aAClC,CAAC;QACJ,CAAC,CAAC;aACD,MAAM,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,KAAK,IAAI,CAAC,CAAC;QAEzC,OAAO,yBAAyB,CAAC;IACnC,CAAC;IAED;;OAEG;IACH,MAAM,CAAC,YAAY,CAAC,IAAa;QAC/B,OAAO,IAAI,EAAE,OAAO,CAAC,YAAY,EAAE,EAAE,CAAC,IAAI,EAAE,CAAC;IAC/C,CAAC;IAED;;OAEG;IACH,MAAM,CAAC,YAAY,CAAC,GAAG,KAAoC;QACzD,OAAO,KAAK,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IAC9D,CAAC;IAED,+CAA+C;IAEvC,uBAAuB,CAAC,aAAqB;QACnD,IAAI,MAAM,GAAG,aAAa,CAAC;QAE3B,IACE,IAAI,CAAC,2BAA2B;YAChC,MAAM,CAAC,UAAU,CAAC,IAAI,CAAC,2BAA2B,CAAC,EACnD,CAAC;YACD,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,2BAA2B,CAAC,MAAM,CAAC,CAAC;QACjE,CAAC;QAED,IACE,IAAI,CAAC,qBAAqB;YAC1B,MAAM,CAAC,UAAU,CAAC,IAAI,CAAC,qBAAqB,CAAC,EAC7C,CAAC;YACD,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,qBAAqB,CAAC,MAAM,CAAC,CAAC;QAC3D,CAAC;QAED,OAAO,UAAU,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC;IACzC,CAAC;IAEO,uBAAuB,CAAC,QAAgB;QAC9C,OAAO,UAAU,CAAC,YAAY,CAAC,IAAI,CAAC,qBAAqB,EAAE,QAAQ,CAAC,CAAC;IACvE,CAAC;IAEO,aAAa,CAAC,QAAgB;QACpC,MAAM,QAAQ,GAAG,IAAI,CAAC,kBAAkB,CAAC,QAAQ,CAAC,CAAC;QACnD,OAAO,GAAG,IAAI,CAAC,MAAM,GAAG,QAAQ,EAAE,CAAC;IACrC,CAAC;IAEO,aAAa,CAAC,GAAW;QAC/B,MAAM,SAAS,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC;QAC/B,OAAO,IAAI,CAAC,kBAAkB,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC;IACrD,CAAC;IAEO,kBAAkB,CAAC,QAAgB;QACzC,MAAM,YAAY,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACpD,OAAO,QAAQ,CAAC,OAAO,CAAC,YAAY,EAAE,EAAE,CAAC,CAAC;IAC5C,CAAC;IAEO,kBAAkB,CAAC,QAAgB;QACzC,OAAO,IAAI,UAAU,CAAC,YAAY,CAAC,IAAI,CAAC,GAAG,EAAE,QAAQ,CAAC,EAAE,CAAC;IAC3D,CAAC;IAEO,cAAc,CAAC,KAA0B;QAC/C,IAAI,KAAK,CAAC,QAAQ,KAAK,SAAS,EAAE,CAAC;YACjC,MAAM,kBAAkB,GAAG,UAAU,CAAC,YAAY,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;YACnE,OAAO,IAAI,CAAC,kBAAkB,CAAC,kBAAkB,CAAC,CAAC;QACrD,CAAC;QAED,IAAI,KAAK,CAAC,aAAa,KAAK,SAAS,EAAE,CAAC;YACtC,MAAM,uBAAuB,GAAG,UAAU,CAAC,YAAY,CACrD,KAAK,CAAC,aAAa,CACpB,CAAC;YACF,OAAO,IAAI,CAAC,uBAAuB,CAAC,uBAAuB,CAAC,CAAC;QAC/D,CAAC;QAED,IAAI,KAAK,CAAC,GAAG,KAAK,SAAS,EAAE,CAAC;YAC5B,MAAM,aAAa,GAAG,UAAU,CAAC,YAAY,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;YACzD,OAAO,IAAI,CAAC,aAAa,CAAC,aAAa,CAAC,CAAC;QAC3C,CAAC;QAED,MAAM,IAAI,KAAK,CAAC,uDAAuD,CAAC,CAAC;IAC3E,CAAC"}