@webnumseoagent/next 0.7.4 → 0.7.5
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 +8 -13
- package/dist/blog-grid.d.ts +11 -0
- package/dist/blog-grid.js +18 -0
- package/dist/blog.js +4 -3
- package/package.json +2 -2
package/blog.tsx
CHANGED
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
// app/[locale]/blog/[slug]/page.tsx: const post = await seoBlogPost(params.slug); if(!post) notFound(); <SeoBlogArticle post={post} />
|
|
14
14
|
|
|
15
15
|
import type { CSSProperties } from "react";
|
|
16
|
+
import { SeoBlogGrid } from "./blog-grid";
|
|
16
17
|
|
|
17
18
|
// Файловый ридер (node:fs) грузим лениво — только когда блог реально рендерится (на сборке).
|
|
18
19
|
const blogfs = () => import("./blog-fs");
|
|
@@ -95,8 +96,8 @@ const GRID_CSS = `.sa-blog-wrap{max-width:1120px;margin:0 auto;padding:var(--sa-
|
|
|
95
96
|
.sa-blog-card h2{font-size:1.1rem;line-height:1.35;margin:0}
|
|
96
97
|
.sa-blog-card p{margin:0;color:var(--sa-muted,#64748b);font-size:.9rem;line-height:1.5;display:-webkit-box;-webkit-line-clamp:3;-webkit-box-orient:vertical;overflow:hidden}
|
|
97
98
|
.sa-blog-more{display:flex;justify-content:center;margin-top:2rem}
|
|
98
|
-
.sa-blog-more a{display:inline-block;padding:.7rem 1.6rem;border:1px solid var(--sa-border,#e5e7eb);border-radius:var(--sa-radius,10px);text-decoration:none;color:inherit;font-weight:500}
|
|
99
|
-
.sa-blog-more a:hover{background:var(--sa-th-bg,#f8fafc)}
|
|
99
|
+
.sa-blog-more a,.sa-blog-more button{display:inline-block;padding:.7rem 1.6rem;border:1px solid var(--sa-border,#e5e7eb);border-radius:var(--sa-radius,10px);text-decoration:none;color:inherit;font:inherit;font-weight:500;background:transparent;cursor:pointer;transition:background .15s ease}
|
|
100
|
+
.sa-blog-more a:hover,.sa-blog-more button:hover{background:var(--sa-th-bg,#f8fafc)}
|
|
100
101
|
.sa-blog-empty{color:var(--sa-muted,#64748b)}`;
|
|
101
102
|
|
|
102
103
|
const BLOG_TITLE: Record<string, string> = { ru: "Блог", uz: "Blog", en: "Blog", ar: "المدونة", tr: "Blog" };
|
|
@@ -121,17 +122,11 @@ export async function SeoBlogIndex(props: { locale?: string; basePath?: string;
|
|
|
121
122
|
<style dangerouslySetInnerHTML={{ __html: GRID_CSS }} />
|
|
122
123
|
<h1 className="sa-blog-h1">{title}</h1>
|
|
123
124
|
{posts.length ? (
|
|
124
|
-
<
|
|
125
|
-
{posts.map((p) => (
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
<h2>{p.title}</h2>
|
|
130
|
-
{p.excerpt ? <p>{p.excerpt}</p> : null}
|
|
131
|
-
</div>
|
|
132
|
-
</a>
|
|
133
|
-
))}
|
|
134
|
-
</div>
|
|
125
|
+
<SeoBlogGrid
|
|
126
|
+
posts={posts.map((p) => ({ slug: p.slug, title: p.title, cover: p.cover, excerpt: p.excerpt }))}
|
|
127
|
+
basePath={basePath}
|
|
128
|
+
locale={locale}
|
|
129
|
+
/>
|
|
135
130
|
) : (
|
|
136
131
|
<p className="sa-blog-empty">{L(EMPTY_LABEL, locale, "No posts yet.")}</p>
|
|
137
132
|
)}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export interface SeoBlogCard {
|
|
2
|
+
slug: string;
|
|
3
|
+
title: string;
|
|
4
|
+
cover?: string | null;
|
|
5
|
+
excerpt?: string | null;
|
|
6
|
+
}
|
|
7
|
+
export declare function SeoBlogGrid({ posts, basePath, locale }: {
|
|
8
|
+
posts: SeoBlogCard[];
|
|
9
|
+
basePath: string;
|
|
10
|
+
locale: string;
|
|
11
|
+
}): import("react").JSX.Element;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SeoBlogGrid = SeoBlogGrid;
|
|
4
|
+
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
5
|
+
// Клиентская сетка блога с «Показать больше» интервалами по 16 карточек (4 ряда × 4 колонки).
|
|
6
|
+
// ≤16 статей → кнопки нет; >16 → кнопка добавляет ещё до 16 карточек, и так далее.
|
|
7
|
+
// Карточки/сетка/кнопка стилизуются классами .sa-blog-* (CSS инжектит серверный SeoBlogIndex).
|
|
8
|
+
const react_1 = require("react");
|
|
9
|
+
const BATCH = 16; // 4 ряда по 4
|
|
10
|
+
const MORE_LABEL = {
|
|
11
|
+
ru: "Показать больше", uz: "Ko‘proq ko‘rsatish", en: "Show more", ar: "عرض المزيد", tr: "Daha fazla göster",
|
|
12
|
+
};
|
|
13
|
+
const label = (locale) => MORE_LABEL[locale] || MORE_LABEL[locale?.split("-")[0]] || "Show more";
|
|
14
|
+
function SeoBlogGrid({ posts, basePath, locale }) {
|
|
15
|
+
const [shown, setShown] = (0, react_1.useState)(BATCH);
|
|
16
|
+
const visible = shown >= posts.length ? posts : posts.slice(0, shown);
|
|
17
|
+
return ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("div", { className: "sa-blog-grid", children: visible.map((p) => ((0, jsx_runtime_1.jsxs)("a", { className: "sa-blog-card", href: `${basePath}/${p.slug}`, children: [p.cover ? (0, jsx_runtime_1.jsx)("img", { src: p.cover, alt: p.title, loading: "lazy" }) : null, (0, jsx_runtime_1.jsxs)("div", { className: "sa-cb", children: [(0, jsx_runtime_1.jsx)("h2", { children: p.title }), p.excerpt ? (0, jsx_runtime_1.jsx)("p", { children: p.excerpt }) : null] })] }, p.slug))) }), posts.length > shown ? ((0, jsx_runtime_1.jsx)("div", { className: "sa-blog-more", children: (0, jsx_runtime_1.jsx)("button", { type: "button", onClick: () => setShown((s) => s + BATCH), children: label(locale) }) })) : null] }));
|
|
18
|
+
}
|
package/dist/blog.js
CHANGED
|
@@ -39,6 +39,7 @@ exports.seoBlogStaticParams = seoBlogStaticParams;
|
|
|
39
39
|
exports.SeoBlogIndex = SeoBlogIndex;
|
|
40
40
|
exports.SeoBlogArticle = SeoBlogArticle;
|
|
41
41
|
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
42
|
+
const blog_grid_1 = require("./blog-grid");
|
|
42
43
|
// Файловый ридер (node:fs) грузим лениво — только когда блог реально рендерится (на сборке).
|
|
43
44
|
const blogfs = () => Promise.resolve().then(() => __importStar(require("./blog-fs")));
|
|
44
45
|
// ── Публичные data-функции (для generateMetadata / generateStaticParams) — читают файлы content/blog. ──
|
|
@@ -117,8 +118,8 @@ const GRID_CSS = `.sa-blog-wrap{max-width:1120px;margin:0 auto;padding:var(--sa-
|
|
|
117
118
|
.sa-blog-card h2{font-size:1.1rem;line-height:1.35;margin:0}
|
|
118
119
|
.sa-blog-card p{margin:0;color:var(--sa-muted,#64748b);font-size:.9rem;line-height:1.5;display:-webkit-box;-webkit-line-clamp:3;-webkit-box-orient:vertical;overflow:hidden}
|
|
119
120
|
.sa-blog-more{display:flex;justify-content:center;margin-top:2rem}
|
|
120
|
-
.sa-blog-more a{display:inline-block;padding:.7rem 1.6rem;border:1px solid var(--sa-border,#e5e7eb);border-radius:var(--sa-radius,10px);text-decoration:none;color:inherit;font-weight:500}
|
|
121
|
-
.sa-blog-more a:hover{background:var(--sa-th-bg,#f8fafc)}
|
|
121
|
+
.sa-blog-more a,.sa-blog-more button{display:inline-block;padding:.7rem 1.6rem;border:1px solid var(--sa-border,#e5e7eb);border-radius:var(--sa-radius,10px);text-decoration:none;color:inherit;font:inherit;font-weight:500;background:transparent;cursor:pointer;transition:background .15s ease}
|
|
122
|
+
.sa-blog-more a:hover,.sa-blog-more button:hover{background:var(--sa-th-bg,#f8fafc)}
|
|
122
123
|
.sa-blog-empty{color:var(--sa-muted,#64748b)}`;
|
|
123
124
|
const BLOG_TITLE = { ru: "Блог", uz: "Blog", en: "Blog", ar: "المدونة", tr: "Blog" };
|
|
124
125
|
const EMPTY_LABEL = { ru: "Пока нет статей.", uz: "Hozircha maqolalar yo‘q.", en: "No posts yet.", ar: "لا توجد مقالات بعد." };
|
|
@@ -136,7 +137,7 @@ async function SeoBlogIndex(props) {
|
|
|
136
137
|
const limit = Math.max(1, Math.floor(props.limit ?? 1000));
|
|
137
138
|
const posts = m.fsBlogList(locale, limit);
|
|
138
139
|
const theme = m.fsTheme(); // тема из .blogfs.json
|
|
139
|
-
return ((0, jsx_runtime_1.jsxs)("div", { className: "sa-blog-wrap", style: themeStyle(theme), children: [(0, jsx_runtime_1.jsx)("style", { dangerouslySetInnerHTML: { __html: GRID_CSS } }), (0, jsx_runtime_1.jsx)("h1", { className: "sa-blog-h1", children: title }), posts.length ? ((0, jsx_runtime_1.jsx)(
|
|
140
|
+
return ((0, jsx_runtime_1.jsxs)("div", { className: "sa-blog-wrap", style: themeStyle(theme), children: [(0, jsx_runtime_1.jsx)("style", { dangerouslySetInnerHTML: { __html: GRID_CSS } }), (0, jsx_runtime_1.jsx)("h1", { className: "sa-blog-h1", children: title }), posts.length ? ((0, jsx_runtime_1.jsx)(blog_grid_1.SeoBlogGrid, { posts: posts.map((p) => ({ slug: p.slug, title: p.title, cover: p.cover, excerpt: p.excerpt })), basePath: basePath, locale: locale })) : ((0, jsx_runtime_1.jsx)("p", { className: "sa-blog-empty", children: L(EMPTY_LABEL, locale, "No posts yet.") }))] }));
|
|
140
141
|
}
|
|
141
142
|
// Одна статья. Принимает пост (из seoBlogPost) — так страница может сделать notFound()/метаданные.
|
|
142
143
|
function SeoBlogArticle({ post, locale }) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@webnumseoagent/next",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.5",
|
|
4
4
|
"description": "Runtime SEO adapter + installer for Next.js App Router sites (SEO Agent platform)",
|
|
5
5
|
"main": "dist/client.js",
|
|
6
6
|
"types": "dist/client.d.ts",
|
|
@@ -52,7 +52,7 @@
|
|
|
52
52
|
"app-router"
|
|
53
53
|
],
|
|
54
54
|
"scripts": {
|
|
55
|
-
"build": "tsc -p tsconfig.build.json",
|
|
55
|
+
"build": "tsc -p tsconfig.build.json && node scripts/postbuild.mjs",
|
|
56
56
|
"prepublishOnly": "npm run build"
|
|
57
57
|
},
|
|
58
58
|
"dependencies": {
|