@fayz-ai/plugin-blog 0.9.0 → 0.10.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/dist/data/loaders.d.ts +17 -0
- package/dist/data/loaders.d.ts.map +1 -0
- package/dist/data/runtime.d.ts +15 -0
- package/dist/data/runtime.d.ts.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +114 -13
- package/dist/index.js.map +1 -1
- package/dist/lib/seo.d.ts +48 -0
- package/dist/lib/seo.d.ts.map +1 -0
- package/package.json +7 -7
- package/dist/seo.d.ts +0 -16
- package/dist/seo.d.ts.map +0 -1
- package/src/components/AuthorAvatar.tsx +0 -20
- package/src/components/BlogList.tsx +0 -54
- package/src/components/PostCard.tsx +0 -44
- package/src/components/PostDetail.tsx +0 -108
- package/src/config.ts +0 -55
- package/src/context.tsx +0 -23
- package/src/data/index.ts +0 -4
- package/src/data/mock.ts +0 -71
- package/src/data/supabase.ts +0 -95
- package/src/data/types.ts +0 -10
- package/src/hooks/index.ts +0 -4
- package/src/hooks/useBlogPosts.ts +0 -47
- package/src/hooks/usePost.ts +0 -46
- package/src/index.ts +0 -92
- package/src/migrations/001_blog.sql +0 -103
- package/src/migrations/index.ts +0 -112
- package/src/seo.ts +0 -63
- package/src/types.ts +0 -58
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export declare const BLOG_POSTS_LOADER = "blog.posts";
|
|
2
|
+
export declare const BLOG_POST_LOADER = "blog.post";
|
|
3
|
+
export interface BlogPostsParams extends Record<string, unknown> {
|
|
4
|
+
/** Cap the list. Omitted means every published post. */
|
|
5
|
+
limit?: number;
|
|
6
|
+
/** Only posts carrying this tag. */
|
|
7
|
+
tag?: string;
|
|
8
|
+
}
|
|
9
|
+
export interface BlogPostParams extends Record<string, unknown> {
|
|
10
|
+
slug?: string;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Register the blog reads. Idempotent, so mounting a second blog (or a hot
|
|
14
|
+
* reload) does not throw on a duplicate id.
|
|
15
|
+
*/
|
|
16
|
+
export declare function registerBlogLoaders(): void;
|
|
17
|
+
//# sourceMappingURL=loaders.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"loaders.d.ts","sourceRoot":"","sources":["../../src/data/loaders.ts"],"names":[],"mappings":"AAmBA,eAAO,MAAM,iBAAiB,eAAe,CAAA;AAC7C,eAAO,MAAM,gBAAgB,cAAc,CAAA;AAE3C,MAAM,WAAW,eAAgB,SAAQ,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IAC9D,wDAAwD;IACxD,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,oCAAoC;IACpC,GAAG,CAAC,EAAE,MAAM,CAAA;CACb;AAED,MAAM,WAAW,cAAe,SAAQ,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IAC7D,IAAI,CAAC,EAAE,MAAM,CAAA;CACd;AAeD;;;GAGG;AACH,wBAAgB,mBAAmB,IAAI,IAAI,CA4C1C"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { BlogDataProvider } from './types';
|
|
2
|
+
export declare function setBlogProvider(provider: BlogDataProvider): void;
|
|
3
|
+
/**
|
|
4
|
+
* The registered provider.
|
|
5
|
+
*
|
|
6
|
+
* Throws rather than returning an empty result: a loader that silently answered
|
|
7
|
+
* "no posts" when the plugin was never mounted would render an empty blog and
|
|
8
|
+
* give no reason, and the caller cannot tell that apart from a blog with no
|
|
9
|
+
* posts yet.
|
|
10
|
+
*/
|
|
11
|
+
export declare function getBlogProvider(): BlogDataProvider;
|
|
12
|
+
export declare function hasBlogProvider(): boolean;
|
|
13
|
+
/** Test seam. */
|
|
14
|
+
export declare function resetBlogProvider(): void;
|
|
15
|
+
//# sourceMappingURL=runtime.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../../src/data/runtime.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAA;AAmB/C,wBAAgB,eAAe,CAAC,QAAQ,EAAE,gBAAgB,GAAG,IAAI,CAEhE;AAED;;;;;;;GAOG;AACH,wBAAgB,eAAe,IAAI,gBAAgB,CAOlD;AAED,wBAAgB,eAAe,IAAI,OAAO,CAEzC;AAED,iBAAiB;AACjB,wBAAgB,iBAAiB,IAAI,IAAI,CAExC"}
|
package/dist/index.d.ts
CHANGED
|
@@ -21,6 +21,11 @@ export type { BlogSeed, BlogSeedPost } from './data/mock';
|
|
|
21
21
|
export { BlogProvider, useBlogContext } from './context';
|
|
22
22
|
export type { BlogContextValue } from './context';
|
|
23
23
|
export { useBlogPosts, usePost } from './hooks';
|
|
24
|
+
export { registerBlogLoaders, BLOG_POSTS_LOADER, BLOG_POST_LOADER, } from './data/loaders';
|
|
25
|
+
export type { BlogPostsParams, BlogPostParams } from './data/loaders';
|
|
26
|
+
export { setBlogProvider, getBlogProvider, hasBlogProvider, resetBlogProvider } from './data/runtime';
|
|
27
|
+
export { postSeo, blogListSeo, articleJsonLd, blogJsonLd, postUrl, blogUrl } from './lib/seo';
|
|
28
|
+
export type { BlogSeoDescriptor, BlogSeoOptions } from './lib/seo';
|
|
24
29
|
export type { UseBlogPostsResult, UsePostResult } from './hooks';
|
|
25
30
|
export { BlogList } from './components/BlogList';
|
|
26
31
|
export { PostDetail } from './components/PostDetail';
|
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,EAAiB,KAAK,EAAE,EAAE,KAAK,SAAS,EAAE,MAAM,OAAO,CAAA;AAC9D,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;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAiB,KAAK,EAAE,EAAE,KAAK,SAAS,EAAE,MAAM,OAAO,CAAA;AAC9D,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;AAiB/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,CAoD/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;AAGrG,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;AAChE,OAAO,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAA;AAChD,OAAO,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAA;AACpD,OAAO,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAA;AAChD,OAAO,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAA"}
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createContext, useContext, useState, useEffect, createElement } from 'react';
|
|
2
|
-
import {
|
|
2
|
+
import { absoluteUrl, organizationJsonLd, breadcrumbJsonLd, useSeo, hasLoader, defineLoader, createSafeDataProvider, getActiveTenantId, getSupabaseClientOptional } from '@fayz-ai/core';
|
|
3
3
|
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
4
4
|
import { Link, useParams } from 'react-router-dom';
|
|
5
5
|
import { Clock } from 'lucide-react';
|
|
@@ -200,11 +200,17 @@ function PostCard({ post, basePath = "/blog" }) {
|
|
|
200
200
|
}
|
|
201
201
|
);
|
|
202
202
|
}
|
|
203
|
-
function
|
|
204
|
-
|
|
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);
|
|
205
211
|
}
|
|
206
212
|
function articleJsonLd(post, opts) {
|
|
207
|
-
const url = postUrl(opts.basePath, post.slug);
|
|
213
|
+
const url = postUrl(opts.basePath, post.slug, opts.origin);
|
|
208
214
|
const posting = {
|
|
209
215
|
"@context": "https://schema.org",
|
|
210
216
|
"@type": "BlogPosting",
|
|
@@ -212,7 +218,7 @@ function articleJsonLd(post, opts) {
|
|
|
212
218
|
description: post.excerpt,
|
|
213
219
|
image: post.image ? [post.image] : void 0,
|
|
214
220
|
articleSection: post.tag,
|
|
215
|
-
mainEntityOfPage: { "@type": "WebPage", "@id": url },
|
|
221
|
+
...url ? { mainEntityOfPage: { "@type": "WebPage", "@id": url } } : {},
|
|
216
222
|
author: {
|
|
217
223
|
"@type": "Person",
|
|
218
224
|
name: post.author.name,
|
|
@@ -221,11 +227,9 @@ function articleJsonLd(post, opts) {
|
|
|
221
227
|
...post.publishedAt ? { datePublished: post.publishedAt, dateModified: post.publishedAt } : {},
|
|
222
228
|
...opts.siteName ? { publisher: organizationJsonLd(opts.siteName) } : {}
|
|
223
229
|
};
|
|
224
|
-
const
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
]);
|
|
228
|
-
return [posting, breadcrumb];
|
|
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 }])];
|
|
229
233
|
}
|
|
230
234
|
function blogJsonLd(posts, opts) {
|
|
231
235
|
return [
|
|
@@ -233,12 +237,12 @@ function blogJsonLd(posts, opts) {
|
|
|
233
237
|
"@context": "https://schema.org",
|
|
234
238
|
"@type": "Blog",
|
|
235
239
|
name: opts.title,
|
|
236
|
-
url:
|
|
240
|
+
...blogUrl(opts.basePath, "", opts.origin) ? { url: blogUrl(opts.basePath, "", opts.origin) } : {},
|
|
237
241
|
...opts.siteName ? { publisher: organizationJsonLd(opts.siteName) } : {},
|
|
238
242
|
blogPost: posts.map((p) => ({
|
|
239
243
|
"@type": "BlogPosting",
|
|
240
244
|
headline: p.title,
|
|
241
|
-
url: postUrl(opts.basePath, p.slug),
|
|
245
|
+
...postUrl(opts.basePath, p.slug, opts.origin) ? { url: postUrl(opts.basePath, p.slug, opts.origin) } : {},
|
|
242
246
|
image: p.image,
|
|
243
247
|
author: { "@type": "Person", name: p.author.name },
|
|
244
248
|
...p.publishedAt ? { datePublished: p.publishedAt } : {}
|
|
@@ -246,6 +250,28 @@ function blogJsonLd(posts, opts) {
|
|
|
246
250
|
}
|
|
247
251
|
];
|
|
248
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
|
+
}
|
|
249
275
|
function BlogList() {
|
|
250
276
|
const { config } = useBlogContext();
|
|
251
277
|
const { posts, loading, error } = useBlogPosts();
|
|
@@ -474,6 +500,79 @@ var MIGRATIONS = [
|
|
|
474
500
|
{ id: "001_blog", sql: MIGRATION_001_BLOG }
|
|
475
501
|
];
|
|
476
502
|
|
|
503
|
+
// src/data/runtime.ts
|
|
504
|
+
var _provider = null;
|
|
505
|
+
function setBlogProvider(provider) {
|
|
506
|
+
_provider = provider;
|
|
507
|
+
}
|
|
508
|
+
function getBlogProvider() {
|
|
509
|
+
if (!_provider) {
|
|
510
|
+
throw new Error(
|
|
511
|
+
"[plugin-blog] no blog provider registered \u2014 call createBlogPlugin() before resolving blog loaders, and mount its Provider at the app root."
|
|
512
|
+
);
|
|
513
|
+
}
|
|
514
|
+
return _provider;
|
|
515
|
+
}
|
|
516
|
+
function hasBlogProvider() {
|
|
517
|
+
return _provider !== null;
|
|
518
|
+
}
|
|
519
|
+
function resetBlogProvider() {
|
|
520
|
+
_provider = null;
|
|
521
|
+
}
|
|
522
|
+
var BLOG_POSTS_LOADER = "blog.posts";
|
|
523
|
+
var BLOG_POST_LOADER = "blog.post";
|
|
524
|
+
async function blogPosts(params) {
|
|
525
|
+
const { limit, tag } = params;
|
|
526
|
+
return getBlogProvider().listPosts({ limit, tag });
|
|
527
|
+
}
|
|
528
|
+
async function blogPost(params) {
|
|
529
|
+
const slug = typeof params.slug === "string" ? params.slug : void 0;
|
|
530
|
+
if (!slug) return null;
|
|
531
|
+
return getBlogProvider().getPost(slug);
|
|
532
|
+
}
|
|
533
|
+
function registerBlogLoaders() {
|
|
534
|
+
if (!hasLoader(BLOG_POSTS_LOADER)) {
|
|
535
|
+
defineLoader(BLOG_POSTS_LOADER, blogPosts, {
|
|
536
|
+
source: "plugin",
|
|
537
|
+
title: "Blog posts",
|
|
538
|
+
description: 'Published posts, newest first. The read behind a blog index, a "latest articles" rail, or a tag archive.',
|
|
539
|
+
output: "Array of BlogPost (slug, title, excerpt, body, image, tag, author, publishedAt \u2026).",
|
|
540
|
+
settings: [
|
|
541
|
+
{
|
|
542
|
+
id: "limit",
|
|
543
|
+
type: "number",
|
|
544
|
+
label: "How many",
|
|
545
|
+
help: "Empty means every published post.",
|
|
546
|
+
min: 1,
|
|
547
|
+
max: 100
|
|
548
|
+
},
|
|
549
|
+
{
|
|
550
|
+
id: "tag",
|
|
551
|
+
type: "text",
|
|
552
|
+
label: "Tag",
|
|
553
|
+
help: "Only posts carrying this tag. Empty means all posts."
|
|
554
|
+
}
|
|
555
|
+
]
|
|
556
|
+
});
|
|
557
|
+
}
|
|
558
|
+
if (!hasLoader(BLOG_POST_LOADER)) {
|
|
559
|
+
defineLoader(BLOG_POST_LOADER, blogPost, {
|
|
560
|
+
source: "plugin",
|
|
561
|
+
title: "Blog post",
|
|
562
|
+
description: "One post by slug. The read behind a post page.",
|
|
563
|
+
output: "A BlogPost, or null when no post has that slug.",
|
|
564
|
+
settings: [
|
|
565
|
+
{
|
|
566
|
+
id: "slug",
|
|
567
|
+
type: "text",
|
|
568
|
+
label: "Slug",
|
|
569
|
+
help: 'Usually bound to the route param, e.g. `{ $param: "slug" }`.'
|
|
570
|
+
}
|
|
571
|
+
]
|
|
572
|
+
});
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
|
|
477
576
|
// src/index.ts
|
|
478
577
|
function createBlogPlugin(options) {
|
|
479
578
|
const config = resolveConfig(options);
|
|
@@ -481,6 +580,8 @@ function createBlogPlugin(options) {
|
|
|
481
580
|
() => createSupabaseBlogProvider({ tenantId: options?.tenantId }),
|
|
482
581
|
() => createMockBlogProvider({ seed: options?.seed, defaultAuthor: options?.defaultAuthor })
|
|
483
582
|
);
|
|
583
|
+
setBlogProvider(provider);
|
|
584
|
+
registerBlogLoaders();
|
|
484
585
|
const value = { provider, config };
|
|
485
586
|
const Provider = ({ children }) => createElement(BlogProvider, { value, children });
|
|
486
587
|
Provider.displayName = "BlogPluginProvider";
|
|
@@ -517,6 +618,6 @@ function createBlogPlugin(options) {
|
|
|
517
618
|
return { manifest, Provider, dataProvider: provider };
|
|
518
619
|
}
|
|
519
620
|
|
|
520
|
-
export { AuthorAvatar, BlogList, BlogProvider, PostCard, PostDetail, createBlogPlugin, createMockBlogProvider, createSupabaseBlogProvider, useBlogContext, useBlogPosts, usePost };
|
|
621
|
+
export { AuthorAvatar, BLOG_POSTS_LOADER, BLOG_POST_LOADER, BlogList, BlogProvider, PostCard, PostDetail, articleJsonLd, blogJsonLd, blogListSeo, blogUrl, createBlogPlugin, createMockBlogProvider, createSupabaseBlogProvider, getBlogProvider, hasBlogProvider, postSeo, postUrl, registerBlogLoaders, resetBlogProvider, setBlogProvider, useBlogContext, useBlogPosts, usePost };
|
|
521
622
|
//# sourceMappingURL=index.js.map
|
|
522
623
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/config.ts","../src/data/mock.ts","../src/data/supabase.ts","../src/context.tsx","../src/hooks/useBlogPosts.ts","../src/components/AuthorAvatar.tsx","../src/components/PostCard.tsx","../src/seo.ts","../src/components/BlogList.tsx","../src/hooks/usePost.ts","../src/components/PostDetail.tsx","../src/migrations/index.ts","../src/index.ts"],"names":["jsx","jsxs","useState","useEffect","Link","Clock"],"mappings":"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","file":"index.js","sourcesContent":["import type { PluginScope, VerticalId } from '@fayz-ai/core'\nimport type { BlogDataProvider } from './data/types'\nimport type { BlogSeed } from './data/mock'\nimport type { BlogAuthor } from './types'\n\nexport interface BlogPluginOptions {\n /** Base path the plugin mounts its public routes under. Default '/blog'. */\n basePath?: string\n /** Seed posts for the mock provider (used until a real backend is wired). */\n seed?: BlogSeed\n /** Default byline author, applied to any post that doesn't specify its own. */\n defaultAuthor?: BlogAuthor\n /** Publisher/brand name — used for SEO (og:site_name, JSON-LD publisher). */\n siteName?: string\n /** Inject a custom provider (e.g. a CMS). Overrides the safe mock/Supabase resolver. */\n dataProvider?: BlogDataProvider\n /** Tenant whose PUBLISHED posts the public (anon) site reads from Supabase.\n * Required for the Supabase provider on an anon website (mirrors how the\n * public booking plugin takes an explicit tenantId). Without it the safe\n * resolver falls back to the mock/seed provider. */\n tenantId?: string\n /** Section eyebrow / labels for the default list screen. */\n labels?: {\n /** Title shown at the top of the /blog list screen. */\n listTitle?: string\n /** Subtitle under the list title. */\n listSubtitle?: string\n /** \"Back to blog\" link label on the detail screen. */\n backToList?: string\n }\n scope?: PluginScope\n verticalId?: VerticalId\n}\n\nexport interface ResolvedBlogConfig {\n basePath: string\n siteName: string\n labels: {\n listTitle: string\n listSubtitle: string\n backToList: string\n }\n}\n\nexport function resolveConfig(options?: BlogPluginOptions): ResolvedBlogConfig {\n return {\n basePath: options?.basePath ?? '/blog',\n siteName: options?.siteName ?? '',\n labels: {\n listTitle: options?.labels?.listTitle ?? 'Blog',\n listSubtitle: options?.labels?.listSubtitle ?? '',\n backToList: options?.labels?.backToList ?? '← Voltar ao blog',\n },\n }\n}\n","import type { BlogDataProvider } from './types'\nimport type { BlogPost, BlogListQuery, BlogAuthor } from '../types'\n\n/** Slugify a title when a seed post omits an explicit slug. */\nfunction slugify(input: string): string {\n return input\n .normalize('NFD')\n // Drop combining accent marks (and any other non-ASCII) so \"saúde\" -> \"saude\".\n .replace(/[^\\x00-\\x7f]/g, '')\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, '-')\n .replace(/(^-|-$)/g, '')\n .slice(0, 80)\n}\n\n/** Seed post — same shape as BlogPost but slug/body/author may be derived. */\nexport type BlogSeedPost = Omit<BlogPost, 'slug' | 'body' | 'author'> & {\n slug?: string\n body?: string\n author?: BlogAuthor\n}\n\nexport interface BlogSeed {\n posts: BlogSeedPost[]\n}\n\nexport interface MockBlogProviderOptions {\n seed?: BlogSeed\n /** Fallback byline used for posts without their own author. */\n defaultAuthor?: BlogAuthor\n}\n\nconst FALLBACK_AUTHOR: BlogAuthor = { name: 'Equipe' }\n\nfunction normalize(post: BlogSeedPost, defaultAuthor: BlogAuthor): BlogPost {\n return {\n ...post,\n slug: post.slug ?? slugify(post.title),\n body: post.body ?? post.excerpt,\n author: post.author ?? defaultAuthor,\n }\n}\n\n/** A built-in fallback post so an unseeded plugin still renders something. */\nconst FALLBACK: BlogSeedPost[] = [\n {\n title: 'Bem-vindo ao blog',\n excerpt: 'Este é um artigo de exemplo servido pelo plugin de blog do Fayz.',\n tag: 'Novidades',\n readTime: '2 min',\n date: 'Hoje',\n image: 'https://images.unsplash.com/photo-1499750310107-5fef28a66643?w=800&q=80',\n },\n]\n\nexport function createMockBlogProvider(options?: MockBlogProviderOptions): BlogDataProvider {\n const defaultAuthor = options?.defaultAuthor ?? FALLBACK_AUTHOR\n const posts: BlogPost[] = (options?.seed?.posts ?? FALLBACK).map((p) => normalize(p, defaultAuthor))\n\n return {\n async listPosts(query?: BlogListQuery): Promise<BlogPost[]> {\n let result = posts\n if (query?.tag) result = result.filter((p) => p.tag === query.tag)\n if (query?.limit != null) result = result.slice(0, query.limit)\n return result\n },\n async getPost(slug: string): Promise<BlogPost | null> {\n return posts.find((p) => p.slug === slug) ?? null\n },\n }\n}\n","import { getSupabaseClientOptional, getActiveTenantId } from '@fayz-ai/core'\nimport type { BlogDataProvider } from './types'\nimport type { BlogPost, BlogListQuery, BlogAuthor } from '../types'\n\n// ---------------------------------------------------------------------------\n// Supabase-backed blog provider (public/website read side).\n//\n// Reads PUBLISHED posts from the anon-safe view public.v_public_blog_posts\n// (see src/migrations/001_blog.sql §4), scoped to a tenant. The backoffice\n// writes to plg_blog_posts directly via the generic CRUD provider; here we only\n// read the published, column-whitelisted view — safe under the publishable key.\n//\n// The tenant is taken from the configured tenantId (anon site) or, failing that,\n// the active session tenant. Without a client or a tenant we return nothing, and\n// createSafeDataProvider falls back to the mock/seed provider.\n// ---------------------------------------------------------------------------\n\nconst PUBLIC_VIEW = 'v_public_blog_posts'\n\nexport interface SupabaseBlogProviderOptions {\n /** Tenant whose published posts to read (anon website). Defaults to the active session tenant. */\n tenantId?: string\n}\n\nfunction getClient() {\n const supabase = getSupabaseClientOptional() as {\n from: (t: string) => any\n } | null\n if (!supabase) throw new Error('[plugin-blog] Supabase client not initialized')\n return supabase\n}\n\n/** Human month-year label (pt-BR) derived from an ISO date, e.g. \"Janeiro 2025\". */\nfunction humanDate(iso?: string | null): string {\n if (!iso) return ''\n const d = new Date(iso)\n if (Number.isNaN(d.getTime())) return ''\n const label = d.toLocaleDateString('pt-BR', { month: 'long', year: 'numeric' })\n return label.charAt(0).toUpperCase() + label.slice(1)\n}\n\nfunction toPost(row: Record<string, any>): BlogPost {\n const author: BlogAuthor = {\n name: row.author_name ?? 'Equipe',\n avatarUrl: row.author_avatar_url ?? undefined,\n role: row.author_role ?? undefined,\n bio: row.author_bio ?? undefined,\n }\n return {\n slug: row.slug,\n title: row.title,\n excerpt: row.excerpt ?? '',\n body: row.body ?? '',\n tag: row.tag ?? '',\n readTime: row.read_time ?? '',\n date: humanDate(row.published_at),\n publishedAt: row.published_at ?? undefined,\n image: row.cover_image ?? '',\n author,\n }\n}\n\nexport function createSupabaseBlogProvider(options?: SupabaseBlogProviderOptions): BlogDataProvider {\n const resolveTenantId = (): string | undefined => options?.tenantId ?? getActiveTenantId() ?? undefined\n\n return {\n async listPosts(query?: BlogListQuery): Promise<BlogPost[]> {\n const tenantId = resolveTenantId()\n if (!tenantId) return []\n let q = getClient()\n .from(PUBLIC_VIEW)\n .select('*')\n .eq('tenant_id', tenantId)\n .order('published_at', { ascending: false, nullsFirst: false })\n if (query?.tag) q = q.eq('tag', query.tag)\n if (query?.limit != null) q = q.limit(query.limit)\n const { data, error } = await q\n if (error) throw error\n return (data ?? []).map(toPost)\n },\n\n async getPost(slug: string): Promise<BlogPost | null> {\n const tenantId = resolveTenantId()\n if (!tenantId) return null\n const { data, error } = await getClient()\n .from(PUBLIC_VIEW)\n .select('*')\n .eq('tenant_id', tenantId)\n .eq('slug', slug)\n .maybeSingle()\n if (error) throw error\n return data ? toPost(data) : null\n },\n }\n}\n","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 { 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","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","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 { 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/** Absolute canonical URL for a post. */\nexport function postUrl(basePath: string, slug: string): string {\n return absoluteUrl(`${basePath}/${slug}`)\n}\n\n/** schema.org BlogPosting + BreadcrumbList for an article page. */\nexport function articleJsonLd(post: BlogPost, opts: { basePath: string; siteName: string }): Array<Record<string, unknown>> {\n const url = postUrl(opts.basePath, post.slug)\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 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 breadcrumb = breadcrumbJsonLd([\n { name: 'Blog', url: absoluteUrl(opts.basePath) },\n { name: post.title, url },\n ])\n return [posting, breadcrumb]\n}\n\n/** schema.org Blog + post list for the listing page. */\nexport function blogJsonLd(posts: BlogPost[], opts: { basePath: string; siteName: string; title: string }): Array<Record<string, unknown>> {\n return [\n {\n '@context': 'https://schema.org',\n '@type': 'Blog',\n name: opts.title,\n url: absoluteUrl(opts.basePath),\n ...(opts.siteName ? { publisher: organizationJsonLd(opts.siteName) } : {}),\n blogPost: posts.map((p) => ({\n '@type': 'BlogPosting',\n headline: p.title,\n url: postUrl(opts.basePath, p.slug),\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","import { useBlogPosts } from '../hooks/useBlogPosts'\nimport { useBlogContext } from '../context'\nimport { PostCard } from './PostCard'\nimport { useSeo, blogJsonLd } from '../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","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","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 '../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","// AUTO-GENERATED from 001_blog.sql — regenerate with scripts/embed-migrations.mjs\n// SQL files are the source of truth; this inline copy lets the manifest declare\n// migrations as data. Do not edit by hand — run the embed script instead.\n\nexport const MIGRATION_001_BLOG = `-- ============================================================================\n-- plugin-blog 001: blog content model + public (anon) read surface\n--\n-- Backs the blog backoffice (managed from the marketing plugin's \"Blog\" tab) and\n-- the public website surface (/blog, /blog/:slug). Prefix: plg_blog_.\n-- §1 plg_blog_categories — first-class category (name/slug/description)\n-- §2 plg_blog_posts — articles; category_id FK, author byline, status\n-- §3 RLS: authenticated tenant CRUD on both tables (the backoffice)\n-- §4 v_public_blog_posts — anon-readable view of PUBLISHED posts (the site)\n--\n-- Anon-access design mirrors plugin-agenda's public booking surface: the public\n-- website runs with the publishable key and no session, so canonical tenant RLS\n-- yields nothing. Anon read is granted ONLY through the column-whitelisted,\n-- published-only view (§4); the front provider filters it by tenant_id.\n-- Idempotent + safe to re-run.\n-- ============================================================================\n\n-- §1 — categories\nCREATE TABLE IF NOT EXISTS public.plg_blog_categories (\n id uuid PRIMARY KEY DEFAULT gen_random_uuid(),\n tenant_id uuid NOT NULL REFERENCES public.tenants(id) ON DELETE CASCADE,\n name text NOT NULL,\n slug text NOT NULL,\n description text,\n created_at timestamptz NOT NULL DEFAULT now(),\n updated_at timestamptz NOT NULL DEFAULT now()\n);\nALTER TABLE public.plg_blog_categories ENABLE ROW LEVEL SECURITY;\nCREATE INDEX IF NOT EXISTS idx_plg_blog_categories_tenant ON public.plg_blog_categories(tenant_id);\nCREATE UNIQUE INDEX IF NOT EXISTS uq_plg_blog_categories_tenant_slug ON public.plg_blog_categories(tenant_id, slug);\n\n-- §2 — posts\nCREATE TABLE IF NOT EXISTS public.plg_blog_posts (\n id uuid PRIMARY KEY DEFAULT gen_random_uuid(),\n tenant_id uuid NOT NULL REFERENCES public.tenants(id) ON DELETE CASCADE,\n category_id uuid REFERENCES public.plg_blog_categories(id) ON DELETE SET NULL,\n slug text NOT NULL,\n title text NOT NULL,\n excerpt text NOT NULL DEFAULT '',\n body text NOT NULL DEFAULT '',\n cover_image text,\n author_name text,\n author_role text,\n author_avatar_url text,\n author_bio text,\n status text NOT NULL DEFAULT 'draft'\n CHECK (status IN ('draft', 'published')),\n read_time text,\n published_at timestamptz,\n created_at timestamptz NOT NULL DEFAULT now(),\n updated_at timestamptz NOT NULL DEFAULT now()\n);\nALTER TABLE public.plg_blog_posts ENABLE ROW LEVEL SECURITY;\nCREATE INDEX IF NOT EXISTS idx_plg_blog_posts_tenant ON public.plg_blog_posts(tenant_id);\nCREATE INDEX IF NOT EXISTS idx_plg_blog_posts_category ON public.plg_blog_posts(category_id);\nCREATE INDEX IF NOT EXISTS idx_plg_blog_posts_status ON public.plg_blog_posts(tenant_id, status, published_at DESC);\nCREATE UNIQUE INDEX IF NOT EXISTS uq_plg_blog_posts_tenant_slug ON public.plg_blog_posts(tenant_id, slug);\n\n-- §3 — RLS: authenticated tenant CRUD (the backoffice writes here)\nDROP POLICY IF EXISTS plg_blog_categories_select ON public.plg_blog_categories;\nDROP POLICY IF EXISTS plg_blog_categories_insert ON public.plg_blog_categories;\nDROP POLICY IF EXISTS plg_blog_categories_update ON public.plg_blog_categories;\nDROP POLICY IF EXISTS plg_blog_categories_delete ON public.plg_blog_categories;\nCREATE POLICY plg_blog_categories_select ON public.plg_blog_categories FOR SELECT TO authenticated USING (tenant_id IN (SELECT public.user_tenant_ids()));\nCREATE POLICY plg_blog_categories_insert ON public.plg_blog_categories FOR INSERT TO authenticated WITH CHECK (tenant_id IN (SELECT public.user_tenant_ids()));\nCREATE POLICY plg_blog_categories_update ON public.plg_blog_categories FOR UPDATE TO authenticated USING (tenant_id IN (SELECT public.user_tenant_ids()));\nCREATE POLICY plg_blog_categories_delete ON public.plg_blog_categories FOR DELETE TO authenticated USING (tenant_id IN (SELECT public.user_tenant_ids()));\nGRANT SELECT, INSERT, UPDATE, DELETE ON public.plg_blog_categories TO authenticated;\n\nDROP POLICY IF EXISTS plg_blog_posts_select ON public.plg_blog_posts;\nDROP POLICY IF EXISTS plg_blog_posts_insert ON public.plg_blog_posts;\nDROP POLICY IF EXISTS plg_blog_posts_update ON public.plg_blog_posts;\nDROP POLICY IF EXISTS plg_blog_posts_delete ON public.plg_blog_posts;\nCREATE POLICY plg_blog_posts_select ON public.plg_blog_posts FOR SELECT TO authenticated USING (tenant_id IN (SELECT public.user_tenant_ids()));\nCREATE POLICY plg_blog_posts_insert ON public.plg_blog_posts FOR INSERT TO authenticated WITH CHECK (tenant_id IN (SELECT public.user_tenant_ids()));\nCREATE POLICY plg_blog_posts_update ON public.plg_blog_posts FOR UPDATE TO authenticated USING (tenant_id IN (SELECT public.user_tenant_ids()));\nCREATE POLICY plg_blog_posts_delete ON public.plg_blog_posts FOR DELETE TO authenticated USING (tenant_id IN (SELECT public.user_tenant_ids()));\nGRANT SELECT, INSERT, UPDATE, DELETE ON public.plg_blog_posts TO authenticated;\n\n-- §4 — anon-readable public catalog (owner-rights view = deliberate, whitelisted,\n-- PUBLISHED only). The front provider filters by tenant_id. Category name is\n-- surfaced as \\`tag\\` so the website's BlogPost shape stays unchanged.\nCREATE OR REPLACE VIEW public.v_public_blog_posts AS\nSELECT\n p.id,\n p.tenant_id,\n p.slug,\n p.title,\n p.excerpt,\n p.body,\n COALESCE(c.name, '') AS tag,\n p.cover_image,\n p.author_name,\n p.author_role,\n p.author_avatar_url,\n p.author_bio,\n p.read_time,\n p.published_at\nFROM public.plg_blog_posts p\nLEFT JOIN public.plg_blog_categories c ON c.id = p.category_id\nWHERE p.status = 'published';\n\nGRANT SELECT ON public.v_public_blog_posts TO anon, authenticated, service_role;\n`\n\nexport const MIGRATIONS: Array<{ id: string; sql: string }> = [\n { id: \"001_blog\", sql: MIGRATION_001_BLOG },\n]\n","import { createElement, type FC, type ReactNode } from 'react'\nimport type { PluginManifest } from '@fayz-ai/core'\nimport { createSafeDataProvider } from '@fayz-ai/core'\nimport { resolveConfig, type BlogPluginOptions } from './config'\nimport { createMockBlogProvider } from './data/mock'\nimport { createSupabaseBlogProvider } from './data/supabase'\nimport { BlogProvider, type BlogContextValue } from './context'\nimport { BlogList } from './components/BlogList'\nimport { PostDetail } from './components/PostDetail'\nimport { MIGRATIONS } from './migrations'\n\n// ---------------------------------------------------------------------------\n// @fayz-ai/plugin-blog — website-surface blog/articles plugin.\n//\n// First real consumer of scaffolds:['website']. Ships public routes (/blog,\n// /blog/:slug) + data hooks (useBlogPosts/usePost). A bespoke host site feeds\n// its own card markup from the hooks and mounts the routes inside its chrome.\n// See createBlogPlugin's return: a { manifest, Provider } bundle the host reads\n// uniformly (manifest.routes for mounting, Provider for the app-root context).\n// ---------------------------------------------------------------------------\n\nexport interface BlogWebsitePlugin {\n /** Standard plugin manifest (routes + metadata). */\n manifest: PluginManifest\n /** App-root context provider, pre-bound to this plugin's resolved provider/config. */\n Provider: FC<{ children: ReactNode }>\n /** The resolved data provider (exposed for advanced host wiring/tests). */\n dataProvider: BlogContextValue['provider']\n}\n\nexport function createBlogPlugin(options?: BlogPluginOptions): BlogWebsitePlugin {\n const config = resolveConfig(options)\n const provider =\n options?.dataProvider ??\n createSafeDataProvider(\n () => createSupabaseBlogProvider({ tenantId: options?.tenantId }),\n () => createMockBlogProvider({ seed: options?.seed, defaultAuthor: options?.defaultAuthor }),\n )\n\n const value: BlogContextValue = { provider, config }\n const Provider: FC<{ children: ReactNode }> = ({ children }) =>\n createElement(BlogProvider, { value, children })\n Provider.displayName = 'BlogPluginProvider'\n\n const manifest: PluginManifest = {\n id: 'blog',\n name: 'Blog',\n icon: 'Newspaper',\n version: '0.1.0',\n scope: options?.scope ?? 'universal',\n verticalId: options?.verticalId,\n scaffolds: ['website', 'landing_page'],\n defaultEnabled: true,\n dependencies: [],\n declaredFeatures: [{ id: 'blog', label: 'Blog', group: 'Attract' }],\n // Blog posts count against the plan cap. The create UI lives in the\n // marketing \"Blog\" backoffice, which writes to this plugin's posts table.\n declaredLimits: [{ key: 'blog_posts', label: 'Blog posts', table: 'plg_blog_posts' }],\n // Host owns website nav; no admin nav in M1.\n navigation: [],\n routes: [\n { path: config.basePath, component: BlogList, guard: 'public' },\n { path: `${config.basePath}/:slug`, component: PostDetail, guard: 'public' },\n ],\n widgets: [],\n // Owns the blog domain tables (categories + posts + anon-read view). The\n // marketing \"Blog\" backoffice writes to these; the website reads them.\n migrations: MIGRATIONS.map((m) => ({\n id: `blog-${m.id}`,\n version: '1.0.0',\n sql: m.sql,\n description: 'Blog content model (plg_blog_categories, plg_blog_posts) + anon-read view',\n })),\n }\n\n return { manifest, Provider, dataProvider: provider }\n}\n\n// Public API\nexport type { BlogPluginOptions, ResolvedBlogConfig } from './config'\nexport type { BlogPost, BlogListQuery, BlogAuthor, BlogCategory } from './types'\nexport type { BlogDataProvider } from './data/types'\nexport { createMockBlogProvider, createSupabaseBlogProvider } from './data'\nexport type { BlogSeed, BlogSeedPost } from './data/mock'\nexport { BlogProvider, useBlogContext } from './context'\nexport type { BlogContextValue } from './context'\nexport { useBlogPosts, usePost } from './hooks'\nexport type { UseBlogPostsResult, UsePostResult } from './hooks'\nexport { BlogList } from './components/BlogList'\nexport { PostDetail } from './components/PostDetail'\nexport { PostCard } from './components/PostCard'\nexport { AuthorAvatar } from './components/AuthorAvatar'\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/config.ts","../src/data/mock.ts","../src/data/supabase.ts","../src/context.tsx","../src/hooks/useBlogPosts.ts","../src/components/AuthorAvatar.tsx","../src/components/PostCard.tsx","../src/lib/seo.ts","../src/components/BlogList.tsx","../src/hooks/usePost.ts","../src/components/PostDetail.tsx","../src/migrations/index.ts","../src/data/runtime.ts","../src/data/loaders.ts","../src/index.ts"],"names":["jsx","jsxs","useState","useEffect","Link","Clock"],"mappings":"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","file":"index.js","sourcesContent":["import type { PluginScope, VerticalId } from '@fayz-ai/core'\nimport type { BlogDataProvider } from './data/types'\nimport type { BlogSeed } from './data/mock'\nimport type { BlogAuthor } from './types'\n\nexport interface BlogPluginOptions {\n /** Base path the plugin mounts its public routes under. Default '/blog'. */\n basePath?: string\n /** Seed posts for the mock provider (used until a real backend is wired). */\n seed?: BlogSeed\n /** Default byline author, applied to any post that doesn't specify its own. */\n defaultAuthor?: BlogAuthor\n /** Publisher/brand name — used for SEO (og:site_name, JSON-LD publisher). */\n siteName?: string\n /** Inject a custom provider (e.g. a CMS). Overrides the safe mock/Supabase resolver. */\n dataProvider?: BlogDataProvider\n /** Tenant whose PUBLISHED posts the public (anon) site reads from Supabase.\n * Required for the Supabase provider on an anon website (mirrors how the\n * public booking plugin takes an explicit tenantId). Without it the safe\n * resolver falls back to the mock/seed provider. */\n tenantId?: string\n /** Section eyebrow / labels for the default list screen. */\n labels?: {\n /** Title shown at the top of the /blog list screen. */\n listTitle?: string\n /** Subtitle under the list title. */\n listSubtitle?: string\n /** \"Back to blog\" link label on the detail screen. */\n backToList?: string\n }\n scope?: PluginScope\n verticalId?: VerticalId\n}\n\nexport interface ResolvedBlogConfig {\n basePath: string\n siteName: string\n labels: {\n listTitle: string\n listSubtitle: string\n backToList: string\n }\n}\n\nexport function resolveConfig(options?: BlogPluginOptions): ResolvedBlogConfig {\n return {\n basePath: options?.basePath ?? '/blog',\n siteName: options?.siteName ?? '',\n labels: {\n listTitle: options?.labels?.listTitle ?? 'Blog',\n listSubtitle: options?.labels?.listSubtitle ?? '',\n backToList: options?.labels?.backToList ?? '← Voltar ao blog',\n },\n }\n}\n","import type { BlogDataProvider } from './types'\nimport type { BlogPost, BlogListQuery, BlogAuthor } from '../types'\n\n/** Slugify a title when a seed post omits an explicit slug. */\nfunction slugify(input: string): string {\n return input\n .normalize('NFD')\n // Drop combining accent marks (and any other non-ASCII) so \"saúde\" -> \"saude\".\n .replace(/[^\\x00-\\x7f]/g, '')\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, '-')\n .replace(/(^-|-$)/g, '')\n .slice(0, 80)\n}\n\n/** Seed post — same shape as BlogPost but slug/body/author may be derived. */\nexport type BlogSeedPost = Omit<BlogPost, 'slug' | 'body' | 'author'> & {\n slug?: string\n body?: string\n author?: BlogAuthor\n}\n\nexport interface BlogSeed {\n posts: BlogSeedPost[]\n}\n\nexport interface MockBlogProviderOptions {\n seed?: BlogSeed\n /** Fallback byline used for posts without their own author. */\n defaultAuthor?: BlogAuthor\n}\n\nconst FALLBACK_AUTHOR: BlogAuthor = { name: 'Equipe' }\n\nfunction normalize(post: BlogSeedPost, defaultAuthor: BlogAuthor): BlogPost {\n return {\n ...post,\n slug: post.slug ?? slugify(post.title),\n body: post.body ?? post.excerpt,\n author: post.author ?? defaultAuthor,\n }\n}\n\n/** A built-in fallback post so an unseeded plugin still renders something. */\nconst FALLBACK: BlogSeedPost[] = [\n {\n title: 'Bem-vindo ao blog',\n excerpt: 'Este é um artigo de exemplo servido pelo plugin de blog do Fayz.',\n tag: 'Novidades',\n readTime: '2 min',\n date: 'Hoje',\n image: 'https://images.unsplash.com/photo-1499750310107-5fef28a66643?w=800&q=80',\n },\n]\n\nexport function createMockBlogProvider(options?: MockBlogProviderOptions): BlogDataProvider {\n const defaultAuthor = options?.defaultAuthor ?? FALLBACK_AUTHOR\n const posts: BlogPost[] = (options?.seed?.posts ?? FALLBACK).map((p) => normalize(p, defaultAuthor))\n\n return {\n async listPosts(query?: BlogListQuery): Promise<BlogPost[]> {\n let result = posts\n if (query?.tag) result = result.filter((p) => p.tag === query.tag)\n if (query?.limit != null) result = result.slice(0, query.limit)\n return result\n },\n async getPost(slug: string): Promise<BlogPost | null> {\n return posts.find((p) => p.slug === slug) ?? null\n },\n }\n}\n","import { getSupabaseClientOptional, getActiveTenantId } from '@fayz-ai/core'\nimport type { BlogDataProvider } from './types'\nimport type { BlogPost, BlogListQuery, BlogAuthor } from '../types'\n\n// ---------------------------------------------------------------------------\n// Supabase-backed blog provider (public/website read side).\n//\n// Reads PUBLISHED posts from the anon-safe view public.v_public_blog_posts\n// (see src/migrations/001_blog.sql §4), scoped to a tenant. The backoffice\n// writes to plg_blog_posts directly via the generic CRUD provider; here we only\n// read the published, column-whitelisted view — safe under the publishable key.\n//\n// The tenant is taken from the configured tenantId (anon site) or, failing that,\n// the active session tenant. Without a client or a tenant we return nothing, and\n// createSafeDataProvider falls back to the mock/seed provider.\n// ---------------------------------------------------------------------------\n\nconst PUBLIC_VIEW = 'v_public_blog_posts'\n\nexport interface SupabaseBlogProviderOptions {\n /** Tenant whose published posts to read (anon website). Defaults to the active session tenant. */\n tenantId?: string\n}\n\nfunction getClient() {\n const supabase = getSupabaseClientOptional() as {\n from: (t: string) => any\n } | null\n if (!supabase) throw new Error('[plugin-blog] Supabase client not initialized')\n return supabase\n}\n\n/** Human month-year label (pt-BR) derived from an ISO date, e.g. \"Janeiro 2025\". */\nfunction humanDate(iso?: string | null): string {\n if (!iso) return ''\n const d = new Date(iso)\n if (Number.isNaN(d.getTime())) return ''\n const label = d.toLocaleDateString('pt-BR', { month: 'long', year: 'numeric' })\n return label.charAt(0).toUpperCase() + label.slice(1)\n}\n\nfunction toPost(row: Record<string, any>): BlogPost {\n const author: BlogAuthor = {\n name: row.author_name ?? 'Equipe',\n avatarUrl: row.author_avatar_url ?? undefined,\n role: row.author_role ?? undefined,\n bio: row.author_bio ?? undefined,\n }\n return {\n slug: row.slug,\n title: row.title,\n excerpt: row.excerpt ?? '',\n body: row.body ?? '',\n tag: row.tag ?? '',\n readTime: row.read_time ?? '',\n date: humanDate(row.published_at),\n publishedAt: row.published_at ?? undefined,\n image: row.cover_image ?? '',\n author,\n }\n}\n\nexport function createSupabaseBlogProvider(options?: SupabaseBlogProviderOptions): BlogDataProvider {\n const resolveTenantId = (): string | undefined => options?.tenantId ?? getActiveTenantId() ?? undefined\n\n return {\n async listPosts(query?: BlogListQuery): Promise<BlogPost[]> {\n const tenantId = resolveTenantId()\n if (!tenantId) return []\n let q = getClient()\n .from(PUBLIC_VIEW)\n .select('*')\n .eq('tenant_id', tenantId)\n .order('published_at', { ascending: false, nullsFirst: false })\n if (query?.tag) q = q.eq('tag', query.tag)\n if (query?.limit != null) q = q.limit(query.limit)\n const { data, error } = await q\n if (error) throw error\n return (data ?? []).map(toPost)\n },\n\n async getPost(slug: string): Promise<BlogPost | null> {\n const tenantId = resolveTenantId()\n if (!tenantId) return null\n const { data, error } = await getClient()\n .from(PUBLIC_VIEW)\n .select('*')\n .eq('tenant_id', tenantId)\n .eq('slug', slug)\n .maybeSingle()\n if (error) throw error\n return data ? toPost(data) : null\n },\n }\n}\n","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 { 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","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","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 { 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","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","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","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","// AUTO-GENERATED from 001_blog.sql — regenerate with scripts/embed-migrations.mjs\n// SQL files are the source of truth; this inline copy lets the manifest declare\n// migrations as data. Do not edit by hand — run the embed script instead.\n\nexport const MIGRATION_001_BLOG = `-- ============================================================================\n-- plugin-blog 001: blog content model + public (anon) read surface\n--\n-- Backs the blog backoffice (managed from the marketing plugin's \"Blog\" tab) and\n-- the public website surface (/blog, /blog/:slug). Prefix: plg_blog_.\n-- §1 plg_blog_categories — first-class category (name/slug/description)\n-- §2 plg_blog_posts — articles; category_id FK, author byline, status\n-- §3 RLS: authenticated tenant CRUD on both tables (the backoffice)\n-- §4 v_public_blog_posts — anon-readable view of PUBLISHED posts (the site)\n--\n-- Anon-access design mirrors plugin-agenda's public booking surface: the public\n-- website runs with the publishable key and no session, so canonical tenant RLS\n-- yields nothing. Anon read is granted ONLY through the column-whitelisted,\n-- published-only view (§4); the front provider filters it by tenant_id.\n-- Idempotent + safe to re-run.\n-- ============================================================================\n\n-- §1 — categories\nCREATE TABLE IF NOT EXISTS public.plg_blog_categories (\n id uuid PRIMARY KEY DEFAULT gen_random_uuid(),\n tenant_id uuid NOT NULL REFERENCES public.tenants(id) ON DELETE CASCADE,\n name text NOT NULL,\n slug text NOT NULL,\n description text,\n created_at timestamptz NOT NULL DEFAULT now(),\n updated_at timestamptz NOT NULL DEFAULT now()\n);\nALTER TABLE public.plg_blog_categories ENABLE ROW LEVEL SECURITY;\nCREATE INDEX IF NOT EXISTS idx_plg_blog_categories_tenant ON public.plg_blog_categories(tenant_id);\nCREATE UNIQUE INDEX IF NOT EXISTS uq_plg_blog_categories_tenant_slug ON public.plg_blog_categories(tenant_id, slug);\n\n-- §2 — posts\nCREATE TABLE IF NOT EXISTS public.plg_blog_posts (\n id uuid PRIMARY KEY DEFAULT gen_random_uuid(),\n tenant_id uuid NOT NULL REFERENCES public.tenants(id) ON DELETE CASCADE,\n category_id uuid REFERENCES public.plg_blog_categories(id) ON DELETE SET NULL,\n slug text NOT NULL,\n title text NOT NULL,\n excerpt text NOT NULL DEFAULT '',\n body text NOT NULL DEFAULT '',\n cover_image text,\n author_name text,\n author_role text,\n author_avatar_url text,\n author_bio text,\n status text NOT NULL DEFAULT 'draft'\n CHECK (status IN ('draft', 'published')),\n read_time text,\n published_at timestamptz,\n created_at timestamptz NOT NULL DEFAULT now(),\n updated_at timestamptz NOT NULL DEFAULT now()\n);\nALTER TABLE public.plg_blog_posts ENABLE ROW LEVEL SECURITY;\nCREATE INDEX IF NOT EXISTS idx_plg_blog_posts_tenant ON public.plg_blog_posts(tenant_id);\nCREATE INDEX IF NOT EXISTS idx_plg_blog_posts_category ON public.plg_blog_posts(category_id);\nCREATE INDEX IF NOT EXISTS idx_plg_blog_posts_status ON public.plg_blog_posts(tenant_id, status, published_at DESC);\nCREATE UNIQUE INDEX IF NOT EXISTS uq_plg_blog_posts_tenant_slug ON public.plg_blog_posts(tenant_id, slug);\n\n-- §3 — RLS: authenticated tenant CRUD (the backoffice writes here)\nDROP POLICY IF EXISTS plg_blog_categories_select ON public.plg_blog_categories;\nDROP POLICY IF EXISTS plg_blog_categories_insert ON public.plg_blog_categories;\nDROP POLICY IF EXISTS plg_blog_categories_update ON public.plg_blog_categories;\nDROP POLICY IF EXISTS plg_blog_categories_delete ON public.plg_blog_categories;\nCREATE POLICY plg_blog_categories_select ON public.plg_blog_categories FOR SELECT TO authenticated USING (tenant_id IN (SELECT public.user_tenant_ids()));\nCREATE POLICY plg_blog_categories_insert ON public.plg_blog_categories FOR INSERT TO authenticated WITH CHECK (tenant_id IN (SELECT public.user_tenant_ids()));\nCREATE POLICY plg_blog_categories_update ON public.plg_blog_categories FOR UPDATE TO authenticated USING (tenant_id IN (SELECT public.user_tenant_ids()));\nCREATE POLICY plg_blog_categories_delete ON public.plg_blog_categories FOR DELETE TO authenticated USING (tenant_id IN (SELECT public.user_tenant_ids()));\nGRANT SELECT, INSERT, UPDATE, DELETE ON public.plg_blog_categories TO authenticated;\n\nDROP POLICY IF EXISTS plg_blog_posts_select ON public.plg_blog_posts;\nDROP POLICY IF EXISTS plg_blog_posts_insert ON public.plg_blog_posts;\nDROP POLICY IF EXISTS plg_blog_posts_update ON public.plg_blog_posts;\nDROP POLICY IF EXISTS plg_blog_posts_delete ON public.plg_blog_posts;\nCREATE POLICY plg_blog_posts_select ON public.plg_blog_posts FOR SELECT TO authenticated USING (tenant_id IN (SELECT public.user_tenant_ids()));\nCREATE POLICY plg_blog_posts_insert ON public.plg_blog_posts FOR INSERT TO authenticated WITH CHECK (tenant_id IN (SELECT public.user_tenant_ids()));\nCREATE POLICY plg_blog_posts_update ON public.plg_blog_posts FOR UPDATE TO authenticated USING (tenant_id IN (SELECT public.user_tenant_ids()));\nCREATE POLICY plg_blog_posts_delete ON public.plg_blog_posts FOR DELETE TO authenticated USING (tenant_id IN (SELECT public.user_tenant_ids()));\nGRANT SELECT, INSERT, UPDATE, DELETE ON public.plg_blog_posts TO authenticated;\n\n-- §4 — anon-readable public catalog (owner-rights view = deliberate, whitelisted,\n-- PUBLISHED only). The front provider filters by tenant_id. Category name is\n-- surfaced as \\`tag\\` so the website's BlogPost shape stays unchanged.\nCREATE OR REPLACE VIEW public.v_public_blog_posts AS\nSELECT\n p.id,\n p.tenant_id,\n p.slug,\n p.title,\n p.excerpt,\n p.body,\n COALESCE(c.name, '') AS tag,\n p.cover_image,\n p.author_name,\n p.author_role,\n p.author_avatar_url,\n p.author_bio,\n p.read_time,\n p.published_at\nFROM public.plg_blog_posts p\nLEFT JOIN public.plg_blog_categories c ON c.id = p.category_id\nWHERE p.status = 'published';\n\nGRANT SELECT ON public.v_public_blog_posts TO anon, authenticated, service_role;\n`\n\nexport const MIGRATIONS: Array<{ id: string; sql: string }> = [\n { id: \"001_blog\", sql: MIGRATION_001_BLOG },\n]\n","import type { BlogDataProvider } from './types'\n\n// ---------------------------------------------------------------------------\n// The active blog provider, reachable outside React.\n//\n// The hooks read the provider from context, which is right for a component but\n// useless to a loader: a loader is a plain function the page resolver calls\n// before (and without) rendering, so it cannot be inside a provider. Registering\n// the provider here is what lets `blog.posts` and `blog.post` be resolved by a\n// server ahead of render — the same shape `@fayz-ai/shop` uses for its own port.\n//\n// One provider per process, set by `createBlogPlugin`. That matches how a blog\n// is mounted (once, at the app root) and how a storefront is served (one store\n// per process). A multi-tenant renderer would need this request-scoped, which is\n// the same open question the shop provider has.\n// ---------------------------------------------------------------------------\n\nlet _provider: BlogDataProvider | null = null\n\nexport function setBlogProvider(provider: BlogDataProvider): void {\n _provider = provider\n}\n\n/**\n * The registered provider.\n *\n * Throws rather than returning an empty result: a loader that silently answered\n * \"no posts\" when the plugin was never mounted would render an empty blog and\n * give no reason, and the caller cannot tell that apart from a blog with no\n * posts yet.\n */\nexport function getBlogProvider(): BlogDataProvider {\n if (!_provider) {\n throw new Error(\n '[plugin-blog] no blog provider registered — call createBlogPlugin() before resolving blog loaders, and mount its Provider at the app root.',\n )\n }\n return _provider\n}\n\nexport function hasBlogProvider(): boolean {\n return _provider !== null\n}\n\n/** Test seam. */\nexport function resetBlogProvider(): void {\n _provider = null\n}\n","import { defineLoader, hasLoader } from '@fayz-ai/core'\nimport { getBlogProvider } from './runtime'\nimport type { BlogPost } from '../types'\n\n// ---------------------------------------------------------------------------\n// The blog's reads, as named loaders.\n//\n// A hook that fetches in an effect can only ever produce a loading state on a\n// server, so a blog rendered headlessly says \"Carregando…\" and a crawler indexes\n// that. Declaring the reads instead puts them in the same registry the shop's\n// loaders live in, which is what the page resolver walks before render and what\n// `renderStore` resolves between passes — so a post's body is in the HTML, and\n// two rails asking for the same list cost one provider call.\n//\n// Registered into core's registry, not the storefront's: a plugin depends on\n// core only, and that is the registry both the storefront resolver and the\n// headless renderer read.\n// ---------------------------------------------------------------------------\n\nexport const BLOG_POSTS_LOADER = 'blog.posts'\nexport const BLOG_POST_LOADER = 'blog.post'\n\nexport interface BlogPostsParams extends Record<string, unknown> {\n /** Cap the list. Omitted means every published post. */\n limit?: number\n /** Only posts carrying this tag. */\n tag?: string\n}\n\nexport interface BlogPostParams extends Record<string, unknown> {\n slug?: string\n}\n\nasync function blogPosts(params: BlogPostsParams): Promise<BlogPost[]> {\n const { limit, tag } = params\n return getBlogProvider().listPosts({ limit, tag })\n}\n\nasync function blogPost(params: BlogPostParams): Promise<BlogPost | null> {\n const slug = typeof params.slug === 'string' ? params.slug : undefined\n // A missing slug is \"no post\", not an error: a route param can legitimately\n // be absent for one render, and throwing would take the page down for it.\n if (!slug) return null\n return getBlogProvider().getPost(slug)\n}\n\n/**\n * Register the blog reads. Idempotent, so mounting a second blog (or a hot\n * reload) does not throw on a duplicate id.\n */\nexport function registerBlogLoaders(): void {\n if (!hasLoader(BLOG_POSTS_LOADER)) {\n defineLoader<BlogPostsParams, BlogPost[]>(BLOG_POSTS_LOADER, blogPosts, {\n source: 'plugin',\n title: 'Blog posts',\n description:\n 'Published posts, newest first. The read behind a blog index, a \"latest articles\" rail, or a tag archive.',\n output:\n 'Array of BlogPost (slug, title, excerpt, body, image, tag, author, publishedAt …).',\n settings: [\n {\n id: 'limit',\n type: 'number',\n label: 'How many',\n help: 'Empty means every published post.',\n min: 1,\n max: 100,\n },\n {\n id: 'tag',\n type: 'text',\n label: 'Tag',\n help: 'Only posts carrying this tag. Empty means all posts.',\n },\n ],\n })\n }\n\n if (!hasLoader(BLOG_POST_LOADER)) {\n defineLoader<BlogPostParams, BlogPost | null>(BLOG_POST_LOADER, blogPost, {\n source: 'plugin',\n title: 'Blog post',\n description: 'One post by slug. The read behind a post page.',\n output: 'A BlogPost, or null when no post has that slug.',\n settings: [\n {\n id: 'slug',\n type: 'text',\n label: 'Slug',\n help: 'Usually bound to the route param, e.g. `{ $param: \"slug\" }`.',\n },\n ],\n })\n }\n}\n","import { createElement, type FC, type ReactNode } from 'react'\nimport type { PluginManifest } from '@fayz-ai/core'\nimport { createSafeDataProvider } from '@fayz-ai/core'\nimport { resolveConfig, type BlogPluginOptions } from './config'\nimport { createMockBlogProvider } from './data/mock'\nimport { createSupabaseBlogProvider } from './data/supabase'\nimport { BlogProvider, type BlogContextValue } from './context'\nimport { BlogList } from './components/BlogList'\nimport { PostDetail } from './components/PostDetail'\nimport { MIGRATIONS } from './migrations'\nimport { setBlogProvider } from './data/runtime'\nimport { registerBlogLoaders } from './data/loaders'\n\n// ---------------------------------------------------------------------------\n// @fayz-ai/plugin-blog — website-surface blog/articles plugin.\n//\n// First real consumer of scaffolds:['website']. Ships public routes (/blog,\n// /blog/:slug) + data hooks (useBlogPosts/usePost). A bespoke host site feeds\n// its own card markup from the hooks and mounts the routes inside its chrome.\n// See createBlogPlugin's return: a { manifest, Provider } bundle the host reads\n// uniformly (manifest.routes for mounting, Provider for the app-root context).\n// ---------------------------------------------------------------------------\n\nexport interface BlogWebsitePlugin {\n /** Standard plugin manifest (routes + metadata). */\n manifest: PluginManifest\n /** App-root context provider, pre-bound to this plugin's resolved provider/config. */\n Provider: FC<{ children: ReactNode }>\n /** The resolved data provider (exposed for advanced host wiring/tests). */\n dataProvider: BlogContextValue['provider']\n}\n\nexport function createBlogPlugin(options?: BlogPluginOptions): BlogWebsitePlugin {\n const config = resolveConfig(options)\n const provider =\n options?.dataProvider ??\n createSafeDataProvider(\n () => createSupabaseBlogProvider({ tenantId: options?.tenantId }),\n () => createMockBlogProvider({ seed: options?.seed, defaultAuthor: options?.defaultAuthor }),\n )\n\n // Reachable outside React, and the reads declared, so a page resolver or a\n // headless render can answer them before anything renders. Without this a\n // server-rendered blog can only emit its loading state.\n setBlogProvider(provider)\n registerBlogLoaders()\n\n const value: BlogContextValue = { provider, config }\n const Provider: FC<{ children: ReactNode }> = ({ children }) =>\n createElement(BlogProvider, { value, children })\n Provider.displayName = 'BlogPluginProvider'\n\n const manifest: PluginManifest = {\n id: 'blog',\n name: 'Blog',\n icon: 'Newspaper',\n version: '0.1.0',\n scope: options?.scope ?? 'universal',\n verticalId: options?.verticalId,\n scaffolds: ['website', 'landing_page'],\n defaultEnabled: true,\n dependencies: [],\n declaredFeatures: [{ id: 'blog', label: 'Blog', group: 'Attract' }],\n // Blog posts count against the plan cap. The create UI lives in the\n // marketing \"Blog\" backoffice, which writes to this plugin's posts table.\n declaredLimits: [{ key: 'blog_posts', label: 'Blog posts', table: 'plg_blog_posts' }],\n // Host owns website nav; no admin nav in M1.\n navigation: [],\n routes: [\n { path: config.basePath, component: BlogList, guard: 'public' },\n { path: `${config.basePath}/:slug`, component: PostDetail, guard: 'public' },\n ],\n widgets: [],\n // Owns the blog domain tables (categories + posts + anon-read view). The\n // marketing \"Blog\" backoffice writes to these; the website reads them.\n migrations: MIGRATIONS.map((m) => ({\n id: `blog-${m.id}`,\n version: '1.0.0',\n sql: m.sql,\n description: 'Blog content model (plg_blog_categories, plg_blog_posts) + anon-read view',\n })),\n }\n\n return { manifest, Provider, dataProvider: provider }\n}\n\n// Public API\nexport type { BlogPluginOptions, ResolvedBlogConfig } from './config'\nexport type { BlogPost, BlogListQuery, BlogAuthor, BlogCategory } from './types'\nexport type { BlogDataProvider } from './data/types'\nexport { createMockBlogProvider, createSupabaseBlogProvider } from './data'\nexport type { BlogSeed, BlogSeedPost } from './data/mock'\nexport { BlogProvider, useBlogContext } from './context'\nexport type { BlogContextValue } from './context'\nexport { useBlogPosts, usePost } from './hooks'\n\n// SSR seam: the reads as named loaders, resolvable before render, plus the\n// provider registry they read from.\nexport {\n registerBlogLoaders,\n BLOG_POSTS_LOADER,\n BLOG_POST_LOADER,\n} from './data/loaders'\nexport type { BlogPostsParams, BlogPostParams } from './data/loaders'\nexport { setBlogProvider, getBlogProvider, hasBlogProvider, resetBlogProvider } from './data/runtime'\n\n// SEO descriptors + schema.org builders.\nexport { postSeo, blogListSeo, articleJsonLd, blogJsonLd, postUrl, blogUrl } from './lib/seo'\nexport type { BlogSeoDescriptor, BlogSeoOptions } from './lib/seo'\nexport type { UseBlogPostsResult, UsePostResult } from './hooks'\nexport { BlogList } from './components/BlogList'\nexport { PostDetail } from './components/PostDetail'\nexport { PostCard } from './components/PostCard'\nexport { AuthorAvatar } from './components/AuthorAvatar'\n"]}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { BlogPost } from '../types';
|
|
2
|
+
/**
|
|
3
|
+
* Absolute URL for a path within the blog.
|
|
4
|
+
*
|
|
5
|
+
* `origin` is explicit because a server has no `window.location` to infer one
|
|
6
|
+
* from, and a relative canonical is ignored by search engines while a relative
|
|
7
|
+
* og:image renders as a broken share — so an unknown origin yields undefined
|
|
8
|
+
* rather than a half-URL that looks fine in the markup and does nothing.
|
|
9
|
+
*/
|
|
10
|
+
export declare function blogUrl(basePath: string, path: string, origin?: string): string | undefined;
|
|
11
|
+
/** Absolute canonical URL for a post. */
|
|
12
|
+
export declare function postUrl(basePath: string, slug: string, origin?: string): string | undefined;
|
|
13
|
+
/** schema.org BlogPosting + BreadcrumbList for an article page. */
|
|
14
|
+
export declare function articleJsonLd(post: BlogPost, opts: {
|
|
15
|
+
basePath: string;
|
|
16
|
+
siteName: string;
|
|
17
|
+
origin?: string;
|
|
18
|
+
}): Array<Record<string, unknown>>;
|
|
19
|
+
/** schema.org Blog + post list for the listing page. */
|
|
20
|
+
export declare function blogJsonLd(posts: BlogPost[], opts: {
|
|
21
|
+
basePath: string;
|
|
22
|
+
siteName: string;
|
|
23
|
+
title: string;
|
|
24
|
+
origin?: string;
|
|
25
|
+
}): Array<Record<string, unknown>>;
|
|
26
|
+
export { useSeo } from '@fayz-ai/core';
|
|
27
|
+
export interface BlogSeoDescriptor {
|
|
28
|
+
title: string;
|
|
29
|
+
description?: string;
|
|
30
|
+
canonical?: string;
|
|
31
|
+
image?: string;
|
|
32
|
+
type?: string;
|
|
33
|
+
jsonLd?: Array<Record<string, unknown>>;
|
|
34
|
+
}
|
|
35
|
+
export interface BlogSeoOptions {
|
|
36
|
+
basePath: string;
|
|
37
|
+
siteName: string;
|
|
38
|
+
/** Absolute origin. Without it canonical, og:image and breadcrumbs are omitted. */
|
|
39
|
+
origin?: string;
|
|
40
|
+
}
|
|
41
|
+
/** The head a post page should carry. */
|
|
42
|
+
export declare function postSeo(post: BlogPost, opts: BlogSeoOptions): BlogSeoDescriptor;
|
|
43
|
+
/** The head a blog index should carry. */
|
|
44
|
+
export declare function blogListSeo(posts: BlogPost[], opts: BlogSeoOptions & {
|
|
45
|
+
title: string;
|
|
46
|
+
description?: string;
|
|
47
|
+
}): BlogSeoDescriptor;
|
|
48
|
+
//# sourceMappingURL=seo.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"seo.d.ts","sourceRoot":"","sources":["../../src/lib/seo.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAA;AAQxC;;;;;;;GAOG;AACH,wBAAgB,OAAO,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAM3F;AAED,yCAAyC;AACzC,wBAAgB,OAAO,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAE3F;AAED,mEAAmE;AACnE,wBAAgB,aAAa,CAC3B,IAAI,EAAE,QAAQ,EACd,IAAI,EAAE;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,GAC5D,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAwBhC;AAED,wDAAwD;AACxD,wBAAgB,UAAU,CACxB,KAAK,EAAE,QAAQ,EAAE,EACjB,IAAI,EAAE;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,GAC3E,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAkBhC;AAGD,OAAO,EAAE,MAAM,EAAE,MAAM,eAAe,CAAA;AAOtC,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE,MAAM,CAAA;IACb,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,MAAM,CAAC,EAAE,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAA;CACxC;AAED,MAAM,WAAW,cAAc;IAC7B,QAAQ,EAAE,MAAM,CAAA;IAChB,QAAQ,EAAE,MAAM,CAAA;IAChB,mFAAmF;IACnF,MAAM,CAAC,EAAE,MAAM,CAAA;CAChB;AAED,yCAAyC;AACzC,wBAAgB,OAAO,CAAC,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,cAAc,GAAG,iBAAiB,CAW/E;AAED,0CAA0C;AAC1C,wBAAgB,WAAW,CACzB,KAAK,EAAE,QAAQ,EAAE,EACjB,IAAI,EAAE,cAAc,GAAG;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,CAAA;CAAE,GAC7D,iBAAiB,CASnB"}
|