hazo_blog 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -47,6 +47,10 @@ export const blogConfig: BlogConfig = {
47
47
  // getAuthor: (id) => lookupAuthor(id), // per-post authors
48
48
  // onAnalyticsEvent: (name, params) => gtag("event", name, params),
49
49
  revalidateSeconds: 3600,
50
+ // Admin URL overrides (omit to use defaults below):
51
+ // adminBasePath: "/admin/blog", // where admin UI pages are mounted
52
+ // adminApiBasePath: "/api/admin/blog", // where admin API routes are mounted
53
+ // searchApiPath: "/api/blog/search", // where search API route is mounted
50
54
  };
51
55
  ```
52
56
 
@@ -133,13 +137,52 @@ export default async function sitemap() {
133
137
  }
134
138
  ```
135
139
 
136
- ## 6. Admin authoring form
140
+ ## 6. Admin pages
141
+
142
+ ### Option A — Sealed admin pages (recommended)
143
+
144
+ Drop in the three sealed admin page factories. They render a full admin UI
145
+ (post list with status/publish-date badges + edit links, create form, edit form)
146
+ and respect your `authorize(req)` gate via the API routes.
147
+
148
+ ```tsx
149
+ // app/admin/blog/page.tsx — post list
150
+ import { createBlogAdminListPage } from "hazo_blog/next";
151
+ import { blogConfig } from "@/lib/blog-config";
152
+ const page = createBlogAdminListPage(blogConfig);
153
+ export default page.default;
154
+ export const dynamic = "force-dynamic"; // must be a static literal
155
+
156
+ // app/admin/blog/new/page.tsx — create form
157
+ import { createBlogAdminNewPage } from "hazo_blog/next";
158
+ const page = createBlogAdminNewPage(blogConfig);
159
+ export default page.default;
160
+ export const dynamic = "force-dynamic";
161
+
162
+ // app/admin/blog/[slug]/edit/page.tsx — edit form
163
+ import { createBlogAdminEditPage } from "hazo_blog/next";
164
+ const page = createBlogAdminEditPage(blogConfig);
165
+ export default page.default;
166
+ export const dynamic = "force-dynamic";
167
+ ```
168
+
169
+ If your admin API or UI routes live at non-default paths, override in `BlogConfig`:
170
+
171
+ ```ts
172
+ adminBasePath: "/admin/blog", // default — where factory pages link to
173
+ adminApiBasePath: "/api/admin/blog", // default — where PostForm POSTs to
174
+ ```
175
+
176
+ ### Option B — Custom admin form
177
+
178
+ Use `PostForm` directly for a fully custom admin UI:
137
179
 
138
180
  ```tsx
139
181
  "use client";
140
182
  import { PostForm } from "hazo_blog/client";
141
183
  // pass categories + an endpoint; wire onImageUpload to hazo_files for paste-upload
142
184
  <PostForm categories={categories} endpoint="/api/admin/blog" onSaved={...} />
185
+ // For edit: <PostForm post={existingPost} categories={categories} ... />
143
186
  ```
144
187
 
145
188
  ## Required Next.js config
@@ -167,10 +210,23 @@ const nextConfig = {
167
210
  |---|---|
168
211
  | `hazo_blog` | server: `createBlogService`, `createBlogRepository`, SEO builders, **route-handler factories** (React-free), types |
169
212
  | `hazo_blog/client` | client components: `PostCard`, `PostHero`, `AuthorBio`, `FaqSection`, `TableOfContents`, `RelatedPosts`, `BlogSearch`, `PostForm`, MDX components, `trackBlogEvent` |
170
- | `hazo_blog/next` | sealed page factories (`createBlogIndexPage/PostPage/TagPage`) + `BlogContent` (React) |
213
+ | `hazo_blog/next` | sealed page factories: public (`createBlogIndexPage`, `createBlogPostPage`, `createBlogTagPage`) + admin (`createBlogAdminListPage`, `createBlogAdminNewPage`, `createBlogAdminEditPage`) + `BlogContent` |
171
214
  | `hazo_blog/seo` | `buildBlogPostingJsonLd`, `buildBreadcrumbJsonLd`, `buildFaqJsonLd`, `getBlogSitemapEntries`, `getBlogRobotsRules`, `getBlogRssXml` |
172
215
  | `hazo_blog/config` | `BlogConfig` types + `resolveConfig` |
173
216
 
217
+ ### Search
218
+
219
+ `BlogSearch` (rendered inside `createBlogIndexPage`) supports:
220
+ - **Dropdown**: appears after 2+ characters with a 250 ms debounce
221
+ - **Enter key**: navigates to `${basePath}?q={term}` — the index page renders a filtered grid with a result count and Clear link
222
+ - **Escape**: dismisses the dropdown
223
+
224
+ If your search API lives at a non-default path, set `searchApiPath` in `BlogConfig`:
225
+
226
+ ```ts
227
+ searchApiPath: "/api/blog/search", // default
228
+ ```
229
+
174
230
  See `SETUP_CHECKLIST.md` for a step-by-step integration list. A runnable demo
175
231
  lives in `test-app/`.
176
232
 
@@ -25,6 +25,7 @@ for full code samples.
25
25
  - [ ] (multi-tenant only) set `resolveScope(req)`
26
26
  - [ ] (per-post authors) set `getAuthor(id)`
27
27
  - [ ] (analytics) set `onAnalyticsEvent` or rely on the `window.gtag` fallback
28
+ - [ ] (non-default paths) set `adminBasePath`, `adminApiBasePath`, `searchApiPath` if your routes differ from defaults (`/admin/blog`, `/api/admin/blog`, `/api/blog/search`)
28
29
 
29
30
  ## 4. Routes
30
31
 
@@ -37,6 +38,13 @@ for full code samples.
37
38
  - [ ] (optional) `app/api/blog/manage/route.ts` → `createBlogManageRoutes`
38
39
  - [ ] Merge `getBlogSitemapEntries()` into root `app/sitemap.ts` and `getBlogRobotsRules()` into `app/robots.ts`
39
40
 
41
+ ### Admin UI pages (sealed, recommended)
42
+
43
+ - [ ] `app/admin/blog/page.tsx` → `createBlogAdminListPage` + `export const dynamic = "force-dynamic"`
44
+ - [ ] `app/admin/blog/new/page.tsx` → `createBlogAdminNewPage` + `export const dynamic = "force-dynamic"`
45
+ - [ ] `app/admin/blog/[slug]/edit/page.tsx` → `createBlogAdminEditPage` + `export const dynamic = "force-dynamic"`
46
+ - [ ] **Note**: `dynamic` must be a static string literal — cannot re-export from the factory object
47
+
40
48
  ## 5. Next.js config (required)
41
49
 
42
50
  - [ ] `transpilePackages: ["hazo_blog", "hazo_ui", "next-mdx-remote"]` ← transpiling `next-mdx-remote` prevents the "React Element from an older version" MDX prerender error
@@ -50,5 +58,7 @@ for full code samples.
50
58
  - [ ] View-source a post: BlogPosting + BreadcrumbList JSON-LD present (FAQPage when the post has FAQ)
51
59
  - [ ] Drafts/future-scheduled posts return 404 and are absent from sitemap/RSS
52
60
  - [ ] `/api/blog/feed` returns valid RSS; `/api/blog/search?q=...` returns matches
53
- - [ ] Admin form (`PostForm`) saves a draft and publishes; published post appears at its URL
61
+ - [ ] Admin list page shows status badges (green=published, grey=draft) and publish dates
62
+ - [ ] Admin edit page pre-populates all fields; Save draft / Publish both work
63
+ - [ ] Search: typing 2+ chars shows dropdown; Enter navigates to `?q=` filtered index
54
64
  - [ ] Paste a post's HTML into Google's Rich Results Test — no errors
@@ -1 +1 @@
1
- {"version":3,"file":"blog-search.d.ts","sourceRoot":"","sources":["../../src/components/blog-search.tsx"],"names":[],"mappings":"AAaA,MAAM,WAAW,eAAe;IAC9B,iGAAiG;IACjG,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,wBAAgB,UAAU,CAAC,EACzB,QAAQ,EACR,QAAkB,EAClB,WAA6B,EAC7B,SAAS,GACV,EAAE,eAAe,2CA+DjB"}
1
+ {"version":3,"file":"blog-search.d.ts","sourceRoot":"","sources":["../../src/components/blog-search.tsx"],"names":[],"mappings":"AAcA,MAAM,WAAW,eAAe;IAC9B,iGAAiG;IACjG,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,wBAAgB,UAAU,CAAC,EACzB,QAAQ,EACR,QAAkB,EAClB,WAA6B,EAC7B,SAAS,GACV,EAAE,eAAe,2CAqFjB"}
@@ -1,6 +1,7 @@
1
1
  "use client";
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { useEffect, useRef, useState } from "react";
4
+ import { useRouter } from "next/navigation";
4
5
  import Link from "next/link";
5
6
  import { cn } from "hazo_ui/utils";
6
7
  import { trackBlogEvent } from "../lib/analytics.js";
@@ -10,6 +11,7 @@ export function BlogSearch({ endpoint, basePath = "/blog", placeholder = "Search
10
11
  const [results, setResults] = useState([]);
11
12
  const [open, setOpen] = useState(false);
12
13
  const timer = useRef(null);
14
+ const router = useRouter();
13
15
  useEffect(() => {
14
16
  if (timer.current)
15
17
  clearTimeout(timer.current);
@@ -39,5 +41,23 @@ export function BlogSearch({ endpoint, basePath = "/blog", placeholder = "Search
39
41
  clearTimeout(timer.current);
40
42
  };
41
43
  }, [query, url]);
42
- return (_jsxs("div", { className: cn("relative", className), children: [_jsx("input", { type: "search", value: query, onChange: (e) => setQuery(e.target.value), placeholder: placeholder, className: "w-full rounded-lg border border-border bg-background px-4 py-2 text-sm outline-none focus:ring-2 focus:ring-primary" }), open && results.length > 0 && (_jsx("ul", { className: "absolute z-10 mt-1 max-h-80 w-full overflow-auto rounded-lg border border-border bg-popover shadow-lg", children: results.map((r) => (_jsx("li", { children: _jsxs(Link, { href: `${basePath}/${r.slug}`, className: "block px-4 py-2 text-sm hover:bg-accent", onClick: () => setOpen(false), children: [_jsx("span", { className: "font-medium", children: r.title }), r.excerpt && (_jsx("span", { className: "block truncate text-xs text-muted-foreground", children: r.excerpt }))] }) }, r.slug))) }))] }));
44
+ function handleKeyDown(e) {
45
+ if (e.key === "Enter") {
46
+ const q = query.trim();
47
+ if (q.length >= 2) {
48
+ if (timer.current)
49
+ clearTimeout(timer.current);
50
+ setOpen(false);
51
+ setResults([]);
52
+ router.push(`${basePath}?q=${encodeURIComponent(q)}`);
53
+ }
54
+ }
55
+ if (e.key === "Escape") {
56
+ if (timer.current)
57
+ clearTimeout(timer.current);
58
+ setOpen(false);
59
+ setResults([]);
60
+ }
61
+ }
62
+ return (_jsxs("div", { className: cn("relative", className), children: [_jsx("input", { type: "search", value: query, onChange: (e) => setQuery(e.target.value), onKeyDown: handleKeyDown, placeholder: placeholder, className: "w-full rounded-lg border border-border bg-background px-4 py-2 text-sm outline-none focus:ring-2 focus:ring-primary" }), open && results.length > 0 && (_jsx("ul", { className: "absolute z-50 mt-1 max-h-80 w-full overflow-auto rounded-lg border border-zinc-200 shadow-xl", style: { backgroundColor: "white" }, children: results.map((r) => (_jsx("li", { className: "border-b border-zinc-100 last:border-0", children: _jsxs(Link, { href: `${basePath}/${r.slug}`, className: "block px-4 py-3 text-sm hover:bg-zinc-50", onClick: () => setOpen(false), children: [_jsx("span", { className: "font-medium text-zinc-900", children: r.title }), r.excerpt && (_jsx("span", { className: "block truncate text-xs text-zinc-500", children: r.excerpt }))] }) }, r.slug))) }))] }));
43
63
  }
@@ -1 +1 @@
1
- {"version":3,"file":"post-card.d.ts","sourceRoot":"","sources":["../../src/components/post-card.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,mBAAmB,CAAC;AAE/D,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,qBAAqB,CAAC;IAC5B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,wBAAgB,QAAQ,CAAC,EAAE,IAAI,EAAE,QAAkB,EAAE,SAAS,EAAE,EAAE,aAAa,2CA2C9E"}
1
+ {"version":3,"file":"post-card.d.ts","sourceRoot":"","sources":["../../src/components/post-card.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,mBAAmB,CAAC;AAE/D,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,qBAAqB,CAAC;IAC5B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,wBAAgB,QAAQ,CAAC,EAAE,IAAI,EAAE,QAAkB,EAAE,SAAS,EAAE,EAAE,aAAa,2CAkD9E"}
@@ -6,5 +6,5 @@ export function PostCard({ post, basePath = "/blog", className }) {
6
6
  return (_jsx("article", { className: cn("group overflow-hidden rounded-xl border border-border bg-card transition-shadow hover:shadow-md", className), children: _jsxs(Link, { href: `${basePath}/${post.slug}`, className: "block", children: [post.featured_image && (_jsx("div", { className: "relative aspect-video w-full overflow-hidden", children: _jsx(Image, { src: post.featured_image, alt: post.title, fill: true, className: "object-cover transition-transform duration-300 group-hover:scale-105", sizes: "(max-width: 768px) 100vw, 33vw" }) })), _jsxs("div", { className: "p-5", children: [post.category && (_jsx("span", { className: "mb-2 inline-block rounded-full px-2.5 py-0.5 text-xs font-medium", style: {
7
7
  backgroundColor: `${post.category.colour}20`,
8
8
  color: post.category.colour,
9
- }, children: post.category.name })), _jsx("h3", { className: "mb-1 line-clamp-2 text-lg font-semibold text-foreground group-hover:text-primary", children: post.title }), post.excerpt && (_jsx("p", { className: "line-clamp-2 text-sm text-muted-foreground", children: post.excerpt })), _jsxs("p", { className: "mt-3 text-xs text-muted-foreground", children: [post.reading_time, " min read"] })] })] }) }));
9
+ }, children: post.category.name })), _jsx("h3", { className: "mb-1 line-clamp-2 text-lg font-semibold text-foreground group-hover:text-primary", children: post.title }), post.excerpt && (_jsx("p", { className: "line-clamp-2 text-sm text-muted-foreground", children: post.excerpt })), _jsxs("p", { className: "mt-3 text-xs text-muted-foreground", children: [post.reading_time, " min read", post.publish_date && (_jsxs("span", { className: "ml-2 text-muted-foreground/60", children: ["\u00B7 ", new Date(post.publish_date).toLocaleDateString("en-US", { year: "numeric", month: "short", day: "numeric" })] }))] })] })] }) }));
10
10
  }
@@ -0,0 +1,9 @@
1
+ import type { BlogCategory, BlogPostWithRelations } from "../types/index.js";
2
+ /** Client wrapper used by sealed admin page factories to handle post-save routing. */
3
+ export declare function AdminFormClient({ post, categories, endpoint, redirectTo, }: {
4
+ post?: BlogPostWithRelations | null;
5
+ categories: BlogCategory[];
6
+ endpoint: string;
7
+ redirectTo: string;
8
+ }): import("react/jsx-runtime").JSX.Element;
9
+ //# sourceMappingURL=admin-client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"admin-client.d.ts","sourceRoot":"","sources":["../../src/next/admin-client.tsx"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,YAAY,EAAE,qBAAqB,EAAE,MAAM,mBAAmB,CAAC;AAE7E,sFAAsF;AACtF,wBAAgB,eAAe,CAAC,EAC9B,IAAI,EACJ,UAAU,EACV,QAAQ,EACR,UAAU,GACX,EAAE;IACD,IAAI,CAAC,EAAE,qBAAqB,GAAG,IAAI,CAAC;IACpC,UAAU,EAAE,YAAY,EAAE,CAAC;IAC3B,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;CACpB,2CAUA"}
@@ -0,0 +1,9 @@
1
+ "use client";
2
+ import { jsx as _jsx } from "react/jsx-runtime";
3
+ import { useRouter } from "next/navigation";
4
+ import { PostForm } from "../components/admin/post-form.js";
5
+ /** Client wrapper used by sealed admin page factories to handle post-save routing. */
6
+ export function AdminFormClient({ post, categories, endpoint, redirectTo, }) {
7
+ const router = useRouter();
8
+ return (_jsx(PostForm, { post: post ?? undefined, categories: categories, endpoint: endpoint, onSaved: () => router.push(redirectTo) }));
9
+ }
@@ -1,4 +1,4 @@
1
- export { createBlogIndexPage, createBlogPostPage, createBlogTagPage, } from "./pages.js";
1
+ export { createBlogIndexPage, createBlogPostPage, createBlogTagPage, createBlogAdminListPage, createBlogAdminNewPage, createBlogAdminEditPage, } from "./pages.js";
2
2
  export { BlogContent, type BlogContentProps } from "./blog-content.js";
3
3
  export { resolveConfig } from "../service/index.js";
4
4
  export type { BlogConfig, ResolvedBlogConfig } from "../types/index.js";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/next/index.ts"],"names":[],"mappings":"AASA,OAAO,EACL,mBAAmB,EACnB,kBAAkB,EAClB,iBAAiB,GAClB,MAAM,YAAY,CAAC;AAEpB,OAAO,EAAE,WAAW,EAAE,KAAK,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAOvE,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,YAAY,EAAE,UAAU,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/next/index.ts"],"names":[],"mappings":"AASA,OAAO,EACL,mBAAmB,EACnB,kBAAkB,EAClB,iBAAiB,EACjB,uBAAuB,EACvB,sBAAsB,EACtB,uBAAuB,GACxB,MAAM,YAAY,CAAC;AAEpB,OAAO,EAAE,WAAW,EAAE,KAAK,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAOvE,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,YAAY,EAAE,UAAU,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC"}
@@ -6,7 +6,7 @@
6
6
  // createBlogManageRoutes, createBlogAdminRoutes, createBlogSearchRoute,
7
7
  // createBlogFeedRoute, createBlogSitemapRoute
8
8
  // Server MDX renderer: BlogContent
9
- export { createBlogIndexPage, createBlogPostPage, createBlogTagPage, } from "./pages.js";
9
+ export { createBlogIndexPage, createBlogPostPage, createBlogTagPage, createBlogAdminListPage, createBlogAdminNewPage, createBlogAdminEditPage, } from "./pages.js";
10
10
  export { BlogContent } from "./blog-content.js";
11
11
  // NOTE: route-handler factories (createBlogAdminRoutes, createBlogManageRoutes,
12
12
  // createBlogSearchRoute, createBlogFeedRoute, createBlogSitemapRoute) are exported
@@ -48,5 +48,23 @@ export declare function createBlogTagPage(rawConfig: BlogConfig): {
48
48
  }) => Promise<Metadata>;
49
49
  revalidate: number;
50
50
  };
51
+ /** Sealed admin list page: mount at `${adminBasePath}` (default `/admin/blog`). */
52
+ export declare function createBlogAdminListPage(rawConfig: BlogConfig): {
53
+ default: () => Promise<import("react/jsx-runtime").JSX.Element>;
54
+ };
55
+ /** Sealed admin new-post page: mount at `${adminBasePath}/new`. */
56
+ export declare function createBlogAdminNewPage(rawConfig: BlogConfig): {
57
+ default: () => Promise<import("react/jsx-runtime").JSX.Element>;
58
+ };
59
+ /** Sealed admin edit page: mount at `${adminBasePath}/[slug]/edit`. */
60
+ export declare function createBlogAdminEditPage(rawConfig: BlogConfig): {
61
+ default: ({ params, }: {
62
+ params: Promise<{
63
+ slug: string;
64
+ }> | {
65
+ slug: string;
66
+ };
67
+ }) => Promise<import("react/jsx-runtime").JSX.Element>;
68
+ };
51
69
  export {};
52
70
  //# sourceMappingURL=pages.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"pages.d.ts","sourceRoot":"","sources":["../../src/next/pages.tsx"],"names":[],"mappings":"AA+BA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAGpD,KAAK,QAAQ,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAexC,qCAAqC;AACrC,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,UAAU;2BAuCnD;QACD,MAAM,EAAE,OAAO,CAAC;YAAE,IAAI,EAAE,MAAM,CAAA;SAAE,CAAC,GAAG;YAAE,IAAI,EAAE,MAAM,CAAA;SAAE,CAAC;KACtD;oCA9BE;QACD,MAAM,EAAE,OAAO,CAAC;YAAE,IAAI,EAAE,MAAM,CAAA;SAAE,CAAC,GAAG;YAAE,IAAI,EAAE,MAAM,CAAA;SAAE,CAAC;KACtD,KAAG,OAAO,CAAC,QAAQ,CAAC;;;;;;EA8DtB;AAED,+BAA+B;AAC/B,wBAAgB,mBAAmB,CAAC,SAAS,EAAE,UAAU;iCAcpD;QACD,YAAY,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;KACzE;4BAZkC,OAAO,CAAC,QAAQ,CAAC;;EA8DrD;AAED,uCAAuC;AACvC,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,UAAU;2BAMlD;QACD,MAAM,EAAE,OAAO,CAAC;YAAE,GAAG,EAAE,MAAM,CAAA;SAAE,CAAC,GAAG;YAAE,GAAG,EAAE,MAAM,CAAA;SAAE,CAAC;KACpD;oCAuBE;QACD,MAAM,EAAE,OAAO,CAAC;YAAE,GAAG,EAAE,MAAM,CAAA;SAAE,CAAC,GAAG;YAAE,GAAG,EAAE,MAAM,CAAA;SAAE,CAAC;KACpD,KAAG,OAAO,CAAC,QAAQ,CAAC;;EAStB"}
1
+ {"version":3,"file":"pages.d.ts","sourceRoot":"","sources":["../../src/next/pages.tsx"],"names":[],"mappings":"AAgCA,OAAO,KAAK,EAAE,UAAU,EAAyB,MAAM,mBAAmB,CAAC;AAG3E,KAAK,QAAQ,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAexC,qCAAqC;AACrC,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,UAAU;2BAuCnD;QACD,MAAM,EAAE,OAAO,CAAC;YAAE,IAAI,EAAE,MAAM,CAAA;SAAE,CAAC,GAAG;YAAE,IAAI,EAAE,MAAM,CAAA;SAAE,CAAC;KACtD;oCA9BE;QACD,MAAM,EAAE,OAAO,CAAC;YAAE,IAAI,EAAE,MAAM,CAAA;SAAE,CAAC,GAAG;YAAE,IAAI,EAAE,MAAM,CAAA;SAAE,CAAC;KACtD,KAAG,OAAO,CAAC,QAAQ,CAAC;;;;;;EA8DtB;AAED,+BAA+B;AAC/B,wBAAgB,mBAAmB,CAAC,SAAS,EAAE,UAAU;iCAcpD;QACD,YAAY,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;KACzE;4BAZkC,OAAO,CAAC,QAAQ,CAAC;;EAuFrD;AAED,uCAAuC;AACvC,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,UAAU;2BAMlD;QACD,MAAM,EAAE,OAAO,CAAC;YAAE,GAAG,EAAE,MAAM,CAAA;SAAE,CAAC,GAAG;YAAE,GAAG,EAAE,MAAM,CAAA;SAAE,CAAC;KACpD;oCAuBE;QACD,MAAM,EAAE,OAAO,CAAC;YAAE,GAAG,EAAE,MAAM,CAAA;SAAE,CAAC,GAAG;YAAE,GAAG,EAAE,MAAM,CAAA;SAAE,CAAC;KACpD,KAAG,OAAO,CAAC,QAAQ,CAAC;;EAStB;AAOD,mFAAmF;AACnF,wBAAgB,uBAAuB,CAAC,SAAS,EAAE,UAAU;;EAuE5D;AAED,mEAAmE;AACnE,wBAAgB,sBAAsB,CAAC,SAAS,EAAE,UAAU;;EAoB3D;AAED,uEAAuE;AACvE,wBAAgB,uBAAuB,CAAC,SAAS,EAAE,UAAU;2BAMxD;QACD,MAAM,EAAE,OAAO,CAAC;YAAE,IAAI,EAAE,MAAM,CAAA;SAAE,CAAC,GAAG;YAAE,IAAI,EAAE,MAAM,CAAA;SAAE,CAAC;KACtD;EAsBF"}
@@ -10,8 +10,9 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
10
10
  // export const { default, generateMetadata, generateStaticParams,
11
11
  // revalidate, dynamicParams } = createBlogPostPage(blogConfig);
12
12
  import { notFound } from "next/navigation";
13
- import { createBlogService } from "../service/index.js";
14
- import { resolveConfig } from "../service/index.js";
13
+ import Link from "next/link";
14
+ import { createBlogService, resolveConfig } from "../service/index.js";
15
+ import { AdminFormClient } from "./admin-client.js";
15
16
  import { buildBlogPostingJsonLd, buildBreadcrumbJsonLd, buildFaqJsonLd, canonicalFor, ogImageFor, } from "../seo/index.js";
16
17
  import { extractToc } from "../lib/text.js";
17
18
  import { PostHero } from "../components/post-hero.js";
@@ -101,9 +102,19 @@ export function createBlogIndexPage(rawConfig) {
101
102
  ? await searchParams
102
103
  : searchParams
103
104
  : {};
105
+ const q = (sp.q ?? "").trim();
104
106
  const page = Number(sp.page ?? "1") || 1;
105
- const { posts, totalPages } = await service.listPosts({ page, perPage: 12 });
106
- return (_jsxs("div", { className: "mx-auto max-w-6xl px-4 py-10", children: [_jsxs("div", { className: "mb-8 flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between", children: [_jsx("h1", { className: "text-3xl font-bold text-foreground", children: "Blog" }), _jsx(BlogSearch, { basePath: config.basePath, className: "sm:w-72" })] }), posts.length === 0 ? (_jsx("p", { className: "text-muted-foreground", children: "No posts yet." })) : (_jsx("div", { className: "grid gap-6 sm:grid-cols-2 lg:grid-cols-3", children: posts.map((p) => (_jsx(PostCard, { post: p, basePath: config.basePath }, p.id))) })), totalPages > 1 && (_jsx("nav", { className: "mt-10 flex justify-center gap-2 text-sm", children: Array.from({ length: totalPages }, (_, i) => i + 1).map((n) => (_jsx("a", { href: `${config.basePath}?page=${n}`, className: n === page
107
+ let displayPosts;
108
+ let totalPages = 1;
109
+ if (q) {
110
+ displayPosts = await service.search(q, 50);
111
+ }
112
+ else {
113
+ const paged = await service.listPosts({ page, perPage: 12 });
114
+ displayPosts = paged.posts;
115
+ totalPages = paged.totalPages;
116
+ }
117
+ return (_jsxs("div", { className: "mx-auto max-w-6xl px-4 py-10", children: [_jsxs("div", { className: "mb-8 flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between", children: [_jsxs("div", { children: [_jsx("h1", { className: "text-3xl font-bold text-foreground", children: "Blog" }), q && (_jsxs("p", { className: "mt-1 text-sm text-muted-foreground", children: [displayPosts.length, " result", displayPosts.length !== 1 ? "s" : "", " for \u201C", q, "\u201D \u00A0\u00B7\u00A0", _jsx("a", { href: config.basePath, className: "text-primary hover:underline", children: "Clear" })] }))] }), _jsx(BlogSearch, { basePath: config.basePath, endpoint: config.searchApiPath ?? `${config.basePath}/api/search`, className: "sm:w-72" })] }), displayPosts.length === 0 ? (_jsx("p", { className: "text-muted-foreground", children: q ? `No posts matching "${q}".` : "No posts yet." })) : (_jsx("div", { className: "grid gap-6 sm:grid-cols-2 lg:grid-cols-3", children: displayPosts.map((p) => (_jsx(PostCard, { post: p, basePath: config.basePath }, p.id))) })), !q && totalPages > 1 && (_jsx("nav", { className: "mt-10 flex justify-center gap-2 text-sm", children: Array.from({ length: totalPages }, (_, i) => i + 1).map((n) => (_jsx("a", { href: `${config.basePath}?page=${n}`, className: n === page
107
118
  ? "rounded bg-primary px-3 py-1 text-primary-foreground"
108
119
  : "rounded border border-border px-3 py-1 hover:bg-accent", children: n }, n))) }))] }));
109
120
  }
@@ -131,3 +142,50 @@ export function createBlogTagPage(rawConfig) {
131
142
  }
132
143
  return { default: Page, generateMetadata, revalidate: config.revalidateSeconds };
133
144
  }
145
+ function fmtDate(iso) {
146
+ if (!iso)
147
+ return "—";
148
+ return new Date(iso).toLocaleDateString("en-US", { year: "numeric", month: "short", day: "numeric" });
149
+ }
150
+ /** Sealed admin list page: mount at `${adminBasePath}` (default `/admin/blog`). */
151
+ export function createBlogAdminListPage(rawConfig) {
152
+ const config = resolveConfig(rawConfig);
153
+ const service = createBlogService(config);
154
+ async function Page() {
155
+ const { posts } = await service.listPosts({ perPage: 200, includeUnpublished: true });
156
+ return (_jsxs("div", { className: "mx-auto max-w-4xl px-4 py-10", children: [_jsxs("div", { className: "mb-8 flex items-center justify-between", children: [_jsx("h1", { className: "text-2xl font-bold text-foreground", children: "Blog posts" }), _jsx(Link, { href: `${config.adminBasePath}/new`, className: "rounded-lg bg-primary px-4 py-2 text-sm font-medium text-primary-foreground hover:opacity-90", children: "+ New post" })] }), posts.length === 0 ? (_jsx("p", { className: "text-muted-foreground", children: "No posts yet." })) : (_jsxs("table", { className: "w-full border-collapse text-sm", children: [_jsx("thead", { children: _jsxs("tr", { className: "border-b border-border text-left text-xs font-medium uppercase tracking-wide text-muted-foreground", children: [_jsx("th", { className: "pb-2 pr-6 font-medium", children: "Title" }), _jsx("th", { className: "pb-2 pr-6 font-medium", children: "Status" }), _jsx("th", { className: "pb-2 pr-6 font-medium", children: "Published" }), _jsx("th", { className: "pb-2 pr-6 font-medium", children: "Slug" }), _jsx("th", { className: "pb-2 font-medium" })] }) }), _jsx("tbody", { children: posts.map((p) => (_jsxs("tr", { className: "border-b border-border/40", children: [_jsx("td", { className: "py-3 pr-6 font-medium", children: _jsx(Link, { href: `${config.basePath}/${p.slug}`, className: "hover:text-primary", children: p.title }) }), _jsx("td", { className: "py-3 pr-6", children: _jsx("span", { className: p.status === "published"
157
+ ? "rounded-full bg-green-100 px-2 py-0.5 text-xs font-medium text-green-700"
158
+ : p.status === "scheduled"
159
+ ? "rounded-full bg-yellow-100 px-2 py-0.5 text-xs font-medium text-yellow-700"
160
+ : "rounded-full bg-muted px-2 py-0.5 text-xs font-medium text-muted-foreground", children: p.status }) }), _jsx("td", { className: "py-3 pr-6 text-muted-foreground", children: fmtDate(p.publish_date) }), _jsx("td", { className: "py-3 pr-6 text-muted-foreground", children: p.slug }), _jsx("td", { className: "py-3", children: _jsx(Link, { href: `${config.adminBasePath}/${p.slug}/edit`, className: "text-xs text-primary hover:underline", children: "Edit" }) })] }, p.id))) })] }))] }));
161
+ }
162
+ return { default: Page };
163
+ }
164
+ /** Sealed admin new-post page: mount at `${adminBasePath}/new`. */
165
+ export function createBlogAdminNewPage(rawConfig) {
166
+ const config = resolveConfig(rawConfig);
167
+ const service = createBlogService(config);
168
+ async function Page() {
169
+ const repo = await service.repository();
170
+ const categories = await repo.listCategories();
171
+ return (_jsxs("div", { className: "mx-auto max-w-4xl px-4 py-10", children: [_jsx("h1", { className: "mb-6 text-2xl font-bold text-foreground", children: "New post" }), _jsx(AdminFormClient, { categories: categories, endpoint: config.adminApiBasePath, redirectTo: config.adminBasePath })] }));
172
+ }
173
+ return { default: Page };
174
+ }
175
+ /** Sealed admin edit page: mount at `${adminBasePath}/[slug]/edit`. */
176
+ export function createBlogAdminEditPage(rawConfig) {
177
+ const config = resolveConfig(rawConfig);
178
+ const service = createBlogService(config);
179
+ async function Page({ params, }) {
180
+ const { slug } = params instanceof Promise ? await params : params;
181
+ const [post, repo] = await Promise.all([
182
+ service.getPost(slug, { includeUnpublished: true }),
183
+ service.repository(),
184
+ ]);
185
+ if (!post)
186
+ notFound();
187
+ const categories = await repo.listCategories();
188
+ return (_jsxs("div", { className: "mx-auto max-w-4xl px-4 py-10", children: [_jsxs("h1", { className: "mb-6 text-2xl font-bold text-foreground", children: ["Edit: ", post.title] }), _jsx(AdminFormClient, { post: post, categories: categories, endpoint: config.adminApiBasePath, redirectTo: config.adminBasePath })] }));
189
+ }
190
+ return { default: Page };
191
+ }
package/dist/seo/index.js CHANGED
@@ -41,7 +41,7 @@ export function buildBlogPostingJsonLd(config, post, author) {
41
41
  return {
42
42
  "@context": "https://schema.org",
43
43
  "@type": "BlogPosting",
44
- headline: post.meta_title || post.title,
44
+ headline: post.title,
45
45
  description: post.meta_description || buildExcerpt(post.content),
46
46
  image: ogImageFor(config, post),
47
47
  datePublished: post.publish_date ?? post.created_at,
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/service/index.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EACV,UAAU,EACV,UAAU,EACV,QAAQ,EACR,qBAAqB,EACrB,cAAc,EACd,kBAAkB,EAClB,eAAe,EAChB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAEL,KAAK,cAAc,EACnB,KAAK,SAAS,EACf,MAAM,wBAAwB,CAAC;AAGhC,oDAAoD;AACpD,wBAAgB,aAAa,CAAC,MAAM,EAAE,UAAU,GAAG,kBAAkB,CAMpE;AAED,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,kBAAkB,CAAC;IAC3B,+EAA+E;IAC/E,UAAU,CAAC,GAAG,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC,cAAc,CAAC,CAAC;IACnD,SAAS,CAAC,KAAK,CAAC,EAAE,SAAS,EAAE,GAAG,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC,cAAc,CAAC,CAAC;IACrE,OAAO,CACL,IAAI,EAAE,MAAM,EACZ,IAAI,CAAC,EAAE;QAAE,kBAAkB,CAAC,EAAE,OAAO,CAAA;KAAE,EACvC,GAAG,CAAC,EAAE,OAAO,GACZ,OAAO,CAAC,qBAAqB,GAAG,IAAI,CAAC,CAAC;IACzC,WAAW,CAAC,kBAAkB,CAAC,EAAE,OAAO,EAAE,GAAG,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;IAC5E,YAAY,CAAC,KAAK,EAAE,eAAe,EAAE,GAAG,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC;IACvE,UAAU,CAAC,EAAE,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACrD,aAAa,CAAC,IAAI,EAAE,qBAAqB,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;IAChE,eAAe,CACb,IAAI,EAAE,qBAAqB,EAC3B,KAAK,CAAC,EAAE,MAAM,EACd,GAAG,CAAC,EAAE,OAAO,GACZ,OAAO,CAAC,qBAAqB,EAAE,CAAC,CAAC;IACpC,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC,qBAAqB,EAAE,CAAC,CAAC;CACvF;AAED,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,UAAU,GAAG,WAAW,CA6GpE"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/service/index.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EACV,UAAU,EACV,UAAU,EACV,QAAQ,EACR,qBAAqB,EACrB,cAAc,EACd,kBAAkB,EAClB,eAAe,EAChB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAEL,KAAK,cAAc,EACnB,KAAK,SAAS,EACf,MAAM,wBAAwB,CAAC;AAGhC,oDAAoD;AACpD,wBAAgB,aAAa,CAAC,MAAM,EAAE,UAAU,GAAG,kBAAkB,CAQpE;AAED,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,kBAAkB,CAAC;IAC3B,+EAA+E;IAC/E,UAAU,CAAC,GAAG,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC,cAAc,CAAC,CAAC;IACnD,SAAS,CAAC,KAAK,CAAC,EAAE,SAAS,EAAE,GAAG,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC,cAAc,CAAC,CAAC;IACrE,OAAO,CACL,IAAI,EAAE,MAAM,EACZ,IAAI,CAAC,EAAE;QAAE,kBAAkB,CAAC,EAAE,OAAO,CAAA;KAAE,EACvC,GAAG,CAAC,EAAE,OAAO,GACZ,OAAO,CAAC,qBAAqB,GAAG,IAAI,CAAC,CAAC;IACzC,WAAW,CAAC,kBAAkB,CAAC,EAAE,OAAO,EAAE,GAAG,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;IAC5E,YAAY,CAAC,KAAK,EAAE,eAAe,EAAE,GAAG,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC;IACvE,UAAU,CAAC,EAAE,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACrD,aAAa,CAAC,IAAI,EAAE,qBAAqB,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;IAChE,eAAe,CACb,IAAI,EAAE,qBAAqB,EAC3B,KAAK,CAAC,EAAE,MAAM,EACd,GAAG,CAAC,EAAE,OAAO,GACZ,OAAO,CAAC,qBAAqB,EAAE,CAAC,CAAC;IACpC,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC,qBAAqB,EAAE,CAAC,CAAC;CACvF;AAED,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,UAAU,GAAG,WAAW,CA6GpE"}
@@ -9,6 +9,8 @@ export function resolveConfig(config) {
9
9
  ...config,
10
10
  basePath: config.basePath?.replace(/\/$/, "") || "/blog",
11
11
  revalidateSeconds: config.revalidateSeconds ?? 3600,
12
+ adminBasePath: config.adminBasePath?.replace(/\/$/, "") || "/admin/blog",
13
+ adminApiBasePath: config.adminApiBasePath?.replace(/\/$/, "") || "/api/admin/blog",
12
14
  };
13
15
  }
14
16
  export function createBlogService(rawConfig) {
@@ -132,10 +132,18 @@ export interface BlogConfig {
132
132
  onAnalyticsEvent?: (name: BlogAnalyticsEvent, params: Record<string, unknown>) => void;
133
133
  /** ISR revalidate window in seconds. Default 3600. */
134
134
  revalidateSeconds?: number;
135
+ /** Override the search API endpoint (default: `${basePath}/api/search`). */
136
+ searchApiPath?: string;
137
+ /** Where the admin UI pages are mounted (default `/admin/blog`). */
138
+ adminBasePath?: string;
139
+ /** Where the admin API routes are mounted (default `/api/admin/blog`). */
140
+ adminApiBasePath?: string;
135
141
  }
136
142
  /** Resolved config with defaults applied. */
137
143
  export interface ResolvedBlogConfig extends BlogConfig {
138
144
  basePath: string;
139
145
  revalidateSeconds: number;
146
+ adminBasePath: string;
147
+ adminApiBasePath: string;
140
148
  }
141
149
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,OAAO,CAAC;AAE3C,gFAAgF;AAChF,MAAM,MAAM,UAAU,GAAG,OAAO,GAAG,WAAW,GAAG,WAAW,CAAC;AAE7D,6EAA6E;AAC7E,MAAM,WAAW,OAAO;IACtB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,yCAAyC;AACzC,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,iDAAiD;AACjD,MAAM,WAAW,OAAO;IACtB,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,kFAAkF;AAClF,MAAM,WAAW,QAAQ;IACvB,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,UAAU,EAAE,MAAM,CAAC;IACnB,gBAAgB,EAAE,MAAM,CAAC;IACzB,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,cAAc,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,UAAU,CAAC;IACnB,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,oEAAoE;AACpE,MAAM,WAAW,qBAAsB,SAAQ,QAAQ;IACrD,QAAQ,CAAC,EAAE,YAAY,GAAG,IAAI,CAAC;IAC/B,IAAI,EAAE,OAAO,EAAE,CAAC;IAChB,GAAG,EAAE,OAAO,EAAE,CAAC;CAChB;AAED,iEAAiE;AACjE,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,2DAA2D;AAC3D,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,cAAc,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,GAAG,CAAC,EAAE,OAAO,EAAE,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,MAAM,CAAC,EAAE,UAAU,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC9B;AAED,iCAAiC;AACjC,MAAM,WAAW,gBAAgB;IAC/B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,sEAAsE;IACtE,kBAAkB,CAAC,EAAE,OAAO,CAAC;CAC9B;AAED,6BAA6B;AAC7B,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,qBAAqB,EAAE,CAAC;IAC/B,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,iDAAiD;AACjD,MAAM,MAAM,kBAAkB,GAC1B,gBAAgB,GAChB,oBAAoB,GACpB,aAAa,GACb,YAAY,CAAC;AAEjB;;;GAGG;AACH,MAAM,WAAW,UAAU;IACzB,qDAAqD;IACrD,QAAQ,EAAE,MAAM,CAAC;IACjB,4EAA4E;IAC5E,OAAO,EAAE,MAAM,CAAC;IAChB,uCAAuC;IACvC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,kDAAkD;IAClD,OAAO,EAAE,MAAM,CAAC;IAChB,sCAAsC;IACtC,cAAc,EAAE,MAAM,CAAC;IACvB,+DAA+D;IAC/D,MAAM,EAAE,UAAU,CAAC;IACnB,6DAA6D;IAC7D,SAAS,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,UAAU,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;IAC7D,0EAA0E;IAC1E,cAAc,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC;IACjD,qEAAqE;IACrE,SAAS,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACxD,iEAAiE;IACjE,YAAY,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,MAAM,GAAG,IAAI,CAAC;IAC/C,iEAAiE;IACjE,aAAa,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC,OAAO,CAAC,CAAC,CAAC;IACvD,wEAAwE;IACxE,gBAAgB,CAAC,EAAE,CAAC,IAAI,EAAE,kBAAkB,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAC;IACvF,sDAAsD;IACtD,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,4EAA4E;IAC5E,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,oEAAoE;IACpE,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,0EAA0E;IAC1E,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED,6CAA6C;AAC7C,MAAM,WAAW,kBAAmB,SAAQ,UAAU;IACpD,QAAQ,EAAE,MAAM,CAAC;IACjB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,aAAa,EAAE,MAAM,CAAC;IACtB,gBAAgB,EAAE,MAAM,CAAC;CAC1B"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hazo_blog",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "SEO-optimized blogging package: posts, categories, tags, MDX content, and GA4/GSC/Bing-ready SEO.",
5
5
  "type": "module",
6
6
  "module": "./dist/index.js",
@@ -39,11 +39,13 @@
39
39
  "scripts": {
40
40
  "build": "tsc -p tsconfig.build.json",
41
41
  "type-check": "tsc --noEmit",
42
- "test": "node --experimental-vm-modules ../node_modules/jest/bin/jest.js --config jest.config.cjs"
42
+ "test": "node --experimental-vm-modules ../node_modules/jest/bin/jest.js --config jest.config.cjs",
43
+ "dev:test-app": "npm run build && cd test-app && npm run dev",
44
+ "build:test-app": "npm run build && cd test-app && npm run build"
43
45
  },
44
46
  "peerDependencies": {
45
47
  "hazo_core": "^1.0.1",
46
- "hazo_connect": "^3.0.0",
48
+ "hazo_connect": "^3.1.0",
47
49
  "hazo_api": "^2.1.1",
48
50
  "hazo_files": "^3.0.0",
49
51
  "hazo_ui": "^3.2.0",
@@ -55,9 +57,15 @@
55
57
  "next": "^14.0.0 || ^16.0.0"
56
58
  },
57
59
  "peerDependenciesMeta": {
58
- "hazo_images": { "optional": true },
59
- "hazo_jobs": { "optional": true },
60
- "hazo_auth": { "optional": true }
60
+ "hazo_images": {
61
+ "optional": true
62
+ },
63
+ "hazo_jobs": {
64
+ "optional": true
65
+ },
66
+ "hazo_auth": {
67
+ "optional": true
68
+ }
61
69
  },
62
70
  "dependencies": {
63
71
  "next-mdx-remote": "^6.0.0",
@@ -76,10 +84,10 @@
76
84
  "react-dom": "^19.0.0",
77
85
  "next": "^16.0.10",
78
86
  "hazo_core": "^1.0.1",
79
- "hazo_connect": "^3.0.0",
87
+ "hazo_connect": "^3.1.0",
80
88
  "hazo_api": "^2.1.1",
81
89
  "hazo_files": "^3.0.0",
82
- "hazo_ui": "^3.2.0",
90
+ "hazo_ui": "^3.2.1",
83
91
  "tailwindcss": "^4.2.4",
84
92
  "@tailwindcss/postcss": "^4.2.4",
85
93
  "postcss": "^8.4.49"