next-blogpanel 1.0.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.
Files changed (127) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +211 -0
  3. package/dist/admin/index.cjs +3062 -0
  4. package/dist/admin/index.cjs.map +1 -0
  5. package/dist/admin/index.d.cts +51 -0
  6. package/dist/admin/index.d.ts +51 -0
  7. package/dist/admin/index.js +3029 -0
  8. package/dist/admin/index.js.map +1 -0
  9. package/dist/api/categories.cjs +82 -0
  10. package/dist/api/categories.cjs.map +1 -0
  11. package/dist/api/categories.d.cts +27 -0
  12. package/dist/api/categories.d.ts +27 -0
  13. package/dist/api/categories.js +77 -0
  14. package/dist/api/categories.js.map +1 -0
  15. package/dist/api/media-file.cjs +46 -0
  16. package/dist/api/media-file.cjs.map +1 -0
  17. package/dist/api/media-file.d.cts +7 -0
  18. package/dist/api/media-file.d.ts +7 -0
  19. package/dist/api/media-file.js +44 -0
  20. package/dist/api/media-file.js.map +1 -0
  21. package/dist/api/media.cjs +180 -0
  22. package/dist/api/media.cjs.map +1 -0
  23. package/dist/api/media.d.cts +22 -0
  24. package/dist/api/media.d.ts +22 -0
  25. package/dist/api/media.js +176 -0
  26. package/dist/api/media.js.map +1 -0
  27. package/dist/api/posts.cjs +103 -0
  28. package/dist/api/posts.cjs.map +1 -0
  29. package/dist/api/posts.d.cts +27 -0
  30. package/dist/api/posts.d.ts +27 -0
  31. package/dist/api/posts.js +98 -0
  32. package/dist/api/posts.js.map +1 -0
  33. package/dist/api/rss.cjs +25 -0
  34. package/dist/api/rss.cjs.map +1 -0
  35. package/dist/api/rss.d.cts +5 -0
  36. package/dist/api/rss.d.ts +5 -0
  37. package/dist/api/rss.js +23 -0
  38. package/dist/api/rss.js.map +1 -0
  39. package/dist/api/settings.cjs +40 -0
  40. package/dist/api/settings.cjs.map +1 -0
  41. package/dist/api/settings.d.cts +17 -0
  42. package/dist/api/settings.d.ts +17 -0
  43. package/dist/api/settings.js +37 -0
  44. package/dist/api/settings.js.map +1 -0
  45. package/dist/api/sitemap.cjs +25 -0
  46. package/dist/api/sitemap.cjs.map +1 -0
  47. package/dist/api/sitemap.d.cts +5 -0
  48. package/dist/api/sitemap.d.ts +5 -0
  49. package/dist/api/sitemap.js +23 -0
  50. package/dist/api/sitemap.js.map +1 -0
  51. package/dist/api/tokens.cjs +56 -0
  52. package/dist/api/tokens.cjs.map +1 -0
  53. package/dist/api/tokens.d.cts +22 -0
  54. package/dist/api/tokens.d.ts +22 -0
  55. package/dist/api/tokens.js +52 -0
  56. package/dist/api/tokens.js.map +1 -0
  57. package/dist/chunk-2M46MMWN.cjs +76 -0
  58. package/dist/chunk-2M46MMWN.cjs.map +1 -0
  59. package/dist/chunk-4GK22Q3R.cjs +229 -0
  60. package/dist/chunk-4GK22Q3R.cjs.map +1 -0
  61. package/dist/chunk-6PZ772NQ.js +595 -0
  62. package/dist/chunk-6PZ772NQ.js.map +1 -0
  63. package/dist/chunk-BZXPRGLN.cjs +351 -0
  64. package/dist/chunk-BZXPRGLN.cjs.map +1 -0
  65. package/dist/chunk-DAX5C6SC.cjs +86 -0
  66. package/dist/chunk-DAX5C6SC.cjs.map +1 -0
  67. package/dist/chunk-DWNMHQ4I.cjs +57 -0
  68. package/dist/chunk-DWNMHQ4I.cjs.map +1 -0
  69. package/dist/chunk-FPGVR2BX.cjs +121 -0
  70. package/dist/chunk-FPGVR2BX.cjs.map +1 -0
  71. package/dist/chunk-JBP3G4QJ.js +50 -0
  72. package/dist/chunk-JBP3G4QJ.js.map +1 -0
  73. package/dist/chunk-JDP4DIHI.js +215 -0
  74. package/dist/chunk-JDP4DIHI.js.map +1 -0
  75. package/dist/chunk-LIZOVA4W.js +118 -0
  76. package/dist/chunk-LIZOVA4W.js.map +1 -0
  77. package/dist/chunk-QLBTOCT3.js +118 -0
  78. package/dist/chunk-QLBTOCT3.js.map +1 -0
  79. package/dist/chunk-UI2KDZ5H.js +344 -0
  80. package/dist/chunk-UI2KDZ5H.js.map +1 -0
  81. package/dist/chunk-V7QPRX4A.cjs +125 -0
  82. package/dist/chunk-V7QPRX4A.cjs.map +1 -0
  83. package/dist/chunk-VEJFN65Y.js +82 -0
  84. package/dist/chunk-VEJFN65Y.js.map +1 -0
  85. package/dist/chunk-WYZFAI6O.js +74 -0
  86. package/dist/chunk-WYZFAI6O.js.map +1 -0
  87. package/dist/chunk-XLYTATRQ.cjs +637 -0
  88. package/dist/chunk-XLYTATRQ.cjs.map +1 -0
  89. package/dist/cli/index.cjs +2036 -0
  90. package/dist/components/index.cjs +647 -0
  91. package/dist/components/index.cjs.map +1 -0
  92. package/dist/components/index.d.cts +187 -0
  93. package/dist/components/index.d.ts +187 -0
  94. package/dist/components/index.js +633 -0
  95. package/dist/components/index.js.map +1 -0
  96. package/dist/db-M3KXLBUP.js +3 -0
  97. package/dist/db-M3KXLBUP.js.map +1 -0
  98. package/dist/db-TDCZNY3Q.cjs +112 -0
  99. package/dist/db-TDCZNY3Q.cjs.map +1 -0
  100. package/dist/editor/index.cjs +1126 -0
  101. package/dist/editor/index.cjs.map +1 -0
  102. package/dist/editor/index.d.cts +137 -0
  103. package/dist/editor/index.d.ts +137 -0
  104. package/dist/editor/index.js +1093 -0
  105. package/dist/editor/index.js.map +1 -0
  106. package/dist/index-BcNka7gK.d.cts +475 -0
  107. package/dist/index-DPRgaNEE.d.ts +475 -0
  108. package/dist/index.cjs +433 -0
  109. package/dist/index.cjs.map +1 -0
  110. package/dist/index.d.cts +27 -0
  111. package/dist/index.d.ts +27 -0
  112. package/dist/index.js +209 -0
  113. package/dist/index.js.map +1 -0
  114. package/dist/lib/index.cjs +193 -0
  115. package/dist/lib/index.cjs.map +1 -0
  116. package/dist/lib/index.d.cts +4 -0
  117. package/dist/lib/index.d.ts +4 -0
  118. package/dist/lib/index.js +8 -0
  119. package/dist/lib/index.js.map +1 -0
  120. package/dist/styles/admin.css +667 -0
  121. package/dist/styles/blog.css +851 -0
  122. package/dist/styles/editor.css +452 -0
  123. package/dist/styles/globals.css +270 -0
  124. package/dist/styles/prose.css +299 -0
  125. package/dist/types-CgrBuig-.d.cts +778 -0
  126. package/dist/types-CgrBuig-.d.ts +778 -0
  127. package/package.json +140 -0
@@ -0,0 +1,475 @@
1
+ import { f as BlogPanelUserConfig, d as BlockType, g as BlogPost, M as Media, e as BlogPanelConfig, i as CreateCategoryInput, C as Category, j as CreatePostInput, h as BlogSettings, k as MediaListQuery, P as PostListQuery, U as UpdateCategoryInput, r as UpdatePostInput, q as SEOScore } from './types-CgrBuig-.cjs';
2
+ import { z } from 'zod';
3
+ import { Document, Collection, Db } from 'mongodb';
4
+
5
+ declare const envSchema: z.ZodObject<{
6
+ BLOGPANEL_MONGODB_URI: z.ZodString;
7
+ BLOGPANEL_API_KEY: z.ZodString;
8
+ BLOGPANEL_MONGODB_DB: z.ZodOptional<z.ZodString>;
9
+ BLOGPANEL_R2_ACCOUNT_ID: z.ZodOptional<z.ZodString>;
10
+ BLOGPANEL_R2_ACCESS_KEY: z.ZodOptional<z.ZodString>;
11
+ BLOGPANEL_R2_SECRET_KEY: z.ZodOptional<z.ZodString>;
12
+ BLOGPANEL_R2_BUCKET: z.ZodOptional<z.ZodString>;
13
+ BLOGPANEL_R2_PUBLIC_URL: z.ZodOptional<z.ZodString>;
14
+ BLOGPANEL_STORAGE: z.ZodOptional<z.ZodString>;
15
+ BLOGPANEL_SITE_URL: z.ZodDefault<z.ZodOptional<z.ZodString>>;
16
+ BLOGPANEL_SITE_NAME: z.ZodDefault<z.ZodOptional<z.ZodString>>;
17
+ }, "strip", z.ZodTypeAny, {
18
+ BLOGPANEL_MONGODB_URI: string;
19
+ BLOGPANEL_API_KEY: string;
20
+ BLOGPANEL_SITE_URL: string;
21
+ BLOGPANEL_SITE_NAME: string;
22
+ BLOGPANEL_MONGODB_DB?: string | undefined;
23
+ BLOGPANEL_R2_ACCOUNT_ID?: string | undefined;
24
+ BLOGPANEL_R2_ACCESS_KEY?: string | undefined;
25
+ BLOGPANEL_R2_SECRET_KEY?: string | undefined;
26
+ BLOGPANEL_R2_BUCKET?: string | undefined;
27
+ BLOGPANEL_R2_PUBLIC_URL?: string | undefined;
28
+ BLOGPANEL_STORAGE?: string | undefined;
29
+ }, {
30
+ BLOGPANEL_MONGODB_URI: string;
31
+ BLOGPANEL_API_KEY: string;
32
+ BLOGPANEL_MONGODB_DB?: string | undefined;
33
+ BLOGPANEL_R2_ACCOUNT_ID?: string | undefined;
34
+ BLOGPANEL_R2_ACCESS_KEY?: string | undefined;
35
+ BLOGPANEL_R2_SECRET_KEY?: string | undefined;
36
+ BLOGPANEL_R2_BUCKET?: string | undefined;
37
+ BLOGPANEL_R2_PUBLIC_URL?: string | undefined;
38
+ BLOGPANEL_STORAGE?: string | undefined;
39
+ BLOGPANEL_SITE_URL?: string | undefined;
40
+ BLOGPANEL_SITE_NAME?: string | undefined;
41
+ }>;
42
+ type EnvConfig = z.infer<typeof envSchema>;
43
+ declare function getEnvConfig(): EnvConfig;
44
+ declare function isR2Configured(): boolean;
45
+ declare function defineConfig(config: BlogPanelUserConfig): BlogPanelUserConfig;
46
+ /**
47
+ * Installs the user's `next-blogpanel.config.ts` into the server runtime.
48
+ *
49
+ * Called by the `next-blogpanel.setup` module that `init` scaffolds, which
50
+ * every generated route handler, layout and page imports. Calling it more
51
+ * than once is fine and expected — each bundled copy of the setup module
52
+ * installs the same object, and repeating an identical call is a no-op.
53
+ *
54
+ * The argument is merged over whatever is already installed: the defaults on
55
+ * the very first call in this process, and the previously installed config on
56
+ * every call after that. It is deliberately NOT always merged over the
57
+ * defaults — that would let a later *partial* call silently discard keys only
58
+ * an earlier call set. Concretely: the generated setup module installs the
59
+ * full config once; if application code then calls `setConfig({ theme: {...} })`
60
+ * on its own (`docs/configuration.md` shows exactly that pattern), merging
61
+ * over the defaults would reset `apiPath` back to `/api/blog` while leaving
62
+ * `installed` true — so `requireApiPath()` would wave the reset value
63
+ * straight through and persist it into GridFS media URLs. Merging over the
64
+ * installed config instead means a later partial call can only add or
65
+ * override the keys it actually mentions.
66
+ *
67
+ * A later call that explicitly sets a *different* `apiPath` than what is
68
+ * already installed is still honoured — this is not a lock — but it is
69
+ * reported with a console warning, once per distinct transition, because that
70
+ * value gets baked into already-persisted media URLs and post bodies.
71
+ */
72
+ declare function setConfig(userConfig: BlogPanelUserConfig): BlogPanelConfig;
73
+ /** True once a `setConfig()` call has installed a configuration in this process. */
74
+ declare function isConfigInstalled(): boolean;
75
+ declare function getConfig(userConfig?: BlogPanelUserConfig): BlogPanelConfig;
76
+ /**
77
+ * Thrown by `requireApiPath()` when nothing has installed a configuration in
78
+ * this server runtime.
79
+ *
80
+ * A dedicated class rather than a string-matched `Error`: callers that need
81
+ * to tell "no config installed" apart from any other failure (e.g.
82
+ * `POST /media`, which passes this specific message through to the client
83
+ * instead of flattening it to a generic 500) should match on `instanceof`,
84
+ * not on `error.message.startsWith(...)`. A message-prefix check breaks the
85
+ * moment the wording changes for any reason — a typo fix, a translation, a
86
+ * clarification — and silently falls back to the generic branch instead of
87
+ * failing a test. `instanceof` cannot drift like that.
88
+ */
89
+ declare class ConfigNotInstalledError extends Error {
90
+ constructor(context: string);
91
+ }
92
+ declare function getBlogConfig(): {
93
+ siteUrl: string;
94
+ siteName: string;
95
+ metadata: {
96
+ title: string;
97
+ description: string;
98
+ openGraph: {
99
+ title: string;
100
+ description: string;
101
+ url: string;
102
+ siteName: string;
103
+ type: string;
104
+ };
105
+ };
106
+ basePath: string;
107
+ adminPath: string;
108
+ apiPath: string;
109
+ postsPerPage: number;
110
+ excerptLength: number;
111
+ codeHighlighter: "shiki" | "prism";
112
+ editor: {
113
+ blocks: BlockType[];
114
+ maxImageSize: number;
115
+ maxUploadSize: number;
116
+ imageFormats: string[];
117
+ autosaveInterval: number;
118
+ };
119
+ seo: {
120
+ titleTemplate: string;
121
+ defaultOgImage?: string;
122
+ generateRSS: boolean;
123
+ generateSitemap: boolean;
124
+ structuredData: boolean;
125
+ minContentLength: number;
126
+ };
127
+ auth: {
128
+ strategy: "api-key" | "custom" | "credentials";
129
+ verify?: (request: Request) => Promise<boolean>;
130
+ admins?: string[];
131
+ };
132
+ features: {
133
+ search: boolean;
134
+ relatedPosts: boolean;
135
+ readingProgress: boolean;
136
+ tableOfContents: boolean;
137
+ shareButtons: boolean;
138
+ darkMode: boolean;
139
+ scheduling: boolean;
140
+ revisionHistory: boolean;
141
+ imageOptimization: boolean;
142
+ };
143
+ theme: {
144
+ variables?: Record<string, string>;
145
+ darkMode?: boolean;
146
+ components?: Record<string, React.ComponentType<unknown>>;
147
+ };
148
+ hooks: {
149
+ beforePublish?: (post: BlogPost) => Promise<void>;
150
+ afterPublish?: (post: BlogPost) => Promise<void>;
151
+ beforeDelete?: (post: BlogPost) => Promise<void>;
152
+ onMediaUpload?: (media: Media) => Promise<void>;
153
+ };
154
+ };
155
+
156
+ declare function getDb(): Promise<Db>;
157
+ declare function getCollection<T extends Document = Document>(name: string): Promise<Collection<T>>;
158
+ declare function ensureIndexes(): Promise<void>;
159
+ declare function createPost(input: CreatePostInput, defaultAuthor?: BlogPost['author']): Promise<BlogPost>;
160
+ declare function updatePost(id: string, input: UpdatePostInput): Promise<BlogPost | null>;
161
+ declare function deletePost(id: string): Promise<boolean>;
162
+ declare function getPostBySlug(slug: string): Promise<BlogPost | null>;
163
+ declare function getPostById(id: string): Promise<BlogPost | null>;
164
+ declare function listPosts(query?: PostListQuery): Promise<{
165
+ posts: BlogPost[];
166
+ total: number;
167
+ }>;
168
+ declare function createCategory(input: CreateCategoryInput): Promise<Category>;
169
+ declare function updateCategory(id: string, input: UpdateCategoryInput): Promise<Category | null>;
170
+ declare function deleteCategory(id: string): Promise<boolean>;
171
+ declare function listCategories(): Promise<Category[]>;
172
+ declare function getCategoryBySlug(slug: string): Promise<Category | null>;
173
+ declare function createMedia(data: Omit<Media, '_id'>): Promise<Media>;
174
+ declare function deleteMedia(id: string): Promise<Media | null>;
175
+ declare function listMedia(query?: MediaListQuery): Promise<{
176
+ media: Media[];
177
+ total: number;
178
+ }>;
179
+ declare function getSettings(): Promise<BlogSettings>;
180
+ declare function updateSettings(data: Partial<BlogSettings>): Promise<BlogSettings>;
181
+
182
+ interface MediaUploadResult {
183
+ key: string;
184
+ url: string;
185
+ size: number;
186
+ contentType: string;
187
+ }
188
+ interface StorageObject {
189
+ key: string;
190
+ size: number;
191
+ lastModified: Date;
192
+ }
193
+ /**
194
+ * The contract every storage backend satisfies — public API, implementable
195
+ * from outside the package.
196
+ *
197
+ * `name` is deliberately a free-form string rather than a union of the two
198
+ * built-ins: it is written verbatim to `Media.provider` on every upload and
199
+ * read back to route the delete, so a third backend that cannot state its own
200
+ * name cannot have its objects deleted. See `registerStorageProvider()` for
201
+ * how a name is made resolvable, and docs/storage.md for what an
202
+ * implementation must guarantee.
203
+ */
204
+ interface StorageProvider {
205
+ /**
206
+ * Stable identifier for this backend, persisted to `Media.provider`.
207
+ *
208
+ * Must be stable across deploys — changing it strands every object already
209
+ * recorded under the old name, because `getStorageProviderByName()` will no
210
+ * longer resolve it. `'gridfs'` and `'r2'` are reserved for the built-ins.
211
+ */
212
+ readonly name: string;
213
+ upload(file: Buffer, filename: string, contentType: string): Promise<MediaUploadResult>;
214
+ delete(key: string): Promise<void>;
215
+ list(prefix?: string): Promise<StorageObject[]>;
216
+ }
217
+ /**
218
+ * Names owned by the built-in providers. Reserved: registering over them
219
+ * would silently redirect deletes for every object already recorded under
220
+ * that name to a different backend.
221
+ */
222
+ declare const BUILT_IN_PROVIDER_NAMES: readonly ["gridfs", "r2"];
223
+ declare function generateKey(filename: string): string;
224
+ declare class R2StorageProvider implements StorageProvider {
225
+ readonly name: "r2";
226
+ upload(file: Buffer, filename: string, contentType: string): Promise<MediaUploadResult>;
227
+ delete(key: string): Promise<void>;
228
+ list(prefix?: string): Promise<StorageObject[]>;
229
+ }
230
+ declare class GridFSStorageProvider implements StorageProvider {
231
+ readonly name: "gridfs";
232
+ private bucket;
233
+ upload(file: Buffer, filename: string, contentType: string): Promise<MediaUploadResult>;
234
+ delete(key: string): Promise<void>;
235
+ list(prefix?: string): Promise<StorageObject[]>;
236
+ }
237
+ /**
238
+ * Thrown when a provider name cannot be resolved to a backend.
239
+ *
240
+ * A dedicated class rather than a string-matched `Error`, for the same reason
241
+ * as `ConfigNotInstalledError`: `POST`/`DELETE /media` need to tell "this
242
+ * install never registered that provider" apart from any other failure so
243
+ * they can pass the actionable message through instead of flattening it to a
244
+ * generic 500, and an `error.message.startsWith(...)` check silently stops
245
+ * working the moment the wording changes.
246
+ *
247
+ * There is deliberately no fallback to the configured default in its place.
248
+ * `generateKey()` produces identically-shaped keys on every backend, so a
249
+ * default asked to delete a key it does not hold finds nothing and returns
250
+ * cleanly — the object is stranded and nothing reports it. Writing to the
251
+ * wrong backend is the same failure with the bytes going somewhere the stored
252
+ * URL does not point.
253
+ */
254
+ declare class StorageProviderNotRegisteredError extends Error {
255
+ /** The unresolvable name, for callers that want to report it structurally. */
256
+ readonly providerName: string;
257
+ /** Every name that *is* resolvable in this process, built-ins included. */
258
+ readonly available: string[];
259
+ constructor(providerName: string, available: string[]);
260
+ }
261
+ /**
262
+ * Makes a custom backend resolvable by name for the lifetime of this process.
263
+ *
264
+ * Call it before any request is served. The generated `next-blogpanel.setup`
265
+ * module is the intended home: every scaffolded route imports it, so whichever
266
+ * route entry a request lands on first has already run registration.
267
+ *
268
+ * Calling it more than once with the same name is expected and is a silent
269
+ * last-write-wins overwrite, not an error — each per-route copy of the setup
270
+ * module evaluates its own `new MyProvider()`, so the duplicate registrations
271
+ * are a normal consequence of the bundling, exactly as repeated `setConfig()`
272
+ * calls are.
273
+ *
274
+ * The registered value is an instance, not a factory, and is shared by every
275
+ * concurrent request in the process — it must be safe to use that way.
276
+ */
277
+ declare function registerStorageProvider(provider: StorageProvider): StorageProvider;
278
+ /** Removes a previously registered custom provider. Built-in names are not removable. */
279
+ declare function unregisterStorageProvider(name: string): boolean;
280
+ /** Every provider name resolvable in this process, built-ins first. */
281
+ declare function listStorageProviderNames(): string[];
282
+ /** True when `name` resolves to a backend without throwing. */
283
+ declare function isStorageProviderRegistered(name: string): boolean;
284
+ /**
285
+ * Resolves the backend new uploads should go to.
286
+ *
287
+ * `BLOGPANEL_STORAGE` forces a specific one by name — including a registered
288
+ * custom provider — and an unresolvable value throws rather than quietly
289
+ * reverting to the automatic choice. Otherwise R2 is selected when all five
290
+ * of its variables are present, and GridFS is the zero-setup default.
291
+ */
292
+ declare function getStorageProvider(): StorageProvider;
293
+ /**
294
+ * Resolves the provider a specific object was stored under, by name — not by
295
+ * whatever backend happens to be configured right now.
296
+ *
297
+ * `getStorageProvider()` answers "what should new uploads use today?", which
298
+ * can diverge from "what actually holds this particular object?" once a
299
+ * deployment switches backends (e.g. GridFS -> R2). `generateKey()` produces
300
+ * identically-shaped keys on every provider, so asking the wrong one to
301
+ * delete a key doesn't error — it just finds nothing and returns cleanly,
302
+ * silently orphaning the real object. Callers that know which backend a
303
+ * document was written under (`Media.provider`) must use this instead.
304
+ *
305
+ * The two built-in names always resolve; anything else must have been handed
306
+ * to `registerStorageProvider()` in this process. An unknown name throws
307
+ * `StorageProviderNotRegisteredError` — it never degrades to a default,
308
+ * because that is the exact shape of the silent-stranding bug above.
309
+ */
310
+ declare function getStorageProviderByName(name: string): StorageProvider;
311
+
312
+ declare function generateSlug(text: string): string;
313
+ declare function ensureUniqueSlug(slug: string, collection: Collection, excludeId?: string): Promise<string>;
314
+
315
+ declare function calculateReadingTime(text: string): number;
316
+ declare function countWords(text: string): number;
317
+ declare function extractTextFromHTML(html: string): string;
318
+ declare function extractTextFromBlocks(blocks: unknown[]): string;
319
+
320
+ interface SearchResult {
321
+ slug: string;
322
+ title: string;
323
+ excerpt: string;
324
+ score: number;
325
+ }
326
+ declare function searchPosts(collection: Collection, query: string, limit?: number): Promise<SearchResult[]>;
327
+
328
+ interface MetaTags {
329
+ title: string;
330
+ description: string;
331
+ canonical: string;
332
+ openGraph: {
333
+ title: string;
334
+ description: string;
335
+ url: string;
336
+ siteName: string;
337
+ type: string;
338
+ images: {
339
+ url: string;
340
+ width?: number;
341
+ height?: number;
342
+ alt?: string;
343
+ }[];
344
+ article?: {
345
+ publishedTime: string;
346
+ modifiedTime: string;
347
+ section?: string;
348
+ tags?: string[];
349
+ };
350
+ };
351
+ twitter: {
352
+ card: string;
353
+ title: string;
354
+ description: string;
355
+ images: string[];
356
+ };
357
+ robots?: string;
358
+ }
359
+ declare function generateMetaTags(post: BlogPost): MetaTags;
360
+ interface ArticleStructuredData {
361
+ '@context': string;
362
+ '@type': string;
363
+ headline: string;
364
+ description: string;
365
+ image?: string;
366
+ datePublished?: string;
367
+ dateModified: string;
368
+ author: {
369
+ '@type': string;
370
+ name: string;
371
+ url?: string;
372
+ };
373
+ publisher: {
374
+ '@type': string;
375
+ name: string;
376
+ };
377
+ mainEntityOfPage: {
378
+ '@type': string;
379
+ '@id': string;
380
+ };
381
+ wordCount: number;
382
+ articleSection?: string;
383
+ }
384
+ declare function generateStructuredData(post: BlogPost): ArticleStructuredData;
385
+ interface FAQStructuredData {
386
+ '@context': string;
387
+ '@type': string;
388
+ mainEntity: {
389
+ '@type': string;
390
+ name: string;
391
+ acceptedAnswer: {
392
+ '@type': string;
393
+ text: string;
394
+ };
395
+ }[];
396
+ }
397
+ declare function generateFAQStructuredData(faqItems: {
398
+ question: string;
399
+ answer: string;
400
+ }[]): FAQStructuredData | null;
401
+ interface BreadcrumbStructuredData {
402
+ '@context': string;
403
+ '@type': string;
404
+ itemListElement: {
405
+ '@type': string;
406
+ position: number;
407
+ name: string;
408
+ item?: string;
409
+ }[];
410
+ }
411
+ declare function generateBreadcrumbs(post: BlogPost, categoryName?: string): BreadcrumbStructuredData;
412
+
413
+ declare function calculateSEOScore(post: BlogPost): SEOScore;
414
+
415
+ declare function generateSitemap(): Promise<string>;
416
+
417
+ declare function generateRSSFeed(fullContent?: boolean): Promise<string>;
418
+
419
+ interface ProcessedImage {
420
+ original: MediaUploadResult;
421
+ width: number;
422
+ height: number;
423
+ format: string;
424
+ /**
425
+ * Every storage key this call wrote — the original plus every responsive
426
+ * derivative and the thumbnail. A caller that persists a reference
427
+ * afterward (e.g. createMedia) and then fails must roll all of these back,
428
+ * since none of them would otherwise be reachable through the UI.
429
+ */
430
+ uploadedKeys: string[];
431
+ }
432
+ declare class ImageTooLargeError extends Error {
433
+ readonly achievedBytes: number;
434
+ readonly limitBytes: number;
435
+ constructor(achievedBytes: number, limitBytes: number);
436
+ }
437
+ declare function processImage(file: Buffer, filename: string, storage: StorageProvider): Promise<ProcessedImage>;
438
+
439
+ /**
440
+ * URL absolutisation for the contexts that require it.
441
+ *
442
+ * Deliberately free of any environment or configuration access, and free of
443
+ * any Node built-in, so it can be imported from client components as well as
444
+ * from server code. Callers supply the site URL; on the server that is
445
+ * `getEnvConfig().BLOGPANEL_SITE_URL`, in a component it is a prop.
446
+ *
447
+ * Why absolutise at the point of consumption rather than at write time: the
448
+ * stored URL has to survive the site moving. GridFS media URLs are persisted
449
+ * into `nbk_media` documents and into the body of every post that embeds the
450
+ * image, so anything baked into them is permanent — a localhost or staging
451
+ * origin captured at upload time would follow the content to production with
452
+ * no way back short of a database rewrite. `BLOGPANEL_SITE_URL` legitimately
453
+ * differs between environments; the storage key does not. Keeping stored URLs
454
+ * origin-relative and prefixing them in the handful of places that require an
455
+ * absolute URL (Open Graph, Twitter cards, JSON-LD, RSS enclosures) keeps the
456
+ * mutable part mutable.
457
+ */
458
+ /** True for `https://…`, `data:…`, `//cdn…` — anything already resolvable without a base. */
459
+ declare function isAbsoluteUrl(url: string): boolean;
460
+ /**
461
+ * Returns `url` as an absolute URL, or `undefined` when it cannot be made one.
462
+ *
463
+ * Already-absolute inputs (R2 public URLs, `data:` URIs, protocol-relative
464
+ * hosts) are returned untouched. Root-relative inputs (the GridFS default,
465
+ * `/api/blog/media/file/…`) are prefixed with `siteUrl`. When `siteUrl` is
466
+ * empty the result is `undefined` — callers must then omit the field, which
467
+ * is the only honest option: emitting the bare relative path produces a
468
+ * malformed `og:image`/`<enclosure url>` that consumers resolve against their
469
+ * own origin, and emitting a half-formed absolute URL is worse still.
470
+ *
471
+ * Pass `context` to have the omission reported once per process.
472
+ */
473
+ declare function absoluteUrl(url: string | null | undefined, siteUrl: string | null | undefined, context?: string): string | undefined;
474
+
475
+ export { searchPosts as $, getCategoryBySlug as A, BUILT_IN_PROVIDER_NAMES as B, ConfigNotInstalledError as C, getCollection as D, getConfig as E, getDb as F, GridFSStorageProvider as G, getEnvConfig as H, ImageTooLargeError as I, getPostById as J, getPostBySlug as K, getSettings as L, type MediaUploadResult as M, getStorageProvider as N, getStorageProviderByName as O, type ProcessedImage as P, isAbsoluteUrl as Q, R2StorageProvider as R, type StorageObject as S, isConfigInstalled as T, isStorageProviderRegistered as U, listCategories as V, listMedia as W, listPosts as X, listStorageProviderNames as Y, processImage as Z, registerStorageProvider as _, type StorageProvider as a, setConfig as a0, unregisterStorageProvider as a1, updateCategory as a2, updatePost as a3, updateSettings as a4, isR2Configured as a5, StorageProviderNotRegisteredError as b, absoluteUrl as c, calculateReadingTime as d, calculateSEOScore as e, countWords as f, createCategory as g, createMedia as h, createPost as i, defineConfig as j, deleteCategory as k, deleteMedia as l, deletePost as m, ensureIndexes as n, ensureUniqueSlug as o, extractTextFromBlocks as p, extractTextFromHTML as q, generateBreadcrumbs as r, generateFAQStructuredData as s, generateKey as t, generateMetaTags as u, generateRSSFeed as v, generateSitemap as w, generateSlug as x, generateStructuredData as y, getBlogConfig as z };