@majordigital/create-acorn 1.3.0 → 1.3.2
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/bin/create-acorn.mjs +73 -129
- package/package.json +2 -1
- package/storyblok/storyblok-api/package.json +15 -0
- package/storyblok/storyblok-api/src/lib/buildCache.ts +64 -0
- package/storyblok/storyblok-api/src/lib/storyblok/bloks.ts +24 -0
- package/storyblok/storyblok-api/src/lib/storyblok/fetch/config.ts +47 -0
- package/storyblok/storyblok-api/src/lib/storyblok/fetch/fetchBreadcrumbs.ts +125 -0
- package/storyblok/storyblok-api/src/lib/storyblok/fetch/fetchCv.ts +45 -0
- package/storyblok/storyblok-api/src/lib/storyblok/fetch/fetchGlobal.ts +48 -0
- package/storyblok/storyblok-api/src/lib/storyblok/fetch/fetchIcons.ts +108 -0
- package/storyblok/storyblok-api/src/lib/storyblok/fetch/fetchPaths.ts +164 -0
- package/storyblok/storyblok-api/src/lib/storyblok/fetch/fetchSitemap.ts +103 -0
- package/storyblok/storyblok-api/src/lib/storyblok/fetch/fetchStories.ts +105 -0
- package/storyblok/storyblok-api/src/lib/storyblok/fetch/fetchStory.ts +95 -0
- package/storyblok/storyblok-api/src/lib/storyblok/helpers.ts +111 -0
- package/storyblok/storyblok-api/src/lib/storyblok/index.ts +16 -0
- package/storyblok/storyblok-api/src/lib/storyblok/mergedRelations.ts +22 -0
- package/storyblok/storyblok-api/src/lib/storyblok/redirects.js +50 -0
- package/storyblok/storyblok-api/src/lib/storyblok/seoMetadata.ts +124 -0
- package/storyblok/storyblok-api/src/lib/storyblok/types.ts +48 -0
- package/storyblok/storyblok-api/src/lib/storyblok/utils/previewTokenValidator.ts +14 -0
- package/storyblok/storyblok-api/src/ui/FallbackComponent.tsx +24 -0
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Merges Storyblok resolve_relations arrays or strings with a base set, ensuring uniqueness.
|
|
3
|
+
*
|
|
4
|
+
* @param {boolean} enable - Whether to enable merging. If false, returns undefined.
|
|
5
|
+
* @param {string | string[] | undefined} incoming - Incoming relations (string or array).
|
|
6
|
+
* @param {readonly string[]} base - Base relations array to merge with.
|
|
7
|
+
* @returns {string[] | undefined} - Merged unique relations array, or undefined if not enabled.
|
|
8
|
+
*/
|
|
9
|
+
export function mergeResolveRelations(
|
|
10
|
+
enable: boolean,
|
|
11
|
+
incoming: string | string[] | undefined,
|
|
12
|
+
base: readonly string[]
|
|
13
|
+
): string[] | undefined {
|
|
14
|
+
if (!enable) return undefined;
|
|
15
|
+
if (Array.isArray(incoming) && incoming.length > 0) {
|
|
16
|
+
return Array.from(new Set<string>([...incoming, ...base]));
|
|
17
|
+
}
|
|
18
|
+
if (typeof incoming === 'string' && incoming.trim() !== '') {
|
|
19
|
+
return Array.from(new Set<string>([incoming, ...base]));
|
|
20
|
+
}
|
|
21
|
+
return [...base];
|
|
22
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
const StoryblokClient = require('storyblok-js-client').default;
|
|
2
|
+
|
|
3
|
+
async function getRedirects() {
|
|
4
|
+
const Storyblok = new StoryblokClient({
|
|
5
|
+
accessToken: process.env.NEXT_PUBLIC_STORYBLOK_PUBLIC_TOKEN,
|
|
6
|
+
cache: { clear: 'auto', type: 'memory' },
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
let data;
|
|
10
|
+
try {
|
|
11
|
+
const response = await Storyblok.get('cdn/stories/redirects', {
|
|
12
|
+
version: 'published',
|
|
13
|
+
resolve_relations: 'redirect_entry.target',
|
|
14
|
+
});
|
|
15
|
+
data = response.data;
|
|
16
|
+
} catch (_error) {
|
|
17
|
+
// If the record is not found, return an empty object
|
|
18
|
+
return {};
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
if (
|
|
22
|
+
!data.story ||
|
|
23
|
+
!data.story.content ||
|
|
24
|
+
!data.story.content.redirect_entries
|
|
25
|
+
) {
|
|
26
|
+
return {};
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const redirects = data.story.content.redirect_entries || [];
|
|
30
|
+
const processedRedirects = {};
|
|
31
|
+
|
|
32
|
+
redirects.forEach(redirect => {
|
|
33
|
+
const { target, source_url: sourceUrl } = redirect;
|
|
34
|
+
|
|
35
|
+
if (target.linktype === 'story') {
|
|
36
|
+
// If the target is a story, we need to resolve its URL
|
|
37
|
+
const targetUrl =
|
|
38
|
+
target.cached_url === 'home' ? '/' : `/${target.cached_url}`;
|
|
39
|
+
processedRedirects[sourceUrl] = targetUrl;
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// If the target is not a story, we can use its cached_url directly
|
|
44
|
+
processedRedirects[sourceUrl] = target.cached_url;
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
return processedRedirects;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
module.exports = { getRedirects };
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import type { Metadata } from 'next';
|
|
2
|
+
|
|
3
|
+
import { siteConfig } from '@/lib/config';
|
|
4
|
+
|
|
5
|
+
interface StoryblokSeoComponent {
|
|
6
|
+
title: string;
|
|
7
|
+
description: string;
|
|
8
|
+
og_title: string;
|
|
9
|
+
og_description: string;
|
|
10
|
+
og_image: string;
|
|
11
|
+
twitter_title: string;
|
|
12
|
+
twitter_description: string;
|
|
13
|
+
twitter_image: string;
|
|
14
|
+
no_index?: boolean;
|
|
15
|
+
no_follow?: boolean;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
interface StoryblokSeoFallback {
|
|
19
|
+
title?: string;
|
|
20
|
+
description?: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
interface Config {
|
|
24
|
+
slug?: string;
|
|
25
|
+
twitterCreator?: string;
|
|
26
|
+
googleVerificationId?: string;
|
|
27
|
+
siteName?: string;
|
|
28
|
+
fallback?: StoryblokSeoFallback;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export const getStoryblokSeoData = (
|
|
32
|
+
data: StoryblokSeoComponent | null,
|
|
33
|
+
config: Config
|
|
34
|
+
): Metadata => {
|
|
35
|
+
if (!config && !data) {
|
|
36
|
+
return {};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const { googleVerificationId, slug, twitterCreator, siteName, fallback } =
|
|
40
|
+
config || {};
|
|
41
|
+
|
|
42
|
+
const fallbackTitle = fallback?.title || '';
|
|
43
|
+
const fallbackDescription = fallback?.description || '';
|
|
44
|
+
|
|
45
|
+
const fallbackMetadata = {
|
|
46
|
+
metadataBase: new URL(
|
|
47
|
+
process.env.NEXT_PUBLIC_APP_URL || siteConfig.url
|
|
48
|
+
),
|
|
49
|
+
title: fallbackTitle,
|
|
50
|
+
description: fallbackDescription,
|
|
51
|
+
alternates: {
|
|
52
|
+
canonical: slug === 'home' ? '/' : slug,
|
|
53
|
+
},
|
|
54
|
+
openGraph: {
|
|
55
|
+
title: fallbackTitle,
|
|
56
|
+
description: fallbackDescription,
|
|
57
|
+
url: slug,
|
|
58
|
+
siteName,
|
|
59
|
+
type: 'website',
|
|
60
|
+
},
|
|
61
|
+
twitter: {
|
|
62
|
+
card: 'summary_large_image',
|
|
63
|
+
title: fallbackTitle,
|
|
64
|
+
description: fallbackDescription,
|
|
65
|
+
site: twitterCreator,
|
|
66
|
+
creator: twitterCreator,
|
|
67
|
+
},
|
|
68
|
+
...(googleVerificationId && {
|
|
69
|
+
verification: {
|
|
70
|
+
google: `google-site-verification=${googleVerificationId}`,
|
|
71
|
+
},
|
|
72
|
+
}),
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
if (!data) {
|
|
76
|
+
return fallbackMetadata;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const {
|
|
80
|
+
title,
|
|
81
|
+
description,
|
|
82
|
+
og_title,
|
|
83
|
+
og_description,
|
|
84
|
+
og_image,
|
|
85
|
+
twitter_title,
|
|
86
|
+
twitter_description,
|
|
87
|
+
twitter_image,
|
|
88
|
+
} = data;
|
|
89
|
+
|
|
90
|
+
const customMetaData = {
|
|
91
|
+
title: title || fallbackTitle,
|
|
92
|
+
description: description || fallbackDescription,
|
|
93
|
+
openGraph: {
|
|
94
|
+
title: og_title || title || fallbackTitle,
|
|
95
|
+
description: og_description || description || fallbackDescription,
|
|
96
|
+
url: slug,
|
|
97
|
+
siteName,
|
|
98
|
+
...(og_image && {
|
|
99
|
+
images: {
|
|
100
|
+
url: og_image,
|
|
101
|
+
},
|
|
102
|
+
}),
|
|
103
|
+
type: 'website',
|
|
104
|
+
},
|
|
105
|
+
twitter: {
|
|
106
|
+
card: 'summary_large_image',
|
|
107
|
+
title: twitter_title || og_title || title || fallbackTitle,
|
|
108
|
+
description:
|
|
109
|
+
twitter_description ||
|
|
110
|
+
og_description ||
|
|
111
|
+
description ||
|
|
112
|
+
fallbackDescription,
|
|
113
|
+
site: twitterCreator,
|
|
114
|
+
creator: twitterCreator,
|
|
115
|
+
...(twitter_image && {
|
|
116
|
+
images: {
|
|
117
|
+
url: twitter_image || og_image,
|
|
118
|
+
},
|
|
119
|
+
}),
|
|
120
|
+
},
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
return { ...fallbackMetadata, ...customMetaData };
|
|
124
|
+
};
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { ISbStoriesParams, ISbStoryData } from '@storyblok/react';
|
|
2
|
+
|
|
3
|
+
import type { BreadcrumbItemProps } from './fetch/fetchBreadcrumbs';
|
|
4
|
+
|
|
5
|
+
export interface Paths {
|
|
6
|
+
slug: string[];
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface ICustomSbStoryData extends Omit<ISbStoryData, 'breadcrumbs'> {
|
|
10
|
+
breadcrumbs?: BreadcrumbItemProps[];
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface ICustomSbStoriesParams extends ISbStoriesParams {
|
|
14
|
+
limit?: number;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface StoryblokLinkObject {
|
|
18
|
+
id: number;
|
|
19
|
+
uuid: string;
|
|
20
|
+
slug: string;
|
|
21
|
+
path: string;
|
|
22
|
+
parent_id: number;
|
|
23
|
+
name: string;
|
|
24
|
+
is_folder: boolean;
|
|
25
|
+
published: boolean;
|
|
26
|
+
is_startpage: boolean;
|
|
27
|
+
position: number;
|
|
28
|
+
real_path: string;
|
|
29
|
+
published_at?: string;
|
|
30
|
+
created_at?: string;
|
|
31
|
+
updated_at?: string;
|
|
32
|
+
alternates?: {
|
|
33
|
+
path: string;
|
|
34
|
+
name: string;
|
|
35
|
+
lang: string;
|
|
36
|
+
published: boolean;
|
|
37
|
+
translated_slug: string;
|
|
38
|
+
}[];
|
|
39
|
+
}
|
|
40
|
+
export interface StoryblokServiceDefaults {
|
|
41
|
+
draftMode?: boolean;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export interface StoryblokServiceStory extends StoryblokServiceDefaults {
|
|
45
|
+
slug: string;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export type DateFormat = 'monthDay' | 'monthYear' | 'dayMonthYear';
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import crypto from 'node:crypto';
|
|
2
|
+
|
|
3
|
+
export const verifyPreviewToken = (token: string, timestamp: string) => {
|
|
4
|
+
const validationString = `${process.env.STORYBLOK_SPACE_ID}:${process.env.STORYBLOK_PREVIEW_TOKEN}:${timestamp}`;
|
|
5
|
+
const validationToken = crypto
|
|
6
|
+
.createHash('sha1')
|
|
7
|
+
.update(validationString)
|
|
8
|
+
.digest('hex');
|
|
9
|
+
|
|
10
|
+
return (
|
|
11
|
+
token === validationToken &&
|
|
12
|
+
parseInt(timestamp, 10) > Math.floor(Date.now() / 1000) - 3600
|
|
13
|
+
);
|
|
14
|
+
};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { storyblokEditable } from '@storyblok/react';
|
|
4
|
+
|
|
5
|
+
const StoryblokFallback = ({ blok }: { blok?: Record<string, any> }) => {
|
|
6
|
+
return (
|
|
7
|
+
blok && (
|
|
8
|
+
<div
|
|
9
|
+
className="my-4 rounded-lg border border-red-400 bg-red-50 p-4 text-red-700"
|
|
10
|
+
{...storyblokEditable(blok)}
|
|
11
|
+
>
|
|
12
|
+
<div className="font-semibold">
|
|
13
|
+
⚠️ Missing component: {blok.component}
|
|
14
|
+
</div>
|
|
15
|
+
|
|
16
|
+
<pre className="mt-2 max-h-36 overflow-x-auto rounded bg-red-100 p-2 font-mono text-red-900 text-xs">
|
|
17
|
+
{JSON.stringify(blok, null, 2)}
|
|
18
|
+
</pre>
|
|
19
|
+
</div>
|
|
20
|
+
)
|
|
21
|
+
);
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export default StoryblokFallback;
|