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.
Files changed (107) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +951 -0
  3. package/dist/admin/index.cjs +2465 -0
  4. package/dist/admin/index.cjs.map +1 -0
  5. package/dist/admin/index.d.cts +44 -0
  6. package/dist/admin/index.d.ts +44 -0
  7. package/dist/admin/index.js +2438 -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.cjs +113 -0
  16. package/dist/api/media.cjs.map +1 -0
  17. package/dist/api/media.d.cts +22 -0
  18. package/dist/api/media.d.ts +22 -0
  19. package/dist/api/media.js +109 -0
  20. package/dist/api/media.js.map +1 -0
  21. package/dist/api/posts.cjs +103 -0
  22. package/dist/api/posts.cjs.map +1 -0
  23. package/dist/api/posts.d.cts +27 -0
  24. package/dist/api/posts.d.ts +27 -0
  25. package/dist/api/posts.js +98 -0
  26. package/dist/api/posts.js.map +1 -0
  27. package/dist/api/rss.cjs +25 -0
  28. package/dist/api/rss.cjs.map +1 -0
  29. package/dist/api/rss.d.cts +5 -0
  30. package/dist/api/rss.d.ts +5 -0
  31. package/dist/api/rss.js +23 -0
  32. package/dist/api/rss.js.map +1 -0
  33. package/dist/api/settings.cjs +40 -0
  34. package/dist/api/settings.cjs.map +1 -0
  35. package/dist/api/settings.d.cts +17 -0
  36. package/dist/api/settings.d.ts +17 -0
  37. package/dist/api/settings.js +37 -0
  38. package/dist/api/settings.js.map +1 -0
  39. package/dist/api/sitemap.cjs +25 -0
  40. package/dist/api/sitemap.cjs.map +1 -0
  41. package/dist/api/sitemap.d.cts +5 -0
  42. package/dist/api/sitemap.d.ts +5 -0
  43. package/dist/api/sitemap.js +23 -0
  44. package/dist/api/sitemap.js.map +1 -0
  45. package/dist/chunk-4NKOJYWJ.js +68 -0
  46. package/dist/chunk-4NKOJYWJ.js.map +1 -0
  47. package/dist/chunk-4PY224XM.js +103 -0
  48. package/dist/chunk-4PY224XM.js.map +1 -0
  49. package/dist/chunk-64HUVJOZ.js +446 -0
  50. package/dist/chunk-64HUVJOZ.js.map +1 -0
  51. package/dist/chunk-6HKMZOI4.cjs +48 -0
  52. package/dist/chunk-6HKMZOI4.cjs.map +1 -0
  53. package/dist/chunk-A2S32RZN.js +138 -0
  54. package/dist/chunk-A2S32RZN.js.map +1 -0
  55. package/dist/chunk-E2QLTHKN.cjs +70 -0
  56. package/dist/chunk-E2QLTHKN.cjs.map +1 -0
  57. package/dist/chunk-JLPJKNRZ.js +37 -0
  58. package/dist/chunk-JLPJKNRZ.js.map +1 -0
  59. package/dist/chunk-JM7QRXXK.js +330 -0
  60. package/dist/chunk-JM7QRXXK.js.map +1 -0
  61. package/dist/chunk-KDZER3PU.cjs +43 -0
  62. package/dist/chunk-KDZER3PU.cjs.map +1 -0
  63. package/dist/chunk-N5MKAD7J.cjs +109 -0
  64. package/dist/chunk-N5MKAD7J.cjs.map +1 -0
  65. package/dist/chunk-QE4VLQYN.cjs +337 -0
  66. package/dist/chunk-QE4VLQYN.cjs.map +1 -0
  67. package/dist/chunk-R6MO3QIP.js +46 -0
  68. package/dist/chunk-R6MO3QIP.js.map +1 -0
  69. package/dist/chunk-U2ROR6AY.cjs +476 -0
  70. package/dist/chunk-U2ROR6AY.cjs.map +1 -0
  71. package/dist/chunk-ZP5XRVVH.cjs +141 -0
  72. package/dist/chunk-ZP5XRVVH.cjs.map +1 -0
  73. package/dist/cli/index.cjs +1308 -0
  74. package/dist/components/index.cjs +541 -0
  75. package/dist/components/index.cjs.map +1 -0
  76. package/dist/components/index.d.cts +165 -0
  77. package/dist/components/index.d.ts +165 -0
  78. package/dist/components/index.js +527 -0
  79. package/dist/components/index.js.map +1 -0
  80. package/dist/editor/index.cjs +1083 -0
  81. package/dist/editor/index.cjs.map +1 -0
  82. package/dist/editor/index.d.cts +133 -0
  83. package/dist/editor/index.d.ts +133 -0
  84. package/dist/editor/index.js +1051 -0
  85. package/dist/editor/index.js.map +1 -0
  86. package/dist/index-Cgzphklp.d.ts +266 -0
  87. package/dist/index-vjlZDWNr.d.cts +266 -0
  88. package/dist/index.cjs +368 -0
  89. package/dist/index.cjs.map +1 -0
  90. package/dist/index.d.cts +27 -0
  91. package/dist/index.d.ts +27 -0
  92. package/dist/index.js +208 -0
  93. package/dist/index.js.map +1 -0
  94. package/dist/lib/index.cjs +120 -0
  95. package/dist/lib/index.cjs.map +1 -0
  96. package/dist/lib/index.d.cts +4 -0
  97. package/dist/lib/index.d.ts +4 -0
  98. package/dist/lib/index.js +7 -0
  99. package/dist/lib/index.js.map +1 -0
  100. package/dist/styles/admin.css +657 -0
  101. package/dist/styles/blog.css +851 -0
  102. package/dist/styles/editor.css +452 -0
  103. package/dist/styles/globals.css +270 -0
  104. package/dist/styles/prose.css +299 -0
  105. package/dist/types-CBEEBR4A.d.cts +732 -0
  106. package/dist/types-CBEEBR4A.d.ts +732 -0
  107. package/package.json +134 -0
@@ -0,0 +1,2465 @@
1
+ "use client";
2
+ 'use strict';
3
+
4
+ var react = require('react');
5
+ var jsxRuntime = require('react/jsx-runtime');
6
+ var react$1 = require('@tiptap/react');
7
+ var StarterKit = require('@tiptap/starter-kit');
8
+ var Placeholder = require('@tiptap/extension-placeholder');
9
+ var Link = require('@tiptap/extension-link');
10
+ var Underline = require('@tiptap/extension-underline');
11
+ var Highlight = require('@tiptap/extension-highlight');
12
+ var Typography = require('@tiptap/extension-typography');
13
+ var TaskList = require('@tiptap/extension-task-list');
14
+ var TaskItem = require('@tiptap/extension-task-item');
15
+ var Table = require('@tiptap/extension-table');
16
+ var TableRow = require('@tiptap/extension-table-row');
17
+ var TableCell = require('@tiptap/extension-table-cell');
18
+ var TableHeader = require('@tiptap/extension-table-header');
19
+ var extensionImage = require('@tiptap/extension-image');
20
+ var state = require('@tiptap/pm/state');
21
+ var core = require('@tiptap/core');
22
+ var CodeBlockLowlight = require('@tiptap/extension-code-block');
23
+
24
+ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
25
+
26
+ var StarterKit__default = /*#__PURE__*/_interopDefault(StarterKit);
27
+ var Placeholder__default = /*#__PURE__*/_interopDefault(Placeholder);
28
+ var Link__default = /*#__PURE__*/_interopDefault(Link);
29
+ var Underline__default = /*#__PURE__*/_interopDefault(Underline);
30
+ var Highlight__default = /*#__PURE__*/_interopDefault(Highlight);
31
+ var Typography__default = /*#__PURE__*/_interopDefault(Typography);
32
+ var TaskList__default = /*#__PURE__*/_interopDefault(TaskList);
33
+ var TaskItem__default = /*#__PURE__*/_interopDefault(TaskItem);
34
+ var Table__default = /*#__PURE__*/_interopDefault(Table);
35
+ var TableRow__default = /*#__PURE__*/_interopDefault(TableRow);
36
+ var TableCell__default = /*#__PURE__*/_interopDefault(TableCell);
37
+ var TableHeader__default = /*#__PURE__*/_interopDefault(TableHeader);
38
+ var CodeBlockLowlight__default = /*#__PURE__*/_interopDefault(CodeBlockLowlight);
39
+
40
+ // src/admin/AdminLayout.tsx
41
+ var _apiBase = "/api/blog";
42
+ function setApiBase(path) {
43
+ _apiBase = path;
44
+ }
45
+ function getApiKey() {
46
+ if (typeof window === "undefined") return "";
47
+ return sessionStorage.getItem("nbk_api_key") || "";
48
+ }
49
+ function getApiBase() {
50
+ return _apiBase;
51
+ }
52
+ async function apiRequest(path, options = {}) {
53
+ const apiKey = getApiKey();
54
+ const url = `${getApiBase()}${path}`;
55
+ const headers = {
56
+ ...options.headers
57
+ };
58
+ if (apiKey) {
59
+ headers["Authorization"] = `Bearer ${apiKey}`;
60
+ }
61
+ if (!(options.body instanceof FormData)) {
62
+ headers["Content-Type"] = "application/json";
63
+ }
64
+ const res = await fetch(url, {
65
+ ...options,
66
+ headers
67
+ });
68
+ const data = await res.json();
69
+ if (!data.success) {
70
+ throw new Error(data.error?.message || "API request failed");
71
+ }
72
+ return data;
73
+ }
74
+ function useAdminApi() {
75
+ const get = react.useCallback(async (path) => {
76
+ return apiRequest(path);
77
+ }, []);
78
+ const post = react.useCallback(async (path, body) => {
79
+ return apiRequest(path, {
80
+ method: "POST",
81
+ body: body instanceof FormData ? body : JSON.stringify(body)
82
+ });
83
+ }, []);
84
+ const put = react.useCallback(async (path, body) => {
85
+ return apiRequest(path, {
86
+ method: "PUT",
87
+ body: JSON.stringify(body)
88
+ });
89
+ }, []);
90
+ const del = react.useCallback(async (path) => {
91
+ return apiRequest(path, { method: "DELETE" });
92
+ }, []);
93
+ return { get, post, put, del };
94
+ }
95
+ function buildNavItems(adminPath) {
96
+ return [
97
+ { label: "Dashboard", href: adminPath, icon: "\u{1F4CA}" },
98
+ { label: "Posts", href: `${adminPath}/posts`, icon: "\u{1F4DD}" },
99
+ { label: "New Post", href: `${adminPath}/new`, icon: "\u270F\uFE0F" },
100
+ { label: "Media", href: `${adminPath}/media`, icon: "\u{1F5BC}\uFE0F" },
101
+ { label: "Categories", href: `${adminPath}/categories`, icon: "\u{1F4C1}" },
102
+ { label: "Settings", href: `${adminPath}/settings`, icon: "\u2699\uFE0F" }
103
+ ];
104
+ }
105
+ function AdminLayout({ children, apiKey, apiPath, adminPath = "/admin/blog" }) {
106
+ const [isAuthenticated, setIsAuthenticated] = react.useState(false);
107
+ const [inputKey, setInputKey] = react.useState("");
108
+ const [sidebarOpen, setSidebarOpen] = react.useState(true);
109
+ const [currentPath, setCurrentPath] = react.useState("");
110
+ react.useEffect(() => {
111
+ if (apiPath) {
112
+ setApiBase(apiPath);
113
+ }
114
+ }, [apiPath]);
115
+ react.useEffect(() => {
116
+ if (typeof window !== "undefined") {
117
+ setCurrentPath(window.location.pathname);
118
+ const stored = sessionStorage.getItem("nbk_api_key");
119
+ if (stored || apiKey) {
120
+ setIsAuthenticated(true);
121
+ }
122
+ }
123
+ }, [apiKey]);
124
+ const handleLogin = (e) => {
125
+ e.preventDefault();
126
+ if (inputKey.trim()) {
127
+ sessionStorage.setItem("nbk_api_key", inputKey);
128
+ setIsAuthenticated(true);
129
+ }
130
+ };
131
+ if (!isAuthenticated) {
132
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "nbk-admin-login", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "nbk-login-card", children: [
133
+ /* @__PURE__ */ jsxRuntime.jsx("h1", { className: "nbk-login-title", children: "Blog Admin" }),
134
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "nbk-login-subtitle", children: "Enter your API key to continue" }),
135
+ /* @__PURE__ */ jsxRuntime.jsxs("form", { onSubmit: handleLogin, children: [
136
+ /* @__PURE__ */ jsxRuntime.jsx(
137
+ "input",
138
+ {
139
+ type: "password",
140
+ value: inputKey,
141
+ onChange: (e) => setInputKey(e.target.value),
142
+ placeholder: "Enter API key",
143
+ className: "nbk-login-input",
144
+ autoFocus: true
145
+ }
146
+ ),
147
+ /* @__PURE__ */ jsxRuntime.jsx("button", { type: "submit", className: "nbk-login-btn", children: "Sign In" })
148
+ ] })
149
+ ] }) });
150
+ }
151
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "nbk-admin", children: [
152
+ /* @__PURE__ */ jsxRuntime.jsxs("aside", { className: `nbk-admin-sidebar ${sidebarOpen ? "open" : "closed"}`, children: [
153
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "nbk-sidebar-header", children: [
154
+ /* @__PURE__ */ jsxRuntime.jsx("h2", { className: "nbk-sidebar-title", children: "NextBlogKit" }),
155
+ /* @__PURE__ */ jsxRuntime.jsx(
156
+ "button",
157
+ {
158
+ onClick: () => setSidebarOpen(!sidebarOpen),
159
+ className: "nbk-sidebar-toggle",
160
+ children: sidebarOpen ? "\u2190" : "\u2192"
161
+ }
162
+ )
163
+ ] }),
164
+ /* @__PURE__ */ jsxRuntime.jsx("nav", { className: "nbk-sidebar-nav", children: buildNavItems(adminPath).map((item) => /* @__PURE__ */ jsxRuntime.jsxs(
165
+ "a",
166
+ {
167
+ href: item.href,
168
+ className: `nbk-sidebar-link ${currentPath === item.href ? "active" : ""}`,
169
+ children: [
170
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "nbk-sidebar-icon", children: item.icon }),
171
+ sidebarOpen && /* @__PURE__ */ jsxRuntime.jsx("span", { children: item.label })
172
+ ]
173
+ },
174
+ item.href
175
+ )) }),
176
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "nbk-sidebar-footer", children: /* @__PURE__ */ jsxRuntime.jsxs(
177
+ "button",
178
+ {
179
+ onClick: () => {
180
+ sessionStorage.removeItem("nbk_api_key");
181
+ setIsAuthenticated(false);
182
+ },
183
+ className: "nbk-sidebar-link",
184
+ children: [
185
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "nbk-sidebar-icon", children: "\u{1F6AA}" }),
186
+ sidebarOpen && /* @__PURE__ */ jsxRuntime.jsx("span", { children: "Sign Out" })
187
+ ]
188
+ }
189
+ ) })
190
+ ] }),
191
+ /* @__PURE__ */ jsxRuntime.jsx("main", { className: "nbk-admin-main", children })
192
+ ] });
193
+ }
194
+ function Dashboard() {
195
+ const api = useAdminApi();
196
+ const [stats, setStats] = react.useState({
197
+ totalPosts: 0,
198
+ publishedPosts: 0,
199
+ draftPosts: 0,
200
+ totalMedia: 0,
201
+ totalCategories: 0
202
+ });
203
+ const [recentDrafts, setRecentDrafts] = react.useState([]);
204
+ const [recentPublished, setRecentPublished] = react.useState([]);
205
+ const [loading, setLoading] = react.useState(true);
206
+ react.useEffect(() => {
207
+ async function loadDashboard() {
208
+ try {
209
+ const [allPosts, published, drafts, media, categories] = await Promise.all([
210
+ api.get("/posts?limit=1"),
211
+ api.get("/posts?status=published&limit=5"),
212
+ api.get("/posts?status=draft&limit=5"),
213
+ api.get("/media?limit=1"),
214
+ api.get("/categories")
215
+ ]);
216
+ setStats({
217
+ totalPosts: allPosts.meta?.total || 0,
218
+ publishedPosts: published.meta?.total || 0,
219
+ draftPosts: drafts.meta?.total || 0,
220
+ totalMedia: media.meta?.total || 0,
221
+ totalCategories: Array.isArray(categories.data) ? categories.data.length : 0
222
+ });
223
+ setRecentDrafts(drafts.data || []);
224
+ setRecentPublished(published.data || []);
225
+ } catch (err) {
226
+ console.error("Dashboard load error:", err);
227
+ } finally {
228
+ setLoading(false);
229
+ }
230
+ }
231
+ loadDashboard();
232
+ }, []);
233
+ if (loading) {
234
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "nbk-loading", children: "Loading dashboard..." });
235
+ }
236
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "nbk-dashboard", children: [
237
+ /* @__PURE__ */ jsxRuntime.jsx("h1", { className: "nbk-page-title", children: "Dashboard" }),
238
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "nbk-stats-grid", children: [
239
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "nbk-stat-card", children: [
240
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "nbk-stat-number", children: stats.totalPosts }),
241
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "nbk-stat-label", children: "Total Posts" })
242
+ ] }),
243
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "nbk-stat-card", children: [
244
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "nbk-stat-number", children: stats.publishedPosts }),
245
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "nbk-stat-label", children: "Published" })
246
+ ] }),
247
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "nbk-stat-card", children: [
248
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "nbk-stat-number", children: stats.draftPosts }),
249
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "nbk-stat-label", children: "Drafts" })
250
+ ] }),
251
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "nbk-stat-card", children: [
252
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "nbk-stat-number", children: stats.totalMedia }),
253
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "nbk-stat-label", children: "Media Files" })
254
+ ] }),
255
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "nbk-stat-card", children: [
256
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "nbk-stat-number", children: stats.totalCategories }),
257
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "nbk-stat-label", children: "Categories" })
258
+ ] })
259
+ ] }),
260
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "nbk-dashboard-grid", children: [
261
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "nbk-dashboard-section", children: [
262
+ /* @__PURE__ */ jsxRuntime.jsx("h2", { className: "nbk-section-title", children: "Recent Drafts" }),
263
+ recentDrafts.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "nbk-empty-state", children: "No drafts yet" }) : /* @__PURE__ */ jsxRuntime.jsx("ul", { className: "nbk-post-list-simple", children: recentDrafts.map((post) => /* @__PURE__ */ jsxRuntime.jsx("li", { children: /* @__PURE__ */ jsxRuntime.jsxs("a", { href: `/admin/blog/${post._id}/edit`, className: "nbk-post-link", children: [
264
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "nbk-post-link-title", children: post.title }),
265
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "nbk-post-link-date", children: new Date(post.updatedAt).toLocaleDateString() })
266
+ ] }) }, post._id)) })
267
+ ] }),
268
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "nbk-dashboard-section", children: [
269
+ /* @__PURE__ */ jsxRuntime.jsx("h2", { className: "nbk-section-title", children: "Recently Published" }),
270
+ recentPublished.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "nbk-empty-state", children: "No published posts yet" }) : /* @__PURE__ */ jsxRuntime.jsx("ul", { className: "nbk-post-list-simple", children: recentPublished.map((post) => /* @__PURE__ */ jsxRuntime.jsx("li", { children: /* @__PURE__ */ jsxRuntime.jsxs("a", { href: `/admin/blog/${post._id}/edit`, className: "nbk-post-link", children: [
271
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "nbk-post-link-title", children: post.title }),
272
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "nbk-post-link-date", children: new Date(post.publishedAt || post.createdAt).toLocaleDateString() })
273
+ ] }) }, post._id)) })
274
+ ] })
275
+ ] }),
276
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "nbk-quick-actions", children: [
277
+ /* @__PURE__ */ jsxRuntime.jsx("a", { href: "/admin/blog/new", className: "nbk-btn nbk-btn-primary", children: "New Post" }),
278
+ /* @__PURE__ */ jsxRuntime.jsx("a", { href: "/admin/blog/media", className: "nbk-btn nbk-btn-secondary", children: "Media Library" })
279
+ ] })
280
+ ] });
281
+ }
282
+ function PostList() {
283
+ const api = useAdminApi();
284
+ const [posts, setPosts] = react.useState([]);
285
+ const [total, setTotal] = react.useState(0);
286
+ const [page, setPage] = react.useState(1);
287
+ const [statusFilter, setStatusFilter] = react.useState("");
288
+ const [searchQuery, setSearchQuery] = react.useState("");
289
+ const [loading, setLoading] = react.useState(true);
290
+ const [selected, setSelected] = react.useState(/* @__PURE__ */ new Set());
291
+ const limit = 20;
292
+ const loadPosts = react.useCallback(async () => {
293
+ setLoading(true);
294
+ try {
295
+ let path = `/posts?page=${page}&limit=${limit}`;
296
+ if (statusFilter) path += `&status=${statusFilter}`;
297
+ if (searchQuery) path += `&q=${encodeURIComponent(searchQuery)}`;
298
+ const res = await api.get(path);
299
+ setPosts(res.data || []);
300
+ setTotal(res.meta?.total || 0);
301
+ } catch (err) {
302
+ console.error("Failed to load posts:", err);
303
+ } finally {
304
+ setLoading(false);
305
+ }
306
+ }, [page, statusFilter, searchQuery]);
307
+ react.useEffect(() => {
308
+ loadPosts();
309
+ }, [loadPosts]);
310
+ const handleDelete = async (id) => {
311
+ if (!confirm("Are you sure you want to archive this post?")) return;
312
+ try {
313
+ await api.del(`/posts?id=${id}`);
314
+ loadPosts();
315
+ } catch (err) {
316
+ console.error("Delete failed:", err);
317
+ }
318
+ };
319
+ const handleBulkAction = async (action) => {
320
+ if (selected.size === 0) return;
321
+ for (const id of selected) {
322
+ try {
323
+ if (action === "publish") {
324
+ await api.put(`/posts?id=${id}`, { status: "published" });
325
+ } else if (action === "draft") {
326
+ await api.put(`/posts?id=${id}`, { status: "draft" });
327
+ } else if (action === "archive") {
328
+ await api.del(`/posts?id=${id}`);
329
+ }
330
+ } catch (err) {
331
+ console.error(`Bulk ${action} failed for ${id}:`, err);
332
+ }
333
+ }
334
+ setSelected(/* @__PURE__ */ new Set());
335
+ loadPosts();
336
+ };
337
+ const totalPages = Math.ceil(total / limit);
338
+ const statusBadge = (status) => {
339
+ const colors = {
340
+ published: "nbk-badge-green",
341
+ draft: "nbk-badge-yellow",
342
+ scheduled: "nbk-badge-blue",
343
+ archived: "nbk-badge-gray"
344
+ };
345
+ return /* @__PURE__ */ jsxRuntime.jsx("span", { className: `nbk-badge ${colors[status] || "nbk-badge-gray"}`, children: status });
346
+ };
347
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "nbk-post-list", children: [
348
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "nbk-page-header", children: [
349
+ /* @__PURE__ */ jsxRuntime.jsx("h1", { className: "nbk-page-title", children: "Posts" }),
350
+ /* @__PURE__ */ jsxRuntime.jsx("a", { href: "/admin/blog/new", className: "nbk-btn nbk-btn-primary", children: "New Post" })
351
+ ] }),
352
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "nbk-filters", children: [
353
+ /* @__PURE__ */ jsxRuntime.jsx(
354
+ "input",
355
+ {
356
+ type: "text",
357
+ placeholder: "Search posts...",
358
+ value: searchQuery,
359
+ onChange: (e) => {
360
+ setSearchQuery(e.target.value);
361
+ setPage(1);
362
+ },
363
+ className: "nbk-input nbk-search-input"
364
+ }
365
+ ),
366
+ /* @__PURE__ */ jsxRuntime.jsxs(
367
+ "select",
368
+ {
369
+ value: statusFilter,
370
+ onChange: (e) => {
371
+ setStatusFilter(e.target.value);
372
+ setPage(1);
373
+ },
374
+ className: "nbk-select",
375
+ children: [
376
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "", children: "All Statuses" }),
377
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "published", children: "Published" }),
378
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "draft", children: "Draft" }),
379
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "scheduled", children: "Scheduled" }),
380
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "archived", children: "Archived" })
381
+ ]
382
+ }
383
+ ),
384
+ selected.size > 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "nbk-bulk-actions", children: [
385
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
386
+ selected.size,
387
+ " selected"
388
+ ] }),
389
+ /* @__PURE__ */ jsxRuntime.jsx("button", { onClick: () => handleBulkAction("publish"), className: "nbk-btn nbk-btn-sm", children: "Publish" }),
390
+ /* @__PURE__ */ jsxRuntime.jsx("button", { onClick: () => handleBulkAction("draft"), className: "nbk-btn nbk-btn-sm", children: "Unpublish" }),
391
+ /* @__PURE__ */ jsxRuntime.jsx("button", { onClick: () => handleBulkAction("archive"), className: "nbk-btn nbk-btn-sm nbk-btn-danger", children: "Archive" })
392
+ ] })
393
+ ] }),
394
+ loading ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "nbk-loading", children: "Loading posts..." }) : posts.length === 0 ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "nbk-empty-state", children: [
395
+ /* @__PURE__ */ jsxRuntime.jsx("p", { children: "No posts found." }),
396
+ /* @__PURE__ */ jsxRuntime.jsx("a", { href: "/admin/blog/new", className: "nbk-btn nbk-btn-primary", children: "Create your first post" })
397
+ ] }) : /* @__PURE__ */ jsxRuntime.jsxs("table", { className: "nbk-table", children: [
398
+ /* @__PURE__ */ jsxRuntime.jsx("thead", { children: /* @__PURE__ */ jsxRuntime.jsxs("tr", { children: [
399
+ /* @__PURE__ */ jsxRuntime.jsx("th", { className: "nbk-th-checkbox", children: /* @__PURE__ */ jsxRuntime.jsx(
400
+ "input",
401
+ {
402
+ type: "checkbox",
403
+ checked: selected.size === posts.length,
404
+ onChange: (e) => {
405
+ if (e.target.checked) {
406
+ setSelected(new Set(posts.map((p) => p._id)));
407
+ } else {
408
+ setSelected(/* @__PURE__ */ new Set());
409
+ }
410
+ }
411
+ }
412
+ ) }),
413
+ /* @__PURE__ */ jsxRuntime.jsx("th", { children: "Title" }),
414
+ /* @__PURE__ */ jsxRuntime.jsx("th", { children: "Status" }),
415
+ /* @__PURE__ */ jsxRuntime.jsx("th", { children: "Categories" }),
416
+ /* @__PURE__ */ jsxRuntime.jsx("th", { children: "Words" }),
417
+ /* @__PURE__ */ jsxRuntime.jsx("th", { children: "Date" }),
418
+ /* @__PURE__ */ jsxRuntime.jsx("th", { children: "Actions" })
419
+ ] }) }),
420
+ /* @__PURE__ */ jsxRuntime.jsx("tbody", { children: posts.map((post) => /* @__PURE__ */ jsxRuntime.jsxs("tr", { children: [
421
+ /* @__PURE__ */ jsxRuntime.jsx("td", { children: /* @__PURE__ */ jsxRuntime.jsx(
422
+ "input",
423
+ {
424
+ type: "checkbox",
425
+ checked: selected.has(post._id),
426
+ onChange: (e) => {
427
+ const next = new Set(selected);
428
+ if (e.target.checked) next.add(post._id);
429
+ else next.delete(post._id);
430
+ setSelected(next);
431
+ }
432
+ }
433
+ ) }),
434
+ /* @__PURE__ */ jsxRuntime.jsxs("td", { children: [
435
+ /* @__PURE__ */ jsxRuntime.jsx("a", { href: `/admin/blog/${post._id}/edit`, className: "nbk-post-title-link", children: post.title }),
436
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "nbk-post-slug", children: [
437
+ "/",
438
+ post.slug
439
+ ] })
440
+ ] }),
441
+ /* @__PURE__ */ jsxRuntime.jsx("td", { children: statusBadge(post.status) }),
442
+ /* @__PURE__ */ jsxRuntime.jsx("td", { children: post.categories.join(", ") || "\u2014" }),
443
+ /* @__PURE__ */ jsxRuntime.jsx("td", { children: post.wordCount }),
444
+ /* @__PURE__ */ jsxRuntime.jsx("td", { children: new Date(
445
+ post.publishedAt || post.updatedAt
446
+ ).toLocaleDateString() }),
447
+ /* @__PURE__ */ jsxRuntime.jsx("td", { children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "nbk-actions", children: [
448
+ /* @__PURE__ */ jsxRuntime.jsx("a", { href: `/admin/blog/${post._id}/edit`, className: "nbk-btn nbk-btn-sm", children: "Edit" }),
449
+ /* @__PURE__ */ jsxRuntime.jsx(
450
+ "a",
451
+ {
452
+ href: `/blog/${post.slug}`,
453
+ target: "_blank",
454
+ rel: "noopener noreferrer",
455
+ className: "nbk-btn nbk-btn-sm",
456
+ children: "View"
457
+ }
458
+ ),
459
+ /* @__PURE__ */ jsxRuntime.jsx(
460
+ "button",
461
+ {
462
+ onClick: () => handleDelete(post._id),
463
+ className: "nbk-btn nbk-btn-sm nbk-btn-danger",
464
+ children: "Delete"
465
+ }
466
+ )
467
+ ] }) })
468
+ ] }, post._id)) })
469
+ ] }),
470
+ totalPages > 1 && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "nbk-pagination-admin", children: [
471
+ /* @__PURE__ */ jsxRuntime.jsx(
472
+ "button",
473
+ {
474
+ onClick: () => setPage(page - 1),
475
+ disabled: page <= 1,
476
+ className: "nbk-btn nbk-btn-sm",
477
+ children: "Previous"
478
+ }
479
+ ),
480
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
481
+ "Page ",
482
+ page,
483
+ " of ",
484
+ totalPages,
485
+ " (",
486
+ total,
487
+ " posts)"
488
+ ] }),
489
+ /* @__PURE__ */ jsxRuntime.jsx(
490
+ "button",
491
+ {
492
+ onClick: () => setPage(page + 1),
493
+ disabled: page >= totalPages,
494
+ className: "nbk-btn nbk-btn-sm",
495
+ children: "Next"
496
+ }
497
+ )
498
+ ] })
499
+ ] });
500
+ }
501
+ var ImageUpload = extensionImage.Image.extend({
502
+ addOptions() {
503
+ return {
504
+ ...this.parent?.(),
505
+ uploadFn: async (_file) => ({ url: "" }),
506
+ maxSize: 10 * 1024 * 1024,
507
+ allowedTypes: ["image/jpeg", "image/png", "image/gif", "image/webp", "image/svg+xml"]
508
+ };
509
+ },
510
+ addAttributes() {
511
+ return {
512
+ ...this.parent?.(),
513
+ loading: {
514
+ default: false,
515
+ renderHTML: (attributes) => {
516
+ if (!attributes.loading) return {};
517
+ return { "data-loading": "true" };
518
+ }
519
+ },
520
+ width: { default: null },
521
+ height: { default: null },
522
+ caption: {
523
+ default: null,
524
+ renderHTML: (attributes) => {
525
+ if (!attributes.caption) return {};
526
+ return { "data-caption": attributes.caption };
527
+ }
528
+ }
529
+ };
530
+ },
531
+ addCommands() {
532
+ return {
533
+ ...this.parent?.(),
534
+ uploadImage: (file) => ({ commands, editor }) => {
535
+ const opts = this.options;
536
+ const { uploadFn, maxSize, allowedTypes } = opts;
537
+ if (maxSize && file.size > maxSize) {
538
+ console.error(`File too large: ${file.size} > ${maxSize}`);
539
+ return false;
540
+ }
541
+ if (allowedTypes && !allowedTypes.includes(file.type)) {
542
+ console.error(`File type not allowed: ${file.type}`);
543
+ return false;
544
+ }
545
+ const placeholderUrl = URL.createObjectURL(file);
546
+ commands.insertContent({
547
+ type: "image",
548
+ attrs: { src: placeholderUrl, loading: true, alt: file.name }
549
+ });
550
+ uploadFn(file).then((result) => {
551
+ const { state } = editor;
552
+ const { doc } = state;
553
+ let pos = null;
554
+ doc.descendants((node, nodePos) => {
555
+ if (node.type.name === "image" && node.attrs.src === placeholderUrl) {
556
+ pos = nodePos;
557
+ return false;
558
+ }
559
+ });
560
+ if (pos !== null) {
561
+ editor.chain().focus().setNodeSelection(pos).updateAttributes("image", {
562
+ src: result.url,
563
+ alt: result.alt || file.name,
564
+ loading: false
565
+ }).run();
566
+ }
567
+ URL.revokeObjectURL(placeholderUrl);
568
+ }).catch((err) => {
569
+ console.error("Image upload failed:", err);
570
+ URL.revokeObjectURL(placeholderUrl);
571
+ });
572
+ return true;
573
+ }
574
+ };
575
+ },
576
+ addProseMirrorPlugins() {
577
+ const opts = this.options;
578
+ const { uploadFn, maxSize, allowedTypes } = opts;
579
+ const editorRef = this.editor;
580
+ return [
581
+ new state.Plugin({
582
+ key: new state.PluginKey("imageUploadDrop"),
583
+ props: {
584
+ handleDOMEvents: {
585
+ drop(view, event) {
586
+ const files = event.dataTransfer?.files;
587
+ if (!files || files.length === 0) return false;
588
+ const imageFiles = Array.from(files).filter(
589
+ (f) => (allowedTypes || []).includes(f.type)
590
+ );
591
+ if (imageFiles.length === 0) return false;
592
+ event.preventDefault();
593
+ for (const file of imageFiles) {
594
+ if (maxSize && file.size > maxSize) continue;
595
+ editorRef.commands.uploadImage(file);
596
+ }
597
+ return true;
598
+ },
599
+ paste(view, event) {
600
+ const files = event.clipboardData?.files;
601
+ if (!files || files.length === 0) return false;
602
+ const imageFiles = Array.from(files).filter(
603
+ (f) => (allowedTypes || []).includes(f.type)
604
+ );
605
+ if (imageFiles.length === 0) return false;
606
+ event.preventDefault();
607
+ for (const file of imageFiles) {
608
+ if (maxSize && file.size > maxSize) continue;
609
+ editorRef.commands.uploadImage(file);
610
+ }
611
+ return true;
612
+ }
613
+ }
614
+ }
615
+ })
616
+ ];
617
+ }
618
+ });
619
+ var Callout = core.Node.create({
620
+ name: "callout",
621
+ group: "block",
622
+ content: "block+",
623
+ defining: true,
624
+ addAttributes() {
625
+ return {
626
+ type: {
627
+ default: "info",
628
+ parseHTML: (element) => element.getAttribute("data-callout-type") || "info",
629
+ renderHTML: (attributes) => ({
630
+ "data-callout-type": attributes.type
631
+ })
632
+ }
633
+ };
634
+ },
635
+ parseHTML() {
636
+ return [{ tag: "div[data-callout]" }];
637
+ },
638
+ renderHTML({ HTMLAttributes }) {
639
+ return [
640
+ "div",
641
+ core.mergeAttributes(HTMLAttributes, { "data-callout": "", class: `nbk-callout nbk-callout-${HTMLAttributes["data-callout-type"] || "info"}` }),
642
+ 0
643
+ ];
644
+ },
645
+ addCommands() {
646
+ return {
647
+ setCallout: (attrs) => ({ commands }) => {
648
+ return commands.wrapIn(this.name, attrs);
649
+ },
650
+ toggleCallout: (attrs) => ({ commands }) => {
651
+ return commands.toggleWrap(this.name, attrs);
652
+ }
653
+ };
654
+ }
655
+ });
656
+ var FAQItem = core.Node.create({
657
+ name: "faqItem",
658
+ group: "block",
659
+ content: "faqQuestion faqAnswer",
660
+ defining: true,
661
+ parseHTML() {
662
+ return [{ tag: "div[data-faq-item]" }];
663
+ },
664
+ renderHTML({ HTMLAttributes }) {
665
+ return ["div", core.mergeAttributes(HTMLAttributes, { "data-faq-item": "", class: "nbk-faq-item" }), 0];
666
+ }
667
+ });
668
+ var FAQQuestion = core.Node.create({
669
+ name: "faqQuestion",
670
+ content: "inline*",
671
+ defining: true,
672
+ parseHTML() {
673
+ return [{ tag: "div[data-faq-question]" }];
674
+ },
675
+ renderHTML({ HTMLAttributes }) {
676
+ return ["div", core.mergeAttributes(HTMLAttributes, { "data-faq-question": "", class: "nbk-faq-question" }), 0];
677
+ }
678
+ });
679
+ var FAQAnswer = core.Node.create({
680
+ name: "faqAnswer",
681
+ content: "block+",
682
+ defining: true,
683
+ parseHTML() {
684
+ return [{ tag: "div[data-faq-answer]" }];
685
+ },
686
+ renderHTML({ HTMLAttributes }) {
687
+ return ["div", core.mergeAttributes(HTMLAttributes, { "data-faq-answer": "", class: "nbk-faq-answer" }), 0];
688
+ }
689
+ });
690
+ var FAQ = core.Node.create({
691
+ name: "faq",
692
+ group: "block",
693
+ content: "faqItem+",
694
+ defining: true,
695
+ parseHTML() {
696
+ return [{ tag: "div[data-faq]" }];
697
+ },
698
+ renderHTML({ HTMLAttributes }) {
699
+ return ["div", core.mergeAttributes(HTMLAttributes, { "data-faq": "", class: "nbk-faq" }), 0];
700
+ },
701
+ addCommands() {
702
+ return {
703
+ insertFAQ: () => ({ chain }) => {
704
+ return chain().insertContent({
705
+ type: "faq",
706
+ content: [
707
+ {
708
+ type: "faqItem",
709
+ content: [
710
+ { type: "faqQuestion", content: [{ type: "text", text: "Question?" }] },
711
+ { type: "faqAnswer", content: [{ type: "paragraph", content: [{ type: "text", text: "Answer." }] }] }
712
+ ]
713
+ }
714
+ ]
715
+ }).run();
716
+ }
717
+ };
718
+ }
719
+ });
720
+ var TableOfContents = core.Node.create({
721
+ name: "tableOfContents",
722
+ group: "block",
723
+ atom: true,
724
+ parseHTML() {
725
+ return [{ tag: "div[data-toc]" }];
726
+ },
727
+ renderHTML({ HTMLAttributes }) {
728
+ return [
729
+ "div",
730
+ core.mergeAttributes(HTMLAttributes, {
731
+ "data-toc": "",
732
+ class: "nbk-toc-placeholder"
733
+ }),
734
+ "Table of Contents (auto-generated)"
735
+ ];
736
+ },
737
+ addCommands() {
738
+ return {
739
+ insertTableOfContents: () => ({ commands }) => {
740
+ return commands.insertContent({ type: this.name });
741
+ }
742
+ };
743
+ }
744
+ });
745
+ var CodeBlockEnhanced = CodeBlockLowlight__default.default.extend({
746
+ addAttributes() {
747
+ return {
748
+ ...this.parent?.(),
749
+ language: {
750
+ default: "plaintext",
751
+ parseHTML: (element) => element.getAttribute("data-language") || element.querySelector("code")?.className?.replace("language-", "") || "plaintext",
752
+ renderHTML: (attributes) => ({
753
+ "data-language": attributes.language
754
+ })
755
+ },
756
+ filename: {
757
+ default: null,
758
+ parseHTML: (element) => element.getAttribute("data-filename"),
759
+ renderHTML: (attributes) => {
760
+ if (!attributes.filename) return {};
761
+ return { "data-filename": attributes.filename };
762
+ }
763
+ }
764
+ };
765
+ }
766
+ });
767
+ var defaultSlashCommands = [
768
+ {
769
+ title: "Heading 2",
770
+ description: "Large section heading",
771
+ icon: "H2",
772
+ command: (editor) => editor.chain().focus().toggleHeading({ level: 2 }).run()
773
+ },
774
+ {
775
+ title: "Heading 3",
776
+ description: "Medium section heading",
777
+ icon: "H3",
778
+ command: (editor) => editor.chain().focus().toggleHeading({ level: 3 }).run()
779
+ },
780
+ {
781
+ title: "Heading 4",
782
+ description: "Small section heading",
783
+ icon: "H4",
784
+ command: (editor) => editor.chain().focus().toggleHeading({ level: 4 }).run()
785
+ },
786
+ {
787
+ title: "Bullet List",
788
+ description: "Create a simple bullet list",
789
+ icon: "\u2022",
790
+ command: (editor) => editor.chain().focus().toggleBulletList().run()
791
+ },
792
+ {
793
+ title: "Numbered List",
794
+ description: "Create a numbered list",
795
+ icon: "1.",
796
+ command: (editor) => editor.chain().focus().toggleOrderedList().run()
797
+ },
798
+ {
799
+ title: "Task List",
800
+ description: "Create a checklist",
801
+ icon: "\u2611",
802
+ command: (editor) => editor.chain().focus().toggleTaskList().run()
803
+ },
804
+ {
805
+ title: "Blockquote",
806
+ description: "Add a quote block",
807
+ icon: '"',
808
+ command: (editor) => editor.chain().focus().toggleBlockquote().run()
809
+ },
810
+ {
811
+ title: "Code Block",
812
+ description: "Add a code snippet",
813
+ icon: "</>",
814
+ command: (editor) => editor.chain().focus().toggleCodeBlock().run()
815
+ },
816
+ {
817
+ title: "Divider",
818
+ description: "Add a horizontal divider",
819
+ icon: "\u2014",
820
+ command: (editor) => editor.chain().focus().setHorizontalRule().run()
821
+ },
822
+ {
823
+ title: "Image",
824
+ description: "Upload or embed an image",
825
+ icon: "\u{1F5BC}",
826
+ command: (editor) => {
827
+ const input = document.createElement("input");
828
+ input.type = "file";
829
+ input.accept = "image/*";
830
+ input.onchange = () => {
831
+ const file = input.files?.[0];
832
+ if (file) {
833
+ editor.commands.uploadImage(file);
834
+ }
835
+ };
836
+ input.click();
837
+ }
838
+ },
839
+ {
840
+ title: "Table",
841
+ description: "Add a table",
842
+ icon: "\u229E",
843
+ command: (editor) => editor.chain().focus().insertTable({ rows: 3, cols: 3, withHeaderRow: true }).run()
844
+ },
845
+ {
846
+ title: "Callout",
847
+ description: "Add an info callout box",
848
+ icon: "\u2139",
849
+ command: (editor) => editor.chain().focus().setCallout({ type: "info" }).run()
850
+ },
851
+ {
852
+ title: "FAQ",
853
+ description: "Add a FAQ section",
854
+ icon: "?",
855
+ command: (editor) => editor.chain().focus().insertFAQ().run()
856
+ },
857
+ {
858
+ title: "Table of Contents",
859
+ description: "Auto-generated from headings",
860
+ icon: "\u2261",
861
+ command: (editor) => editor.chain().focus().insertTableOfContents().run()
862
+ }
863
+ ];
864
+ var SlashCommand = core.Extension.create({
865
+ name: "slashCommand",
866
+ addOptions() {
867
+ return {
868
+ commands: defaultSlashCommands,
869
+ onStateChange: (_state) => {
870
+ }
871
+ };
872
+ },
873
+ addStorage() {
874
+ return {
875
+ deleteSlashAndRun: (_item) => {
876
+ }
877
+ };
878
+ },
879
+ addProseMirrorPlugins() {
880
+ const { commands, onStateChange } = this.options;
881
+ const editorRef = this.editor;
882
+ const storage = this.editor.storage.slashCommand;
883
+ let state$1 = {
884
+ isOpen: false,
885
+ query: "",
886
+ position: null,
887
+ selectedIndex: 0,
888
+ items: commands
889
+ };
890
+ function updateState(partial) {
891
+ state$1 = { ...state$1, ...partial };
892
+ onStateChange(state$1);
893
+ }
894
+ function deleteSlashText(view) {
895
+ const { $from } = view.state.selection;
896
+ const textBefore = $from.parent.textContent.slice(0, $from.parentOffset);
897
+ const slashIndex = textBefore.lastIndexOf("/");
898
+ if (slashIndex >= 0) {
899
+ const start = $from.start() + slashIndex;
900
+ const end = $from.pos;
901
+ view.dispatch(view.state.tr.delete(start, end));
902
+ }
903
+ }
904
+ storage.deleteSlashAndRun = (item) => {
905
+ deleteSlashText(editorRef.view);
906
+ item.command(editorRef);
907
+ updateState({ isOpen: false, query: "", selectedIndex: 0 });
908
+ };
909
+ return [
910
+ new state.Plugin({
911
+ key: new state.PluginKey("slashCommand"),
912
+ props: {
913
+ handleKeyDown(view, event) {
914
+ if (!state$1.isOpen) {
915
+ return false;
916
+ }
917
+ if (event.key === "ArrowDown") {
918
+ event.preventDefault();
919
+ updateState({
920
+ selectedIndex: (state$1.selectedIndex + 1) % state$1.items.length
921
+ });
922
+ return true;
923
+ }
924
+ if (event.key === "ArrowUp") {
925
+ event.preventDefault();
926
+ updateState({
927
+ selectedIndex: (state$1.selectedIndex - 1 + state$1.items.length) % state$1.items.length
928
+ });
929
+ return true;
930
+ }
931
+ if (event.key === "Enter") {
932
+ event.preventDefault();
933
+ const item = state$1.items[state$1.selectedIndex];
934
+ if (item) {
935
+ deleteSlashText(view);
936
+ item.command(editorRef);
937
+ }
938
+ updateState({ isOpen: false, query: "", selectedIndex: 0 });
939
+ return true;
940
+ }
941
+ if (event.key === "Escape") {
942
+ updateState({ isOpen: false, query: "", selectedIndex: 0 });
943
+ return true;
944
+ }
945
+ return false;
946
+ },
947
+ handleTextInput(view, from, _to, text) {
948
+ const { $from } = view.state.selection;
949
+ const textBefore = $from.parent.textContent.slice(0, $from.parentOffset) + text;
950
+ const slashIndex = textBefore.lastIndexOf("/");
951
+ if (slashIndex >= 0) {
952
+ const query = textBefore.slice(slashIndex + 1).toLowerCase();
953
+ const filtered = commands.filter(
954
+ (cmd) => cmd.title.toLowerCase().includes(query) || cmd.description.toLowerCase().includes(query)
955
+ );
956
+ if (filtered.length > 0) {
957
+ const coords = view.coordsAtPos(from);
958
+ updateState({
959
+ isOpen: true,
960
+ query,
961
+ position: { top: coords.bottom + 4, left: coords.left },
962
+ items: filtered,
963
+ selectedIndex: 0
964
+ });
965
+ } else {
966
+ updateState({ isOpen: false, query: "", selectedIndex: 0 });
967
+ }
968
+ } else if (state$1.isOpen) {
969
+ updateState({ isOpen: false, query: "", selectedIndex: 0 });
970
+ }
971
+ return false;
972
+ }
973
+ }
974
+ })
975
+ ];
976
+ }
977
+ });
978
+ function BlogEditor({
979
+ content,
980
+ onChange,
981
+ onSave,
982
+ uploadImage,
983
+ placeholder = 'Start writing your post... Type "/" for commands',
984
+ autosaveInterval = 3e4,
985
+ className = ""
986
+ }) {
987
+ const [slashState, setSlashState] = react.useState({
988
+ isOpen: false,
989
+ query: "",
990
+ position: null,
991
+ selectedIndex: 0,
992
+ items: []
993
+ });
994
+ const [wordCount, setWordCount] = react.useState(0);
995
+ const [isSaving, setIsSaving] = react.useState(false);
996
+ const autosaveTimerRef = react.useRef(null);
997
+ const lastSavedRef = react.useRef("");
998
+ const defaultUpload = react.useCallback(async (file) => {
999
+ if (!uploadImage) {
1000
+ return { url: URL.createObjectURL(file), alt: file.name };
1001
+ }
1002
+ return uploadImage(file);
1003
+ }, [uploadImage]);
1004
+ const editor = react$1.useEditor({
1005
+ extensions: [
1006
+ StarterKit__default.default.configure({
1007
+ codeBlock: false,
1008
+ dropcursor: { color: "#2563eb", width: 2 }
1009
+ }),
1010
+ Placeholder__default.default.configure({ placeholder }),
1011
+ Link__default.default.configure({ openOnClick: false, HTMLAttributes: { class: "nbk-link" } }),
1012
+ Underline__default.default,
1013
+ Highlight__default.default.configure({ multicolor: false }),
1014
+ Typography__default.default,
1015
+ TaskList__default.default,
1016
+ TaskItem__default.default.configure({ nested: true }),
1017
+ Table__default.default.configure({ resizable: true }),
1018
+ TableRow__default.default,
1019
+ TableCell__default.default,
1020
+ TableHeader__default.default,
1021
+ ImageUpload.configure({ uploadFn: defaultUpload }),
1022
+ CodeBlockEnhanced,
1023
+ Callout,
1024
+ FAQ,
1025
+ FAQItem,
1026
+ FAQQuestion,
1027
+ FAQAnswer,
1028
+ TableOfContents,
1029
+ SlashCommand.configure({
1030
+ onStateChange: setSlashState
1031
+ })
1032
+ ],
1033
+ content: content || { type: "doc", content: [{ type: "paragraph" }] },
1034
+ onUpdate: ({ editor: editor2 }) => {
1035
+ const json = editor2.getJSON();
1036
+ onChange?.(json);
1037
+ const text = editor2.getText();
1038
+ setWordCount(text.trim() ? text.trim().split(/\s+/).length : 0);
1039
+ },
1040
+ editorProps: {
1041
+ attributes: {
1042
+ class: `nbk-editor-content ${className}`
1043
+ }
1044
+ }
1045
+ });
1046
+ react.useEffect(() => {
1047
+ if (!onSave || !autosaveInterval || !editor) return;
1048
+ autosaveTimerRef.current = setInterval(() => {
1049
+ const json = JSON.stringify(editor.getJSON());
1050
+ if (json !== lastSavedRef.current) {
1051
+ setIsSaving(true);
1052
+ onSave(editor.getJSON());
1053
+ lastSavedRef.current = json;
1054
+ setTimeout(() => setIsSaving(false), 1e3);
1055
+ }
1056
+ }, autosaveInterval);
1057
+ return () => {
1058
+ if (autosaveTimerRef.current) clearInterval(autosaveTimerRef.current);
1059
+ };
1060
+ }, [editor, onSave, autosaveInterval]);
1061
+ const readingTime = Math.max(1, Math.ceil(wordCount / 200));
1062
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "nbk-editor", children: [
1063
+ editor && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "nbk-editor-toolbar", children: [
1064
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "nbk-toolbar-group", children: [
1065
+ /* @__PURE__ */ jsxRuntime.jsx(
1066
+ "button",
1067
+ {
1068
+ onClick: () => editor.chain().focus().toggleBold().run(),
1069
+ className: editor.isActive("bold") ? "nbk-toolbar-btn active" : "nbk-toolbar-btn",
1070
+ title: "Bold",
1071
+ children: /* @__PURE__ */ jsxRuntime.jsx("strong", { children: "B" })
1072
+ }
1073
+ ),
1074
+ /* @__PURE__ */ jsxRuntime.jsx(
1075
+ "button",
1076
+ {
1077
+ onClick: () => editor.chain().focus().toggleItalic().run(),
1078
+ className: editor.isActive("italic") ? "nbk-toolbar-btn active" : "nbk-toolbar-btn",
1079
+ title: "Italic",
1080
+ children: /* @__PURE__ */ jsxRuntime.jsx("em", { children: "I" })
1081
+ }
1082
+ ),
1083
+ /* @__PURE__ */ jsxRuntime.jsx(
1084
+ "button",
1085
+ {
1086
+ onClick: () => editor.chain().focus().toggleUnderline().run(),
1087
+ className: editor.isActive("underline") ? "nbk-toolbar-btn active" : "nbk-toolbar-btn",
1088
+ title: "Underline",
1089
+ children: /* @__PURE__ */ jsxRuntime.jsx("u", { children: "U" })
1090
+ }
1091
+ ),
1092
+ /* @__PURE__ */ jsxRuntime.jsx(
1093
+ "button",
1094
+ {
1095
+ onClick: () => editor.chain().focus().toggleStrike().run(),
1096
+ className: editor.isActive("strike") ? "nbk-toolbar-btn active" : "nbk-toolbar-btn",
1097
+ title: "Strikethrough",
1098
+ children: /* @__PURE__ */ jsxRuntime.jsx("s", { children: "S" })
1099
+ }
1100
+ ),
1101
+ /* @__PURE__ */ jsxRuntime.jsx(
1102
+ "button",
1103
+ {
1104
+ onClick: () => editor.chain().focus().toggleCode().run(),
1105
+ className: editor.isActive("code") ? "nbk-toolbar-btn active" : "nbk-toolbar-btn",
1106
+ title: "Inline Code",
1107
+ children: "</>"
1108
+ }
1109
+ ),
1110
+ /* @__PURE__ */ jsxRuntime.jsx(
1111
+ "button",
1112
+ {
1113
+ onClick: () => editor.chain().focus().toggleHighlight().run(),
1114
+ className: editor.isActive("highlight") ? "nbk-toolbar-btn active" : "nbk-toolbar-btn",
1115
+ title: "Highlight",
1116
+ children: "H"
1117
+ }
1118
+ )
1119
+ ] }),
1120
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "nbk-toolbar-divider" }),
1121
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "nbk-toolbar-group", children: [2, 3, 4].map((level) => /* @__PURE__ */ jsxRuntime.jsxs(
1122
+ "button",
1123
+ {
1124
+ onClick: () => editor.chain().focus().toggleHeading({ level }).run(),
1125
+ className: editor.isActive("heading", { level }) ? "nbk-toolbar-btn active" : "nbk-toolbar-btn",
1126
+ title: `Heading ${level}`,
1127
+ children: [
1128
+ "H",
1129
+ level
1130
+ ]
1131
+ },
1132
+ level
1133
+ )) }),
1134
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "nbk-toolbar-divider" }),
1135
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "nbk-toolbar-group", children: [
1136
+ /* @__PURE__ */ jsxRuntime.jsx(
1137
+ "button",
1138
+ {
1139
+ onClick: () => editor.chain().focus().toggleBulletList().run(),
1140
+ className: editor.isActive("bulletList") ? "nbk-toolbar-btn active" : "nbk-toolbar-btn",
1141
+ title: "Bullet List",
1142
+ children: "\u2022"
1143
+ }
1144
+ ),
1145
+ /* @__PURE__ */ jsxRuntime.jsx(
1146
+ "button",
1147
+ {
1148
+ onClick: () => editor.chain().focus().toggleOrderedList().run(),
1149
+ className: editor.isActive("orderedList") ? "nbk-toolbar-btn active" : "nbk-toolbar-btn",
1150
+ title: "Numbered List",
1151
+ children: "1."
1152
+ }
1153
+ ),
1154
+ /* @__PURE__ */ jsxRuntime.jsx(
1155
+ "button",
1156
+ {
1157
+ onClick: () => editor.chain().focus().toggleTaskList().run(),
1158
+ className: editor.isActive("taskList") ? "nbk-toolbar-btn active" : "nbk-toolbar-btn",
1159
+ title: "Task List",
1160
+ children: "\u2611"
1161
+ }
1162
+ )
1163
+ ] }),
1164
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "nbk-toolbar-divider" }),
1165
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "nbk-toolbar-group", children: [
1166
+ /* @__PURE__ */ jsxRuntime.jsx(
1167
+ "button",
1168
+ {
1169
+ onClick: () => editor.chain().focus().toggleBlockquote().run(),
1170
+ className: editor.isActive("blockquote") ? "nbk-toolbar-btn active" : "nbk-toolbar-btn",
1171
+ title: "Quote",
1172
+ children: "\u201C"
1173
+ }
1174
+ ),
1175
+ /* @__PURE__ */ jsxRuntime.jsx(
1176
+ "button",
1177
+ {
1178
+ onClick: () => editor.chain().focus().toggleCodeBlock().run(),
1179
+ className: editor.isActive("codeBlock") ? "nbk-toolbar-btn active" : "nbk-toolbar-btn",
1180
+ title: "Code Block",
1181
+ children: "{ }"
1182
+ }
1183
+ ),
1184
+ /* @__PURE__ */ jsxRuntime.jsx(
1185
+ "button",
1186
+ {
1187
+ onClick: () => editor.chain().focus().setHorizontalRule().run(),
1188
+ className: "nbk-toolbar-btn",
1189
+ title: "Divider",
1190
+ children: "\u2014"
1191
+ }
1192
+ ),
1193
+ /* @__PURE__ */ jsxRuntime.jsx(
1194
+ "button",
1195
+ {
1196
+ onClick: () => editor.chain().focus().insertTable({ rows: 3, cols: 3, withHeaderRow: true }).run(),
1197
+ className: "nbk-toolbar-btn",
1198
+ title: "Table",
1199
+ children: "\u229E"
1200
+ }
1201
+ )
1202
+ ] }),
1203
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "nbk-toolbar-divider" }),
1204
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "nbk-toolbar-group", children: [
1205
+ /* @__PURE__ */ jsxRuntime.jsx(
1206
+ "button",
1207
+ {
1208
+ onClick: () => {
1209
+ const url = window.prompt("Enter URL");
1210
+ if (url) editor.chain().focus().setLink({ href: url }).run();
1211
+ },
1212
+ className: editor.isActive("link") ? "nbk-toolbar-btn active" : "nbk-toolbar-btn",
1213
+ title: "Link",
1214
+ children: "\u{1F517}"
1215
+ }
1216
+ ),
1217
+ /* @__PURE__ */ jsxRuntime.jsx(
1218
+ "button",
1219
+ {
1220
+ onClick: () => {
1221
+ const input = document.createElement("input");
1222
+ input.type = "file";
1223
+ input.accept = "image/*";
1224
+ input.onchange = () => {
1225
+ const file = input.files?.[0];
1226
+ if (file) editor.commands.uploadImage(file);
1227
+ };
1228
+ input.click();
1229
+ },
1230
+ className: "nbk-toolbar-btn",
1231
+ title: "Upload Image",
1232
+ children: "\u{1F4F7}"
1233
+ }
1234
+ )
1235
+ ] })
1236
+ ] }),
1237
+ editor && /* @__PURE__ */ jsxRuntime.jsx(react$1.BubbleMenu, { editor, tippyOptions: { duration: 100 }, children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "nbk-bubble-menu", children: [
1238
+ /* @__PURE__ */ jsxRuntime.jsx("button", { onClick: () => editor.chain().focus().toggleBold().run(), className: editor.isActive("bold") ? "active" : "", children: "B" }),
1239
+ /* @__PURE__ */ jsxRuntime.jsx("button", { onClick: () => editor.chain().focus().toggleItalic().run(), className: editor.isActive("italic") ? "active" : "", children: "I" }),
1240
+ /* @__PURE__ */ jsxRuntime.jsx("button", { onClick: () => editor.chain().focus().toggleCode().run(), className: editor.isActive("code") ? "active" : "", children: "</>" }),
1241
+ /* @__PURE__ */ jsxRuntime.jsx(
1242
+ "button",
1243
+ {
1244
+ onClick: () => {
1245
+ const url = window.prompt("Enter URL");
1246
+ if (url) editor.chain().focus().setLink({ href: url }).run();
1247
+ },
1248
+ className: editor.isActive("link") ? "active" : "",
1249
+ children: "Link"
1250
+ }
1251
+ )
1252
+ ] }) }),
1253
+ /* @__PURE__ */ jsxRuntime.jsx(react$1.EditorContent, { editor }),
1254
+ slashState.isOpen && slashState.position && /* @__PURE__ */ jsxRuntime.jsx(
1255
+ "div",
1256
+ {
1257
+ className: "nbk-slash-menu",
1258
+ style: {
1259
+ position: "fixed",
1260
+ top: slashState.position.top,
1261
+ left: slashState.position.left
1262
+ },
1263
+ children: slashState.items.map((item, index) => /* @__PURE__ */ jsxRuntime.jsxs(
1264
+ "button",
1265
+ {
1266
+ className: `nbk-slash-item ${index === slashState.selectedIndex ? "selected" : ""}`,
1267
+ onMouseDown: (e) => {
1268
+ e.preventDefault();
1269
+ editor.storage.slashCommand.deleteSlashAndRun(item);
1270
+ },
1271
+ children: [
1272
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "nbk-slash-icon", children: item.icon }),
1273
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
1274
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "nbk-slash-title", children: item.title }),
1275
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "nbk-slash-desc", children: item.description })
1276
+ ] })
1277
+ ]
1278
+ },
1279
+ item.title
1280
+ ))
1281
+ }
1282
+ ),
1283
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "nbk-editor-status", children: [
1284
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
1285
+ wordCount,
1286
+ " words"
1287
+ ] }),
1288
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
1289
+ readingTime,
1290
+ " min read"
1291
+ ] }),
1292
+ isSaving && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "nbk-saving", children: "Saving..." })
1293
+ ] })
1294
+ ] });
1295
+ }
1296
+
1297
+ // src/editor/renderer.ts
1298
+ function renderBlocksToHTML(doc) {
1299
+ if (!doc.content) return "";
1300
+ return doc.content.map(renderNode).join("");
1301
+ }
1302
+ function renderNode(node) {
1303
+ switch (node.type) {
1304
+ case "paragraph":
1305
+ return `<p>${renderInline(node)}</p>`;
1306
+ case "heading": {
1307
+ const level = node.attrs?.level || 2;
1308
+ const text = renderInline(node);
1309
+ const id = slugify(stripTags(text));
1310
+ return `<h${level} id="${id}">${text}</h${level}>`;
1311
+ }
1312
+ case "bulletList":
1313
+ return `<ul>${renderChildren(node)}</ul>`;
1314
+ case "orderedList":
1315
+ return `<ol>${renderChildren(node)}</ol>`;
1316
+ case "listItem":
1317
+ return `<li>${renderChildren(node)}</li>`;
1318
+ case "taskList":
1319
+ return `<ul class="nbk-task-list">${renderChildren(node)}</ul>`;
1320
+ case "taskItem": {
1321
+ const checked = node.attrs?.checked ? "checked" : "";
1322
+ return `<li class="nbk-task-item" data-checked="${checked}"><input type="checkbox" ${checked} disabled />${renderChildren(node)}</li>`;
1323
+ }
1324
+ case "blockquote":
1325
+ return `<blockquote>${renderChildren(node)}</blockquote>`;
1326
+ case "codeBlock": {
1327
+ const lang = node.attrs?.language || "plaintext";
1328
+ const filename = node.attrs?.filename;
1329
+ const code = escapeHtml(getTextContent(node));
1330
+ const header = filename ? `<div class="nbk-code-header">${escapeHtml(filename)}</div>` : "";
1331
+ return `${header}<pre><code class="language-${lang}">${code}</code></pre>`;
1332
+ }
1333
+ case "image": {
1334
+ const src = node.attrs?.src || "";
1335
+ const alt = node.attrs?.alt || "";
1336
+ const caption = node.attrs?.caption;
1337
+ const width = node.attrs?.width;
1338
+ const height = node.attrs?.height;
1339
+ let img = `<img src="${escapeAttr(src)}" alt="${escapeAttr(alt)}"`;
1340
+ if (width) img += ` width="${width}"`;
1341
+ if (height) img += ` height="${height}"`;
1342
+ img += ' loading="lazy" />';
1343
+ if (caption) {
1344
+ return `<figure>${img}<figcaption>${escapeHtml(caption)}</figcaption></figure>`;
1345
+ }
1346
+ return img;
1347
+ }
1348
+ case "horizontalRule":
1349
+ return "<hr />";
1350
+ case "table":
1351
+ return `<table>${renderChildren(node)}</table>`;
1352
+ case "tableRow":
1353
+ return `<tr>${renderChildren(node)}</tr>`;
1354
+ case "tableHeader":
1355
+ return `<th>${renderInline(node)}</th>`;
1356
+ case "tableCell":
1357
+ return `<td>${renderInline(node)}</td>`;
1358
+ case "callout": {
1359
+ const calloutType = node.attrs?.type || "info";
1360
+ const icons = {
1361
+ info: "\u2139\uFE0F",
1362
+ warning: "\u26A0\uFE0F",
1363
+ tip: "\u{1F4A1}",
1364
+ danger: "\u{1F6A8}"
1365
+ };
1366
+ return `<div class="nbk-callout nbk-callout-${calloutType}"><span class="nbk-callout-icon">${icons[calloutType] || ""}</span><div class="nbk-callout-content">${renderChildren(node)}</div></div>`;
1367
+ }
1368
+ case "faq":
1369
+ return `<div class="nbk-faq" itemscope itemtype="https://schema.org/FAQPage">${renderChildren(node)}</div>`;
1370
+ case "faqItem":
1371
+ return `<div class="nbk-faq-item" itemscope itemprop="mainEntity" itemtype="https://schema.org/Question">${renderChildren(node)}</div>`;
1372
+ case "faqQuestion":
1373
+ return `<h3 itemprop="name">${renderInline(node)}</h3>`;
1374
+ case "faqAnswer":
1375
+ return `<div itemprop="acceptedAnswer" itemscope itemtype="https://schema.org/Answer"><div itemprop="text">${renderChildren(node)}</div></div>`;
1376
+ case "tableOfContents":
1377
+ return '<div data-toc="true" class="nbk-toc"></div>';
1378
+ case "html":
1379
+ return getTextContent(node);
1380
+ case "embed": {
1381
+ const embedUrl = node.attrs?.src || "";
1382
+ return `<div class="nbk-embed"><iframe src="${escapeAttr(embedUrl)}" frameborder="0" allowfullscreen loading="lazy"></iframe></div>`;
1383
+ }
1384
+ case "text":
1385
+ return renderTextNode(node);
1386
+ case "hardBreak":
1387
+ return "<br />";
1388
+ default:
1389
+ if (node.content) return renderChildren(node);
1390
+ if (node.text) return escapeHtml(node.text);
1391
+ return "";
1392
+ }
1393
+ }
1394
+ function renderChildren(node) {
1395
+ if (!node.content) return "";
1396
+ return node.content.map(renderNode).join("");
1397
+ }
1398
+ function renderInline(node) {
1399
+ if (!node.content) return "";
1400
+ return node.content.map(renderNode).join("");
1401
+ }
1402
+ function renderTextNode(node) {
1403
+ let text = escapeHtml(node.text || "");
1404
+ if (node.marks) {
1405
+ for (const mark of node.marks) {
1406
+ switch (mark.type) {
1407
+ case "bold":
1408
+ text = `<strong>${text}</strong>`;
1409
+ break;
1410
+ case "italic":
1411
+ text = `<em>${text}</em>`;
1412
+ break;
1413
+ case "strike":
1414
+ text = `<s>${text}</s>`;
1415
+ break;
1416
+ case "code":
1417
+ text = `<code>${text}</code>`;
1418
+ break;
1419
+ case "underline":
1420
+ text = `<u>${text}</u>`;
1421
+ break;
1422
+ case "highlight":
1423
+ text = `<mark>${text}</mark>`;
1424
+ break;
1425
+ case "link": {
1426
+ const href = mark.attrs?.href || "";
1427
+ const target = href.startsWith("http") ? ' target="_blank" rel="noopener noreferrer"' : "";
1428
+ text = `<a href="${escapeAttr(href)}"${target}>${text}</a>`;
1429
+ break;
1430
+ }
1431
+ }
1432
+ }
1433
+ }
1434
+ return text;
1435
+ }
1436
+ function getTextContent(node) {
1437
+ if (node.text) return node.text;
1438
+ if (!node.content) return "";
1439
+ return node.content.map(getTextContent).join("");
1440
+ }
1441
+ function escapeHtml(str) {
1442
+ return str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
1443
+ }
1444
+ function escapeAttr(str) {
1445
+ return str.replace(/"/g, "&quot;").replace(/&/g, "&amp;");
1446
+ }
1447
+ function stripTags(html) {
1448
+ return html.replace(/<[^>]+>/g, "");
1449
+ }
1450
+ function slugify(text) {
1451
+ return text.toLowerCase().trim().replace(/[^\w\s-]/g, "").replace(/[\s_]+/g, "-").replace(/-+/g, "-");
1452
+ }
1453
+ function SEOPanel({ seo, onChange, title, slug, excerpt }) {
1454
+ const metaTitle = seo.metaTitle || "";
1455
+ const metaDescription = seo.metaDescription || "";
1456
+ const focusKeyword = seo.focusKeyword || "";
1457
+ const canonicalUrl = seo.canonicalUrl || "";
1458
+ const ogImage = seo.ogImage || "";
1459
+ const noIndex = seo.noIndex || false;
1460
+ const displayTitle = metaTitle || title || "Post Title";
1461
+ const displayDesc = metaDescription || excerpt || "Post description will appear here...";
1462
+ const displayUrl = `/blog/${slug || "post-url"}`;
1463
+ const titleLength = displayTitle.length;
1464
+ const descLength = displayDesc.length;
1465
+ const titleColor = titleLength >= 50 && titleLength <= 60 ? "nbk-count-good" : titleLength > 70 ? "nbk-count-bad" : "nbk-count-warn";
1466
+ const descColor = descLength >= 150 && descLength <= 160 ? "nbk-count-good" : descLength > 170 ? "nbk-count-bad" : "nbk-count-warn";
1467
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "nbk-seo-panel", children: [
1468
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "nbk-serp-preview", children: [
1469
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "nbk-serp-title", children: displayTitle }),
1470
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "nbk-serp-url", children: displayUrl }),
1471
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "nbk-serp-desc", children: displayDesc.slice(0, 160) })
1472
+ ] }),
1473
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "nbk-field", children: [
1474
+ /* @__PURE__ */ jsxRuntime.jsx("label", { className: "nbk-label", children: "Focus Keyword" }),
1475
+ /* @__PURE__ */ jsxRuntime.jsx(
1476
+ "input",
1477
+ {
1478
+ type: "text",
1479
+ value: focusKeyword,
1480
+ onChange: (e) => onChange({ ...seo, focusKeyword: e.target.value }),
1481
+ className: "nbk-input",
1482
+ placeholder: "e.g. nextjs blog"
1483
+ }
1484
+ )
1485
+ ] }),
1486
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "nbk-field", children: [
1487
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { className: "nbk-label", children: [
1488
+ "Meta Title ",
1489
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: titleColor, children: [
1490
+ "(",
1491
+ titleLength,
1492
+ "/60)"
1493
+ ] })
1494
+ ] }),
1495
+ /* @__PURE__ */ jsxRuntime.jsx(
1496
+ "input",
1497
+ {
1498
+ type: "text",
1499
+ value: metaTitle,
1500
+ onChange: (e) => onChange({ ...seo, metaTitle: e.target.value }),
1501
+ className: "nbk-input",
1502
+ placeholder: title || "Custom meta title"
1503
+ }
1504
+ )
1505
+ ] }),
1506
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "nbk-field", children: [
1507
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { className: "nbk-label", children: [
1508
+ "Meta Description ",
1509
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: descColor, children: [
1510
+ "(",
1511
+ descLength,
1512
+ "/160)"
1513
+ ] })
1514
+ ] }),
1515
+ /* @__PURE__ */ jsxRuntime.jsx(
1516
+ "textarea",
1517
+ {
1518
+ value: metaDescription,
1519
+ onChange: (e) => onChange({ ...seo, metaDescription: e.target.value }),
1520
+ className: "nbk-textarea",
1521
+ rows: 3,
1522
+ placeholder: excerpt || "Custom meta description"
1523
+ }
1524
+ )
1525
+ ] }),
1526
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "nbk-field", children: [
1527
+ /* @__PURE__ */ jsxRuntime.jsx("label", { className: "nbk-label", children: "Canonical URL" }),
1528
+ /* @__PURE__ */ jsxRuntime.jsx(
1529
+ "input",
1530
+ {
1531
+ type: "url",
1532
+ value: canonicalUrl,
1533
+ onChange: (e) => onChange({ ...seo, canonicalUrl: e.target.value }),
1534
+ className: "nbk-input",
1535
+ placeholder: "https://..."
1536
+ }
1537
+ )
1538
+ ] }),
1539
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "nbk-field", children: [
1540
+ /* @__PURE__ */ jsxRuntime.jsx("label", { className: "nbk-label", children: "OG Image Override" }),
1541
+ /* @__PURE__ */ jsxRuntime.jsx(
1542
+ "input",
1543
+ {
1544
+ type: "url",
1545
+ value: ogImage,
1546
+ onChange: (e) => onChange({ ...seo, ogImage: e.target.value }),
1547
+ className: "nbk-input",
1548
+ placeholder: "https://..."
1549
+ }
1550
+ )
1551
+ ] }),
1552
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "nbk-field", children: /* @__PURE__ */ jsxRuntime.jsxs("label", { className: "nbk-checkbox-label", children: [
1553
+ /* @__PURE__ */ jsxRuntime.jsx(
1554
+ "input",
1555
+ {
1556
+ type: "checkbox",
1557
+ checked: noIndex,
1558
+ onChange: (e) => onChange({ ...seo, noIndex: e.target.checked })
1559
+ }
1560
+ ),
1561
+ "No Index (hide from search engines)"
1562
+ ] }) })
1563
+ ] });
1564
+ }
1565
+ function PostEditor({ postId }) {
1566
+ const api = useAdminApi();
1567
+ const [title, setTitle] = react.useState("");
1568
+ const [slug, setSlug] = react.useState("");
1569
+ const [content, setContent] = react.useState({ type: "doc", content: [{ type: "paragraph" }] });
1570
+ const [excerpt, setExcerpt] = react.useState("");
1571
+ const [status, setStatus] = react.useState("draft");
1572
+ const [categories, setCategories] = react.useState([]);
1573
+ const [tags, setTags] = react.useState("");
1574
+ const [coverImageUrl, setCoverImageUrl] = react.useState("");
1575
+ const [seo, setSeo] = react.useState({});
1576
+ const [authorName, setAuthorName] = react.useState("");
1577
+ const [scheduledAt, setScheduledAt] = react.useState("");
1578
+ const [allCategories, setAllCategories] = react.useState([]);
1579
+ const [saving, setSaving] = react.useState(false);
1580
+ const [lastSaved, setLastSaved] = react.useState("");
1581
+ const [error, setError] = react.useState("");
1582
+ const [seoExpanded, setSeoExpanded] = react.useState(false);
1583
+ const [loading, setLoading] = react.useState(!!postId);
1584
+ const [sidebarOpen, setSidebarOpen] = react.useState(true);
1585
+ react.useEffect(() => {
1586
+ api.get("/categories").then((res) => {
1587
+ setAllCategories(res.data || []);
1588
+ }).catch(() => {
1589
+ });
1590
+ if (postId) {
1591
+ api.get(`/posts?id=${postId}`).then((res) => {
1592
+ const post = res.data;
1593
+ setTitle(post.title || "");
1594
+ setSlug(post.slug || "");
1595
+ setContent(post.content?.length ? { type: "doc", content: post.content } : { type: "doc", content: [{ type: "paragraph" }] });
1596
+ setExcerpt(post.excerpt || "");
1597
+ setStatus(post.status || "draft");
1598
+ setCategories(post.categories || []);
1599
+ setTags((post.tags || []).join(", "));
1600
+ setCoverImageUrl(post.coverImage?.url || "");
1601
+ setSeo(post.seo || {});
1602
+ setAuthorName(post.author?.name || "");
1603
+ setScheduledAt(post.scheduledAt ? new Date(post.scheduledAt).toISOString().slice(0, 16) : "");
1604
+ }).catch((err) => setError(err.message)).finally(() => setLoading(false));
1605
+ }
1606
+ }, [postId]);
1607
+ const generateSlug = (text) => {
1608
+ return text.toLowerCase().trim().replace(/[^\w\s-]/g, "").replace(/[\s_]+/g, "-").replace(/-+/g, "-").replace(/^-+|-+$/g, "");
1609
+ };
1610
+ const handleTitleChange = (value) => {
1611
+ setTitle(value);
1612
+ if (!postId) {
1613
+ setSlug(generateSlug(value));
1614
+ }
1615
+ };
1616
+ const handleSave = async (targetStatus) => {
1617
+ setSaving(true);
1618
+ setError("");
1619
+ try {
1620
+ const contentArray = content.content || [];
1621
+ const html = renderBlocksToHTML(content);
1622
+ const body = {
1623
+ title,
1624
+ slug,
1625
+ excerpt,
1626
+ content: contentArray,
1627
+ contentHTML: html,
1628
+ status: targetStatus || status,
1629
+ categories,
1630
+ tags: tags.split(",").map((t) => t.trim()).filter(Boolean),
1631
+ seo
1632
+ };
1633
+ if (coverImageUrl) {
1634
+ body.coverImage = { _id: "", url: coverImageUrl, alt: title };
1635
+ }
1636
+ if (authorName) {
1637
+ body.author = { name: authorName };
1638
+ }
1639
+ if (scheduledAt && (targetStatus || status) === "scheduled") {
1640
+ body.scheduledAt = new Date(scheduledAt).toISOString();
1641
+ }
1642
+ if (postId) {
1643
+ await api.put(`/posts?id=${postId}`, body);
1644
+ } else {
1645
+ const res = await api.post("/posts", body);
1646
+ if (res.data?._id) {
1647
+ window.location.href = `/admin/blog/${res.data._id}/edit`;
1648
+ return;
1649
+ }
1650
+ }
1651
+ setLastSaved((/* @__PURE__ */ new Date()).toLocaleTimeString());
1652
+ } catch (err) {
1653
+ setError(err.message || "Failed to save");
1654
+ } finally {
1655
+ setSaving(false);
1656
+ }
1657
+ };
1658
+ const handleAutosave = react.useCallback(
1659
+ (editorContent) => {
1660
+ if (!postId) return;
1661
+ const contentArray = editorContent.content || [];
1662
+ const html = renderBlocksToHTML(editorContent);
1663
+ api.put(`/posts?id=${postId}`, {
1664
+ content: contentArray,
1665
+ contentHTML: html
1666
+ }).then(() => {
1667
+ setLastSaved((/* @__PURE__ */ new Date()).toLocaleTimeString());
1668
+ }).catch(() => {
1669
+ });
1670
+ },
1671
+ [postId]
1672
+ );
1673
+ const uploadImage = async (file) => {
1674
+ const formData = new FormData();
1675
+ formData.append("file", file);
1676
+ const res = await api.post("/media", formData);
1677
+ return { url: res.data.url, alt: res.data.alt || file.name };
1678
+ };
1679
+ if (loading) {
1680
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "nbk-post-editor", children: [
1681
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "nbk-editor-header", children: /* @__PURE__ */ jsxRuntime.jsx("h1", { className: "nbk-page-title", children: "Loading..." }) }),
1682
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: { padding: "2rem", textAlign: "center", color: "var(--nbk-text-muted)" }, children: "Loading post..." })
1683
+ ] });
1684
+ }
1685
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "nbk-post-editor", children: [
1686
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "nbk-editor-header", children: [
1687
+ /* @__PURE__ */ jsxRuntime.jsx("h1", { className: "nbk-page-title", children: postId ? "Edit Post" : "New Post" }),
1688
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "nbk-editor-actions", children: [
1689
+ lastSaved && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "nbk-last-saved", children: [
1690
+ "Last saved: ",
1691
+ lastSaved
1692
+ ] }),
1693
+ /* @__PURE__ */ jsxRuntime.jsx(
1694
+ "button",
1695
+ {
1696
+ onClick: () => handleSave("draft"),
1697
+ className: "nbk-btn nbk-btn-secondary",
1698
+ disabled: saving,
1699
+ children: saving ? "Saving..." : "Save Draft"
1700
+ }
1701
+ ),
1702
+ /* @__PURE__ */ jsxRuntime.jsx(
1703
+ "button",
1704
+ {
1705
+ onClick: () => handleSave("published"),
1706
+ className: "nbk-btn nbk-btn-primary",
1707
+ disabled: saving,
1708
+ children: saving ? "Publishing..." : "Publish"
1709
+ }
1710
+ ),
1711
+ /* @__PURE__ */ jsxRuntime.jsx(
1712
+ "button",
1713
+ {
1714
+ onClick: () => setSidebarOpen(!sidebarOpen),
1715
+ className: "nbk-btn nbk-btn-ghost",
1716
+ title: sidebarOpen ? "Hide sidebar" : "Show sidebar",
1717
+ children: sidebarOpen ? "\u21E5" : "\u21E4"
1718
+ }
1719
+ )
1720
+ ] })
1721
+ ] }),
1722
+ error && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "nbk-error", children: error }),
1723
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `nbk-editor-layout ${sidebarOpen ? "" : "nbk-sidebar-collapsed"}`, children: [
1724
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "nbk-editor-main", children: [
1725
+ /* @__PURE__ */ jsxRuntime.jsx(
1726
+ "input",
1727
+ {
1728
+ type: "text",
1729
+ value: title,
1730
+ onChange: (e) => handleTitleChange(e.target.value),
1731
+ placeholder: "Post title...",
1732
+ className: "nbk-title-input"
1733
+ }
1734
+ ),
1735
+ /* @__PURE__ */ jsxRuntime.jsx(
1736
+ BlogEditor,
1737
+ {
1738
+ content,
1739
+ onChange: setContent,
1740
+ onSave: postId ? handleAutosave : void 0,
1741
+ uploadImage
1742
+ }
1743
+ )
1744
+ ] }),
1745
+ sidebarOpen && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "nbk-editor-sidebar", children: [
1746
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "nbk-sidebar-section", children: [
1747
+ /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "nbk-sidebar-heading", children: "Publish" }),
1748
+ /* @__PURE__ */ jsxRuntime.jsxs(
1749
+ "select",
1750
+ {
1751
+ value: status,
1752
+ onChange: (e) => setStatus(e.target.value),
1753
+ className: "nbk-select",
1754
+ children: [
1755
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "draft", children: "Draft" }),
1756
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "published", children: "Published" }),
1757
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "scheduled", children: "Scheduled" })
1758
+ ]
1759
+ }
1760
+ ),
1761
+ status === "scheduled" && /* @__PURE__ */ jsxRuntime.jsx(
1762
+ "input",
1763
+ {
1764
+ type: "datetime-local",
1765
+ value: scheduledAt,
1766
+ onChange: (e) => setScheduledAt(e.target.value),
1767
+ className: "nbk-input"
1768
+ }
1769
+ )
1770
+ ] }),
1771
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "nbk-sidebar-section", children: [
1772
+ /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "nbk-sidebar-heading", children: "URL Slug" }),
1773
+ /* @__PURE__ */ jsxRuntime.jsx(
1774
+ "input",
1775
+ {
1776
+ type: "text",
1777
+ value: slug,
1778
+ onChange: (e) => setSlug(e.target.value),
1779
+ className: "nbk-input",
1780
+ placeholder: "post-url-slug"
1781
+ }
1782
+ )
1783
+ ] }),
1784
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "nbk-sidebar-section", children: [
1785
+ /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "nbk-sidebar-heading", children: "Categories" }),
1786
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "nbk-checkbox-list", children: allCategories.map((cat) => /* @__PURE__ */ jsxRuntime.jsxs("label", { className: "nbk-checkbox-label", children: [
1787
+ /* @__PURE__ */ jsxRuntime.jsx(
1788
+ "input",
1789
+ {
1790
+ type: "checkbox",
1791
+ checked: categories.includes(cat.slug),
1792
+ onChange: (e) => {
1793
+ if (e.target.checked) {
1794
+ setCategories([...categories, cat.slug]);
1795
+ } else {
1796
+ setCategories(categories.filter((c) => c !== cat.slug));
1797
+ }
1798
+ }
1799
+ }
1800
+ ),
1801
+ cat.name
1802
+ ] }, cat.slug)) })
1803
+ ] }),
1804
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "nbk-sidebar-section", children: [
1805
+ /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "nbk-sidebar-heading", children: "Tags" }),
1806
+ /* @__PURE__ */ jsxRuntime.jsx(
1807
+ "input",
1808
+ {
1809
+ type: "text",
1810
+ value: tags,
1811
+ onChange: (e) => setTags(e.target.value),
1812
+ className: "nbk-input",
1813
+ placeholder: "tag1, tag2, tag3"
1814
+ }
1815
+ )
1816
+ ] }),
1817
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "nbk-sidebar-section", children: [
1818
+ /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "nbk-sidebar-heading", children: "Cover Image" }),
1819
+ coverImageUrl && /* @__PURE__ */ jsxRuntime.jsx("img", { src: coverImageUrl, alt: "Cover", className: "nbk-cover-preview" }),
1820
+ /* @__PURE__ */ jsxRuntime.jsx(
1821
+ "input",
1822
+ {
1823
+ type: "text",
1824
+ value: coverImageUrl,
1825
+ onChange: (e) => setCoverImageUrl(e.target.value),
1826
+ className: "nbk-input",
1827
+ placeholder: "Image URL"
1828
+ }
1829
+ ),
1830
+ /* @__PURE__ */ jsxRuntime.jsx(
1831
+ "button",
1832
+ {
1833
+ onClick: async () => {
1834
+ const input = document.createElement("input");
1835
+ input.type = "file";
1836
+ input.accept = "image/*";
1837
+ input.onchange = async () => {
1838
+ const file = input.files?.[0];
1839
+ if (!file) return;
1840
+ try {
1841
+ const result = await uploadImage(file);
1842
+ setCoverImageUrl(result.url);
1843
+ } catch (err) {
1844
+ console.error("Cover upload failed:", err);
1845
+ }
1846
+ };
1847
+ input.click();
1848
+ },
1849
+ className: "nbk-btn nbk-btn-sm nbk-btn-secondary",
1850
+ children: "Upload Cover Image"
1851
+ }
1852
+ )
1853
+ ] }),
1854
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "nbk-sidebar-section", children: [
1855
+ /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "nbk-sidebar-heading", children: "Author" }),
1856
+ /* @__PURE__ */ jsxRuntime.jsx(
1857
+ "input",
1858
+ {
1859
+ type: "text",
1860
+ value: authorName,
1861
+ onChange: (e) => setAuthorName(e.target.value),
1862
+ className: "nbk-input",
1863
+ placeholder: "Author name"
1864
+ }
1865
+ )
1866
+ ] }),
1867
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "nbk-sidebar-section", children: [
1868
+ /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "nbk-sidebar-heading", children: "Excerpt" }),
1869
+ /* @__PURE__ */ jsxRuntime.jsx(
1870
+ "textarea",
1871
+ {
1872
+ value: excerpt,
1873
+ onChange: (e) => setExcerpt(e.target.value),
1874
+ className: "nbk-textarea",
1875
+ rows: 3,
1876
+ placeholder: "Short description..."
1877
+ }
1878
+ )
1879
+ ] }),
1880
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "nbk-sidebar-section", children: [
1881
+ /* @__PURE__ */ jsxRuntime.jsxs(
1882
+ "button",
1883
+ {
1884
+ onClick: () => setSeoExpanded(!seoExpanded),
1885
+ className: "nbk-sidebar-heading nbk-expandable",
1886
+ children: [
1887
+ "SEO Settings ",
1888
+ seoExpanded ? "\u25BC" : "\u25B6"
1889
+ ]
1890
+ }
1891
+ ),
1892
+ seoExpanded && /* @__PURE__ */ jsxRuntime.jsx(
1893
+ SEOPanel,
1894
+ {
1895
+ seo,
1896
+ onChange: setSeo,
1897
+ title,
1898
+ slug,
1899
+ excerpt
1900
+ }
1901
+ )
1902
+ ] })
1903
+ ] })
1904
+ ] })
1905
+ ] });
1906
+ }
1907
+ function MediaLibrary() {
1908
+ const api = useAdminApi();
1909
+ const [media, setMedia] = react.useState([]);
1910
+ const [total, setTotal] = react.useState(0);
1911
+ const [page, setPage] = react.useState(1);
1912
+ const [loading, setLoading] = react.useState(true);
1913
+ const [uploading, setUploading] = react.useState(false);
1914
+ const [selected, setSelected] = react.useState(null);
1915
+ const [dragOver, setDragOver] = react.useState(false);
1916
+ const limit = 24;
1917
+ const loadMedia = react.useCallback(async () => {
1918
+ setLoading(true);
1919
+ try {
1920
+ const res = await api.get(`/media?page=${page}&limit=${limit}`);
1921
+ setMedia(res.data || []);
1922
+ setTotal(res.meta?.total || 0);
1923
+ } catch (err) {
1924
+ console.error("Failed to load media:", err);
1925
+ } finally {
1926
+ setLoading(false);
1927
+ }
1928
+ }, [page]);
1929
+ react.useEffect(() => {
1930
+ loadMedia();
1931
+ }, [loadMedia]);
1932
+ const handleUpload = async (files) => {
1933
+ setUploading(true);
1934
+ try {
1935
+ for (const file of Array.from(files)) {
1936
+ const formData = new FormData();
1937
+ formData.append("file", file);
1938
+ await api.post("/media", formData);
1939
+ }
1940
+ loadMedia();
1941
+ } catch (err) {
1942
+ console.error("Upload failed:", err);
1943
+ } finally {
1944
+ setUploading(false);
1945
+ }
1946
+ };
1947
+ const handleDelete = async (id) => {
1948
+ if (!confirm("Delete this media file? This cannot be undone.")) return;
1949
+ try {
1950
+ await api.del(`/media?id=${id}`);
1951
+ setSelected(null);
1952
+ loadMedia();
1953
+ } catch (err) {
1954
+ console.error("Delete failed:", err);
1955
+ }
1956
+ };
1957
+ const formatSize = (bytes) => {
1958
+ if (bytes < 1024) return `${bytes} B`;
1959
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
1960
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
1961
+ };
1962
+ const handleDrop = (e) => {
1963
+ e.preventDefault();
1964
+ setDragOver(false);
1965
+ if (e.dataTransfer.files.length) {
1966
+ handleUpload(e.dataTransfer.files);
1967
+ }
1968
+ };
1969
+ const totalPages = Math.ceil(total / limit);
1970
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "nbk-media-library", children: [
1971
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "nbk-page-header", children: [
1972
+ /* @__PURE__ */ jsxRuntime.jsx("h1", { className: "nbk-page-title", children: "Media Library" }),
1973
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { className: "nbk-btn nbk-btn-primary", children: [
1974
+ uploading ? "Uploading..." : "Upload Files",
1975
+ /* @__PURE__ */ jsxRuntime.jsx(
1976
+ "input",
1977
+ {
1978
+ type: "file",
1979
+ multiple: true,
1980
+ accept: "image/*",
1981
+ onChange: (e) => e.target.files && handleUpload(e.target.files),
1982
+ className: "nbk-hidden"
1983
+ }
1984
+ )
1985
+ ] })
1986
+ ] }),
1987
+ /* @__PURE__ */ jsxRuntime.jsx(
1988
+ "div",
1989
+ {
1990
+ className: `nbk-dropzone ${dragOver ? "active" : ""}`,
1991
+ onDragOver: (e) => {
1992
+ e.preventDefault();
1993
+ setDragOver(true);
1994
+ },
1995
+ onDragLeave: () => setDragOver(false),
1996
+ onDrop: handleDrop,
1997
+ children: "Drop files here to upload"
1998
+ }
1999
+ ),
2000
+ loading ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "nbk-loading", children: "Loading media..." }) : media.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "nbk-empty-state", children: /* @__PURE__ */ jsxRuntime.jsx("p", { children: "No media files yet. Upload your first image!" }) }) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: "nbk-media-grid", children: media.map((item) => /* @__PURE__ */ jsxRuntime.jsxs(
2001
+ "div",
2002
+ {
2003
+ className: `nbk-media-card ${selected?._id === item._id ? "selected" : ""}`,
2004
+ onClick: () => setSelected(item),
2005
+ children: [
2006
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "nbk-media-thumb", children: /* @__PURE__ */ jsxRuntime.jsx("img", { src: item.url, alt: item.alt || item.originalName, loading: "lazy" }) }),
2007
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "nbk-media-info", children: [
2008
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "nbk-media-name", title: item.originalName, children: item.originalName }),
2009
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "nbk-media-size", children: formatSize(item.size) })
2010
+ ] })
2011
+ ]
2012
+ },
2013
+ item._id
2014
+ )) }),
2015
+ selected && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "nbk-media-detail", children: [
2016
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "nbk-media-detail-header", children: [
2017
+ /* @__PURE__ */ jsxRuntime.jsx("h3", { children: "File Details" }),
2018
+ /* @__PURE__ */ jsxRuntime.jsx("button", { onClick: () => setSelected(null), className: "nbk-close-btn", children: "\xD7" })
2019
+ ] }),
2020
+ /* @__PURE__ */ jsxRuntime.jsx("img", { src: selected.url, alt: selected.alt || "", className: "nbk-media-preview" }),
2021
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "nbk-media-meta", children: [
2022
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
2023
+ /* @__PURE__ */ jsxRuntime.jsx("strong", { children: "Name:" }),
2024
+ " ",
2025
+ selected.originalName
2026
+ ] }),
2027
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
2028
+ /* @__PURE__ */ jsxRuntime.jsx("strong", { children: "Type:" }),
2029
+ " ",
2030
+ selected.mimeType
2031
+ ] }),
2032
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
2033
+ /* @__PURE__ */ jsxRuntime.jsx("strong", { children: "Size:" }),
2034
+ " ",
2035
+ formatSize(selected.size)
2036
+ ] }),
2037
+ selected.width && selected.height && /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
2038
+ /* @__PURE__ */ jsxRuntime.jsx("strong", { children: "Dimensions:" }),
2039
+ " ",
2040
+ selected.width,
2041
+ " x ",
2042
+ selected.height
2043
+ ] }),
2044
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
2045
+ /* @__PURE__ */ jsxRuntime.jsx("strong", { children: "Uploaded:" }),
2046
+ " ",
2047
+ new Date(selected.createdAt).toLocaleString()
2048
+ ] })
2049
+ ] }),
2050
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "nbk-media-url", children: [
2051
+ /* @__PURE__ */ jsxRuntime.jsx("label", { className: "nbk-label", children: "URL" }),
2052
+ /* @__PURE__ */ jsxRuntime.jsx(
2053
+ "input",
2054
+ {
2055
+ type: "text",
2056
+ readOnly: true,
2057
+ value: selected.url,
2058
+ className: "nbk-input",
2059
+ onClick: (e) => e.target.select()
2060
+ }
2061
+ )
2062
+ ] }),
2063
+ /* @__PURE__ */ jsxRuntime.jsx(
2064
+ "button",
2065
+ {
2066
+ onClick: () => handleDelete(selected._id),
2067
+ className: "nbk-btn nbk-btn-danger",
2068
+ children: "Delete File"
2069
+ }
2070
+ )
2071
+ ] }),
2072
+ totalPages > 1 && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "nbk-pagination-admin", children: [
2073
+ /* @__PURE__ */ jsxRuntime.jsx("button", { onClick: () => setPage(page - 1), disabled: page <= 1, className: "nbk-btn nbk-btn-sm", children: "Previous" }),
2074
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
2075
+ "Page ",
2076
+ page,
2077
+ " of ",
2078
+ totalPages
2079
+ ] }),
2080
+ /* @__PURE__ */ jsxRuntime.jsx("button", { onClick: () => setPage(page + 1), disabled: page >= totalPages, className: "nbk-btn nbk-btn-sm", children: "Next" })
2081
+ ] })
2082
+ ] });
2083
+ }
2084
+ function CategoryManager() {
2085
+ const api = useAdminApi();
2086
+ const [categories, setCategories] = react.useState([]);
2087
+ const [loading, setLoading] = react.useState(true);
2088
+ const [editingId, setEditingId] = react.useState(null);
2089
+ const [name, setName] = react.useState("");
2090
+ const [slug, setSlug] = react.useState("");
2091
+ const [description, setDescription] = react.useState("");
2092
+ const [error, setError] = react.useState("");
2093
+ const loadCategories = react.useCallback(async () => {
2094
+ try {
2095
+ const res = await api.get("/categories");
2096
+ setCategories(res.data || []);
2097
+ } catch (err) {
2098
+ console.error("Failed to load categories:", err);
2099
+ } finally {
2100
+ setLoading(false);
2101
+ }
2102
+ }, []);
2103
+ react.useEffect(() => {
2104
+ loadCategories();
2105
+ }, [loadCategories]);
2106
+ const resetForm = () => {
2107
+ setEditingId(null);
2108
+ setName("");
2109
+ setSlug("");
2110
+ setDescription("");
2111
+ setError("");
2112
+ };
2113
+ const handleSave = async () => {
2114
+ if (!name.trim()) {
2115
+ setError("Name is required");
2116
+ return;
2117
+ }
2118
+ setError("");
2119
+ try {
2120
+ const body = {
2121
+ name: name.trim(),
2122
+ description: description.trim() || void 0
2123
+ };
2124
+ if (slug.trim()) body.slug = slug.trim();
2125
+ if (editingId) {
2126
+ await api.put(`/categories?id=${editingId}`, body);
2127
+ } else {
2128
+ await api.post("/categories", body);
2129
+ }
2130
+ resetForm();
2131
+ loadCategories();
2132
+ } catch (err) {
2133
+ setError(err.message || "Failed to save");
2134
+ }
2135
+ };
2136
+ const handleEdit = (cat) => {
2137
+ setEditingId(cat._id);
2138
+ setName(cat.name);
2139
+ setSlug(cat.slug);
2140
+ setDescription(cat.description || "");
2141
+ };
2142
+ const handleDelete = async (id) => {
2143
+ if (!confirm("Delete this category?")) return;
2144
+ try {
2145
+ await api.del(`/categories?id=${id}`);
2146
+ loadCategories();
2147
+ } catch (err) {
2148
+ console.error("Delete failed:", err);
2149
+ }
2150
+ };
2151
+ const handleReorder = async (id, direction) => {
2152
+ const idx = categories.findIndex((c) => c._id === id);
2153
+ if (idx < 0) return;
2154
+ const swapIdx = direction === "up" ? idx - 1 : idx + 1;
2155
+ if (swapIdx < 0 || swapIdx >= categories.length) return;
2156
+ try {
2157
+ await Promise.all([
2158
+ api.put(`/categories?id=${categories[idx]._id}`, { order: categories[swapIdx].order }),
2159
+ api.put(`/categories?id=${categories[swapIdx]._id}`, { order: categories[idx].order })
2160
+ ]);
2161
+ loadCategories();
2162
+ } catch (err) {
2163
+ console.error("Reorder failed:", err);
2164
+ }
2165
+ };
2166
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "nbk-category-manager", children: [
2167
+ /* @__PURE__ */ jsxRuntime.jsx("h1", { className: "nbk-page-title", children: "Categories" }),
2168
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "nbk-category-form", children: [
2169
+ /* @__PURE__ */ jsxRuntime.jsx("h3", { children: editingId ? "Edit Category" : "Add Category" }),
2170
+ error && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "nbk-error", children: error }),
2171
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "nbk-form-row", children: [
2172
+ /* @__PURE__ */ jsxRuntime.jsx(
2173
+ "input",
2174
+ {
2175
+ type: "text",
2176
+ value: name,
2177
+ onChange: (e) => setName(e.target.value),
2178
+ className: "nbk-input",
2179
+ placeholder: "Category name"
2180
+ }
2181
+ ),
2182
+ /* @__PURE__ */ jsxRuntime.jsx(
2183
+ "input",
2184
+ {
2185
+ type: "text",
2186
+ value: slug,
2187
+ onChange: (e) => setSlug(e.target.value),
2188
+ className: "nbk-input",
2189
+ placeholder: "slug (auto-generated)"
2190
+ }
2191
+ )
2192
+ ] }),
2193
+ /* @__PURE__ */ jsxRuntime.jsx(
2194
+ "textarea",
2195
+ {
2196
+ value: description,
2197
+ onChange: (e) => setDescription(e.target.value),
2198
+ className: "nbk-textarea",
2199
+ rows: 2,
2200
+ placeholder: "Description (optional)"
2201
+ }
2202
+ ),
2203
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "nbk-form-actions", children: [
2204
+ /* @__PURE__ */ jsxRuntime.jsx("button", { onClick: handleSave, className: "nbk-btn nbk-btn-primary", children: editingId ? "Update" : "Add Category" }),
2205
+ editingId && /* @__PURE__ */ jsxRuntime.jsx("button", { onClick: resetForm, className: "nbk-btn nbk-btn-secondary", children: "Cancel" })
2206
+ ] })
2207
+ ] }),
2208
+ loading ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "nbk-loading", children: "Loading categories..." }) : categories.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "nbk-empty-state", children: "No categories yet." }) : /* @__PURE__ */ jsxRuntime.jsxs("table", { className: "nbk-table", children: [
2209
+ /* @__PURE__ */ jsxRuntime.jsx("thead", { children: /* @__PURE__ */ jsxRuntime.jsxs("tr", { children: [
2210
+ /* @__PURE__ */ jsxRuntime.jsx("th", { children: "Order" }),
2211
+ /* @__PURE__ */ jsxRuntime.jsx("th", { children: "Name" }),
2212
+ /* @__PURE__ */ jsxRuntime.jsx("th", { children: "Slug" }),
2213
+ /* @__PURE__ */ jsxRuntime.jsx("th", { children: "Posts" }),
2214
+ /* @__PURE__ */ jsxRuntime.jsx("th", { children: "Actions" })
2215
+ ] }) }),
2216
+ /* @__PURE__ */ jsxRuntime.jsx("tbody", { children: categories.map((cat, idx) => /* @__PURE__ */ jsxRuntime.jsxs("tr", { children: [
2217
+ /* @__PURE__ */ jsxRuntime.jsx("td", { children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "nbk-reorder", children: [
2218
+ /* @__PURE__ */ jsxRuntime.jsx(
2219
+ "button",
2220
+ {
2221
+ onClick: () => handleReorder(cat._id, "up"),
2222
+ disabled: idx === 0,
2223
+ className: "nbk-btn-icon",
2224
+ children: "\u25B2"
2225
+ }
2226
+ ),
2227
+ /* @__PURE__ */ jsxRuntime.jsx(
2228
+ "button",
2229
+ {
2230
+ onClick: () => handleReorder(cat._id, "down"),
2231
+ disabled: idx === categories.length - 1,
2232
+ className: "nbk-btn-icon",
2233
+ children: "\u25BC"
2234
+ }
2235
+ )
2236
+ ] }) }),
2237
+ /* @__PURE__ */ jsxRuntime.jsx("td", { children: cat.name }),
2238
+ /* @__PURE__ */ jsxRuntime.jsxs("td", { className: "nbk-text-muted", children: [
2239
+ "/",
2240
+ cat.slug
2241
+ ] }),
2242
+ /* @__PURE__ */ jsxRuntime.jsx("td", { children: cat.postCount }),
2243
+ /* @__PURE__ */ jsxRuntime.jsx("td", { children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "nbk-actions", children: [
2244
+ /* @__PURE__ */ jsxRuntime.jsx("button", { onClick: () => handleEdit(cat), className: "nbk-btn nbk-btn-sm", children: "Edit" }),
2245
+ /* @__PURE__ */ jsxRuntime.jsx(
2246
+ "button",
2247
+ {
2248
+ onClick: () => handleDelete(cat._id),
2249
+ className: "nbk-btn nbk-btn-sm nbk-btn-danger",
2250
+ children: "Delete"
2251
+ }
2252
+ )
2253
+ ] }) })
2254
+ ] }, cat._id)) })
2255
+ ] })
2256
+ ] });
2257
+ }
2258
+ function SettingsPage() {
2259
+ const api = useAdminApi();
2260
+ const [settings, setSettings] = react.useState({});
2261
+ const [loading, setLoading] = react.useState(true);
2262
+ const [saving, setSaving] = react.useState(false);
2263
+ const [saved, setSaved] = react.useState(false);
2264
+ const [error, setError] = react.useState("");
2265
+ react.useEffect(() => {
2266
+ api.get("/settings").then((res) => setSettings(res.data || {})).catch((err) => setError(err.message)).finally(() => setLoading(false));
2267
+ }, []);
2268
+ const handleSave = async () => {
2269
+ setSaving(true);
2270
+ setError("");
2271
+ setSaved(false);
2272
+ try {
2273
+ await api.put("/settings", settings);
2274
+ setSaved(true);
2275
+ setTimeout(() => setSaved(false), 3e3);
2276
+ } catch (err) {
2277
+ setError(err.message || "Failed to save");
2278
+ } finally {
2279
+ setSaving(false);
2280
+ }
2281
+ };
2282
+ const update = (key, value) => {
2283
+ setSettings((prev) => ({ ...prev, [key]: value }));
2284
+ };
2285
+ const updateNested = (parent, key, value) => {
2286
+ setSettings((prev) => ({
2287
+ ...prev,
2288
+ [parent]: { ...prev[parent] || {}, [key]: value }
2289
+ }));
2290
+ };
2291
+ if (loading) return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "nbk-loading", children: "Loading settings..." });
2292
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "nbk-settings", children: [
2293
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "nbk-page-header", children: [
2294
+ /* @__PURE__ */ jsxRuntime.jsx("h1", { className: "nbk-page-title", children: "Settings" }),
2295
+ /* @__PURE__ */ jsxRuntime.jsx(
2296
+ "button",
2297
+ {
2298
+ onClick: handleSave,
2299
+ className: "nbk-btn nbk-btn-primary",
2300
+ disabled: saving,
2301
+ children: saving ? "Saving..." : saved ? "Saved!" : "Save Settings"
2302
+ }
2303
+ )
2304
+ ] }),
2305
+ error && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "nbk-error", children: error }),
2306
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "nbk-settings-section", children: [
2307
+ /* @__PURE__ */ jsxRuntime.jsx("h2", { className: "nbk-section-title", children: "General" }),
2308
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "nbk-field", children: [
2309
+ /* @__PURE__ */ jsxRuntime.jsx("label", { className: "nbk-label", children: "Posts Per Page" }),
2310
+ /* @__PURE__ */ jsxRuntime.jsx(
2311
+ "input",
2312
+ {
2313
+ type: "number",
2314
+ value: settings.postsPerPage || 10,
2315
+ onChange: (e) => update("postsPerPage", parseInt(e.target.value) || 10),
2316
+ className: "nbk-input",
2317
+ min: "1",
2318
+ max: "100"
2319
+ }
2320
+ )
2321
+ ] })
2322
+ ] }),
2323
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "nbk-settings-section", children: [
2324
+ /* @__PURE__ */ jsxRuntime.jsx("h2", { className: "nbk-section-title", children: "Default Author" }),
2325
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "nbk-field", children: [
2326
+ /* @__PURE__ */ jsxRuntime.jsx("label", { className: "nbk-label", children: "Name" }),
2327
+ /* @__PURE__ */ jsxRuntime.jsx(
2328
+ "input",
2329
+ {
2330
+ type: "text",
2331
+ value: settings.defaultAuthor?.name || "",
2332
+ onChange: (e) => updateNested("defaultAuthor", "name", e.target.value),
2333
+ className: "nbk-input"
2334
+ }
2335
+ )
2336
+ ] }),
2337
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "nbk-field", children: [
2338
+ /* @__PURE__ */ jsxRuntime.jsx("label", { className: "nbk-label", children: "Bio" }),
2339
+ /* @__PURE__ */ jsxRuntime.jsx(
2340
+ "textarea",
2341
+ {
2342
+ value: settings.defaultAuthor?.bio || "",
2343
+ onChange: (e) => updateNested("defaultAuthor", "bio", e.target.value),
2344
+ className: "nbk-textarea",
2345
+ rows: 2
2346
+ }
2347
+ )
2348
+ ] }),
2349
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "nbk-field", children: [
2350
+ /* @__PURE__ */ jsxRuntime.jsx("label", { className: "nbk-label", children: "Avatar URL" }),
2351
+ /* @__PURE__ */ jsxRuntime.jsx(
2352
+ "input",
2353
+ {
2354
+ type: "url",
2355
+ value: settings.defaultAuthor?.avatar || "",
2356
+ onChange: (e) => updateNested("defaultAuthor", "avatar", e.target.value),
2357
+ className: "nbk-input"
2358
+ }
2359
+ )
2360
+ ] }),
2361
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "nbk-field", children: [
2362
+ /* @__PURE__ */ jsxRuntime.jsx("label", { className: "nbk-label", children: "Profile URL" }),
2363
+ /* @__PURE__ */ jsxRuntime.jsx(
2364
+ "input",
2365
+ {
2366
+ type: "url",
2367
+ value: settings.defaultAuthor?.url || "",
2368
+ onChange: (e) => updateNested("defaultAuthor", "url", e.target.value),
2369
+ className: "nbk-input"
2370
+ }
2371
+ )
2372
+ ] })
2373
+ ] }),
2374
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "nbk-settings-section", children: [
2375
+ /* @__PURE__ */ jsxRuntime.jsx("h2", { className: "nbk-section-title", children: "SEO" }),
2376
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "nbk-field", children: [
2377
+ /* @__PURE__ */ jsxRuntime.jsx("label", { className: "nbk-label", children: "Default OG Image URL" }),
2378
+ /* @__PURE__ */ jsxRuntime.jsx(
2379
+ "input",
2380
+ {
2381
+ type: "url",
2382
+ value: settings.defaultOgImage || "",
2383
+ onChange: (e) => update("defaultOgImage", e.target.value),
2384
+ className: "nbk-input",
2385
+ placeholder: "https://..."
2386
+ }
2387
+ )
2388
+ ] })
2389
+ ] }),
2390
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "nbk-settings-section", children: [
2391
+ /* @__PURE__ */ jsxRuntime.jsx("h2", { className: "nbk-section-title", children: "Comments" }),
2392
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "nbk-field", children: [
2393
+ /* @__PURE__ */ jsxRuntime.jsx("label", { className: "nbk-label", children: "Comment System" }),
2394
+ /* @__PURE__ */ jsxRuntime.jsxs(
2395
+ "select",
2396
+ {
2397
+ value: settings.commentSystem || "none",
2398
+ onChange: (e) => update("commentSystem", e.target.value),
2399
+ className: "nbk-select",
2400
+ children: [
2401
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "none", children: "None" }),
2402
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "giscus", children: "Giscus" }),
2403
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "disqus", children: "Disqus" })
2404
+ ]
2405
+ }
2406
+ )
2407
+ ] })
2408
+ ] }),
2409
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "nbk-settings-section", children: [
2410
+ /* @__PURE__ */ jsxRuntime.jsx("h2", { className: "nbk-section-title", children: "Analytics" }),
2411
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "nbk-field", children: [
2412
+ /* @__PURE__ */ jsxRuntime.jsx("label", { className: "nbk-label", children: "Google Analytics ID" }),
2413
+ /* @__PURE__ */ jsxRuntime.jsx(
2414
+ "input",
2415
+ {
2416
+ type: "text",
2417
+ value: settings.analytics?.gaId || "",
2418
+ onChange: (e) => updateNested("analytics", "gaId", e.target.value),
2419
+ className: "nbk-input",
2420
+ placeholder: "G-XXXXXXXXXX"
2421
+ }
2422
+ )
2423
+ ] }),
2424
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "nbk-field", children: [
2425
+ /* @__PURE__ */ jsxRuntime.jsx("label", { className: "nbk-label", children: "Plausible Domain" }),
2426
+ /* @__PURE__ */ jsxRuntime.jsx(
2427
+ "input",
2428
+ {
2429
+ type: "text",
2430
+ value: settings.analytics?.plausibleDomain || "",
2431
+ onChange: (e) => updateNested("analytics", "plausibleDomain", e.target.value),
2432
+ className: "nbk-input",
2433
+ placeholder: "yourdomain.com"
2434
+ }
2435
+ )
2436
+ ] })
2437
+ ] }),
2438
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "nbk-settings-section", children: [
2439
+ /* @__PURE__ */ jsxRuntime.jsx("h2", { className: "nbk-section-title", children: "Custom CSS" }),
2440
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "nbk-field", children: /* @__PURE__ */ jsxRuntime.jsx(
2441
+ "textarea",
2442
+ {
2443
+ value: settings.customCSS || "",
2444
+ onChange: (e) => update("customCSS", e.target.value),
2445
+ className: "nbk-textarea nbk-code-textarea",
2446
+ rows: 6,
2447
+ placeholder: "/* Custom CSS styles */"
2448
+ }
2449
+ ) })
2450
+ ] })
2451
+ ] });
2452
+ }
2453
+
2454
+ exports.AdminLayout = AdminLayout;
2455
+ exports.CategoryManager = CategoryManager;
2456
+ exports.Dashboard = Dashboard;
2457
+ exports.MediaLibrary = MediaLibrary;
2458
+ exports.PostEditor = PostEditor;
2459
+ exports.PostList = PostList;
2460
+ exports.SEOPanel = SEOPanel;
2461
+ exports.SettingsPage = SettingsPage;
2462
+ exports.setApiBase = setApiBase;
2463
+ exports.useAdminApi = useAdminApi;
2464
+ //# sourceMappingURL=index.cjs.map
2465
+ //# sourceMappingURL=index.cjs.map