@fayz-ai/plugin-blog 0.11.0 → 0.12.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 +2 -2
- package/dist/BlogList-FBNCM3S3.js +6 -0
- package/dist/BlogList-FBNCM3S3.js.map +1 -0
- package/dist/PostDetail-AM2XHIMM.js +6 -0
- package/dist/PostDetail-AM2XHIMM.js.map +1 -0
- package/dist/chunk-3TZYTDNI.js +21 -0
- package/dist/chunk-3TZYTDNI.js.map +1 -0
- package/dist/chunk-GTH5Z4I6.js +33 -0
- package/dist/chunk-GTH5Z4I6.js.map +1 -0
- package/dist/chunk-HBD463WC.js +36 -0
- package/dist/chunk-HBD463WC.js.map +1 -0
- package/dist/chunk-LCYIM3AB.js +80 -0
- package/dist/chunk-LCYIM3AB.js.map +1 -0
- package/dist/chunk-TYSSR2VZ.js +93 -0
- package/dist/chunk-TYSSR2VZ.js.map +1 -0
- package/dist/chunk-YI3WLZ4S.js +68 -0
- package/dist/chunk-YI3WLZ4S.js.map +1 -0
- package/dist/components/index.d.ts +5 -0
- package/dist/components/index.d.ts.map +1 -0
- package/dist/components.js +8 -0
- package/dist/components.js.map +1 -0
- package/dist/index.d.ts +0 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +18 -288
- package/dist/index.js.map +1 -1
- package/package.json +6 -2
package/README.md
CHANGED
|
@@ -12,8 +12,8 @@ Marketing sites need a blog, and every one ends up rebuilt from scratch. `plugin
|
|
|
12
12
|
It follows the "website plugin" (Model A) pattern: instead of a `defineSaas` plugin, `createBlogPlugin` returns a manifest (with public routes), an app-root `Provider`, and the resolved data provider. Drop the routes into your router, wrap your app in the `Provider`, and you have a blog.
|
|
13
13
|
|
|
14
14
|
## What's inside
|
|
15
|
-
- **Public routes** — `GET /blog` (list) and `/blog/:slug` (detail), both `guard: "public"`, mounted under a configurable `basePath
|
|
16
|
-
- **Components** — `BlogList`, `PostDetail`, `PostCard`, `AuthorAvatar` (
|
|
15
|
+
- **Public routes** — `GET /blog` (list) and `/blog/:slug` (detail), both `guard: "public"`, mounted under a configurable `basePath`. Their components load lazily, so a host that never mounts them (a storefront on the SDK's router adapter, a TanStack Start host) never downloads them — or the react-router they are written against
|
|
16
|
+
- **Components** — `BlogList`, `PostDetail`, `PostCard`, `AuthorAvatar`, on their own door: `import { BlogList } from '@fayz-ai/plugin-blog/components'` (they are bound to react-router-dom; the main entry is router-free)
|
|
17
17
|
- **Hooks** — `useBlogPosts`, `usePost`, and `useBlogContext`
|
|
18
18
|
- **Data seam** — `createSafeDataProvider` picks `createSupabaseBlogProvider` when configured, else `createMockBlogProvider` seeded from your `seed` posts
|
|
19
19
|
- **SEO** — per-page `useSeo` with `og:site_name`, plus organization + breadcrumb JSON-LD
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"names":[],"mappings":"","file":"BlogList-FBNCM3S3.js"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"names":[],"mappings":"","file":"PostDetail-AM2XHIMM.js"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { jsx } from 'react/jsx-runtime';
|
|
2
|
+
|
|
3
|
+
// src/components/AuthorAvatar.tsx
|
|
4
|
+
function AuthorAvatar({ author, className = "h-8 w-8 text-xs" }) {
|
|
5
|
+
if (author.avatarUrl) {
|
|
6
|
+
return /* @__PURE__ */ jsx(
|
|
7
|
+
"img",
|
|
8
|
+
{
|
|
9
|
+
src: author.avatarUrl,
|
|
10
|
+
alt: author.name,
|
|
11
|
+
loading: "lazy",
|
|
12
|
+
className: `shrink-0 rounded-full object-cover ${className}`
|
|
13
|
+
}
|
|
14
|
+
);
|
|
15
|
+
}
|
|
16
|
+
return /* @__PURE__ */ jsx("span", { className: `flex shrink-0 items-center justify-center rounded-full bg-accent font-semibold text-primary ${className}`, children: author.name.charAt(0).toUpperCase() });
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export { AuthorAvatar };
|
|
20
|
+
//# sourceMappingURL=chunk-3TZYTDNI.js.map
|
|
21
|
+
//# sourceMappingURL=chunk-3TZYTDNI.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/components/AuthorAvatar.tsx"],"names":[],"mappings":";;;AAGO,SAAS,YAAA,CAAa,EAAE,MAAA,EAAQ,SAAA,GAAY,mBAAkB,EAA+C;AAClH,EAAA,IAAI,OAAO,SAAA,EAAW;AACpB,IAAA,uBACE,GAAA;AAAA,MAAC,KAAA;AAAA,MAAA;AAAA,QACC,KAAK,MAAA,CAAO,SAAA;AAAA,QACZ,KAAK,MAAA,CAAO,IAAA;AAAA,QACZ,OAAA,EAAQ,MAAA;AAAA,QACR,SAAA,EAAW,sCAAsC,SAAS,CAAA;AAAA;AAAA,KAC5D;AAAA,EAEJ;AACA,EAAA,uBACE,GAAA,CAAC,MAAA,EAAA,EAAK,SAAA,EAAW,CAAA,4FAAA,EAA+F,SAAS,CAAA,CAAA,EACtH,QAAA,EAAA,MAAA,CAAO,IAAA,CAAK,MAAA,CAAO,CAAC,CAAA,CAAE,WAAA,EAAY,EACrC,CAAA;AAEJ","file":"chunk-3TZYTDNI.js","sourcesContent":["import type { BlogAuthor } from '../types'\n\n/** Round author avatar with a name-initial fallback (token-only). */\nexport function AuthorAvatar({ author, className = 'h-8 w-8 text-xs' }: { author: BlogAuthor; className?: string }) {\n if (author.avatarUrl) {\n return (\n <img\n src={author.avatarUrl}\n alt={author.name}\n loading=\"lazy\"\n className={`shrink-0 rounded-full object-cover ${className}`}\n />\n )\n }\n return (\n <span className={`flex shrink-0 items-center justify-center rounded-full bg-accent font-semibold text-primary ${className}`}>\n {author.name.charAt(0).toUpperCase()}\n </span>\n )\n}\n"]}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { useBlogContext } from './chunk-TYSSR2VZ.js';
|
|
2
|
+
import { useState, useEffect } from 'react';
|
|
3
|
+
|
|
4
|
+
function useBlogPosts(query) {
|
|
5
|
+
const { provider } = useBlogContext();
|
|
6
|
+
const [posts, setPosts] = useState([]);
|
|
7
|
+
const [loading, setLoading] = useState(true);
|
|
8
|
+
const [error, setError] = useState(null);
|
|
9
|
+
const limit = query?.limit;
|
|
10
|
+
const tag = query?.tag;
|
|
11
|
+
useEffect(() => {
|
|
12
|
+
let active = true;
|
|
13
|
+
setLoading(true);
|
|
14
|
+
provider.listPosts({ limit, tag }).then((result) => {
|
|
15
|
+
if (!active) return;
|
|
16
|
+
setPosts(result);
|
|
17
|
+
setError(null);
|
|
18
|
+
}).catch((err) => {
|
|
19
|
+
if (!active) return;
|
|
20
|
+
setError(err instanceof Error ? err : new Error(String(err)));
|
|
21
|
+
}).finally(() => {
|
|
22
|
+
if (active) setLoading(false);
|
|
23
|
+
});
|
|
24
|
+
return () => {
|
|
25
|
+
active = false;
|
|
26
|
+
};
|
|
27
|
+
}, [provider, limit, tag]);
|
|
28
|
+
return { posts, loading, error };
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export { useBlogPosts };
|
|
32
|
+
//# sourceMappingURL=chunk-GTH5Z4I6.js.map
|
|
33
|
+
//# sourceMappingURL=chunk-GTH5Z4I6.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/hooks/useBlogPosts.ts"],"names":[],"mappings":";;;AAcO,SAAS,aAAa,KAAA,EAA2C;AACtE,EAAA,MAAM,EAAE,QAAA,EAAS,GAAI,cAAA,EAAe;AACpC,EAAA,MAAM,CAAC,KAAA,EAAO,QAAQ,CAAA,GAAI,QAAA,CAAqB,EAAE,CAAA;AACjD,EAAA,MAAM,CAAC,OAAA,EAAS,UAAU,CAAA,GAAI,SAAS,IAAI,CAAA;AAC3C,EAAA,MAAM,CAAC,KAAA,EAAO,QAAQ,CAAA,GAAI,SAAuB,IAAI,CAAA;AAErD,EAAA,MAAM,QAAQ,KAAA,EAAO,KAAA;AACrB,EAAA,MAAM,MAAM,KAAA,EAAO,GAAA;AAEnB,EAAA,SAAA,CAAU,MAAM;AACd,IAAA,IAAI,MAAA,GAAS,IAAA;AACb,IAAA,UAAA,CAAW,IAAI,CAAA;AACf,IAAA,QAAA,CACG,SAAA,CAAU,EAAE,KAAA,EAAO,GAAA,EAAK,CAAA,CACxB,IAAA,CAAK,CAAC,MAAA,KAAW;AAChB,MAAA,IAAI,CAAC,MAAA,EAAQ;AACb,MAAA,QAAA,CAAS,MAAM,CAAA;AACf,MAAA,QAAA,CAAS,IAAI,CAAA;AAAA,IACf,CAAC,CAAA,CACA,KAAA,CAAM,CAAC,GAAA,KAAQ;AACd,MAAA,IAAI,CAAC,MAAA,EAAQ;AACb,MAAA,QAAA,CAAS,GAAA,YAAe,QAAQ,GAAA,GAAM,IAAI,MAAM,MAAA,CAAO,GAAG,CAAC,CAAC,CAAA;AAAA,IAC9D,CAAC,CAAA,CACA,OAAA,CAAQ,MAAM;AACb,MAAA,IAAI,MAAA,aAAmB,KAAK,CAAA;AAAA,IAC9B,CAAC,CAAA;AACH,IAAA,OAAO,MAAM;AACX,MAAA,MAAA,GAAS,KAAA;AAAA,IACX,CAAA;AAAA,EACF,CAAA,EAAG,CAAC,QAAA,EAAU,KAAA,EAAO,GAAG,CAAC,CAAA;AAEzB,EAAA,OAAO,EAAE,KAAA,EAAO,OAAA,EAAS,KAAA,EAAM;AACjC","file":"chunk-GTH5Z4I6.js","sourcesContent":["import { useEffect, useState } from 'react'\nimport { useBlogContext } from '../context'\nimport type { BlogPost, BlogListQuery } from '../types'\n\nexport interface UseBlogPostsResult {\n posts: BlogPost[]\n loading: boolean\n error: Error | null\n}\n\n/**\n * Fetch the post list from the active blog provider. The host maps `posts` into\n * its own card markup; the plugin never dictates presentation.\n */\nexport function useBlogPosts(query?: BlogListQuery): UseBlogPostsResult {\n const { provider } = useBlogContext()\n const [posts, setPosts] = useState<BlogPost[]>([])\n const [loading, setLoading] = useState(true)\n const [error, setError] = useState<Error | null>(null)\n\n const limit = query?.limit\n const tag = query?.tag\n\n useEffect(() => {\n let active = true\n setLoading(true)\n provider\n .listPosts({ limit, tag })\n .then((result) => {\n if (!active) return\n setPosts(result)\n setError(null)\n })\n .catch((err) => {\n if (!active) return\n setError(err instanceof Error ? err : new Error(String(err)))\n })\n .finally(() => {\n if (active) setLoading(false)\n })\n return () => {\n active = false\n }\n }, [provider, limit, tag])\n\n return { posts, loading, error }\n}\n"]}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { useBlogContext } from './chunk-TYSSR2VZ.js';
|
|
2
|
+
import { useState, useEffect } from 'react';
|
|
3
|
+
|
|
4
|
+
function usePost(slug) {
|
|
5
|
+
const { provider } = useBlogContext();
|
|
6
|
+
const [post, setPost] = useState(null);
|
|
7
|
+
const [loading, setLoading] = useState(true);
|
|
8
|
+
const [error, setError] = useState(null);
|
|
9
|
+
useEffect(() => {
|
|
10
|
+
let active = true;
|
|
11
|
+
if (!slug) {
|
|
12
|
+
setPost(null);
|
|
13
|
+
setLoading(false);
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
setLoading(true);
|
|
17
|
+
provider.getPost(slug).then((result) => {
|
|
18
|
+
if (!active) return;
|
|
19
|
+
setPost(result);
|
|
20
|
+
setError(null);
|
|
21
|
+
}).catch((err) => {
|
|
22
|
+
if (!active) return;
|
|
23
|
+
setError(err instanceof Error ? err : new Error(String(err)));
|
|
24
|
+
}).finally(() => {
|
|
25
|
+
if (active) setLoading(false);
|
|
26
|
+
});
|
|
27
|
+
return () => {
|
|
28
|
+
active = false;
|
|
29
|
+
};
|
|
30
|
+
}, [provider, slug]);
|
|
31
|
+
return { post, loading, error };
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export { usePost };
|
|
35
|
+
//# sourceMappingURL=chunk-HBD463WC.js.map
|
|
36
|
+
//# sourceMappingURL=chunk-HBD463WC.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/hooks/usePost.ts"],"names":[],"mappings":";;;AAWO,SAAS,QAAQ,IAAA,EAAyC;AAC/D,EAAA,MAAM,EAAE,QAAA,EAAS,GAAI,cAAA,EAAe;AACpC,EAAA,MAAM,CAAC,IAAA,EAAM,OAAO,CAAA,GAAI,SAA0B,IAAI,CAAA;AACtD,EAAA,MAAM,CAAC,OAAA,EAAS,UAAU,CAAA,GAAI,SAAS,IAAI,CAAA;AAC3C,EAAA,MAAM,CAAC,KAAA,EAAO,QAAQ,CAAA,GAAI,SAAuB,IAAI,CAAA;AAErD,EAAA,SAAA,CAAU,MAAM;AACd,IAAA,IAAI,MAAA,GAAS,IAAA;AACb,IAAA,IAAI,CAAC,IAAA,EAAM;AACT,MAAA,OAAA,CAAQ,IAAI,CAAA;AACZ,MAAA,UAAA,CAAW,KAAK,CAAA;AAChB,MAAA;AAAA,IACF;AACA,IAAA,UAAA,CAAW,IAAI,CAAA;AACf,IAAA,QAAA,CACG,OAAA,CAAQ,IAAI,CAAA,CACZ,IAAA,CAAK,CAAC,MAAA,KAAW;AAChB,MAAA,IAAI,CAAC,MAAA,EAAQ;AACb,MAAA,OAAA,CAAQ,MAAM,CAAA;AACd,MAAA,QAAA,CAAS,IAAI,CAAA;AAAA,IACf,CAAC,CAAA,CACA,KAAA,CAAM,CAAC,GAAA,KAAQ;AACd,MAAA,IAAI,CAAC,MAAA,EAAQ;AACb,MAAA,QAAA,CAAS,GAAA,YAAe,QAAQ,GAAA,GAAM,IAAI,MAAM,MAAA,CAAO,GAAG,CAAC,CAAC,CAAA;AAAA,IAC9D,CAAC,CAAA,CACA,OAAA,CAAQ,MAAM;AACb,MAAA,IAAI,MAAA,aAAmB,KAAK,CAAA;AAAA,IAC9B,CAAC,CAAA;AACH,IAAA,OAAO,MAAM;AACX,MAAA,MAAA,GAAS,KAAA;AAAA,IACX,CAAA;AAAA,EACF,CAAA,EAAG,CAAC,QAAA,EAAU,IAAI,CAAC,CAAA;AAEnB,EAAA,OAAO,EAAE,IAAA,EAAM,OAAA,EAAS,KAAA,EAAM;AAChC","file":"chunk-HBD463WC.js","sourcesContent":["import { useEffect, useState } from 'react'\nimport { useBlogContext } from '../context'\nimport type { BlogPost } from '../types'\n\nexport interface UsePostResult {\n post: BlogPost | null\n loading: boolean\n error: Error | null\n}\n\n/** Fetch a single post by slug from the active blog provider. */\nexport function usePost(slug: string | undefined): UsePostResult {\n const { provider } = useBlogContext()\n const [post, setPost] = useState<BlogPost | null>(null)\n const [loading, setLoading] = useState(true)\n const [error, setError] = useState<Error | null>(null)\n\n useEffect(() => {\n let active = true\n if (!slug) {\n setPost(null)\n setLoading(false)\n return\n }\n setLoading(true)\n provider\n .getPost(slug)\n .then((result) => {\n if (!active) return\n setPost(result)\n setError(null)\n })\n .catch((err) => {\n if (!active) return\n setError(err instanceof Error ? err : new Error(String(err)))\n })\n .finally(() => {\n if (active) setLoading(false)\n })\n return () => {\n active = false\n }\n }, [provider, slug])\n\n return { post, loading, error }\n}\n"]}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { usePost } from './chunk-HBD463WC.js';
|
|
2
|
+
import { AuthorAvatar } from './chunk-3TZYTDNI.js';
|
|
3
|
+
import { useBlogContext, useSeo, articleJsonLd, postUrl } from './chunk-TYSSR2VZ.js';
|
|
4
|
+
import { useParams, Link } from 'react-router-dom';
|
|
5
|
+
import { Clock } from 'lucide-react';
|
|
6
|
+
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
7
|
+
|
|
8
|
+
function PostDetail() {
|
|
9
|
+
const { config } = useBlogContext();
|
|
10
|
+
const { slug } = useParams();
|
|
11
|
+
const { post, loading, error } = usePost(slug);
|
|
12
|
+
useSeo(
|
|
13
|
+
post ? {
|
|
14
|
+
title: `${post.title}${config.siteName ? ` \xB7 ${config.siteName}` : ""}`,
|
|
15
|
+
description: post.excerpt,
|
|
16
|
+
canonical: postUrl(config.basePath, post.slug),
|
|
17
|
+
image: post.image,
|
|
18
|
+
type: "article",
|
|
19
|
+
siteName: config.siteName,
|
|
20
|
+
jsonLd: articleJsonLd(post, { basePath: config.basePath, siteName: config.siteName })
|
|
21
|
+
} : { title: config.siteName || "Blog" }
|
|
22
|
+
);
|
|
23
|
+
const paragraphs = post?.body ? post.body.split(/\n\s*\n/).map((p) => p.trim()).filter(Boolean) : [];
|
|
24
|
+
return /* @__PURE__ */ jsx("article", { className: "py-16 bg-background", children: /* @__PURE__ */ jsxs("div", { className: "container mx-auto px-6 max-w-3xl", children: [
|
|
25
|
+
/* @__PURE__ */ jsx(Link, { to: config.basePath, className: "text-sm font-medium text-primary hover:underline", children: config.labels.backToList }),
|
|
26
|
+
loading ? /* @__PURE__ */ jsx("p", { className: "mt-10 text-muted-foreground", children: "Carregando\u2026" }) : error || !post ? /* @__PURE__ */ jsxs("div", { className: "mt-10", children: [
|
|
27
|
+
/* @__PURE__ */ jsx("h1", { className: "font-heading text-3xl font-bold text-foreground", children: "Artigo n\xE3o encontrado" }),
|
|
28
|
+
/* @__PURE__ */ jsx("p", { className: "mt-2 text-muted-foreground", children: "Este artigo pode ter sido movido ou removido." })
|
|
29
|
+
] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
30
|
+
/* @__PURE__ */ jsx("h1", { className: "mt-8 font-heading text-4xl md:text-5xl font-bold text-foreground leading-tight", children: post.title }),
|
|
31
|
+
/* @__PURE__ */ jsxs("div", { className: "mt-6 flex items-center gap-3", children: [
|
|
32
|
+
/* @__PURE__ */ jsx(AuthorAvatar, { author: post.author, className: "h-11 w-11 text-base" }),
|
|
33
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
34
|
+
/* @__PURE__ */ jsx("p", { className: "font-semibold text-foreground", children: post.author.name }),
|
|
35
|
+
/* @__PURE__ */ jsxs("p", { className: "flex flex-wrap items-center gap-1.5 text-sm text-muted-foreground", children: [
|
|
36
|
+
post.author.role ? /* @__PURE__ */ jsxs("span", { children: [
|
|
37
|
+
post.author.role,
|
|
38
|
+
/* @__PURE__ */ jsx("span", { "aria-hidden": true, children: " \xB7 " })
|
|
39
|
+
] }) : null,
|
|
40
|
+
/* @__PURE__ */ jsx("time", { dateTime: post.publishedAt, children: post.date }),
|
|
41
|
+
/* @__PURE__ */ jsxs("span", { className: "flex items-center gap-1", children: [
|
|
42
|
+
/* @__PURE__ */ jsx(Clock, { className: "h-3 w-3" }),
|
|
43
|
+
post.readTime,
|
|
44
|
+
" de leitura"
|
|
45
|
+
] })
|
|
46
|
+
] })
|
|
47
|
+
] })
|
|
48
|
+
] }),
|
|
49
|
+
/* @__PURE__ */ jsx("div", { className: "mt-8 overflow-hidden rounded-2xl", children: /* @__PURE__ */ jsx("img", { src: post.image, alt: post.title, className: "w-full object-cover", loading: "lazy" }) }),
|
|
50
|
+
/* @__PURE__ */ jsxs("div", { className: "mt-8 space-y-5", children: [
|
|
51
|
+
/* @__PURE__ */ jsx("p", { className: "text-lg text-foreground/90 leading-relaxed font-medium", children: post.excerpt }),
|
|
52
|
+
paragraphs.map((para, i) => /* @__PURE__ */ jsx("p", { className: "text-muted-foreground leading-relaxed", children: para }, i))
|
|
53
|
+
] }),
|
|
54
|
+
/* @__PURE__ */ jsxs("div", { className: "mt-10 flex flex-wrap items-center gap-2 border-t border-border pt-6", children: [
|
|
55
|
+
/* @__PURE__ */ jsx("span", { className: "text-sm text-muted-foreground", children: "Categoria:" }),
|
|
56
|
+
/* @__PURE__ */ jsx(
|
|
57
|
+
Link,
|
|
58
|
+
{
|
|
59
|
+
to: config.basePath,
|
|
60
|
+
className: "rounded-full bg-accent px-3 py-1 text-xs font-semibold text-primary transition-colors hover:bg-accent/70",
|
|
61
|
+
children: post.tag
|
|
62
|
+
}
|
|
63
|
+
)
|
|
64
|
+
] }),
|
|
65
|
+
post.author.bio ? /* @__PURE__ */ jsxs("div", { className: "mt-8 flex items-start gap-4 rounded-2xl border border-border bg-muted/40 p-6", children: [
|
|
66
|
+
/* @__PURE__ */ jsx(AuthorAvatar, { author: post.author, className: "h-14 w-14 text-lg" }),
|
|
67
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
68
|
+
/* @__PURE__ */ jsx("p", { className: "text-xs font-semibold uppercase tracking-wide text-muted-foreground", children: "Escrito por" }),
|
|
69
|
+
/* @__PURE__ */ jsx("p", { className: "font-heading text-lg font-bold text-foreground", children: post.author.name }),
|
|
70
|
+
post.author.role ? /* @__PURE__ */ jsx("p", { className: "text-sm text-primary", children: post.author.role }) : null,
|
|
71
|
+
/* @__PURE__ */ jsx("p", { className: "mt-2 text-sm text-muted-foreground leading-relaxed", children: post.author.bio })
|
|
72
|
+
] })
|
|
73
|
+
] }) : null
|
|
74
|
+
] })
|
|
75
|
+
] }) });
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export { PostDetail };
|
|
79
|
+
//# sourceMappingURL=chunk-LCYIM3AB.js.map
|
|
80
|
+
//# sourceMappingURL=chunk-LCYIM3AB.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/components/PostDetail.tsx"],"names":[],"mappings":"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","file":"chunk-LCYIM3AB.js","sourcesContent":["import { Link, useParams } from 'react-router-dom'\nimport { Clock } from 'lucide-react'\nimport { usePost } from '../hooks/usePost'\nimport { useBlogContext } from '../context'\nimport { AuthorAvatar } from './AuthorAvatar'\nimport { useSeo, articleJsonLd, postUrl } from '../lib/seo'\n\n/**\n * /blog/:slug detail screen. Reads the slug from the router and renders the\n * article inside the host chrome. Token-only for theme inheritance. Sets rich\n * SEO/JSON-LD per article (BlogPosting + BreadcrumbList).\n */\nexport function PostDetail() {\n const { config } = useBlogContext()\n const { slug } = useParams<{ slug: string }>()\n const { post, loading, error } = usePost(slug)\n\n useSeo(\n post\n ? {\n title: `${post.title}${config.siteName ? ` · ${config.siteName}` : ''}`,\n description: post.excerpt,\n canonical: postUrl(config.basePath, post.slug),\n image: post.image,\n type: 'article',\n siteName: config.siteName,\n jsonLd: articleJsonLd(post, { basePath: config.basePath, siteName: config.siteName }),\n }\n : { title: config.siteName || 'Blog' },\n )\n\n const paragraphs = post?.body ? post.body.split(/\\n\\s*\\n/).map((p) => p.trim()).filter(Boolean) : []\n\n return (\n <article className=\"py-16 bg-background\">\n <div className=\"container mx-auto px-6 max-w-3xl\">\n <Link to={config.basePath} className=\"text-sm font-medium text-primary hover:underline\">\n {config.labels.backToList}\n </Link>\n\n {loading ? (\n <p className=\"mt-10 text-muted-foreground\">Carregando…</p>\n ) : error || !post ? (\n <div className=\"mt-10\">\n <h1 className=\"font-heading text-3xl font-bold text-foreground\">Artigo não encontrado</h1>\n <p className=\"mt-2 text-muted-foreground\">Este artigo pode ter sido movido ou removido.</p>\n </div>\n ) : (\n <>\n <h1 className=\"mt-8 font-heading text-4xl md:text-5xl font-bold text-foreground leading-tight\">\n {post.title}\n </h1>\n\n {/* Medium-style byline */}\n <div className=\"mt-6 flex items-center gap-3\">\n <AuthorAvatar author={post.author} className=\"h-11 w-11 text-base\" />\n <div>\n <p className=\"font-semibold text-foreground\">{post.author.name}</p>\n <p className=\"flex flex-wrap items-center gap-1.5 text-sm text-muted-foreground\">\n {post.author.role ? <span>{post.author.role}<span aria-hidden> · </span></span> : null}\n <time dateTime={post.publishedAt}>{post.date}</time>\n <span className=\"flex items-center gap-1\"><Clock className=\"h-3 w-3\" />{post.readTime} de leitura</span>\n </p>\n </div>\n </div>\n\n <div className=\"mt-8 overflow-hidden rounded-2xl\">\n <img src={post.image} alt={post.title} className=\"w-full object-cover\" loading=\"lazy\" />\n </div>\n\n <div className=\"mt-8 space-y-5\">\n <p className=\"text-lg text-foreground/90 leading-relaxed font-medium\">{post.excerpt}</p>\n {paragraphs.map((para, i) => (\n <p key={i} className=\"text-muted-foreground leading-relaxed\">\n {para}\n </p>\n ))}\n </div>\n\n {/* Category tag — at the bottom, Medium-style */}\n <div className=\"mt-10 flex flex-wrap items-center gap-2 border-t border-border pt-6\">\n <span className=\"text-sm text-muted-foreground\">Categoria:</span>\n <Link\n to={config.basePath}\n className=\"rounded-full bg-accent px-3 py-1 text-xs font-semibold text-primary transition-colors hover:bg-accent/70\"\n >\n {post.tag}\n </Link>\n </div>\n\n {/* About the author */}\n {post.author.bio ? (\n <div className=\"mt-8 flex items-start gap-4 rounded-2xl border border-border bg-muted/40 p-6\">\n <AuthorAvatar author={post.author} className=\"h-14 w-14 text-lg\" />\n <div>\n <p className=\"text-xs font-semibold uppercase tracking-wide text-muted-foreground\">Escrito por</p>\n <p className=\"font-heading text-lg font-bold text-foreground\">{post.author.name}</p>\n {post.author.role ? <p className=\"text-sm text-primary\">{post.author.role}</p> : null}\n <p className=\"mt-2 text-sm text-muted-foreground leading-relaxed\">{post.author.bio}</p>\n </div>\n </div>\n ) : null}\n </>\n )}\n </div>\n </article>\n )\n}\n"]}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { createContext, useContext } from 'react';
|
|
2
|
+
import { jsx } from 'react/jsx-runtime';
|
|
3
|
+
import { absoluteUrl, organizationJsonLd, breadcrumbJsonLd } from '@fayz-ai/core';
|
|
4
|
+
export { useSeo } from '@fayz-ai/core';
|
|
5
|
+
|
|
6
|
+
// src/context.tsx
|
|
7
|
+
var BlogContext = createContext(null);
|
|
8
|
+
function BlogProvider({ value, children }) {
|
|
9
|
+
return /* @__PURE__ */ jsx(BlogContext.Provider, { value, children });
|
|
10
|
+
}
|
|
11
|
+
function useBlogContext() {
|
|
12
|
+
const ctx = useContext(BlogContext);
|
|
13
|
+
if (!ctx) {
|
|
14
|
+
throw new Error("[plugin-blog] useBlogContext must be used within <BlogProvider>. Mount the plugin Provider at the app root.");
|
|
15
|
+
}
|
|
16
|
+
return ctx;
|
|
17
|
+
}
|
|
18
|
+
function blogUrl(basePath, path, origin) {
|
|
19
|
+
const joined = `${basePath}${path}`.replace(/\/+/g, "/");
|
|
20
|
+
if (origin) return `${origin.replace(/\/$/, "")}${joined.startsWith("/") ? joined : `/${joined}`}`;
|
|
21
|
+
const ambient = absoluteUrl(joined);
|
|
22
|
+
return ambient.startsWith("http") ? ambient : void 0;
|
|
23
|
+
}
|
|
24
|
+
function postUrl(basePath, slug, origin) {
|
|
25
|
+
return blogUrl(basePath, `/${slug}`, origin);
|
|
26
|
+
}
|
|
27
|
+
function articleJsonLd(post, opts) {
|
|
28
|
+
const url = postUrl(opts.basePath, post.slug, opts.origin);
|
|
29
|
+
const posting = {
|
|
30
|
+
"@context": "https://schema.org",
|
|
31
|
+
"@type": "BlogPosting",
|
|
32
|
+
headline: post.title,
|
|
33
|
+
description: post.excerpt,
|
|
34
|
+
image: post.image ? [post.image] : void 0,
|
|
35
|
+
articleSection: post.tag,
|
|
36
|
+
...url ? { mainEntityOfPage: { "@type": "WebPage", "@id": url } } : {},
|
|
37
|
+
author: {
|
|
38
|
+
"@type": "Person",
|
|
39
|
+
name: post.author.name,
|
|
40
|
+
...post.author.role ? { jobTitle: post.author.role } : {}
|
|
41
|
+
},
|
|
42
|
+
...post.publishedAt ? { datePublished: post.publishedAt, dateModified: post.publishedAt } : {},
|
|
43
|
+
...opts.siteName ? { publisher: organizationJsonLd(opts.siteName) } : {}
|
|
44
|
+
};
|
|
45
|
+
const listUrl = blogUrl(opts.basePath, "", opts.origin);
|
|
46
|
+
if (!url || !listUrl) return [posting];
|
|
47
|
+
return [posting, breadcrumbJsonLd([{ name: "Blog", url: listUrl }, { name: post.title, url }])];
|
|
48
|
+
}
|
|
49
|
+
function blogJsonLd(posts, opts) {
|
|
50
|
+
return [
|
|
51
|
+
{
|
|
52
|
+
"@context": "https://schema.org",
|
|
53
|
+
"@type": "Blog",
|
|
54
|
+
name: opts.title,
|
|
55
|
+
...blogUrl(opts.basePath, "", opts.origin) ? { url: blogUrl(opts.basePath, "", opts.origin) } : {},
|
|
56
|
+
...opts.siteName ? { publisher: organizationJsonLd(opts.siteName) } : {},
|
|
57
|
+
blogPost: posts.map((p) => ({
|
|
58
|
+
"@type": "BlogPosting",
|
|
59
|
+
headline: p.title,
|
|
60
|
+
...postUrl(opts.basePath, p.slug, opts.origin) ? { url: postUrl(opts.basePath, p.slug, opts.origin) } : {},
|
|
61
|
+
image: p.image,
|
|
62
|
+
author: { "@type": "Person", name: p.author.name },
|
|
63
|
+
...p.publishedAt ? { datePublished: p.publishedAt } : {}
|
|
64
|
+
}))
|
|
65
|
+
}
|
|
66
|
+
];
|
|
67
|
+
}
|
|
68
|
+
function postSeo(post, opts) {
|
|
69
|
+
return {
|
|
70
|
+
title: opts.siteName ? `${post.title} \u2014 ${opts.siteName}` : post.title,
|
|
71
|
+
description: post.excerpt?.slice(0, 160),
|
|
72
|
+
canonical: postUrl(opts.basePath, post.slug, opts.origin),
|
|
73
|
+
image: post.image,
|
|
74
|
+
// 'article', not 'website': it is what tells a share card to show the author
|
|
75
|
+
// and published date rather than treating the post as a site landing page.
|
|
76
|
+
type: "article",
|
|
77
|
+
jsonLd: articleJsonLd(post, opts)
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
function blogListSeo(posts, opts) {
|
|
81
|
+
return {
|
|
82
|
+
title: opts.siteName ? `${opts.title} \u2014 ${opts.siteName}` : opts.title,
|
|
83
|
+
description: opts.description,
|
|
84
|
+
canonical: blogUrl(opts.basePath, "", opts.origin),
|
|
85
|
+
image: posts.find((p) => p.image)?.image,
|
|
86
|
+
type: "website",
|
|
87
|
+
jsonLd: blogJsonLd(posts, opts)
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export { BlogProvider, articleJsonLd, blogJsonLd, blogListSeo, blogUrl, postSeo, postUrl, useBlogContext };
|
|
92
|
+
//# sourceMappingURL=chunk-TYSSR2VZ.js.map
|
|
93
|
+
//# sourceMappingURL=chunk-TYSSR2VZ.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/context.tsx","../src/lib/seo.ts"],"names":[],"mappings":"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","file":"chunk-TYSSR2VZ.js","sourcesContent":["import { createContext, useContext, type ReactNode } from 'react'\nimport type { BlogDataProvider } from './data/types'\nimport type { ResolvedBlogConfig } from './config'\n\nexport interface BlogContextValue {\n provider: BlogDataProvider\n config: ResolvedBlogConfig\n}\n\nconst BlogContext = createContext<BlogContextValue | null>(null)\n\nexport function BlogProvider({ value, children }: { value: BlogContextValue; children: ReactNode }) {\n return <BlogContext.Provider value={value}>{children}</BlogContext.Provider>\n}\n\n/** Read the blog provider + config. Throws if used outside a BlogProvider. */\nexport function useBlogContext(): BlogContextValue {\n const ctx = useContext(BlogContext)\n if (!ctx) {\n throw new Error('[plugin-blog] useBlogContext must be used within <BlogProvider>. Mount the plugin Provider at the app root.')\n }\n return ctx\n}\n","import { absoluteUrl, breadcrumbJsonLd, organizationJsonLd } from '@fayz-ai/core'\nimport type { BlogPost } from '../types'\n\n// ---------------------------------------------------------------------------\n// Blog-specific schema.org builders. The head-manager mechanics (useSeo /\n// applySeo / meta+OG+Twitter) live in @fayz-ai/core — this only owns the\n// domain shapes (BlogPosting, Blog) that plug into core's SeoConfig.jsonLd.\n// ---------------------------------------------------------------------------\n\n/**\n * Absolute URL for a path within the blog.\n *\n * `origin` is explicit because a server has no `window.location` to infer one\n * from, and a relative canonical is ignored by search engines while a relative\n * og:image renders as a broken share — so an unknown origin yields undefined\n * rather than a half-URL that looks fine in the markup and does nothing.\n */\nexport function blogUrl(basePath: string, path: string, origin?: string): string | undefined {\n const joined = `${basePath}${path}`.replace(/\\/+/g, '/')\n if (origin) return `${origin.replace(/\\/$/, '')}${joined.startsWith('/') ? joined : `/${joined}`}`\n const ambient = absoluteUrl(joined)\n // absoluteUrl returns just the path when there is no origin to prepend.\n return ambient.startsWith('http') ? ambient : undefined\n}\n\n/** Absolute canonical URL for a post. */\nexport function postUrl(basePath: string, slug: string, origin?: string): string | undefined {\n return blogUrl(basePath, `/${slug}`, origin)\n}\n\n/** schema.org BlogPosting + BreadcrumbList for an article page. */\nexport function articleJsonLd(\n post: BlogPost,\n opts: { basePath: string; siteName: string; origin?: string },\n): Array<Record<string, unknown>> {\n const url = postUrl(opts.basePath, post.slug, opts.origin)\n const posting: Record<string, unknown> = {\n '@context': 'https://schema.org',\n '@type': 'BlogPosting',\n headline: post.title,\n description: post.excerpt,\n image: post.image ? [post.image] : undefined,\n articleSection: post.tag,\n ...(url ? { mainEntityOfPage: { '@type': 'WebPage', '@id': url } } : {}),\n author: {\n '@type': 'Person',\n name: post.author.name,\n ...(post.author.role ? { jobTitle: post.author.role } : {}),\n },\n ...(post.publishedAt ? { datePublished: post.publishedAt, dateModified: post.publishedAt } : {}),\n ...(opts.siteName ? { publisher: organizationJsonLd(opts.siteName) } : {}),\n }\n const listUrl = blogUrl(opts.basePath, '', opts.origin)\n // Breadcrumbs are dropped entirely without an origin: schema.org requires\n // absolute `item` URLs, and a half-populated trail is invalid structured data\n // rather than a partial improvement.\n if (!url || !listUrl) return [posting]\n return [posting, breadcrumbJsonLd([{ name: 'Blog', url: listUrl }, { name: post.title, url }])]\n}\n\n/** schema.org Blog + post list for the listing page. */\nexport function blogJsonLd(\n posts: BlogPost[],\n opts: { basePath: string; siteName: string; title: string; origin?: string },\n): Array<Record<string, unknown>> {\n return [\n {\n '@context': 'https://schema.org',\n '@type': 'Blog',\n name: opts.title,\n ...(blogUrl(opts.basePath, '', opts.origin) ? { url: blogUrl(opts.basePath, '', opts.origin) } : {}),\n ...(opts.siteName ? { publisher: organizationJsonLd(opts.siteName) } : {}),\n blogPost: posts.map((p) => ({\n '@type': 'BlogPosting',\n headline: p.title,\n ...(postUrl(opts.basePath, p.slug, opts.origin) ? { url: postUrl(opts.basePath, p.slug, opts.origin) } : {}),\n image: p.image,\n author: { '@type': 'Person', name: p.author.name },\n ...(p.publishedAt ? { datePublished: p.publishedAt } : {}),\n })),\n },\n ]\n}\n\n// Re-export the generic hook so blog components import SEO from one place.\nexport { useSeo } from '@fayz-ai/core'\n\n// --- Full head descriptors ----------------------------------------------------\n//\n// The same shape the storefront's `productSeo` returns, so a host hands blog and\n// commerce pages to its head API through one code path.\n\nexport interface BlogSeoDescriptor {\n title: string\n description?: string\n canonical?: string\n image?: string\n type?: string\n jsonLd?: Array<Record<string, unknown>>\n}\n\nexport interface BlogSeoOptions {\n basePath: string\n siteName: string\n /** Absolute origin. Without it canonical, og:image and breadcrumbs are omitted. */\n origin?: string\n}\n\n/** The head a post page should carry. */\nexport function postSeo(post: BlogPost, opts: BlogSeoOptions): BlogSeoDescriptor {\n return {\n title: opts.siteName ? `${post.title} — ${opts.siteName}` : post.title,\n description: post.excerpt?.slice(0, 160),\n canonical: postUrl(opts.basePath, post.slug, opts.origin),\n image: post.image,\n // 'article', not 'website': it is what tells a share card to show the author\n // and published date rather than treating the post as a site landing page.\n type: 'article',\n jsonLd: articleJsonLd(post, opts),\n }\n}\n\n/** The head a blog index should carry. */\nexport function blogListSeo(\n posts: BlogPost[],\n opts: BlogSeoOptions & { title: string; description?: string },\n): BlogSeoDescriptor {\n return {\n title: opts.siteName ? `${opts.title} — ${opts.siteName}` : opts.title,\n description: opts.description,\n canonical: blogUrl(opts.basePath, '', opts.origin),\n image: posts.find((p) => p.image)?.image,\n type: 'website',\n jsonLd: blogJsonLd(posts, opts),\n }\n}\n"]}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { useBlogPosts } from './chunk-GTH5Z4I6.js';
|
|
2
|
+
import { AuthorAvatar } from './chunk-3TZYTDNI.js';
|
|
3
|
+
import { useBlogContext, useSeo, blogJsonLd } from './chunk-TYSSR2VZ.js';
|
|
4
|
+
import { Link } from 'react-router-dom';
|
|
5
|
+
import { Clock } from 'lucide-react';
|
|
6
|
+
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
7
|
+
|
|
8
|
+
function PostCard({ post, basePath = "/blog" }) {
|
|
9
|
+
return /* @__PURE__ */ jsxs(
|
|
10
|
+
Link,
|
|
11
|
+
{
|
|
12
|
+
to: `${basePath}/${post.slug}`,
|
|
13
|
+
className: "group flex flex-col rounded-2xl border border-border bg-card overflow-hidden shadow-sm hover:shadow-lg hover:-translate-y-1 transition-all duration-300",
|
|
14
|
+
children: [
|
|
15
|
+
/* @__PURE__ */ jsx("div", { className: "overflow-hidden h-48", children: /* @__PURE__ */ jsx(
|
|
16
|
+
"img",
|
|
17
|
+
{
|
|
18
|
+
src: post.image,
|
|
19
|
+
alt: post.title,
|
|
20
|
+
loading: "lazy",
|
|
21
|
+
className: "h-full w-full object-cover transition-transform duration-500 group-hover:scale-105"
|
|
22
|
+
}
|
|
23
|
+
) }),
|
|
24
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-col flex-1 p-6", children: [
|
|
25
|
+
/* @__PURE__ */ jsx("span", { className: "inline-block rounded-full bg-accent px-3 py-0.5 text-xs font-semibold text-primary mb-3 w-fit", children: post.tag }),
|
|
26
|
+
/* @__PURE__ */ jsx("h3", { className: "font-heading text-lg font-semibold text-foreground leading-snug mb-3 group-hover:text-primary transition-colors duration-200", children: post.title }),
|
|
27
|
+
/* @__PURE__ */ jsx("p", { className: "text-muted-foreground text-sm leading-relaxed flex-1 mb-4", children: post.excerpt }),
|
|
28
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2.5 pt-4 border-t border-border", children: [
|
|
29
|
+
/* @__PURE__ */ jsx(AuthorAvatar, { author: post.author, className: "h-7 w-7 text-xs" }),
|
|
30
|
+
/* @__PURE__ */ jsxs("div", { className: "min-w-0", children: [
|
|
31
|
+
/* @__PURE__ */ jsx("p", { className: "truncate text-xs font-semibold text-foreground", children: post.author.name }),
|
|
32
|
+
/* @__PURE__ */ jsxs("p", { className: "flex items-center gap-1 text-[11px] text-muted-foreground", children: [
|
|
33
|
+
post.date,
|
|
34
|
+
/* @__PURE__ */ jsx("span", { "aria-hidden": true, children: "\xB7" }),
|
|
35
|
+
/* @__PURE__ */ jsx(Clock, { className: "h-3 w-3" }),
|
|
36
|
+
post.readTime
|
|
37
|
+
] })
|
|
38
|
+
] })
|
|
39
|
+
] })
|
|
40
|
+
] })
|
|
41
|
+
]
|
|
42
|
+
}
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
function BlogList() {
|
|
46
|
+
const { config } = useBlogContext();
|
|
47
|
+
const { posts, loading, error } = useBlogPosts();
|
|
48
|
+
const listTitle = config.labels.listSubtitle || config.labels.listTitle;
|
|
49
|
+
useSeo({
|
|
50
|
+
title: `${listTitle}${config.siteName ? ` \xB7 ${config.siteName}` : ""}`,
|
|
51
|
+
description: config.labels.listSubtitle || `Artigos do blog${config.siteName ? ` da ${config.siteName}` : ""}.`,
|
|
52
|
+
canonical: `${typeof window !== "undefined" ? window.location.origin : ""}${config.basePath}`,
|
|
53
|
+
type: "website",
|
|
54
|
+
siteName: config.siteName,
|
|
55
|
+
jsonLd: posts.length ? blogJsonLd(posts, { basePath: config.basePath, siteName: config.siteName, title: listTitle }) : void 0
|
|
56
|
+
});
|
|
57
|
+
return /* @__PURE__ */ jsx("section", { className: "py-20 bg-background", children: /* @__PURE__ */ jsxs("div", { className: "container mx-auto px-6", children: [
|
|
58
|
+
/* @__PURE__ */ jsxs("div", { className: "mb-12 max-w-2xl", children: [
|
|
59
|
+
/* @__PURE__ */ jsx("span", { className: "inline-block rounded-full bg-accent px-4 py-1 text-xs font-semibold uppercase tracking-widest text-primary mb-4", children: config.labels.listTitle }),
|
|
60
|
+
config.labels.listSubtitle ? /* @__PURE__ */ jsx("h1", { className: "font-heading text-4xl md:text-5xl font-bold text-foreground", children: config.labels.listSubtitle }) : null
|
|
61
|
+
] }),
|
|
62
|
+
loading ? /* @__PURE__ */ jsx("p", { className: "text-muted-foreground", children: "Carregando artigos\u2026" }) : error ? /* @__PURE__ */ jsx("p", { className: "text-muted-foreground", children: "N\xE3o foi poss\xEDvel carregar os artigos." }) : posts.length === 0 ? /* @__PURE__ */ jsx("p", { className: "text-muted-foreground", children: "Nenhum artigo publicado ainda." }) : /* @__PURE__ */ jsx("div", { className: "grid grid-cols-1 md:grid-cols-3 gap-6", children: posts.map((post) => /* @__PURE__ */ jsx(PostCard, { post, basePath: config.basePath }, post.slug)) })
|
|
63
|
+
] }) });
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export { BlogList, PostCard };
|
|
67
|
+
//# sourceMappingURL=chunk-YI3WLZ4S.js.map
|
|
68
|
+
//# sourceMappingURL=chunk-YI3WLZ4S.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/components/PostCard.tsx","../src/components/BlogList.tsx"],"names":["jsx","jsxs"],"mappings":";;;;;;;AASO,SAAS,QAAA,CAAS,EAAE,IAAA,EAAM,QAAA,GAAW,SAAQ,EAA0C;AAC5F,EAAA,uBACE,IAAA;AAAA,IAAC,IAAA;AAAA,IAAA;AAAA,MACC,EAAA,EAAI,CAAA,EAAG,QAAQ,CAAA,CAAA,EAAI,KAAK,IAAI,CAAA,CAAA;AAAA,MAC5B,SAAA,EAAU,yJAAA;AAAA,MAEV,QAAA,EAAA;AAAA,wBAAA,GAAA,CAAC,KAAA,EAAA,EAAI,WAAU,sBAAA,EACb,QAAA,kBAAA,GAAA;AAAA,UAAC,KAAA;AAAA,UAAA;AAAA,YACC,KAAK,IAAA,CAAK,KAAA;AAAA,YACV,KAAK,IAAA,CAAK,KAAA;AAAA,YACV,OAAA,EAAQ,MAAA;AAAA,YACR,SAAA,EAAU;AAAA;AAAA,SACZ,EACF,CAAA;AAAA,wBACA,IAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAU,0BAAA,EACb,QAAA,EAAA;AAAA,0BAAA,GAAA,CAAC,MAAA,EAAA,EAAK,SAAA,EAAU,+FAAA,EACb,QAAA,EAAA,IAAA,CAAK,GAAA,EACR,CAAA;AAAA,0BACA,GAAA,CAAC,IAAA,EAAA,EAAG,SAAA,EAAU,8HAAA,EACX,eAAK,KAAA,EACR,CAAA;AAAA,0BACA,GAAA,CAAC,GAAA,EAAA,EAAE,SAAA,EAAU,2DAAA,EAA6D,eAAK,OAAA,EAAQ,CAAA;AAAA,0BACvF,IAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAU,uDAAA,EACb,QAAA,EAAA;AAAA,4BAAA,GAAA,CAAC,YAAA,EAAA,EAAa,MAAA,EAAQ,IAAA,CAAK,MAAA,EAAQ,WAAU,iBAAA,EAAkB,CAAA;AAAA,4BAC/D,IAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAU,SAAA,EACb,QAAA,EAAA;AAAA,8BAAA,GAAA,CAAC,GAAA,EAAA,EAAE,SAAA,EAAU,gDAAA,EAAkD,QAAA,EAAA,IAAA,CAAK,OAAO,IAAA,EAAK,CAAA;AAAA,8BAChF,IAAA,CAAC,GAAA,EAAA,EAAE,SAAA,EAAU,2DAAA,EACV,QAAA,EAAA;AAAA,gBAAA,IAAA,CAAK,IAAA;AAAA,gCAAK,GAAA,CAAC,MAAA,EAAA,EAAK,aAAA,EAAW,IAAA,EAAC,QAAA,EAAA,MAAA,EAAC,CAAA;AAAA,gCAAO,GAAA,CAAC,KAAA,EAAA,EAAM,SAAA,EAAU,SAAA,EAAU,CAAA;AAAA,gBAAG,IAAA,CAAK;AAAA,eAAA,EAC1E;AAAA,aAAA,EACF;AAAA,WAAA,EACF;AAAA,SAAA,EACF;AAAA;AAAA;AAAA,GACF;AAEJ;AClCO,SAAS,QAAA,GAAW;AACzB,EAAA,MAAM,EAAE,MAAA,EAAO,GAAI,cAAA,EAAe;AAClC,EAAA,MAAM,EAAE,KAAA,EAAO,OAAA,EAAS,KAAA,KAAU,YAAA,EAAa;AAE/C,EAAA,MAAM,SAAA,GAAY,MAAA,CAAO,MAAA,CAAO,YAAA,IAAgB,OAAO,MAAA,CAAO,SAAA;AAC9D,EAAA,MAAA,CAAO;AAAA,IACL,KAAA,EAAO,CAAA,EAAG,SAAS,CAAA,EAAG,MAAA,CAAO,WAAW,CAAA,MAAA,EAAM,MAAA,CAAO,QAAQ,CAAA,CAAA,GAAK,EAAE,CAAA,CAAA;AAAA,IACpE,WAAA,EAAa,MAAA,CAAO,MAAA,CAAO,YAAA,IAAgB,CAAA,eAAA,EAAkB,MAAA,CAAO,QAAA,GAAW,CAAA,IAAA,EAAO,MAAA,CAAO,QAAQ,CAAA,CAAA,GAAK,EAAE,CAAA,CAAA,CAAA;AAAA,IAC5G,SAAA,EAAW,CAAA,EAAG,OAAO,MAAA,KAAW,WAAA,GAAc,MAAA,CAAO,QAAA,CAAS,MAAA,GAAS,EAAE,CAAA,EAAG,MAAA,CAAO,QAAQ,CAAA,CAAA;AAAA,IAC3F,IAAA,EAAM,SAAA;AAAA,IACN,UAAU,MAAA,CAAO,QAAA;AAAA,IACjB,MAAA,EAAQ,KAAA,CAAM,MAAA,GAAS,UAAA,CAAW,OAAO,EAAE,QAAA,EAAU,MAAA,CAAO,QAAA,EAAU,UAAU,MAAA,CAAO,QAAA,EAAU,KAAA,EAAO,SAAA,EAAW,CAAA,GAAI;AAAA,GACxH,CAAA;AAED,EAAA,uBACEA,IAAC,SAAA,EAAA,EAAQ,SAAA,EAAU,uBACjB,QAAA,kBAAAC,IAAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAU,wBAAA,EACb,QAAA,EAAA;AAAA,oBAAAA,IAAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAU,iBAAA,EACb,QAAA,EAAA;AAAA,sBAAAD,IAAC,MAAA,EAAA,EAAK,SAAA,EAAU,iHAAA,EACb,QAAA,EAAA,MAAA,CAAO,OAAO,SAAA,EACjB,CAAA;AAAA,MACC,MAAA,CAAO,MAAA,CAAO,YAAA,mBACbA,GAAAA,CAAC,IAAA,EAAA,EAAG,SAAA,EAAU,6DAAA,EACX,QAAA,EAAA,MAAA,CAAO,MAAA,CAAO,YAAA,EACjB,CAAA,GACE;AAAA,KAAA,EACN,CAAA;AAAA,IAEC,OAAA,mBACCA,GAAAA,CAAC,GAAA,EAAA,EAAE,WAAU,uBAAA,EAAwB,QAAA,EAAA,0BAAA,EAAmB,IACtD,KAAA,mBACFA,IAAC,GAAA,EAAA,EAAE,SAAA,EAAU,yBAAwB,QAAA,EAAA,6CAAA,EAAqC,CAAA,GACxE,MAAM,MAAA,KAAW,CAAA,mBACnBA,GAAAA,CAAC,GAAA,EAAA,EAAE,WAAU,uBAAA,EAAwB,QAAA,EAAA,gCAAA,EAA8B,oBAEnEA,GAAAA,CAAC,SAAI,SAAA,EAAU,uCAAA,EACZ,gBAAM,GAAA,CAAI,CAAC,yBACVA,GAAAA,CAAC,YAAyB,IAAA,EAAY,QAAA,EAAU,OAAO,QAAA,EAAA,EAAxC,IAAA,CAAK,IAA6C,CAClE,CAAA,EACH;AAAA,GAAA,EAEJ,CAAA,EACF,CAAA;AAEJ","file":"chunk-YI3WLZ4S.js","sourcesContent":["import { Link } from 'react-router-dom'\nimport { Clock } from 'lucide-react'\nimport type { BlogPost } from '../types'\nimport { AuthorAvatar } from './AuthorAvatar'\n\n/**\n * Default post card — token-only so it inherits the host theme. Optional: a host\n * with bespoke card markup can ignore this and map `useBlogPosts()` itself.\n */\nexport function PostCard({ post, basePath = '/blog' }: { post: BlogPost; basePath?: string }) {\n return (\n <Link\n to={`${basePath}/${post.slug}`}\n className=\"group flex flex-col rounded-2xl border border-border bg-card overflow-hidden shadow-sm hover:shadow-lg hover:-translate-y-1 transition-all duration-300\"\n >\n <div className=\"overflow-hidden h-48\">\n <img\n src={post.image}\n alt={post.title}\n loading=\"lazy\"\n className=\"h-full w-full object-cover transition-transform duration-500 group-hover:scale-105\"\n />\n </div>\n <div className=\"flex flex-col flex-1 p-6\">\n <span className=\"inline-block rounded-full bg-accent px-3 py-0.5 text-xs font-semibold text-primary mb-3 w-fit\">\n {post.tag}\n </span>\n <h3 className=\"font-heading text-lg font-semibold text-foreground leading-snug mb-3 group-hover:text-primary transition-colors duration-200\">\n {post.title}\n </h3>\n <p className=\"text-muted-foreground text-sm leading-relaxed flex-1 mb-4\">{post.excerpt}</p>\n <div className=\"flex items-center gap-2.5 pt-4 border-t border-border\">\n <AuthorAvatar author={post.author} className=\"h-7 w-7 text-xs\" />\n <div className=\"min-w-0\">\n <p className=\"truncate text-xs font-semibold text-foreground\">{post.author.name}</p>\n <p className=\"flex items-center gap-1 text-[11px] text-muted-foreground\">\n {post.date}<span aria-hidden>·</span><Clock className=\"h-3 w-3\" />{post.readTime}\n </p>\n </div>\n </div>\n </div>\n </Link>\n )\n}\n","import { useBlogPosts } from '../hooks/useBlogPosts'\nimport { useBlogContext } from '../context'\nimport { PostCard } from './PostCard'\nimport { useSeo, blogJsonLd } from '../lib/seo'\n\n/**\n * Full /blog list screen. Rendered inside the host's chrome (Header/Footer),\n * so it only owns the section body — token-only for theme inheritance.\n */\nexport function BlogList() {\n const { config } = useBlogContext()\n const { posts, loading, error } = useBlogPosts()\n\n const listTitle = config.labels.listSubtitle || config.labels.listTitle\n useSeo({\n title: `${listTitle}${config.siteName ? ` · ${config.siteName}` : ''}`,\n description: config.labels.listSubtitle || `Artigos do blog${config.siteName ? ` da ${config.siteName}` : ''}.`,\n canonical: `${typeof window !== 'undefined' ? window.location.origin : ''}${config.basePath}`,\n type: 'website',\n siteName: config.siteName,\n jsonLd: posts.length ? blogJsonLd(posts, { basePath: config.basePath, siteName: config.siteName, title: listTitle }) : undefined,\n })\n\n return (\n <section className=\"py-20 bg-background\">\n <div className=\"container mx-auto px-6\">\n <div className=\"mb-12 max-w-2xl\">\n <span className=\"inline-block rounded-full bg-accent px-4 py-1 text-xs font-semibold uppercase tracking-widest text-primary mb-4\">\n {config.labels.listTitle}\n </span>\n {config.labels.listSubtitle ? (\n <h1 className=\"font-heading text-4xl md:text-5xl font-bold text-foreground\">\n {config.labels.listSubtitle}\n </h1>\n ) : null}\n </div>\n\n {loading ? (\n <p className=\"text-muted-foreground\">Carregando artigos…</p>\n ) : error ? (\n <p className=\"text-muted-foreground\">Não foi possível carregar os artigos.</p>\n ) : posts.length === 0 ? (\n <p className=\"text-muted-foreground\">Nenhum artigo publicado ainda.</p>\n ) : (\n <div className=\"grid grid-cols-1 md:grid-cols-3 gap-6\">\n {posts.map((post) => (\n <PostCard key={post.slug} post={post} basePath={config.basePath} />\n ))}\n </div>\n )}\n </div>\n </section>\n )\n}\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/components/index.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAA;AACrC,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAA;AACzC,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAA;AACrC,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAA"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export { BlogList, PostCard } from './chunk-YI3WLZ4S.js';
|
|
2
|
+
import './chunk-GTH5Z4I6.js';
|
|
3
|
+
export { PostDetail } from './chunk-LCYIM3AB.js';
|
|
4
|
+
import './chunk-HBD463WC.js';
|
|
5
|
+
export { AuthorAvatar } from './chunk-3TZYTDNI.js';
|
|
6
|
+
import './chunk-TYSSR2VZ.js';
|
|
7
|
+
//# sourceMappingURL=components.js.map
|
|
8
|
+
//# sourceMappingURL=components.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"names":[],"mappings":"","file":"components.js"}
|
package/dist/index.d.ts
CHANGED
|
@@ -28,8 +28,4 @@ export { buildBlogDomainEvents, BLOG_POST_EVENT_FIELDS } from './lib/blog-events
|
|
|
28
28
|
export { postSeo, blogListSeo, articleJsonLd, blogJsonLd, postUrl, blogUrl } from './lib/seo';
|
|
29
29
|
export type { BlogSeoDescriptor, BlogSeoOptions } from './lib/seo';
|
|
30
30
|
export type { UseBlogPostsResult, UsePostResult } from './hooks';
|
|
31
|
-
export { BlogList } from './components/BlogList';
|
|
32
|
-
export { PostDetail } from './components/PostDetail';
|
|
33
|
-
export { PostCard } from './components/PostCard';
|
|
34
|
-
export { AuthorAvatar } from './components/AuthorAvatar';
|
|
35
31
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAqD,KAAK,EAAE,EAAE,KAAK,SAAS,EAAE,MAAM,OAAO,CAAA;AAClG,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,eAAe,CAAA;AAEnD,OAAO,EAAiB,KAAK,iBAAiB,EAAE,MAAM,UAAU,CAAA;AAGhE,OAAO,EAAgB,KAAK,gBAAgB,EAAE,MAAM,WAAW,CAAA;AA2D/D,MAAM,WAAW,iBAAiB;IAChC,oDAAoD;IACpD,QAAQ,EAAE,cAAc,CAAA;IACxB,sFAAsF;IACtF,QAAQ,EAAE,EAAE,CAAC;QAAE,QAAQ,EAAE,SAAS,CAAA;KAAE,CAAC,CAAA;IACrC,2EAA2E;IAC3E,YAAY,EAAE,gBAAgB,CAAC,UAAU,CAAC,CAAA;CAC3C;AAED,wBAAgB,gBAAgB,CAAC,OAAO,CAAC,EAAE,iBAAiB,GAAG,iBAAiB,CAyJ/E;AAGD,YAAY,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,MAAM,UAAU,CAAA;AACrE,YAAY,EAAE,QAAQ,EAAE,aAAa,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA;AAChF,YAAY,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAA;AACpD,OAAO,EAAE,sBAAsB,EAAE,0BAA0B,EAAE,MAAM,QAAQ,CAAA;AAC3E,YAAY,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AACzD,OAAO,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,WAAW,CAAA;AACxD,YAAY,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAA;AACjD,OAAO,EAAE,YAAY,EAAE,OAAO,EAAE,MAAM,SAAS,CAAA;AAI/C,OAAO,EACL,mBAAmB,EACnB,iBAAiB,EACjB,gBAAgB,GACjB,MAAM,gBAAgB,CAAA;AACvB,YAAY,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAA;AACrE,OAAO,EAAE,eAAe,EAAE,eAAe,EAAE,eAAe,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAA;AAIrG,OAAO,EAAE,qBAAqB,EAAE,sBAAsB,EAAE,MAAM,mBAAmB,CAAA;AAGjF,OAAO,EAAE,OAAO,EAAE,WAAW,EAAE,aAAa,EAAE,UAAU,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AAC7F,YAAY,EAAE,iBAAiB,EAAE,cAAc,EAAE,MAAM,WAAW,CAAA;AAClE,YAAY,EAAE,kBAAkB,EAAE,aAAa,EAAE,MAAM,SAAS,CAAA"}
|