nextblogkit 0.6.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/LICENSE +21 -0
- package/README.md +951 -0
- package/dist/admin/index.cjs +2465 -0
- package/dist/admin/index.cjs.map +1 -0
- package/dist/admin/index.d.cts +44 -0
- package/dist/admin/index.d.ts +44 -0
- package/dist/admin/index.js +2438 -0
- package/dist/admin/index.js.map +1 -0
- package/dist/api/categories.cjs +82 -0
- package/dist/api/categories.cjs.map +1 -0
- package/dist/api/categories.d.cts +27 -0
- package/dist/api/categories.d.ts +27 -0
- package/dist/api/categories.js +77 -0
- package/dist/api/categories.js.map +1 -0
- package/dist/api/media.cjs +113 -0
- package/dist/api/media.cjs.map +1 -0
- package/dist/api/media.d.cts +22 -0
- package/dist/api/media.d.ts +22 -0
- package/dist/api/media.js +109 -0
- package/dist/api/media.js.map +1 -0
- package/dist/api/posts.cjs +103 -0
- package/dist/api/posts.cjs.map +1 -0
- package/dist/api/posts.d.cts +27 -0
- package/dist/api/posts.d.ts +27 -0
- package/dist/api/posts.js +98 -0
- package/dist/api/posts.js.map +1 -0
- package/dist/api/rss.cjs +25 -0
- package/dist/api/rss.cjs.map +1 -0
- package/dist/api/rss.d.cts +5 -0
- package/dist/api/rss.d.ts +5 -0
- package/dist/api/rss.js +23 -0
- package/dist/api/rss.js.map +1 -0
- package/dist/api/settings.cjs +40 -0
- package/dist/api/settings.cjs.map +1 -0
- package/dist/api/settings.d.cts +17 -0
- package/dist/api/settings.d.ts +17 -0
- package/dist/api/settings.js +37 -0
- package/dist/api/settings.js.map +1 -0
- package/dist/api/sitemap.cjs +25 -0
- package/dist/api/sitemap.cjs.map +1 -0
- package/dist/api/sitemap.d.cts +5 -0
- package/dist/api/sitemap.d.ts +5 -0
- package/dist/api/sitemap.js +23 -0
- package/dist/api/sitemap.js.map +1 -0
- package/dist/chunk-4NKOJYWJ.js +68 -0
- package/dist/chunk-4NKOJYWJ.js.map +1 -0
- package/dist/chunk-4PY224XM.js +103 -0
- package/dist/chunk-4PY224XM.js.map +1 -0
- package/dist/chunk-64HUVJOZ.js +446 -0
- package/dist/chunk-64HUVJOZ.js.map +1 -0
- package/dist/chunk-6HKMZOI4.cjs +48 -0
- package/dist/chunk-6HKMZOI4.cjs.map +1 -0
- package/dist/chunk-A2S32RZN.js +138 -0
- package/dist/chunk-A2S32RZN.js.map +1 -0
- package/dist/chunk-E2QLTHKN.cjs +70 -0
- package/dist/chunk-E2QLTHKN.cjs.map +1 -0
- package/dist/chunk-JLPJKNRZ.js +37 -0
- package/dist/chunk-JLPJKNRZ.js.map +1 -0
- package/dist/chunk-JM7QRXXK.js +330 -0
- package/dist/chunk-JM7QRXXK.js.map +1 -0
- package/dist/chunk-KDZER3PU.cjs +43 -0
- package/dist/chunk-KDZER3PU.cjs.map +1 -0
- package/dist/chunk-N5MKAD7J.cjs +109 -0
- package/dist/chunk-N5MKAD7J.cjs.map +1 -0
- package/dist/chunk-QE4VLQYN.cjs +337 -0
- package/dist/chunk-QE4VLQYN.cjs.map +1 -0
- package/dist/chunk-R6MO3QIP.js +46 -0
- package/dist/chunk-R6MO3QIP.js.map +1 -0
- package/dist/chunk-U2ROR6AY.cjs +476 -0
- package/dist/chunk-U2ROR6AY.cjs.map +1 -0
- package/dist/chunk-ZP5XRVVH.cjs +141 -0
- package/dist/chunk-ZP5XRVVH.cjs.map +1 -0
- package/dist/cli/index.cjs +1308 -0
- package/dist/components/index.cjs +541 -0
- package/dist/components/index.cjs.map +1 -0
- package/dist/components/index.d.cts +165 -0
- package/dist/components/index.d.ts +165 -0
- package/dist/components/index.js +527 -0
- package/dist/components/index.js.map +1 -0
- package/dist/editor/index.cjs +1083 -0
- package/dist/editor/index.cjs.map +1 -0
- package/dist/editor/index.d.cts +133 -0
- package/dist/editor/index.d.ts +133 -0
- package/dist/editor/index.js +1051 -0
- package/dist/editor/index.js.map +1 -0
- package/dist/index-Cgzphklp.d.ts +266 -0
- package/dist/index-vjlZDWNr.d.cts +266 -0
- package/dist/index.cjs +368 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +27 -0
- package/dist/index.d.ts +27 -0
- package/dist/index.js +208 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/index.cjs +120 -0
- package/dist/lib/index.cjs.map +1 -0
- package/dist/lib/index.d.cts +4 -0
- package/dist/lib/index.d.ts +4 -0
- package/dist/lib/index.js +7 -0
- package/dist/lib/index.js.map +1 -0
- package/dist/styles/admin.css +657 -0
- package/dist/styles/blog.css +851 -0
- package/dist/styles/editor.css +452 -0
- package/dist/styles/globals.css +270 -0
- package/dist/styles/prose.css +299 -0
- package/dist/types-CBEEBR4A.d.cts +732 -0
- package/dist/types-CBEEBR4A.d.ts +732 -0
- package/package.json +134 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/lib/search.ts","../src/lib/seo.ts","../src/lib/seo-scorer.ts"],"names":[],"mappings":"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","file":"chunk-JM7QRXXK.js","sourcesContent":["import type { Collection, Document } from 'mongodb';\n\nexport interface SearchResult {\n slug: string;\n title: string;\n excerpt: string;\n score: number;\n}\n\nexport async function searchPosts(\n collection: Collection,\n query: string,\n limit: number = 10\n): Promise<SearchResult[]> {\n if (!query.trim()) return [];\n\n const results = await collection\n .find(\n {\n $text: { $search: query },\n status: 'published',\n },\n {\n projection: {\n slug: 1,\n title: 1,\n excerpt: 1,\n score: { $meta: 'textScore' },\n },\n }\n )\n .sort({ score: { $meta: 'textScore' } })\n .limit(limit)\n .toArray();\n\n return results.map((doc: Document) => ({\n slug: doc.slug as string,\n title: doc.title as string,\n excerpt: doc.excerpt as string,\n score: doc.score as number,\n }));\n}\n","import type { BlogPost } from './types';\nimport { getEnvConfig } from './config';\n\nexport interface MetaTags {\n title: string;\n description: string;\n canonical: string;\n openGraph: {\n title: string;\n description: string;\n url: string;\n siteName: string;\n type: string;\n images: { url: string; width?: number; height?: number; alt?: string }[];\n article?: {\n publishedTime: string;\n modifiedTime: string;\n section?: string;\n tags?: string[];\n };\n };\n twitter: {\n card: string;\n title: string;\n description: string;\n images: string[];\n };\n robots?: string;\n}\n\nexport function generateMetaTags(post: BlogPost): MetaTags {\n const env = getEnvConfig();\n const postUrl = `${env.NEXTBLOGKIT_SITE_URL}/blog/${post.slug}`;\n const title = post.seo?.metaTitle || post.title;\n const description = post.seo?.metaDescription || post.excerpt;\n const canonical = post.seo?.canonicalUrl || postUrl;\n const ogImage = post.seo?.ogImage || post.coverImage?.url;\n\n return {\n title: `${title} | ${env.NEXTBLOGKIT_SITE_NAME}`,\n description,\n canonical,\n openGraph: {\n title,\n description,\n url: postUrl,\n siteName: env.NEXTBLOGKIT_SITE_NAME,\n type: post.seo?.ogType || 'article',\n images: ogImage\n ? [\n {\n url: ogImage,\n width: 1200,\n height: 630,\n alt: title,\n },\n ]\n : [],\n article: {\n publishedTime: post.publishedAt?.toISOString() || '',\n modifiedTime: post.updatedAt.toISOString(),\n section: post.categories[0],\n tags: post.tags,\n },\n },\n twitter: {\n card: 'summary_large_image',\n title,\n description,\n images: ogImage ? [ogImage] : [],\n },\n robots: post.seo?.noIndex ? 'noindex, nofollow' : undefined,\n };\n}\n\nexport interface ArticleStructuredData {\n '@context': string;\n '@type': string;\n headline: string;\n description: string;\n image?: string;\n datePublished?: string;\n dateModified: string;\n author: {\n '@type': string;\n name: string;\n url?: string;\n };\n publisher: {\n '@type': string;\n name: string;\n };\n mainEntityOfPage: {\n '@type': string;\n '@id': string;\n };\n wordCount: number;\n articleSection?: string;\n}\n\nexport function generateStructuredData(post: BlogPost): ArticleStructuredData {\n const env = getEnvConfig();\n const postUrl = `${env.NEXTBLOGKIT_SITE_URL}/blog/${post.slug}`;\n\n return {\n '@context': 'https://schema.org',\n '@type': 'BlogPosting',\n headline: post.title,\n description: post.excerpt,\n image: post.coverImage?.url || post.seo?.ogImage,\n datePublished: post.publishedAt?.toISOString(),\n dateModified: post.updatedAt.toISOString(),\n author: {\n '@type': 'Person',\n name: post.author.name,\n url: post.author.url,\n },\n publisher: {\n '@type': 'Organization',\n name: env.NEXTBLOGKIT_SITE_NAME,\n },\n mainEntityOfPage: {\n '@type': 'WebPage',\n '@id': postUrl,\n },\n wordCount: post.wordCount,\n articleSection: post.categories[0],\n };\n}\n\nexport interface FAQStructuredData {\n '@context': string;\n '@type': string;\n mainEntity: {\n '@type': string;\n name: string;\n acceptedAnswer: {\n '@type': string;\n text: string;\n };\n }[];\n}\n\nexport function generateFAQStructuredData(\n faqItems: { question: string; answer: string }[]\n): FAQStructuredData | null {\n if (!faqItems.length) return null;\n\n return {\n '@context': 'https://schema.org',\n '@type': 'FAQPage',\n mainEntity: faqItems.map((item) => ({\n '@type': 'Question',\n name: item.question,\n acceptedAnswer: {\n '@type': 'Answer',\n text: item.answer,\n },\n })),\n };\n}\n\nexport interface BreadcrumbStructuredData {\n '@context': string;\n '@type': string;\n itemListElement: {\n '@type': string;\n position: number;\n name: string;\n item?: string;\n }[];\n}\n\nexport function generateBreadcrumbs(\n post: BlogPost,\n categoryName?: string\n): BreadcrumbStructuredData {\n const env = getEnvConfig();\n const items: BreadcrumbStructuredData['itemListElement'] = [\n {\n '@type': 'ListItem',\n position: 1,\n name: 'Home',\n item: env.NEXTBLOGKIT_SITE_URL,\n },\n {\n '@type': 'ListItem',\n position: 2,\n name: 'Blog',\n item: `${env.NEXTBLOGKIT_SITE_URL}/blog`,\n },\n ];\n\n if (categoryName && post.categories[0]) {\n items.push({\n '@type': 'ListItem',\n position: 3,\n name: categoryName,\n item: `${env.NEXTBLOGKIT_SITE_URL}/blog/category/${post.categories[0]}`,\n });\n items.push({\n '@type': 'ListItem',\n position: 4,\n name: post.title,\n });\n } else {\n items.push({\n '@type': 'ListItem',\n position: 3,\n name: post.title,\n });\n }\n\n return {\n '@context': 'https://schema.org',\n '@type': 'BreadcrumbList',\n itemListElement: items,\n };\n}\n","import type { BlogPost, SEOScore, SEOCheck, SEOCheckStatus } from './types';\n\nfunction check(id: string, status: SEOCheckStatus, message: string): SEOCheck {\n return { id, status, message };\n}\n\nexport function calculateSEOScore(post: BlogPost): SEOScore {\n const checks: SEOCheck[] = [];\n const keyword = post.seo?.focusKeyword?.toLowerCase() || '';\n const title = post.title.toLowerCase();\n const slug = post.slug.toLowerCase();\n const excerpt = (post.seo?.metaDescription || post.excerpt || '').toLowerCase();\n const contentText = post.contentText?.toLowerCase() || '';\n const contentHTML = post.contentHTML || '';\n\n // 1. Focus keyword in title\n if (!keyword) {\n checks.push(check('focus-keyword-in-title', 'warn', 'No focus keyword set'));\n } else if (title.includes(keyword)) {\n checks.push(check('focus-keyword-in-title', 'pass', 'Focus keyword appears in title'));\n } else {\n checks.push(check('focus-keyword-in-title', 'fail', 'Focus keyword not found in title'));\n }\n\n // 2. Focus keyword in slug\n if (!keyword) {\n checks.push(check('focus-keyword-in-slug', 'warn', 'No focus keyword set'));\n } else if (slug.includes(keyword.replace(/\\s+/g, '-'))) {\n checks.push(check('focus-keyword-in-slug', 'pass', 'Focus keyword appears in URL slug'));\n } else {\n checks.push(check('focus-keyword-in-slug', 'fail', 'Focus keyword not found in URL slug'));\n }\n\n // 3. Focus keyword in meta description\n if (!keyword) {\n checks.push(check('focus-keyword-in-excerpt', 'warn', 'No focus keyword set'));\n } else if (excerpt.includes(keyword)) {\n checks.push(check('focus-keyword-in-excerpt', 'pass', 'Focus keyword appears in meta description'));\n } else {\n checks.push(check('focus-keyword-in-excerpt', 'fail', 'Focus keyword not found in meta description'));\n }\n\n // 4. Focus keyword in H2\n if (keyword) {\n const h2Regex = /<h2[^>]*>(.*?)<\\/h2>/gi;\n const h2s = contentHTML.match(h2Regex) || [];\n const keywordInH2 = h2s.some((h2) => h2.toLowerCase().includes(keyword));\n if (keywordInH2) {\n checks.push(check('focus-keyword-in-h2', 'pass', 'Focus keyword found in a subheading'));\n } else {\n checks.push(check('focus-keyword-in-h2', 'warn', 'Focus keyword not found in any H2 subheading'));\n }\n }\n\n // 5. Focus keyword in first paragraph\n if (keyword && contentText) {\n const first150Words = contentText.split(/\\s+/).slice(0, 150).join(' ');\n if (first150Words.includes(keyword)) {\n checks.push(check('focus-keyword-in-first-paragraph', 'pass', 'Focus keyword appears early in content'));\n } else {\n checks.push(check('focus-keyword-in-first-paragraph', 'warn', 'Focus keyword not found in the first paragraph'));\n }\n }\n\n // 6. Keyword density\n if (keyword && contentText) {\n const words = contentText.split(/\\s+/).length;\n const keywordCount = (contentText.match(new RegExp(keyword, 'g')) || []).length;\n const density = (keywordCount / words) * 100;\n if (density >= 0.5 && density <= 2.5) {\n checks.push(check('focus-keyword-density', 'pass', `Keyword density is ${density.toFixed(1)}% (ideal)`));\n } else if (density < 0.5) {\n checks.push(check('focus-keyword-density', 'warn', `Keyword density is ${density.toFixed(1)}% (too low, aim for 0.5-2.5%)`));\n } else {\n checks.push(check('focus-keyword-density', 'warn', `Keyword density is ${density.toFixed(1)}% (too high, aim for 0.5-2.5%)`));\n }\n }\n\n // 7. Title length\n const metaTitle = post.seo?.metaTitle || post.title;\n if (metaTitle.length >= 50 && metaTitle.length <= 60) {\n checks.push(check('title-length', 'pass', `Title is ${metaTitle.length} characters (ideal)`));\n } else if (metaTitle.length < 30) {\n checks.push(check('title-length', 'fail', `Title is ${metaTitle.length} characters (too short, aim for 50-60)`));\n } else if (metaTitle.length > 70) {\n checks.push(check('title-length', 'warn', `Title is ${metaTitle.length} characters (too long, aim for 50-60)`));\n } else {\n checks.push(check('title-length', 'warn', `Title is ${metaTitle.length} characters (aim for 50-60)`));\n }\n\n // 8. Meta description length\n const metaDesc = post.seo?.metaDescription || post.excerpt || '';\n if (metaDesc.length >= 150 && metaDesc.length <= 160) {\n checks.push(check('meta-description-length', 'pass', `Meta description is ${metaDesc.length} characters (ideal)`));\n } else if (metaDesc.length < 120) {\n checks.push(check('meta-description-length', 'warn', `Meta description is ${metaDesc.length} characters (too short, aim for 150-160)`));\n } else if (metaDesc.length > 170) {\n checks.push(check('meta-description-length', 'warn', `Meta description is ${metaDesc.length} characters (too long, may be truncated)`));\n } else {\n checks.push(check('meta-description-length', 'pass', `Meta description is ${metaDesc.length} characters`));\n }\n\n // 9. Slug length\n if (post.slug.length <= 75) {\n checks.push(check('slug-length', 'pass', `URL slug is ${post.slug.length} characters`));\n } else {\n checks.push(check('slug-length', 'warn', `URL slug is ${post.slug.length} characters (should be under 75)`));\n }\n\n // 10. Content length\n if (post.wordCount >= 300) {\n checks.push(check('content-length', 'pass', `Content is ${post.wordCount} words`));\n } else {\n checks.push(check('content-length', 'fail', `Content is only ${post.wordCount} words (aim for 300+)`));\n }\n\n // 11. Heading hierarchy\n const headingRegex = /<(h[2-6])[^>]*>/gi;\n const headings = [...contentHTML.matchAll(headingRegex)].map((m) => parseInt(m[1][1]));\n let hierarchyOk = true;\n for (let i = 1; i < headings.length; i++) {\n if (headings[i] > headings[i - 1] + 1) {\n hierarchyOk = false;\n break;\n }\n }\n if (headings.length === 0) {\n checks.push(check('heading-hierarchy', 'warn', 'No subheadings found — add H2s to structure content'));\n } else if (hierarchyOk) {\n checks.push(check('heading-hierarchy', 'pass', 'Heading hierarchy is correct'));\n } else {\n checks.push(check('heading-hierarchy', 'warn', 'Heading levels are skipped (e.g., H2 → H4)'));\n }\n\n // 12. Image alt text\n const imgRegex = /<img[^>]*>/gi;\n const images = contentHTML.match(imgRegex) || [];\n const missingAlt = images.filter((img) => !img.includes('alt=') || img.includes('alt=\"\"'));\n if (images.length === 0) {\n checks.push(check('image-alt-text', 'warn', 'No images found in content'));\n } else if (missingAlt.length === 0) {\n checks.push(check('image-alt-text', 'pass', 'All images have alt text'));\n } else {\n checks.push(check('image-alt-text', 'fail', `${missingAlt.length} image(s) missing alt text`));\n }\n\n // 13. Internal links\n const internalLinkRegex = /href=[\"']\\/[^\"']*[\"']/gi;\n const internalLinks = contentHTML.match(internalLinkRegex) || [];\n if (internalLinks.length > 0) {\n checks.push(check('internal-links', 'pass', `${internalLinks.length} internal link(s) found`));\n } else {\n checks.push(check('internal-links', 'warn', 'No internal links found — add links to related content'));\n }\n\n // 14. External links\n const externalLinkRegex = /href=[\"']https?:\\/\\/[^\"']*[\"']/gi;\n const externalLinks = contentHTML.match(externalLinkRegex) || [];\n if (externalLinks.length > 0) {\n checks.push(check('external-links', 'pass', `${externalLinks.length} external link(s) found`));\n } else {\n checks.push(check('external-links', 'warn', 'No external links found'));\n }\n\n // 15. Paragraph length\n const paragraphs = contentHTML.split(/<\\/p>/i).filter((p) => p.trim());\n const longParagraphs = paragraphs.filter((p) => {\n const text = p.replace(/<[^>]+>/g, '');\n return text.split(/\\s+/).length > 300;\n });\n if (longParagraphs.length === 0) {\n checks.push(check('paragraph-length', 'pass', 'All paragraphs are a reasonable length'));\n } else {\n checks.push(check('paragraph-length', 'warn', `${longParagraphs.length} paragraph(s) exceed 300 words`));\n }\n\n // 16. Cover image\n if (post.coverImage?.url) {\n checks.push(check('cover-image', 'pass', 'Post has a cover image'));\n } else {\n checks.push(check('cover-image', 'warn', 'No cover image set — social shares may look plain'));\n }\n\n // 17. Readability (simplified Flesch-like check)\n if (contentText) {\n const sentences = contentText.split(/[.!?]+/).filter((s) => s.trim().length > 0);\n const words = contentText.split(/\\s+/).length;\n const avgWordsPerSentence = sentences.length > 0 ? words / sentences.length : 0;\n if (avgWordsPerSentence <= 20) {\n checks.push(check('readability-score', 'pass', `Average sentence length: ${avgWordsPerSentence.toFixed(0)} words`));\n } else if (avgWordsPerSentence <= 25) {\n checks.push(check('readability-score', 'warn', `Average sentence length: ${avgWordsPerSentence.toFixed(0)} words (try to keep under 20)`));\n } else {\n checks.push(check('readability-score', 'fail', `Average sentence length: ${avgWordsPerSentence.toFixed(0)} words (too long, aim for under 20)`));\n }\n }\n\n // Calculate overall score\n const fails = checks.filter((c) => c.status === 'fail').length;\n const warns = checks.filter((c) => c.status === 'warn').length;\n\n let overall: SEOScore['overall'];\n if (fails >= 3) {\n overall = 'poor';\n } else if (fails >= 1 || warns >= 5) {\n overall = 'ok';\n } else {\n overall = 'good';\n }\n\n return { overall, checks };\n}\n"]}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var chunkU2ROR6AY_cjs = require('./chunk-U2ROR6AY.cjs');
|
|
4
|
+
var server = require('next/server');
|
|
5
|
+
|
|
6
|
+
function jsonSuccess(data, meta, status = 200) {
|
|
7
|
+
return server.NextResponse.json({ success: true, data, meta }, { status });
|
|
8
|
+
}
|
|
9
|
+
function jsonError(code, message, status = 400) {
|
|
10
|
+
return server.NextResponse.json(
|
|
11
|
+
{ success: false, error: { code, message } },
|
|
12
|
+
{ status }
|
|
13
|
+
);
|
|
14
|
+
}
|
|
15
|
+
function requireAuth(request) {
|
|
16
|
+
const env = chunkU2ROR6AY_cjs.getEnvConfig();
|
|
17
|
+
const authHeader = request.headers.get("authorization");
|
|
18
|
+
if (!authHeader) {
|
|
19
|
+
return jsonError("UNAUTHORIZED", "Authorization header is required", 401);
|
|
20
|
+
}
|
|
21
|
+
const token = authHeader.replace(/^Bearer\s+/i, "");
|
|
22
|
+
if (token !== env.NEXTBLOGKIT_API_KEY) {
|
|
23
|
+
return jsonError("FORBIDDEN", "Invalid API key", 403);
|
|
24
|
+
}
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
function getSearchParams(request) {
|
|
28
|
+
const url = new URL(request.url);
|
|
29
|
+
return Object.fromEntries(url.searchParams.entries());
|
|
30
|
+
}
|
|
31
|
+
function parseIntParam(value, defaultValue) {
|
|
32
|
+
if (!value) return defaultValue;
|
|
33
|
+
const parsed = parseInt(value, 10);
|
|
34
|
+
return isNaN(parsed) ? defaultValue : parsed;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
exports.getSearchParams = getSearchParams;
|
|
38
|
+
exports.jsonError = jsonError;
|
|
39
|
+
exports.jsonSuccess = jsonSuccess;
|
|
40
|
+
exports.parseIntParam = parseIntParam;
|
|
41
|
+
exports.requireAuth = requireAuth;
|
|
42
|
+
//# sourceMappingURL=chunk-KDZER3PU.cjs.map
|
|
43
|
+
//# sourceMappingURL=chunk-KDZER3PU.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/api/middleware.ts"],"names":["NextResponse","getEnvConfig"],"mappings":";;;;;AAIO,SAAS,WAAA,CAAY,IAAA,EAAe,IAAA,EAAgC,MAAA,GAAS,GAAA,EAAK;AACvF,EAAA,OAAOA,mBAAA,CAAa,IAAA,CAAK,EAAE,OAAA,EAAS,IAAA,EAAM,MAAM,IAAA,EAAK,EAAG,EAAE,MAAA,EAAQ,CAAA;AACpE;AAEO,SAAS,SAAA,CAAU,IAAA,EAAc,OAAA,EAAiB,MAAA,GAAS,GAAA,EAAqC;AACrG,EAAA,OAAOA,mBAAA,CAAa,IAAA;AAAA,IAClB,EAAE,OAAA,EAAS,KAAA,EAAO,OAAO,EAAE,IAAA,EAAM,SAAQ,EAAE;AAAA,IAC3C,EAAE,MAAA;AAAO,GACX;AACF;AAEO,SAAS,YAAY,OAAA,EAAyD;AACnF,EAAA,MAAM,MAAMC,8BAAA,EAAa;AACzB,EAAA,MAAM,UAAA,GAAa,OAAA,CAAQ,OAAA,CAAQ,GAAA,CAAI,eAAe,CAAA;AAEtD,EAAA,IAAI,CAAC,UAAA,EAAY;AACf,IAAA,OAAO,SAAA,CAAU,cAAA,EAAgB,kCAAA,EAAoC,GAAG,CAAA;AAAA,EAC1E;AAEA,EAAA,MAAM,KAAA,GAAQ,UAAA,CAAW,OAAA,CAAQ,aAAA,EAAe,EAAE,CAAA;AAClD,EAAA,IAAI,KAAA,KAAU,IAAI,mBAAA,EAAqB;AACrC,IAAA,OAAO,SAAA,CAAU,WAAA,EAAa,iBAAA,EAAmB,GAAG,CAAA;AAAA,EACtD;AAEA,EAAA,OAAO,IAAA;AACT;AAEO,SAAS,gBAAgB,OAAA,EAAkB;AAChD,EAAA,MAAM,GAAA,GAAM,IAAI,GAAA,CAAI,OAAA,CAAQ,GAAG,CAAA;AAC/B,EAAA,OAAO,MAAA,CAAO,WAAA,CAAY,GAAA,CAAI,YAAA,CAAa,SAAS,CAAA;AACtD;AAEO,SAAS,aAAA,CAAc,OAA2B,YAAA,EAA8B;AACrF,EAAA,IAAI,CAAC,OAAO,OAAO,YAAA;AACnB,EAAA,MAAM,MAAA,GAAS,QAAA,CAAS,KAAA,EAAO,EAAE,CAAA;AACjC,EAAA,OAAO,KAAA,CAAM,MAAM,CAAA,GAAI,YAAA,GAAe,MAAA;AACxC","file":"chunk-KDZER3PU.cjs","sourcesContent":["import { NextResponse } from 'next/server';\nimport { getEnvConfig } from '../lib/config';\nimport type { ApiErrorResponse } from '../lib/types';\n\nexport function jsonSuccess(data: unknown, meta?: Record<string, unknown>, status = 200) {\n return NextResponse.json({ success: true, data, meta }, { status });\n}\n\nexport function jsonError(code: string, message: string, status = 400): NextResponse<ApiErrorResponse> {\n return NextResponse.json(\n { success: false, error: { code, message } },\n { status }\n ) as NextResponse<ApiErrorResponse>;\n}\n\nexport function requireAuth(request: Request): NextResponse<ApiErrorResponse> | null {\n const env = getEnvConfig();\n const authHeader = request.headers.get('authorization');\n\n if (!authHeader) {\n return jsonError('UNAUTHORIZED', 'Authorization header is required', 401);\n }\n\n const token = authHeader.replace(/^Bearer\\s+/i, '');\n if (token !== env.NEXTBLOGKIT_API_KEY) {\n return jsonError('FORBIDDEN', 'Invalid API key', 403);\n }\n\n return null;\n}\n\nexport function getSearchParams(request: Request) {\n const url = new URL(request.url);\n return Object.fromEntries(url.searchParams.entries());\n}\n\nexport function parseIntParam(value: string | undefined, defaultValue: number): number {\n if (!value) return defaultValue;\n const parsed = parseInt(value, 10);\n return isNaN(parsed) ? defaultValue : parsed;\n}\n"]}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var zod = require('zod');
|
|
4
|
+
|
|
5
|
+
// src/lib/types.ts
|
|
6
|
+
zod.z.enum([
|
|
7
|
+
"paragraph",
|
|
8
|
+
"heading",
|
|
9
|
+
"image",
|
|
10
|
+
"codeBlock",
|
|
11
|
+
"blockquote",
|
|
12
|
+
"bulletList",
|
|
13
|
+
"orderedList",
|
|
14
|
+
"taskList",
|
|
15
|
+
"table",
|
|
16
|
+
"embed",
|
|
17
|
+
"horizontalRule",
|
|
18
|
+
"callout",
|
|
19
|
+
"tableOfContents",
|
|
20
|
+
"faq",
|
|
21
|
+
"html"
|
|
22
|
+
]);
|
|
23
|
+
zod.z.object({
|
|
24
|
+
filename: zod.z.string(),
|
|
25
|
+
originalName: zod.z.string(),
|
|
26
|
+
mimeType: zod.z.string(),
|
|
27
|
+
size: zod.z.number(),
|
|
28
|
+
width: zod.z.number().optional(),
|
|
29
|
+
height: zod.z.number().optional(),
|
|
30
|
+
r2Key: zod.z.string(),
|
|
31
|
+
url: zod.z.string().url(),
|
|
32
|
+
alt: zod.z.string().optional(),
|
|
33
|
+
caption: zod.z.string().optional(),
|
|
34
|
+
createdAt: zod.z.date()
|
|
35
|
+
});
|
|
36
|
+
var AuthorSchema = zod.z.object({
|
|
37
|
+
name: zod.z.string().min(1),
|
|
38
|
+
avatar: zod.z.string().url().optional(),
|
|
39
|
+
bio: zod.z.string().optional(),
|
|
40
|
+
url: zod.z.string().url().optional()
|
|
41
|
+
});
|
|
42
|
+
var PostSEOSchema = zod.z.object({
|
|
43
|
+
metaTitle: zod.z.string().optional(),
|
|
44
|
+
metaDescription: zod.z.string().optional(),
|
|
45
|
+
canonicalUrl: zod.z.string().url().optional(),
|
|
46
|
+
ogImage: zod.z.string().url().optional(),
|
|
47
|
+
ogType: zod.z.string().default("article"),
|
|
48
|
+
noIndex: zod.z.boolean().default(false),
|
|
49
|
+
structuredData: zod.z.record(zod.z.unknown()).optional(),
|
|
50
|
+
focusKeyword: zod.z.string().optional()
|
|
51
|
+
});
|
|
52
|
+
var PostStatusSchema = zod.z.enum(["draft", "published", "scheduled", "archived"]);
|
|
53
|
+
var CreatePostSchema = zod.z.object({
|
|
54
|
+
title: zod.z.string().min(1, "Title is required"),
|
|
55
|
+
slug: zod.z.string().optional(),
|
|
56
|
+
excerpt: zod.z.string().optional(),
|
|
57
|
+
content: zod.z.any(),
|
|
58
|
+
// BlockContent[] — validated structurally
|
|
59
|
+
contentHTML: zod.z.string().optional(),
|
|
60
|
+
contentText: zod.z.string().optional(),
|
|
61
|
+
coverImage: zod.z.object({
|
|
62
|
+
_id: zod.z.string(),
|
|
63
|
+
url: zod.z.string().url(),
|
|
64
|
+
alt: zod.z.string().optional(),
|
|
65
|
+
caption: zod.z.string().optional(),
|
|
66
|
+
width: zod.z.number().optional(),
|
|
67
|
+
height: zod.z.number().optional()
|
|
68
|
+
}).optional(),
|
|
69
|
+
categories: zod.z.array(zod.z.string()).default([]),
|
|
70
|
+
tags: zod.z.array(zod.z.string()).default([]),
|
|
71
|
+
author: AuthorSchema.optional(),
|
|
72
|
+
seo: PostSEOSchema.optional(),
|
|
73
|
+
status: PostStatusSchema.default("draft"),
|
|
74
|
+
publishedAt: zod.z.coerce.date().optional(),
|
|
75
|
+
scheduledAt: zod.z.coerce.date().optional()
|
|
76
|
+
});
|
|
77
|
+
var UpdatePostSchema = CreatePostSchema.partial();
|
|
78
|
+
var CreateCategorySchema = zod.z.object({
|
|
79
|
+
name: zod.z.string().min(1, "Category name is required"),
|
|
80
|
+
slug: zod.z.string().optional(),
|
|
81
|
+
description: zod.z.string().optional(),
|
|
82
|
+
seo: zod.z.object({
|
|
83
|
+
metaTitle: zod.z.string().optional(),
|
|
84
|
+
metaDescription: zod.z.string().optional()
|
|
85
|
+
}).optional(),
|
|
86
|
+
order: zod.z.number().default(0),
|
|
87
|
+
parentId: zod.z.string().optional()
|
|
88
|
+
});
|
|
89
|
+
var UpdateCategorySchema = CreateCategorySchema.partial();
|
|
90
|
+
var BlogSettingsSchema = zod.z.object({
|
|
91
|
+
postsPerPage: zod.z.number().min(1).max(100).default(10),
|
|
92
|
+
defaultAuthor: AuthorSchema.optional(),
|
|
93
|
+
defaultOgImage: zod.z.string().url().optional(),
|
|
94
|
+
commentSystem: zod.z.enum(["none", "giscus", "disqus"]).default("none"),
|
|
95
|
+
commentConfig: zod.z.record(zod.z.unknown()).optional(),
|
|
96
|
+
customCSS: zod.z.string().optional(),
|
|
97
|
+
analytics: zod.z.object({
|
|
98
|
+
gaId: zod.z.string().optional(),
|
|
99
|
+
plausibleDomain: zod.z.string().optional()
|
|
100
|
+
}).optional()
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
exports.BlogSettingsSchema = BlogSettingsSchema;
|
|
104
|
+
exports.CreateCategorySchema = CreateCategorySchema;
|
|
105
|
+
exports.CreatePostSchema = CreatePostSchema;
|
|
106
|
+
exports.UpdateCategorySchema = UpdateCategorySchema;
|
|
107
|
+
exports.UpdatePostSchema = UpdatePostSchema;
|
|
108
|
+
//# sourceMappingURL=chunk-N5MKAD7J.cjs.map
|
|
109
|
+
//# sourceMappingURL=chunk-N5MKAD7J.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/lib/types.ts"],"names":["z"],"mappings":"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","file":"chunk-N5MKAD7J.cjs","sourcesContent":["import { z } from 'zod';\nimport type { ObjectId } from 'mongodb';\n\n// ============================================================\n// Block Editor Types\n// ============================================================\n\nexport const BlockTypeSchema = z.enum([\n 'paragraph',\n 'heading',\n 'image',\n 'codeBlock',\n 'blockquote',\n 'bulletList',\n 'orderedList',\n 'taskList',\n 'table',\n 'embed',\n 'horizontalRule',\n 'callout',\n 'tableOfContents',\n 'faq',\n 'html',\n]);\n\nexport type BlockType = z.infer<typeof BlockTypeSchema>;\n\nexport interface BlockContent {\n type: string;\n attrs?: Record<string, unknown>;\n content?: BlockContent[];\n text?: string;\n marks?: { type: string; attrs?: Record<string, unknown> }[];\n}\n\n// ============================================================\n// Media\n// ============================================================\n\nexport interface MediaReference {\n _id: string;\n url: string;\n alt?: string;\n caption?: string;\n width?: number;\n height?: number;\n}\n\nexport const MediaSchema = z.object({\n filename: z.string(),\n originalName: z.string(),\n mimeType: z.string(),\n size: z.number(),\n width: z.number().optional(),\n height: z.number().optional(),\n r2Key: z.string(),\n url: z.string().url(),\n alt: z.string().optional(),\n caption: z.string().optional(),\n createdAt: z.date(),\n});\n\nexport interface Media {\n _id: ObjectId;\n filename: string;\n originalName: string;\n mimeType: string;\n size: number;\n width?: number;\n height?: number;\n r2Key: string;\n url: string;\n alt?: string;\n caption?: string;\n createdAt: Date;\n}\n\n// ============================================================\n// Author\n// ============================================================\n\nexport const AuthorSchema = z.object({\n name: z.string().min(1),\n avatar: z.string().url().optional(),\n bio: z.string().optional(),\n url: z.string().url().optional(),\n});\n\nexport interface Author {\n name: string;\n avatar?: string;\n bio?: string;\n url?: string;\n}\n\n// ============================================================\n// SEO\n// ============================================================\n\nexport const PostSEOSchema = z.object({\n metaTitle: z.string().optional(),\n metaDescription: z.string().optional(),\n canonicalUrl: z.string().url().optional(),\n ogImage: z.string().url().optional(),\n ogType: z.string().default('article'),\n noIndex: z.boolean().default(false),\n structuredData: z.record(z.unknown()).optional(),\n focusKeyword: z.string().optional(),\n});\n\nexport interface PostSEO {\n metaTitle?: string;\n metaDescription?: string;\n canonicalUrl?: string;\n ogImage?: string;\n ogType: string;\n noIndex: boolean;\n structuredData?: Record<string, unknown>;\n focusKeyword?: string;\n}\n\n// ============================================================\n// Revision\n// ============================================================\n\nexport interface Revision {\n version: number;\n title: string;\n content: BlockContent[];\n contentHTML: string;\n savedAt: Date;\n}\n\n// ============================================================\n// Post\n// ============================================================\n\nexport const PostStatusSchema = z.enum(['draft', 'published', 'scheduled', 'archived']);\nexport type PostStatus = z.infer<typeof PostStatusSchema>;\n\nexport const CreatePostSchema = z.object({\n title: z.string().min(1, 'Title is required'),\n slug: z.string().optional(),\n excerpt: z.string().optional(),\n content: z.any(), // BlockContent[] — validated structurally\n contentHTML: z.string().optional(),\n contentText: z.string().optional(),\n coverImage: z\n .object({\n _id: z.string(),\n url: z.string().url(),\n alt: z.string().optional(),\n caption: z.string().optional(),\n width: z.number().optional(),\n height: z.number().optional(),\n })\n .optional(),\n categories: z.array(z.string()).default([]),\n tags: z.array(z.string()).default([]),\n author: AuthorSchema.optional(),\n seo: PostSEOSchema.optional(),\n status: PostStatusSchema.default('draft'),\n publishedAt: z.coerce.date().optional(),\n scheduledAt: z.coerce.date().optional(),\n});\n\nexport const UpdatePostSchema = CreatePostSchema.partial();\n\nexport type CreatePostInput = z.infer<typeof CreatePostSchema>;\nexport type UpdatePostInput = z.infer<typeof UpdatePostSchema>;\n\nexport interface BlogPost {\n _id: ObjectId;\n title: string;\n slug: string;\n excerpt: string;\n content: BlockContent[];\n contentHTML: string;\n contentText: string;\n coverImage?: MediaReference;\n categories: string[];\n tags: string[];\n author: Author;\n seo: PostSEO;\n status: PostStatus;\n publishedAt?: Date;\n scheduledAt?: Date;\n readingTime: number;\n wordCount: number;\n version: number;\n revisions: Revision[];\n createdAt: Date;\n updatedAt: Date;\n}\n\n// ============================================================\n// Category\n// ============================================================\n\nexport const CreateCategorySchema = z.object({\n name: z.string().min(1, 'Category name is required'),\n slug: z.string().optional(),\n description: z.string().optional(),\n seo: z\n .object({\n metaTitle: z.string().optional(),\n metaDescription: z.string().optional(),\n })\n .optional(),\n order: z.number().default(0),\n parentId: z.string().optional(),\n});\n\nexport const UpdateCategorySchema = CreateCategorySchema.partial();\n\nexport type CreateCategoryInput = z.infer<typeof CreateCategorySchema>;\nexport type UpdateCategoryInput = z.infer<typeof UpdateCategorySchema>;\n\nexport interface Category {\n _id: ObjectId;\n name: string;\n slug: string;\n description?: string;\n seo?: {\n metaTitle?: string;\n metaDescription?: string;\n };\n order: number;\n parentId?: ObjectId;\n postCount: number;\n}\n\n// ============================================================\n// Settings\n// ============================================================\n\nexport const BlogSettingsSchema = z.object({\n postsPerPage: z.number().min(1).max(100).default(10),\n defaultAuthor: AuthorSchema.optional(),\n defaultOgImage: z.string().url().optional(),\n commentSystem: z.enum(['none', 'giscus', 'disqus']).default('none'),\n commentConfig: z.record(z.unknown()).optional(),\n customCSS: z.string().optional(),\n analytics: z\n .object({\n gaId: z.string().optional(),\n plausibleDomain: z.string().optional(),\n })\n .optional(),\n});\n\nexport interface BlogSettings {\n _id: string;\n postsPerPage: number;\n defaultAuthor?: Author;\n defaultOgImage?: string;\n commentSystem: 'none' | 'giscus' | 'disqus';\n commentConfig?: Record<string, unknown>;\n customCSS?: string;\n analytics?: {\n gaId?: string;\n plausibleDomain?: string;\n };\n}\n\n// ============================================================\n// API Response Types\n// ============================================================\n\nexport interface ApiSuccessResponse<T = unknown> {\n success: true;\n data: T;\n meta?: {\n page: number;\n limit: number;\n total: number;\n totalPages: number;\n };\n}\n\nexport interface ApiErrorResponse {\n success: false;\n error: {\n code: string;\n message: string;\n };\n}\n\nexport type ApiResponse<T = unknown> = ApiSuccessResponse<T> | ApiErrorResponse;\n\n// ============================================================\n// SEO Score Types\n// ============================================================\n\nexport type SEOCheckStatus = 'pass' | 'warn' | 'fail';\nexport type SEOOverallScore = 'good' | 'ok' | 'poor';\n\nexport interface SEOCheck {\n id: string;\n status: SEOCheckStatus;\n message: string;\n}\n\nexport interface SEOScore {\n overall: SEOOverallScore;\n checks: SEOCheck[];\n}\n\n// ============================================================\n// Config Types\n// ============================================================\n\nexport interface NextBlogKitConfig {\n basePath: string;\n adminPath: string;\n apiPath: string;\n postsPerPage: number;\n excerptLength: number;\n codeHighlighter: 'shiki' | 'prism';\n editor: {\n blocks: BlockType[];\n maxImageSize: number;\n imageFormats: string[];\n autosaveInterval: number;\n };\n seo: {\n titleTemplate: string;\n defaultOgImage?: string;\n generateRSS: boolean;\n generateSitemap: boolean;\n structuredData: boolean;\n minContentLength: number;\n };\n auth: {\n strategy: 'api-key' | 'custom' | 'credentials';\n verify?: (request: Request) => Promise<boolean>;\n admins?: string[];\n };\n features: {\n search: boolean;\n relatedPosts: boolean;\n readingProgress: boolean;\n tableOfContents: boolean;\n shareButtons: boolean;\n darkMode: boolean;\n scheduling: boolean;\n revisionHistory: boolean;\n imageOptimization: boolean;\n };\n theme: {\n variables?: Record<string, string>;\n darkMode?: boolean;\n components?: Record<string, React.ComponentType<unknown>>;\n };\n hooks: {\n beforePublish?: (post: BlogPost) => Promise<void>;\n afterPublish?: (post: BlogPost) => Promise<void>;\n beforeDelete?: (post: BlogPost) => Promise<void>;\n onMediaUpload?: (media: Media) => Promise<void>;\n };\n}\n\n// ============================================================\n// Query Types\n// ============================================================\n\nexport interface PostListQuery {\n page?: number;\n limit?: number;\n category?: string;\n tag?: string;\n status?: PostStatus;\n search?: string;\n sortBy?: 'publishedAt' | 'createdAt' | 'title';\n sortOrder?: 'asc' | 'desc';\n}\n\nexport interface MediaListQuery {\n page?: number;\n limit?: number;\n mimeType?: string;\n}\n"]}
|
|
@@ -0,0 +1,337 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var chunkU2ROR6AY_cjs = require('./chunk-U2ROR6AY.cjs');
|
|
4
|
+
|
|
5
|
+
// src/lib/search.ts
|
|
6
|
+
async function searchPosts(collection, query, limit = 10) {
|
|
7
|
+
if (!query.trim()) return [];
|
|
8
|
+
const results = await collection.find(
|
|
9
|
+
{
|
|
10
|
+
$text: { $search: query },
|
|
11
|
+
status: "published"
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
projection: {
|
|
15
|
+
slug: 1,
|
|
16
|
+
title: 1,
|
|
17
|
+
excerpt: 1,
|
|
18
|
+
score: { $meta: "textScore" }
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
).sort({ score: { $meta: "textScore" } }).limit(limit).toArray();
|
|
22
|
+
return results.map((doc) => ({
|
|
23
|
+
slug: doc.slug,
|
|
24
|
+
title: doc.title,
|
|
25
|
+
excerpt: doc.excerpt,
|
|
26
|
+
score: doc.score
|
|
27
|
+
}));
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// src/lib/seo.ts
|
|
31
|
+
function generateMetaTags(post) {
|
|
32
|
+
const env = chunkU2ROR6AY_cjs.getEnvConfig();
|
|
33
|
+
const postUrl = `${env.NEXTBLOGKIT_SITE_URL}/blog/${post.slug}`;
|
|
34
|
+
const title = post.seo?.metaTitle || post.title;
|
|
35
|
+
const description = post.seo?.metaDescription || post.excerpt;
|
|
36
|
+
const canonical = post.seo?.canonicalUrl || postUrl;
|
|
37
|
+
const ogImage = post.seo?.ogImage || post.coverImage?.url;
|
|
38
|
+
return {
|
|
39
|
+
title: `${title} | ${env.NEXTBLOGKIT_SITE_NAME}`,
|
|
40
|
+
description,
|
|
41
|
+
canonical,
|
|
42
|
+
openGraph: {
|
|
43
|
+
title,
|
|
44
|
+
description,
|
|
45
|
+
url: postUrl,
|
|
46
|
+
siteName: env.NEXTBLOGKIT_SITE_NAME,
|
|
47
|
+
type: post.seo?.ogType || "article",
|
|
48
|
+
images: ogImage ? [
|
|
49
|
+
{
|
|
50
|
+
url: ogImage,
|
|
51
|
+
width: 1200,
|
|
52
|
+
height: 630,
|
|
53
|
+
alt: title
|
|
54
|
+
}
|
|
55
|
+
] : [],
|
|
56
|
+
article: {
|
|
57
|
+
publishedTime: post.publishedAt?.toISOString() || "",
|
|
58
|
+
modifiedTime: post.updatedAt.toISOString(),
|
|
59
|
+
section: post.categories[0],
|
|
60
|
+
tags: post.tags
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
twitter: {
|
|
64
|
+
card: "summary_large_image",
|
|
65
|
+
title,
|
|
66
|
+
description,
|
|
67
|
+
images: ogImage ? [ogImage] : []
|
|
68
|
+
},
|
|
69
|
+
robots: post.seo?.noIndex ? "noindex, nofollow" : void 0
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
function generateStructuredData(post) {
|
|
73
|
+
const env = chunkU2ROR6AY_cjs.getEnvConfig();
|
|
74
|
+
const postUrl = `${env.NEXTBLOGKIT_SITE_URL}/blog/${post.slug}`;
|
|
75
|
+
return {
|
|
76
|
+
"@context": "https://schema.org",
|
|
77
|
+
"@type": "BlogPosting",
|
|
78
|
+
headline: post.title,
|
|
79
|
+
description: post.excerpt,
|
|
80
|
+
image: post.coverImage?.url || post.seo?.ogImage,
|
|
81
|
+
datePublished: post.publishedAt?.toISOString(),
|
|
82
|
+
dateModified: post.updatedAt.toISOString(),
|
|
83
|
+
author: {
|
|
84
|
+
"@type": "Person",
|
|
85
|
+
name: post.author.name,
|
|
86
|
+
url: post.author.url
|
|
87
|
+
},
|
|
88
|
+
publisher: {
|
|
89
|
+
"@type": "Organization",
|
|
90
|
+
name: env.NEXTBLOGKIT_SITE_NAME
|
|
91
|
+
},
|
|
92
|
+
mainEntityOfPage: {
|
|
93
|
+
"@type": "WebPage",
|
|
94
|
+
"@id": postUrl
|
|
95
|
+
},
|
|
96
|
+
wordCount: post.wordCount,
|
|
97
|
+
articleSection: post.categories[0]
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
function generateFAQStructuredData(faqItems) {
|
|
101
|
+
if (!faqItems.length) return null;
|
|
102
|
+
return {
|
|
103
|
+
"@context": "https://schema.org",
|
|
104
|
+
"@type": "FAQPage",
|
|
105
|
+
mainEntity: faqItems.map((item) => ({
|
|
106
|
+
"@type": "Question",
|
|
107
|
+
name: item.question,
|
|
108
|
+
acceptedAnswer: {
|
|
109
|
+
"@type": "Answer",
|
|
110
|
+
text: item.answer
|
|
111
|
+
}
|
|
112
|
+
}))
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
function generateBreadcrumbs(post, categoryName) {
|
|
116
|
+
const env = chunkU2ROR6AY_cjs.getEnvConfig();
|
|
117
|
+
const items = [
|
|
118
|
+
{
|
|
119
|
+
"@type": "ListItem",
|
|
120
|
+
position: 1,
|
|
121
|
+
name: "Home",
|
|
122
|
+
item: env.NEXTBLOGKIT_SITE_URL
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
"@type": "ListItem",
|
|
126
|
+
position: 2,
|
|
127
|
+
name: "Blog",
|
|
128
|
+
item: `${env.NEXTBLOGKIT_SITE_URL}/blog`
|
|
129
|
+
}
|
|
130
|
+
];
|
|
131
|
+
if (categoryName && post.categories[0]) {
|
|
132
|
+
items.push({
|
|
133
|
+
"@type": "ListItem",
|
|
134
|
+
position: 3,
|
|
135
|
+
name: categoryName,
|
|
136
|
+
item: `${env.NEXTBLOGKIT_SITE_URL}/blog/category/${post.categories[0]}`
|
|
137
|
+
});
|
|
138
|
+
items.push({
|
|
139
|
+
"@type": "ListItem",
|
|
140
|
+
position: 4,
|
|
141
|
+
name: post.title
|
|
142
|
+
});
|
|
143
|
+
} else {
|
|
144
|
+
items.push({
|
|
145
|
+
"@type": "ListItem",
|
|
146
|
+
position: 3,
|
|
147
|
+
name: post.title
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
return {
|
|
151
|
+
"@context": "https://schema.org",
|
|
152
|
+
"@type": "BreadcrumbList",
|
|
153
|
+
itemListElement: items
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// src/lib/seo-scorer.ts
|
|
158
|
+
function check(id, status, message) {
|
|
159
|
+
return { id, status, message };
|
|
160
|
+
}
|
|
161
|
+
function calculateSEOScore(post) {
|
|
162
|
+
const checks = [];
|
|
163
|
+
const keyword = post.seo?.focusKeyword?.toLowerCase() || "";
|
|
164
|
+
const title = post.title.toLowerCase();
|
|
165
|
+
const slug = post.slug.toLowerCase();
|
|
166
|
+
const excerpt = (post.seo?.metaDescription || post.excerpt || "").toLowerCase();
|
|
167
|
+
const contentText = post.contentText?.toLowerCase() || "";
|
|
168
|
+
const contentHTML = post.contentHTML || "";
|
|
169
|
+
if (!keyword) {
|
|
170
|
+
checks.push(check("focus-keyword-in-title", "warn", "No focus keyword set"));
|
|
171
|
+
} else if (title.includes(keyword)) {
|
|
172
|
+
checks.push(check("focus-keyword-in-title", "pass", "Focus keyword appears in title"));
|
|
173
|
+
} else {
|
|
174
|
+
checks.push(check("focus-keyword-in-title", "fail", "Focus keyword not found in title"));
|
|
175
|
+
}
|
|
176
|
+
if (!keyword) {
|
|
177
|
+
checks.push(check("focus-keyword-in-slug", "warn", "No focus keyword set"));
|
|
178
|
+
} else if (slug.includes(keyword.replace(/\s+/g, "-"))) {
|
|
179
|
+
checks.push(check("focus-keyword-in-slug", "pass", "Focus keyword appears in URL slug"));
|
|
180
|
+
} else {
|
|
181
|
+
checks.push(check("focus-keyword-in-slug", "fail", "Focus keyword not found in URL slug"));
|
|
182
|
+
}
|
|
183
|
+
if (!keyword) {
|
|
184
|
+
checks.push(check("focus-keyword-in-excerpt", "warn", "No focus keyword set"));
|
|
185
|
+
} else if (excerpt.includes(keyword)) {
|
|
186
|
+
checks.push(check("focus-keyword-in-excerpt", "pass", "Focus keyword appears in meta description"));
|
|
187
|
+
} else {
|
|
188
|
+
checks.push(check("focus-keyword-in-excerpt", "fail", "Focus keyword not found in meta description"));
|
|
189
|
+
}
|
|
190
|
+
if (keyword) {
|
|
191
|
+
const h2Regex = /<h2[^>]*>(.*?)<\/h2>/gi;
|
|
192
|
+
const h2s = contentHTML.match(h2Regex) || [];
|
|
193
|
+
const keywordInH2 = h2s.some((h2) => h2.toLowerCase().includes(keyword));
|
|
194
|
+
if (keywordInH2) {
|
|
195
|
+
checks.push(check("focus-keyword-in-h2", "pass", "Focus keyword found in a subheading"));
|
|
196
|
+
} else {
|
|
197
|
+
checks.push(check("focus-keyword-in-h2", "warn", "Focus keyword not found in any H2 subheading"));
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
if (keyword && contentText) {
|
|
201
|
+
const first150Words = contentText.split(/\s+/).slice(0, 150).join(" ");
|
|
202
|
+
if (first150Words.includes(keyword)) {
|
|
203
|
+
checks.push(check("focus-keyword-in-first-paragraph", "pass", "Focus keyword appears early in content"));
|
|
204
|
+
} else {
|
|
205
|
+
checks.push(check("focus-keyword-in-first-paragraph", "warn", "Focus keyword not found in the first paragraph"));
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
if (keyword && contentText) {
|
|
209
|
+
const words = contentText.split(/\s+/).length;
|
|
210
|
+
const keywordCount = (contentText.match(new RegExp(keyword, "g")) || []).length;
|
|
211
|
+
const density = keywordCount / words * 100;
|
|
212
|
+
if (density >= 0.5 && density <= 2.5) {
|
|
213
|
+
checks.push(check("focus-keyword-density", "pass", `Keyword density is ${density.toFixed(1)}% (ideal)`));
|
|
214
|
+
} else if (density < 0.5) {
|
|
215
|
+
checks.push(check("focus-keyword-density", "warn", `Keyword density is ${density.toFixed(1)}% (too low, aim for 0.5-2.5%)`));
|
|
216
|
+
} else {
|
|
217
|
+
checks.push(check("focus-keyword-density", "warn", `Keyword density is ${density.toFixed(1)}% (too high, aim for 0.5-2.5%)`));
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
const metaTitle = post.seo?.metaTitle || post.title;
|
|
221
|
+
if (metaTitle.length >= 50 && metaTitle.length <= 60) {
|
|
222
|
+
checks.push(check("title-length", "pass", `Title is ${metaTitle.length} characters (ideal)`));
|
|
223
|
+
} else if (metaTitle.length < 30) {
|
|
224
|
+
checks.push(check("title-length", "fail", `Title is ${metaTitle.length} characters (too short, aim for 50-60)`));
|
|
225
|
+
} else if (metaTitle.length > 70) {
|
|
226
|
+
checks.push(check("title-length", "warn", `Title is ${metaTitle.length} characters (too long, aim for 50-60)`));
|
|
227
|
+
} else {
|
|
228
|
+
checks.push(check("title-length", "warn", `Title is ${metaTitle.length} characters (aim for 50-60)`));
|
|
229
|
+
}
|
|
230
|
+
const metaDesc = post.seo?.metaDescription || post.excerpt || "";
|
|
231
|
+
if (metaDesc.length >= 150 && metaDesc.length <= 160) {
|
|
232
|
+
checks.push(check("meta-description-length", "pass", `Meta description is ${metaDesc.length} characters (ideal)`));
|
|
233
|
+
} else if (metaDesc.length < 120) {
|
|
234
|
+
checks.push(check("meta-description-length", "warn", `Meta description is ${metaDesc.length} characters (too short, aim for 150-160)`));
|
|
235
|
+
} else if (metaDesc.length > 170) {
|
|
236
|
+
checks.push(check("meta-description-length", "warn", `Meta description is ${metaDesc.length} characters (too long, may be truncated)`));
|
|
237
|
+
} else {
|
|
238
|
+
checks.push(check("meta-description-length", "pass", `Meta description is ${metaDesc.length} characters`));
|
|
239
|
+
}
|
|
240
|
+
if (post.slug.length <= 75) {
|
|
241
|
+
checks.push(check("slug-length", "pass", `URL slug is ${post.slug.length} characters`));
|
|
242
|
+
} else {
|
|
243
|
+
checks.push(check("slug-length", "warn", `URL slug is ${post.slug.length} characters (should be under 75)`));
|
|
244
|
+
}
|
|
245
|
+
if (post.wordCount >= 300) {
|
|
246
|
+
checks.push(check("content-length", "pass", `Content is ${post.wordCount} words`));
|
|
247
|
+
} else {
|
|
248
|
+
checks.push(check("content-length", "fail", `Content is only ${post.wordCount} words (aim for 300+)`));
|
|
249
|
+
}
|
|
250
|
+
const headingRegex = /<(h[2-6])[^>]*>/gi;
|
|
251
|
+
const headings = [...contentHTML.matchAll(headingRegex)].map((m) => parseInt(m[1][1]));
|
|
252
|
+
let hierarchyOk = true;
|
|
253
|
+
for (let i = 1; i < headings.length; i++) {
|
|
254
|
+
if (headings[i] > headings[i - 1] + 1) {
|
|
255
|
+
hierarchyOk = false;
|
|
256
|
+
break;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
if (headings.length === 0) {
|
|
260
|
+
checks.push(check("heading-hierarchy", "warn", "No subheadings found \u2014 add H2s to structure content"));
|
|
261
|
+
} else if (hierarchyOk) {
|
|
262
|
+
checks.push(check("heading-hierarchy", "pass", "Heading hierarchy is correct"));
|
|
263
|
+
} else {
|
|
264
|
+
checks.push(check("heading-hierarchy", "warn", "Heading levels are skipped (e.g., H2 \u2192 H4)"));
|
|
265
|
+
}
|
|
266
|
+
const imgRegex = /<img[^>]*>/gi;
|
|
267
|
+
const images = contentHTML.match(imgRegex) || [];
|
|
268
|
+
const missingAlt = images.filter((img) => !img.includes("alt=") || img.includes('alt=""'));
|
|
269
|
+
if (images.length === 0) {
|
|
270
|
+
checks.push(check("image-alt-text", "warn", "No images found in content"));
|
|
271
|
+
} else if (missingAlt.length === 0) {
|
|
272
|
+
checks.push(check("image-alt-text", "pass", "All images have alt text"));
|
|
273
|
+
} else {
|
|
274
|
+
checks.push(check("image-alt-text", "fail", `${missingAlt.length} image(s) missing alt text`));
|
|
275
|
+
}
|
|
276
|
+
const internalLinkRegex = /href=["']\/[^"']*["']/gi;
|
|
277
|
+
const internalLinks = contentHTML.match(internalLinkRegex) || [];
|
|
278
|
+
if (internalLinks.length > 0) {
|
|
279
|
+
checks.push(check("internal-links", "pass", `${internalLinks.length} internal link(s) found`));
|
|
280
|
+
} else {
|
|
281
|
+
checks.push(check("internal-links", "warn", "No internal links found \u2014 add links to related content"));
|
|
282
|
+
}
|
|
283
|
+
const externalLinkRegex = /href=["']https?:\/\/[^"']*["']/gi;
|
|
284
|
+
const externalLinks = contentHTML.match(externalLinkRegex) || [];
|
|
285
|
+
if (externalLinks.length > 0) {
|
|
286
|
+
checks.push(check("external-links", "pass", `${externalLinks.length} external link(s) found`));
|
|
287
|
+
} else {
|
|
288
|
+
checks.push(check("external-links", "warn", "No external links found"));
|
|
289
|
+
}
|
|
290
|
+
const paragraphs = contentHTML.split(/<\/p>/i).filter((p) => p.trim());
|
|
291
|
+
const longParagraphs = paragraphs.filter((p) => {
|
|
292
|
+
const text = p.replace(/<[^>]+>/g, "");
|
|
293
|
+
return text.split(/\s+/).length > 300;
|
|
294
|
+
});
|
|
295
|
+
if (longParagraphs.length === 0) {
|
|
296
|
+
checks.push(check("paragraph-length", "pass", "All paragraphs are a reasonable length"));
|
|
297
|
+
} else {
|
|
298
|
+
checks.push(check("paragraph-length", "warn", `${longParagraphs.length} paragraph(s) exceed 300 words`));
|
|
299
|
+
}
|
|
300
|
+
if (post.coverImage?.url) {
|
|
301
|
+
checks.push(check("cover-image", "pass", "Post has a cover image"));
|
|
302
|
+
} else {
|
|
303
|
+
checks.push(check("cover-image", "warn", "No cover image set \u2014 social shares may look plain"));
|
|
304
|
+
}
|
|
305
|
+
if (contentText) {
|
|
306
|
+
const sentences = contentText.split(/[.!?]+/).filter((s) => s.trim().length > 0);
|
|
307
|
+
const words = contentText.split(/\s+/).length;
|
|
308
|
+
const avgWordsPerSentence = sentences.length > 0 ? words / sentences.length : 0;
|
|
309
|
+
if (avgWordsPerSentence <= 20) {
|
|
310
|
+
checks.push(check("readability-score", "pass", `Average sentence length: ${avgWordsPerSentence.toFixed(0)} words`));
|
|
311
|
+
} else if (avgWordsPerSentence <= 25) {
|
|
312
|
+
checks.push(check("readability-score", "warn", `Average sentence length: ${avgWordsPerSentence.toFixed(0)} words (try to keep under 20)`));
|
|
313
|
+
} else {
|
|
314
|
+
checks.push(check("readability-score", "fail", `Average sentence length: ${avgWordsPerSentence.toFixed(0)} words (too long, aim for under 20)`));
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
const fails = checks.filter((c) => c.status === "fail").length;
|
|
318
|
+
const warns = checks.filter((c) => c.status === "warn").length;
|
|
319
|
+
let overall;
|
|
320
|
+
if (fails >= 3) {
|
|
321
|
+
overall = "poor";
|
|
322
|
+
} else if (fails >= 1 || warns >= 5) {
|
|
323
|
+
overall = "ok";
|
|
324
|
+
} else {
|
|
325
|
+
overall = "good";
|
|
326
|
+
}
|
|
327
|
+
return { overall, checks };
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
exports.calculateSEOScore = calculateSEOScore;
|
|
331
|
+
exports.generateBreadcrumbs = generateBreadcrumbs;
|
|
332
|
+
exports.generateFAQStructuredData = generateFAQStructuredData;
|
|
333
|
+
exports.generateMetaTags = generateMetaTags;
|
|
334
|
+
exports.generateStructuredData = generateStructuredData;
|
|
335
|
+
exports.searchPosts = searchPosts;
|
|
336
|
+
//# sourceMappingURL=chunk-QE4VLQYN.cjs.map
|
|
337
|
+
//# sourceMappingURL=chunk-QE4VLQYN.cjs.map
|