@weekend-studio/easyblog-next 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +16 -0
- package/dist/app-index.d.ts +39 -0
- package/dist/app-index.mjs +585 -0
- package/dist/app-index.mjs.map +1 -0
- package/dist/globals.css +1990 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.mjs +776 -0
- package/dist/index.mjs.map +1 -0
- package/dist/models.d.ts +34 -0
- package/dist/models.mjs +1 -0
- package/dist/models.mjs.map +1 -0
- package/dist/page-index.d.ts +96 -0
- package/dist/page-index.mjs +627 -0
- package/dist/page-index.mjs.map +1 -0
- package/package.json +119 -0
package/README.md
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
## Easyblog
|
|
2
|
+
|
|
3
|
+
- `npm install @weekend-studio/easyblog`
|
|
4
|
+
- Publish: `npm publish --access public`
|
|
5
|
+
|
|
6
|
+
## NextJS with App Router
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
## NextJS with Page Router
|
|
10
|
+
|
|
11
|
+
## Release
|
|
12
|
+
|
|
13
|
+
- Create a release branch from main: `git checkout -b release/v1.0.0`
|
|
14
|
+
- Update version in package.json: `npm version patch/minor/major`
|
|
15
|
+
- Push release branch: `git push -u origin release/v1.0.0`
|
|
16
|
+
- Create a Pull Request to merge into main
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { EasyBlogConfig, IQueryParams } from './models.js';
|
|
2
|
+
import { Metadata } from 'next';
|
|
3
|
+
import React from 'react';
|
|
4
|
+
|
|
5
|
+
declare function getBlogPosts(config: EasyBlogConfig, params?: IQueryParams): Promise<any>;
|
|
6
|
+
declare function getBlogPost(config: EasyBlogConfig, slug: string): Promise<any>;
|
|
7
|
+
declare function getBlogPaths(config: EasyBlogConfig): Promise<any>;
|
|
8
|
+
declare function generateMetadata(config: EasyBlogConfig, slug?: string): Promise<Metadata>;
|
|
9
|
+
|
|
10
|
+
interface NextBlogPageProps {
|
|
11
|
+
config: EasyBlogConfig;
|
|
12
|
+
slug: string;
|
|
13
|
+
style?: React.CSSProperties;
|
|
14
|
+
}
|
|
15
|
+
declare function NextBlogPage(props: NextBlogPageProps): Promise<React.JSX.Element>;
|
|
16
|
+
|
|
17
|
+
interface NextBlogListPageProps {
|
|
18
|
+
config: EasyBlogConfig;
|
|
19
|
+
type: 'grid' | 'list';
|
|
20
|
+
searchParams?: {
|
|
21
|
+
page?: string;
|
|
22
|
+
category?: string;
|
|
23
|
+
tags?: string;
|
|
24
|
+
};
|
|
25
|
+
displayOptions?: {
|
|
26
|
+
showThumbnail?: boolean;
|
|
27
|
+
showReadingTime?: boolean;
|
|
28
|
+
showExcerpt?: boolean;
|
|
29
|
+
showTags?: boolean;
|
|
30
|
+
showDate?: boolean;
|
|
31
|
+
showAuthor?: boolean;
|
|
32
|
+
showCategory?: boolean;
|
|
33
|
+
isNextPage?: boolean;
|
|
34
|
+
blogPerPage?: number;
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
declare function NextBlogListPage(props: NextBlogListPageProps): Promise<React.JSX.Element>;
|
|
38
|
+
|
|
39
|
+
export { NextBlogListPage, NextBlogPage, generateMetadata, getBlogPaths, getBlogPost, getBlogPosts };
|
|
@@ -0,0 +1,585 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __getOwnPropSymbols = Object.getOwnPropertySymbols;
|
|
3
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
4
|
+
var __propIsEnum = Object.prototype.propertyIsEnumerable;
|
|
5
|
+
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
6
|
+
var __spreadValues = (a, b) => {
|
|
7
|
+
for (var prop in b || (b = {}))
|
|
8
|
+
if (__hasOwnProp.call(b, prop))
|
|
9
|
+
__defNormalProp(a, prop, b[prop]);
|
|
10
|
+
if (__getOwnPropSymbols)
|
|
11
|
+
for (var prop of __getOwnPropSymbols(b)) {
|
|
12
|
+
if (__propIsEnum.call(b, prop))
|
|
13
|
+
__defNormalProp(a, prop, b[prop]);
|
|
14
|
+
}
|
|
15
|
+
return a;
|
|
16
|
+
};
|
|
17
|
+
var __objRest = (source, exclude) => {
|
|
18
|
+
var target = {};
|
|
19
|
+
for (var prop in source)
|
|
20
|
+
if (__hasOwnProp.call(source, prop) && exclude.indexOf(prop) < 0)
|
|
21
|
+
target[prop] = source[prop];
|
|
22
|
+
if (source != null && __getOwnPropSymbols)
|
|
23
|
+
for (var prop of __getOwnPropSymbols(source)) {
|
|
24
|
+
if (exclude.indexOf(prop) < 0 && __propIsEnum.call(source, prop))
|
|
25
|
+
target[prop] = source[prop];
|
|
26
|
+
}
|
|
27
|
+
return target;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
// src/app/NextAppRouterUtils.ts
|
|
31
|
+
import { cache } from "react";
|
|
32
|
+
import { notFound } from "next/navigation";
|
|
33
|
+
|
|
34
|
+
// src/app/BaseUtils.ts
|
|
35
|
+
async function exchangeToken(config) {
|
|
36
|
+
try {
|
|
37
|
+
const response = await fetch(`${config.apiUrl}/exchange-token`, {
|
|
38
|
+
method: "POST",
|
|
39
|
+
headers: {
|
|
40
|
+
"Authorization": `Bearer ${config.apiKey}`,
|
|
41
|
+
"x-project-id": config.projectId
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
if (!response.ok) {
|
|
45
|
+
console.error("Failed to exchange token:", response);
|
|
46
|
+
throw new Error("Failed to authenticate");
|
|
47
|
+
}
|
|
48
|
+
const data = await response.json();
|
|
49
|
+
return data.token;
|
|
50
|
+
} catch (error) {
|
|
51
|
+
console.error("Failed to exchange token:", error);
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// src/app/lib/utils.ts
|
|
57
|
+
import { clsx } from "clsx";
|
|
58
|
+
import { twMerge } from "tailwind-merge";
|
|
59
|
+
function cn(...inputs) {
|
|
60
|
+
return twMerge(clsx(inputs));
|
|
61
|
+
}
|
|
62
|
+
var getFormattedDate = (date) => {
|
|
63
|
+
if (!date) return "";
|
|
64
|
+
return new Date(date).toISOString().split("T")[0];
|
|
65
|
+
};
|
|
66
|
+
var getVisiblePages = (current, total) => {
|
|
67
|
+
if (total <= 7) return Array.from({ length: total }, (_, i) => i + 1);
|
|
68
|
+
if (current <= 3) return [1, 2, 3, 4, 5, null, total];
|
|
69
|
+
if (current >= total - 2) return [1, null, total - 4, total - 3, total - 2, total - 1, total];
|
|
70
|
+
return [1, null, current - 1, current, current + 1, null, total];
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
// src/app/NextAppRouterUtils.ts
|
|
74
|
+
var getAuthToken = cache(async (config) => {
|
|
75
|
+
return exchangeToken(config);
|
|
76
|
+
});
|
|
77
|
+
async function getBlogPosts(config, params = {}) {
|
|
78
|
+
const token = await getAuthToken(config);
|
|
79
|
+
const queryString = new URLSearchParams(
|
|
80
|
+
Object.entries(params).map(([key, value]) => [key, String(value)])
|
|
81
|
+
).toString();
|
|
82
|
+
const url = `${config.apiUrl}/sdk/blogs${queryString ? `?${queryString}` : ""}`;
|
|
83
|
+
const response = await fetch(url, {
|
|
84
|
+
headers: {
|
|
85
|
+
"Authorization": `Bearer ${token}`,
|
|
86
|
+
"x-project-id": config.projectId
|
|
87
|
+
},
|
|
88
|
+
next: { revalidate: 3600 }
|
|
89
|
+
});
|
|
90
|
+
if (!response.ok) {
|
|
91
|
+
console.error("Failed to fetch blog posts:", response);
|
|
92
|
+
throw new Error(`Failed to fetch blog posts: ${response.status} ${response.statusText}`);
|
|
93
|
+
}
|
|
94
|
+
const data = await response.json();
|
|
95
|
+
return data;
|
|
96
|
+
}
|
|
97
|
+
async function getBlogPost(config, slug) {
|
|
98
|
+
const token = await getAuthToken(config);
|
|
99
|
+
const response = await fetch(`${config.apiUrl}/sdk/blogs/${slug}`, {
|
|
100
|
+
headers: {
|
|
101
|
+
"Authorization": `Bearer ${token}`,
|
|
102
|
+
"x-project-id": config.projectId
|
|
103
|
+
},
|
|
104
|
+
next: { revalidate: 3600 }
|
|
105
|
+
});
|
|
106
|
+
if (!response.ok) {
|
|
107
|
+
throw new Error(`Failed to fetch blog post: ${response.status} ${response.statusText}`);
|
|
108
|
+
}
|
|
109
|
+
return response.json();
|
|
110
|
+
}
|
|
111
|
+
async function getBlogPaths(config) {
|
|
112
|
+
const { blogs } = await getBlogPosts(config, { index: 0, limit: 1e4 });
|
|
113
|
+
return blogs.map((blog) => ({ slug: blog.slug }));
|
|
114
|
+
}
|
|
115
|
+
async function generateMetadata(config, slug) {
|
|
116
|
+
var _a, _b;
|
|
117
|
+
if (slug) {
|
|
118
|
+
const post = await getBlogPost(config, slug);
|
|
119
|
+
if (!post) return notFound();
|
|
120
|
+
return {
|
|
121
|
+
title: post.title,
|
|
122
|
+
description: (_a = post.excerpt) == null ? void 0 : _a.substring(0, 160),
|
|
123
|
+
openGraph: {
|
|
124
|
+
title: post.title,
|
|
125
|
+
description: (_b = post.excerpt) == null ? void 0 : _b.substring(0, 160),
|
|
126
|
+
type: "article",
|
|
127
|
+
publishedTime: getFormattedDate(post.created_at),
|
|
128
|
+
tags: post.tags
|
|
129
|
+
}
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
return {
|
|
133
|
+
title: "Blog",
|
|
134
|
+
description: "Read our latest blog posts",
|
|
135
|
+
openGraph: {
|
|
136
|
+
title: "Blog",
|
|
137
|
+
description: "Read our latest blog posts",
|
|
138
|
+
type: "website"
|
|
139
|
+
}
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// src/app/components/NextBlogPage.tsx
|
|
144
|
+
import React4 from "react";
|
|
145
|
+
import { notFound as notFound2 } from "next/navigation";
|
|
146
|
+
|
|
147
|
+
// src/app/components/Article.tsx
|
|
148
|
+
import React3 from "react";
|
|
149
|
+
import { lowlight as low } from "lowlight/lib/core";
|
|
150
|
+
|
|
151
|
+
// src/app/components/ui/linkBadge.tsx
|
|
152
|
+
import Link from "next/link";
|
|
153
|
+
|
|
154
|
+
// src/app/components/ui/badge.tsx
|
|
155
|
+
import * as React from "react";
|
|
156
|
+
import { cva } from "class-variance-authority";
|
|
157
|
+
var badgeVariants = cva(
|
|
158
|
+
"inline-flex items-center rounded-md border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
|
|
159
|
+
{
|
|
160
|
+
variants: {
|
|
161
|
+
variant: {
|
|
162
|
+
default: "border-transparent bg-primary text-primary-foreground shadow hover:bg-primary/80",
|
|
163
|
+
secondary: "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",
|
|
164
|
+
destructive: "border-transparent bg-destructive text-destructive-foreground shadow hover:bg-destructive/80",
|
|
165
|
+
outline: "text-foreground"
|
|
166
|
+
}
|
|
167
|
+
},
|
|
168
|
+
defaultVariants: {
|
|
169
|
+
variant: "default"
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
);
|
|
173
|
+
function Badge(_a) {
|
|
174
|
+
var _b = _a, { className, variant } = _b, props = __objRest(_b, ["className", "variant"]);
|
|
175
|
+
return /* @__PURE__ */ React.createElement("div", __spreadValues({ className: cn(badgeVariants({ variant }), className) }, props));
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// src/app/components/ui/linkBadge.tsx
|
|
179
|
+
import React2 from "react";
|
|
180
|
+
var LinkBadge = ({ children, href }) => {
|
|
181
|
+
return /* @__PURE__ */ React2.createElement(Link, { href }, /* @__PURE__ */ React2.createElement(Badge, { variant: "outline", className: "text-xs hover:cursor-pointer hover:bg-primary hover:text-primary-foreground" }, children));
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
// src/app/components/Article.tsx
|
|
185
|
+
import javascript from "highlight.js/lib/languages/javascript";
|
|
186
|
+
import typescript from "highlight.js/lib/languages/typescript";
|
|
187
|
+
import css from "highlight.js/lib/languages/css";
|
|
188
|
+
import html from "highlight.js/lib/languages/xml";
|
|
189
|
+
var Article = ({ post, content, style }) => {
|
|
190
|
+
low.registerLanguage("css", css);
|
|
191
|
+
low.registerLanguage("js", javascript);
|
|
192
|
+
low.registerLanguage("ts", typescript);
|
|
193
|
+
low.registerLanguage("html", html);
|
|
194
|
+
const contentHtml = {
|
|
195
|
+
__html: content
|
|
196
|
+
};
|
|
197
|
+
return post && /* @__PURE__ */ React3.createElement("article", { style, className: "easyblog" }, post.thumbnail && /* @__PURE__ */ React3.createElement("img", { src: post.thumbnail, alt: post.title, className: "w-full h-64 object-cover mb-8 rounded-lg" }), /* @__PURE__ */ React3.createElement("header", null, /* @__PURE__ */ React3.createElement("h1", { className: "title" }, post.title), /* @__PURE__ */ React3.createElement("div", { className: "info" }, /* @__PURE__ */ React3.createElement("div", { className: "author" }, "Created by ", /* @__PURE__ */ React3.createElement("span", { className: "font-semibold" }, post.author)), /* @__PURE__ */ React3.createElement("div", { className: "date" }, "Published at ", /* @__PURE__ */ React3.createElement("span", { className: "font-semibold" }, getFormattedDate(post.created_at))))), /* @__PURE__ */ React3.createElement("div", { className: "content", dangerouslySetInnerHTML: contentHtml }), /* @__PURE__ */ React3.createElement("footer", { className: "mt-8 pt-4 border-t border-gray-200" }, post.tags && post.tags.length > 0 && /* @__PURE__ */ React3.createElement("div", { className: "flex flex-wrap gap-2 my-4" }, /* @__PURE__ */ React3.createElement("span", { className: "text-sm font-semibold " }, "Tags"), /* @__PURE__ */ React3.createElement("div", { className: "flex flex-wrap gap-2" }, post.tags.map((tag, index) => /* @__PURE__ */ React3.createElement(LinkBadge, { href: `/blogs?tags=${tag}`, key: index }, tag)))), post.category && /* @__PURE__ */ React3.createElement("div", { className: "flex flex-wrap gap-2 my-4" }, /* @__PURE__ */ React3.createElement("span", { className: "text-sm font-semibold" }, "Category"), /* @__PURE__ */ React3.createElement(LinkBadge, { href: `/blogs?category=${post.category}` }, post.category))));
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
// src/app/components/NextBlogPage.tsx
|
|
201
|
+
async function NextBlogPage(props) {
|
|
202
|
+
try {
|
|
203
|
+
const response = await getBlogPost({
|
|
204
|
+
apiKey: props.config.apiKey,
|
|
205
|
+
projectId: props.config.projectId,
|
|
206
|
+
apiUrl: props.config.apiUrl
|
|
207
|
+
}, props.slug);
|
|
208
|
+
if (!response.blog) return notFound2();
|
|
209
|
+
const post = response.blog;
|
|
210
|
+
const htmlContent = response.html_content;
|
|
211
|
+
return /* @__PURE__ */ React4.createElement(
|
|
212
|
+
Article,
|
|
213
|
+
{
|
|
214
|
+
post,
|
|
215
|
+
content: htmlContent,
|
|
216
|
+
style: props.style
|
|
217
|
+
}
|
|
218
|
+
);
|
|
219
|
+
} catch (error) {
|
|
220
|
+
console.error("Error fetching blog post:", error);
|
|
221
|
+
return notFound2();
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// src/app/components/NextBlogListPage.tsx
|
|
226
|
+
import React10 from "react";
|
|
227
|
+
|
|
228
|
+
// src/app/components/ArticleGroup.tsx
|
|
229
|
+
import React7 from "react";
|
|
230
|
+
|
|
231
|
+
// src/app/components/ArticleCard.tsx
|
|
232
|
+
import React6 from "react";
|
|
233
|
+
|
|
234
|
+
// src/app/components/ui/card.tsx
|
|
235
|
+
import * as React5 from "react";
|
|
236
|
+
var Card = React5.forwardRef((_a, ref) => {
|
|
237
|
+
var _b = _a, { className } = _b, props = __objRest(_b, ["className"]);
|
|
238
|
+
return /* @__PURE__ */ React5.createElement(
|
|
239
|
+
"div",
|
|
240
|
+
__spreadValues({
|
|
241
|
+
ref,
|
|
242
|
+
className: cn(
|
|
243
|
+
"rounded-xl border bg-card text-card-foreground shadow",
|
|
244
|
+
className
|
|
245
|
+
)
|
|
246
|
+
}, props)
|
|
247
|
+
);
|
|
248
|
+
});
|
|
249
|
+
Card.displayName = "Card";
|
|
250
|
+
var CardHeader = React5.forwardRef((_a, ref) => {
|
|
251
|
+
var _b = _a, { className } = _b, props = __objRest(_b, ["className"]);
|
|
252
|
+
return /* @__PURE__ */ React5.createElement(
|
|
253
|
+
"div",
|
|
254
|
+
__spreadValues({
|
|
255
|
+
ref,
|
|
256
|
+
className: cn("flex flex-col space-y-1.5 p-6", className)
|
|
257
|
+
}, props)
|
|
258
|
+
);
|
|
259
|
+
});
|
|
260
|
+
CardHeader.displayName = "CardHeader";
|
|
261
|
+
var CardTitle = React5.forwardRef((_a, ref) => {
|
|
262
|
+
var _b = _a, { className } = _b, props = __objRest(_b, ["className"]);
|
|
263
|
+
return /* @__PURE__ */ React5.createElement(
|
|
264
|
+
"div",
|
|
265
|
+
__spreadValues({
|
|
266
|
+
ref,
|
|
267
|
+
className: cn("font-semibold leading-none tracking-tight", className)
|
|
268
|
+
}, props)
|
|
269
|
+
);
|
|
270
|
+
});
|
|
271
|
+
CardTitle.displayName = "CardTitle";
|
|
272
|
+
var CardDescription = React5.forwardRef((_a, ref) => {
|
|
273
|
+
var _b = _a, { className } = _b, props = __objRest(_b, ["className"]);
|
|
274
|
+
return /* @__PURE__ */ React5.createElement(
|
|
275
|
+
"div",
|
|
276
|
+
__spreadValues({
|
|
277
|
+
ref,
|
|
278
|
+
className: cn("text-sm text-muted-foreground", className)
|
|
279
|
+
}, props)
|
|
280
|
+
);
|
|
281
|
+
});
|
|
282
|
+
CardDescription.displayName = "CardDescription";
|
|
283
|
+
var CardContent = React5.forwardRef((_a, ref) => {
|
|
284
|
+
var _b = _a, { className } = _b, props = __objRest(_b, ["className"]);
|
|
285
|
+
return /* @__PURE__ */ React5.createElement("div", __spreadValues({ ref, className: cn("p-6 pt-0", className) }, props));
|
|
286
|
+
});
|
|
287
|
+
CardContent.displayName = "CardContent";
|
|
288
|
+
var CardFooter = React5.forwardRef((_a, ref) => {
|
|
289
|
+
var _b = _a, { className } = _b, props = __objRest(_b, ["className"]);
|
|
290
|
+
return /* @__PURE__ */ React5.createElement(
|
|
291
|
+
"div",
|
|
292
|
+
__spreadValues({
|
|
293
|
+
ref,
|
|
294
|
+
className: cn("flex items-center p-6 pt-0", className)
|
|
295
|
+
}, props)
|
|
296
|
+
);
|
|
297
|
+
});
|
|
298
|
+
CardFooter.displayName = "CardFooter";
|
|
299
|
+
|
|
300
|
+
// src/app/components/ArticleCard.tsx
|
|
301
|
+
import Link2 from "next/link.js";
|
|
302
|
+
var ArticleCard = ({
|
|
303
|
+
blog,
|
|
304
|
+
showThumbnail = true,
|
|
305
|
+
showReadingTime = true,
|
|
306
|
+
showExcerpt = true,
|
|
307
|
+
showTags = true,
|
|
308
|
+
showDate = true,
|
|
309
|
+
horizontal = false,
|
|
310
|
+
showAuthor = true,
|
|
311
|
+
showCategory = true,
|
|
312
|
+
isNextPage = false
|
|
313
|
+
}) => {
|
|
314
|
+
const renderTags = (tags) => {
|
|
315
|
+
return tags.map((tag, index) => {
|
|
316
|
+
return isNextPage ? /* @__PURE__ */ React6.createElement(LinkBadge, { key: index, href: `/blogs/?tags=${tag}` }, tag) : /* @__PURE__ */ React6.createElement(Badge, { key: index, variant: "outline", className: "text-xs rounded-full hover:cursor-pointer hover:bg-primary hover:text-primary-foreground" }, tag);
|
|
317
|
+
});
|
|
318
|
+
};
|
|
319
|
+
const renderCategory = () => {
|
|
320
|
+
return isNextPage ? /* @__PURE__ */ React6.createElement(Link2, { href: `/blogs/?category=${blog.category}`, className: "hover:underline" }, " | ", blog.category) : /* @__PURE__ */ React6.createElement("span", null, " | ", blog.category);
|
|
321
|
+
};
|
|
322
|
+
const renderThumbnail = () => {
|
|
323
|
+
return isNextPage ? /* @__PURE__ */ React6.createElement(Link2, { href: `/blogs/${blog.slug}` }, /* @__PURE__ */ React6.createElement("img", { className: `${horizontal ? "w-48 h-full" : "w-full h-48"} object-cover`, src: blog.thumbnail, alt: blog.title })) : /* @__PURE__ */ React6.createElement("img", { className: `${horizontal ? "w-48 h-full" : "w-full h-48"} object-cover`, src: blog.thumbnail, alt: blog.title });
|
|
324
|
+
};
|
|
325
|
+
const renderTitle = () => {
|
|
326
|
+
return isNextPage ? /* @__PURE__ */ React6.createElement(Link2, { href: `/blogs/${blog.slug}`, className: "hover:underline text-primary" }, blog.title) : /* @__PURE__ */ React6.createElement("h3", { className: "font-bold text-xl mb-2 text-wrap" }, blog.title);
|
|
327
|
+
};
|
|
328
|
+
return /* @__PURE__ */ React6.createElement(Card, { className: `max-w-full overflow-hidden ${horizontal ? "flex flex-row" : ""}` }, blog.thumbnail && showThumbnail && /* @__PURE__ */ React6.createElement(CardHeader, { className: "p-0" }, renderThumbnail()), /* @__PURE__ */ React6.createElement(CardContent, { className: "p-6" }, renderTitle(), (showAuthor || showDate || showCategory) && /* @__PURE__ */ React6.createElement("p", { className: "text-muted-foreground" }, showAuthor && /* @__PURE__ */ React6.createElement("span", null, blog.author), " ", showDate && /* @__PURE__ */ React6.createElement("span", null, "| ", getFormattedDate(blog.created_at)), " ", blog.category && showCategory && renderCategory()), blog.reading_time && showReadingTime && /* @__PURE__ */ React6.createElement("p", { className: "text-muted-foreground text-sm mt-2" }, blog.reading_time, " minutes to read"), showExcerpt && /* @__PURE__ */ React6.createElement("p", { className: "text-muted-foreground text-sm mt-2" }, blog.excerpt), horizontal && /* @__PURE__ */ React6.createElement("div", { className: "mt-4 flex flex-wrap gap-2 items-start" }, showTags && blog.tags.map((tag, index) => /* @__PURE__ */ React6.createElement(LinkBadge, { key: index, href: `/blogs?tags=${tag}` }, tag)))), !horizontal && /* @__PURE__ */ React6.createElement(CardFooter, { className: "flex flex-wrap gap-2 items-start" }, showTags && renderTags(blog.tags)));
|
|
329
|
+
};
|
|
330
|
+
|
|
331
|
+
// src/app/components/ArticleGroup.tsx
|
|
332
|
+
var ArticleGroup = ({
|
|
333
|
+
type,
|
|
334
|
+
blogs,
|
|
335
|
+
showThumbnail = true,
|
|
336
|
+
showReadingTime = true,
|
|
337
|
+
showExcerpt = true,
|
|
338
|
+
showTags = true,
|
|
339
|
+
showDate = true,
|
|
340
|
+
showAuthor = true,
|
|
341
|
+
showCategory = true,
|
|
342
|
+
isNextPage = false
|
|
343
|
+
}) => {
|
|
344
|
+
if (!blogs || blogs.length === 0) {
|
|
345
|
+
return /* @__PURE__ */ React7.createElement("div", null, "No blogs found. Please refresh the page.");
|
|
346
|
+
}
|
|
347
|
+
const renderGridView = () => {
|
|
348
|
+
return /* @__PURE__ */ React7.createElement("div", { className: "grid grid-cols-1 md:grid-cols-3 lg:grid-cols-3 gap-6" }, blogs.map((blog) => /* @__PURE__ */ React7.createElement(
|
|
349
|
+
ArticleCard,
|
|
350
|
+
{
|
|
351
|
+
key: blog._id,
|
|
352
|
+
blog,
|
|
353
|
+
showThumbnail,
|
|
354
|
+
showReadingTime,
|
|
355
|
+
showExcerpt,
|
|
356
|
+
showTags,
|
|
357
|
+
showDate,
|
|
358
|
+
showAuthor,
|
|
359
|
+
showCategory,
|
|
360
|
+
isNextPage
|
|
361
|
+
}
|
|
362
|
+
)));
|
|
363
|
+
};
|
|
364
|
+
const renderListView = () => {
|
|
365
|
+
return /* @__PURE__ */ React7.createElement("div", { className: "flex flex-col gap-4 min-w-[800px]" }, blogs.map((blog) => /* @__PURE__ */ React7.createElement(
|
|
366
|
+
ArticleCard,
|
|
367
|
+
{
|
|
368
|
+
key: blog._id,
|
|
369
|
+
blog,
|
|
370
|
+
showThumbnail,
|
|
371
|
+
showReadingTime,
|
|
372
|
+
showExcerpt,
|
|
373
|
+
showTags,
|
|
374
|
+
showDate,
|
|
375
|
+
showAuthor,
|
|
376
|
+
showCategory,
|
|
377
|
+
horizontal: true,
|
|
378
|
+
isNextPage
|
|
379
|
+
}
|
|
380
|
+
)));
|
|
381
|
+
};
|
|
382
|
+
return /* @__PURE__ */ React7.createElement("div", null, type === "grid" ? renderGridView() : renderListView());
|
|
383
|
+
};
|
|
384
|
+
|
|
385
|
+
// src/app/components/ui/pagination.tsx
|
|
386
|
+
import * as React9 from "react";
|
|
387
|
+
import { ChevronLeft, ChevronRight, MoreHorizontal } from "lucide-react";
|
|
388
|
+
|
|
389
|
+
// src/app/components/ui/button.tsx
|
|
390
|
+
import * as React8 from "react";
|
|
391
|
+
import { Slot } from "@radix-ui/react-slot";
|
|
392
|
+
import { cva as cva2 } from "class-variance-authority";
|
|
393
|
+
var buttonVariants = cva2(
|
|
394
|
+
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
|
|
395
|
+
{
|
|
396
|
+
variants: {
|
|
397
|
+
variant: {
|
|
398
|
+
default: "bg-primary text-primary-foreground shadow hover:bg-primary/90",
|
|
399
|
+
destructive: "bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90",
|
|
400
|
+
outline: "border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground",
|
|
401
|
+
secondary: "bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80",
|
|
402
|
+
ghost: "hover:bg-accent hover:text-accent-foreground",
|
|
403
|
+
link: "text-primary underline-offset-4 hover:underline"
|
|
404
|
+
},
|
|
405
|
+
size: {
|
|
406
|
+
default: "h-9 px-4 py-2",
|
|
407
|
+
sm: "h-8 rounded-md px-3 text-xs",
|
|
408
|
+
lg: "h-10 rounded-md px-8",
|
|
409
|
+
icon: "h-9 w-9"
|
|
410
|
+
}
|
|
411
|
+
},
|
|
412
|
+
defaultVariants: {
|
|
413
|
+
variant: "default",
|
|
414
|
+
size: "default"
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
);
|
|
418
|
+
var Button = React8.forwardRef(
|
|
419
|
+
(_a, ref) => {
|
|
420
|
+
var _b = _a, { className, variant, size, asChild = false } = _b, props = __objRest(_b, ["className", "variant", "size", "asChild"]);
|
|
421
|
+
const Comp = asChild ? Slot : "button";
|
|
422
|
+
return /* @__PURE__ */ React8.createElement(
|
|
423
|
+
Comp,
|
|
424
|
+
__spreadValues({
|
|
425
|
+
className: cn(buttonVariants({ variant, size, className })),
|
|
426
|
+
ref
|
|
427
|
+
}, props)
|
|
428
|
+
);
|
|
429
|
+
}
|
|
430
|
+
);
|
|
431
|
+
Button.displayName = "Button";
|
|
432
|
+
|
|
433
|
+
// src/app/components/ui/pagination.tsx
|
|
434
|
+
import Link3 from "next/link";
|
|
435
|
+
var Pagination = (_a) => {
|
|
436
|
+
var _b = _a, { className } = _b, props = __objRest(_b, ["className"]);
|
|
437
|
+
return /* @__PURE__ */ React9.createElement(
|
|
438
|
+
"nav",
|
|
439
|
+
__spreadValues({
|
|
440
|
+
role: "navigation",
|
|
441
|
+
"aria-label": "pagination",
|
|
442
|
+
className: cn("mx-auto flex w-full justify-center", className)
|
|
443
|
+
}, props)
|
|
444
|
+
);
|
|
445
|
+
};
|
|
446
|
+
Pagination.displayName = "Pagination";
|
|
447
|
+
var PaginationContent = React9.forwardRef((_a, ref) => {
|
|
448
|
+
var _b = _a, { className } = _b, props = __objRest(_b, ["className"]);
|
|
449
|
+
return /* @__PURE__ */ React9.createElement(
|
|
450
|
+
"ul",
|
|
451
|
+
__spreadValues({
|
|
452
|
+
ref,
|
|
453
|
+
className: cn("flex flex-row items-center gap-1", className)
|
|
454
|
+
}, props)
|
|
455
|
+
);
|
|
456
|
+
});
|
|
457
|
+
PaginationContent.displayName = "PaginationContent";
|
|
458
|
+
var PaginationItem = React9.forwardRef((_a, ref) => {
|
|
459
|
+
var _b = _a, { className } = _b, props = __objRest(_b, ["className"]);
|
|
460
|
+
return /* @__PURE__ */ React9.createElement("li", __spreadValues({ ref, className: cn("", className) }, props));
|
|
461
|
+
});
|
|
462
|
+
PaginationItem.displayName = "PaginationItem";
|
|
463
|
+
var PaginationLink = (_a) => {
|
|
464
|
+
var _b = _a, {
|
|
465
|
+
className,
|
|
466
|
+
isActive,
|
|
467
|
+
size = "icon"
|
|
468
|
+
} = _b, props = __objRest(_b, [
|
|
469
|
+
"className",
|
|
470
|
+
"isActive",
|
|
471
|
+
"size"
|
|
472
|
+
]);
|
|
473
|
+
return /* @__PURE__ */ React9.createElement(
|
|
474
|
+
Link3,
|
|
475
|
+
__spreadValues({
|
|
476
|
+
"aria-current": isActive ? "page" : void 0,
|
|
477
|
+
className: cn(
|
|
478
|
+
buttonVariants({
|
|
479
|
+
variant: isActive ? "outline" : "ghost",
|
|
480
|
+
size
|
|
481
|
+
}),
|
|
482
|
+
className
|
|
483
|
+
)
|
|
484
|
+
}, props)
|
|
485
|
+
);
|
|
486
|
+
};
|
|
487
|
+
PaginationLink.displayName = "PaginationLink";
|
|
488
|
+
var PaginationPrevious = (_a) => {
|
|
489
|
+
var _b = _a, {
|
|
490
|
+
className
|
|
491
|
+
} = _b, props = __objRest(_b, [
|
|
492
|
+
"className"
|
|
493
|
+
]);
|
|
494
|
+
return /* @__PURE__ */ React9.createElement(
|
|
495
|
+
PaginationLink,
|
|
496
|
+
__spreadValues({
|
|
497
|
+
"aria-label": "Go to previous page",
|
|
498
|
+
size: "default",
|
|
499
|
+
className: cn("gap-1 pl-2.5", className)
|
|
500
|
+
}, props),
|
|
501
|
+
/* @__PURE__ */ React9.createElement(ChevronLeft, { className: "h-4 w-4" }),
|
|
502
|
+
/* @__PURE__ */ React9.createElement("span", null, "Previous")
|
|
503
|
+
);
|
|
504
|
+
};
|
|
505
|
+
PaginationPrevious.displayName = "PaginationPrevious";
|
|
506
|
+
var PaginationNext = (_a) => {
|
|
507
|
+
var _b = _a, {
|
|
508
|
+
className
|
|
509
|
+
} = _b, props = __objRest(_b, [
|
|
510
|
+
"className"
|
|
511
|
+
]);
|
|
512
|
+
return /* @__PURE__ */ React9.createElement(
|
|
513
|
+
PaginationLink,
|
|
514
|
+
__spreadValues({
|
|
515
|
+
"aria-label": "Go to next page",
|
|
516
|
+
size: "default",
|
|
517
|
+
className: cn("gap-1 pr-2.5", className)
|
|
518
|
+
}, props),
|
|
519
|
+
/* @__PURE__ */ React9.createElement("span", null, "Next"),
|
|
520
|
+
/* @__PURE__ */ React9.createElement(ChevronRight, { className: "h-4 w-4" })
|
|
521
|
+
);
|
|
522
|
+
};
|
|
523
|
+
PaginationNext.displayName = "PaginationNext";
|
|
524
|
+
var PaginationEllipsis = (_a) => {
|
|
525
|
+
var _b = _a, {
|
|
526
|
+
className
|
|
527
|
+
} = _b, props = __objRest(_b, [
|
|
528
|
+
"className"
|
|
529
|
+
]);
|
|
530
|
+
return /* @__PURE__ */ React9.createElement(
|
|
531
|
+
"span",
|
|
532
|
+
__spreadValues({
|
|
533
|
+
"aria-hidden": true,
|
|
534
|
+
className: cn("flex h-9 w-9 items-center justify-center", className)
|
|
535
|
+
}, props),
|
|
536
|
+
/* @__PURE__ */ React9.createElement(MoreHorizontal, { className: "h-4 w-4" }),
|
|
537
|
+
/* @__PURE__ */ React9.createElement("span", { className: "sr-only" }, "More pages")
|
|
538
|
+
);
|
|
539
|
+
};
|
|
540
|
+
PaginationEllipsis.displayName = "PaginationEllipsis";
|
|
541
|
+
|
|
542
|
+
// src/app/components/NextBlogListPage.tsx
|
|
543
|
+
var DEFAULT_BLOG_PER_PAGE = 9;
|
|
544
|
+
async function NextBlogListPage(props) {
|
|
545
|
+
var _a, _b;
|
|
546
|
+
const postsPerPage = ((_a = props.displayOptions) == null ? void 0 : _a.blogPerPage) || DEFAULT_BLOG_PER_PAGE;
|
|
547
|
+
const searchParams = props.searchParams || {};
|
|
548
|
+
const currentPage = Number(searchParams.page) || 1;
|
|
549
|
+
const queryParams = {
|
|
550
|
+
index: (currentPage - 1) * postsPerPage,
|
|
551
|
+
limit: postsPerPage,
|
|
552
|
+
category: searchParams.category || "",
|
|
553
|
+
tags: ((_b = searchParams.tags) == null ? void 0 : _b.split(",")) || []
|
|
554
|
+
};
|
|
555
|
+
const res = await getBlogPosts(
|
|
556
|
+
props.config,
|
|
557
|
+
queryParams
|
|
558
|
+
);
|
|
559
|
+
const { blogs, total } = res;
|
|
560
|
+
const totalPages = Math.ceil(total / queryParams.limit);
|
|
561
|
+
const visiblePages = getVisiblePages(currentPage, totalPages);
|
|
562
|
+
return /* @__PURE__ */ React10.createElement("div", null, /* @__PURE__ */ React10.createElement(
|
|
563
|
+
ArticleGroup,
|
|
564
|
+
__spreadValues({
|
|
565
|
+
blogs,
|
|
566
|
+
type: props.type
|
|
567
|
+
}, props.displayOptions)
|
|
568
|
+
), totalPages > 1 && /* @__PURE__ */ React10.createElement(Pagination, { className: "mt-8" }, /* @__PURE__ */ React10.createElement(PaginationContent, null, currentPage > 1 && /* @__PURE__ */ React10.createElement(PaginationItem, null, /* @__PURE__ */ React10.createElement(PaginationPrevious, { href: `/blogs?page=${currentPage - 1}` })), visiblePages.map((pageNum, idx) => pageNum === null ? /* @__PURE__ */ React10.createElement(PaginationItem, { key: `ellipsis-${idx}` }, /* @__PURE__ */ React10.createElement(PaginationEllipsis, null)) : /* @__PURE__ */ React10.createElement(PaginationItem, { key: pageNum }, /* @__PURE__ */ React10.createElement(
|
|
569
|
+
PaginationLink,
|
|
570
|
+
{
|
|
571
|
+
href: `/blogs?page=${pageNum}`,
|
|
572
|
+
isActive: pageNum === currentPage
|
|
573
|
+
},
|
|
574
|
+
pageNum
|
|
575
|
+
))), currentPage < totalPages && /* @__PURE__ */ React10.createElement(PaginationItem, null, /* @__PURE__ */ React10.createElement(PaginationNext, { href: `/blogs?page=${currentPage + 1}` })))));
|
|
576
|
+
}
|
|
577
|
+
export {
|
|
578
|
+
NextBlogListPage,
|
|
579
|
+
NextBlogPage,
|
|
580
|
+
generateMetadata,
|
|
581
|
+
getBlogPaths,
|
|
582
|
+
getBlogPost,
|
|
583
|
+
getBlogPosts
|
|
584
|
+
};
|
|
585
|
+
//# sourceMappingURL=app-index.mjs.map
|