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,446 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { MongoClient, ObjectId } from 'mongodb';
|
|
3
|
+
|
|
4
|
+
// src/lib/config.ts
|
|
5
|
+
var envSchema = z.object({
|
|
6
|
+
NEXTBLOGKIT_MONGODB_URI: z.string().min(1, "MongoDB URI is required"),
|
|
7
|
+
NEXTBLOGKIT_R2_ACCOUNT_ID: z.string().min(1, "R2 Account ID is required"),
|
|
8
|
+
NEXTBLOGKIT_R2_ACCESS_KEY: z.string().min(1, "R2 Access Key is required"),
|
|
9
|
+
NEXTBLOGKIT_R2_SECRET_KEY: z.string().min(1, "R2 Secret Key is required"),
|
|
10
|
+
NEXTBLOGKIT_R2_BUCKET: z.string().min(1, "R2 Bucket name is required"),
|
|
11
|
+
NEXTBLOGKIT_R2_PUBLIC_URL: z.string().url("R2 Public URL must be a valid URL"),
|
|
12
|
+
NEXTBLOGKIT_API_KEY: z.string().min(32, "API key must be at least 32 characters"),
|
|
13
|
+
NEXTBLOGKIT_SITE_URL: z.string().url("Site URL must be a valid URL"),
|
|
14
|
+
NEXTBLOGKIT_SITE_NAME: z.string().min(1, "Site name is required")
|
|
15
|
+
});
|
|
16
|
+
var cachedEnv = null;
|
|
17
|
+
function getEnvConfig() {
|
|
18
|
+
if (cachedEnv) return cachedEnv;
|
|
19
|
+
const result = envSchema.safeParse(process.env);
|
|
20
|
+
if (!result.success) {
|
|
21
|
+
const missing = result.error.issues.map((i) => ` - ${i.path.join(".")}: ${i.message}`);
|
|
22
|
+
throw new Error(`NextBlogKit configuration error:
|
|
23
|
+
${missing.join("\n")}`);
|
|
24
|
+
}
|
|
25
|
+
cachedEnv = result.data;
|
|
26
|
+
return cachedEnv;
|
|
27
|
+
}
|
|
28
|
+
var defaultConfig = {
|
|
29
|
+
basePath: "/blog",
|
|
30
|
+
adminPath: "/admin/blog",
|
|
31
|
+
apiPath: "/api/blog",
|
|
32
|
+
postsPerPage: 10,
|
|
33
|
+
excerptLength: 160,
|
|
34
|
+
codeHighlighter: "shiki",
|
|
35
|
+
editor: {
|
|
36
|
+
blocks: [
|
|
37
|
+
"paragraph",
|
|
38
|
+
"heading",
|
|
39
|
+
"image",
|
|
40
|
+
"codeBlock",
|
|
41
|
+
"blockquote",
|
|
42
|
+
"bulletList",
|
|
43
|
+
"orderedList",
|
|
44
|
+
"taskList",
|
|
45
|
+
"table",
|
|
46
|
+
"embed",
|
|
47
|
+
"horizontalRule",
|
|
48
|
+
"callout",
|
|
49
|
+
"tableOfContents",
|
|
50
|
+
"faq",
|
|
51
|
+
"html"
|
|
52
|
+
],
|
|
53
|
+
maxImageSize: 10 * 1024 * 1024,
|
|
54
|
+
imageFormats: ["jpg", "jpeg", "png", "gif", "webp", "svg"],
|
|
55
|
+
autosaveInterval: 3e4
|
|
56
|
+
},
|
|
57
|
+
seo: {
|
|
58
|
+
titleTemplate: "%s | %siteName%",
|
|
59
|
+
generateRSS: true,
|
|
60
|
+
generateSitemap: true,
|
|
61
|
+
structuredData: true,
|
|
62
|
+
minContentLength: 300
|
|
63
|
+
},
|
|
64
|
+
auth: {
|
|
65
|
+
strategy: "api-key"
|
|
66
|
+
},
|
|
67
|
+
features: {
|
|
68
|
+
search: true,
|
|
69
|
+
relatedPosts: true,
|
|
70
|
+
readingProgress: true,
|
|
71
|
+
tableOfContents: true,
|
|
72
|
+
shareButtons: true,
|
|
73
|
+
darkMode: true,
|
|
74
|
+
scheduling: true,
|
|
75
|
+
revisionHistory: true,
|
|
76
|
+
imageOptimization: true
|
|
77
|
+
},
|
|
78
|
+
theme: {
|
|
79
|
+
darkMode: true,
|
|
80
|
+
variables: {
|
|
81
|
+
"--nbk-primary": "#2563eb",
|
|
82
|
+
"--nbk-primary-hover": "#1d4ed8",
|
|
83
|
+
"--nbk-text": "#1f2937",
|
|
84
|
+
"--nbk-text-muted": "#6b7280",
|
|
85
|
+
"--nbk-bg": "#ffffff",
|
|
86
|
+
"--nbk-bg-secondary": "#f9fafb",
|
|
87
|
+
"--nbk-card-bg": "#ffffff",
|
|
88
|
+
"--nbk-border": "#e5e7eb",
|
|
89
|
+
"--nbk-radius": "0.5rem",
|
|
90
|
+
"--nbk-font-heading": '"Inter", system-ui, sans-serif',
|
|
91
|
+
"--nbk-font-body": '"Inter", system-ui, sans-serif',
|
|
92
|
+
"--nbk-font-code": '"JetBrains Mono", "Fira Code", monospace'
|
|
93
|
+
}
|
|
94
|
+
},
|
|
95
|
+
hooks: {}
|
|
96
|
+
};
|
|
97
|
+
var cachedConfig = null;
|
|
98
|
+
function defineConfig(config) {
|
|
99
|
+
return config;
|
|
100
|
+
}
|
|
101
|
+
function getConfig(userConfig) {
|
|
102
|
+
if (cachedConfig && !userConfig) return cachedConfig;
|
|
103
|
+
const merged = {
|
|
104
|
+
...defaultConfig,
|
|
105
|
+
...userConfig,
|
|
106
|
+
editor: { ...defaultConfig.editor, ...userConfig?.editor },
|
|
107
|
+
seo: { ...defaultConfig.seo, ...userConfig?.seo },
|
|
108
|
+
auth: { ...defaultConfig.auth, ...userConfig?.auth },
|
|
109
|
+
features: { ...defaultConfig.features, ...userConfig?.features },
|
|
110
|
+
theme: {
|
|
111
|
+
...defaultConfig.theme,
|
|
112
|
+
...userConfig?.theme,
|
|
113
|
+
variables: { ...defaultConfig.theme.variables, ...userConfig?.theme?.variables }
|
|
114
|
+
},
|
|
115
|
+
hooks: { ...defaultConfig.hooks, ...userConfig?.hooks }
|
|
116
|
+
};
|
|
117
|
+
if (!userConfig) {
|
|
118
|
+
cachedConfig = merged;
|
|
119
|
+
}
|
|
120
|
+
return merged;
|
|
121
|
+
}
|
|
122
|
+
function getBlogConfig() {
|
|
123
|
+
const env = getEnvConfig();
|
|
124
|
+
const config = getConfig();
|
|
125
|
+
return {
|
|
126
|
+
...config,
|
|
127
|
+
siteUrl: env.NEXTBLOGKIT_SITE_URL,
|
|
128
|
+
siteName: env.NEXTBLOGKIT_SITE_NAME,
|
|
129
|
+
metadata: {
|
|
130
|
+
title: `Blog | ${env.NEXTBLOGKIT_SITE_NAME}`,
|
|
131
|
+
description: `Latest posts from ${env.NEXTBLOGKIT_SITE_NAME}`,
|
|
132
|
+
openGraph: {
|
|
133
|
+
title: `Blog | ${env.NEXTBLOGKIT_SITE_NAME}`,
|
|
134
|
+
description: `Latest posts from ${env.NEXTBLOGKIT_SITE_NAME}`,
|
|
135
|
+
url: `${env.NEXTBLOGKIT_SITE_URL}${config.basePath}`,
|
|
136
|
+
siteName: env.NEXTBLOGKIT_SITE_NAME,
|
|
137
|
+
type: "website"
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// src/lib/slug.ts
|
|
144
|
+
function generateSlug(text) {
|
|
145
|
+
return text.toLowerCase().trim().replace(/[^\w\s-]/g, "").replace(/[\s_]+/g, "-").replace(/-+/g, "-").replace(/^-+|-+$/g, "");
|
|
146
|
+
}
|
|
147
|
+
async function ensureUniqueSlug(slug, collection, excludeId) {
|
|
148
|
+
let candidate = slug;
|
|
149
|
+
let counter = 1;
|
|
150
|
+
while (true) {
|
|
151
|
+
const query = { slug: candidate };
|
|
152
|
+
if (excludeId) {
|
|
153
|
+
query._id = { $ne: excludeId };
|
|
154
|
+
}
|
|
155
|
+
const existing = await collection.findOne(query);
|
|
156
|
+
if (!existing) return candidate;
|
|
157
|
+
candidate = `${slug}-${counter}`;
|
|
158
|
+
counter++;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// src/lib/reading-time.ts
|
|
163
|
+
var WORDS_PER_MINUTE = 200;
|
|
164
|
+
function calculateReadingTime(text) {
|
|
165
|
+
const words = countWords(text);
|
|
166
|
+
return Math.max(1, Math.ceil(words / WORDS_PER_MINUTE));
|
|
167
|
+
}
|
|
168
|
+
function countWords(text) {
|
|
169
|
+
if (!text || !text.trim()) return 0;
|
|
170
|
+
return text.trim().split(/\s+/).length;
|
|
171
|
+
}
|
|
172
|
+
function extractTextFromHTML(html) {
|
|
173
|
+
return html.replace(/<script[^>]*>[\s\S]*?<\/script>/gi, "").replace(/<style[^>]*>[\s\S]*?<\/style>/gi, "").replace(/<[^>]+>/g, " ").replace(/ /g, " ").replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, '"').replace(/'/g, "'").replace(/\s+/g, " ").trim();
|
|
174
|
+
}
|
|
175
|
+
function extractTextFromBlocks(blocks) {
|
|
176
|
+
if (!Array.isArray(blocks)) return "";
|
|
177
|
+
const parts = [];
|
|
178
|
+
function walk(node) {
|
|
179
|
+
if (!node || typeof node !== "object") return;
|
|
180
|
+
const n = node;
|
|
181
|
+
if (typeof n.text === "string") {
|
|
182
|
+
parts.push(n.text);
|
|
183
|
+
}
|
|
184
|
+
if (Array.isArray(n.content)) {
|
|
185
|
+
for (const child of n.content) {
|
|
186
|
+
walk(child);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
for (const block of blocks) {
|
|
191
|
+
walk(block);
|
|
192
|
+
}
|
|
193
|
+
return parts.join(" ");
|
|
194
|
+
}
|
|
195
|
+
var client = null;
|
|
196
|
+
var db = null;
|
|
197
|
+
async function getDb() {
|
|
198
|
+
if (db) return db;
|
|
199
|
+
const env = getEnvConfig();
|
|
200
|
+
client = new MongoClient(env.NEXTBLOGKIT_MONGODB_URI);
|
|
201
|
+
await client.connect();
|
|
202
|
+
db = client.db();
|
|
203
|
+
return db;
|
|
204
|
+
}
|
|
205
|
+
async function getCollection(name) {
|
|
206
|
+
const database = await getDb();
|
|
207
|
+
return database.collection(name);
|
|
208
|
+
}
|
|
209
|
+
async function ensureIndexes() {
|
|
210
|
+
const database = await getDb();
|
|
211
|
+
const posts = database.collection("nbk_posts");
|
|
212
|
+
await posts.createIndex({ slug: 1 }, { unique: true });
|
|
213
|
+
await posts.createIndex({ status: 1, publishedAt: -1 });
|
|
214
|
+
await posts.createIndex({ categories: 1, status: 1 });
|
|
215
|
+
await posts.createIndex({ tags: 1, status: 1 });
|
|
216
|
+
await posts.createIndex({ "seo.focusKeyword": 1 });
|
|
217
|
+
await posts.createIndex({ contentText: "text", title: "text", excerpt: "text" });
|
|
218
|
+
const categories = database.collection("nbk_categories");
|
|
219
|
+
await categories.createIndex({ slug: 1 }, { unique: true });
|
|
220
|
+
await categories.createIndex({ order: 1 });
|
|
221
|
+
const media = database.collection("nbk_media");
|
|
222
|
+
await media.createIndex({ createdAt: -1 });
|
|
223
|
+
await media.createIndex({ r2Key: 1 }, { unique: true });
|
|
224
|
+
}
|
|
225
|
+
async function createPost(input, defaultAuthor) {
|
|
226
|
+
const col = await getCollection("nbk_posts");
|
|
227
|
+
const slug = await ensureUniqueSlug(
|
|
228
|
+
input.slug || generateSlug(input.title),
|
|
229
|
+
col
|
|
230
|
+
);
|
|
231
|
+
const contentText = input.contentText || extractTextFromBlocks(input.content || []);
|
|
232
|
+
const wordCount = countWords(contentText);
|
|
233
|
+
const readingTime = calculateReadingTime(contentText);
|
|
234
|
+
const excerpt = input.excerpt || contentText.slice(0, 160).replace(/\s+\S*$/, "") + "...";
|
|
235
|
+
const now = /* @__PURE__ */ new Date();
|
|
236
|
+
const doc = {
|
|
237
|
+
title: input.title,
|
|
238
|
+
slug,
|
|
239
|
+
excerpt,
|
|
240
|
+
content: input.content || [],
|
|
241
|
+
contentHTML: input.contentHTML || "",
|
|
242
|
+
contentText,
|
|
243
|
+
coverImage: input.coverImage,
|
|
244
|
+
categories: input.categories || [],
|
|
245
|
+
tags: input.tags || [],
|
|
246
|
+
author: input.author || defaultAuthor || { name: "Admin" },
|
|
247
|
+
seo: {
|
|
248
|
+
ogType: "article",
|
|
249
|
+
noIndex: false,
|
|
250
|
+
...input.seo
|
|
251
|
+
},
|
|
252
|
+
status: input.status || "draft",
|
|
253
|
+
publishedAt: input.status === "published" ? now : input.publishedAt,
|
|
254
|
+
scheduledAt: input.scheduledAt,
|
|
255
|
+
readingTime,
|
|
256
|
+
wordCount,
|
|
257
|
+
version: 1,
|
|
258
|
+
revisions: [],
|
|
259
|
+
createdAt: now,
|
|
260
|
+
updatedAt: now
|
|
261
|
+
};
|
|
262
|
+
const result = await col.insertOne(doc);
|
|
263
|
+
return { _id: result.insertedId, ...doc };
|
|
264
|
+
}
|
|
265
|
+
async function updatePost(id, input) {
|
|
266
|
+
const col = await getCollection("nbk_posts");
|
|
267
|
+
const objectId = new ObjectId(id);
|
|
268
|
+
const existing = await col.findOne({ _id: objectId });
|
|
269
|
+
if (!existing) return null;
|
|
270
|
+
const updates = { ...input, updatedAt: /* @__PURE__ */ new Date() };
|
|
271
|
+
if (input.slug && input.slug !== existing.slug) {
|
|
272
|
+
updates.slug = await ensureUniqueSlug(input.slug, col, id);
|
|
273
|
+
}
|
|
274
|
+
if (input.content) {
|
|
275
|
+
const contentText = input.contentText || extractTextFromBlocks(input.content);
|
|
276
|
+
updates.contentText = contentText;
|
|
277
|
+
updates.wordCount = countWords(contentText);
|
|
278
|
+
updates.readingTime = calculateReadingTime(contentText);
|
|
279
|
+
if (!input.excerpt) {
|
|
280
|
+
updates.excerpt = contentText.slice(0, 160).replace(/\s+\S*$/, "") + "...";
|
|
281
|
+
}
|
|
282
|
+
const revision = {
|
|
283
|
+
version: existing.version || 1,
|
|
284
|
+
title: existing.title,
|
|
285
|
+
content: existing.content,
|
|
286
|
+
contentHTML: existing.contentHTML,
|
|
287
|
+
savedAt: /* @__PURE__ */ new Date()
|
|
288
|
+
};
|
|
289
|
+
const revisions = [...existing.revisions || [], revision].slice(-10);
|
|
290
|
+
updates.revisions = revisions;
|
|
291
|
+
updates.version = (existing.version || 1) + 1;
|
|
292
|
+
}
|
|
293
|
+
if (input.status === "published" && existing.status !== "published") {
|
|
294
|
+
updates.publishedAt = /* @__PURE__ */ new Date();
|
|
295
|
+
}
|
|
296
|
+
await col.updateOne({ _id: objectId }, { $set: updates });
|
|
297
|
+
return await col.findOne({ _id: objectId });
|
|
298
|
+
}
|
|
299
|
+
async function deletePost(id) {
|
|
300
|
+
const col = await getCollection("nbk_posts");
|
|
301
|
+
const result = await col.updateOne(
|
|
302
|
+
{ _id: new ObjectId(id) },
|
|
303
|
+
{ $set: { status: "archived", updatedAt: /* @__PURE__ */ new Date() } }
|
|
304
|
+
);
|
|
305
|
+
return result.modifiedCount > 0;
|
|
306
|
+
}
|
|
307
|
+
async function getPostBySlug(slug) {
|
|
308
|
+
const col = await getCollection("nbk_posts");
|
|
309
|
+
return await col.findOne({ slug });
|
|
310
|
+
}
|
|
311
|
+
async function getPostById(id) {
|
|
312
|
+
const col = await getCollection("nbk_posts");
|
|
313
|
+
return await col.findOne({ _id: new ObjectId(id) });
|
|
314
|
+
}
|
|
315
|
+
async function listPosts(query = {}) {
|
|
316
|
+
const col = await getCollection("nbk_posts");
|
|
317
|
+
const {
|
|
318
|
+
page = 1,
|
|
319
|
+
limit = 10,
|
|
320
|
+
category,
|
|
321
|
+
tag,
|
|
322
|
+
status,
|
|
323
|
+
search,
|
|
324
|
+
sortBy = "publishedAt",
|
|
325
|
+
sortOrder = "desc"
|
|
326
|
+
} = query;
|
|
327
|
+
const filter = {};
|
|
328
|
+
if (status) {
|
|
329
|
+
filter.status = status;
|
|
330
|
+
} else {
|
|
331
|
+
filter.status = { $ne: "archived" };
|
|
332
|
+
}
|
|
333
|
+
if (category) filter.categories = category;
|
|
334
|
+
if (tag) filter.tags = tag;
|
|
335
|
+
if (search) {
|
|
336
|
+
filter.$text = { $search: search };
|
|
337
|
+
}
|
|
338
|
+
const sort = {
|
|
339
|
+
[sortBy]: sortOrder === "asc" ? 1 : -1
|
|
340
|
+
};
|
|
341
|
+
const skip = (page - 1) * limit;
|
|
342
|
+
const [posts, total] = await Promise.all([
|
|
343
|
+
col.find(filter).sort(sort).skip(skip).limit(limit).toArray(),
|
|
344
|
+
col.countDocuments(filter)
|
|
345
|
+
]);
|
|
346
|
+
return {
|
|
347
|
+
posts,
|
|
348
|
+
total
|
|
349
|
+
};
|
|
350
|
+
}
|
|
351
|
+
async function createCategory(input) {
|
|
352
|
+
const col = await getCollection("nbk_categories");
|
|
353
|
+
const slug = await ensureUniqueSlug(
|
|
354
|
+
input.slug || generateSlug(input.name),
|
|
355
|
+
col
|
|
356
|
+
);
|
|
357
|
+
const doc = {
|
|
358
|
+
name: input.name,
|
|
359
|
+
slug,
|
|
360
|
+
description: input.description,
|
|
361
|
+
seo: input.seo,
|
|
362
|
+
order: input.order ?? 0,
|
|
363
|
+
parentId: input.parentId ? new ObjectId(input.parentId) : void 0,
|
|
364
|
+
postCount: 0
|
|
365
|
+
};
|
|
366
|
+
const result = await col.insertOne(doc);
|
|
367
|
+
return { _id: result.insertedId, ...doc };
|
|
368
|
+
}
|
|
369
|
+
async function updateCategory(id, input) {
|
|
370
|
+
const col = await getCollection("nbk_categories");
|
|
371
|
+
const objectId = new ObjectId(id);
|
|
372
|
+
const updates = { ...input };
|
|
373
|
+
if (input.slug) {
|
|
374
|
+
updates.slug = await ensureUniqueSlug(input.slug, col, id);
|
|
375
|
+
}
|
|
376
|
+
if (input.parentId) {
|
|
377
|
+
updates.parentId = new ObjectId(input.parentId);
|
|
378
|
+
}
|
|
379
|
+
await col.updateOne({ _id: objectId }, { $set: updates });
|
|
380
|
+
return await col.findOne({ _id: objectId });
|
|
381
|
+
}
|
|
382
|
+
async function deleteCategory(id) {
|
|
383
|
+
const col = await getCollection("nbk_categories");
|
|
384
|
+
const result = await col.deleteOne({ _id: new ObjectId(id) });
|
|
385
|
+
return result.deletedCount > 0;
|
|
386
|
+
}
|
|
387
|
+
async function listCategories() {
|
|
388
|
+
const col = await getCollection("nbk_categories");
|
|
389
|
+
return await col.find({}).sort({ order: 1 }).toArray();
|
|
390
|
+
}
|
|
391
|
+
async function getCategoryBySlug(slug) {
|
|
392
|
+
const col = await getCollection("nbk_categories");
|
|
393
|
+
return await col.findOne({ slug });
|
|
394
|
+
}
|
|
395
|
+
async function createMedia(data) {
|
|
396
|
+
const col = await getCollection("nbk_media");
|
|
397
|
+
const result = await col.insertOne(data);
|
|
398
|
+
return { _id: result.insertedId, ...data };
|
|
399
|
+
}
|
|
400
|
+
async function deleteMedia(id) {
|
|
401
|
+
const col = await getCollection("nbk_media");
|
|
402
|
+
const media = await col.findOne({ _id: new ObjectId(id) });
|
|
403
|
+
if (!media) return null;
|
|
404
|
+
await col.deleteOne({ _id: new ObjectId(id) });
|
|
405
|
+
return media;
|
|
406
|
+
}
|
|
407
|
+
async function listMedia(query = {}) {
|
|
408
|
+
const col = await getCollection("nbk_media");
|
|
409
|
+
const { page = 1, limit = 20, mimeType } = query;
|
|
410
|
+
const filter = {};
|
|
411
|
+
if (mimeType) filter.mimeType = { $regex: mimeType };
|
|
412
|
+
const skip = (page - 1) * limit;
|
|
413
|
+
const [media, total] = await Promise.all([
|
|
414
|
+
col.find(filter).sort({ createdAt: -1 }).skip(skip).limit(limit).toArray(),
|
|
415
|
+
col.countDocuments(filter)
|
|
416
|
+
]);
|
|
417
|
+
return { media, total };
|
|
418
|
+
}
|
|
419
|
+
async function getSettings() {
|
|
420
|
+
const col = await getCollection("nbk_settings");
|
|
421
|
+
const settings = await col.findOne({ _id: "global" });
|
|
422
|
+
if (!settings) {
|
|
423
|
+
const defaults = {
|
|
424
|
+
_id: "global",
|
|
425
|
+
postsPerPage: 10,
|
|
426
|
+
commentSystem: "none"
|
|
427
|
+
};
|
|
428
|
+
await col.insertOne(defaults);
|
|
429
|
+
return defaults;
|
|
430
|
+
}
|
|
431
|
+
return settings;
|
|
432
|
+
}
|
|
433
|
+
async function updateSettings(data) {
|
|
434
|
+
const col = await getCollection("nbk_settings");
|
|
435
|
+
const { _id, ...updates } = data;
|
|
436
|
+
await col.updateOne(
|
|
437
|
+
{ _id: "global" },
|
|
438
|
+
{ $set: updates },
|
|
439
|
+
{ upsert: true }
|
|
440
|
+
);
|
|
441
|
+
return getSettings();
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
export { calculateReadingTime, countWords, createCategory, createMedia, createPost, defineConfig, deleteCategory, deleteMedia, deletePost, ensureIndexes, ensureUniqueSlug, extractTextFromBlocks, extractTextFromHTML, generateSlug, getBlogConfig, getCategoryBySlug, getCollection, getConfig, getDb, getEnvConfig, getPostById, getPostBySlug, getSettings, listCategories, listMedia, listPosts, updateCategory, updatePost, updateSettings };
|
|
445
|
+
//# sourceMappingURL=chunk-64HUVJOZ.js.map
|
|
446
|
+
//# sourceMappingURL=chunk-64HUVJOZ.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/lib/config.ts","../src/lib/slug.ts","../src/lib/reading-time.ts","../src/lib/db.ts"],"names":[],"mappings":"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","file":"chunk-64HUVJOZ.js","sourcesContent":["import { z } from 'zod';\nimport type { NextBlogKitConfig } from './types';\n\nconst envSchema = z.object({\n NEXTBLOGKIT_MONGODB_URI: z.string().min(1, 'MongoDB URI is required'),\n NEXTBLOGKIT_R2_ACCOUNT_ID: z.string().min(1, 'R2 Account ID is required'),\n NEXTBLOGKIT_R2_ACCESS_KEY: z.string().min(1, 'R2 Access Key is required'),\n NEXTBLOGKIT_R2_SECRET_KEY: z.string().min(1, 'R2 Secret Key is required'),\n NEXTBLOGKIT_R2_BUCKET: z.string().min(1, 'R2 Bucket name is required'),\n NEXTBLOGKIT_R2_PUBLIC_URL: z.string().url('R2 Public URL must be a valid URL'),\n NEXTBLOGKIT_API_KEY: z.string().min(32, 'API key must be at least 32 characters'),\n NEXTBLOGKIT_SITE_URL: z.string().url('Site URL must be a valid URL'),\n NEXTBLOGKIT_SITE_NAME: z.string().min(1, 'Site name is required'),\n});\n\nexport type EnvConfig = z.infer<typeof envSchema>;\n\nlet cachedEnv: EnvConfig | null = null;\n\nexport function getEnvConfig(): EnvConfig {\n if (cachedEnv) return cachedEnv;\n\n const result = envSchema.safeParse(process.env);\n if (!result.success) {\n const missing = result.error.issues.map((i) => ` - ${i.path.join('.')}: ${i.message}`);\n throw new Error(`NextBlogKit configuration error:\\n${missing.join('\\n')}`);\n }\n\n cachedEnv = result.data;\n return cachedEnv;\n}\n\nconst defaultConfig: NextBlogKitConfig = {\n basePath: '/blog',\n adminPath: '/admin/blog',\n apiPath: '/api/blog',\n postsPerPage: 10,\n excerptLength: 160,\n codeHighlighter: 'shiki',\n editor: {\n blocks: [\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 maxImageSize: 10 * 1024 * 1024,\n imageFormats: ['jpg', 'jpeg', 'png', 'gif', 'webp', 'svg'],\n autosaveInterval: 30000,\n },\n seo: {\n titleTemplate: '%s | %siteName%',\n generateRSS: true,\n generateSitemap: true,\n structuredData: true,\n minContentLength: 300,\n },\n auth: {\n strategy: 'api-key',\n },\n features: {\n search: true,\n relatedPosts: true,\n readingProgress: true,\n tableOfContents: true,\n shareButtons: true,\n darkMode: true,\n scheduling: true,\n revisionHistory: true,\n imageOptimization: true,\n },\n theme: {\n darkMode: true,\n variables: {\n '--nbk-primary': '#2563eb',\n '--nbk-primary-hover': '#1d4ed8',\n '--nbk-text': '#1f2937',\n '--nbk-text-muted': '#6b7280',\n '--nbk-bg': '#ffffff',\n '--nbk-bg-secondary': '#f9fafb',\n '--nbk-card-bg': '#ffffff',\n '--nbk-border': '#e5e7eb',\n '--nbk-radius': '0.5rem',\n '--nbk-font-heading': '\"Inter\", system-ui, sans-serif',\n '--nbk-font-body': '\"Inter\", system-ui, sans-serif',\n '--nbk-font-code': '\"JetBrains Mono\", \"Fira Code\", monospace',\n },\n },\n hooks: {},\n};\n\nlet cachedConfig: NextBlogKitConfig | null = null;\n\nexport function defineConfig(config: Partial<NextBlogKitConfig>): Partial<NextBlogKitConfig> {\n return config;\n}\n\nexport function getConfig(userConfig?: Partial<NextBlogKitConfig>): NextBlogKitConfig {\n if (cachedConfig && !userConfig) return cachedConfig;\n\n const merged: NextBlogKitConfig = {\n ...defaultConfig,\n ...userConfig,\n editor: { ...defaultConfig.editor, ...userConfig?.editor },\n seo: { ...defaultConfig.seo, ...userConfig?.seo },\n auth: { ...defaultConfig.auth, ...userConfig?.auth },\n features: { ...defaultConfig.features, ...userConfig?.features },\n theme: {\n ...defaultConfig.theme,\n ...userConfig?.theme,\n variables: { ...defaultConfig.theme.variables, ...userConfig?.theme?.variables },\n },\n hooks: { ...defaultConfig.hooks, ...userConfig?.hooks },\n };\n\n if (!userConfig) {\n cachedConfig = merged;\n }\n\n return merged;\n}\n\nexport function getBlogConfig() {\n const env = getEnvConfig();\n const config = getConfig();\n\n return {\n ...config,\n siteUrl: env.NEXTBLOGKIT_SITE_URL,\n siteName: env.NEXTBLOGKIT_SITE_NAME,\n metadata: {\n title: `Blog | ${env.NEXTBLOGKIT_SITE_NAME}`,\n description: `Latest posts from ${env.NEXTBLOGKIT_SITE_NAME}`,\n openGraph: {\n title: `Blog | ${env.NEXTBLOGKIT_SITE_NAME}`,\n description: `Latest posts from ${env.NEXTBLOGKIT_SITE_NAME}`,\n url: `${env.NEXTBLOGKIT_SITE_URL}${config.basePath}`,\n siteName: env.NEXTBLOGKIT_SITE_NAME,\n type: 'website',\n },\n },\n };\n}\n","import type { Collection } from 'mongodb';\n\nexport function generateSlug(text: string): string {\n return text\n .toLowerCase()\n .trim()\n .replace(/[^\\w\\s-]/g, '')\n .replace(/[\\s_]+/g, '-')\n .replace(/-+/g, '-')\n .replace(/^-+|-+$/g, '');\n}\n\nexport async function ensureUniqueSlug(\n slug: string,\n collection: Collection,\n excludeId?: string\n): Promise<string> {\n let candidate = slug;\n let counter = 1;\n\n while (true) {\n const query: Record<string, unknown> = { slug: candidate };\n if (excludeId) {\n query._id = { $ne: excludeId };\n }\n\n const existing = await collection.findOne(query);\n if (!existing) return candidate;\n\n candidate = `${slug}-${counter}`;\n counter++;\n }\n}\n","const WORDS_PER_MINUTE = 200;\n\nexport function calculateReadingTime(text: string): number {\n const words = countWords(text);\n return Math.max(1, Math.ceil(words / WORDS_PER_MINUTE));\n}\n\nexport function countWords(text: string): number {\n if (!text || !text.trim()) return 0;\n return text.trim().split(/\\s+/).length;\n}\n\nexport function extractTextFromHTML(html: string): string {\n return html\n .replace(/<script[^>]*>[\\s\\S]*?<\\/script>/gi, '')\n .replace(/<style[^>]*>[\\s\\S]*?<\\/style>/gi, '')\n .replace(/<[^>]+>/g, ' ')\n .replace(/ /g, ' ')\n .replace(/&/g, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n .replace(/"/g, '\"')\n .replace(/'/g, \"'\")\n .replace(/\\s+/g, ' ')\n .trim();\n}\n\nexport function extractTextFromBlocks(blocks: unknown[]): string {\n if (!Array.isArray(blocks)) return '';\n\n const parts: string[] = [];\n\n function walk(node: unknown) {\n if (!node || typeof node !== 'object') return;\n const n = node as Record<string, unknown>;\n\n if (typeof n.text === 'string') {\n parts.push(n.text);\n }\n\n if (Array.isArray(n.content)) {\n for (const child of n.content) {\n walk(child);\n }\n }\n }\n\n for (const block of blocks) {\n walk(block);\n }\n\n return parts.join(' ');\n}\n","import { MongoClient, type Db, type Collection, type Document, ObjectId } from 'mongodb';\nimport { getEnvConfig } from './config';\nimport type {\n BlogPost,\n Category,\n Media,\n BlogSettings,\n CreatePostInput,\n UpdatePostInput,\n CreateCategoryInput,\n UpdateCategoryInput,\n PostListQuery,\n MediaListQuery,\n} from './types';\nimport { generateSlug, ensureUniqueSlug } from './slug';\nimport { calculateReadingTime, countWords, extractTextFromBlocks } from './reading-time';\n\nlet client: MongoClient | null = null;\nlet db: Db | null = null;\n\nexport async function getDb(): Promise<Db> {\n if (db) return db;\n\n const env = getEnvConfig();\n client = new MongoClient(env.NEXTBLOGKIT_MONGODB_URI);\n await client.connect();\n db = client.db();\n return db;\n}\n\nexport async function getCollection<T extends Document = Document>(\n name: string\n): Promise<Collection<T>> {\n const database = await getDb();\n return database.collection<T>(name);\n}\n\nexport async function ensureIndexes(): Promise<void> {\n const database = await getDb();\n\n const posts = database.collection('nbk_posts');\n await posts.createIndex({ slug: 1 }, { unique: true });\n await posts.createIndex({ status: 1, publishedAt: -1 });\n await posts.createIndex({ categories: 1, status: 1 });\n await posts.createIndex({ tags: 1, status: 1 });\n await posts.createIndex({ 'seo.focusKeyword': 1 });\n await posts.createIndex({ contentText: 'text', title: 'text', excerpt: 'text' });\n\n const categories = database.collection('nbk_categories');\n await categories.createIndex({ slug: 1 }, { unique: true });\n await categories.createIndex({ order: 1 });\n\n const media = database.collection('nbk_media');\n await media.createIndex({ createdAt: -1 });\n await media.createIndex({ r2Key: 1 }, { unique: true });\n}\n\n// ============================================================\n// Posts\n// ============================================================\n\nexport async function createPost(input: CreatePostInput, defaultAuthor?: BlogPost['author']): Promise<BlogPost> {\n const col = await getCollection('nbk_posts');\n\n const slug = await ensureUniqueSlug(\n input.slug || generateSlug(input.title),\n col\n );\n\n const contentText = input.contentText || extractTextFromBlocks(input.content || []);\n const wordCount = countWords(contentText);\n const readingTime = calculateReadingTime(contentText);\n\n const excerpt =\n input.excerpt || contentText.slice(0, 160).replace(/\\s+\\S*$/, '') + '...';\n\n const now = new Date();\n const doc = {\n title: input.title,\n slug,\n excerpt,\n content: input.content || [],\n contentHTML: input.contentHTML || '',\n contentText,\n coverImage: input.coverImage,\n categories: input.categories || [],\n tags: input.tags || [],\n author: input.author || defaultAuthor || { name: 'Admin' },\n seo: {\n ogType: 'article',\n noIndex: false,\n ...input.seo,\n },\n status: input.status || 'draft',\n publishedAt: input.status === 'published' ? now : input.publishedAt,\n scheduledAt: input.scheduledAt,\n readingTime,\n wordCount,\n version: 1,\n revisions: [],\n createdAt: now,\n updatedAt: now,\n };\n\n const result = await col.insertOne(doc);\n return { _id: result.insertedId, ...doc } as unknown as BlogPost;\n}\n\nexport async function updatePost(\n id: string,\n input: UpdatePostInput\n): Promise<BlogPost | null> {\n const col = await getCollection('nbk_posts');\n const objectId = new ObjectId(id);\n const existing = await col.findOne({ _id: objectId });\n if (!existing) return null;\n\n const updates: Record<string, unknown> = { ...input, updatedAt: new Date() };\n\n if (input.slug && input.slug !== existing.slug) {\n updates.slug = await ensureUniqueSlug(input.slug, col, id);\n }\n\n if (input.content) {\n const contentText = input.contentText || extractTextFromBlocks(input.content);\n updates.contentText = contentText;\n updates.wordCount = countWords(contentText);\n updates.readingTime = calculateReadingTime(contentText);\n\n if (!input.excerpt) {\n updates.excerpt =\n contentText.slice(0, 160).replace(/\\s+\\S*$/, '') + '...';\n }\n\n // Save revision\n const revision = {\n version: existing.version || 1,\n title: existing.title,\n content: existing.content,\n contentHTML: existing.contentHTML,\n savedAt: new Date(),\n };\n const revisions = [...(existing.revisions || []), revision].slice(-10);\n updates.revisions = revisions;\n updates.version = (existing.version || 1) + 1;\n }\n\n if (input.status === 'published' && existing.status !== 'published') {\n updates.publishedAt = new Date();\n }\n\n await col.updateOne({ _id: objectId }, { $set: updates });\n return (await col.findOne({ _id: objectId })) as unknown as BlogPost;\n}\n\nexport async function deletePost(id: string): Promise<boolean> {\n const col = await getCollection('nbk_posts');\n const result = await col.updateOne(\n { _id: new ObjectId(id) },\n { $set: { status: 'archived', updatedAt: new Date() } }\n );\n return result.modifiedCount > 0;\n}\n\nexport async function hardDeletePost(id: string): Promise<boolean> {\n const col = await getCollection('nbk_posts');\n const result = await col.deleteOne({ _id: new ObjectId(id) });\n return result.deletedCount > 0;\n}\n\nexport async function getPostBySlug(slug: string): Promise<BlogPost | null> {\n const col = await getCollection('nbk_posts');\n return (await col.findOne({ slug })) as unknown as BlogPost | null;\n}\n\nexport async function getPostById(id: string): Promise<BlogPost | null> {\n const col = await getCollection('nbk_posts');\n return (await col.findOne({ _id: new ObjectId(id) })) as unknown as BlogPost | null;\n}\n\nexport async function listPosts(\n query: PostListQuery = {}\n): Promise<{ posts: BlogPost[]; total: number }> {\n const col = await getCollection('nbk_posts');\n const {\n page = 1,\n limit = 10,\n category,\n tag,\n status,\n search,\n sortBy = 'publishedAt',\n sortOrder = 'desc',\n } = query;\n\n const filter: Record<string, unknown> = {};\n\n if (status) {\n filter.status = status;\n } else {\n filter.status = { $ne: 'archived' };\n }\n\n if (category) filter.categories = category;\n if (tag) filter.tags = tag;\n if (search) {\n filter.$text = { $search: search };\n }\n\n const sort: Record<string, 1 | -1> = {\n [sortBy]: sortOrder === 'asc' ? 1 : -1,\n };\n\n const skip = (page - 1) * limit;\n\n const [posts, total] = await Promise.all([\n col.find(filter).sort(sort).skip(skip).limit(limit).toArray(),\n col.countDocuments(filter),\n ]);\n\n return {\n posts: posts as unknown as BlogPost[],\n total,\n };\n}\n\n// ============================================================\n// Categories\n// ============================================================\n\nexport async function createCategory(input: CreateCategoryInput): Promise<Category> {\n const col = await getCollection('nbk_categories');\n const slug = await ensureUniqueSlug(\n input.slug || generateSlug(input.name),\n col\n );\n\n const doc = {\n name: input.name,\n slug,\n description: input.description,\n seo: input.seo,\n order: input.order ?? 0,\n parentId: input.parentId ? new ObjectId(input.parentId) : undefined,\n postCount: 0,\n };\n\n const result = await col.insertOne(doc);\n return { _id: result.insertedId, ...doc } as unknown as Category;\n}\n\nexport async function updateCategory(\n id: string,\n input: UpdateCategoryInput\n): Promise<Category | null> {\n const col = await getCollection('nbk_categories');\n const objectId = new ObjectId(id);\n\n const updates: Record<string, unknown> = { ...input };\n if (input.slug) {\n updates.slug = await ensureUniqueSlug(input.slug, col, id);\n }\n if (input.parentId) {\n updates.parentId = new ObjectId(input.parentId);\n }\n\n await col.updateOne({ _id: objectId }, { $set: updates });\n return (await col.findOne({ _id: objectId })) as unknown as Category | null;\n}\n\nexport async function deleteCategory(id: string): Promise<boolean> {\n const col = await getCollection('nbk_categories');\n const result = await col.deleteOne({ _id: new ObjectId(id) });\n return result.deletedCount > 0;\n}\n\nexport async function listCategories(): Promise<Category[]> {\n const col = await getCollection('nbk_categories');\n return (await col.find({}).sort({ order: 1 }).toArray()) as unknown as Category[];\n}\n\nexport async function getCategoryBySlug(slug: string): Promise<Category | null> {\n const col = await getCollection('nbk_categories');\n return (await col.findOne({ slug })) as unknown as Category | null;\n}\n\nexport async function updateCategoryPostCount(categorySlug: string): Promise<void> {\n const posts = await getCollection('nbk_posts');\n const categories = await getCollection('nbk_categories');\n const count = await posts.countDocuments({\n categories: categorySlug,\n status: 'published',\n });\n await categories.updateOne({ slug: categorySlug }, { $set: { postCount: count } });\n}\n\n// ============================================================\n// Media\n// ============================================================\n\nexport async function createMedia(data: Omit<Media, '_id'>): Promise<Media> {\n const col = await getCollection('nbk_media');\n const result = await col.insertOne(data);\n return { _id: result.insertedId, ...data } as unknown as Media;\n}\n\nexport async function deleteMedia(id: string): Promise<Media | null> {\n const col = await getCollection('nbk_media');\n const media = await col.findOne({ _id: new ObjectId(id) });\n if (!media) return null;\n await col.deleteOne({ _id: new ObjectId(id) });\n return media as unknown as Media;\n}\n\nexport async function listMedia(\n query: MediaListQuery = {}\n): Promise<{ media: Media[]; total: number }> {\n const col = await getCollection('nbk_media');\n const { page = 1, limit = 20, mimeType } = query;\n\n const filter: Record<string, unknown> = {};\n if (mimeType) filter.mimeType = { $regex: mimeType };\n\n const skip = (page - 1) * limit;\n const [media, total] = await Promise.all([\n col.find(filter).sort({ createdAt: -1 }).skip(skip).limit(limit).toArray(),\n col.countDocuments(filter),\n ]);\n\n return { media: media as unknown as Media[], total };\n}\n\n// ============================================================\n// Settings\n// ============================================================\n\nexport async function getSettings(): Promise<BlogSettings> {\n const col = await getCollection('nbk_settings');\n const settings = await col.findOne({ _id: 'global' as unknown as ObjectId });\n if (!settings) {\n const defaults: Record<string, unknown> = {\n _id: 'global',\n postsPerPage: 10,\n commentSystem: 'none',\n };\n await col.insertOne(defaults as Document);\n return defaults as unknown as BlogSettings;\n }\n return settings as unknown as BlogSettings;\n}\n\nexport async function updateSettings(\n data: Partial<BlogSettings>\n): Promise<BlogSettings> {\n const col = await getCollection('nbk_settings');\n const { _id, ...updates } = data as Record<string, unknown>;\n await col.updateOne(\n { _id: 'global' as unknown as ObjectId },\n { $set: updates },\n { upsert: true }\n );\n return getSettings();\n}\n"]}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var chunkU2ROR6AY_cjs = require('./chunk-U2ROR6AY.cjs');
|
|
4
|
+
|
|
5
|
+
// src/lib/rss.ts
|
|
6
|
+
async function generateRSSFeed(fullContent = false) {
|
|
7
|
+
const env = chunkU2ROR6AY_cjs.getEnvConfig();
|
|
8
|
+
const posts = await chunkU2ROR6AY_cjs.getCollection("nbk_posts");
|
|
9
|
+
const publishedPosts = await posts.find({ status: "published" }).sort({ publishedAt: -1 }).limit(50).toArray();
|
|
10
|
+
const items = publishedPosts.map((post) => {
|
|
11
|
+
const postUrl = `${env.NEXTBLOGKIT_SITE_URL}/blog/${post.slug}`;
|
|
12
|
+
const content = fullContent ? post.contentHTML : post.excerpt;
|
|
13
|
+
const pubDate = post.publishedAt ? new Date(post.publishedAt).toUTCString() : new Date(post.createdAt).toUTCString();
|
|
14
|
+
let enclosure = "";
|
|
15
|
+
if (post.coverImage?.url) {
|
|
16
|
+
enclosure = `
|
|
17
|
+
<enclosure url="${escapeXml(post.coverImage.url)}" type="image/jpeg" />`;
|
|
18
|
+
}
|
|
19
|
+
return ` <item>
|
|
20
|
+
<title>${escapeXml(post.title)}</title>
|
|
21
|
+
<link>${escapeXml(postUrl)}</link>
|
|
22
|
+
<guid isPermaLink="true">${escapeXml(postUrl)}</guid>
|
|
23
|
+
<description><![CDATA[${content || ""}]]></description>
|
|
24
|
+
<pubDate>${pubDate}</pubDate>${enclosure}${post.categories?.length ? post.categories.map((c) => `
|
|
25
|
+
<category>${escapeXml(c)}</category>`).join("") : ""}
|
|
26
|
+
</item>`;
|
|
27
|
+
}).join("\n");
|
|
28
|
+
const buildDate = (/* @__PURE__ */ new Date()).toUTCString();
|
|
29
|
+
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
30
|
+
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
|
|
31
|
+
<channel>
|
|
32
|
+
<title>${escapeXml(env.NEXTBLOGKIT_SITE_NAME)} Blog</title>
|
|
33
|
+
<link>${escapeXml(env.NEXTBLOGKIT_SITE_URL)}/blog</link>
|
|
34
|
+
<description>Latest posts from ${escapeXml(env.NEXTBLOGKIT_SITE_NAME)}</description>
|
|
35
|
+
<language>en</language>
|
|
36
|
+
<lastBuildDate>${buildDate}</lastBuildDate>
|
|
37
|
+
<atom:link href="${escapeXml(env.NEXTBLOGKIT_SITE_URL)}/api/blog/rss.xml" rel="self" type="application/rss+xml" />
|
|
38
|
+
${items}
|
|
39
|
+
</channel>
|
|
40
|
+
</rss>`;
|
|
41
|
+
}
|
|
42
|
+
function escapeXml(str) {
|
|
43
|
+
return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
exports.generateRSSFeed = generateRSSFeed;
|
|
47
|
+
//# sourceMappingURL=chunk-6HKMZOI4.cjs.map
|
|
48
|
+
//# sourceMappingURL=chunk-6HKMZOI4.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/lib/rss.ts"],"names":["getEnvConfig","getCollection"],"mappings":";;;;;AAGA,eAAsB,eAAA,CAAgB,cAAc,KAAA,EAAwB;AAC1E,EAAA,MAAM,MAAMA,8BAAA,EAAa;AACzB,EAAA,MAAM,KAAA,GAAQ,MAAMC,+BAAA,CAAc,WAAW,CAAA;AAE7C,EAAA,MAAM,iBAAiB,MAAM,KAAA,CAC1B,KAAK,EAAE,MAAA,EAAQ,aAAa,CAAA,CAC5B,IAAA,CAAK,EAAE,aAAa,EAAA,EAAI,EACxB,KAAA,CAAM,EAAE,EACR,OAAA,EAAQ;AAEX,EAAA,MAAM,KAAA,GAAQ,cAAA,CACX,GAAA,CAAI,CAAC,IAAA,KAAS;AACb,IAAA,MAAM,UAAU,CAAA,EAAG,GAAA,CAAI,oBAAoB,CAAA,MAAA,EAAS,KAAK,IAAI,CAAA,CAAA;AAC7D,IAAA,MAAM,OAAA,GAAU,WAAA,GAAc,IAAA,CAAK,WAAA,GAAc,IAAA,CAAK,OAAA;AACtD,IAAA,MAAM,OAAA,GAAU,IAAA,CAAK,WAAA,GACjB,IAAI,KAAK,IAAA,CAAK,WAAW,CAAA,CAAE,WAAA,KAC3B,IAAI,IAAA,CAAK,IAAA,CAAK,SAAS,EAAE,WAAA,EAAY;AAEzC,IAAA,IAAI,SAAA,GAAY,EAAA;AAChB,IAAA,IAAI,IAAA,CAAK,YAAY,GAAA,EAAK;AACxB,MAAA,SAAA,GAAY;AAAA,sBAAA,EAA2B,SAAA,CAAU,IAAA,CAAK,UAAA,CAAW,GAAG,CAAC,CAAA,sBAAA,CAAA;AAAA,IACvE;AAEA,IAAA,OAAO,CAAA;AAAA,aAAA,EACE,SAAA,CAAU,IAAA,CAAK,KAAK,CAAC,CAAA;AAAA,YAAA,EACtB,SAAA,CAAU,OAAO,CAAC,CAAA;AAAA,+BAAA,EACC,SAAA,CAAU,OAAO,CAAC,CAAA;AAAA,4BAAA,EACrB,WAAW,EAAE,CAAA;AAAA,eAAA,EAC1B,OAAO,CAAA,UAAA,EAAa,SAAS,CAAA,EACtC,IAAA,CAAK,UAAA,EAAY,MAAA,GACb,IAAA,CAAK,UAAA,CAAW,GAAA,CAAI,CAAC,CAAA,KAAc;AAAA,gBAAA,EAAqB,SAAA,CAAU,CAAC,CAAC,CAAA,WAAA,CAAa,EAAE,IAAA,CAAK,EAAE,IAC1F,EACN;AAAA,WAAA,CAAA;AAAA,EAEF,CAAC,CAAA,CACA,IAAA,CAAK,IAAI,CAAA;AAEZ,EAAA,MAAM,SAAA,GAAA,iBAAY,IAAI,IAAA,EAAK,EAAE,WAAA,EAAY;AAEzC,EAAA,OAAO,CAAA;AAAA;AAAA;AAAA,WAAA,EAGI,SAAA,CAAU,GAAA,CAAI,qBAAqB,CAAC,CAAA;AAAA,UAAA,EACrC,SAAA,CAAU,GAAA,CAAI,oBAAoB,CAAC,CAAA;AAAA,mCAAA,EACV,SAAA,CAAU,GAAA,CAAI,qBAAqB,CAAC,CAAA;AAAA;AAAA,mBAAA,EAEpD,SAAS,CAAA;AAAA,qBAAA,EACP,SAAA,CAAU,GAAA,CAAI,oBAAoB,CAAC,CAAA;AAAA,EACxD,KAAK;AAAA;AAAA,MAAA,CAAA;AAGP;AAEA,SAAS,UAAU,GAAA,EAAqB;AACtC,EAAA,OAAO,IACJ,OAAA,CAAQ,IAAA,EAAM,OAAO,CAAA,CACrB,OAAA,CAAQ,MAAM,MAAM,CAAA,CACpB,QAAQ,IAAA,EAAM,MAAM,EACpB,OAAA,CAAQ,IAAA,EAAM,QAAQ,CAAA,CACtB,OAAA,CAAQ,MAAM,QAAQ,CAAA;AAC3B","file":"chunk-6HKMZOI4.cjs","sourcesContent":["import { getCollection } from './db';\nimport { getEnvConfig } from './config';\n\nexport async function generateRSSFeed(fullContent = false): Promise<string> {\n const env = getEnvConfig();\n const posts = await getCollection('nbk_posts');\n\n const publishedPosts = await posts\n .find({ status: 'published' })\n .sort({ publishedAt: -1 })\n .limit(50)\n .toArray();\n\n const items = publishedPosts\n .map((post) => {\n const postUrl = `${env.NEXTBLOGKIT_SITE_URL}/blog/${post.slug}`;\n const content = fullContent ? post.contentHTML : post.excerpt;\n const pubDate = post.publishedAt\n ? new Date(post.publishedAt).toUTCString()\n : new Date(post.createdAt).toUTCString();\n\n let enclosure = '';\n if (post.coverImage?.url) {\n enclosure = `\\n <enclosure url=\"${escapeXml(post.coverImage.url)}\" type=\"image/jpeg\" />`;\n }\n\n return ` <item>\n <title>${escapeXml(post.title)}</title>\n <link>${escapeXml(postUrl)}</link>\n <guid isPermaLink=\"true\">${escapeXml(postUrl)}</guid>\n <description><![CDATA[${content || ''}]]></description>\n <pubDate>${pubDate}</pubDate>${enclosure}${\n post.categories?.length\n ? post.categories.map((c: string) => `\\n <category>${escapeXml(c)}</category>`).join('')\n : ''\n }\n </item>`;\n })\n .join('\\n');\n\n const buildDate = new Date().toUTCString();\n\n return `<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<rss version=\"2.0\" xmlns:atom=\"http://www.w3.org/2005/Atom\" xmlns:content=\"http://purl.org/rss/1.0/modules/content/\">\n <channel>\n <title>${escapeXml(env.NEXTBLOGKIT_SITE_NAME)} Blog</title>\n <link>${escapeXml(env.NEXTBLOGKIT_SITE_URL)}/blog</link>\n <description>Latest posts from ${escapeXml(env.NEXTBLOGKIT_SITE_NAME)}</description>\n <language>en</language>\n <lastBuildDate>${buildDate}</lastBuildDate>\n <atom:link href=\"${escapeXml(env.NEXTBLOGKIT_SITE_URL)}/api/blog/rss.xml\" rel=\"self\" type=\"application/rss+xml\" />\n${items}\n </channel>\n</rss>`;\n}\n\nfunction escapeXml(str: string): string {\n return str\n .replace(/&/g, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n .replace(/\"/g, '"')\n .replace(/'/g, ''');\n}\n"]}
|