@risali/react 0.2.0 → 0.5.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.
@@ -0,0 +1,74 @@
1
+ import type { RisaliBlogPost } from "./blog.js";
2
+ export type BuildBlogPostJsonLdOptions = {
3
+ /** Absolute origin of the client web (https://klient.sk). Required for
4
+ * `mainEntityOfPage` and absolute image URLs. */
5
+ origin: string;
6
+ /** Site name — shown in the `publisher.name` slot. */
7
+ siteName: string;
8
+ /** Optional publisher logo URL (used in `publisher.logo.url`). */
9
+ publisherLogoUrl?: string;
10
+ };
11
+ /**
12
+ * Produce a `BlogPosting` JSON-LD object compliant with schema.org.
13
+ * Drop fields when source data is missing — Google's structured data
14
+ * validator silently ignores empty strings but warns on null/undefined.
15
+ *
16
+ * The HTML embed pattern:
17
+ *
18
+ * const ld = buildBlogPostJsonLd(post, { origin, siteName });
19
+ * <script
20
+ * type="application/ld+json"
21
+ * dangerouslySetInnerHTML={{ __html: JSON.stringify(ld) }}
22
+ * />
23
+ */
24
+ export declare function buildBlogPostJsonLd(post: RisaliBlogPost, opts: BuildBlogPostJsonLdOptions): Record<string, unknown>;
25
+ /**
26
+ * Build a Next.js `Metadata` partial for a blog post (PR 36d helper).
27
+ * The consumer uses it in `generateMetadata`:
28
+ *
29
+ * export async function generateMetadata({ params }) {
30
+ * const { slug } = await params;
31
+ * const post = await getRisaliBlogPost(slug);
32
+ * return buildBlogPostMetadata(post, { siteName, origin });
33
+ * }
34
+ *
35
+ * Returns the empty title fallback so an unknown post renders a
36
+ * generic "Article" tab title rather than `undefined - …`.
37
+ */
38
+ export declare function buildBlogPostMetadata(post: RisaliBlogPost | null, opts: {
39
+ siteName: string;
40
+ origin: string;
41
+ defaultTitle?: string;
42
+ }): {
43
+ title: string;
44
+ description?: undefined;
45
+ alternates?: undefined;
46
+ openGraph?: undefined;
47
+ twitter?: undefined;
48
+ } | {
49
+ title: string;
50
+ description: string | undefined;
51
+ alternates: {
52
+ canonical: string;
53
+ };
54
+ openGraph: {
55
+ title: string;
56
+ description: string | undefined;
57
+ url: string;
58
+ type: string;
59
+ siteName: string;
60
+ publishedTime: string;
61
+ modifiedTime: string;
62
+ authors: string[] | undefined;
63
+ images: {
64
+ url: string;
65
+ }[] | undefined;
66
+ };
67
+ twitter: {
68
+ card: string;
69
+ title: string;
70
+ description: string | undefined;
71
+ images: string[] | undefined;
72
+ };
73
+ };
74
+ //# sourceMappingURL=blog-jsonld.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"blog-jsonld.d.ts","sourceRoot":"","sources":["../src/blog-jsonld.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAEhD,MAAM,MAAM,0BAA0B,GAAG;IACvC;qDACiD;IACjD,MAAM,EAAE,MAAM,CAAC;IACf,sDAAsD;IACtD,QAAQ,EAAE,MAAM,CAAC;IACjB,kEAAkE;IAClE,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B,CAAC;AAEF;;;;;;;;;;;;GAYG;AACH,wBAAgB,mBAAmB,CACjC,IAAI,EAAE,cAAc,EACpB,IAAI,EAAE,0BAA0B,GAC/B,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CA8CzB;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,qBAAqB,CACnC,IAAI,EAAE,cAAc,GAAG,IAAI,EAC3B,IAAI,EAAE;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,YAAY,CAAC,EAAE,MAAM,CAAA;CAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAmClE"}
@@ -0,0 +1,111 @@
1
+ // JSON-LD `BlogPosting` schema builder for the blog post page (PR 36d).
2
+ // Pure: takes a post payload (typed) + site context, returns a JS
3
+ // object the consumer can JSON.stringify into a `<script
4
+ // type="application/ld+json">` tag.
5
+ //
6
+ // Standalone helper so the consumer can wire it into their
7
+ // `generateMetadata` flow or inline-emit it next to <RisaliBlogPost />.
8
+ /**
9
+ * Produce a `BlogPosting` JSON-LD object compliant with schema.org.
10
+ * Drop fields when source data is missing — Google's structured data
11
+ * validator silently ignores empty strings but warns on null/undefined.
12
+ *
13
+ * The HTML embed pattern:
14
+ *
15
+ * const ld = buildBlogPostJsonLd(post, { origin, siteName });
16
+ * <script
17
+ * type="application/ld+json"
18
+ * dangerouslySetInnerHTML={{ __html: JSON.stringify(ld) }}
19
+ * />
20
+ */
21
+ export function buildBlogPostJsonLd(post, opts) {
22
+ const baseOrigin = opts.origin.replace(/\/+$/, "");
23
+ const url = `${baseOrigin}/blog/${post.slug}`;
24
+ const headline = (post.seo_meta.title || post.title || "").slice(0, 110);
25
+ const description = post.seo_meta.description || post.excerpt || undefined;
26
+ const imageUrl = post.seo_meta.og_image_url || post.hero_image_url || undefined;
27
+ const author = post.author_display
28
+ ? { "@type": "Person", name: post.author_display }
29
+ : { "@type": "Organization", name: opts.siteName };
30
+ const publisher = {
31
+ "@type": "Organization",
32
+ name: opts.siteName,
33
+ };
34
+ if (opts.publisherLogoUrl) {
35
+ publisher.logo = {
36
+ "@type": "ImageObject",
37
+ url: opts.publisherLogoUrl,
38
+ };
39
+ }
40
+ const ld = {
41
+ "@context": "https://schema.org",
42
+ "@type": "BlogPosting",
43
+ headline,
44
+ mainEntityOfPage: {
45
+ "@type": "WebPage",
46
+ "@id": url,
47
+ },
48
+ url,
49
+ author,
50
+ publisher,
51
+ datePublished: post.published_at,
52
+ dateModified: post.updated_at || post.published_at,
53
+ };
54
+ if (description)
55
+ ld.description = description;
56
+ if (imageUrl)
57
+ ld.image = imageUrl;
58
+ if (post.category)
59
+ ld.articleSection = post.category;
60
+ if (post.reading_minutes) {
61
+ ld.timeRequired = `PT${post.reading_minutes}M`;
62
+ }
63
+ return ld;
64
+ }
65
+ /**
66
+ * Build a Next.js `Metadata` partial for a blog post (PR 36d helper).
67
+ * The consumer uses it in `generateMetadata`:
68
+ *
69
+ * export async function generateMetadata({ params }) {
70
+ * const { slug } = await params;
71
+ * const post = await getRisaliBlogPost(slug);
72
+ * return buildBlogPostMetadata(post, { siteName, origin });
73
+ * }
74
+ *
75
+ * Returns the empty title fallback so an unknown post renders a
76
+ * generic "Article" tab title rather than `undefined - …`.
77
+ */
78
+ export function buildBlogPostMetadata(post, opts) {
79
+ if (!post) {
80
+ return {
81
+ title: opts.defaultTitle ?? "Článok",
82
+ };
83
+ }
84
+ const title = post.seo_meta.title || post.title;
85
+ const description = post.seo_meta.description || post.excerpt || undefined;
86
+ const ogImage = post.seo_meta.og_image_url || post.hero_image_url || undefined;
87
+ const url = `${opts.origin.replace(/\/+$/, "")}/blog/${post.slug}`;
88
+ return {
89
+ title: `${title} — ${opts.siteName}`,
90
+ description,
91
+ alternates: { canonical: url },
92
+ openGraph: {
93
+ title,
94
+ description,
95
+ url,
96
+ type: "article",
97
+ siteName: opts.siteName,
98
+ publishedTime: post.published_at,
99
+ modifiedTime: post.updated_at,
100
+ authors: post.author_display ? [post.author_display] : undefined,
101
+ images: ogImage ? [{ url: ogImage }] : undefined,
102
+ },
103
+ twitter: {
104
+ card: ogImage ? "summary_large_image" : "summary",
105
+ title,
106
+ description,
107
+ images: ogImage ? [ogImage] : undefined,
108
+ },
109
+ };
110
+ }
111
+ //# sourceMappingURL=blog-jsonld.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"blog-jsonld.js","sourceRoot":"","sources":["../src/blog-jsonld.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,29 @@
1
+ import type { CSSProperties } from "react";
2
+ import { type GetBlogPostsOptions } from "./blog.js";
3
+ export type RisaliBlogListProps = GetBlogPostsOptions & {
4
+ /** Hide the empty-state placeholder when the list is empty. */
5
+ hideEmptyState?: boolean;
6
+ className?: string;
7
+ style?: CSSProperties;
8
+ };
9
+ /**
10
+ * Server component that renders a responsive grid of blog cards
11
+ * (hero image + title + excerpt + date + reading minutes + category
12
+ * chip). Use it on the client web's `/blog` route:
13
+ *
14
+ * // app/blog/page.tsx
15
+ * import { RisaliBlogList } from "@risali/react";
16
+ * export default function BlogIndex() {
17
+ * return <RisaliBlogList limit={12} />;
18
+ * }
19
+ *
20
+ * Renders an empty-state placeholder when the site has no published
21
+ * posts yet (suppress with `hideEmptyState`).
22
+ *
23
+ * Styling: unopinionated. Tailwind-friendly class names on the wrapper;
24
+ * pass a `className` to override. The card layout uses inline CSS so it
25
+ * works on hosts without a CSS framework, but tenants are free to
26
+ * style via `.risali-blog-*` class names.
27
+ */
28
+ export declare function RisaliBlogList(props: RisaliBlogListProps): Promise<import("react/jsx-runtime").JSX.Element | null>;
29
+ //# sourceMappingURL=blog-list.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"blog-list.d.ts","sourceRoot":"","sources":["../src/blog-list.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,OAAO,CAAC;AAC3C,OAAO,EAEL,KAAK,mBAAmB,EAEzB,MAAM,WAAW,CAAC;AAEnB,MAAM,MAAM,mBAAmB,GAAG,mBAAmB,GAAG;IACtD,+DAA+D;IAC/D,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,aAAa,CAAC;CACvB,CAAC;AAgBF;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAsB,cAAc,CAAC,KAAK,EAAE,mBAAmB,2DAoC9D"}
@@ -0,0 +1,113 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { getRisaliBlogPosts, } from "./blog.js";
3
+ // Format ISO date for the SK locale, falling back to the raw value on
4
+ // any parse failure (never throws — runtime errors in a server
5
+ // component crash the whole route).
6
+ function fmtDate(iso) {
7
+ if (!iso)
8
+ return "";
9
+ const d = new Date(iso);
10
+ if (!Number.isFinite(d.getTime()))
11
+ return "";
12
+ return d.toLocaleDateString("sk-SK", {
13
+ day: "numeric",
14
+ month: "long",
15
+ year: "numeric",
16
+ });
17
+ }
18
+ /**
19
+ * Server component that renders a responsive grid of blog cards
20
+ * (hero image + title + excerpt + date + reading minutes + category
21
+ * chip). Use it on the client web's `/blog` route:
22
+ *
23
+ * // app/blog/page.tsx
24
+ * import { RisaliBlogList } from "@risali/react";
25
+ * export default function BlogIndex() {
26
+ * return <RisaliBlogList limit={12} />;
27
+ * }
28
+ *
29
+ * Renders an empty-state placeholder when the site has no published
30
+ * posts yet (suppress with `hideEmptyState`).
31
+ *
32
+ * Styling: unopinionated. Tailwind-friendly class names on the wrapper;
33
+ * pass a `className` to override. The card layout uses inline CSS so it
34
+ * works on hosts without a CSS framework, but tenants are free to
35
+ * style via `.risali-blog-*` class names.
36
+ */
37
+ export async function RisaliBlogList(props) {
38
+ const posts = await getRisaliBlogPosts({
39
+ siteSlug: props.siteSlug,
40
+ category: props.category,
41
+ limit: props.limit,
42
+ offset: props.offset,
43
+ });
44
+ if (posts.length === 0) {
45
+ if (props.hideEmptyState)
46
+ return null;
47
+ return (_jsx("div", { className: props.className ?? "risali-blog-empty", style: props.style, children: _jsx("p", { style: { color: "#6b7280" }, children: "Zatia\u013E tu nie s\u00FA \u017Eiadne \u010Dl\u00E1nky." }) }));
48
+ }
49
+ return (_jsx("div", { className: props.className ?? "risali-blog-list", style: {
50
+ display: "grid",
51
+ gap: "1.5rem",
52
+ gridTemplateColumns: "repeat(auto-fill, minmax(280px, 1fr))",
53
+ ...props.style,
54
+ }, children: posts.map((post) => (_jsx(BlogCard, { post: post }, post.slug))) }));
55
+ }
56
+ function BlogCard({ post }) {
57
+ return (_jsx("article", { className: "risali-blog-card", style: {
58
+ display: "flex",
59
+ flexDirection: "column",
60
+ overflow: "hidden",
61
+ borderRadius: "0.75rem",
62
+ background: "#fff",
63
+ boxShadow: "0 1px 2px rgba(0,0,0,0.05)",
64
+ border: "1px solid #e5e7eb",
65
+ }, children: _jsxs("a", { href: `/blog/${encodeURIComponent(post.slug)}`, className: "risali-blog-card-link", style: {
66
+ color: "inherit",
67
+ textDecoration: "none",
68
+ display: "flex",
69
+ flexDirection: "column",
70
+ height: "100%",
71
+ }, children: [post.hero_image_url ? (
72
+ // eslint-disable-next-line @next/next/no-img-element
73
+ _jsx("img", { src: post.hero_image_url, alt: "", loading: "lazy", style: {
74
+ width: "100%",
75
+ aspectRatio: "16 / 9",
76
+ objectFit: "cover",
77
+ display: "block",
78
+ } })) : (_jsx("div", { style: {
79
+ width: "100%",
80
+ aspectRatio: "16 / 9",
81
+ background: "#f3f4f6",
82
+ } })), _jsxs("div", { style: {
83
+ padding: "1.25rem",
84
+ display: "flex",
85
+ flexDirection: "column",
86
+ gap: "0.5rem",
87
+ flex: 1,
88
+ }, children: [post.category ? (_jsx("span", { className: "risali-blog-card-category", style: {
89
+ alignSelf: "flex-start",
90
+ fontSize: "0.75rem",
91
+ textTransform: "uppercase",
92
+ letterSpacing: "0.05em",
93
+ color: "#6b7280",
94
+ }, children: post.category })) : null, _jsx("h2", { className: "risali-blog-card-title", style: {
95
+ fontSize: "1.25rem",
96
+ fontWeight: 600,
97
+ lineHeight: 1.3,
98
+ margin: 0,
99
+ }, children: post.title }), post.excerpt ? (_jsx("p", { className: "risali-blog-card-excerpt", style: {
100
+ color: "#4b5563",
101
+ fontSize: "0.95rem",
102
+ lineHeight: 1.5,
103
+ margin: 0,
104
+ }, children: post.excerpt })) : null, _jsxs("div", { style: {
105
+ marginTop: "auto",
106
+ paddingTop: "0.75rem",
107
+ display: "flex",
108
+ gap: "0.75rem",
109
+ fontSize: "0.85rem",
110
+ color: "#6b7280",
111
+ }, children: [_jsx("span", { children: fmtDate(post.published_at) }), post.reading_minutes ? (_jsxs("span", { children: ["\u00B7 ", post.reading_minutes, " min \u010D\u00EDtania"] })) : null, post.author_display ? (_jsxs("span", { children: ["\u00B7 ", post.author_display] })) : null] })] })] }) }));
112
+ }
113
+ //# sourceMappingURL=blog-list.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"blog-list.js","sourceRoot":"","sources":["../src/blog-list.tsx"],"names":[],"mappings":"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"}
@@ -0,0 +1,40 @@
1
+ import type { CSSProperties } from "react";
2
+ import { type GetBlogPostOptions } from "./blog.js";
3
+ export type RisaliBlogPostProps = GetBlogPostOptions & {
4
+ /** Required: the post slug from the route segment. */
5
+ slug: string;
6
+ /** Fallback to render when the post isn't available. */
7
+ fallback?: React.ReactNode;
8
+ /**
9
+ * Origin of the client web (https://klient.sk). When supplied
10
+ * together with `siteName`, the component emits a `BlogPosting`
11
+ * JSON-LD `<script>` tag for SEO rich results.
12
+ */
13
+ origin?: string;
14
+ /** Site name — required for JSON-LD `publisher.name`. */
15
+ siteName?: string;
16
+ /** Optional publisher logo URL for JSON-LD. */
17
+ publisherLogoUrl?: string;
18
+ className?: string;
19
+ style?: CSSProperties;
20
+ };
21
+ /**
22
+ * Server component for a single blog post. Use it on the client web's
23
+ * `/blog/[slug]` route:
24
+ *
25
+ * // app/blog/[slug]/page.tsx
26
+ * import { RisaliBlogPost } from "@risali/react";
27
+ * export default async function PostPage(
28
+ * { params }: { params: Promise<{ slug: string }> },
29
+ * ) {
30
+ * const { slug } = await params;
31
+ * return <RisaliBlogPost slug={slug} />;
32
+ * }
33
+ *
34
+ * Returns the `fallback` prop (or a generic "not found" message) when
35
+ * the post isn't available. Renders the sanitised HTML via
36
+ * dangerouslySetInnerHTML — the API has already sanitised the payload
37
+ * server-side; we sanitise again here as defense-in-depth.
38
+ */
39
+ export declare function RisaliBlogPost(props: RisaliBlogPostProps): Promise<import("react/jsx-runtime").JSX.Element>;
40
+ //# sourceMappingURL=blog-post.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"blog-post.d.ts","sourceRoot":"","sources":["../src/blog-post.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,OAAO,CAAC;AAC3C,OAAO,EAEL,KAAK,kBAAkB,EACxB,MAAM,WAAW,CAAC;AAInB,MAAM,MAAM,mBAAmB,GAAG,kBAAkB,GAAG;IACrD,sDAAsD;IACtD,IAAI,EAAE,MAAM,CAAC;IACb,wDAAwD;IACxD,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAC3B;;;;OAIG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,yDAAyD;IACzD,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,+CAA+C;IAC/C,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,aAAa,CAAC;CACvB,CAAC;AAaF;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAsB,cAAc,CAAC,KAAK,EAAE,mBAAmB,oDAyI9D"}
@@ -0,0 +1,109 @@
1
+ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { getRisaliBlogPost, } from "./blog.js";
3
+ import { sanitiseRichText } from "./sanitize.js";
4
+ import { buildBlogPostJsonLd } from "./blog-jsonld.js";
5
+ function fmtDate(iso) {
6
+ if (!iso)
7
+ return "";
8
+ const d = new Date(iso);
9
+ if (!Number.isFinite(d.getTime()))
10
+ return "";
11
+ return d.toLocaleDateString("sk-SK", {
12
+ day: "numeric",
13
+ month: "long",
14
+ year: "numeric",
15
+ });
16
+ }
17
+ /**
18
+ * Server component for a single blog post. Use it on the client web's
19
+ * `/blog/[slug]` route:
20
+ *
21
+ * // app/blog/[slug]/page.tsx
22
+ * import { RisaliBlogPost } from "@risali/react";
23
+ * export default async function PostPage(
24
+ * { params }: { params: Promise<{ slug: string }> },
25
+ * ) {
26
+ * const { slug } = await params;
27
+ * return <RisaliBlogPost slug={slug} />;
28
+ * }
29
+ *
30
+ * Returns the `fallback` prop (or a generic "not found" message) when
31
+ * the post isn't available. Renders the sanitised HTML via
32
+ * dangerouslySetInnerHTML — the API has already sanitised the payload
33
+ * server-side; we sanitise again here as defense-in-depth.
34
+ */
35
+ export async function RisaliBlogPost(props) {
36
+ const post = await getRisaliBlogPost(props.slug, {
37
+ siteSlug: props.siteSlug,
38
+ previewToken: props.previewToken,
39
+ });
40
+ if (!post) {
41
+ if (props.fallback !== undefined)
42
+ return _jsx(_Fragment, { children: props.fallback });
43
+ return (_jsx("div", { className: props.className ?? "risali-blog-not-found", style: props.style, children: _jsx("p", { style: { color: "#6b7280" }, children: "Tento \u010Dl\u00E1nok sa nena\u0161iel." }) }));
44
+ }
45
+ const safeHtml = sanitiseRichText(post.content_html);
46
+ const jsonLd = props.origin && props.siteName
47
+ ? buildBlogPostJsonLd(post, {
48
+ origin: props.origin,
49
+ siteName: props.siteName,
50
+ publisherLogoUrl: props.publisherLogoUrl,
51
+ })
52
+ : null;
53
+ return (_jsxs("article", { className: props.className ?? "risali-blog-post", style: {
54
+ maxWidth: "780px",
55
+ margin: "0 auto",
56
+ ...props.style,
57
+ }, children: [jsonLd ? (_jsx("script", { type: "application/ld+json",
58
+ // JSON.stringify is XSS-safe enough for JSON-LD; the
59
+ // payload contains only typed primitives from the
60
+ // sanitised post + caller-supplied origin/name. The HTML
61
+ // injection vector for JSON-LD is `</script>` inside a
62
+ // string — replace `<` so the parser can't close the tag
63
+ // early.
64
+ dangerouslySetInnerHTML: {
65
+ __html: JSON.stringify(jsonLd).replace(/</g, "\\u003c"),
66
+ } })) : null, props.previewToken ? (_jsx("div", { className: "risali-blog-preview-banner", style: {
67
+ background: "#fef3c7",
68
+ color: "#854d0e",
69
+ padding: "0.5rem 1rem",
70
+ borderRadius: "0.5rem",
71
+ marginBottom: "1rem",
72
+ fontSize: "0.9rem",
73
+ textAlign: "center",
74
+ }, children: "N\u00E1h\u013Ead \u2014 tento odkaz je platn\u00FD 15 min\u00FAt a uvid\u00ED\u0161 ho len ty." })) : null, post.hero_image_url ? (
75
+ // eslint-disable-next-line @next/next/no-img-element
76
+ _jsx("img", { src: post.hero_image_url, alt: "", loading: "eager", fetchPriority: "high", style: {
77
+ width: "100%",
78
+ aspectRatio: "16 / 9",
79
+ objectFit: "cover",
80
+ borderRadius: "0.75rem",
81
+ marginBottom: "1.5rem",
82
+ } })) : null, post.category ? (_jsx("div", { className: "risali-blog-post-category", style: {
83
+ fontSize: "0.85rem",
84
+ textTransform: "uppercase",
85
+ letterSpacing: "0.05em",
86
+ color: "#6b7280",
87
+ marginBottom: "0.5rem",
88
+ }, children: post.category })) : null, _jsx("h1", { className: "risali-blog-post-title", style: {
89
+ fontSize: "2.25rem",
90
+ fontWeight: 700,
91
+ lineHeight: 1.2,
92
+ margin: "0 0 1rem 0",
93
+ }, children: post.title }), _jsxs("div", { className: "risali-blog-post-meta", style: {
94
+ display: "flex",
95
+ gap: "0.75rem",
96
+ fontSize: "0.9rem",
97
+ color: "#6b7280",
98
+ marginBottom: "2rem",
99
+ }, children: [post.author_display ? _jsx("span", { children: post.author_display }) : null, _jsx("span", { children: fmtDate(post.published_at) }), post.reading_minutes ? (_jsxs("span", { children: ["\u00B7 ", post.reading_minutes, " min \u010D\u00EDtania"] })) : null] }), _jsx("div", { className: "risali-blog-post-body", style: {
100
+ fontSize: "1.05rem",
101
+ lineHeight: 1.7,
102
+ color: "#111827",
103
+ },
104
+ // Server-side sanitised HTML. The render-time sanitiseRichText
105
+ // run is a defense-in-depth second pass over what the API
106
+ // already cleaned at save time (PR 36a normalizer).
107
+ dangerouslySetInnerHTML: { __html: safeHtml } })] }));
108
+ }
109
+ //# sourceMappingURL=blog-post.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"blog-post.js","sourceRoot":"","sources":["../src/blog-post.tsx"],"names":[],"mappings":"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"}
package/dist/blog.d.ts ADDED
@@ -0,0 +1,69 @@
1
+ export type RisaliBlogPostCard = {
2
+ slug: string;
3
+ title: string;
4
+ excerpt: string | null;
5
+ hero_image_url: string | null;
6
+ category: string | null;
7
+ published_at: string;
8
+ author_display: string | null;
9
+ reading_minutes: number | null;
10
+ };
11
+ export type RisaliBlogPostSeo = {
12
+ title?: string;
13
+ description?: string;
14
+ og_image_url?: string;
15
+ };
16
+ export type RisaliBlogPost = {
17
+ slug: string;
18
+ title: string;
19
+ content_html: string;
20
+ excerpt: string | null;
21
+ hero_image_url: string | null;
22
+ category: string | null;
23
+ published_at: string;
24
+ updated_at: string;
25
+ author_display: string | null;
26
+ reading_minutes: number | null;
27
+ seo_meta: RisaliBlogPostSeo;
28
+ };
29
+ export type GetBlogPostsOptions = {
30
+ /** Override the resolved site slug (default: RISALI_SITE_SLUG env). */
31
+ siteSlug?: string;
32
+ /** Filter by single free-text category. */
33
+ category?: string;
34
+ /** Page size (1..50, default 12). */
35
+ limit?: number;
36
+ /** Pagination offset (default 0). */
37
+ offset?: number;
38
+ };
39
+ export type GetBlogPostOptions = {
40
+ /** Override the resolved site slug. */
41
+ siteSlug?: string;
42
+ /**
43
+ * Optional preview token from the dashboard "Preview" button. When
44
+ * supplied, the fetch hits the preview path which bypasses
45
+ * `status='published'` and returns drafts + scheduled posts.
46
+ * Tokens expire 15 minutes after issuance.
47
+ */
48
+ previewToken?: string;
49
+ };
50
+ /**
51
+ * Fetch the published blog post list for the resolved site. Returns
52
+ * an empty array on any failure (unknown site, plan downgrade,
53
+ * network) so the consumer can fall back to an empty grid without
54
+ * dropping the whole page.
55
+ */
56
+ export declare function getRisaliBlogPosts(opts?: GetBlogPostsOptions): Promise<RisaliBlogPostCard[]>;
57
+ /**
58
+ * Fetch a single blog post by slug. Returns null on any failure so
59
+ * the consumer can render a notFound() page instead of throwing.
60
+ * When `previewToken` is supplied, the call goes through the
61
+ * preview path (no cache, drafts allowed).
62
+ */
63
+ export declare function getRisaliBlogPost(postSlug: string, opts?: GetBlogPostOptions): Promise<RisaliBlogPost | null>;
64
+ /**
65
+ * Build a public `/blog/<slug>` URL for the client web. Helper for
66
+ * hand-rolled card grids that don't use `<RisaliBlogList />`.
67
+ */
68
+ export declare function buildBlogPostHref(postSlug: string): string;
69
+ //# sourceMappingURL=blog.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"blog.d.ts","sourceRoot":"","sources":["../src/blog.ts"],"names":[],"mappings":"AAWA,MAAM,MAAM,kBAAkB,GAAG;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,YAAY,EAAE,MAAM,CAAC;IACrB,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;CAChC,CAAC;AAEF,MAAM,MAAM,iBAAiB,GAAG;IAC9B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB,CAAC;AAEF,MAAM,MAAM,cAAc,GAAG;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,YAAY,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,QAAQ,EAAE,iBAAiB,CAAC;CAC7B,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG;IAChC,uEAAuE;IACvE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,2CAA2C;IAC3C,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,qCAAqC;IACrC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,qCAAqC;IACrC,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAAG;IAC/B,uCAAuC;IACvC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;;;OAKG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB,CAAC;AAEF;;;;;GAKG;AACH,wBAAsB,kBAAkB,CACtC,IAAI,GAAE,mBAAwB,GAC7B,OAAO,CAAC,kBAAkB,EAAE,CAAC,CAqB/B;AAED;;;;;GAKG;AACH,wBAAsB,iBAAiB,CACrC,QAAQ,EAAE,MAAM,EAChB,IAAI,GAAE,kBAAuB,GAC5B,OAAO,CAAC,cAAc,GAAG,IAAI,CAAC,CA2BhC;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAE1D"}