@djangocfg/nextjs 2.1.42 → 2.1.43
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/dist/ai/cli.mjs.map +1 -1
- package/dist/ai/index.mjs.map +1 -1
- package/dist/config/index.mjs +261 -263
- package/dist/config/index.mjs.map +1 -1
- package/dist/health/index.mjs.map +1 -1
- package/dist/index.mjs +580 -582
- package/dist/index.mjs.map +1 -1
- package/dist/og-image/index.mjs +468 -468
- package/dist/og-image/index.mjs.map +1 -1
- package/dist/og-image/utils/index.mjs.map +1 -1
- package/dist/pwa/server/index.mjs +1 -1
- package/dist/pwa/server/index.mjs.map +1 -1
- package/dist/pwa/server/routes.mjs +1 -1
- package/dist/pwa/server/routes.mjs.map +1 -1
- package/dist/pwa/worker/index.mjs +1 -1
- package/dist/pwa/worker/index.mjs.map +1 -1
- package/dist/scripts/index.mjs +75 -73
- package/dist/scripts/index.mjs.map +1 -1
- package/dist/sitemap/index.mjs.map +1 -1
- package/package.json +4 -4
- package/src/ai/cli.ts +2 -1
- package/src/ai/client.ts +2 -6
- package/src/config/createNextConfig.ts +5 -5
- package/src/config/packages/checker.ts +3 -2
- package/src/config/packages/installer.ts +6 -5
- package/src/config/packages/updater.ts +5 -4
- package/src/config/plugins/devStartup.ts +3 -2
- package/src/config/utils/version.ts +4 -3
- package/src/health/route.ts +1 -0
- package/src/og-image/route.tsx +4 -2
- package/src/og-image/utils/metadata.ts +1 -1
- package/src/pwa/server/push.ts +1 -1
- package/src/pwa/server/routes.ts +1 -0
- package/src/pwa/worker/index.ts +2 -1
- package/src/scripts/check-links.ts +4 -4
- package/src/sitemap/route.ts +3 -1
package/dist/og-image/index.mjs
CHANGED
|
@@ -2,312 +2,177 @@
|
|
|
2
2
|
import { ImageResponse } from "next/og";
|
|
3
3
|
import { NextRequest } from "next/server";
|
|
4
4
|
|
|
5
|
-
// src/og-image/
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
if (!cache.has(cacheKey)) {
|
|
55
|
-
cache.set(cacheKey, loadGoogleFont(family, text, weight));
|
|
56
|
-
}
|
|
57
|
-
return cache.get(cacheKey);
|
|
58
|
-
},
|
|
59
|
-
/**
|
|
60
|
-
* Clear the cache
|
|
61
|
-
*/
|
|
62
|
-
clear() {
|
|
63
|
-
cache.clear();
|
|
64
|
-
},
|
|
65
|
-
/**
|
|
66
|
-
* Get cache size
|
|
67
|
-
*/
|
|
68
|
-
size() {
|
|
69
|
-
return cache.size;
|
|
70
|
-
}
|
|
71
|
-
};
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
// src/og-image/utils/url.ts
|
|
75
|
-
var DEFAULT_OG_IMAGE_BASE_URL = "https://djangocfg.com/api/og";
|
|
76
|
-
function encodeBase64(str) {
|
|
77
|
-
if (typeof Buffer !== "undefined") {
|
|
78
|
-
return Buffer.from(str, "utf-8").toString("base64");
|
|
79
|
-
}
|
|
80
|
-
return btoa(unescape(encodeURIComponent(str)));
|
|
81
|
-
}
|
|
82
|
-
function decodeBase64(str) {
|
|
83
|
-
if (typeof Buffer !== "undefined") {
|
|
84
|
-
return Buffer.from(str, "base64").toString("utf-8");
|
|
85
|
-
}
|
|
86
|
-
try {
|
|
87
|
-
const binaryString = atob(str);
|
|
88
|
-
return decodeURIComponent(
|
|
89
|
-
binaryString.split("").map((c) => "%" + ("00" + c.charCodeAt(0).toString(16)).slice(-2)).join("")
|
|
90
|
-
);
|
|
91
|
-
} catch (error) {
|
|
92
|
-
return decodeURIComponent(escape(atob(str)));
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
function generateOgImageUrl(params, options = {}) {
|
|
96
|
-
const {
|
|
97
|
-
baseUrl = DEFAULT_OG_IMAGE_BASE_URL,
|
|
98
|
-
useBase64 = true
|
|
99
|
-
} = options;
|
|
100
|
-
if (useBase64) {
|
|
101
|
-
const cleanParams = {};
|
|
102
|
-
Object.entries(params).forEach(([key, value]) => {
|
|
103
|
-
if (value !== void 0 && value !== null && value !== "") {
|
|
104
|
-
cleanParams[key] = value;
|
|
105
|
-
}
|
|
106
|
-
});
|
|
107
|
-
const jsonString = JSON.stringify(cleanParams);
|
|
108
|
-
const base64Data = encodeBase64(jsonString);
|
|
109
|
-
return `${baseUrl}/${base64Data}`;
|
|
110
|
-
} else {
|
|
111
|
-
const searchParams = new URLSearchParams();
|
|
112
|
-
Object.entries(params).forEach(([key, value]) => {
|
|
113
|
-
if (value !== void 0 && value !== null && value !== "") {
|
|
114
|
-
searchParams.append(key, String(value));
|
|
115
|
-
}
|
|
116
|
-
});
|
|
117
|
-
const query = searchParams.toString();
|
|
118
|
-
return query ? `${baseUrl}?${query}` : baseUrl;
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
function getAbsoluteOgImageUrl(relativePath, siteUrl) {
|
|
122
|
-
if (relativePath.startsWith("http://") || relativePath.startsWith("https://")) {
|
|
123
|
-
return relativePath;
|
|
124
|
-
}
|
|
125
|
-
const cleanSiteUrl = siteUrl.replace(/\/$/, "");
|
|
126
|
-
const cleanPath = relativePath.startsWith("/") ? relativePath : `/${relativePath}`;
|
|
127
|
-
return `${cleanSiteUrl}${cleanPath}`;
|
|
128
|
-
}
|
|
129
|
-
function createOgImageUrlBuilder(defaults = {}, options = {}) {
|
|
130
|
-
return (params) => {
|
|
131
|
-
return generateOgImageUrl(
|
|
132
|
-
{ ...defaults, ...params },
|
|
133
|
-
options
|
|
134
|
-
);
|
|
135
|
-
};
|
|
136
|
-
}
|
|
137
|
-
function parseOgImageUrl(url) {
|
|
138
|
-
try {
|
|
139
|
-
const urlObj = new URL(url, "http://dummy.com");
|
|
140
|
-
const params = {};
|
|
141
|
-
urlObj.searchParams.forEach((value, key) => {
|
|
142
|
-
params[key] = value;
|
|
143
|
-
});
|
|
144
|
-
return params;
|
|
145
|
-
} catch {
|
|
146
|
-
return {};
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
function parseOgImageData(searchParams) {
|
|
150
|
-
try {
|
|
151
|
-
let params;
|
|
152
|
-
if (searchParams instanceof URLSearchParams) {
|
|
153
|
-
params = {};
|
|
154
|
-
for (const [key, value] of searchParams.entries()) {
|
|
155
|
-
params[key] = value;
|
|
5
|
+
// src/og-image/components/DefaultTemplate.tsx
|
|
6
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
7
|
+
function DefaultTemplate({
|
|
8
|
+
title,
|
|
9
|
+
description,
|
|
10
|
+
siteName,
|
|
11
|
+
logo,
|
|
12
|
+
// Visibility flags
|
|
13
|
+
showLogo = true,
|
|
14
|
+
showSiteName = true,
|
|
15
|
+
// Background customization
|
|
16
|
+
backgroundType = "gradient",
|
|
17
|
+
gradientStart = "#667eea",
|
|
18
|
+
gradientEnd = "#764ba2",
|
|
19
|
+
backgroundColor = "#ffffff",
|
|
20
|
+
// Typography - Title
|
|
21
|
+
titleSize,
|
|
22
|
+
titleWeight = 800,
|
|
23
|
+
titleColor = "white",
|
|
24
|
+
// Typography - Description
|
|
25
|
+
descriptionSize = 32,
|
|
26
|
+
descriptionColor = "rgba(255, 255, 255, 0.85)",
|
|
27
|
+
// Typography - Site Name
|
|
28
|
+
siteNameSize = 28,
|
|
29
|
+
siteNameColor = "rgba(255, 255, 255, 0.95)",
|
|
30
|
+
// Layout
|
|
31
|
+
padding = 80,
|
|
32
|
+
logoSize = 48,
|
|
33
|
+
// Dev mode
|
|
34
|
+
devMode = false
|
|
35
|
+
}) {
|
|
36
|
+
const calculatedTitleSize = titleSize || (title.length > 60 ? 56 : 72);
|
|
37
|
+
const backgroundStyle = backgroundType === "gradient" ? `linear-gradient(135deg, ${gradientStart} 0%, ${gradientEnd} 100%)` : backgroundColor;
|
|
38
|
+
const gridOverlay = devMode ? /* @__PURE__ */ jsx(
|
|
39
|
+
"div",
|
|
40
|
+
{
|
|
41
|
+
style: {
|
|
42
|
+
position: "absolute",
|
|
43
|
+
top: 0,
|
|
44
|
+
left: 0,
|
|
45
|
+
right: 0,
|
|
46
|
+
bottom: 0,
|
|
47
|
+
backgroundImage: `
|
|
48
|
+
linear-gradient(rgba(0, 0, 0, 0.1) 1px, transparent 1px),
|
|
49
|
+
linear-gradient(90deg, rgba(0, 0, 0, 0.1) 1px, transparent 1px)
|
|
50
|
+
`,
|
|
51
|
+
backgroundSize: "20px 20px",
|
|
52
|
+
pointerEvents: "none",
|
|
53
|
+
zIndex: 10
|
|
156
54
|
}
|
|
157
|
-
} else {
|
|
158
|
-
params = searchParams;
|
|
159
|
-
}
|
|
160
|
-
if (process.env.NODE_ENV === "development") {
|
|
161
|
-
console.log("[parseOgImageData] Input params keys:", Object.keys(params));
|
|
162
|
-
console.log("[parseOgImageData] Input params:", params);
|
|
163
55
|
}
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
56
|
+
) : null;
|
|
57
|
+
return /* @__PURE__ */ jsxs(
|
|
58
|
+
"div",
|
|
59
|
+
{
|
|
60
|
+
style: {
|
|
61
|
+
height: "100%",
|
|
62
|
+
width: "100%",
|
|
63
|
+
display: "flex",
|
|
64
|
+
flexDirection: "column",
|
|
65
|
+
alignItems: "flex-start",
|
|
66
|
+
justifyContent: "space-between",
|
|
67
|
+
background: backgroundStyle,
|
|
68
|
+
padding: `${padding}px`,
|
|
69
|
+
fontFamily: "system-ui, -apple-system, sans-serif",
|
|
70
|
+
position: "relative"
|
|
71
|
+
},
|
|
72
|
+
children: [
|
|
73
|
+
gridOverlay,
|
|
74
|
+
(showLogo && logo || showSiteName && siteName) && /* @__PURE__ */ jsxs(
|
|
75
|
+
"div",
|
|
76
|
+
{
|
|
77
|
+
style: {
|
|
78
|
+
display: "flex",
|
|
79
|
+
alignItems: "center",
|
|
80
|
+
gap: "16px"
|
|
81
|
+
},
|
|
82
|
+
children: [
|
|
83
|
+
showLogo && logo && // eslint-disable-next-line @next/next/no-img-element
|
|
84
|
+
/* @__PURE__ */ jsx(
|
|
85
|
+
"img",
|
|
86
|
+
{
|
|
87
|
+
src: logo,
|
|
88
|
+
alt: "Logo",
|
|
89
|
+
width: logoSize,
|
|
90
|
+
height: logoSize,
|
|
91
|
+
style: {
|
|
92
|
+
borderRadius: "8px"
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
),
|
|
96
|
+
showSiteName && siteName && /* @__PURE__ */ jsx(
|
|
97
|
+
"div",
|
|
98
|
+
{
|
|
99
|
+
style: {
|
|
100
|
+
fontSize: siteNameSize,
|
|
101
|
+
fontWeight: 600,
|
|
102
|
+
color: siteNameColor,
|
|
103
|
+
letterSpacing: "-0.02em"
|
|
104
|
+
},
|
|
105
|
+
children: siteName
|
|
106
|
+
}
|
|
107
|
+
)
|
|
108
|
+
]
|
|
182
109
|
}
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
ogImageBaseUrl = "https://djangocfg.com/api/og",
|
|
245
|
-
siteUrl: providedSiteUrl,
|
|
246
|
-
defaultParams = {},
|
|
247
|
-
useBase64 = true
|
|
248
|
-
} = options;
|
|
249
|
-
const siteUrl = providedSiteUrl && providedSiteUrl !== "undefined" ? providedSiteUrl : getSiteUrl();
|
|
250
|
-
const extractedTitle = extractTitle(metadata);
|
|
251
|
-
const extractedDescription = extractDescription(metadata);
|
|
252
|
-
const finalOgImageParams = {
|
|
253
|
-
...defaultParams,
|
|
254
|
-
title: ogImageParams?.title || extractedTitle || defaultParams.title || "",
|
|
255
|
-
description: ogImageParams?.description || extractedDescription || defaultParams.description || "",
|
|
256
|
-
...ogImageParams
|
|
257
|
-
};
|
|
258
|
-
const imageAlt = finalOgImageParams.title || finalOgImageParams.siteName;
|
|
259
|
-
const relativeOgImageUrl = generateOgImageUrl(
|
|
260
|
-
finalOgImageParams,
|
|
261
|
-
{ baseUrl: ogImageBaseUrl, useBase64 }
|
|
262
|
-
);
|
|
263
|
-
const ogImageUrl = siteUrl ? getAbsoluteOgImageUrl(relativeOgImageUrl, siteUrl) : relativeOgImageUrl;
|
|
264
|
-
const existingOgImages = metadata.openGraph?.images ? Array.isArray(metadata.openGraph.images) ? metadata.openGraph.images : [metadata.openGraph.images] : [];
|
|
265
|
-
const existingTwitterImages = metadata.twitter?.images ? Array.isArray(metadata.twitter.images) ? metadata.twitter.images : [metadata.twitter.images] : [];
|
|
266
|
-
const finalMetadata = {
|
|
267
|
-
...metadata,
|
|
268
|
-
openGraph: {
|
|
269
|
-
...metadata.openGraph,
|
|
270
|
-
images: [
|
|
271
|
-
...existingOgImages,
|
|
272
|
-
{
|
|
273
|
-
url: ogImageUrl,
|
|
274
|
-
width: 1200,
|
|
275
|
-
height: 630,
|
|
276
|
-
alt: imageAlt
|
|
277
|
-
}
|
|
278
|
-
]
|
|
279
|
-
},
|
|
280
|
-
twitter: {
|
|
281
|
-
...metadata.twitter,
|
|
282
|
-
card: "summary_large_image",
|
|
283
|
-
images: [
|
|
284
|
-
...existingTwitterImages,
|
|
285
|
-
{
|
|
286
|
-
url: ogImageUrl,
|
|
287
|
-
alt: imageAlt
|
|
288
|
-
}
|
|
110
|
+
),
|
|
111
|
+
/* @__PURE__ */ jsxs(
|
|
112
|
+
"div",
|
|
113
|
+
{
|
|
114
|
+
style: {
|
|
115
|
+
display: "flex",
|
|
116
|
+
flexDirection: "column",
|
|
117
|
+
gap: "24px",
|
|
118
|
+
flex: 1,
|
|
119
|
+
justifyContent: "center"
|
|
120
|
+
},
|
|
121
|
+
children: [
|
|
122
|
+
/* @__PURE__ */ jsx(
|
|
123
|
+
"div",
|
|
124
|
+
{
|
|
125
|
+
style: {
|
|
126
|
+
fontSize: calculatedTitleSize,
|
|
127
|
+
fontWeight: titleWeight,
|
|
128
|
+
color: titleColor,
|
|
129
|
+
lineHeight: 1.1,
|
|
130
|
+
letterSpacing: "-0.03em",
|
|
131
|
+
textShadow: backgroundType === "gradient" ? "0 2px 20px rgba(0, 0, 0, 0.2)" : "none",
|
|
132
|
+
maxWidth: "100%",
|
|
133
|
+
wordWrap: "break-word"
|
|
134
|
+
},
|
|
135
|
+
children: title
|
|
136
|
+
}
|
|
137
|
+
),
|
|
138
|
+
description && /* @__PURE__ */ jsx(
|
|
139
|
+
"div",
|
|
140
|
+
{
|
|
141
|
+
style: {
|
|
142
|
+
fontSize: descriptionSize,
|
|
143
|
+
fontWeight: 400,
|
|
144
|
+
color: descriptionColor,
|
|
145
|
+
lineHeight: 1.5,
|
|
146
|
+
letterSpacing: "-0.01em",
|
|
147
|
+
maxWidth: "90%",
|
|
148
|
+
display: "-webkit-box",
|
|
149
|
+
WebkitLineClamp: 2,
|
|
150
|
+
WebkitBoxOrient: "vertical",
|
|
151
|
+
overflow: "hidden"
|
|
152
|
+
},
|
|
153
|
+
children: description
|
|
154
|
+
}
|
|
155
|
+
)
|
|
156
|
+
]
|
|
157
|
+
}
|
|
158
|
+
),
|
|
159
|
+
/* @__PURE__ */ jsx(
|
|
160
|
+
"div",
|
|
161
|
+
{
|
|
162
|
+
style: {
|
|
163
|
+
display: "flex",
|
|
164
|
+
width: "100%",
|
|
165
|
+
height: "4px",
|
|
166
|
+
background: backgroundType === "gradient" ? `linear-gradient(90deg, ${gradientStart} 0%, ${gradientEnd} 100%)` : gradientStart,
|
|
167
|
+
borderRadius: "2px"
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
)
|
|
289
171
|
]
|
|
290
172
|
}
|
|
291
|
-
|
|
292
|
-
if (!finalMetadata.metadataBase && siteUrl) {
|
|
293
|
-
if (siteUrl.startsWith("http://") || siteUrl.startsWith("https://")) {
|
|
294
|
-
try {
|
|
295
|
-
finalMetadata.metadataBase = new URL(siteUrl);
|
|
296
|
-
} catch (e) {
|
|
297
|
-
}
|
|
298
|
-
}
|
|
299
|
-
}
|
|
300
|
-
return finalMetadata;
|
|
301
|
-
}
|
|
302
|
-
function createOgImageMetadataGenerator(options) {
|
|
303
|
-
return (metadata, ogImageParams) => {
|
|
304
|
-
return generateOgImageMetadata(metadata, ogImageParams, options);
|
|
305
|
-
};
|
|
173
|
+
);
|
|
306
174
|
}
|
|
307
|
-
|
|
308
|
-
// src/og-image/components/DefaultTemplate.tsx
|
|
309
|
-
import { jsx, jsxs } from "react/jsx-runtime";
|
|
310
|
-
function DefaultTemplate({
|
|
175
|
+
function LightTemplate({
|
|
311
176
|
title,
|
|
312
177
|
description,
|
|
313
178
|
siteName,
|
|
@@ -315,21 +180,21 @@ function DefaultTemplate({
|
|
|
315
180
|
// Visibility flags
|
|
316
181
|
showLogo = true,
|
|
317
182
|
showSiteName = true,
|
|
318
|
-
// Background customization
|
|
319
|
-
backgroundType = "
|
|
183
|
+
// Background customization (defaults to light theme)
|
|
184
|
+
backgroundType = "solid",
|
|
320
185
|
gradientStart = "#667eea",
|
|
321
186
|
gradientEnd = "#764ba2",
|
|
322
187
|
backgroundColor = "#ffffff",
|
|
323
188
|
// Typography - Title
|
|
324
189
|
titleSize,
|
|
325
190
|
titleWeight = 800,
|
|
326
|
-
titleColor = "
|
|
191
|
+
titleColor = "#111",
|
|
327
192
|
// Typography - Description
|
|
328
193
|
descriptionSize = 32,
|
|
329
|
-
descriptionColor = "
|
|
194
|
+
descriptionColor = "#666",
|
|
330
195
|
// Typography - Site Name
|
|
331
196
|
siteNameSize = 28,
|
|
332
|
-
siteNameColor = "
|
|
197
|
+
siteNameColor = "#111",
|
|
333
198
|
// Layout
|
|
334
199
|
padding = 80,
|
|
335
200
|
logoSize = 48,
|
|
@@ -431,7 +296,6 @@ function DefaultTemplate({
|
|
|
431
296
|
color: titleColor,
|
|
432
297
|
lineHeight: 1.1,
|
|
433
298
|
letterSpacing: "-0.03em",
|
|
434
|
-
textShadow: backgroundType === "gradient" ? "0 2px 20px rgba(0, 0, 0, 0.2)" : "none",
|
|
435
299
|
maxWidth: "100%",
|
|
436
300
|
wordWrap: "break-word"
|
|
437
301
|
},
|
|
@@ -447,11 +311,7 @@ function DefaultTemplate({
|
|
|
447
311
|
color: descriptionColor,
|
|
448
312
|
lineHeight: 1.5,
|
|
449
313
|
letterSpacing: "-0.01em",
|
|
450
|
-
maxWidth: "90%"
|
|
451
|
-
display: "-webkit-box",
|
|
452
|
-
WebkitLineClamp: 2,
|
|
453
|
-
WebkitBoxOrient: "vertical",
|
|
454
|
-
overflow: "hidden"
|
|
314
|
+
maxWidth: "90%"
|
|
455
315
|
},
|
|
456
316
|
children: description
|
|
457
317
|
}
|
|
@@ -475,168 +335,308 @@ function DefaultTemplate({
|
|
|
475
335
|
}
|
|
476
336
|
);
|
|
477
337
|
}
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
const
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
338
|
+
|
|
339
|
+
// src/og-image/utils/fonts.ts
|
|
340
|
+
async function loadGoogleFont(font, text, weight = 700) {
|
|
341
|
+
let url = `https://fonts.googleapis.com/css2?family=${font}:wght@${weight}`;
|
|
342
|
+
if (text) {
|
|
343
|
+
url += `&text=${encodeURIComponent(text)}`;
|
|
344
|
+
}
|
|
345
|
+
try {
|
|
346
|
+
const css = await fetch(url, {
|
|
347
|
+
headers: {
|
|
348
|
+
// Required to get TTF format instead of WOFF2
|
|
349
|
+
"User-Agent": "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_8; de-at) AppleWebKit/533.21.1 (KHTML, like Gecko) Version/5.0.5 Safari/533.21.1"
|
|
350
|
+
}
|
|
351
|
+
}).then((res) => res.text());
|
|
352
|
+
const resource = css.match(/src: url\((.+)\) format\('(opentype|truetype)'\)/);
|
|
353
|
+
if (!resource || !resource[1]) {
|
|
354
|
+
throw new Error(`Failed to parse font URL from CSS for font: ${font}`);
|
|
355
|
+
}
|
|
356
|
+
const response = await fetch(resource[1]);
|
|
357
|
+
if (response.status !== 200) {
|
|
358
|
+
throw new Error(`Failed to fetch font data: HTTP ${response.status}`);
|
|
359
|
+
}
|
|
360
|
+
return await response.arrayBuffer();
|
|
361
|
+
} catch (error) {
|
|
362
|
+
console.error(`Error loading Google Font "${font}":`, error);
|
|
363
|
+
throw new Error(`Failed to load font "${font}": ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
async function loadGoogleFonts(fonts) {
|
|
367
|
+
const fontConfigs = await Promise.all(
|
|
368
|
+
fonts.map(async ({ family, weight = 700, style = "normal", text }) => {
|
|
369
|
+
const data = await loadGoogleFont(family, text, weight);
|
|
370
|
+
return {
|
|
371
|
+
name: family,
|
|
372
|
+
weight,
|
|
373
|
+
style,
|
|
374
|
+
data
|
|
375
|
+
};
|
|
376
|
+
})
|
|
377
|
+
);
|
|
378
|
+
return fontConfigs;
|
|
379
|
+
}
|
|
380
|
+
function createFontLoader() {
|
|
381
|
+
const cache = /* @__PURE__ */ new Map();
|
|
382
|
+
return {
|
|
383
|
+
/**
|
|
384
|
+
* Load a font with caching
|
|
385
|
+
*/
|
|
386
|
+
async load(family, weight = 700, text) {
|
|
387
|
+
const cacheKey = `${family}-${weight}-${text || "all"}`;
|
|
388
|
+
if (!cache.has(cacheKey)) {
|
|
389
|
+
cache.set(cacheKey, loadGoogleFont(family, text, weight));
|
|
390
|
+
}
|
|
391
|
+
return cache.get(cacheKey);
|
|
392
|
+
},
|
|
393
|
+
/**
|
|
394
|
+
* Clear the cache
|
|
395
|
+
*/
|
|
396
|
+
clear() {
|
|
397
|
+
cache.clear();
|
|
398
|
+
},
|
|
399
|
+
/**
|
|
400
|
+
* Get cache size
|
|
401
|
+
*/
|
|
402
|
+
size() {
|
|
403
|
+
return cache.size;
|
|
404
|
+
}
|
|
405
|
+
};
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
// src/og-image/utils/url.ts
|
|
409
|
+
var DEFAULT_OG_IMAGE_BASE_URL = "https://djangocfg.com/api/og";
|
|
410
|
+
function encodeBase64(str) {
|
|
411
|
+
if (typeof Buffer !== "undefined") {
|
|
412
|
+
return Buffer.from(str, "utf-8").toString("base64");
|
|
413
|
+
}
|
|
414
|
+
return btoa(unescape(encodeURIComponent(str)));
|
|
415
|
+
}
|
|
416
|
+
function decodeBase64(str) {
|
|
417
|
+
if (typeof Buffer !== "undefined") {
|
|
418
|
+
return Buffer.from(str, "base64").toString("utf-8");
|
|
419
|
+
}
|
|
420
|
+
try {
|
|
421
|
+
const binaryString = atob(str);
|
|
422
|
+
return decodeURIComponent(
|
|
423
|
+
binaryString.split("").map((c) => "%" + ("00" + c.charCodeAt(0).toString(16)).slice(-2)).join("")
|
|
424
|
+
);
|
|
425
|
+
} catch (error) {
|
|
426
|
+
return decodeURIComponent(escape(atob(str)));
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
function generateOgImageUrl(params, options = {}) {
|
|
430
|
+
const {
|
|
431
|
+
baseUrl = DEFAULT_OG_IMAGE_BASE_URL,
|
|
432
|
+
useBase64 = true
|
|
433
|
+
} = options;
|
|
434
|
+
if (useBase64) {
|
|
435
|
+
const cleanParams = {};
|
|
436
|
+
Object.entries(params).forEach(([key, value]) => {
|
|
437
|
+
if (value !== void 0 && value !== null && value !== "") {
|
|
438
|
+
cleanParams[key] = value;
|
|
439
|
+
}
|
|
440
|
+
});
|
|
441
|
+
const jsonString = JSON.stringify(cleanParams);
|
|
442
|
+
const base64Data = encodeBase64(jsonString);
|
|
443
|
+
return `${baseUrl}/${base64Data}`;
|
|
444
|
+
} else {
|
|
445
|
+
const searchParams = new URLSearchParams();
|
|
446
|
+
Object.entries(params).forEach(([key, value]) => {
|
|
447
|
+
if (value !== void 0 && value !== null && value !== "") {
|
|
448
|
+
searchParams.append(key, String(value));
|
|
449
|
+
}
|
|
450
|
+
});
|
|
451
|
+
const query = searchParams.toString();
|
|
452
|
+
return query ? `${baseUrl}?${query}` : baseUrl;
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
function getAbsoluteOgImageUrl(relativePath, siteUrl) {
|
|
456
|
+
if (relativePath.startsWith("http://") || relativePath.startsWith("https://")) {
|
|
457
|
+
return relativePath;
|
|
458
|
+
}
|
|
459
|
+
const cleanSiteUrl = siteUrl.replace(/\/$/, "");
|
|
460
|
+
const cleanPath = relativePath.startsWith("/") ? relativePath : `/${relativePath}`;
|
|
461
|
+
return `${cleanSiteUrl}${cleanPath}`;
|
|
462
|
+
}
|
|
463
|
+
function createOgImageUrlBuilder(defaults = {}, options = {}) {
|
|
464
|
+
return (params) => {
|
|
465
|
+
return generateOgImageUrl(
|
|
466
|
+
{ ...defaults, ...params },
|
|
467
|
+
options
|
|
468
|
+
);
|
|
469
|
+
};
|
|
470
|
+
}
|
|
471
|
+
function parseOgImageUrl(url) {
|
|
472
|
+
try {
|
|
473
|
+
const urlObj = new URL(url, "http://dummy.com");
|
|
474
|
+
const params = {};
|
|
475
|
+
urlObj.searchParams.forEach((value, key) => {
|
|
476
|
+
params[key] = value;
|
|
477
|
+
});
|
|
478
|
+
return params;
|
|
479
|
+
} catch {
|
|
480
|
+
return {};
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
function parseOgImageData(searchParams) {
|
|
484
|
+
try {
|
|
485
|
+
let params;
|
|
486
|
+
if (searchParams instanceof URLSearchParams) {
|
|
487
|
+
params = {};
|
|
488
|
+
for (const [key, value] of searchParams.entries()) {
|
|
489
|
+
params[key] = value;
|
|
525
490
|
}
|
|
491
|
+
} else {
|
|
492
|
+
params = searchParams;
|
|
526
493
|
}
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
{
|
|
548
|
-
|
|
549
|
-
display: "flex",
|
|
550
|
-
alignItems: "center",
|
|
551
|
-
gap: "16px"
|
|
552
|
-
},
|
|
553
|
-
children: [
|
|
554
|
-
showLogo && logo && // eslint-disable-next-line @next/next/no-img-element
|
|
555
|
-
/* @__PURE__ */ jsx(
|
|
556
|
-
"img",
|
|
557
|
-
{
|
|
558
|
-
src: logo,
|
|
559
|
-
alt: "Logo",
|
|
560
|
-
width: logoSize,
|
|
561
|
-
height: logoSize,
|
|
562
|
-
style: {
|
|
563
|
-
borderRadius: "8px"
|
|
564
|
-
}
|
|
565
|
-
}
|
|
566
|
-
),
|
|
567
|
-
showSiteName && siteName && /* @__PURE__ */ jsx(
|
|
568
|
-
"div",
|
|
569
|
-
{
|
|
570
|
-
style: {
|
|
571
|
-
fontSize: siteNameSize,
|
|
572
|
-
fontWeight: 600,
|
|
573
|
-
color: siteNameColor,
|
|
574
|
-
letterSpacing: "-0.02em"
|
|
575
|
-
},
|
|
576
|
-
children: siteName
|
|
577
|
-
}
|
|
578
|
-
)
|
|
579
|
-
]
|
|
580
|
-
}
|
|
581
|
-
),
|
|
582
|
-
/* @__PURE__ */ jsxs(
|
|
583
|
-
"div",
|
|
584
|
-
{
|
|
585
|
-
style: {
|
|
586
|
-
display: "flex",
|
|
587
|
-
flexDirection: "column",
|
|
588
|
-
gap: "24px",
|
|
589
|
-
flex: 1,
|
|
590
|
-
justifyContent: "center"
|
|
591
|
-
},
|
|
592
|
-
children: [
|
|
593
|
-
/* @__PURE__ */ jsx(
|
|
594
|
-
"div",
|
|
595
|
-
{
|
|
596
|
-
style: {
|
|
597
|
-
fontSize: calculatedTitleSize,
|
|
598
|
-
fontWeight: titleWeight,
|
|
599
|
-
color: titleColor,
|
|
600
|
-
lineHeight: 1.1,
|
|
601
|
-
letterSpacing: "-0.03em",
|
|
602
|
-
maxWidth: "100%",
|
|
603
|
-
wordWrap: "break-word"
|
|
604
|
-
},
|
|
605
|
-
children: title
|
|
606
|
-
}
|
|
607
|
-
),
|
|
608
|
-
description && /* @__PURE__ */ jsx(
|
|
609
|
-
"div",
|
|
610
|
-
{
|
|
611
|
-
style: {
|
|
612
|
-
fontSize: descriptionSize,
|
|
613
|
-
fontWeight: 400,
|
|
614
|
-
color: descriptionColor,
|
|
615
|
-
lineHeight: 1.5,
|
|
616
|
-
letterSpacing: "-0.01em",
|
|
617
|
-
maxWidth: "90%"
|
|
618
|
-
},
|
|
619
|
-
children: description
|
|
620
|
-
}
|
|
621
|
-
)
|
|
622
|
-
]
|
|
623
|
-
}
|
|
624
|
-
),
|
|
625
|
-
/* @__PURE__ */ jsx(
|
|
626
|
-
"div",
|
|
627
|
-
{
|
|
628
|
-
style: {
|
|
629
|
-
display: "flex",
|
|
630
|
-
width: "100%",
|
|
631
|
-
height: "4px",
|
|
632
|
-
background: backgroundType === "gradient" ? `linear-gradient(90deg, ${gradientStart} 0%, ${gradientEnd} 100%)` : gradientStart,
|
|
633
|
-
borderRadius: "2px"
|
|
634
|
-
}
|
|
494
|
+
if (process.env.NODE_ENV === "development") {
|
|
495
|
+
console.log("[parseOgImageData] Input params keys:", Object.keys(params));
|
|
496
|
+
console.log("[parseOgImageData] Input params:", params);
|
|
497
|
+
}
|
|
498
|
+
const dataParam = params.data;
|
|
499
|
+
if (dataParam && typeof dataParam === "string" && dataParam.trim() !== "") {
|
|
500
|
+
if (process.env.NODE_ENV === "development") {
|
|
501
|
+
console.log("[parseOgImageData] Found data param, length:", dataParam.length);
|
|
502
|
+
}
|
|
503
|
+
try {
|
|
504
|
+
const decoded = decodeBase64(dataParam);
|
|
505
|
+
if (process.env.NODE_ENV === "development") {
|
|
506
|
+
console.log("[parseOgImageData] Decoded string:", decoded.substring(0, 100));
|
|
507
|
+
}
|
|
508
|
+
const parsed = JSON.parse(decoded);
|
|
509
|
+
if (process.env.NODE_ENV === "development") {
|
|
510
|
+
console.log("[parseOgImageData] Parsed JSON:", parsed);
|
|
511
|
+
}
|
|
512
|
+
const result2 = {};
|
|
513
|
+
for (const [key, value] of Object.entries(parsed)) {
|
|
514
|
+
if (value !== void 0 && value !== null) {
|
|
515
|
+
result2[key] = String(value);
|
|
635
516
|
}
|
|
636
|
-
|
|
637
|
-
|
|
517
|
+
}
|
|
518
|
+
if (process.env.NODE_ENV === "development") {
|
|
519
|
+
console.log("[parseOgImageData] Result:", result2);
|
|
520
|
+
}
|
|
521
|
+
return result2;
|
|
522
|
+
} catch (decodeError) {
|
|
523
|
+
console.error("[parseOgImageData] Error decoding/parsing data param:", decodeError);
|
|
524
|
+
if (decodeError instanceof Error) {
|
|
525
|
+
console.error("[parseOgImageData] Error message:", decodeError.message);
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
} else {
|
|
529
|
+
if (process.env.NODE_ENV === "development") {
|
|
530
|
+
console.log("[parseOgImageData] No data param found or empty");
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
const result = {};
|
|
534
|
+
for (const [key, value] of Object.entries(params)) {
|
|
535
|
+
if (key !== "data" && value !== void 0 && value !== null) {
|
|
536
|
+
result[key] = Array.isArray(value) ? value[0] : String(value);
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
if (process.env.NODE_ENV === "development") {
|
|
540
|
+
console.log("[parseOgImageData] Fallback result:", result);
|
|
541
|
+
}
|
|
542
|
+
return result;
|
|
543
|
+
} catch (error) {
|
|
544
|
+
console.error("[parseOgImageData] Unexpected error:", error);
|
|
545
|
+
return {};
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
// src/og-image/utils/metadata.ts
|
|
550
|
+
function extractTitle(metadata) {
|
|
551
|
+
if (typeof metadata.title === "string") {
|
|
552
|
+
return metadata.title;
|
|
553
|
+
}
|
|
554
|
+
if (metadata.title) {
|
|
555
|
+
if ("default" in metadata.title) {
|
|
556
|
+
return metadata.title.default;
|
|
557
|
+
}
|
|
558
|
+
if ("absolute" in metadata.title) {
|
|
559
|
+
return metadata.title.absolute;
|
|
638
560
|
}
|
|
561
|
+
}
|
|
562
|
+
return "";
|
|
563
|
+
}
|
|
564
|
+
function extractDescription(metadata) {
|
|
565
|
+
if (typeof metadata.description === "string") {
|
|
566
|
+
return metadata.description;
|
|
567
|
+
}
|
|
568
|
+
return "";
|
|
569
|
+
}
|
|
570
|
+
function getSiteUrl() {
|
|
571
|
+
if (typeof process !== "undefined" && process.env.NEXT_PUBLIC_SITE_URL) {
|
|
572
|
+
return process.env.NEXT_PUBLIC_SITE_URL;
|
|
573
|
+
}
|
|
574
|
+
return "";
|
|
575
|
+
}
|
|
576
|
+
function generateOgImageMetadata(metadata, ogImageParams, options = {}) {
|
|
577
|
+
const {
|
|
578
|
+
ogImageBaseUrl = "https://djangocfg.com/api/og",
|
|
579
|
+
siteUrl: providedSiteUrl,
|
|
580
|
+
defaultParams = {},
|
|
581
|
+
useBase64 = true
|
|
582
|
+
} = options;
|
|
583
|
+
const siteUrl = providedSiteUrl && providedSiteUrl !== "undefined" ? providedSiteUrl : getSiteUrl();
|
|
584
|
+
const extractedTitle = extractTitle(metadata);
|
|
585
|
+
const extractedDescription = extractDescription(metadata);
|
|
586
|
+
const finalOgImageParams = {
|
|
587
|
+
...defaultParams,
|
|
588
|
+
title: ogImageParams?.title || extractedTitle || defaultParams.title || "",
|
|
589
|
+
description: ogImageParams?.description || extractedDescription || defaultParams.description || "",
|
|
590
|
+
...ogImageParams
|
|
591
|
+
};
|
|
592
|
+
const imageAlt = finalOgImageParams.title || finalOgImageParams.siteName;
|
|
593
|
+
const relativeOgImageUrl = generateOgImageUrl(
|
|
594
|
+
finalOgImageParams,
|
|
595
|
+
{ baseUrl: ogImageBaseUrl, useBase64 }
|
|
639
596
|
);
|
|
597
|
+
const ogImageUrl = siteUrl ? getAbsoluteOgImageUrl(relativeOgImageUrl, siteUrl) : relativeOgImageUrl;
|
|
598
|
+
const existingOgImages = metadata.openGraph?.images ? Array.isArray(metadata.openGraph.images) ? metadata.openGraph.images : [metadata.openGraph.images] : [];
|
|
599
|
+
const existingTwitterImages = metadata.twitter?.images ? Array.isArray(metadata.twitter.images) ? metadata.twitter.images : [metadata.twitter.images] : [];
|
|
600
|
+
const finalMetadata = {
|
|
601
|
+
...metadata,
|
|
602
|
+
openGraph: {
|
|
603
|
+
...metadata.openGraph,
|
|
604
|
+
images: [
|
|
605
|
+
...existingOgImages,
|
|
606
|
+
{
|
|
607
|
+
url: ogImageUrl,
|
|
608
|
+
width: 1200,
|
|
609
|
+
height: 630,
|
|
610
|
+
alt: imageAlt
|
|
611
|
+
}
|
|
612
|
+
]
|
|
613
|
+
},
|
|
614
|
+
twitter: {
|
|
615
|
+
...metadata.twitter,
|
|
616
|
+
card: "summary_large_image",
|
|
617
|
+
images: [
|
|
618
|
+
...existingTwitterImages,
|
|
619
|
+
{
|
|
620
|
+
url: ogImageUrl,
|
|
621
|
+
alt: imageAlt
|
|
622
|
+
}
|
|
623
|
+
]
|
|
624
|
+
}
|
|
625
|
+
};
|
|
626
|
+
if (!finalMetadata.metadataBase && siteUrl) {
|
|
627
|
+
if (siteUrl.startsWith("http://") || siteUrl.startsWith("https://")) {
|
|
628
|
+
try {
|
|
629
|
+
finalMetadata.metadataBase = new URL(siteUrl);
|
|
630
|
+
} catch (e) {
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
return finalMetadata;
|
|
635
|
+
}
|
|
636
|
+
function createOgImageMetadataGenerator(options) {
|
|
637
|
+
return (metadata, ogImageParams) => {
|
|
638
|
+
return generateOgImageMetadata(metadata, ogImageParams, options);
|
|
639
|
+
};
|
|
640
640
|
}
|
|
641
641
|
|
|
642
642
|
// src/og-image/route.tsx
|