nextblogkit 0.7.1 → 0.7.3

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.
@@ -1,9 +1,9 @@
1
1
  "use client";
2
2
  'use strict';
3
3
 
4
- var react = require('react');
4
+ var React4 = require('react');
5
5
  var jsxRuntime = require('react/jsx-runtime');
6
- var react$1 = require('@tiptap/react');
6
+ var react = require('@tiptap/react');
7
7
  var StarterKit = require('@tiptap/starter-kit');
8
8
  var Placeholder = require('@tiptap/extension-placeholder');
9
9
  var Link = require('@tiptap/extension-link');
@@ -23,6 +23,7 @@ var CodeBlockLowlight = require('@tiptap/extension-code-block');
23
23
 
24
24
  function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
25
25
 
26
+ var React4__default = /*#__PURE__*/_interopDefault(React4);
26
27
  var StarterKit__default = /*#__PURE__*/_interopDefault(StarterKit);
27
28
  var Placeholder__default = /*#__PURE__*/_interopDefault(Placeholder);
28
29
  var Link__default = /*#__PURE__*/_interopDefault(Link);
@@ -79,22 +80,22 @@ async function apiRequest(path, options = {}) {
79
80
  return data;
80
81
  }
81
82
  function useAdminApi() {
82
- const get = react.useCallback(async (path) => {
83
+ const get = React4.useCallback(async (path) => {
83
84
  return apiRequest(path);
84
85
  }, []);
85
- const post = react.useCallback(async (path, body) => {
86
+ const post = React4.useCallback(async (path, body) => {
86
87
  return apiRequest(path, {
87
88
  method: "POST",
88
89
  body: body instanceof FormData ? body : JSON.stringify(body)
89
90
  });
90
91
  }, []);
91
- const put = react.useCallback(async (path, body) => {
92
+ const put = React4.useCallback(async (path, body) => {
92
93
  return apiRequest(path, {
93
94
  method: "PUT",
94
95
  body: JSON.stringify(body)
95
96
  });
96
97
  }, []);
97
- const del = react.useCallback(async (path) => {
98
+ const del = React4.useCallback(async (path) => {
98
99
  return apiRequest(path, { method: "DELETE" });
99
100
  }, []);
100
101
  return { get, post, put, del };
@@ -110,11 +111,11 @@ function buildNavItems(adminPath) {
110
111
  ];
111
112
  }
112
113
  function AdminLayout({ children, apiKey, apiPath, adminPath = "/admin/blog", basePath = "/blog" }) {
113
- const [isAuthenticated, setIsAuthenticated] = react.useState(false);
114
- const [inputKey, setInputKey] = react.useState("");
115
- const [sidebarOpen, setSidebarOpen] = react.useState(true);
116
- const [currentPath, setCurrentPath] = react.useState("");
117
- react.useEffect(() => {
114
+ const [isAuthenticated, setIsAuthenticated] = React4.useState(false);
115
+ const [inputKey, setInputKey] = React4.useState("");
116
+ const [sidebarOpen, setSidebarOpen] = React4.useState(true);
117
+ const [currentPath, setCurrentPath] = React4.useState("");
118
+ React4.useEffect(() => {
118
119
  if (apiPath) {
119
120
  setApiBase(apiPath);
120
121
  }
@@ -122,22 +123,56 @@ function AdminLayout({ children, apiKey, apiPath, adminPath = "/admin/blog", bas
122
123
  setBasePath(basePath);
123
124
  }
124
125
  }, [apiPath, basePath]);
125
- react.useEffect(() => {
126
- if (typeof window !== "undefined") {
127
- setCurrentPath(window.location.pathname);
128
- const stored = sessionStorage.getItem("nbk_api_key");
129
- if (stored || apiKey) {
126
+ const [initializing, setInitializing] = React4.useState(true);
127
+ React4.useEffect(() => {
128
+ if (typeof window === "undefined") return;
129
+ setCurrentPath(window.location.pathname);
130
+ const stored = sessionStorage.getItem("nbk_api_key");
131
+ const key = stored || apiKey;
132
+ if (!key) {
133
+ setInitializing(false);
134
+ return;
135
+ }
136
+ const base = apiPath || "/api/blog";
137
+ fetch(`${base}/settings`, {
138
+ headers: { Authorization: `Bearer ${key}` }
139
+ }).then((res) => {
140
+ if (res.ok) {
130
141
  setIsAuthenticated(true);
142
+ } else {
143
+ sessionStorage.removeItem("nbk_api_key");
131
144
  }
132
- }
133
- }, [apiKey]);
134
- const handleLogin = (e) => {
145
+ }).catch(() => {
146
+ if (key) setIsAuthenticated(true);
147
+ }).finally(() => setInitializing(false));
148
+ }, [apiKey, apiPath]);
149
+ const [loginError, setLoginError] = React4.useState("");
150
+ const [loginLoading, setLoginLoading] = React4.useState(false);
151
+ const handleLogin = async (e) => {
135
152
  e.preventDefault();
136
- if (inputKey.trim()) {
137
- sessionStorage.setItem("nbk_api_key", inputKey);
138
- setIsAuthenticated(true);
153
+ if (!inputKey.trim()) return;
154
+ setLoginError("");
155
+ setLoginLoading(true);
156
+ try {
157
+ const base = apiPath || "/api/blog";
158
+ const res = await fetch(`${base}/settings`, {
159
+ headers: { Authorization: `Bearer ${inputKey}` }
160
+ });
161
+ if (res.ok) {
162
+ sessionStorage.setItem("nbk_api_key", inputKey);
163
+ setIsAuthenticated(true);
164
+ } else {
165
+ setLoginError("Invalid API key");
166
+ }
167
+ } catch {
168
+ setLoginError("Unable to connect to server");
169
+ } finally {
170
+ setLoginLoading(false);
139
171
  }
140
172
  };
173
+ if (initializing) {
174
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "nbk-admin-login", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "nbk-login-card", children: /* @__PURE__ */ jsxRuntime.jsx("p", { className: "nbk-login-subtitle", children: "Verifying..." }) }) });
175
+ }
141
176
  if (!isAuthenticated) {
142
177
  return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "nbk-admin-login", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "nbk-login-card", children: [
143
178
  /* @__PURE__ */ jsxRuntime.jsx("h1", { className: "nbk-login-title", children: "Blog Admin" }),
@@ -151,10 +186,12 @@ function AdminLayout({ children, apiKey, apiPath, adminPath = "/admin/blog", bas
151
186
  onChange: (e) => setInputKey(e.target.value),
152
187
  placeholder: "Enter API key",
153
188
  className: "nbk-login-input",
154
- autoFocus: true
189
+ autoFocus: true,
190
+ disabled: loginLoading
155
191
  }
156
192
  ),
157
- /* @__PURE__ */ jsxRuntime.jsx("button", { type: "submit", className: "nbk-login-btn", children: "Sign In" })
193
+ loginError && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "nbk-login-error", children: loginError }),
194
+ /* @__PURE__ */ jsxRuntime.jsx("button", { type: "submit", className: "nbk-login-btn", disabled: loginLoading, children: loginLoading ? "Verifying..." : "Sign In" })
158
195
  ] })
159
196
  ] }) });
160
197
  }
@@ -203,17 +240,17 @@ function AdminLayout({ children, apiKey, apiPath, adminPath = "/admin/blog", bas
203
240
  }
204
241
  function Dashboard() {
205
242
  const api = useAdminApi();
206
- const [stats, setStats] = react.useState({
243
+ const [stats, setStats] = React4.useState({
207
244
  totalPosts: 0,
208
245
  publishedPosts: 0,
209
246
  draftPosts: 0,
210
247
  totalMedia: 0,
211
248
  totalCategories: 0
212
249
  });
213
- const [recentDrafts, setRecentDrafts] = react.useState([]);
214
- const [recentPublished, setRecentPublished] = react.useState([]);
215
- const [loading, setLoading] = react.useState(true);
216
- react.useEffect(() => {
250
+ const [recentDrafts, setRecentDrafts] = React4.useState([]);
251
+ const [recentPublished, setRecentPublished] = React4.useState([]);
252
+ const [loading, setLoading] = React4.useState(true);
253
+ React4.useEffect(() => {
217
254
  async function loadDashboard() {
218
255
  try {
219
256
  const [allPosts, published, drafts, media, categories] = await Promise.all([
@@ -291,15 +328,15 @@ function Dashboard() {
291
328
  }
292
329
  function PostList() {
293
330
  const api = useAdminApi();
294
- const [posts, setPosts] = react.useState([]);
295
- const [total, setTotal] = react.useState(0);
296
- const [page, setPage] = react.useState(1);
297
- const [statusFilter, setStatusFilter] = react.useState("");
298
- const [searchQuery, setSearchQuery] = react.useState("");
299
- const [loading, setLoading] = react.useState(true);
300
- const [selected, setSelected] = react.useState(/* @__PURE__ */ new Set());
331
+ const [posts, setPosts] = React4.useState([]);
332
+ const [total, setTotal] = React4.useState(0);
333
+ const [page, setPage] = React4.useState(1);
334
+ const [statusFilter, setStatusFilter] = React4.useState("");
335
+ const [searchQuery, setSearchQuery] = React4.useState("");
336
+ const [loading, setLoading] = React4.useState(true);
337
+ const [selected, setSelected] = React4.useState(/* @__PURE__ */ new Set());
301
338
  const limit = 20;
302
- const loadPosts = react.useCallback(async () => {
339
+ const loadPosts = React4.useCallback(async () => {
303
340
  setLoading(true);
304
341
  try {
305
342
  let path = `/posts?page=${page}&limit=${limit}`;
@@ -314,7 +351,7 @@ function PostList() {
314
351
  setLoading(false);
315
352
  }
316
353
  }, [page, statusFilter, searchQuery]);
317
- react.useEffect(() => {
354
+ React4.useEffect(() => {
318
355
  loadPosts();
319
356
  }, [loadPosts]);
320
357
  const handleDelete = async (id) => {
@@ -990,29 +1027,49 @@ function BlogEditor({
990
1027
  onChange,
991
1028
  onSave,
992
1029
  uploadImage,
1030
+ onBrowseMedia,
993
1031
  placeholder = 'Start writing your post... Type "/" for commands',
994
1032
  autosaveInterval = 3e4,
995
1033
  className = ""
996
1034
  }) {
997
- const [slashState, setSlashState] = react.useState({
1035
+ const [slashState, setSlashState] = React4.useState({
998
1036
  isOpen: false,
999
1037
  query: "",
1000
1038
  position: null,
1001
1039
  selectedIndex: 0,
1002
1040
  items: []
1003
1041
  });
1004
- const [wordCount, setWordCount] = react.useState(0);
1005
- const [isSaving, setIsSaving] = react.useState(false);
1006
- const autosaveTimerRef = react.useRef(null);
1007
- const lastSavedRef = react.useRef("");
1008
- const defaultUpload = react.useCallback(async (file) => {
1042
+ const [wordCount, setWordCount] = React4.useState(0);
1043
+ const [isSaving, setIsSaving] = React4.useState(false);
1044
+ const autosaveTimerRef = React4.useRef(null);
1045
+ const lastSavedRef = React4.useRef("");
1046
+ const defaultUpload = React4.useCallback(async (file) => {
1009
1047
  if (!uploadImage) {
1010
1048
  console.warn("[NextBlogKit] No uploadImage handler provided. Using blob URL \u2014 image will not persist across page reloads. Configure Cloudflare R2 for persistent image storage.");
1011
1049
  return { url: URL.createObjectURL(file), alt: file.name };
1012
1050
  }
1013
1051
  return uploadImage(file);
1014
1052
  }, [uploadImage]);
1015
- const editor = react$1.useEditor({
1053
+ const slashCommands = React4__default.default.useMemo(() => {
1054
+ if (!onBrowseMedia) return defaultSlashCommands;
1055
+ const imageIndex = defaultSlashCommands.findIndex((c) => c.title === "Image");
1056
+ const mediaItem = {
1057
+ title: "Media Library",
1058
+ description: "Choose from uploaded images",
1059
+ icon: "\u{1F4C1}",
1060
+ command: (editor2) => {
1061
+ onBrowseMedia().then((result) => {
1062
+ if (result) {
1063
+ editor2.chain().focus().setImage({ src: result.url, alt: result.alt || "" }).run();
1064
+ }
1065
+ });
1066
+ }
1067
+ };
1068
+ const cmds = [...defaultSlashCommands];
1069
+ cmds.splice(imageIndex + 1, 0, mediaItem);
1070
+ return cmds;
1071
+ }, [onBrowseMedia]);
1072
+ const editor = react.useEditor({
1016
1073
  extensions: [
1017
1074
  StarterKit__default.default.configure({
1018
1075
  codeBlock: false,
@@ -1038,6 +1095,7 @@ function BlogEditor({
1038
1095
  FAQAnswer,
1039
1096
  TableOfContents,
1040
1097
  SlashCommand.configure({
1098
+ commands: slashCommands,
1041
1099
  onStateChange: setSlashState
1042
1100
  })
1043
1101
  ],
@@ -1054,7 +1112,7 @@ function BlogEditor({
1054
1112
  }
1055
1113
  }
1056
1114
  });
1057
- react.useEffect(() => {
1115
+ React4.useEffect(() => {
1058
1116
  if (!onSave || !autosaveInterval || !editor) return;
1059
1117
  autosaveTimerRef.current = setInterval(() => {
1060
1118
  const json = JSON.stringify(editor.getJSON());
@@ -1242,10 +1300,24 @@ function BlogEditor({
1242
1300
  title: "Upload Image",
1243
1301
  children: "\u{1F4F7}"
1244
1302
  }
1303
+ ),
1304
+ onBrowseMedia && /* @__PURE__ */ jsxRuntime.jsx(
1305
+ "button",
1306
+ {
1307
+ onClick: async () => {
1308
+ const result = await onBrowseMedia();
1309
+ if (result && editor) {
1310
+ editor.chain().focus().setImage({ src: result.url, alt: result.alt || "" }).run();
1311
+ }
1312
+ },
1313
+ className: "nbk-toolbar-btn",
1314
+ title: "Choose from Media Library",
1315
+ children: "\u{1F5BC}"
1316
+ }
1245
1317
  )
1246
1318
  ] })
1247
1319
  ] }),
1248
- editor && /* @__PURE__ */ jsxRuntime.jsx(react$1.BubbleMenu, { editor, tippyOptions: { duration: 100 }, children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "nbk-bubble-menu", children: [
1320
+ editor && /* @__PURE__ */ jsxRuntime.jsx(react.BubbleMenu, { editor, tippyOptions: { duration: 100 }, children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "nbk-bubble-menu", children: [
1249
1321
  /* @__PURE__ */ jsxRuntime.jsx("button", { onClick: () => editor.chain().focus().toggleBold().run(), className: editor.isActive("bold") ? "active" : "", children: "B" }),
1250
1322
  /* @__PURE__ */ jsxRuntime.jsx("button", { onClick: () => editor.chain().focus().toggleItalic().run(), className: editor.isActive("italic") ? "active" : "", children: "I" }),
1251
1323
  /* @__PURE__ */ jsxRuntime.jsx("button", { onClick: () => editor.chain().focus().toggleCode().run(), className: editor.isActive("code") ? "active" : "", children: "</>" }),
@@ -1261,7 +1333,7 @@ function BlogEditor({
1261
1333
  }
1262
1334
  )
1263
1335
  ] }) }),
1264
- /* @__PURE__ */ jsxRuntime.jsx(react$1.EditorContent, { editor }),
1336
+ /* @__PURE__ */ jsxRuntime.jsx(react.EditorContent, { editor }),
1265
1337
  slashState.isOpen && slashState.position && /* @__PURE__ */ jsxRuntime.jsx(
1266
1338
  "div",
1267
1339
  {
@@ -1575,25 +1647,30 @@ function SEOPanel({ seo, onChange, title, slug, excerpt, basePath = "/blog" }) {
1575
1647
  }
1576
1648
  function PostEditor({ postId }) {
1577
1649
  const api = useAdminApi();
1578
- const [title, setTitle] = react.useState("");
1579
- const [slug, setSlug] = react.useState("");
1580
- const [content, setContent] = react.useState({ type: "doc", content: [{ type: "paragraph" }] });
1581
- const [excerpt, setExcerpt] = react.useState("");
1582
- const [status, setStatus] = react.useState("draft");
1583
- const [categories, setCategories] = react.useState([]);
1584
- const [tags, setTags] = react.useState("");
1585
- const [coverImageUrl, setCoverImageUrl] = react.useState("");
1586
- const [seo, setSeo] = react.useState({});
1587
- const [authorName, setAuthorName] = react.useState("");
1588
- const [scheduledAt, setScheduledAt] = react.useState("");
1589
- const [allCategories, setAllCategories] = react.useState([]);
1590
- const [saving, setSaving] = react.useState(false);
1591
- const [lastSaved, setLastSaved] = react.useState("");
1592
- const [error, setError] = react.useState("");
1593
- const [seoExpanded, setSeoExpanded] = react.useState(false);
1594
- const [loading, setLoading] = react.useState(!!postId);
1595
- const [sidebarOpen, setSidebarOpen] = react.useState(true);
1596
- react.useEffect(() => {
1650
+ const [title, setTitle] = React4.useState("");
1651
+ const [slug, setSlug] = React4.useState("");
1652
+ const [content, setContent] = React4.useState({ type: "doc", content: [{ type: "paragraph" }] });
1653
+ const [excerpt, setExcerpt] = React4.useState("");
1654
+ const [status, setStatus] = React4.useState("draft");
1655
+ const [categories, setCategories] = React4.useState([]);
1656
+ const [tags, setTags] = React4.useState("");
1657
+ const [coverImageUrl, setCoverImageUrl] = React4.useState("");
1658
+ const [seo, setSeo] = React4.useState({});
1659
+ const [authorName, setAuthorName] = React4.useState("");
1660
+ const [scheduledAt, setScheduledAt] = React4.useState("");
1661
+ const [allCategories, setAllCategories] = React4.useState([]);
1662
+ const [saving, setSaving] = React4.useState(false);
1663
+ const [lastSaved, setLastSaved] = React4.useState("");
1664
+ const [error, setError] = React4.useState("");
1665
+ const [seoExpanded, setSeoExpanded] = React4.useState(false);
1666
+ const [loading, setLoading] = React4.useState(!!postId);
1667
+ const [sidebarOpen, setSidebarOpen] = React4.useState(true);
1668
+ const [showMediaPicker, setShowMediaPicker] = React4.useState(false);
1669
+ const [mediaItems, setMediaItems] = React4.useState([]);
1670
+ const [mediaLoading, setMediaLoading] = React4.useState(false);
1671
+ const [mediaPickerTarget, setMediaPickerTarget] = React4.useState("cover");
1672
+ const [mediaPickerResolve, setMediaPickerResolve] = React4.useState(null);
1673
+ React4.useEffect(() => {
1597
1674
  api.get("/categories").then((res) => {
1598
1675
  setAllCategories(res.data || []);
1599
1676
  }).catch(() => {
@@ -1666,7 +1743,7 @@ function PostEditor({ postId }) {
1666
1743
  setSaving(false);
1667
1744
  }
1668
1745
  };
1669
- const handleAutosave = react.useCallback(
1746
+ const handleAutosave = React4.useCallback(
1670
1747
  (editorContent) => {
1671
1748
  if (!postId) return;
1672
1749
  const contentArray = editorContent.content || [];
@@ -1697,6 +1774,42 @@ function PostEditor({ postId }) {
1697
1774
  throw err;
1698
1775
  }
1699
1776
  };
1777
+ const openMediaPicker = async (target) => {
1778
+ setMediaPickerTarget(target);
1779
+ setShowMediaPicker(true);
1780
+ setMediaLoading(true);
1781
+ try {
1782
+ const res = await api.get("/media?limit=50");
1783
+ setMediaItems(res.data || []);
1784
+ } catch {
1785
+ setMediaItems([]);
1786
+ } finally {
1787
+ setMediaLoading(false);
1788
+ }
1789
+ };
1790
+ const selectMedia = (item) => {
1791
+ if (mediaPickerTarget === "cover") {
1792
+ setCoverImageUrl(item.url);
1793
+ }
1794
+ if (mediaPickerTarget === "editor" && mediaPickerResolve) {
1795
+ mediaPickerResolve(item);
1796
+ setMediaPickerResolve(null);
1797
+ }
1798
+ setShowMediaPicker(false);
1799
+ };
1800
+ const closeMediaPicker = () => {
1801
+ setShowMediaPicker(false);
1802
+ if (mediaPickerResolve) {
1803
+ mediaPickerResolve(null);
1804
+ setMediaPickerResolve(null);
1805
+ }
1806
+ };
1807
+ const handleBrowseMedia = () => {
1808
+ return new Promise((resolve) => {
1809
+ setMediaPickerResolve(() => resolve);
1810
+ openMediaPicker("editor");
1811
+ });
1812
+ };
1700
1813
  if (loading) {
1701
1814
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "nbk-post-editor", children: [
1702
1815
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "nbk-editor-header", children: /* @__PURE__ */ jsxRuntime.jsx("h1", { className: "nbk-page-title", children: "Loading..." }) }),
@@ -1759,7 +1872,8 @@ function PostEditor({ postId }) {
1759
1872
  content,
1760
1873
  onChange: setContent,
1761
1874
  onSave: postId ? handleAutosave : void 0,
1762
- uploadImage
1875
+ uploadImage,
1876
+ onBrowseMedia: handleBrowseMedia
1763
1877
  }
1764
1878
  )
1765
1879
  ] }),
@@ -1848,29 +1962,39 @@ function PostEditor({ postId }) {
1848
1962
  placeholder: "Image URL"
1849
1963
  }
1850
1964
  ),
1851
- /* @__PURE__ */ jsxRuntime.jsx(
1852
- "button",
1853
- {
1854
- onClick: async () => {
1855
- const input = document.createElement("input");
1856
- input.type = "file";
1857
- input.accept = "image/*";
1858
- input.onchange = async () => {
1859
- const file = input.files?.[0];
1860
- if (!file) return;
1861
- try {
1862
- const result = await uploadImage(file);
1863
- setCoverImageUrl(result.url);
1864
- } catch (err) {
1865
- console.error("Cover upload failed:", err);
1866
- }
1867
- };
1868
- input.click();
1869
- },
1870
- className: "nbk-btn nbk-btn-sm nbk-btn-secondary",
1871
- children: "Upload Cover Image"
1872
- }
1873
- )
1965
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", gap: "0.5rem", flexWrap: "wrap" }, children: [
1966
+ /* @__PURE__ */ jsxRuntime.jsx(
1967
+ "button",
1968
+ {
1969
+ onClick: async () => {
1970
+ const input = document.createElement("input");
1971
+ input.type = "file";
1972
+ input.accept = "image/*";
1973
+ input.onchange = async () => {
1974
+ const file = input.files?.[0];
1975
+ if (!file) return;
1976
+ try {
1977
+ const result = await uploadImage(file);
1978
+ setCoverImageUrl(result.url);
1979
+ } catch (err) {
1980
+ console.error("Cover upload failed:", err);
1981
+ }
1982
+ };
1983
+ input.click();
1984
+ },
1985
+ className: "nbk-btn nbk-btn-sm nbk-btn-secondary",
1986
+ children: "Upload New"
1987
+ }
1988
+ ),
1989
+ /* @__PURE__ */ jsxRuntime.jsx(
1990
+ "button",
1991
+ {
1992
+ onClick: () => openMediaPicker("cover"),
1993
+ className: "nbk-btn nbk-btn-sm nbk-btn-secondary",
1994
+ children: "Choose from Library"
1995
+ }
1996
+ )
1997
+ ] })
1874
1998
  ] }),
1875
1999
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "nbk-sidebar-section", children: [
1876
2000
  /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "nbk-sidebar-heading", children: "Author" }),
@@ -1923,20 +2047,117 @@ function PostEditor({ postId }) {
1923
2047
  )
1924
2048
  ] })
1925
2049
  ] })
1926
- ] })
2050
+ ] }),
2051
+ showMediaPicker && /* @__PURE__ */ jsxRuntime.jsx(
2052
+ "div",
2053
+ {
2054
+ style: {
2055
+ position: "fixed",
2056
+ inset: 0,
2057
+ background: "rgba(0,0,0,0.5)",
2058
+ zIndex: 9999,
2059
+ display: "flex",
2060
+ alignItems: "center",
2061
+ justifyContent: "center"
2062
+ },
2063
+ onClick: closeMediaPicker,
2064
+ children: /* @__PURE__ */ jsxRuntime.jsxs(
2065
+ "div",
2066
+ {
2067
+ style: {
2068
+ background: "var(--nbk-bg, #fff)",
2069
+ borderRadius: "var(--nbk-radius, 0.5rem)",
2070
+ width: "90%",
2071
+ maxWidth: "800px",
2072
+ maxHeight: "80vh",
2073
+ overflow: "hidden",
2074
+ display: "flex",
2075
+ flexDirection: "column"
2076
+ },
2077
+ onClick: (e) => e.stopPropagation(),
2078
+ children: [
2079
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: {
2080
+ display: "flex",
2081
+ justifyContent: "space-between",
2082
+ alignItems: "center",
2083
+ padding: "1rem 1.25rem",
2084
+ borderBottom: "1px solid var(--nbk-border, #e5e7eb)"
2085
+ }, children: [
2086
+ /* @__PURE__ */ jsxRuntime.jsx("h2", { style: { margin: 0, fontSize: "1.125rem", fontWeight: 600 }, children: "Choose from Media Library" }),
2087
+ /* @__PURE__ */ jsxRuntime.jsx(
2088
+ "button",
2089
+ {
2090
+ onClick: closeMediaPicker,
2091
+ style: {
2092
+ background: "none",
2093
+ border: "none",
2094
+ fontSize: "1.5rem",
2095
+ cursor: "pointer",
2096
+ color: "var(--nbk-text-muted)",
2097
+ lineHeight: 1
2098
+ },
2099
+ children: "\xD7"
2100
+ }
2101
+ )
2102
+ ] }),
2103
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: { padding: "1rem 1.25rem", overflowY: "auto", flex: 1 }, children: mediaLoading ? /* @__PURE__ */ jsxRuntime.jsx("div", { style: { textAlign: "center", padding: "2rem", color: "var(--nbk-text-muted)" }, children: "Loading media..." }) : mediaItems.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { style: { textAlign: "center", padding: "2rem", color: "var(--nbk-text-muted)" }, children: "No media files found. Upload images via the Media Library first." }) : /* @__PURE__ */ jsxRuntime.jsx("div", { style: {
2104
+ display: "grid",
2105
+ gridTemplateColumns: "repeat(auto-fill, minmax(120px, 1fr))",
2106
+ gap: "0.75rem"
2107
+ }, children: mediaItems.filter((m) => m.url).map((item) => /* @__PURE__ */ jsxRuntime.jsx(
2108
+ "button",
2109
+ {
2110
+ onClick: () => selectMedia({ url: item.url, alt: item.alt || item.originalName }),
2111
+ style: {
2112
+ background: "none",
2113
+ border: "2px solid var(--nbk-border, #e5e7eb)",
2114
+ borderRadius: "var(--nbk-radius, 0.5rem)",
2115
+ padding: "0.25rem",
2116
+ cursor: "pointer",
2117
+ overflow: "hidden",
2118
+ aspectRatio: "1",
2119
+ display: "flex",
2120
+ alignItems: "center",
2121
+ justifyContent: "center"
2122
+ },
2123
+ onMouseEnter: (e) => e.currentTarget.style.borderColor = "var(--nbk-primary, #2563eb)",
2124
+ onMouseLeave: (e) => e.currentTarget.style.borderColor = "var(--nbk-border, #e5e7eb)",
2125
+ title: item.originalName,
2126
+ children: /* @__PURE__ */ jsxRuntime.jsx(
2127
+ "img",
2128
+ {
2129
+ src: item.url,
2130
+ alt: item.alt || item.originalName,
2131
+ style: {
2132
+ width: "100%",
2133
+ height: "100%",
2134
+ objectFit: "cover",
2135
+ borderRadius: "calc(var(--nbk-radius, 0.5rem) - 4px)"
2136
+ },
2137
+ loading: "lazy"
2138
+ }
2139
+ )
2140
+ },
2141
+ item._id
2142
+ )) }) })
2143
+ ]
2144
+ }
2145
+ )
2146
+ }
2147
+ )
1927
2148
  ] });
1928
2149
  }
1929
2150
  function MediaLibrary() {
1930
2151
  const api = useAdminApi();
1931
- const [media, setMedia] = react.useState([]);
1932
- const [total, setTotal] = react.useState(0);
1933
- const [page, setPage] = react.useState(1);
1934
- const [loading, setLoading] = react.useState(true);
1935
- const [uploading, setUploading] = react.useState(false);
1936
- const [selected, setSelected] = react.useState(null);
1937
- const [dragOver, setDragOver] = react.useState(false);
2152
+ const [media, setMedia] = React4.useState([]);
2153
+ const [total, setTotal] = React4.useState(0);
2154
+ const [page, setPage] = React4.useState(1);
2155
+ const [loading, setLoading] = React4.useState(true);
2156
+ const [uploading, setUploading] = React4.useState(false);
2157
+ const [selected, setSelected] = React4.useState(null);
2158
+ const [dragOver, setDragOver] = React4.useState(false);
1938
2159
  const limit = 24;
1939
- const loadMedia = react.useCallback(async () => {
2160
+ const loadMedia = React4.useCallback(async () => {
1940
2161
  setLoading(true);
1941
2162
  try {
1942
2163
  const res = await api.get(`/media?page=${page}&limit=${limit}`);
@@ -1948,7 +2169,7 @@ function MediaLibrary() {
1948
2169
  setLoading(false);
1949
2170
  }
1950
2171
  }, [page]);
1951
- react.useEffect(() => {
2172
+ React4.useEffect(() => {
1952
2173
  loadMedia();
1953
2174
  }, [loadMedia]);
1954
2175
  const handleUpload = async (files) => {
@@ -2105,14 +2326,14 @@ function MediaLibrary() {
2105
2326
  }
2106
2327
  function CategoryManager() {
2107
2328
  const api = useAdminApi();
2108
- const [categories, setCategories] = react.useState([]);
2109
- const [loading, setLoading] = react.useState(true);
2110
- const [editingId, setEditingId] = react.useState(null);
2111
- const [name, setName] = react.useState("");
2112
- const [slug, setSlug] = react.useState("");
2113
- const [description, setDescription] = react.useState("");
2114
- const [error, setError] = react.useState("");
2115
- const loadCategories = react.useCallback(async () => {
2329
+ const [categories, setCategories] = React4.useState([]);
2330
+ const [loading, setLoading] = React4.useState(true);
2331
+ const [editingId, setEditingId] = React4.useState(null);
2332
+ const [name, setName] = React4.useState("");
2333
+ const [slug, setSlug] = React4.useState("");
2334
+ const [description, setDescription] = React4.useState("");
2335
+ const [error, setError] = React4.useState("");
2336
+ const loadCategories = React4.useCallback(async () => {
2116
2337
  try {
2117
2338
  const res = await api.get("/categories");
2118
2339
  setCategories(res.data || []);
@@ -2122,7 +2343,7 @@ function CategoryManager() {
2122
2343
  setLoading(false);
2123
2344
  }
2124
2345
  }, []);
2125
- react.useEffect(() => {
2346
+ React4.useEffect(() => {
2126
2347
  loadCategories();
2127
2348
  }, [loadCategories]);
2128
2349
  const resetForm = () => {
@@ -2279,16 +2500,16 @@ function CategoryManager() {
2279
2500
  }
2280
2501
  function ApiTokensSection() {
2281
2502
  const api = useAdminApi();
2282
- const [tokens, setTokens] = react.useState([]);
2283
- const [loading, setLoading] = react.useState(true);
2284
- const [tokenName, setTokenName] = react.useState("");
2285
- const [generating, setGenerating] = react.useState(false);
2286
- const [newToken, setNewToken] = react.useState("");
2287
- const [copied, setCopied] = react.useState(false);
2288
- const [error, setError] = react.useState("");
2289
- const [showDialog, setShowDialog] = react.useState(false);
2290
- const [revoking, setRevoking] = react.useState(null);
2291
- const fetchTokens = react.useCallback(async () => {
2503
+ const [tokens, setTokens] = React4.useState([]);
2504
+ const [loading, setLoading] = React4.useState(true);
2505
+ const [tokenName, setTokenName] = React4.useState("");
2506
+ const [generating, setGenerating] = React4.useState(false);
2507
+ const [newToken, setNewToken] = React4.useState("");
2508
+ const [copied, setCopied] = React4.useState(false);
2509
+ const [error, setError] = React4.useState("");
2510
+ const [showDialog, setShowDialog] = React4.useState(false);
2511
+ const [revoking, setRevoking] = React4.useState(null);
2512
+ const fetchTokens = React4.useCallback(async () => {
2292
2513
  try {
2293
2514
  const res = await api.get("/tokens");
2294
2515
  setTokens(res.data || []);
@@ -2298,7 +2519,7 @@ function ApiTokensSection() {
2298
2519
  setLoading(false);
2299
2520
  }
2300
2521
  }, []);
2301
- react.useEffect(() => {
2522
+ React4.useEffect(() => {
2302
2523
  fetchTokens();
2303
2524
  }, [fetchTokens]);
2304
2525
  const handleGenerate = async () => {
@@ -2463,9 +2684,9 @@ function ApiTokensSection() {
2463
2684
  ] });
2464
2685
  }
2465
2686
  function ApiReferenceSection() {
2466
- const [open, setOpen] = react.useState(false);
2467
- const [copiedCurl, setCopiedCurl] = react.useState(false);
2468
- const [copiedJson, setCopiedJson] = react.useState(false);
2687
+ const [open, setOpen] = React4.useState(false);
2688
+ const [copiedCurl, setCopiedCurl] = React4.useState(false);
2689
+ const [copiedJson, setCopiedJson] = React4.useState(false);
2469
2690
  const sampleJson = `{
2470
2691
  "title": "My Blog Post",
2471
2692
  "content": [{ "type": "paragraph", "content": [{ "type": "text", "text": "Hello world!" }] }],
@@ -2606,12 +2827,12 @@ function ApiReferenceSection() {
2606
2827
  }
2607
2828
  function SettingsPage() {
2608
2829
  const api = useAdminApi();
2609
- const [settings, setSettings] = react.useState({});
2610
- const [loading, setLoading] = react.useState(true);
2611
- const [saving, setSaving] = react.useState(false);
2612
- const [saved, setSaved] = react.useState(false);
2613
- const [error, setError] = react.useState("");
2614
- react.useEffect(() => {
2830
+ const [settings, setSettings] = React4.useState({});
2831
+ const [loading, setLoading] = React4.useState(true);
2832
+ const [saving, setSaving] = React4.useState(false);
2833
+ const [saved, setSaved] = React4.useState(false);
2834
+ const [error, setError] = React4.useState("");
2835
+ React4.useEffect(() => {
2615
2836
  api.get("/settings").then((res) => setSettings(res.data || {})).catch((err) => setError(err.message)).finally(() => setLoading(false));
2616
2837
  }, []);
2617
2838
  const handleSave = async () => {