@fayz-ai/plugin-blog 0.11.1 → 0.12.1

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/dist/index.js CHANGED
@@ -1,10 +1,9 @@
1
- import { createContext, useContext, useState, useEffect, createElement } from 'react';
2
- import { absoluteUrl, organizationJsonLd, breadcrumbJsonLd, useSeo, hasLoader, defineLoader, createSafeDataProvider, getActiveTenantId, getSupabaseClientOptional } from '@fayz-ai/core';
3
- import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
4
- import { Link, useParams } from 'react-router-dom';
5
- import { Clock } from 'lucide-react';
6
-
7
- // src/index.ts
1
+ export { useBlogPosts } from './chunk-GTH5Z4I6.js';
2
+ export { usePost } from './chunk-HBD463WC.js';
3
+ import { BlogProvider } from './chunk-TYSSR2VZ.js';
4
+ export { BlogProvider, articleJsonLd, blogJsonLd, blogListSeo, blogUrl, postSeo, postUrl, useBlogContext } from './chunk-TYSSR2VZ.js';
5
+ import { lazy, createElement, Suspense } from 'react';
6
+ import { hasLoader, defineLoader, createSafeDataProvider, getActiveTenantId, getSupabaseClientOptional } from '@fayz-ai/core';
8
7
 
9
8
  // src/config.ts
10
9
  function resolveConfig(options) {
@@ -112,284 +111,6 @@ function createSupabaseBlogProvider(options) {
112
111
  }
113
112
  };
114
113
  }
115
- var BlogContext = createContext(null);
116
- function BlogProvider({ value, children }) {
117
- return /* @__PURE__ */ jsx(BlogContext.Provider, { value, children });
118
- }
119
- function useBlogContext() {
120
- const ctx = useContext(BlogContext);
121
- if (!ctx) {
122
- throw new Error("[plugin-blog] useBlogContext must be used within <BlogProvider>. Mount the plugin Provider at the app root.");
123
- }
124
- return ctx;
125
- }
126
- function useBlogPosts(query) {
127
- const { provider } = useBlogContext();
128
- const [posts, setPosts] = useState([]);
129
- const [loading, setLoading] = useState(true);
130
- const [error, setError] = useState(null);
131
- const limit = query?.limit;
132
- const tag = query?.tag;
133
- useEffect(() => {
134
- let active = true;
135
- setLoading(true);
136
- provider.listPosts({ limit, tag }).then((result) => {
137
- if (!active) return;
138
- setPosts(result);
139
- setError(null);
140
- }).catch((err) => {
141
- if (!active) return;
142
- setError(err instanceof Error ? err : new Error(String(err)));
143
- }).finally(() => {
144
- if (active) setLoading(false);
145
- });
146
- return () => {
147
- active = false;
148
- };
149
- }, [provider, limit, tag]);
150
- return { posts, loading, error };
151
- }
152
- function AuthorAvatar({ author, className = "h-8 w-8 text-xs" }) {
153
- if (author.avatarUrl) {
154
- return /* @__PURE__ */ jsx(
155
- "img",
156
- {
157
- src: author.avatarUrl,
158
- alt: author.name,
159
- loading: "lazy",
160
- className: `shrink-0 rounded-full object-cover ${className}`
161
- }
162
- );
163
- }
164
- 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() });
165
- }
166
- function PostCard({ post, basePath = "/blog" }) {
167
- return /* @__PURE__ */ jsxs(
168
- Link,
169
- {
170
- to: `${basePath}/${post.slug}`,
171
- 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",
172
- children: [
173
- /* @__PURE__ */ jsx("div", { className: "overflow-hidden h-48", children: /* @__PURE__ */ jsx(
174
- "img",
175
- {
176
- src: post.image,
177
- alt: post.title,
178
- loading: "lazy",
179
- className: "h-full w-full object-cover transition-transform duration-500 group-hover:scale-105"
180
- }
181
- ) }),
182
- /* @__PURE__ */ jsxs("div", { className: "flex flex-col flex-1 p-6", children: [
183
- /* @__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 }),
184
- /* @__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 }),
185
- /* @__PURE__ */ jsx("p", { className: "text-muted-foreground text-sm leading-relaxed flex-1 mb-4", children: post.excerpt }),
186
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2.5 pt-4 border-t border-border", children: [
187
- /* @__PURE__ */ jsx(AuthorAvatar, { author: post.author, className: "h-7 w-7 text-xs" }),
188
- /* @__PURE__ */ jsxs("div", { className: "min-w-0", children: [
189
- /* @__PURE__ */ jsx("p", { className: "truncate text-xs font-semibold text-foreground", children: post.author.name }),
190
- /* @__PURE__ */ jsxs("p", { className: "flex items-center gap-1 text-[11px] text-muted-foreground", children: [
191
- post.date,
192
- /* @__PURE__ */ jsx("span", { "aria-hidden": true, children: "\xB7" }),
193
- /* @__PURE__ */ jsx(Clock, { className: "h-3 w-3" }),
194
- post.readTime
195
- ] })
196
- ] })
197
- ] })
198
- ] })
199
- ]
200
- }
201
- );
202
- }
203
- function blogUrl(basePath, path, origin) {
204
- const joined = `${basePath}${path}`.replace(/\/+/g, "/");
205
- if (origin) return `${origin.replace(/\/$/, "")}${joined.startsWith("/") ? joined : `/${joined}`}`;
206
- const ambient = absoluteUrl(joined);
207
- return ambient.startsWith("http") ? ambient : void 0;
208
- }
209
- function postUrl(basePath, slug, origin) {
210
- return blogUrl(basePath, `/${slug}`, origin);
211
- }
212
- function articleJsonLd(post, opts) {
213
- const url = postUrl(opts.basePath, post.slug, opts.origin);
214
- const posting = {
215
- "@context": "https://schema.org",
216
- "@type": "BlogPosting",
217
- headline: post.title,
218
- description: post.excerpt,
219
- image: post.image ? [post.image] : void 0,
220
- articleSection: post.tag,
221
- ...url ? { mainEntityOfPage: { "@type": "WebPage", "@id": url } } : {},
222
- author: {
223
- "@type": "Person",
224
- name: post.author.name,
225
- ...post.author.role ? { jobTitle: post.author.role } : {}
226
- },
227
- ...post.publishedAt ? { datePublished: post.publishedAt, dateModified: post.publishedAt } : {},
228
- ...opts.siteName ? { publisher: organizationJsonLd(opts.siteName) } : {}
229
- };
230
- const listUrl = blogUrl(opts.basePath, "", opts.origin);
231
- if (!url || !listUrl) return [posting];
232
- return [posting, breadcrumbJsonLd([{ name: "Blog", url: listUrl }, { name: post.title, url }])];
233
- }
234
- function blogJsonLd(posts, opts) {
235
- return [
236
- {
237
- "@context": "https://schema.org",
238
- "@type": "Blog",
239
- name: opts.title,
240
- ...blogUrl(opts.basePath, "", opts.origin) ? { url: blogUrl(opts.basePath, "", opts.origin) } : {},
241
- ...opts.siteName ? { publisher: organizationJsonLd(opts.siteName) } : {},
242
- blogPost: posts.map((p) => ({
243
- "@type": "BlogPosting",
244
- headline: p.title,
245
- ...postUrl(opts.basePath, p.slug, opts.origin) ? { url: postUrl(opts.basePath, p.slug, opts.origin) } : {},
246
- image: p.image,
247
- author: { "@type": "Person", name: p.author.name },
248
- ...p.publishedAt ? { datePublished: p.publishedAt } : {}
249
- }))
250
- }
251
- ];
252
- }
253
- function postSeo(post, opts) {
254
- return {
255
- title: opts.siteName ? `${post.title} \u2014 ${opts.siteName}` : post.title,
256
- description: post.excerpt?.slice(0, 160),
257
- canonical: postUrl(opts.basePath, post.slug, opts.origin),
258
- image: post.image,
259
- // 'article', not 'website': it is what tells a share card to show the author
260
- // and published date rather than treating the post as a site landing page.
261
- type: "article",
262
- jsonLd: articleJsonLd(post, opts)
263
- };
264
- }
265
- function blogListSeo(posts, opts) {
266
- return {
267
- title: opts.siteName ? `${opts.title} \u2014 ${opts.siteName}` : opts.title,
268
- description: opts.description,
269
- canonical: blogUrl(opts.basePath, "", opts.origin),
270
- image: posts.find((p) => p.image)?.image,
271
- type: "website",
272
- jsonLd: blogJsonLd(posts, opts)
273
- };
274
- }
275
- function BlogList() {
276
- const { config } = useBlogContext();
277
- const { posts, loading, error } = useBlogPosts();
278
- const listTitle = config.labels.listSubtitle || config.labels.listTitle;
279
- useSeo({
280
- title: `${listTitle}${config.siteName ? ` \xB7 ${config.siteName}` : ""}`,
281
- description: config.labels.listSubtitle || `Artigos do blog${config.siteName ? ` da ${config.siteName}` : ""}.`,
282
- canonical: `${typeof window !== "undefined" ? window.location.origin : ""}${config.basePath}`,
283
- type: "website",
284
- siteName: config.siteName,
285
- jsonLd: posts.length ? blogJsonLd(posts, { basePath: config.basePath, siteName: config.siteName, title: listTitle }) : void 0
286
- });
287
- return /* @__PURE__ */ jsx("section", { className: "py-20 bg-background", children: /* @__PURE__ */ jsxs("div", { className: "container mx-auto px-6", children: [
288
- /* @__PURE__ */ jsxs("div", { className: "mb-12 max-w-2xl", children: [
289
- /* @__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 }),
290
- config.labels.listSubtitle ? /* @__PURE__ */ jsx("h1", { className: "font-heading text-4xl md:text-5xl font-bold text-foreground", children: config.labels.listSubtitle }) : null
291
- ] }),
292
- 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)) })
293
- ] }) });
294
- }
295
- function usePost(slug) {
296
- const { provider } = useBlogContext();
297
- const [post, setPost] = useState(null);
298
- const [loading, setLoading] = useState(true);
299
- const [error, setError] = useState(null);
300
- useEffect(() => {
301
- let active = true;
302
- if (!slug) {
303
- setPost(null);
304
- setLoading(false);
305
- return;
306
- }
307
- setLoading(true);
308
- provider.getPost(slug).then((result) => {
309
- if (!active) return;
310
- setPost(result);
311
- setError(null);
312
- }).catch((err) => {
313
- if (!active) return;
314
- setError(err instanceof Error ? err : new Error(String(err)));
315
- }).finally(() => {
316
- if (active) setLoading(false);
317
- });
318
- return () => {
319
- active = false;
320
- };
321
- }, [provider, slug]);
322
- return { post, loading, error };
323
- }
324
- function PostDetail() {
325
- const { config } = useBlogContext();
326
- const { slug } = useParams();
327
- const { post, loading, error } = usePost(slug);
328
- useSeo(
329
- post ? {
330
- title: `${post.title}${config.siteName ? ` \xB7 ${config.siteName}` : ""}`,
331
- description: post.excerpt,
332
- canonical: postUrl(config.basePath, post.slug),
333
- image: post.image,
334
- type: "article",
335
- siteName: config.siteName,
336
- jsonLd: articleJsonLd(post, { basePath: config.basePath, siteName: config.siteName })
337
- } : { title: config.siteName || "Blog" }
338
- );
339
- const paragraphs = post?.body ? post.body.split(/\n\s*\n/).map((p) => p.trim()).filter(Boolean) : [];
340
- return /* @__PURE__ */ jsx("article", { className: "py-16 bg-background", children: /* @__PURE__ */ jsxs("div", { className: "container mx-auto px-6 max-w-3xl", children: [
341
- /* @__PURE__ */ jsx(Link, { to: config.basePath, className: "text-sm font-medium text-primary hover:underline", children: config.labels.backToList }),
342
- loading ? /* @__PURE__ */ jsx("p", { className: "mt-10 text-muted-foreground", children: "Carregando\u2026" }) : error || !post ? /* @__PURE__ */ jsxs("div", { className: "mt-10", children: [
343
- /* @__PURE__ */ jsx("h1", { className: "font-heading text-3xl font-bold text-foreground", children: "Artigo n\xE3o encontrado" }),
344
- /* @__PURE__ */ jsx("p", { className: "mt-2 text-muted-foreground", children: "Este artigo pode ter sido movido ou removido." })
345
- ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
346
- /* @__PURE__ */ jsx("h1", { className: "mt-8 font-heading text-4xl md:text-5xl font-bold text-foreground leading-tight", children: post.title }),
347
- /* @__PURE__ */ jsxs("div", { className: "mt-6 flex items-center gap-3", children: [
348
- /* @__PURE__ */ jsx(AuthorAvatar, { author: post.author, className: "h-11 w-11 text-base" }),
349
- /* @__PURE__ */ jsxs("div", { children: [
350
- /* @__PURE__ */ jsx("p", { className: "font-semibold text-foreground", children: post.author.name }),
351
- /* @__PURE__ */ jsxs("p", { className: "flex flex-wrap items-center gap-1.5 text-sm text-muted-foreground", children: [
352
- post.author.role ? /* @__PURE__ */ jsxs("span", { children: [
353
- post.author.role,
354
- /* @__PURE__ */ jsx("span", { "aria-hidden": true, children: " \xB7 " })
355
- ] }) : null,
356
- /* @__PURE__ */ jsx("time", { dateTime: post.publishedAt, children: post.date }),
357
- /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-1", children: [
358
- /* @__PURE__ */ jsx(Clock, { className: "h-3 w-3" }),
359
- post.readTime,
360
- " de leitura"
361
- ] })
362
- ] })
363
- ] })
364
- ] }),
365
- /* @__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" }) }),
366
- /* @__PURE__ */ jsxs("div", { className: "mt-8 space-y-5", children: [
367
- /* @__PURE__ */ jsx("p", { className: "text-lg text-foreground/90 leading-relaxed font-medium", children: post.excerpt }),
368
- paragraphs.map((para, i) => /* @__PURE__ */ jsx("p", { className: "text-muted-foreground leading-relaxed", children: para }, i))
369
- ] }),
370
- /* @__PURE__ */ jsxs("div", { className: "mt-10 flex flex-wrap items-center gap-2 border-t border-border pt-6", children: [
371
- /* @__PURE__ */ jsx("span", { className: "text-sm text-muted-foreground", children: "Categoria:" }),
372
- /* @__PURE__ */ jsx(
373
- Link,
374
- {
375
- to: config.basePath,
376
- className: "rounded-full bg-accent px-3 py-1 text-xs font-semibold text-primary transition-colors hover:bg-accent/70",
377
- children: post.tag
378
- }
379
- )
380
- ] }),
381
- 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: [
382
- /* @__PURE__ */ jsx(AuthorAvatar, { author: post.author, className: "h-14 w-14 text-lg" }),
383
- /* @__PURE__ */ jsxs("div", { children: [
384
- /* @__PURE__ */ jsx("p", { className: "text-xs font-semibold uppercase tracking-wide text-muted-foreground", children: "Escrito por" }),
385
- /* @__PURE__ */ jsx("p", { className: "font-heading text-lg font-bold text-foreground", children: post.author.name }),
386
- post.author.role ? /* @__PURE__ */ jsx("p", { className: "text-sm text-primary", children: post.author.role }) : null,
387
- /* @__PURE__ */ jsx("p", { className: "mt-2 text-sm text-muted-foreground leading-relaxed", children: post.author.bio })
388
- ] })
389
- ] }) : null
390
- ] })
391
- ] }) });
392
- }
393
114
 
394
115
  // src/migrations/index.ts
395
116
  var MIGRATION_001_BLOG = `-- ============================================================================
@@ -1343,6 +1064,15 @@ var MIGRATION_META = {
1343
1064
  description: "Per-post SEO, external identity (external_ref/source/language/tags), blog_upsert_external_post + agent_blog_draft_post, and the blog.post.* triggers"
1344
1065
  }
1345
1066
  };
1067
+ function lazyRoute(load) {
1068
+ const Lazy = lazy(load);
1069
+ const Route = () => createElement(Suspense, { fallback: null }, createElement(Lazy));
1070
+ return Route;
1071
+ }
1072
+ var BlogListRoute = lazyRoute(() => import('./BlogList-FBNCM3S3.js').then((m) => ({ default: m.BlogList })));
1073
+ BlogListRoute.displayName = "BlogListRoute";
1074
+ var PostDetailRoute = lazyRoute(() => import('./PostDetail-AM2XHIMM.js').then((m) => ({ default: m.PostDetail })));
1075
+ PostDetailRoute.displayName = "PostDetailRoute";
1346
1076
  function createBlogPlugin(options) {
1347
1077
  const config = resolveConfig(options);
1348
1078
  const provider = options?.dataProvider ?? createSafeDataProvider(
@@ -1466,8 +1196,8 @@ function createBlogPlugin(options) {
1466
1196
  // Host owns website nav; no admin nav in M1.
1467
1197
  navigation: [],
1468
1198
  routes: [
1469
- { path: config.basePath, component: BlogList, guard: "public" },
1470
- { path: `${config.basePath}/:slug`, component: PostDetail, guard: "public" }
1199
+ { path: config.basePath, component: BlogListRoute, guard: "public" },
1200
+ { path: `${config.basePath}/:slug`, component: PostDetailRoute, guard: "public" }
1471
1201
  ],
1472
1202
  widgets: [],
1473
1203
  // Measuring what the posts brought in, and pushing a new one anywhere else.
@@ -1484,6 +1214,6 @@ function createBlogPlugin(options) {
1484
1214
  return { manifest, Provider, dataProvider: provider };
1485
1215
  }
1486
1216
 
1487
- export { AuthorAvatar, BLOG_POSTS_LOADER, BLOG_POST_EVENT_FIELDS, BLOG_POST_LOADER, BlogList, BlogProvider, PostCard, PostDetail, articleJsonLd, blogJsonLd, blogListSeo, blogUrl, buildBlogDomainEvents, createBlogPlugin, createMockBlogProvider, createSupabaseBlogProvider, getBlogProvider, hasBlogProvider, postSeo, postUrl, registerBlogLoaders, resetBlogProvider, setBlogProvider, useBlogContext, useBlogPosts, usePost };
1217
+ export { BLOG_POSTS_LOADER, BLOG_POST_EVENT_FIELDS, BLOG_POST_LOADER, buildBlogDomainEvents, createBlogPlugin, createMockBlogProvider, createSupabaseBlogProvider, getBlogProvider, hasBlogProvider, registerBlogLoaders, resetBlogProvider, setBlogProvider };
1488
1218
  //# sourceMappingURL=index.js.map
1489
1219
  //# sourceMappingURL=index.js.map