@zoyth/simple-site-framework 1.0.2 → 1.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.
- package/dist/components/index.d.mts +74 -1
- package/dist/components/index.d.ts +74 -1
- package/dist/components/index.js +210 -2
- package/dist/components/index.js.map +1 -1
- package/dist/components/index.mjs +208 -2
- package/dist/components/index.mjs.map +1 -1
- package/dist/index.d.mts +113 -2
- package/dist/index.d.ts +113 -2
- package/dist/index.js +196 -16
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +187 -16
- package/dist/index.mjs.map +1 -1
- package/docs/BLOG.md +1005 -0
- package/docs/guides/webflow-migration.md +300 -0
- package/package.json +2 -2
package/dist/index.mjs
CHANGED
|
@@ -351,6 +351,168 @@ function getPolicyLocales(slug, contentDir = "src/content/policies") {
|
|
|
351
351
|
return locales2;
|
|
352
352
|
}
|
|
353
353
|
|
|
354
|
+
// src/lib/content/blog.ts
|
|
355
|
+
import fs2 from "fs";
|
|
356
|
+
import path2 from "path";
|
|
357
|
+
import { compileMDX as compileMDX2 } from "next-mdx-remote/rsc";
|
|
358
|
+
import rehypeSlug2 from "rehype-slug";
|
|
359
|
+
function resolveDir(contentDir) {
|
|
360
|
+
return path2.isAbsolute(contentDir) ? contentDir : path2.join(process.cwd(), contentDir);
|
|
361
|
+
}
|
|
362
|
+
var REQUIRED_FIELDS = ["title", "excerpt", "author", "date", "tags"];
|
|
363
|
+
async function loadBlogPost(slug, locale, contentDir = "src/content/blog") {
|
|
364
|
+
const dir = resolveDir(contentDir);
|
|
365
|
+
const filePath = path2.join(dir, `${slug}.${locale}.md`);
|
|
366
|
+
if (!fs2.existsSync(filePath)) {
|
|
367
|
+
throw new Error(
|
|
368
|
+
`Blog post file not found: ${slug}.${locale}.md in ${contentDir}
|
|
369
|
+
Looking for: ${filePath}`
|
|
370
|
+
);
|
|
371
|
+
}
|
|
372
|
+
const source = fs2.readFileSync(filePath, "utf8");
|
|
373
|
+
const { content, frontmatter } = await compileMDX2({
|
|
374
|
+
source,
|
|
375
|
+
options: {
|
|
376
|
+
parseFrontmatter: true,
|
|
377
|
+
mdxOptions: {
|
|
378
|
+
rehypePlugins: [rehypeSlug2]
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
});
|
|
382
|
+
for (const field of REQUIRED_FIELDS) {
|
|
383
|
+
if (!frontmatter[field]) {
|
|
384
|
+
throw new Error(
|
|
385
|
+
`Blog post ${slug}.${locale}.md is missing required frontmatter field: ${field}`
|
|
386
|
+
);
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
return {
|
|
390
|
+
content,
|
|
391
|
+
metadata: frontmatter,
|
|
392
|
+
slug,
|
|
393
|
+
locale
|
|
394
|
+
};
|
|
395
|
+
}
|
|
396
|
+
function getBlogPostSlugs(contentDir = "src/content/blog") {
|
|
397
|
+
const dir = resolveDir(contentDir);
|
|
398
|
+
if (!fs2.existsSync(dir)) {
|
|
399
|
+
console.warn(`Blog directory not found: ${dir}`);
|
|
400
|
+
return [];
|
|
401
|
+
}
|
|
402
|
+
const files = fs2.readdirSync(dir);
|
|
403
|
+
const slugs = Array.from(
|
|
404
|
+
new Set(
|
|
405
|
+
files.filter((file) => file.endsWith(".md") || file.endsWith(".mdx")).map((file) => file.replace(/\.[a-z]{2}(-[A-Z]{2})?\.mdx?$/, ""))
|
|
406
|
+
)
|
|
407
|
+
);
|
|
408
|
+
return slugs;
|
|
409
|
+
}
|
|
410
|
+
async function getAllBlogPosts(locale, contentDir = "src/content/blog") {
|
|
411
|
+
const slugs = getBlogPostSlugs(contentDir);
|
|
412
|
+
const posts = await Promise.all(
|
|
413
|
+
slugs.map(async (slug) => {
|
|
414
|
+
try {
|
|
415
|
+
const post = await loadBlogPost(slug, locale, contentDir);
|
|
416
|
+
return {
|
|
417
|
+
slug: post.slug,
|
|
418
|
+
locale: post.locale,
|
|
419
|
+
metadata: post.metadata
|
|
420
|
+
};
|
|
421
|
+
} catch (error) {
|
|
422
|
+
console.warn(`Skipping ${slug} for locale ${locale}:`, error);
|
|
423
|
+
return null;
|
|
424
|
+
}
|
|
425
|
+
})
|
|
426
|
+
);
|
|
427
|
+
return posts.filter((p) => p !== null).sort((a, b) => new Date(b.metadata.date).getTime() - new Date(a.metadata.date).getTime());
|
|
428
|
+
}
|
|
429
|
+
function getBlogPostLocales(slug, contentDir = "src/content/blog") {
|
|
430
|
+
const dir = resolveDir(contentDir);
|
|
431
|
+
if (!fs2.existsSync(dir)) {
|
|
432
|
+
return [];
|
|
433
|
+
}
|
|
434
|
+
const files = fs2.readdirSync(dir);
|
|
435
|
+
const locales2 = files.filter((file) => {
|
|
436
|
+
const pattern = new RegExp(`^${slug}\\.[a-z]{2}(-[A-Z]{2})?\\.mdx?$`);
|
|
437
|
+
return pattern.test(file);
|
|
438
|
+
}).map((file) => {
|
|
439
|
+
const match = file.match(/\.([a-z]{2}(-[A-Z]{2})?)\.mdx?$/);
|
|
440
|
+
return match ? match[1] : null;
|
|
441
|
+
}).filter((locale) => locale !== null);
|
|
442
|
+
return locales2;
|
|
443
|
+
}
|
|
444
|
+
async function getBlogPostsByTag(tag, locale, contentDir = "src/content/blog") {
|
|
445
|
+
const posts = await getAllBlogPosts(locale, contentDir);
|
|
446
|
+
return posts.filter((p) => p.metadata.tags.includes(tag));
|
|
447
|
+
}
|
|
448
|
+
async function getFeaturedBlogPosts(locale, contentDir = "src/content/blog") {
|
|
449
|
+
const posts = await getAllBlogPosts(locale, contentDir);
|
|
450
|
+
return posts.filter((p) => p.metadata.featured === true);
|
|
451
|
+
}
|
|
452
|
+
async function getRelatedBlogPosts(slug, locale, count = 3, contentDir = "src/content/blog") {
|
|
453
|
+
const allPosts = await getAllBlogPosts(locale, contentDir);
|
|
454
|
+
const sourcePost = allPosts.find((p) => p.slug === slug);
|
|
455
|
+
if (!sourcePost) return [];
|
|
456
|
+
const sourceTags = new Set(sourcePost.metadata.tags);
|
|
457
|
+
const candidates = allPosts.filter((p) => p.slug !== slug);
|
|
458
|
+
const scored = candidates.map((post) => ({
|
|
459
|
+
post,
|
|
460
|
+
sharedTags: post.metadata.tags.filter((t) => sourceTags.has(t)).length
|
|
461
|
+
}));
|
|
462
|
+
return scored.filter((s) => s.sharedTags > 0).sort((a, b) => {
|
|
463
|
+
if (b.sharedTags !== a.sharedTags) return b.sharedTags - a.sharedTags;
|
|
464
|
+
return new Date(b.post.metadata.date).getTime() - new Date(a.post.metadata.date).getTime();
|
|
465
|
+
}).slice(0, count).map((s) => s.post);
|
|
466
|
+
}
|
|
467
|
+
async function getAllTags(locale, contentDir = "src/content/blog") {
|
|
468
|
+
const posts = await getAllBlogPosts(locale, contentDir);
|
|
469
|
+
const counts = /* @__PURE__ */ new Map();
|
|
470
|
+
for (const post of posts) {
|
|
471
|
+
for (const tag of post.metadata.tags) {
|
|
472
|
+
counts.set(tag, (counts.get(tag) ?? 0) + 1);
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
return Array.from(counts.entries()).map(([tag, count]) => ({ tag, count })).sort((a, b) => b.count - a.count);
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
// src/lib/content/rss.ts
|
|
479
|
+
var MAX_ITEMS = 20;
|
|
480
|
+
async function generateBlogRssFeed(options) {
|
|
481
|
+
const { siteUrl, siteName, locale, contentDir } = options;
|
|
482
|
+
const description = typeof options.description === "string" ? options.description : getLocalizedString(options.description, locale);
|
|
483
|
+
const posts = await getAllBlogPosts(locale, contentDir);
|
|
484
|
+
const items = posts.slice(0, MAX_ITEMS);
|
|
485
|
+
const itemsXml = items.map((post) => {
|
|
486
|
+
const link = `${siteUrl}/${locale}/blog/${post.slug}`;
|
|
487
|
+
const pubDate = (/* @__PURE__ */ new Date(`${post.metadata.date}T12:00:00Z`)).toUTCString();
|
|
488
|
+
const categories = post.metadata.tags.map((tag) => ` <category>${escapeXml(tag)}</category>`).join("\n");
|
|
489
|
+
return ` <item>
|
|
490
|
+
<title>${escapeXml(post.metadata.title)}</title>
|
|
491
|
+
<description>${escapeXml(post.metadata.excerpt)}</description>
|
|
492
|
+
<link>${escapeXml(link)}</link>
|
|
493
|
+
<guid>${escapeXml(link)}</guid>
|
|
494
|
+
<pubDate>${pubDate}</pubDate>
|
|
495
|
+
<author>${escapeXml(post.metadata.author)}</author>
|
|
496
|
+
${categories}
|
|
497
|
+
</item>`;
|
|
498
|
+
}).join("\n");
|
|
499
|
+
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
500
|
+
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
|
|
501
|
+
<channel>
|
|
502
|
+
<title>${escapeXml(siteName)}</title>
|
|
503
|
+
<description>${escapeXml(description)}</description>
|
|
504
|
+
<link>${escapeXml(siteUrl)}</link>
|
|
505
|
+
<language>${escapeXml(locale)}</language>
|
|
506
|
+
<lastBuildDate>${(/* @__PURE__ */ new Date()).toUTCString()}</lastBuildDate>
|
|
507
|
+
<atom:link href="${escapeXml(siteUrl)}/${escapeXml(locale)}/blog/feed.xml" rel="self" type="application/rss+xml"/>
|
|
508
|
+
${itemsXml}
|
|
509
|
+
</channel>
|
|
510
|
+
</rss>`;
|
|
511
|
+
}
|
|
512
|
+
function escapeXml(str) {
|
|
513
|
+
return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
514
|
+
}
|
|
515
|
+
|
|
354
516
|
// src/lib/navigation/utils.ts
|
|
355
517
|
function getNavigationString(localizedString, locale) {
|
|
356
518
|
return localizedString[locale] || localizedString["en"] || "";
|
|
@@ -428,7 +590,7 @@ var defaultSlugTranslations = {
|
|
|
428
590
|
"/anti-spam-policy": "/politique-anti-pourriel"
|
|
429
591
|
}
|
|
430
592
|
};
|
|
431
|
-
function translateSlug(
|
|
593
|
+
function translateSlug(path3, fromLocale, toLocale, customTranslations) {
|
|
432
594
|
let configTranslations = {};
|
|
433
595
|
try {
|
|
434
596
|
const { getI18nConfig: getI18nConfig2 } = (init_config(), __toCommonJS(config_exports));
|
|
@@ -443,17 +605,17 @@ function translateSlug(path2, fromLocale, toLocale, customTranslations) {
|
|
|
443
605
|
);
|
|
444
606
|
const translationMap = translations[fromLocale];
|
|
445
607
|
if (!translationMap) {
|
|
446
|
-
return
|
|
608
|
+
return path3;
|
|
447
609
|
}
|
|
448
|
-
if (translationMap[
|
|
449
|
-
return translationMap[
|
|
610
|
+
if (translationMap[path3]) {
|
|
611
|
+
return translationMap[path3];
|
|
450
612
|
}
|
|
451
613
|
for (const [fromSlug, toSlug] of Object.entries(translationMap)) {
|
|
452
|
-
if (
|
|
453
|
-
return
|
|
614
|
+
if (path3.startsWith(fromSlug + "/")) {
|
|
615
|
+
return path3.replace(fromSlug, toSlug);
|
|
454
616
|
}
|
|
455
617
|
}
|
|
456
|
-
return
|
|
618
|
+
return path3;
|
|
457
619
|
}
|
|
458
620
|
function mergeTranslations(...translations) {
|
|
459
621
|
const merged = {};
|
|
@@ -1282,7 +1444,7 @@ function generateSitemap(config) {
|
|
|
1282
1444
|
const urlEntries = entries.map((entry) => {
|
|
1283
1445
|
const parts = [];
|
|
1284
1446
|
parts.push(`${indent}<url>${newline}`);
|
|
1285
|
-
parts.push(`${indent}${indent}<loc>${
|
|
1447
|
+
parts.push(`${indent}${indent}<loc>${escapeXml2(entry.url)}</loc>${newline}`);
|
|
1286
1448
|
if (entry.lastModified) {
|
|
1287
1449
|
const date = entry.lastModified instanceof Date ? entry.lastModified.toISOString() : entry.lastModified;
|
|
1288
1450
|
parts.push(`${indent}${indent}<lastmod>${date}</lastmod>${newline}`);
|
|
@@ -1297,9 +1459,9 @@ function generateSitemap(config) {
|
|
|
1297
1459
|
if (entry.alternates && entry.alternates.length > 0) {
|
|
1298
1460
|
entry.alternates.forEach((alternate) => {
|
|
1299
1461
|
parts.push(
|
|
1300
|
-
`${indent}${indent}<xhtml:link rel="alternate" hreflang="${
|
|
1462
|
+
`${indent}${indent}<xhtml:link rel="alternate" hreflang="${escapeXml2(
|
|
1301
1463
|
alternate.hreflang
|
|
1302
|
-
)}" href="${
|
|
1464
|
+
)}" href="${escapeXml2(alternate.href)}" />${newline}`
|
|
1303
1465
|
);
|
|
1304
1466
|
});
|
|
1305
1467
|
}
|
|
@@ -1316,8 +1478,8 @@ function generateSitemap(config) {
|
|
|
1316
1478
|
].join("");
|
|
1317
1479
|
return xml;
|
|
1318
1480
|
}
|
|
1319
|
-
function createMultiLanguageEntries(baseUrl,
|
|
1320
|
-
const cleanPath =
|
|
1481
|
+
function createMultiLanguageEntries(baseUrl, path3, locales2, defaultLocale2, options) {
|
|
1482
|
+
const cleanPath = path3.startsWith("/") ? path3 : `/${path3}`;
|
|
1321
1483
|
return locales2.map((locale) => {
|
|
1322
1484
|
const alternates = locales2.map((altLocale) => ({
|
|
1323
1485
|
hreflang: altLocale,
|
|
@@ -1336,15 +1498,15 @@ function createMultiLanguageEntries(baseUrl, path2, locales2, defaultLocale2, op
|
|
|
1336
1498
|
};
|
|
1337
1499
|
});
|
|
1338
1500
|
}
|
|
1339
|
-
function createSitemapEntry(baseUrl,
|
|
1340
|
-
const cleanPath =
|
|
1341
|
-
const url =
|
|
1501
|
+
function createSitemapEntry(baseUrl, path3, options) {
|
|
1502
|
+
const cleanPath = path3.startsWith("/") ? path3 : `/${path3}`;
|
|
1503
|
+
const url = path3 === "/" ? baseUrl : `${baseUrl}${cleanPath}`;
|
|
1342
1504
|
return {
|
|
1343
1505
|
url,
|
|
1344
1506
|
...options
|
|
1345
1507
|
};
|
|
1346
1508
|
}
|
|
1347
|
-
function
|
|
1509
|
+
function escapeXml2(unsafe) {
|
|
1348
1510
|
return unsafe.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
1349
1511
|
}
|
|
1350
1512
|
function validateSitemapEntry(entry) {
|
|
@@ -1534,14 +1696,21 @@ export {
|
|
|
1534
1696
|
formatNumber,
|
|
1535
1697
|
formatRelativeTime,
|
|
1536
1698
|
generateArticleMetadata,
|
|
1699
|
+
generateBlogRssFeed,
|
|
1537
1700
|
generateDesignTokens,
|
|
1538
1701
|
generateMetadata,
|
|
1539
1702
|
generateSitemap,
|
|
1540
1703
|
generateThemeCSS,
|
|
1704
|
+
getAllBlogPosts,
|
|
1541
1705
|
getAllPolicies,
|
|
1706
|
+
getAllTags,
|
|
1542
1707
|
getAlternateLocales,
|
|
1708
|
+
getBlogPostLocales,
|
|
1709
|
+
getBlogPostSlugs,
|
|
1710
|
+
getBlogPostsByTag,
|
|
1543
1711
|
getDefaultLocale,
|
|
1544
1712
|
getErrorMessage,
|
|
1713
|
+
getFeaturedBlogPosts,
|
|
1545
1714
|
getFontConfig,
|
|
1546
1715
|
getFontVariables,
|
|
1547
1716
|
getI18nConfig,
|
|
@@ -1558,6 +1727,7 @@ export {
|
|
|
1558
1727
|
getNavigationString,
|
|
1559
1728
|
getPolicyLocales,
|
|
1560
1729
|
getPolicySlugs,
|
|
1730
|
+
getRelatedBlogPosts,
|
|
1561
1731
|
getRelativeTime,
|
|
1562
1732
|
getRtlLocales,
|
|
1563
1733
|
getTextDirection,
|
|
@@ -1566,6 +1736,7 @@ export {
|
|
|
1566
1736
|
isLocaleDetectionEnabled,
|
|
1567
1737
|
isRtlLocale,
|
|
1568
1738
|
isSupportedLocale,
|
|
1739
|
+
loadBlogPost,
|
|
1569
1740
|
loadPolicy,
|
|
1570
1741
|
locales,
|
|
1571
1742
|
matchLocale,
|