nextblogkit 0.7.1 → 0.7.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -815,6 +815,7 @@ Type `/` in the editor to open the command menu:
815
815
  | Blockquote | Quote block |
816
816
  | Code Block | Syntax-highlighted code |
817
817
  | Image | Upload an image |
818
+ | Media Library | Choose from uploaded images (when `onBrowseMedia` is provided) |
818
819
  | Table | Insert a 3x3 table |
819
820
  | Divider | Horizontal rule |
820
821
  | Callout | Info/warning/tip/danger box |
@@ -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,7 +123,7 @@ function AdminLayout({ children, apiKey, apiPath, adminPath = "/admin/blog", bas
122
123
  setBasePath(basePath);
123
124
  }
124
125
  }, [apiPath, basePath]);
125
- react.useEffect(() => {
126
+ React4.useEffect(() => {
126
127
  if (typeof window !== "undefined") {
127
128
  setCurrentPath(window.location.pathname);
128
129
  const stored = sessionStorage.getItem("nbk_api_key");
@@ -203,17 +204,17 @@ function AdminLayout({ children, apiKey, apiPath, adminPath = "/admin/blog", bas
203
204
  }
204
205
  function Dashboard() {
205
206
  const api = useAdminApi();
206
- const [stats, setStats] = react.useState({
207
+ const [stats, setStats] = React4.useState({
207
208
  totalPosts: 0,
208
209
  publishedPosts: 0,
209
210
  draftPosts: 0,
210
211
  totalMedia: 0,
211
212
  totalCategories: 0
212
213
  });
213
- const [recentDrafts, setRecentDrafts] = react.useState([]);
214
- const [recentPublished, setRecentPublished] = react.useState([]);
215
- const [loading, setLoading] = react.useState(true);
216
- react.useEffect(() => {
214
+ const [recentDrafts, setRecentDrafts] = React4.useState([]);
215
+ const [recentPublished, setRecentPublished] = React4.useState([]);
216
+ const [loading, setLoading] = React4.useState(true);
217
+ React4.useEffect(() => {
217
218
  async function loadDashboard() {
218
219
  try {
219
220
  const [allPosts, published, drafts, media, categories] = await Promise.all([
@@ -291,15 +292,15 @@ function Dashboard() {
291
292
  }
292
293
  function PostList() {
293
294
  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());
295
+ const [posts, setPosts] = React4.useState([]);
296
+ const [total, setTotal] = React4.useState(0);
297
+ const [page, setPage] = React4.useState(1);
298
+ const [statusFilter, setStatusFilter] = React4.useState("");
299
+ const [searchQuery, setSearchQuery] = React4.useState("");
300
+ const [loading, setLoading] = React4.useState(true);
301
+ const [selected, setSelected] = React4.useState(/* @__PURE__ */ new Set());
301
302
  const limit = 20;
302
- const loadPosts = react.useCallback(async () => {
303
+ const loadPosts = React4.useCallback(async () => {
303
304
  setLoading(true);
304
305
  try {
305
306
  let path = `/posts?page=${page}&limit=${limit}`;
@@ -314,7 +315,7 @@ function PostList() {
314
315
  setLoading(false);
315
316
  }
316
317
  }, [page, statusFilter, searchQuery]);
317
- react.useEffect(() => {
318
+ React4.useEffect(() => {
318
319
  loadPosts();
319
320
  }, [loadPosts]);
320
321
  const handleDelete = async (id) => {
@@ -990,29 +991,49 @@ function BlogEditor({
990
991
  onChange,
991
992
  onSave,
992
993
  uploadImage,
994
+ onBrowseMedia,
993
995
  placeholder = 'Start writing your post... Type "/" for commands',
994
996
  autosaveInterval = 3e4,
995
997
  className = ""
996
998
  }) {
997
- const [slashState, setSlashState] = react.useState({
999
+ const [slashState, setSlashState] = React4.useState({
998
1000
  isOpen: false,
999
1001
  query: "",
1000
1002
  position: null,
1001
1003
  selectedIndex: 0,
1002
1004
  items: []
1003
1005
  });
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) => {
1006
+ const [wordCount, setWordCount] = React4.useState(0);
1007
+ const [isSaving, setIsSaving] = React4.useState(false);
1008
+ const autosaveTimerRef = React4.useRef(null);
1009
+ const lastSavedRef = React4.useRef("");
1010
+ const defaultUpload = React4.useCallback(async (file) => {
1009
1011
  if (!uploadImage) {
1010
1012
  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
1013
  return { url: URL.createObjectURL(file), alt: file.name };
1012
1014
  }
1013
1015
  return uploadImage(file);
1014
1016
  }, [uploadImage]);
1015
- const editor = react$1.useEditor({
1017
+ const slashCommands = React4__default.default.useMemo(() => {
1018
+ if (!onBrowseMedia) return defaultSlashCommands;
1019
+ const imageIndex = defaultSlashCommands.findIndex((c) => c.title === "Image");
1020
+ const mediaItem = {
1021
+ title: "Media Library",
1022
+ description: "Choose from uploaded images",
1023
+ icon: "\u{1F4C1}",
1024
+ command: (editor2) => {
1025
+ onBrowseMedia().then((result) => {
1026
+ if (result) {
1027
+ editor2.chain().focus().setImage({ src: result.url, alt: result.alt || "" }).run();
1028
+ }
1029
+ });
1030
+ }
1031
+ };
1032
+ const cmds = [...defaultSlashCommands];
1033
+ cmds.splice(imageIndex + 1, 0, mediaItem);
1034
+ return cmds;
1035
+ }, [onBrowseMedia]);
1036
+ const editor = react.useEditor({
1016
1037
  extensions: [
1017
1038
  StarterKit__default.default.configure({
1018
1039
  codeBlock: false,
@@ -1038,6 +1059,7 @@ function BlogEditor({
1038
1059
  FAQAnswer,
1039
1060
  TableOfContents,
1040
1061
  SlashCommand.configure({
1062
+ commands: slashCommands,
1041
1063
  onStateChange: setSlashState
1042
1064
  })
1043
1065
  ],
@@ -1054,7 +1076,7 @@ function BlogEditor({
1054
1076
  }
1055
1077
  }
1056
1078
  });
1057
- react.useEffect(() => {
1079
+ React4.useEffect(() => {
1058
1080
  if (!onSave || !autosaveInterval || !editor) return;
1059
1081
  autosaveTimerRef.current = setInterval(() => {
1060
1082
  const json = JSON.stringify(editor.getJSON());
@@ -1242,10 +1264,24 @@ function BlogEditor({
1242
1264
  title: "Upload Image",
1243
1265
  children: "\u{1F4F7}"
1244
1266
  }
1267
+ ),
1268
+ onBrowseMedia && /* @__PURE__ */ jsxRuntime.jsx(
1269
+ "button",
1270
+ {
1271
+ onClick: async () => {
1272
+ const result = await onBrowseMedia();
1273
+ if (result && editor) {
1274
+ editor.chain().focus().setImage({ src: result.url, alt: result.alt || "" }).run();
1275
+ }
1276
+ },
1277
+ className: "nbk-toolbar-btn",
1278
+ title: "Choose from Media Library",
1279
+ children: "\u{1F5BC}"
1280
+ }
1245
1281
  )
1246
1282
  ] })
1247
1283
  ] }),
1248
- editor && /* @__PURE__ */ jsxRuntime.jsx(react$1.BubbleMenu, { editor, tippyOptions: { duration: 100 }, children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "nbk-bubble-menu", children: [
1284
+ editor && /* @__PURE__ */ jsxRuntime.jsx(react.BubbleMenu, { editor, tippyOptions: { duration: 100 }, children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "nbk-bubble-menu", children: [
1249
1285
  /* @__PURE__ */ jsxRuntime.jsx("button", { onClick: () => editor.chain().focus().toggleBold().run(), className: editor.isActive("bold") ? "active" : "", children: "B" }),
1250
1286
  /* @__PURE__ */ jsxRuntime.jsx("button", { onClick: () => editor.chain().focus().toggleItalic().run(), className: editor.isActive("italic") ? "active" : "", children: "I" }),
1251
1287
  /* @__PURE__ */ jsxRuntime.jsx("button", { onClick: () => editor.chain().focus().toggleCode().run(), className: editor.isActive("code") ? "active" : "", children: "</>" }),
@@ -1261,7 +1297,7 @@ function BlogEditor({
1261
1297
  }
1262
1298
  )
1263
1299
  ] }) }),
1264
- /* @__PURE__ */ jsxRuntime.jsx(react$1.EditorContent, { editor }),
1300
+ /* @__PURE__ */ jsxRuntime.jsx(react.EditorContent, { editor }),
1265
1301
  slashState.isOpen && slashState.position && /* @__PURE__ */ jsxRuntime.jsx(
1266
1302
  "div",
1267
1303
  {
@@ -1575,25 +1611,30 @@ function SEOPanel({ seo, onChange, title, slug, excerpt, basePath = "/blog" }) {
1575
1611
  }
1576
1612
  function PostEditor({ postId }) {
1577
1613
  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(() => {
1614
+ const [title, setTitle] = React4.useState("");
1615
+ const [slug, setSlug] = React4.useState("");
1616
+ const [content, setContent] = React4.useState({ type: "doc", content: [{ type: "paragraph" }] });
1617
+ const [excerpt, setExcerpt] = React4.useState("");
1618
+ const [status, setStatus] = React4.useState("draft");
1619
+ const [categories, setCategories] = React4.useState([]);
1620
+ const [tags, setTags] = React4.useState("");
1621
+ const [coverImageUrl, setCoverImageUrl] = React4.useState("");
1622
+ const [seo, setSeo] = React4.useState({});
1623
+ const [authorName, setAuthorName] = React4.useState("");
1624
+ const [scheduledAt, setScheduledAt] = React4.useState("");
1625
+ const [allCategories, setAllCategories] = React4.useState([]);
1626
+ const [saving, setSaving] = React4.useState(false);
1627
+ const [lastSaved, setLastSaved] = React4.useState("");
1628
+ const [error, setError] = React4.useState("");
1629
+ const [seoExpanded, setSeoExpanded] = React4.useState(false);
1630
+ const [loading, setLoading] = React4.useState(!!postId);
1631
+ const [sidebarOpen, setSidebarOpen] = React4.useState(true);
1632
+ const [showMediaPicker, setShowMediaPicker] = React4.useState(false);
1633
+ const [mediaItems, setMediaItems] = React4.useState([]);
1634
+ const [mediaLoading, setMediaLoading] = React4.useState(false);
1635
+ const [mediaPickerTarget, setMediaPickerTarget] = React4.useState("cover");
1636
+ const [mediaPickerResolve, setMediaPickerResolve] = React4.useState(null);
1637
+ React4.useEffect(() => {
1597
1638
  api.get("/categories").then((res) => {
1598
1639
  setAllCategories(res.data || []);
1599
1640
  }).catch(() => {
@@ -1666,7 +1707,7 @@ function PostEditor({ postId }) {
1666
1707
  setSaving(false);
1667
1708
  }
1668
1709
  };
1669
- const handleAutosave = react.useCallback(
1710
+ const handleAutosave = React4.useCallback(
1670
1711
  (editorContent) => {
1671
1712
  if (!postId) return;
1672
1713
  const contentArray = editorContent.content || [];
@@ -1697,6 +1738,42 @@ function PostEditor({ postId }) {
1697
1738
  throw err;
1698
1739
  }
1699
1740
  };
1741
+ const openMediaPicker = async (target) => {
1742
+ setMediaPickerTarget(target);
1743
+ setShowMediaPicker(true);
1744
+ setMediaLoading(true);
1745
+ try {
1746
+ const res = await api.get("/media?limit=50");
1747
+ setMediaItems(res.data || []);
1748
+ } catch {
1749
+ setMediaItems([]);
1750
+ } finally {
1751
+ setMediaLoading(false);
1752
+ }
1753
+ };
1754
+ const selectMedia = (item) => {
1755
+ if (mediaPickerTarget === "cover") {
1756
+ setCoverImageUrl(item.url);
1757
+ }
1758
+ if (mediaPickerTarget === "editor" && mediaPickerResolve) {
1759
+ mediaPickerResolve(item);
1760
+ setMediaPickerResolve(null);
1761
+ }
1762
+ setShowMediaPicker(false);
1763
+ };
1764
+ const closeMediaPicker = () => {
1765
+ setShowMediaPicker(false);
1766
+ if (mediaPickerResolve) {
1767
+ mediaPickerResolve(null);
1768
+ setMediaPickerResolve(null);
1769
+ }
1770
+ };
1771
+ const handleBrowseMedia = () => {
1772
+ return new Promise((resolve) => {
1773
+ setMediaPickerResolve(() => resolve);
1774
+ openMediaPicker("editor");
1775
+ });
1776
+ };
1700
1777
  if (loading) {
1701
1778
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "nbk-post-editor", children: [
1702
1779
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "nbk-editor-header", children: /* @__PURE__ */ jsxRuntime.jsx("h1", { className: "nbk-page-title", children: "Loading..." }) }),
@@ -1759,7 +1836,8 @@ function PostEditor({ postId }) {
1759
1836
  content,
1760
1837
  onChange: setContent,
1761
1838
  onSave: postId ? handleAutosave : void 0,
1762
- uploadImage
1839
+ uploadImage,
1840
+ onBrowseMedia: handleBrowseMedia
1763
1841
  }
1764
1842
  )
1765
1843
  ] }),
@@ -1848,29 +1926,39 @@ function PostEditor({ postId }) {
1848
1926
  placeholder: "Image URL"
1849
1927
  }
1850
1928
  ),
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
- )
1929
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", gap: "0.5rem", flexWrap: "wrap" }, children: [
1930
+ /* @__PURE__ */ jsxRuntime.jsx(
1931
+ "button",
1932
+ {
1933
+ onClick: async () => {
1934
+ const input = document.createElement("input");
1935
+ input.type = "file";
1936
+ input.accept = "image/*";
1937
+ input.onchange = async () => {
1938
+ const file = input.files?.[0];
1939
+ if (!file) return;
1940
+ try {
1941
+ const result = await uploadImage(file);
1942
+ setCoverImageUrl(result.url);
1943
+ } catch (err) {
1944
+ console.error("Cover upload failed:", err);
1945
+ }
1946
+ };
1947
+ input.click();
1948
+ },
1949
+ className: "nbk-btn nbk-btn-sm nbk-btn-secondary",
1950
+ children: "Upload New"
1951
+ }
1952
+ ),
1953
+ /* @__PURE__ */ jsxRuntime.jsx(
1954
+ "button",
1955
+ {
1956
+ onClick: () => openMediaPicker("cover"),
1957
+ className: "nbk-btn nbk-btn-sm nbk-btn-secondary",
1958
+ children: "Choose from Library"
1959
+ }
1960
+ )
1961
+ ] })
1874
1962
  ] }),
1875
1963
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "nbk-sidebar-section", children: [
1876
1964
  /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "nbk-sidebar-heading", children: "Author" }),
@@ -1923,20 +2011,117 @@ function PostEditor({ postId }) {
1923
2011
  )
1924
2012
  ] })
1925
2013
  ] })
1926
- ] })
2014
+ ] }),
2015
+ showMediaPicker && /* @__PURE__ */ jsxRuntime.jsx(
2016
+ "div",
2017
+ {
2018
+ style: {
2019
+ position: "fixed",
2020
+ inset: 0,
2021
+ background: "rgba(0,0,0,0.5)",
2022
+ zIndex: 9999,
2023
+ display: "flex",
2024
+ alignItems: "center",
2025
+ justifyContent: "center"
2026
+ },
2027
+ onClick: closeMediaPicker,
2028
+ children: /* @__PURE__ */ jsxRuntime.jsxs(
2029
+ "div",
2030
+ {
2031
+ style: {
2032
+ background: "var(--nbk-bg, #fff)",
2033
+ borderRadius: "var(--nbk-radius, 0.5rem)",
2034
+ width: "90%",
2035
+ maxWidth: "800px",
2036
+ maxHeight: "80vh",
2037
+ overflow: "hidden",
2038
+ display: "flex",
2039
+ flexDirection: "column"
2040
+ },
2041
+ onClick: (e) => e.stopPropagation(),
2042
+ children: [
2043
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: {
2044
+ display: "flex",
2045
+ justifyContent: "space-between",
2046
+ alignItems: "center",
2047
+ padding: "1rem 1.25rem",
2048
+ borderBottom: "1px solid var(--nbk-border, #e5e7eb)"
2049
+ }, children: [
2050
+ /* @__PURE__ */ jsxRuntime.jsx("h2", { style: { margin: 0, fontSize: "1.125rem", fontWeight: 600 }, children: "Choose from Media Library" }),
2051
+ /* @__PURE__ */ jsxRuntime.jsx(
2052
+ "button",
2053
+ {
2054
+ onClick: closeMediaPicker,
2055
+ style: {
2056
+ background: "none",
2057
+ border: "none",
2058
+ fontSize: "1.5rem",
2059
+ cursor: "pointer",
2060
+ color: "var(--nbk-text-muted)",
2061
+ lineHeight: 1
2062
+ },
2063
+ children: "\xD7"
2064
+ }
2065
+ )
2066
+ ] }),
2067
+ /* @__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: {
2068
+ display: "grid",
2069
+ gridTemplateColumns: "repeat(auto-fill, minmax(120px, 1fr))",
2070
+ gap: "0.75rem"
2071
+ }, children: mediaItems.filter((m) => m.url).map((item) => /* @__PURE__ */ jsxRuntime.jsx(
2072
+ "button",
2073
+ {
2074
+ onClick: () => selectMedia({ url: item.url, alt: item.alt || item.originalName }),
2075
+ style: {
2076
+ background: "none",
2077
+ border: "2px solid var(--nbk-border, #e5e7eb)",
2078
+ borderRadius: "var(--nbk-radius, 0.5rem)",
2079
+ padding: "0.25rem",
2080
+ cursor: "pointer",
2081
+ overflow: "hidden",
2082
+ aspectRatio: "1",
2083
+ display: "flex",
2084
+ alignItems: "center",
2085
+ justifyContent: "center"
2086
+ },
2087
+ onMouseEnter: (e) => e.currentTarget.style.borderColor = "var(--nbk-primary, #2563eb)",
2088
+ onMouseLeave: (e) => e.currentTarget.style.borderColor = "var(--nbk-border, #e5e7eb)",
2089
+ title: item.originalName,
2090
+ children: /* @__PURE__ */ jsxRuntime.jsx(
2091
+ "img",
2092
+ {
2093
+ src: item.url,
2094
+ alt: item.alt || item.originalName,
2095
+ style: {
2096
+ width: "100%",
2097
+ height: "100%",
2098
+ objectFit: "cover",
2099
+ borderRadius: "calc(var(--nbk-radius, 0.5rem) - 4px)"
2100
+ },
2101
+ loading: "lazy"
2102
+ }
2103
+ )
2104
+ },
2105
+ item._id
2106
+ )) }) })
2107
+ ]
2108
+ }
2109
+ )
2110
+ }
2111
+ )
1927
2112
  ] });
1928
2113
  }
1929
2114
  function MediaLibrary() {
1930
2115
  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);
2116
+ const [media, setMedia] = React4.useState([]);
2117
+ const [total, setTotal] = React4.useState(0);
2118
+ const [page, setPage] = React4.useState(1);
2119
+ const [loading, setLoading] = React4.useState(true);
2120
+ const [uploading, setUploading] = React4.useState(false);
2121
+ const [selected, setSelected] = React4.useState(null);
2122
+ const [dragOver, setDragOver] = React4.useState(false);
1938
2123
  const limit = 24;
1939
- const loadMedia = react.useCallback(async () => {
2124
+ const loadMedia = React4.useCallback(async () => {
1940
2125
  setLoading(true);
1941
2126
  try {
1942
2127
  const res = await api.get(`/media?page=${page}&limit=${limit}`);
@@ -1948,7 +2133,7 @@ function MediaLibrary() {
1948
2133
  setLoading(false);
1949
2134
  }
1950
2135
  }, [page]);
1951
- react.useEffect(() => {
2136
+ React4.useEffect(() => {
1952
2137
  loadMedia();
1953
2138
  }, [loadMedia]);
1954
2139
  const handleUpload = async (files) => {
@@ -2105,14 +2290,14 @@ function MediaLibrary() {
2105
2290
  }
2106
2291
  function CategoryManager() {
2107
2292
  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 () => {
2293
+ const [categories, setCategories] = React4.useState([]);
2294
+ const [loading, setLoading] = React4.useState(true);
2295
+ const [editingId, setEditingId] = React4.useState(null);
2296
+ const [name, setName] = React4.useState("");
2297
+ const [slug, setSlug] = React4.useState("");
2298
+ const [description, setDescription] = React4.useState("");
2299
+ const [error, setError] = React4.useState("");
2300
+ const loadCategories = React4.useCallback(async () => {
2116
2301
  try {
2117
2302
  const res = await api.get("/categories");
2118
2303
  setCategories(res.data || []);
@@ -2122,7 +2307,7 @@ function CategoryManager() {
2122
2307
  setLoading(false);
2123
2308
  }
2124
2309
  }, []);
2125
- react.useEffect(() => {
2310
+ React4.useEffect(() => {
2126
2311
  loadCategories();
2127
2312
  }, [loadCategories]);
2128
2313
  const resetForm = () => {
@@ -2279,16 +2464,16 @@ function CategoryManager() {
2279
2464
  }
2280
2465
  function ApiTokensSection() {
2281
2466
  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 () => {
2467
+ const [tokens, setTokens] = React4.useState([]);
2468
+ const [loading, setLoading] = React4.useState(true);
2469
+ const [tokenName, setTokenName] = React4.useState("");
2470
+ const [generating, setGenerating] = React4.useState(false);
2471
+ const [newToken, setNewToken] = React4.useState("");
2472
+ const [copied, setCopied] = React4.useState(false);
2473
+ const [error, setError] = React4.useState("");
2474
+ const [showDialog, setShowDialog] = React4.useState(false);
2475
+ const [revoking, setRevoking] = React4.useState(null);
2476
+ const fetchTokens = React4.useCallback(async () => {
2292
2477
  try {
2293
2478
  const res = await api.get("/tokens");
2294
2479
  setTokens(res.data || []);
@@ -2298,7 +2483,7 @@ function ApiTokensSection() {
2298
2483
  setLoading(false);
2299
2484
  }
2300
2485
  }, []);
2301
- react.useEffect(() => {
2486
+ React4.useEffect(() => {
2302
2487
  fetchTokens();
2303
2488
  }, [fetchTokens]);
2304
2489
  const handleGenerate = async () => {
@@ -2463,9 +2648,9 @@ function ApiTokensSection() {
2463
2648
  ] });
2464
2649
  }
2465
2650
  function ApiReferenceSection() {
2466
- const [open, setOpen] = react.useState(false);
2467
- const [copiedCurl, setCopiedCurl] = react.useState(false);
2468
- const [copiedJson, setCopiedJson] = react.useState(false);
2651
+ const [open, setOpen] = React4.useState(false);
2652
+ const [copiedCurl, setCopiedCurl] = React4.useState(false);
2653
+ const [copiedJson, setCopiedJson] = React4.useState(false);
2469
2654
  const sampleJson = `{
2470
2655
  "title": "My Blog Post",
2471
2656
  "content": [{ "type": "paragraph", "content": [{ "type": "text", "text": "Hello world!" }] }],
@@ -2606,12 +2791,12 @@ function ApiReferenceSection() {
2606
2791
  }
2607
2792
  function SettingsPage() {
2608
2793
  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(() => {
2794
+ const [settings, setSettings] = React4.useState({});
2795
+ const [loading, setLoading] = React4.useState(true);
2796
+ const [saving, setSaving] = React4.useState(false);
2797
+ const [saved, setSaved] = React4.useState(false);
2798
+ const [error, setError] = React4.useState("");
2799
+ React4.useEffect(() => {
2615
2800
  api.get("/settings").then((res) => setSettings(res.data || {})).catch((err) => setError(err.message)).finally(() => setLoading(false));
2616
2801
  }, []);
2617
2802
  const handleSave = async () => {