@webnumseoagent/next 0.2.2 → 0.2.3
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/blog.tsx +41 -14
- package/dist/blog.d.ts +10 -0
- package/dist/blog.js +34 -23
- package/package.json +1 -1
package/blog.tsx
CHANGED
|
@@ -14,6 +14,8 @@
|
|
|
14
14
|
// app/blog/[slug]/page.tsx: const post = await seoBlogPost(params.slug); if(!post) notFound();
|
|
15
15
|
// return <SeoBlogArticle post={post} />
|
|
16
16
|
|
|
17
|
+
import type { CSSProperties } from "react";
|
|
18
|
+
|
|
17
19
|
const API = process.env.SEOAGENT_API_BASE ?? "";
|
|
18
20
|
const SITE = process.env.SEOAGENT_SITE_ID ?? "";
|
|
19
21
|
const TOKEN = process.env.SEOAGENT_TOKEN ?? "";
|
|
@@ -23,12 +25,16 @@ export interface SeoBlogMapping {
|
|
|
23
25
|
title: string; excerpt?: string; body: string;
|
|
24
26
|
id?: string; image?: string; published?: string; publishedAt?: string;
|
|
25
27
|
}
|
|
28
|
+
// Тема блога от платформы (config-API): цвета/скругления как ВАЛИДНЫЕ CSS-значения, которые пакет
|
|
29
|
+
// кладёт в свои переменные --sa-*. Сейчас платформа шлёт только accent (бренд-цвет владельца);
|
|
30
|
+
// border/surface/muted/radius зарезервированы под будущий ИИ-профиль дизайна сайта.
|
|
31
|
+
export interface SeoBlogTheme { accent?: string; border?: string; muted?: string; surface?: string; thBg?: string; radius?: string }
|
|
26
32
|
export interface SeoBlogConfig {
|
|
27
33
|
supabaseUrl: string; anonKey: string; table: string;
|
|
28
|
-
format: "html" | "minimarkup"; locales: string[]; mapping: SeoBlogMapping;
|
|
34
|
+
format: "html" | "minimarkup"; locales: string[]; mapping: SeoBlogMapping; theme?: SeoBlogTheme;
|
|
29
35
|
}
|
|
30
36
|
export interface SeoBlogSummary { slug: string; title: string; excerpt: string; cover: string | null; date: string | null }
|
|
31
|
-
export interface SeoBlogPostFull extends SeoBlogSummary { bodyHtml: string }
|
|
37
|
+
export interface SeoBlogPostFull extends SeoBlogSummary { bodyHtml: string; theme?: SeoBlogTheme }
|
|
32
38
|
|
|
33
39
|
function firstLocale(cfg: SeoBlogConfig): string {
|
|
34
40
|
return (cfg.locales ?? []).filter(Boolean)[0] ?? "";
|
|
@@ -113,6 +119,7 @@ async function fetchPost(cfg: SeoBlogConfig, slug: string, locale: string): Prom
|
|
|
113
119
|
cover: m.image ? (r[m.image] as string | null) ?? null : null,
|
|
114
120
|
date: m.publishedAt ? (r[m.publishedAt] as string | null) ?? null : null,
|
|
115
121
|
bodyHtml: cfg.format === "minimarkup" ? miniMarkupToHtml(rawBody) : sanitizeDeliveryHtml(rawBody),
|
|
122
|
+
theme: cfg.theme, // тему тащим с постом — чтобы SeoBlogArticle применил её без отдельного запроса
|
|
116
123
|
};
|
|
117
124
|
}
|
|
118
125
|
|
|
@@ -209,17 +216,37 @@ function sanitizeDeliveryHtml(html: string): string {
|
|
|
209
216
|
}
|
|
210
217
|
|
|
211
218
|
// ── Компоненты (серверные) ──────────────────────────────────────────────────────────────────
|
|
212
|
-
|
|
219
|
+
// Цвета/рамки/скругления — через СОБСТВЕННЫЕ переменные пакета (--sa-*) с нейтральным дефолтом.
|
|
220
|
+
// НЕ ссылаемся на токены сайта напрямую: shadcn/Tailwind хранят их как сырые HSL-компоненты
|
|
221
|
+
// (--primary: 222 47% 11%), в color/border это невалидно и ломает вид. Реальные цвета сайта
|
|
222
|
+
// проставит платформа: config-API отдаст профиль, а хост-обёртка выставит --sa-* валидными цветами.
|
|
223
|
+
// Шрифт и базовый цвет текста наследуются от layout сайта (color:inherit, без font-family).
|
|
224
|
+
const BODY_CSS = `.seoagent-blog-body{color:inherit}
|
|
225
|
+
.seoagent-blog-body img{max-width:100%;height:auto;border-radius:var(--sa-radius,8px);margin:1rem 0}
|
|
213
226
|
.seoagent-blog-body table{width:100%;border-collapse:collapse;margin:1rem 0;font-size:.95em}
|
|
214
|
-
.seoagent-blog-body th,.seoagent-blog-body td{border:1px solid
|
|
215
|
-
.seoagent-blog-body th{background
|
|
227
|
+
.seoagent-blog-body th,.seoagent-blog-body td{border:1px solid var(--sa-border,#e5e7eb);padding:.5rem .75rem;text-align:left}
|
|
228
|
+
.seoagent-blog-body th{background:var(--sa-th-bg,#f8fafc)}
|
|
216
229
|
.seoagent-blog-body h2{margin:1.75rem 0 .75rem;font-size:1.5rem;line-height:1.3}
|
|
217
230
|
.seoagent-blog-body h3{margin:1.4rem 0 .6rem;font-size:1.2rem}
|
|
218
231
|
.seoagent-blog-body p{margin:0 0 1rem}
|
|
219
|
-
.seoagent-blog-body a{color
|
|
232
|
+
.seoagent-blog-body a{color:var(--sa-accent,#2563eb)}
|
|
220
233
|
.seoagent-blog-body figure{margin:1.25rem 0}
|
|
221
|
-
.seoagent-blog-body figcaption{color
|
|
222
|
-
.seoagent-blog-body blockquote{margin:1rem 0;padding:.5rem 1rem;border-left:3px solid
|
|
234
|
+
.seoagent-blog-body figcaption{color:var(--sa-muted,#94a3b8);font-size:.85em;text-align:center;margin-top:.4rem}
|
|
235
|
+
.seoagent-blog-body blockquote{margin:1rem 0;padding:.5rem 1rem;border-left:3px solid var(--sa-border,#e5e7eb);color:var(--sa-muted,#475569)}`;
|
|
236
|
+
|
|
237
|
+
// Тема → CSS-переменные пакета (--sa-*). Кладём на корень блока — они каскадируются вниз к
|
|
238
|
+
// ссылкам/карточкам/телу. Отдаём только заданные ключи (пустые не трогаем → останется fallback).
|
|
239
|
+
function themeStyle(theme?: SeoBlogTheme): Record<string, string> {
|
|
240
|
+
const t = theme ?? {};
|
|
241
|
+
const out: Record<string, string> = {};
|
|
242
|
+
if (t.accent) out["--sa-accent"] = t.accent;
|
|
243
|
+
if (t.border) out["--sa-border"] = t.border;
|
|
244
|
+
if (t.muted) out["--sa-muted"] = t.muted;
|
|
245
|
+
if (t.surface) out["--sa-surface"] = t.surface;
|
|
246
|
+
if (t.thBg) out["--sa-th-bg"] = t.thBg;
|
|
247
|
+
if (t.radius) out["--sa-radius"] = t.radius;
|
|
248
|
+
return out;
|
|
249
|
+
}
|
|
223
250
|
|
|
224
251
|
// Список статей блога. Сам тянет конфиг+статьи. Пусто/ошибка → null (ничего не ломаем).
|
|
225
252
|
export async function SeoBlogIndex(props: { locale?: string; basePath?: string; limit?: number; title?: string }) {
|
|
@@ -230,16 +257,16 @@ export async function SeoBlogIndex(props: { locale?: string; basePath?: string;
|
|
|
230
257
|
if (!posts.length) return null;
|
|
231
258
|
const basePath = (props.basePath ?? "/blog").replace(/\/+$/, "");
|
|
232
259
|
return (
|
|
233
|
-
<div style={{ maxWidth: 1080, margin: "0 auto", padding: "1.5rem" }}>
|
|
260
|
+
<div style={{ maxWidth: 1080, margin: "0 auto", padding: "1.5rem", ...themeStyle(cfg.theme) } as CSSProperties}>
|
|
234
261
|
{props.title ? <h1 style={{ fontSize: "2rem", margin: "0 0 1.5rem" }}>{props.title}</h1> : null}
|
|
235
262
|
<div style={{ display: "grid", gridTemplateColumns: "repeat(auto-fill, minmax(280px, 1fr))", gap: "1.5rem" }}>
|
|
236
263
|
{posts.map((p) => (
|
|
237
264
|
<a key={p.slug} href={`${basePath}/${p.slug}`}
|
|
238
|
-
style={{ display: "block", textDecoration: "none", color: "inherit", border: "1px solid #e5e7eb", borderRadius:
|
|
265
|
+
style={{ display: "block", textDecoration: "none", color: "inherit", border: "1px solid var(--sa-border, #e5e7eb)", borderRadius: "var(--sa-radius, 12px)", overflow: "hidden", background: "var(--sa-surface, transparent)" }}>
|
|
239
266
|
{p.cover ? <img src={p.cover} alt={p.title} style={{ width: "100%", height: 180, objectFit: "cover", display: "block" }} /> : null}
|
|
240
267
|
<div style={{ padding: "1rem" }}>
|
|
241
268
|
<h2 style={{ fontSize: "1.1rem", margin: "0 0 .5rem", lineHeight: 1.3 }}>{p.title}</h2>
|
|
242
|
-
{p.excerpt ? <p style={{ margin: 0, color: "#64748b", fontSize: ".9rem", lineHeight: 1.5 }}>{p.excerpt}</p> : null}
|
|
269
|
+
{p.excerpt ? <p style={{ margin: 0, color: "var(--sa-muted, #64748b)", fontSize: ".9rem", lineHeight: 1.5 }}>{p.excerpt}</p> : null}
|
|
243
270
|
</div>
|
|
244
271
|
</a>
|
|
245
272
|
))}
|
|
@@ -251,11 +278,11 @@ export async function SeoBlogIndex(props: { locale?: string; basePath?: string;
|
|
|
251
278
|
// Одна статья. Принимает пост (из seoBlogPost) — так страница может сделать notFound()/метаданные.
|
|
252
279
|
export function SeoBlogArticle({ post, locale }: { post: SeoBlogPostFull; locale?: string }) {
|
|
253
280
|
return (
|
|
254
|
-
<article style={{ maxWidth: 760, margin: "0 auto", padding: "1.5rem", lineHeight: 1.7 }}>
|
|
281
|
+
<article style={{ maxWidth: 760, margin: "0 auto", padding: "1.5rem", lineHeight: 1.7, ...themeStyle(post.theme) } as CSSProperties}>
|
|
255
282
|
<style dangerouslySetInnerHTML={{ __html: BODY_CSS }} />
|
|
256
|
-
{post.cover ? <img src={post.cover} alt={post.title} style={{ width: "100%", borderRadius:
|
|
283
|
+
{post.cover ? <img src={post.cover} alt={post.title} style={{ width: "100%", borderRadius: "var(--sa-radius, 12px)", marginBottom: "1.5rem" }} /> : null}
|
|
257
284
|
<h1 style={{ fontSize: "2.2rem", lineHeight: 1.2, margin: "0 0 1rem" }}>{post.title}</h1>
|
|
258
|
-
{post.date ? <p style={{ color: "#94a3b8", margin: "0 0 1.5rem" }}>{fmtDate(post.date, locale)}</p> : null}
|
|
285
|
+
{post.date ? <p style={{ color: "var(--sa-muted, #94a3b8)", margin: "0 0 1.5rem" }}>{fmtDate(post.date, locale)}</p> : null}
|
|
259
286
|
{/* Тело уже санитайзено при доставке (Фаза 3.1b) — рендерим как HTML. */}
|
|
260
287
|
<div className="seoagent-blog-body" dangerouslySetInnerHTML={{ __html: post.bodyHtml }} />
|
|
261
288
|
</article>
|
package/dist/blog.d.ts
CHANGED
|
@@ -7,6 +7,14 @@ export interface SeoBlogMapping {
|
|
|
7
7
|
published?: string;
|
|
8
8
|
publishedAt?: string;
|
|
9
9
|
}
|
|
10
|
+
export interface SeoBlogTheme {
|
|
11
|
+
accent?: string;
|
|
12
|
+
border?: string;
|
|
13
|
+
muted?: string;
|
|
14
|
+
surface?: string;
|
|
15
|
+
thBg?: string;
|
|
16
|
+
radius?: string;
|
|
17
|
+
}
|
|
10
18
|
export interface SeoBlogConfig {
|
|
11
19
|
supabaseUrl: string;
|
|
12
20
|
anonKey: string;
|
|
@@ -14,6 +22,7 @@ export interface SeoBlogConfig {
|
|
|
14
22
|
format: "html" | "minimarkup";
|
|
15
23
|
locales: string[];
|
|
16
24
|
mapping: SeoBlogMapping;
|
|
25
|
+
theme?: SeoBlogTheme;
|
|
17
26
|
}
|
|
18
27
|
export interface SeoBlogSummary {
|
|
19
28
|
slug: string;
|
|
@@ -24,6 +33,7 @@ export interface SeoBlogSummary {
|
|
|
24
33
|
}
|
|
25
34
|
export interface SeoBlogPostFull extends SeoBlogSummary {
|
|
26
35
|
bodyHtml: string;
|
|
36
|
+
theme?: SeoBlogTheme;
|
|
27
37
|
}
|
|
28
38
|
export declare function seoBlogList(opts?: {
|
|
29
39
|
locale?: string;
|
package/dist/blog.js
CHANGED
|
@@ -5,21 +5,6 @@ exports.seoBlogPost = seoBlogPost;
|
|
|
5
5
|
exports.SeoBlogIndex = SeoBlogIndex;
|
|
6
6
|
exports.SeoBlogArticle = SeoBlogArticle;
|
|
7
7
|
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
8
|
-
// @webnumseoagent/next/blog — рендер блога код-сайта (Фаза 3.1c).
|
|
9
|
-
// Пакет тянет ПУБЛИЧНЫЙ конфиг блога с платформы (config-API /blog: supabaseUrl + anonKey +
|
|
10
|
-
// таблица/формат/локали/маппинг), затем читает ОПУБЛИКОВАННЫЕ статьи прямо из Supabase клиента
|
|
11
|
-
// (anon-ключ + public-read RLS) и рендерит обложку + заголовок + тело (картинки/таблицы/ссылки).
|
|
12
|
-
//
|
|
13
|
-
// Тело статьи уже САНИТАЙЗЕНО на стороне платформы при доставке (Фаза 3.1b), поэтому здесь оно
|
|
14
|
-
// рендерится как HTML (dangerouslySetInnerHTML) — граница доверия проходит по доставке, не по рендеру.
|
|
15
|
-
//
|
|
16
|
-
// Все функции FAIL-SAFE: при ошибке/таймауте возвращают пусто — блог просто не покажет контент,
|
|
17
|
-
// сборка/рендер сайта НИКОГДА не падает.
|
|
18
|
-
//
|
|
19
|
-
// Использование (роуты ставит `npx @webnumseoagent/next init --blog`):
|
|
20
|
-
// app/blog/page.tsx: export default () => <SeoBlogIndex title="Блог" />
|
|
21
|
-
// app/blog/[slug]/page.tsx: const post = await seoBlogPost(params.slug); if(!post) notFound();
|
|
22
|
-
// return <SeoBlogArticle post={post} />
|
|
23
8
|
const API = process.env.SEOAGENT_API_BASE ?? "";
|
|
24
9
|
const SITE = process.env.SEOAGENT_SITE_ID ?? "";
|
|
25
10
|
const TOKEN = process.env.SEOAGENT_TOKEN ?? "";
|
|
@@ -114,6 +99,7 @@ async function fetchPost(cfg, slug, locale) {
|
|
|
114
99
|
cover: m.image ? r[m.image] ?? null : null,
|
|
115
100
|
date: m.publishedAt ? r[m.publishedAt] ?? null : null,
|
|
116
101
|
bodyHtml: cfg.format === "minimarkup" ? miniMarkupToHtml(rawBody) : sanitizeDeliveryHtml(rawBody),
|
|
102
|
+
theme: cfg.theme, // тему тащим с постом — чтобы SeoBlogArticle применил её без отдельного запроса
|
|
117
103
|
};
|
|
118
104
|
}
|
|
119
105
|
// ── Публичные data-функции (для generateMetadata / generateStaticParams) ──
|
|
@@ -229,17 +215,42 @@ function sanitizeDeliveryHtml(html) {
|
|
|
229
215
|
return s.trim();
|
|
230
216
|
}
|
|
231
217
|
// ── Компоненты (серверные) ──────────────────────────────────────────────────────────────────
|
|
232
|
-
|
|
218
|
+
// Цвета/рамки/скругления — через СОБСТВЕННЫЕ переменные пакета (--sa-*) с нейтральным дефолтом.
|
|
219
|
+
// НЕ ссылаемся на токены сайта напрямую: shadcn/Tailwind хранят их как сырые HSL-компоненты
|
|
220
|
+
// (--primary: 222 47% 11%), в color/border это невалидно и ломает вид. Реальные цвета сайта
|
|
221
|
+
// проставит платформа: config-API отдаст профиль, а хост-обёртка выставит --sa-* валидными цветами.
|
|
222
|
+
// Шрифт и базовый цвет текста наследуются от layout сайта (color:inherit, без font-family).
|
|
223
|
+
const BODY_CSS = `.seoagent-blog-body{color:inherit}
|
|
224
|
+
.seoagent-blog-body img{max-width:100%;height:auto;border-radius:var(--sa-radius,8px);margin:1rem 0}
|
|
233
225
|
.seoagent-blog-body table{width:100%;border-collapse:collapse;margin:1rem 0;font-size:.95em}
|
|
234
|
-
.seoagent-blog-body th,.seoagent-blog-body td{border:1px solid
|
|
235
|
-
.seoagent-blog-body th{background
|
|
226
|
+
.seoagent-blog-body th,.seoagent-blog-body td{border:1px solid var(--sa-border,#e5e7eb);padding:.5rem .75rem;text-align:left}
|
|
227
|
+
.seoagent-blog-body th{background:var(--sa-th-bg,#f8fafc)}
|
|
236
228
|
.seoagent-blog-body h2{margin:1.75rem 0 .75rem;font-size:1.5rem;line-height:1.3}
|
|
237
229
|
.seoagent-blog-body h3{margin:1.4rem 0 .6rem;font-size:1.2rem}
|
|
238
230
|
.seoagent-blog-body p{margin:0 0 1rem}
|
|
239
|
-
.seoagent-blog-body a{color
|
|
231
|
+
.seoagent-blog-body a{color:var(--sa-accent,#2563eb)}
|
|
240
232
|
.seoagent-blog-body figure{margin:1.25rem 0}
|
|
241
|
-
.seoagent-blog-body figcaption{color
|
|
242
|
-
.seoagent-blog-body blockquote{margin:1rem 0;padding:.5rem 1rem;border-left:3px solid
|
|
233
|
+
.seoagent-blog-body figcaption{color:var(--sa-muted,#94a3b8);font-size:.85em;text-align:center;margin-top:.4rem}
|
|
234
|
+
.seoagent-blog-body blockquote{margin:1rem 0;padding:.5rem 1rem;border-left:3px solid var(--sa-border,#e5e7eb);color:var(--sa-muted,#475569)}`;
|
|
235
|
+
// Тема → CSS-переменные пакета (--sa-*). Кладём на корень блока — они каскадируются вниз к
|
|
236
|
+
// ссылкам/карточкам/телу. Отдаём только заданные ключи (пустые не трогаем → останется fallback).
|
|
237
|
+
function themeStyle(theme) {
|
|
238
|
+
const t = theme ?? {};
|
|
239
|
+
const out = {};
|
|
240
|
+
if (t.accent)
|
|
241
|
+
out["--sa-accent"] = t.accent;
|
|
242
|
+
if (t.border)
|
|
243
|
+
out["--sa-border"] = t.border;
|
|
244
|
+
if (t.muted)
|
|
245
|
+
out["--sa-muted"] = t.muted;
|
|
246
|
+
if (t.surface)
|
|
247
|
+
out["--sa-surface"] = t.surface;
|
|
248
|
+
if (t.thBg)
|
|
249
|
+
out["--sa-th-bg"] = t.thBg;
|
|
250
|
+
if (t.radius)
|
|
251
|
+
out["--sa-radius"] = t.radius;
|
|
252
|
+
return out;
|
|
253
|
+
}
|
|
243
254
|
// Список статей блога. Сам тянет конфиг+статьи. Пусто/ошибка → null (ничего не ломаем).
|
|
244
255
|
async function SeoBlogIndex(props) {
|
|
245
256
|
const cfg = await fetchBlogConfig();
|
|
@@ -250,11 +261,11 @@ async function SeoBlogIndex(props) {
|
|
|
250
261
|
if (!posts.length)
|
|
251
262
|
return null;
|
|
252
263
|
const basePath = (props.basePath ?? "/blog").replace(/\/+$/, "");
|
|
253
|
-
return ((0, jsx_runtime_1.jsxs)("div", { style: { maxWidth: 1080, margin: "0 auto", padding: "1.5rem" }, children: [props.title ? (0, jsx_runtime_1.jsx)("h1", { style: { fontSize: "2rem", margin: "0 0 1.5rem" }, children: props.title }) : null, (0, jsx_runtime_1.jsx)("div", { style: { display: "grid", gridTemplateColumns: "repeat(auto-fill, minmax(280px, 1fr))", gap: "1.5rem" }, children: posts.map((p) => ((0, jsx_runtime_1.jsxs)("a", { href: `${basePath}/${p.slug}`, style: { display: "block", textDecoration: "none", color: "inherit", border: "1px solid #e5e7eb", borderRadius:
|
|
264
|
+
return ((0, jsx_runtime_1.jsxs)("div", { style: { maxWidth: 1080, margin: "0 auto", padding: "1.5rem", ...themeStyle(cfg.theme) }, children: [props.title ? (0, jsx_runtime_1.jsx)("h1", { style: { fontSize: "2rem", margin: "0 0 1.5rem" }, children: props.title }) : null, (0, jsx_runtime_1.jsx)("div", { style: { display: "grid", gridTemplateColumns: "repeat(auto-fill, minmax(280px, 1fr))", gap: "1.5rem" }, children: posts.map((p) => ((0, jsx_runtime_1.jsxs)("a", { href: `${basePath}/${p.slug}`, style: { display: "block", textDecoration: "none", color: "inherit", border: "1px solid var(--sa-border, #e5e7eb)", borderRadius: "var(--sa-radius, 12px)", overflow: "hidden", background: "var(--sa-surface, transparent)" }, children: [p.cover ? (0, jsx_runtime_1.jsx)("img", { src: p.cover, alt: p.title, style: { width: "100%", height: 180, objectFit: "cover", display: "block" } }) : null, (0, jsx_runtime_1.jsxs)("div", { style: { padding: "1rem" }, children: [(0, jsx_runtime_1.jsx)("h2", { style: { fontSize: "1.1rem", margin: "0 0 .5rem", lineHeight: 1.3 }, children: p.title }), p.excerpt ? (0, jsx_runtime_1.jsx)("p", { style: { margin: 0, color: "var(--sa-muted, #64748b)", fontSize: ".9rem", lineHeight: 1.5 }, children: p.excerpt }) : null] })] }, p.slug))) })] }));
|
|
254
265
|
}
|
|
255
266
|
// Одна статья. Принимает пост (из seoBlogPost) — так страница может сделать notFound()/метаданные.
|
|
256
267
|
function SeoBlogArticle({ post, locale }) {
|
|
257
|
-
return ((0, jsx_runtime_1.jsxs)("article", { style: { maxWidth: 760, margin: "0 auto", padding: "1.5rem", lineHeight: 1.7 }, children: [(0, jsx_runtime_1.jsx)("style", { dangerouslySetInnerHTML: { __html: BODY_CSS } }), post.cover ? (0, jsx_runtime_1.jsx)("img", { src: post.cover, alt: post.title, style: { width: "100%", borderRadius:
|
|
268
|
+
return ((0, jsx_runtime_1.jsxs)("article", { style: { maxWidth: 760, margin: "0 auto", padding: "1.5rem", lineHeight: 1.7, ...themeStyle(post.theme) }, children: [(0, jsx_runtime_1.jsx)("style", { dangerouslySetInnerHTML: { __html: BODY_CSS } }), post.cover ? (0, jsx_runtime_1.jsx)("img", { src: post.cover, alt: post.title, style: { width: "100%", borderRadius: "var(--sa-radius, 12px)", marginBottom: "1.5rem" } }) : null, (0, jsx_runtime_1.jsx)("h1", { style: { fontSize: "2.2rem", lineHeight: 1.2, margin: "0 0 1rem" }, children: post.title }), post.date ? (0, jsx_runtime_1.jsx)("p", { style: { color: "var(--sa-muted, #94a3b8)", margin: "0 0 1.5rem" }, children: fmtDate(post.date, locale) }) : null, (0, jsx_runtime_1.jsx)("div", { className: "seoagent-blog-body", dangerouslySetInnerHTML: { __html: post.bodyHtml } })] }));
|
|
258
269
|
}
|
|
259
270
|
function fmtDate(d, locale) {
|
|
260
271
|
try {
|
package/package.json
CHANGED