@next-md-blog/core 1.0.2 → 1.0.4

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 (42) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +44 -207
  3. package/dist/components/BlogPostSEO.d.ts +3 -1
  4. package/dist/components/BlogPostSEO.d.ts.map +1 -1
  5. package/dist/components/BlogPostSEO.js +6 -4
  6. package/dist/components/MarkdownContent.d.ts +5 -6
  7. package/dist/components/MarkdownContent.d.ts.map +1 -1
  8. package/dist/components/MarkdownContent.js +8 -4
  9. package/dist/components/markdown/img.d.ts.map +1 -1
  10. package/dist/components/markdown/img.js +4 -1
  11. package/dist/core/config.d.ts +1 -1
  12. package/dist/core/config.js +1 -1
  13. package/dist/core/organization-schema.d.ts +22 -0
  14. package/dist/core/organization-schema.d.ts.map +1 -0
  15. package/dist/core/organization-schema.js +83 -0
  16. package/dist/core/seo-feeds.d.ts +2 -12
  17. package/dist/core/seo-feeds.d.ts.map +1 -1
  18. package/dist/core/seo-feeds.js +4 -34
  19. package/dist/core/seo-metadata.d.ts.map +1 -1
  20. package/dist/core/seo-metadata.js +16 -12
  21. package/dist/core/seo-schema.d.ts +13 -1
  22. package/dist/core/seo-schema.d.ts.map +1 -1
  23. package/dist/core/seo-schema.js +42 -13
  24. package/dist/core/seo-utils.d.ts +19 -7
  25. package/dist/core/seo-utils.d.ts.map +1 -1
  26. package/dist/core/seo-utils.js +65 -7
  27. package/dist/core/seo.d.ts +8 -4
  28. package/dist/core/seo.d.ts.map +1 -1
  29. package/dist/core/seo.js +6 -4
  30. package/dist/core/sitemap-data.d.ts +18 -0
  31. package/dist/core/sitemap-data.d.ts.map +1 -0
  32. package/dist/core/sitemap-data.js +42 -0
  33. package/dist/core/types.d.ts +21 -0
  34. package/dist/core/types.d.ts.map +1 -1
  35. package/dist/core/utils.js +2 -2
  36. package/dist/index.d.ts +4 -2
  37. package/dist/index.d.ts.map +1 -1
  38. package/dist/index.js +2 -1
  39. package/dist/next.d.ts +19 -0
  40. package/dist/next.d.ts.map +1 -0
  41. package/dist/next.js +44 -0
  42. package/package.json +33 -28
@@ -1,52 +1,22 @@
1
1
  import { getConfig } from './config.js';
2
2
  import { resolveFrontmatterField } from './type-guards.js';
3
3
  import { DEFAULT_SITE_NAME, RSS_POST_LIMIT } from './constants.js';
4
- import { resolvePostUrl, escapeXml, getAuthorName } from './seo-utils.js';
4
+ import { resolvePostUrlWithConfig, escapeXml, getAuthorName, } from './seo-utils.js';
5
5
  /**
6
- * Generates sitemap XML for blog posts
7
- * @param posts - Array of blog post metadata
8
- * @param config - SEO configuration
9
- * @returns Sitemap XML string
10
- */
11
- export function generateSitemap(posts, config) {
12
- const blogConfig = config || getConfig();
13
- const { siteUrl = '' } = blogConfig;
14
- const urls = posts
15
- .map((post) => {
16
- const lastmod = resolveFrontmatterField(['modifiedDate', 'date'], post.frontmatter) || new Date().toISOString().split('T')[0];
17
- const url = `${siteUrl}/blog/${post.slug}`;
18
- return ` <url>
19
- <loc>${escapeXml(url)}</loc>
20
- <lastmod>${lastmod}</lastmod>
21
- <changefreq>monthly</changefreq>
22
- <priority>0.8</priority>
23
- </url>`;
24
- })
25
- .join('\n');
26
- return `<?xml version="1.0" encoding="UTF-8"?>
27
- <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
28
- ${urls}
29
- </urlset>`;
30
- }
31
- /**
32
- * Generates RSS feed XML for blog posts
33
- * @param posts - Array of blog posts
34
- * @param config - SEO configuration
35
- * @returns RSS XML string
6
+ * Generates RSS feed XML for blog posts (used by `createRssFeedResponse` in `@next-md-blog/core/next`).
36
7
  */
37
8
  export function generateRSSFeed(posts, config) {
38
9
  const blogConfig = config || getConfig();
39
10
  const { siteName = DEFAULT_SITE_NAME, siteUrl = '', defaultAuthor, } = blogConfig;
40
11
  const items = posts
41
- .slice(0, RSS_POST_LIMIT) // Limit to most recent posts
12
+ .slice(0, RSS_POST_LIMIT)
42
13
  .map((post) => {
43
14
  const title = resolveFrontmatterField(['title'], post.frontmatter, post.slug) || post.slug;
44
15
  const description = resolveFrontmatterField(['description', 'excerpt'], post.frontmatter, '') || '';
45
16
  const authorObj = post.authors[0];
46
17
  const author = authorObj ? getAuthorName(authorObj) : (defaultAuthor || '');
47
18
  const pubDate = resolveFrontmatterField(['publishedDate', 'date'], post.frontmatter) || new Date().toISOString();
48
- const url = resolvePostUrl(resolveFrontmatterField(['canonicalUrl'], post.frontmatter), post.slug, siteUrl);
49
- // Format date for RSS (RFC 822)
19
+ const url = resolvePostUrlWithConfig(resolveFrontmatterField(['canonicalUrl'], post.frontmatter), post.slug, siteUrl, blogConfig);
50
20
  const rssDate = new Date(pubDate).toUTCString();
51
21
  return ` <item>
52
22
  <title>${escapeXml(title)}</title>
@@ -1 +1 @@
1
- {"version":3,"file":"seo-metadata.d.ts","sourceRoot":"","sources":["../../src/core/seo-metadata.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,gBAAgB,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC;AACrE,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,MAAM,CAAC;AAcrC;;;;;GAKG;AACH,wBAAgB,wBAAwB,CACtC,IAAI,EAAE,QAAQ,EACd,MAAM,CAAC,EAAE,MAAM,GACd,QAAQ,CAmMV;AAED;;;;;GAKG;AACH,wBAAgB,wBAAwB,CACtC,KAAK,EAAE,gBAAgB,EAAE,EACzB,MAAM,CAAC,EAAE,MAAM,GACd,QAAQ,CAwBV"}
1
+ {"version":3,"file":"seo-metadata.d.ts","sourceRoot":"","sources":["../../src/core/seo-metadata.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,gBAAgB,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC;AACrE,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,MAAM,CAAC;AAgBrC;;;;;GAKG;AACH,wBAAgB,wBAAwB,CACtC,IAAI,EAAE,QAAQ,EACd,MAAM,CAAC,EAAE,MAAM,GACd,QAAQ,CAwMV;AAED;;;;;GAKG;AACH,wBAAgB,wBAAwB,CACtC,KAAK,EAAE,gBAAgB,EAAE,EACzB,MAAM,CAAC,EAAE,MAAM,GACd,QAAQ,CAyBV"}
@@ -1,8 +1,7 @@
1
- import { calculateReadingTime, calculateWordCount } from './utils.js';
2
1
  import { getConfig } from './config.js';
3
2
  import { resolveFrontmatterField, isStringArray } from './type-guards.js';
4
3
  import { DEFAULT_SITE_NAME, DEFAULT_LANG } from './constants.js';
5
- import { normalizeKeywords, getAuthorNames, ensureAuthorsResolved, resolveDefaultAuthor, buildRobotsMeta, resolvePostUrl, } from './seo-utils.js';
4
+ import { normalizeKeywords, getAuthorNames, ensureAuthorsResolved, resolveDefaultAuthor, buildRobotsMeta, resolvePostUrlWithConfig, resolveHreflangMap, resolveBlogIndexUrl, resolveCanonicalUrl, } from './seo-utils.js';
6
5
  /**
7
6
  * Generates comprehensive metadata for a blog post
8
7
  * @param post - The blog post
@@ -11,7 +10,7 @@ import { normalizeKeywords, getAuthorNames, ensureAuthorsResolved, resolveDefaul
11
10
  */
12
11
  export function generateBlogPostMetadata(post, config) {
13
12
  const blogConfig = config || getConfig();
14
- const { siteName = DEFAULT_SITE_NAME, siteUrl = '', defaultAuthor, authors: configAuthors, twitterHandle, defaultOgImage, defaultLang = DEFAULT_LANG, alternateLanguages, } = blogConfig;
13
+ const { siteName = DEFAULT_SITE_NAME, siteUrl = '', defaultAuthor, authors: configAuthors, twitterHandle, defaultOgImage, defaultLang = DEFAULT_LANG, } = blogConfig;
15
14
  const fm = post.frontmatter;
16
15
  // Title resolution: seoTitle > title > slug
17
16
  const baseTitle = resolveFrontmatterField(['title'], fm, post.slug) || post.slug;
@@ -45,9 +44,6 @@ export function generateBlogPostMetadata(post, config) {
45
44
  const tags = isStringArray(fm.tags) ? fm.tags : [];
46
45
  const keywords = normalizeKeywords(fm.keywords);
47
46
  const allKeywords = [...tags, ...keywords].filter((k, i, arr) => arr.indexOf(k) === i); // Remove duplicates
48
- // Calculate reading time if not provided
49
- const readingTime = resolveFrontmatterField(['readingTime'], fm) || calculateReadingTime(post.content);
50
- const wordCount = calculateWordCount(post.content);
51
47
  // OG image resolution: ogImage > image > defaultOgImage
52
48
  // If not provided, Next.js will automatically use opengraph-image.tsx file convention
53
49
  const ogImageUrl = resolveFrontmatterField(['ogImage', 'image'], fm) ||
@@ -61,13 +57,12 @@ export function generateBlogPostMetadata(post, config) {
61
57
  const twitterDescription = resolveFrontmatterField(['twitterDescription'], fm) || ogDescription;
62
58
  // Canonical URL - supports both absolute and relative URLs
63
59
  // Relative URLs will be resolved against siteUrl
64
- const canonicalUrl = resolvePostUrl(resolveFrontmatterField(['canonicalUrl'], fm), post.slug, siteUrl);
60
+ const canonicalUrl = resolvePostUrlWithConfig(resolveFrontmatterField(['canonicalUrl'], fm), post.slug, siteUrl, blogConfig);
65
61
  // Language
66
62
  const lang = resolveFrontmatterField(['lang'], fm) || defaultLang;
67
63
  // Robots meta
68
64
  const robots = buildRobotsMeta(fm);
69
- // Post URL
70
- const postUrl = `${siteUrl}/blog/${post.slug}`;
65
+ const postUrl = canonicalUrl;
71
66
  // Article meta tags (for better SEO)
72
67
  const articleMeta = {};
73
68
  if (publishedDate)
@@ -104,8 +99,16 @@ export function generateBlogPostMetadata(post, config) {
104
99
  const alternates = {};
105
100
  if (canonicalUrl)
106
101
  alternates.canonical = canonicalUrl;
107
- if (alternateLanguages && Object.keys(alternateLanguages).length > 0) {
108
- alternates.languages = alternateLanguages;
102
+ const hreflang = resolveHreflangMap(fm, blogConfig);
103
+ if (hreflang && Object.keys(hreflang).length > 0) {
104
+ alternates.languages = siteUrl
105
+ ? Object.fromEntries(Object.entries(hreflang).map(([k, v]) => [
106
+ k,
107
+ v.startsWith('http://') || v.startsWith('https://')
108
+ ? v
109
+ : resolveCanonicalUrl(v, siteUrl),
110
+ ]))
111
+ : hreflang;
109
112
  }
110
113
  // Merge all custom meta tags into the 'other' field
111
114
  const otherMeta = {};
@@ -177,6 +180,7 @@ export function generateBlogListMetadata(posts, config) {
177
180
  const { siteName = DEFAULT_SITE_NAME, siteUrl = '' } = blogConfig;
178
181
  const title = 'Blog Posts';
179
182
  const description = `Browse all ${posts.length} blog posts`;
183
+ const listUrl = resolveBlogIndexUrl(siteUrl, blogConfig);
180
184
  const metadata = {
181
185
  title: `${title} | ${siteName}`,
182
186
  description,
@@ -184,7 +188,7 @@ export function generateBlogListMetadata(posts, config) {
184
188
  title,
185
189
  description,
186
190
  type: 'website',
187
- url: `${siteUrl}/blogs`,
191
+ url: listUrl,
188
192
  siteName,
189
193
  },
190
194
  twitter: {
@@ -1,11 +1,16 @@
1
1
  import type { BlogPost, Config } from './types.js';
2
+ /** Options for `generateBlogPostSchema` */
3
+ export interface BlogPostSchemaOptions {
4
+ /** If true and an organization @id exists, publisher is `{ "@id": "..." }` only */
5
+ publisherReference?: boolean;
6
+ }
2
7
  /**
3
8
  * Generates JSON-LD structured data (Schema.org) for a blog post
4
9
  * @param post - The blog post
5
10
  * @param config - SEO configuration
6
11
  * @returns JSON-LD schema object
7
12
  */
8
- export declare function generateBlogPostSchema(post: BlogPost, config?: Config): Record<string, unknown>;
13
+ export declare function generateBlogPostSchema(post: BlogPost, config?: Config, options?: BlogPostSchemaOptions): Record<string, unknown>;
9
14
  /**
10
15
  * Generates breadcrumbs schema for a blog post
11
16
  * @param post - The blog post
@@ -17,4 +22,11 @@ export declare function generateBreadcrumbsSchema(post: BlogPost, config?: Confi
17
22
  name: string;
18
23
  url: string;
19
24
  }>): Record<string, unknown>;
25
+ /**
26
+ * Single JSON-LD `@graph` for Organization + BlogPosting + BreadcrumbList.
27
+ */
28
+ export declare function generateBlogPostSchemaGraph(post: BlogPost, config?: Config, breadcrumbs?: Array<{
29
+ name: string;
30
+ url: string;
31
+ }>, includeBreadcrumbs?: boolean): Record<string, unknown>;
20
32
  //# sourceMappingURL=seo-schema.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"seo-schema.d.ts","sourceRoot":"","sources":["../../src/core/seo-schema.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAU,MAAM,EAAE,MAAM,YAAY,CAAC;AAY3D;;;;;GAKG;AACH,wBAAgB,sBAAsB,CACpC,IAAI,EAAE,QAAQ,EACd,MAAM,CAAC,EAAE,MAAM,GACd,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CA8GzB;AAED;;;;;;GAMG;AACH,wBAAgB,yBAAyB,CACvC,IAAI,EAAE,QAAQ,EACd,MAAM,CAAC,EAAE,MAAM,EACf,WAAW,CAAC,EAAE,KAAK,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,CAAC,GACjD,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAiCzB"}
1
+ {"version":3,"file":"seo-schema.d.ts","sourceRoot":"","sources":["../../src/core/seo-schema.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAU,MAAM,EAAE,MAAM,YAAY,CAAC;AAiB3D,2CAA2C;AAC3C,MAAM,WAAW,qBAAqB;IACpC,mFAAmF;IACnF,kBAAkB,CAAC,EAAE,OAAO,CAAC;CAC9B;AAED;;;;;GAKG;AACH,wBAAgB,sBAAsB,CACpC,IAAI,EAAE,QAAQ,EACd,MAAM,CAAC,EAAE,MAAM,EACf,OAAO,CAAC,EAAE,qBAAqB,GAC9B,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAmHzB;AAED;;;;;;GAMG;AACH,wBAAgB,yBAAyB,CACvC,IAAI,EAAE,QAAQ,EACd,MAAM,CAAC,EAAE,MAAM,EACf,WAAW,CAAC,EAAE,KAAK,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,CAAC,GACjD,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAoCzB;AAED;;GAEG;AACH,wBAAgB,2BAA2B,CACzC,IAAI,EAAE,QAAQ,EACd,MAAM,CAAC,EAAE,MAAM,EACf,WAAW,CAAC,EAAE,KAAK,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,CAAC,EAClD,kBAAkB,UAAO,GACxB,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAqBzB"}
@@ -2,16 +2,18 @@ import { calculateReadingTime, calculateWordCount } from './utils.js';
2
2
  import { getConfig } from './config.js';
3
3
  import { resolveFrontmatterField, isStringArray } from './type-guards.js';
4
4
  import { DEFAULT_SITE_NAME } from './constants.js';
5
- import { ensureAuthorsResolved, resolveDefaultAuthor, getAuthorNames, resolvePostUrl, } from './seo-utils.js';
5
+ import { ensureAuthorsResolved, resolveDefaultAuthor, resolvePostUrlWithConfig, resolveBlogIndexUrl, } from './seo-utils.js';
6
+ import { buildPublisherEmbedded, buildOrganizationGraphNode, resolveOrganizationId, } from './organization-schema.js';
6
7
  /**
7
8
  * Generates JSON-LD structured data (Schema.org) for a blog post
8
9
  * @param post - The blog post
9
10
  * @param config - SEO configuration
10
11
  * @returns JSON-LD schema object
11
12
  */
12
- export function generateBlogPostSchema(post, config) {
13
+ export function generateBlogPostSchema(post, config, options) {
13
14
  const blogConfig = config || getConfig();
14
15
  const { siteName = DEFAULT_SITE_NAME, siteUrl = '', defaultAuthor, authors: configAuthors, } = blogConfig;
16
+ const publisherReference = options?.publisherReference === true;
15
17
  const fm = post.frontmatter;
16
18
  const title = resolveFrontmatterField(['seoTitle', 'title'], fm, post.slug) || post.slug;
17
19
  const description = resolveFrontmatterField(['seoDescription', 'description', 'excerpt'], fm, '') || '';
@@ -24,10 +26,9 @@ export function generateBlogPostSchema(post, config) {
24
26
  : (resolvedDefaultAuthor ? [resolvedDefaultAuthor] : []);
25
27
  // Ensure all authors are resolved from config (safety net)
26
28
  const authors = ensureAuthorsResolved(postAuthors, configAuthors);
27
- const authorNames = getAuthorNames(authors);
28
29
  const publishedDate = resolveFrontmatterField(['publishedDate', 'date'], fm);
29
30
  const modifiedDate = resolveFrontmatterField(['modifiedDate'], fm) || publishedDate;
30
- const postUrl = resolvePostUrl(resolveFrontmatterField(['canonicalUrl'], fm), post.slug, siteUrl);
31
+ const postUrl = resolvePostUrlWithConfig(resolveFrontmatterField(['canonicalUrl'], fm), post.slug, siteUrl, blogConfig);
31
32
  const ogImageUrl = resolveFrontmatterField(['ogImage', 'image'], fm);
32
33
  // Calculate reading time and word count if not provided
33
34
  const readingTime = resolveFrontmatterField(['readingTime'], fm) || calculateReadingTime(post.content);
@@ -66,13 +67,17 @@ export function generateBlogPostSchema(post, config) {
66
67
  ? buildAuthorSchema(authors[0])
67
68
  : authors.map(buildAuthorSchema),
68
69
  }),
69
- ...(siteName && {
70
- publisher: {
71
- '@type': 'Organization',
72
- name: siteName,
73
- ...(siteUrl && { url: siteUrl }),
74
- },
75
- }),
70
+ ...(siteName &&
71
+ (() => {
72
+ if (publisherReference) {
73
+ const orgId = resolveOrganizationId(blogConfig);
74
+ if (orgId) {
75
+ return { publisher: { '@id': orgId } };
76
+ }
77
+ }
78
+ const pub = buildPublisherEmbedded(blogConfig);
79
+ return pub ? { publisher: pub } : {};
80
+ })()),
76
81
  ...(ogImageUrl && {
77
82
  image: {
78
83
  '@type': 'ImageObject',
@@ -110,11 +115,12 @@ export function generateBreadcrumbsSchema(post, config, breadcrumbs) {
110
115
  const blogConfig = config || getConfig();
111
116
  const { siteUrl = '' } = blogConfig;
112
117
  const title = resolveFrontmatterField(['seoTitle', 'title'], post.frontmatter, post.slug) || post.slug;
113
- const postUrl = resolvePostUrl(resolveFrontmatterField(['canonicalUrl'], post.frontmatter), post.slug, siteUrl);
118
+ const postUrl = resolvePostUrlWithConfig(resolveFrontmatterField(['canonicalUrl'], post.frontmatter), post.slug, siteUrl, blogConfig);
119
+ const blogIndexUrl = resolveBlogIndexUrl(siteUrl, blogConfig);
114
120
  // Default breadcrumbs: Home > Blog > Post
115
121
  const defaultBreadcrumbs = [
116
122
  { name: 'Home', url: siteUrl || '/' },
117
- { name: 'Blog', url: `${siteUrl}/blogs` },
123
+ { name: 'Blog', url: blogIndexUrl },
118
124
  { name: title, url: postUrl },
119
125
  ];
120
126
  const items = breadcrumbs || defaultBreadcrumbs;
@@ -129,3 +135,26 @@ export function generateBreadcrumbsSchema(post, config, breadcrumbs) {
129
135
  })),
130
136
  };
131
137
  }
138
+ /**
139
+ * Single JSON-LD `@graph` for Organization + BlogPosting + BreadcrumbList.
140
+ */
141
+ export function generateBlogPostSchemaGraph(post, config, breadcrumbs, includeBreadcrumbs = true) {
142
+ const orgNode = buildOrganizationGraphNode(config);
143
+ const article = generateBlogPostSchema(post, config, { publisherReference: true });
144
+ const articleBody = { ...article };
145
+ delete articleBody['@context'];
146
+ const graph = [];
147
+ if (orgNode)
148
+ graph.push(orgNode);
149
+ graph.push(articleBody);
150
+ if (includeBreadcrumbs) {
151
+ const crumbs = generateBreadcrumbsSchema(post, config, breadcrumbs);
152
+ const crumbsBody = { ...crumbs };
153
+ delete crumbsBody['@context'];
154
+ graph.push(crumbsBody);
155
+ }
156
+ return {
157
+ '@context': 'https://schema.org',
158
+ '@graph': graph,
159
+ };
160
+ }
@@ -1,4 +1,4 @@
1
- import type { BlogPost, Author } from './types.js';
1
+ import type { BlogPost, Author, Config, BlogPostFrontmatter } from './types.js';
2
2
  /**
3
3
  * Normalizes keywords to an array of strings
4
4
  * Handles both string (comma-separated) and array formats
@@ -50,13 +50,25 @@ export declare function buildRobotsMeta(frontmatter: BlogPost['frontmatter']): s
50
50
  */
51
51
  export declare function resolveCanonicalUrl(url: string, siteUrl: string): string;
52
52
  /**
53
- * Resolves a post URL from canonical URL or generates default
54
- * @param canonicalUrl - Optional canonical URL from frontmatter
55
- * @param slug - Post slug
56
- * @param siteUrl - Base site URL
57
- * @returns Resolved absolute URL
53
+ * Default path segment for post URLs (no leading/trailing slashes).
58
54
  */
59
- export declare function resolvePostUrl(canonicalUrl: string | undefined, slug: string, siteUrl: string): string;
55
+ export declare function getBlogPostPathSegment(config?: Config): string;
56
+ /**
57
+ * Resolves a post URL from canonical URL or generates default `/{segment}/{slug}` under siteUrl.
58
+ */
59
+ export declare function resolvePostUrl(canonicalUrl: string | undefined, slug: string, siteUrl: string, blogPostPathSegment?: string): string;
60
+ /**
61
+ * Resolves post URL using optional blog config for path segment.
62
+ */
63
+ export declare function resolvePostUrlWithConfig(canonicalUrl: string | undefined, slug: string, siteUrl: string, config?: Config): string;
64
+ /**
65
+ * Default blog listing URL for breadcrumbs (config.blogIndexPath or `{siteUrl}/blogs`).
66
+ */
67
+ /**
68
+ * Per-post hreflang map: frontmatter.alternateLanguages overrides config.alternateLanguages.
69
+ */
70
+ export declare function resolveHreflangMap(fm: BlogPostFrontmatter, config?: Config): Record<string, string> | undefined;
71
+ export declare function resolveBlogIndexUrl(siteUrl: string, config?: Config): string;
60
72
  /**
61
73
  * Escapes XML special characters to prevent injection attacks
62
74
  * @param unsafe - String that may contain XML special characters
@@ -1 +1 @@
1
- {"version":3,"file":"seo-utils.d.ts","sourceRoot":"","sources":["../../src/core/seo-utils.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC;AAInD;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,MAAM,EAAE,CAOxE;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,CAE7D;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,CAAC,MAAM,GAAG,MAAM,CAAC,EAAE,GAAG,MAAM,EAAE,CAErE;AAED;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,CAAC,MAAM,GAAG,MAAM,CAAC,EAAE,EAC5B,aAAa,CAAC,EAAE,MAAM,EAAE,GACvB,CAAC,MAAM,GAAG,MAAM,CAAC,EAAE,CAcrB;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAClC,aAAa,EAAE,MAAM,GAAG,SAAS,EACjC,aAAa,CAAC,EAAE,MAAM,EAAE,GACvB,MAAM,GAAG,MAAM,GAAG,SAAS,CAI7B;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,WAAW,EAAE,QAAQ,CAAC,aAAa,CAAC,GAAG,MAAM,GAAG,SAAS,CAqBxF;AAED;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAgBxE;AAED;;;;;;GAMG;AACH,wBAAgB,cAAc,CAC5B,YAAY,EAAE,MAAM,GAAG,SAAS,EAChC,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,GACd,MAAM,CAGR;AAED;;;;GAIG;AACH,wBAAgB,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAOhD"}
1
+ {"version":3,"file":"seo-utils.d.ts","sourceRoot":"","sources":["../../src/core/seo-utils.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AAGhF;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,MAAM,EAAE,CAOxE;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,CAE7D;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,CAAC,MAAM,GAAG,MAAM,CAAC,EAAE,GAAG,MAAM,EAAE,CAErE;AAED;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,CAAC,MAAM,GAAG,MAAM,CAAC,EAAE,EAC5B,aAAa,CAAC,EAAE,MAAM,EAAE,GACvB,CAAC,MAAM,GAAG,MAAM,CAAC,EAAE,CAcrB;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAClC,aAAa,EAAE,MAAM,GAAG,SAAS,EACjC,aAAa,CAAC,EAAE,MAAM,EAAE,GACvB,MAAM,GAAG,MAAM,GAAG,SAAS,CAI7B;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,WAAW,EAAE,QAAQ,CAAC,aAAa,CAAC,GAAG,MAAM,GAAG,SAAS,CAqBxF;AAED;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAgBxE;AAED;;GAEG;AACH,wBAAgB,sBAAsB,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,CAI9D;AAED;;GAEG;AACH,wBAAgB,cAAc,CAC5B,YAAY,EAAE,MAAM,GAAG,SAAS,EAChC,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,EACf,mBAAmB,SAAS,GAC3B,MAAM,CAMR;AAED;;GAEG;AACH,wBAAgB,wBAAwB,CACtC,YAAY,EAAE,MAAM,GAAG,SAAS,EAChC,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,EACf,MAAM,CAAC,EAAE,MAAM,GACd,MAAM,CAOR;AAED;;GAEG;AACH;;GAEG;AACH,wBAAgB,kBAAkB,CAChC,EAAE,EAAE,mBAAmB,EACvB,MAAM,CAAC,EAAE,MAAM,GACd,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,SAAS,CAkBpC;AAED,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,CAe5E;AAED;;;;GAIG;AACH,wBAAgB,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAOhD"}
@@ -110,16 +110,74 @@ export function resolveCanonicalUrl(url, siteUrl) {
110
110
  return `${baseUrl}/${url}`;
111
111
  }
112
112
  /**
113
- * Resolves a post URL from canonical URL or generates default
114
- * @param canonicalUrl - Optional canonical URL from frontmatter
115
- * @param slug - Post slug
116
- * @param siteUrl - Base site URL
117
- * @returns Resolved absolute URL
113
+ * Default path segment for post URLs (no leading/trailing slashes).
118
114
  */
119
- export function resolvePostUrl(canonicalUrl, slug, siteUrl) {
120
- const urlRaw = canonicalUrl || `${siteUrl}/blog/${slug}`;
115
+ export function getBlogPostPathSegment(config) {
116
+ const raw = config?.blogPostPathSegment?.trim();
117
+ if (!raw)
118
+ return 'blog';
119
+ return raw.replace(/^\/+|\/+$/g, '') || 'blog';
120
+ }
121
+ /**
122
+ * Resolves a post URL from canonical URL or generates default `/{segment}/{slug}` under siteUrl.
123
+ */
124
+ export function resolvePostUrl(canonicalUrl, slug, siteUrl, blogPostPathSegment = 'blog') {
125
+ const seg = blogPostPathSegment.replace(/^\/+|\/+$/g, '') || 'blog';
126
+ const base = siteUrl.replace(/\/$/, '');
127
+ const urlRaw = canonicalUrl || (base ? `${base}/${seg}/${slug}` : `/${seg}/${slug}`);
121
128
  return siteUrl ? resolveCanonicalUrl(urlRaw, siteUrl) : urlRaw;
122
129
  }
130
+ /**
131
+ * Resolves post URL using optional blog config for path segment.
132
+ */
133
+ export function resolvePostUrlWithConfig(canonicalUrl, slug, siteUrl, config) {
134
+ return resolvePostUrl(canonicalUrl, slug, siteUrl, getBlogPostPathSegment(config));
135
+ }
136
+ /**
137
+ * Default blog listing URL for breadcrumbs (config.blogIndexPath or `{siteUrl}/blogs`).
138
+ */
139
+ /**
140
+ * Per-post hreflang map: frontmatter.alternateLanguages overrides config.alternateLanguages.
141
+ */
142
+ export function resolveHreflangMap(fm, config) {
143
+ const fmAlt = fm.alternateLanguages;
144
+ if (fmAlt && typeof fmAlt === 'object' && !Array.isArray(fmAlt)) {
145
+ const out = {};
146
+ for (const [k, v] of Object.entries(fmAlt)) {
147
+ if (typeof v === 'string' && v.trim())
148
+ out[k] = v;
149
+ }
150
+ if (Object.keys(out).length > 0)
151
+ return out;
152
+ }
153
+ const cfgAlt = config?.alternateLanguages;
154
+ if (cfgAlt && typeof cfgAlt === 'object') {
155
+ const out = {};
156
+ for (const [k, v] of Object.entries(cfgAlt)) {
157
+ if (typeof v === 'string' && v.trim())
158
+ out[k] = v;
159
+ }
160
+ if (Object.keys(out).length > 0)
161
+ return out;
162
+ }
163
+ return undefined;
164
+ }
165
+ export function resolveBlogIndexUrl(siteUrl, config) {
166
+ const custom = config?.blogIndexPath?.trim();
167
+ if (!custom) {
168
+ const base = siteUrl.replace(/\/$/, '');
169
+ return base ? `${base}/blogs` : '/blogs';
170
+ }
171
+ if (custom.startsWith('http://') || custom.startsWith('https://')) {
172
+ return custom;
173
+ }
174
+ if (custom.startsWith('/')) {
175
+ const base = siteUrl.replace(/\/$/, '');
176
+ return base ? `${base}${custom}` : custom;
177
+ }
178
+ const base = siteUrl.replace(/\/$/, '');
179
+ return base ? `${base}/${custom.replace(/^\/+|\/+$/g, '')}` : `/${custom.replace(/^\/+|\/+$/g, '')}`;
180
+ }
123
181
  /**
124
182
  * Escapes XML special characters to prevent injection attacks
125
183
  * @param unsafe - String that may contain XML special characters
@@ -2,10 +2,14 @@
2
2
  * SEO utilities and generators for next-md-blog
3
3
  *
4
4
  * This module provides functions for generating metadata, schema.org structured data,
5
- * and RSS/sitemap feeds for blog posts.
5
+ * RSS feeds, and sitemap entry builders for blog posts.
6
6
  */
7
7
  export { generateBlogPostMetadata, generateBlogListMetadata, } from './seo-metadata.js';
8
- export { generateBlogPostSchema, generateBreadcrumbsSchema, } from './seo-schema.js';
9
- export { generateSitemap, generateRSSFeed, } from './seo-feeds.js';
10
- export { normalizeKeywords, getAuthorName, getAuthorNames, ensureAuthorsResolved, resolveDefaultAuthor, buildRobotsMeta, resolveCanonicalUrl, resolvePostUrl, escapeXml, } from './seo-utils.js';
8
+ export { generateBlogPostSchema, generateBreadcrumbsSchema, generateBlogPostSchemaGraph, } from './seo-schema.js';
9
+ export type { BlogPostSchemaOptions } from './seo-schema.js';
10
+ export { generateRSSFeed } from './seo-feeds.js';
11
+ export { generateOrganizationSchema } from './organization-schema.js';
12
+ export { getBlogSitemapEntries } from './sitemap-data.js';
13
+ export type { BlogSitemapEntry } from './sitemap-data.js';
14
+ export { normalizeKeywords, getAuthorName, getAuthorNames, ensureAuthorsResolved, resolveDefaultAuthor, buildRobotsMeta, resolveCanonicalUrl, resolvePostUrl, resolvePostUrlWithConfig, getBlogPostPathSegment, resolveHreflangMap, resolveBlogIndexUrl, escapeXml, } from './seo-utils.js';
11
15
  //# sourceMappingURL=seo.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"seo.d.ts","sourceRoot":"","sources":["../../src/core/seo.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,EACL,wBAAwB,EACxB,wBAAwB,GACzB,MAAM,mBAAmB,CAAC;AAE3B,OAAO,EACL,sBAAsB,EACtB,yBAAyB,GAC1B,MAAM,iBAAiB,CAAC;AAEzB,OAAO,EACL,eAAe,EACf,eAAe,GAChB,MAAM,gBAAgB,CAAC;AAGxB,OAAO,EACL,iBAAiB,EACjB,aAAa,EACb,cAAc,EACd,qBAAqB,EACrB,oBAAoB,EACpB,eAAe,EACf,mBAAmB,EACnB,cAAc,EACd,SAAS,GACV,MAAM,gBAAgB,CAAC"}
1
+ {"version":3,"file":"seo.d.ts","sourceRoot":"","sources":["../../src/core/seo.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,EACL,wBAAwB,EACxB,wBAAwB,GACzB,MAAM,mBAAmB,CAAC;AAE3B,OAAO,EACL,sBAAsB,EACtB,yBAAyB,EACzB,2BAA2B,GAC5B,MAAM,iBAAiB,CAAC;AACzB,YAAY,EAAE,qBAAqB,EAAE,MAAM,iBAAiB,CAAC;AAE7D,OAAO,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAEjD,OAAO,EAAE,0BAA0B,EAAE,MAAM,0BAA0B,CAAC;AAEtE,OAAO,EAAE,qBAAqB,EAAE,MAAM,mBAAmB,CAAC;AAC1D,YAAY,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAG1D,OAAO,EACL,iBAAiB,EACjB,aAAa,EACb,cAAc,EACd,qBAAqB,EACrB,oBAAoB,EACpB,eAAe,EACf,mBAAmB,EACnB,cAAc,EACd,wBAAwB,EACxB,sBAAsB,EACtB,kBAAkB,EAClB,mBAAmB,EACnB,SAAS,GACV,MAAM,gBAAgB,CAAC"}
package/dist/core/seo.js CHANGED
@@ -2,11 +2,13 @@
2
2
  * SEO utilities and generators for next-md-blog
3
3
  *
4
4
  * This module provides functions for generating metadata, schema.org structured data,
5
- * and RSS/sitemap feeds for blog posts.
5
+ * RSS feeds, and sitemap entry builders for blog posts.
6
6
  */
7
7
  // Re-export all SEO functions from their respective modules
8
8
  export { generateBlogPostMetadata, generateBlogListMetadata, } from './seo-metadata.js';
9
- export { generateBlogPostSchema, generateBreadcrumbsSchema, } from './seo-schema.js';
10
- export { generateSitemap, generateRSSFeed, } from './seo-feeds.js';
9
+ export { generateBlogPostSchema, generateBreadcrumbsSchema, generateBlogPostSchemaGraph, } from './seo-schema.js';
10
+ export { generateRSSFeed } from './seo-feeds.js';
11
+ export { generateOrganizationSchema } from './organization-schema.js';
12
+ export { getBlogSitemapEntries } from './sitemap-data.js';
11
13
  // Re-export utility functions that might be useful
12
- export { normalizeKeywords, getAuthorName, getAuthorNames, ensureAuthorsResolved, resolveDefaultAuthor, buildRobotsMeta, resolveCanonicalUrl, resolvePostUrl, escapeXml, } from './seo-utils.js';
14
+ export { normalizeKeywords, getAuthorName, getAuthorNames, ensureAuthorsResolved, resolveDefaultAuthor, buildRobotsMeta, resolveCanonicalUrl, resolvePostUrl, resolvePostUrlWithConfig, getBlogPostPathSegment, resolveHreflangMap, resolveBlogIndexUrl, escapeXml, } from './seo-utils.js';
@@ -0,0 +1,18 @@
1
+ import type { BlogPostMetadata, Config } from './types.js';
2
+ /**
3
+ * One sitemap row compatible with Next.js `MetadataRoute.Sitemap` entries.
4
+ */
5
+ export type BlogSitemapEntry = {
6
+ url: string;
7
+ lastModified?: string | Date;
8
+ changeFrequency?: 'always' | 'hourly' | 'daily' | 'weekly' | 'monthly' | 'yearly' | 'never';
9
+ priority?: number;
10
+ alternates?: {
11
+ languages?: Record<string, string>;
12
+ };
13
+ };
14
+ /**
15
+ * Builds sitemap entries for blog posts (use with `app/sitemap.ts` via `@next-md-blog/core/next`).
16
+ */
17
+ export declare function getBlogSitemapEntries(posts: BlogPostMetadata[], config?: Config): BlogSitemapEntry[];
18
+ //# sourceMappingURL=sitemap-data.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sitemap-data.d.ts","sourceRoot":"","sources":["../../src/core/sitemap-data.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC;AAwB3D;;GAEG;AACH,MAAM,MAAM,gBAAgB,GAAG;IAC7B,GAAG,EAAE,MAAM,CAAC;IACZ,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,eAAe,CAAC,EAAE,QAAQ,GAAG,QAAQ,GAAG,OAAO,GAAG,QAAQ,GAAG,SAAS,GAAG,QAAQ,GAAG,OAAO,CAAC;IAC5F,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE;QAAE,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;KAAE,CAAC;CACrD,CAAC;AAEF;;GAEG;AACH,wBAAgB,qBAAqB,CACnC,KAAK,EAAE,gBAAgB,EAAE,EACzB,MAAM,CAAC,EAAE,MAAM,GACd,gBAAgB,EAAE,CAmCpB"}
@@ -0,0 +1,42 @@
1
+ import { getConfig } from './config.js';
2
+ import { resolveFrontmatterField } from './type-guards.js';
3
+ import { resolvePostUrlWithConfig, resolveHreflangMap, resolveCanonicalUrl, } from './seo-utils.js';
4
+ function absolutizeLanguageMap(langs, siteUrl) {
5
+ if (!siteUrl)
6
+ return langs;
7
+ const out = {};
8
+ for (const [k, v] of Object.entries(langs)) {
9
+ out[k] =
10
+ v.startsWith('http://') || v.startsWith('https://')
11
+ ? v
12
+ : resolveCanonicalUrl(v, siteUrl);
13
+ }
14
+ return out;
15
+ }
16
+ /**
17
+ * Builds sitemap entries for blog posts (use with `app/sitemap.ts` via `@next-md-blog/core/next`).
18
+ */
19
+ export function getBlogSitemapEntries(posts, config) {
20
+ const blogConfig = config || getConfig();
21
+ const { siteUrl = '' } = blogConfig;
22
+ return posts.map((post) => {
23
+ const url = resolvePostUrlWithConfig(resolveFrontmatterField(['canonicalUrl'], post.frontmatter), post.slug, siteUrl, blogConfig);
24
+ const dateStr = resolveFrontmatterField(['modifiedDate', 'date'], post.frontmatter) ||
25
+ new Date().toISOString().split('T')[0];
26
+ const parsed = new Date(dateStr);
27
+ const lastModified = Number.isNaN(parsed.getTime()) ? dateStr : parsed;
28
+ const languagesRaw = resolveHreflangMap(post.frontmatter, blogConfig);
29
+ const entry = {
30
+ url,
31
+ lastModified,
32
+ changeFrequency: 'monthly',
33
+ priority: 0.8,
34
+ };
35
+ if (languagesRaw && Object.keys(languagesRaw).length > 0) {
36
+ entry.alternates = {
37
+ languages: absolutizeLanguageMap(languagesRaw, siteUrl),
38
+ };
39
+ }
40
+ return entry;
41
+ });
42
+ }
@@ -1,3 +1,16 @@
1
+ /**
2
+ * Publisher / Organization fields for JSON-LD (extends siteName + siteUrl).
3
+ */
4
+ export interface SiteOrganization {
5
+ /** Override JSON-LD @id (default: `${origin}/#organization`) */
6
+ id?: string;
7
+ legalName?: string;
8
+ description?: string;
9
+ /** Absolute URL of the logo image */
10
+ logo?: string;
11
+ /** Official profiles (Schema.org sameAs) */
12
+ sameAs?: string[];
13
+ }
1
14
  /**
2
15
  * Blog configuration
3
16
  */
@@ -18,6 +31,12 @@ export interface Config {
18
31
  defaultLang?: string;
19
32
  /** Alternate language URLs for hreflang (e.g., { 'en': 'https://example.com/blog/post', 'fr': 'https://example.com/fr/blog/post' }) */
20
33
  alternateLanguages?: Record<string, string>;
34
+ /** Path segment for post URLs when no canonicalUrl (default: `blog` → `/blog/{slug}`) */
35
+ blogPostPathSegment?: string;
36
+ /** Absolute or site-relative URL for the blog index in default breadcrumbs (default: `{siteUrl}/blogs`) */
37
+ blogIndexPath?: string;
38
+ /** Rich Organization / publisher JSON-LD */
39
+ organization?: SiteOrganization;
21
40
  }
22
41
  /**
23
42
  * Author information
@@ -64,6 +83,8 @@ export interface BlogPostFrontmatter {
64
83
  nofollow?: boolean;
65
84
  /** Reading time in minutes (auto-calculated if not provided) */
66
85
  readingTime?: number;
86
+ /** Per-post hreflang map (overrides config.alternateLanguages for this post) */
87
+ alternateLanguages?: Record<string, string>;
67
88
  [key: string]: unknown;
68
89
  }
69
90
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/core/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,MAAM,WAAW,MAAM;IACrB,gBAAgB;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,uBAAuB;IACvB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,0BAA0B;IAC1B,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,wDAAwD;IACxD,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,qBAAqB;IACrB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,2BAA2B;IAC3B,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,4BAA4B;IAC5B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,uIAAuI;IACvI,kBAAkB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC7C;AAED;;GAEG;AACH,MAAM,WAAW,MAAM;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,kGAAkG;IAClG,MAAM,CAAC,EAAE,MAAM,GAAG;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;KAAE,GAAG,CAAC,MAAM,GAAG;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;KAAE,CAAC,EAAE,CAAC;IACnH,uEAAuE;IACvE,OAAO,CAAC,EAAE,CAAC,MAAM,GAAG;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;KAAE,CAAC,EAAE,CAAC;IAChE,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,0BAA0B;IAC1B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,iDAAiD;IACjD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,2EAA2E;IAC3E,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,qDAAqD;IACrD,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,8CAA8C;IAC9C,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,gEAAgE;IAChE,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED;;GAEG;AACH,MAAM,WAAW,QAAQ;IACvB,oEAAoE;IACpE,IAAI,EAAE,MAAM,CAAC;IACb,uCAAuC;IACvC,OAAO,EAAE,MAAM,CAAC;IAChB,yDAAyD;IACzD,WAAW,EAAE,mBAAmB,CAAC;IACjC,gDAAgD;IAChD,WAAW,EAAE,MAAM,CAAC;IACpB,mCAAmC;IACnC,SAAS,EAAE,MAAM,CAAC;IAClB,0DAA0D;IAC1D,OAAO,EAAE,CAAC,MAAM,GAAG,MAAM,CAAC,EAAE,CAAC;CAC9B;AAED;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,uCAAuC;IACvC,IAAI,EAAE,MAAM,CAAC;IACb,yDAAyD;IACzD,WAAW,EAAE,mBAAmB,CAAC;IACjC,0DAA0D;IAC1D,OAAO,EAAE,CAAC,MAAM,GAAG,MAAM,CAAC,EAAE,CAAC;CAC9B;AAED;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC,qCAAqC;IACrC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,gEAAgE;IAChE,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,+CAA+C;IAC/C,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED;;GAEG;AACH,MAAM,WAAW,gBAAgB,CAAC,CAAC;IACjC,OAAO,EAAE,OAAO,CAAC;IACjB,IAAI,CAAC,EAAE,CAAC,CAAC;IACT,KAAK,CAAC,EAAE,KAAK,CAAC;CACf"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/core/types.ts"],"names":[],"mappings":"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"}
@@ -1,8 +1,8 @@
1
1
  // Regex patterns for markdown stripping (compiled once at module level)
2
2
  const CODE_BLOCK_REGEX = /```[\s\S]*?```/g;
3
3
  const INLINE_CODE_REGEX = /`[^`]*`/g;
4
- const LINK_REGEX = /\[([^\]]*)\]\([^\)]*\)/g;
5
- const IMAGE_REGEX = /!\[([^\]]*)\]\([^\)]*\)/g;
4
+ const LINK_REGEX = /\[([^\]]*)]\([^)]*\)/g;
5
+ const IMAGE_REGEX = /!\[([^\]]*)]\([^)]*\)/g;
6
6
  const HTML_TAG_REGEX = /<[^>]*>/g;
7
7
  const HEADER_REGEX = /#+\s+/g;
8
8
  const HORIZONTAL_RULE_REGEX = /---+/g;
package/dist/index.d.ts CHANGED
@@ -6,9 +6,11 @@ export type { OgImageProps } from './components/OgImage.js';
6
6
  export { BlogPostSEO } from './components/BlogPostSEO.js';
7
7
  export type { BlogPostSEOProps } from './components/BlogPostSEO.js';
8
8
  export { getBlogPost, getAllBlogPosts, getAllBlogPostSlugs } from './core/file-utils.js';
9
- export { generateBlogPostMetadata, generateBlogListMetadata, generateBlogPostSchema, generateBreadcrumbsSchema, generateSitemap, generateRSSFeed, } from './core/seo.js';
9
+ export { generateBlogPostMetadata, generateBlogListMetadata, generateBlogPostSchema, generateBreadcrumbsSchema, generateBlogPostSchemaGraph, generateRSSFeed, generateOrganizationSchema, getBlogSitemapEntries, } from './core/seo.js';
10
+ export type { BlogPostSchemaOptions, BlogSitemapEntry } from './core/seo.js';
11
+ export { getBlogSitemap, getBlogRobots, createRssFeedResponse, } from './next.js';
10
12
  export { createConfig, loadConfig, getConfig } from './core/config.js';
11
- export type { BlogPost, BlogPostMetadata, BlogPostFrontmatter, GetBlogPostOptions, Author, Config, } from './core/types.js';
13
+ export type { BlogPost, BlogPostMetadata, BlogPostFrontmatter, GetBlogPostOptions, Author, Config, SiteOrganization, } from './core/types.js';
12
14
  export { MdxBlogError, BlogPostNotFoundError, FileReadError, DirectoryError, } from './core/errors.js';
13
15
  export { POSTS_DIR_NAME, getPostsDirectory } from './core/constants.js';
14
16
  export { calculateReadingTime, calculateWordCount, normalizeAuthors } from './core/utils.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,eAAe,EAAE,MAAM,iCAAiC,CAAC;AAClE,YAAY,EAAE,oBAAoB,EAAE,kBAAkB,EAAE,MAAM,iCAAiC,CAAC;AAChG,OAAO,EAAE,yBAAyB,EAAE,MAAM,mCAAmC,CAAC;AAC9E,OAAO,EAAE,OAAO,EAAE,MAAM,yBAAyB,CAAC;AAClD,YAAY,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AAC5D,OAAO,EAAE,WAAW,EAAE,MAAM,6BAA6B,CAAC;AAC1D,YAAY,EAAE,gBAAgB,EAAE,MAAM,6BAA6B,CAAC;AAGpE,OAAO,EAAE,WAAW,EAAE,eAAe,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAGzF,OAAO,EACL,wBAAwB,EACxB,wBAAwB,EACxB,sBAAsB,EACtB,yBAAyB,EACzB,eAAe,EACf,eAAe,GAChB,MAAM,eAAe,CAAC;AAGvB,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAGvE,YAAY,EACV,QAAQ,EACR,gBAAgB,EAChB,mBAAmB,EACnB,kBAAkB,EAClB,MAAM,EACN,MAAM,GACP,MAAM,iBAAiB,CAAC;AAGzB,OAAO,EACL,YAAY,EACZ,qBAAqB,EACrB,aAAa,EACb,cAAc,GACf,MAAM,kBAAkB,CAAC;AAG1B,OAAO,EAAE,cAAc,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AAGxE,OAAO,EAAE,oBAAoB,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,eAAe,EAAE,MAAM,iCAAiC,CAAC;AAClE,YAAY,EAAE,oBAAoB,EAAE,kBAAkB,EAAE,MAAM,iCAAiC,CAAC;AAChG,OAAO,EAAE,yBAAyB,EAAE,MAAM,mCAAmC,CAAC;AAC9E,OAAO,EAAE,OAAO,EAAE,MAAM,yBAAyB,CAAC;AAClD,YAAY,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AAC5D,OAAO,EAAE,WAAW,EAAE,MAAM,6BAA6B,CAAC;AAC1D,YAAY,EAAE,gBAAgB,EAAE,MAAM,6BAA6B,CAAC;AAGpE,OAAO,EAAE,WAAW,EAAE,eAAe,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAGzF,OAAO,EACL,wBAAwB,EACxB,wBAAwB,EACxB,sBAAsB,EACtB,yBAAyB,EACzB,2BAA2B,EAC3B,eAAe,EACf,0BAA0B,EAC1B,qBAAqB,GACtB,MAAM,eAAe,CAAC;AACvB,YAAY,EAAE,qBAAqB,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAE7E,OAAO,EACL,cAAc,EACd,aAAa,EACb,qBAAqB,GACtB,MAAM,WAAW,CAAC;AAGnB,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAGvE,YAAY,EACV,QAAQ,EACR,gBAAgB,EAChB,mBAAmB,EACnB,kBAAkB,EAClB,MAAM,EACN,MAAM,EACN,gBAAgB,GACjB,MAAM,iBAAiB,CAAC;AAGzB,OAAO,EACL,YAAY,EACZ,qBAAqB,EACrB,aAAa,EACb,cAAc,GACf,MAAM,kBAAkB,CAAC;AAG1B,OAAO,EAAE,cAAc,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AAGxE,OAAO,EAAE,oBAAoB,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC"}
package/dist/index.js CHANGED
@@ -6,7 +6,8 @@ export { BlogPostSEO } from './components/BlogPostSEO.js';
6
6
  // Utilities
7
7
  export { getBlogPost, getAllBlogPosts, getAllBlogPostSlugs } from './core/file-utils.js';
8
8
  // SEO
9
- export { generateBlogPostMetadata, generateBlogListMetadata, generateBlogPostSchema, generateBreadcrumbsSchema, generateSitemap, generateRSSFeed, } from './core/seo.js';
9
+ export { generateBlogPostMetadata, generateBlogListMetadata, generateBlogPostSchema, generateBreadcrumbsSchema, generateBlogPostSchemaGraph, generateRSSFeed, generateOrganizationSchema, getBlogSitemapEntries, } from './core/seo.js';
10
+ export { getBlogSitemap, getBlogRobots, createRssFeedResponse, } from './next.js';
10
11
  // Config
11
12
  export { createConfig, loadConfig, getConfig } from './core/config.js';
12
13
  // Errors