@victusvinceere/saas-blog 0.1.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/components/index.d.mts +57 -0
- package/dist/components/index.d.ts +57 -0
- package/dist/components/index.js +128 -0
- package/dist/components/index.js.map +1 -0
- package/dist/components/index.mjs +91 -0
- package/dist/components/index.mjs.map +1 -0
- package/dist/index.d.mts +2 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +224 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +181 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +54 -0
- package/prisma/schema.prisma +70 -0
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
+
|
|
3
|
+
interface BlogPost {
|
|
4
|
+
slug: string;
|
|
5
|
+
title: string;
|
|
6
|
+
description: string;
|
|
7
|
+
date: string;
|
|
8
|
+
author: {
|
|
9
|
+
name: string;
|
|
10
|
+
image?: string;
|
|
11
|
+
};
|
|
12
|
+
image?: string;
|
|
13
|
+
tags?: string[];
|
|
14
|
+
content: string;
|
|
15
|
+
readingTime: string;
|
|
16
|
+
}
|
|
17
|
+
interface BlogPostMeta {
|
|
18
|
+
slug: string;
|
|
19
|
+
title: string;
|
|
20
|
+
description: string;
|
|
21
|
+
date: string;
|
|
22
|
+
author: {
|
|
23
|
+
name: string;
|
|
24
|
+
image?: string;
|
|
25
|
+
};
|
|
26
|
+
image?: string;
|
|
27
|
+
tags?: string[];
|
|
28
|
+
readingTime: string;
|
|
29
|
+
}
|
|
30
|
+
interface BlogConfig {
|
|
31
|
+
contentDirectory?: string;
|
|
32
|
+
defaultAuthor?: {
|
|
33
|
+
name: string;
|
|
34
|
+
image?: string;
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
declare function configureBlog(options: BlogConfig): void;
|
|
38
|
+
declare function getAllPosts(): BlogPostMeta[];
|
|
39
|
+
declare function getPostBySlug(slug: string): BlogPost | null;
|
|
40
|
+
declare function getAllPostSlugs(): string[];
|
|
41
|
+
declare function getPostsByTag(tag: string): BlogPostMeta[];
|
|
42
|
+
declare function getAllTags(): string[];
|
|
43
|
+
|
|
44
|
+
interface PostCardProps {
|
|
45
|
+
post: BlogPostMeta;
|
|
46
|
+
basePath?: string;
|
|
47
|
+
className?: string;
|
|
48
|
+
}
|
|
49
|
+
declare function PostCard({ post, basePath, className }: PostCardProps): react_jsx_runtime.JSX.Element;
|
|
50
|
+
|
|
51
|
+
interface PostHeaderProps {
|
|
52
|
+
post: BlogPost;
|
|
53
|
+
className?: string;
|
|
54
|
+
}
|
|
55
|
+
declare function PostHeader({ post, className }: PostHeaderProps): react_jsx_runtime.JSX.Element;
|
|
56
|
+
|
|
57
|
+
export { type BlogConfig as B, PostCard, type PostCardProps, PostHeader, type PostHeaderProps, type BlogPost as a, type BlogPostMeta as b, configureBlog as c, getAllPosts as d, getAllTags as e, getPostBySlug as f, getAllPostSlugs as g, getPostsByTag as h };
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
+
|
|
3
|
+
interface BlogPost {
|
|
4
|
+
slug: string;
|
|
5
|
+
title: string;
|
|
6
|
+
description: string;
|
|
7
|
+
date: string;
|
|
8
|
+
author: {
|
|
9
|
+
name: string;
|
|
10
|
+
image?: string;
|
|
11
|
+
};
|
|
12
|
+
image?: string;
|
|
13
|
+
tags?: string[];
|
|
14
|
+
content: string;
|
|
15
|
+
readingTime: string;
|
|
16
|
+
}
|
|
17
|
+
interface BlogPostMeta {
|
|
18
|
+
slug: string;
|
|
19
|
+
title: string;
|
|
20
|
+
description: string;
|
|
21
|
+
date: string;
|
|
22
|
+
author: {
|
|
23
|
+
name: string;
|
|
24
|
+
image?: string;
|
|
25
|
+
};
|
|
26
|
+
image?: string;
|
|
27
|
+
tags?: string[];
|
|
28
|
+
readingTime: string;
|
|
29
|
+
}
|
|
30
|
+
interface BlogConfig {
|
|
31
|
+
contentDirectory?: string;
|
|
32
|
+
defaultAuthor?: {
|
|
33
|
+
name: string;
|
|
34
|
+
image?: string;
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
declare function configureBlog(options: BlogConfig): void;
|
|
38
|
+
declare function getAllPosts(): BlogPostMeta[];
|
|
39
|
+
declare function getPostBySlug(slug: string): BlogPost | null;
|
|
40
|
+
declare function getAllPostSlugs(): string[];
|
|
41
|
+
declare function getPostsByTag(tag: string): BlogPostMeta[];
|
|
42
|
+
declare function getAllTags(): string[];
|
|
43
|
+
|
|
44
|
+
interface PostCardProps {
|
|
45
|
+
post: BlogPostMeta;
|
|
46
|
+
basePath?: string;
|
|
47
|
+
className?: string;
|
|
48
|
+
}
|
|
49
|
+
declare function PostCard({ post, basePath, className }: PostCardProps): react_jsx_runtime.JSX.Element;
|
|
50
|
+
|
|
51
|
+
interface PostHeaderProps {
|
|
52
|
+
post: BlogPost;
|
|
53
|
+
className?: string;
|
|
54
|
+
}
|
|
55
|
+
declare function PostHeader({ post, className }: PostHeaderProps): react_jsx_runtime.JSX.Element;
|
|
56
|
+
|
|
57
|
+
export { type BlogConfig as B, PostCard, type PostCardProps, PostHeader, type PostHeaderProps, type BlogPost as a, type BlogPostMeta as b, configureBlog as c, getAllPosts as d, getAllTags as e, getPostBySlug as f, getAllPostSlugs as g, getPostsByTag as h };
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
"use strict";
|
|
3
|
+
var __create = Object.create;
|
|
4
|
+
var __defProp = Object.defineProperty;
|
|
5
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
6
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
8
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
9
|
+
var __export = (target, all) => {
|
|
10
|
+
for (var name in all)
|
|
11
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
12
|
+
};
|
|
13
|
+
var __copyProps = (to, from, except, desc) => {
|
|
14
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
15
|
+
for (let key of __getOwnPropNames(from))
|
|
16
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
17
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
18
|
+
}
|
|
19
|
+
return to;
|
|
20
|
+
};
|
|
21
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
22
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
23
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
24
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
25
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
26
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
27
|
+
mod
|
|
28
|
+
));
|
|
29
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
30
|
+
|
|
31
|
+
// src/components/index.ts
|
|
32
|
+
var components_exports = {};
|
|
33
|
+
__export(components_exports, {
|
|
34
|
+
PostCard: () => PostCard,
|
|
35
|
+
PostHeader: () => PostHeader
|
|
36
|
+
});
|
|
37
|
+
module.exports = __toCommonJS(components_exports);
|
|
38
|
+
|
|
39
|
+
// src/components/post-card.tsx
|
|
40
|
+
var import_link = __toESM(require("next/link"));
|
|
41
|
+
var import_image = __toESM(require("next/image"));
|
|
42
|
+
var import_date_fns = require("date-fns");
|
|
43
|
+
var import_jsx_runtime = require("react/jsx-runtime");
|
|
44
|
+
function PostCard({ post, basePath = "/blog", className }) {
|
|
45
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("article", { className: `group relative flex flex-col space-y-2 ${className}`, children: [
|
|
46
|
+
post.image && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "relative aspect-video overflow-hidden rounded-lg", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
47
|
+
import_image.default,
|
|
48
|
+
{
|
|
49
|
+
src: post.image,
|
|
50
|
+
alt: post.title,
|
|
51
|
+
fill: true,
|
|
52
|
+
className: "object-cover transition-transform group-hover:scale-105"
|
|
53
|
+
}
|
|
54
|
+
) }),
|
|
55
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "flex items-center gap-2", children: post.tags?.slice(0, 2).map((tag) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
56
|
+
"span",
|
|
57
|
+
{
|
|
58
|
+
className: "inline-flex items-center rounded-full bg-secondary px-2.5 py-0.5 text-xs font-medium text-secondary-foreground",
|
|
59
|
+
children: tag
|
|
60
|
+
},
|
|
61
|
+
tag
|
|
62
|
+
)) }),
|
|
63
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("h2", { className: "text-2xl font-semibold tracking-tight", children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_link.default, { href: `${basePath}/${post.slug}`, children: [
|
|
64
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "absolute inset-0" }),
|
|
65
|
+
post.title
|
|
66
|
+
] }) }),
|
|
67
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "line-clamp-2 text-muted-foreground", children: post.description }),
|
|
68
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex items-center gap-4 text-sm text-muted-foreground", children: [
|
|
69
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: (0, import_date_fns.format)(new Date(post.date), "MMM d, yyyy") }),
|
|
70
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: post.readingTime })
|
|
71
|
+
] })
|
|
72
|
+
] });
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// src/components/post-header.tsx
|
|
76
|
+
var import_image2 = __toESM(require("next/image"));
|
|
77
|
+
var import_date_fns2 = require("date-fns");
|
|
78
|
+
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
79
|
+
function PostHeader({ post, className }) {
|
|
80
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("header", { className: `space-y-4 ${className}`, children: [
|
|
81
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "flex items-center gap-2", children: post.tags?.map((tag) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
82
|
+
"span",
|
|
83
|
+
{
|
|
84
|
+
className: "inline-flex items-center rounded-full bg-secondary px-2.5 py-0.5 text-xs font-medium text-secondary-foreground",
|
|
85
|
+
children: tag
|
|
86
|
+
},
|
|
87
|
+
tag
|
|
88
|
+
)) }),
|
|
89
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("h1", { className: "text-4xl font-bold tracking-tight lg:text-5xl", children: post.title }),
|
|
90
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "text-xl text-muted-foreground", children: post.description }),
|
|
91
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex items-center gap-4", children: [
|
|
92
|
+
post.author.image && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
93
|
+
import_image2.default,
|
|
94
|
+
{
|
|
95
|
+
src: post.author.image,
|
|
96
|
+
alt: post.author.name,
|
|
97
|
+
width: 40,
|
|
98
|
+
height: 40,
|
|
99
|
+
className: "rounded-full"
|
|
100
|
+
}
|
|
101
|
+
),
|
|
102
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { children: [
|
|
103
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "font-medium", children: post.author.name }),
|
|
104
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("p", { className: "text-sm text-muted-foreground", children: [
|
|
105
|
+
(0, import_date_fns2.format)(new Date(post.date), "MMMM d, yyyy"),
|
|
106
|
+
" \xB7 ",
|
|
107
|
+
post.readingTime
|
|
108
|
+
] })
|
|
109
|
+
] })
|
|
110
|
+
] }),
|
|
111
|
+
post.image && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "relative aspect-video overflow-hidden rounded-lg", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
112
|
+
import_image2.default,
|
|
113
|
+
{
|
|
114
|
+
src: post.image,
|
|
115
|
+
alt: post.title,
|
|
116
|
+
fill: true,
|
|
117
|
+
className: "object-cover",
|
|
118
|
+
priority: true
|
|
119
|
+
}
|
|
120
|
+
) })
|
|
121
|
+
] });
|
|
122
|
+
}
|
|
123
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
124
|
+
0 && (module.exports = {
|
|
125
|
+
PostCard,
|
|
126
|
+
PostHeader
|
|
127
|
+
});
|
|
128
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/components/index.ts","../../src/components/post-card.tsx","../../src/components/post-header.tsx"],"sourcesContent":["export { PostCard } from \"./post-card\";\nexport type { PostCardProps } from \"./post-card\";\n\nexport { PostHeader } from \"./post-header\";\nexport type { PostHeaderProps } from \"./post-header\";\n","\"use client\";\n\nimport Link from \"next/link\";\nimport Image from \"next/image\";\nimport { format } from \"date-fns\";\nimport type { BlogPostMeta } from \"../lib/blog\";\n\nexport interface PostCardProps {\n post: BlogPostMeta;\n basePath?: string;\n className?: string;\n}\n\nexport function PostCard({ post, basePath = \"/blog\", className }: PostCardProps) {\n return (\n <article className={`group relative flex flex-col space-y-2 ${className}`}>\n {post.image && (\n <div className=\"relative aspect-video overflow-hidden rounded-lg\">\n <Image\n src={post.image}\n alt={post.title}\n fill\n className=\"object-cover transition-transform group-hover:scale-105\"\n />\n </div>\n )}\n <div className=\"flex items-center gap-2\">\n {post.tags?.slice(0, 2).map((tag) => (\n <span\n key={tag}\n className=\"inline-flex items-center rounded-full bg-secondary px-2.5 py-0.5 text-xs font-medium text-secondary-foreground\"\n >\n {tag}\n </span>\n ))}\n </div>\n <h2 className=\"text-2xl font-semibold tracking-tight\">\n <Link href={`${basePath}/${post.slug}`}>\n <span className=\"absolute inset-0\" />\n {post.title}\n </Link>\n </h2>\n <p className=\"line-clamp-2 text-muted-foreground\">{post.description}</p>\n <div className=\"flex items-center gap-4 text-sm text-muted-foreground\">\n <span>{format(new Date(post.date), \"MMM d, yyyy\")}</span>\n <span>{post.readingTime}</span>\n </div>\n </article>\n );\n}\n","\"use client\";\n\nimport Image from \"next/image\";\nimport { format } from \"date-fns\";\nimport type { BlogPost } from \"../lib/blog\";\n\nexport interface PostHeaderProps {\n post: BlogPost;\n className?: string;\n}\n\nexport function PostHeader({ post, className }: PostHeaderProps) {\n return (\n <header className={`space-y-4 ${className}`}>\n <div className=\"flex items-center gap-2\">\n {post.tags?.map((tag) => (\n <span\n key={tag}\n className=\"inline-flex items-center rounded-full bg-secondary px-2.5 py-0.5 text-xs font-medium text-secondary-foreground\"\n >\n {tag}\n </span>\n ))}\n </div>\n <h1 className=\"text-4xl font-bold tracking-tight lg:text-5xl\">\n {post.title}\n </h1>\n <p className=\"text-xl text-muted-foreground\">{post.description}</p>\n <div className=\"flex items-center gap-4\">\n {post.author.image && (\n <Image\n src={post.author.image}\n alt={post.author.name}\n width={40}\n height={40}\n className=\"rounded-full\"\n />\n )}\n <div>\n <p className=\"font-medium\">{post.author.name}</p>\n <p className=\"text-sm text-muted-foreground\">\n {format(new Date(post.date), \"MMMM d, yyyy\")} · {post.readingTime}\n </p>\n </div>\n </div>\n {post.image && (\n <div className=\"relative aspect-video overflow-hidden rounded-lg\">\n <Image\n src={post.image}\n alt={post.title}\n fill\n className=\"object-cover\"\n priority\n />\n </div>\n )}\n </header>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACEA,kBAAiB;AACjB,mBAAkB;AAClB,sBAAuB;AAcb;AALH,SAAS,SAAS,EAAE,MAAM,WAAW,SAAS,UAAU,GAAkB;AAC/E,SACE,6CAAC,aAAQ,WAAW,0CAA0C,SAAS,IACpE;AAAA,SAAK,SACJ,4CAAC,SAAI,WAAU,oDACb;AAAA,MAAC,aAAAA;AAAA,MAAA;AAAA,QACC,KAAK,KAAK;AAAA,QACV,KAAK,KAAK;AAAA,QACV,MAAI;AAAA,QACJ,WAAU;AAAA;AAAA,IACZ,GACF;AAAA,IAEF,4CAAC,SAAI,WAAU,2BACZ,eAAK,MAAM,MAAM,GAAG,CAAC,EAAE,IAAI,CAAC,QAC3B;AAAA,MAAC;AAAA;AAAA,QAEC,WAAU;AAAA,QAET;AAAA;AAAA,MAHI;AAAA,IAIP,CACD,GACH;AAAA,IACA,4CAAC,QAAG,WAAU,yCACZ,uDAAC,YAAAC,SAAA,EAAK,MAAM,GAAG,QAAQ,IAAI,KAAK,IAAI,IAClC;AAAA,kDAAC,UAAK,WAAU,oBAAmB;AAAA,MAClC,KAAK;AAAA,OACR,GACF;AAAA,IACA,4CAAC,OAAE,WAAU,sCAAsC,eAAK,aAAY;AAAA,IACpE,6CAAC,SAAI,WAAU,yDACb;AAAA,kDAAC,UAAM,sCAAO,IAAI,KAAK,KAAK,IAAI,GAAG,aAAa,GAAE;AAAA,MAClD,4CAAC,UAAM,eAAK,aAAY;AAAA,OAC1B;AAAA,KACF;AAEJ;;;AC/CA,IAAAC,gBAAkB;AAClB,IAAAC,mBAAuB;AAab,IAAAC,sBAAA;AALH,SAAS,WAAW,EAAE,MAAM,UAAU,GAAoB;AAC/D,SACE,8CAAC,YAAO,WAAW,aAAa,SAAS,IACvC;AAAA,iDAAC,SAAI,WAAU,2BACZ,eAAK,MAAM,IAAI,CAAC,QACf;AAAA,MAAC;AAAA;AAAA,QAEC,WAAU;AAAA,QAET;AAAA;AAAA,MAHI;AAAA,IAIP,CACD,GACH;AAAA,IACA,6CAAC,QAAG,WAAU,iDACX,eAAK,OACR;AAAA,IACA,6CAAC,OAAE,WAAU,iCAAiC,eAAK,aAAY;AAAA,IAC/D,8CAAC,SAAI,WAAU,2BACZ;AAAA,WAAK,OAAO,SACX;AAAA,QAAC,cAAAC;AAAA,QAAA;AAAA,UACC,KAAK,KAAK,OAAO;AAAA,UACjB,KAAK,KAAK,OAAO;AAAA,UACjB,OAAO;AAAA,UACP,QAAQ;AAAA,UACR,WAAU;AAAA;AAAA,MACZ;AAAA,MAEF,8CAAC,SACC;AAAA,qDAAC,OAAE,WAAU,eAAe,eAAK,OAAO,MAAK;AAAA,QAC7C,8CAAC,OAAE,WAAU,iCACV;AAAA,uCAAO,IAAI,KAAK,KAAK,IAAI,GAAG,cAAc;AAAA,UAAE;AAAA,UAAI,KAAK;AAAA,WACxD;AAAA,SACF;AAAA,OACF;AAAA,IACC,KAAK,SACJ,6CAAC,SAAI,WAAU,oDACb;AAAA,MAAC,cAAAA;AAAA,MAAA;AAAA,QACC,KAAK,KAAK;AAAA,QACV,KAAK,KAAK;AAAA,QACV,MAAI;AAAA,QACJ,WAAU;AAAA,QACV,UAAQ;AAAA;AAAA,IACV,GACF;AAAA,KAEJ;AAEJ;","names":["Image","Link","import_image","import_date_fns","import_jsx_runtime","Image"]}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
// src/components/post-card.tsx
|
|
4
|
+
import Link from "next/link";
|
|
5
|
+
import Image from "next/image";
|
|
6
|
+
import { format } from "date-fns";
|
|
7
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
8
|
+
function PostCard({ post, basePath = "/blog", className }) {
|
|
9
|
+
return /* @__PURE__ */ jsxs("article", { className: `group relative flex flex-col space-y-2 ${className}`, children: [
|
|
10
|
+
post.image && /* @__PURE__ */ jsx("div", { className: "relative aspect-video overflow-hidden rounded-lg", children: /* @__PURE__ */ jsx(
|
|
11
|
+
Image,
|
|
12
|
+
{
|
|
13
|
+
src: post.image,
|
|
14
|
+
alt: post.title,
|
|
15
|
+
fill: true,
|
|
16
|
+
className: "object-cover transition-transform group-hover:scale-105"
|
|
17
|
+
}
|
|
18
|
+
) }),
|
|
19
|
+
/* @__PURE__ */ jsx("div", { className: "flex items-center gap-2", children: post.tags?.slice(0, 2).map((tag) => /* @__PURE__ */ jsx(
|
|
20
|
+
"span",
|
|
21
|
+
{
|
|
22
|
+
className: "inline-flex items-center rounded-full bg-secondary px-2.5 py-0.5 text-xs font-medium text-secondary-foreground",
|
|
23
|
+
children: tag
|
|
24
|
+
},
|
|
25
|
+
tag
|
|
26
|
+
)) }),
|
|
27
|
+
/* @__PURE__ */ jsx("h2", { className: "text-2xl font-semibold tracking-tight", children: /* @__PURE__ */ jsxs(Link, { href: `${basePath}/${post.slug}`, children: [
|
|
28
|
+
/* @__PURE__ */ jsx("span", { className: "absolute inset-0" }),
|
|
29
|
+
post.title
|
|
30
|
+
] }) }),
|
|
31
|
+
/* @__PURE__ */ jsx("p", { className: "line-clamp-2 text-muted-foreground", children: post.description }),
|
|
32
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-4 text-sm text-muted-foreground", children: [
|
|
33
|
+
/* @__PURE__ */ jsx("span", { children: format(new Date(post.date), "MMM d, yyyy") }),
|
|
34
|
+
/* @__PURE__ */ jsx("span", { children: post.readingTime })
|
|
35
|
+
] })
|
|
36
|
+
] });
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// src/components/post-header.tsx
|
|
40
|
+
import Image2 from "next/image";
|
|
41
|
+
import { format as format2 } from "date-fns";
|
|
42
|
+
import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
43
|
+
function PostHeader({ post, className }) {
|
|
44
|
+
return /* @__PURE__ */ jsxs2("header", { className: `space-y-4 ${className}`, children: [
|
|
45
|
+
/* @__PURE__ */ jsx2("div", { className: "flex items-center gap-2", children: post.tags?.map((tag) => /* @__PURE__ */ jsx2(
|
|
46
|
+
"span",
|
|
47
|
+
{
|
|
48
|
+
className: "inline-flex items-center rounded-full bg-secondary px-2.5 py-0.5 text-xs font-medium text-secondary-foreground",
|
|
49
|
+
children: tag
|
|
50
|
+
},
|
|
51
|
+
tag
|
|
52
|
+
)) }),
|
|
53
|
+
/* @__PURE__ */ jsx2("h1", { className: "text-4xl font-bold tracking-tight lg:text-5xl", children: post.title }),
|
|
54
|
+
/* @__PURE__ */ jsx2("p", { className: "text-xl text-muted-foreground", children: post.description }),
|
|
55
|
+
/* @__PURE__ */ jsxs2("div", { className: "flex items-center gap-4", children: [
|
|
56
|
+
post.author.image && /* @__PURE__ */ jsx2(
|
|
57
|
+
Image2,
|
|
58
|
+
{
|
|
59
|
+
src: post.author.image,
|
|
60
|
+
alt: post.author.name,
|
|
61
|
+
width: 40,
|
|
62
|
+
height: 40,
|
|
63
|
+
className: "rounded-full"
|
|
64
|
+
}
|
|
65
|
+
),
|
|
66
|
+
/* @__PURE__ */ jsxs2("div", { children: [
|
|
67
|
+
/* @__PURE__ */ jsx2("p", { className: "font-medium", children: post.author.name }),
|
|
68
|
+
/* @__PURE__ */ jsxs2("p", { className: "text-sm text-muted-foreground", children: [
|
|
69
|
+
format2(new Date(post.date), "MMMM d, yyyy"),
|
|
70
|
+
" \xB7 ",
|
|
71
|
+
post.readingTime
|
|
72
|
+
] })
|
|
73
|
+
] })
|
|
74
|
+
] }),
|
|
75
|
+
post.image && /* @__PURE__ */ jsx2("div", { className: "relative aspect-video overflow-hidden rounded-lg", children: /* @__PURE__ */ jsx2(
|
|
76
|
+
Image2,
|
|
77
|
+
{
|
|
78
|
+
src: post.image,
|
|
79
|
+
alt: post.title,
|
|
80
|
+
fill: true,
|
|
81
|
+
className: "object-cover",
|
|
82
|
+
priority: true
|
|
83
|
+
}
|
|
84
|
+
) })
|
|
85
|
+
] });
|
|
86
|
+
}
|
|
87
|
+
export {
|
|
88
|
+
PostCard,
|
|
89
|
+
PostHeader
|
|
90
|
+
};
|
|
91
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/components/post-card.tsx","../../src/components/post-header.tsx"],"sourcesContent":["\"use client\";\n\nimport Link from \"next/link\";\nimport Image from \"next/image\";\nimport { format } from \"date-fns\";\nimport type { BlogPostMeta } from \"../lib/blog\";\n\nexport interface PostCardProps {\n post: BlogPostMeta;\n basePath?: string;\n className?: string;\n}\n\nexport function PostCard({ post, basePath = \"/blog\", className }: PostCardProps) {\n return (\n <article className={`group relative flex flex-col space-y-2 ${className}`}>\n {post.image && (\n <div className=\"relative aspect-video overflow-hidden rounded-lg\">\n <Image\n src={post.image}\n alt={post.title}\n fill\n className=\"object-cover transition-transform group-hover:scale-105\"\n />\n </div>\n )}\n <div className=\"flex items-center gap-2\">\n {post.tags?.slice(0, 2).map((tag) => (\n <span\n key={tag}\n className=\"inline-flex items-center rounded-full bg-secondary px-2.5 py-0.5 text-xs font-medium text-secondary-foreground\"\n >\n {tag}\n </span>\n ))}\n </div>\n <h2 className=\"text-2xl font-semibold tracking-tight\">\n <Link href={`${basePath}/${post.slug}`}>\n <span className=\"absolute inset-0\" />\n {post.title}\n </Link>\n </h2>\n <p className=\"line-clamp-2 text-muted-foreground\">{post.description}</p>\n <div className=\"flex items-center gap-4 text-sm text-muted-foreground\">\n <span>{format(new Date(post.date), \"MMM d, yyyy\")}</span>\n <span>{post.readingTime}</span>\n </div>\n </article>\n );\n}\n","\"use client\";\n\nimport Image from \"next/image\";\nimport { format } from \"date-fns\";\nimport type { BlogPost } from \"../lib/blog\";\n\nexport interface PostHeaderProps {\n post: BlogPost;\n className?: string;\n}\n\nexport function PostHeader({ post, className }: PostHeaderProps) {\n return (\n <header className={`space-y-4 ${className}`}>\n <div className=\"flex items-center gap-2\">\n {post.tags?.map((tag) => (\n <span\n key={tag}\n className=\"inline-flex items-center rounded-full bg-secondary px-2.5 py-0.5 text-xs font-medium text-secondary-foreground\"\n >\n {tag}\n </span>\n ))}\n </div>\n <h1 className=\"text-4xl font-bold tracking-tight lg:text-5xl\">\n {post.title}\n </h1>\n <p className=\"text-xl text-muted-foreground\">{post.description}</p>\n <div className=\"flex items-center gap-4\">\n {post.author.image && (\n <Image\n src={post.author.image}\n alt={post.author.name}\n width={40}\n height={40}\n className=\"rounded-full\"\n />\n )}\n <div>\n <p className=\"font-medium\">{post.author.name}</p>\n <p className=\"text-sm text-muted-foreground\">\n {format(new Date(post.date), \"MMMM d, yyyy\")} · {post.readingTime}\n </p>\n </div>\n </div>\n {post.image && (\n <div className=\"relative aspect-video overflow-hidden rounded-lg\">\n <Image\n src={post.image}\n alt={post.title}\n fill\n className=\"object-cover\"\n priority\n />\n </div>\n )}\n </header>\n );\n}\n"],"mappings":";;;AAEA,OAAO,UAAU;AACjB,OAAO,WAAW;AAClB,SAAS,cAAc;AAcb,cAmBF,YAnBE;AALH,SAAS,SAAS,EAAE,MAAM,WAAW,SAAS,UAAU,GAAkB;AAC/E,SACE,qBAAC,aAAQ,WAAW,0CAA0C,SAAS,IACpE;AAAA,SAAK,SACJ,oBAAC,SAAI,WAAU,oDACb;AAAA,MAAC;AAAA;AAAA,QACC,KAAK,KAAK;AAAA,QACV,KAAK,KAAK;AAAA,QACV,MAAI;AAAA,QACJ,WAAU;AAAA;AAAA,IACZ,GACF;AAAA,IAEF,oBAAC,SAAI,WAAU,2BACZ,eAAK,MAAM,MAAM,GAAG,CAAC,EAAE,IAAI,CAAC,QAC3B;AAAA,MAAC;AAAA;AAAA,QAEC,WAAU;AAAA,QAET;AAAA;AAAA,MAHI;AAAA,IAIP,CACD,GACH;AAAA,IACA,oBAAC,QAAG,WAAU,yCACZ,+BAAC,QAAK,MAAM,GAAG,QAAQ,IAAI,KAAK,IAAI,IAClC;AAAA,0BAAC,UAAK,WAAU,oBAAmB;AAAA,MAClC,KAAK;AAAA,OACR,GACF;AAAA,IACA,oBAAC,OAAE,WAAU,sCAAsC,eAAK,aAAY;AAAA,IACpE,qBAAC,SAAI,WAAU,yDACb;AAAA,0BAAC,UAAM,iBAAO,IAAI,KAAK,KAAK,IAAI,GAAG,aAAa,GAAE;AAAA,MAClD,oBAAC,UAAM,eAAK,aAAY;AAAA,OAC1B;AAAA,KACF;AAEJ;;;AC/CA,OAAOA,YAAW;AAClB,SAAS,UAAAC,eAAc;AAab,gBAAAC,MAwBA,QAAAC,aAxBA;AALH,SAAS,WAAW,EAAE,MAAM,UAAU,GAAoB;AAC/D,SACE,gBAAAA,MAAC,YAAO,WAAW,aAAa,SAAS,IACvC;AAAA,oBAAAD,KAAC,SAAI,WAAU,2BACZ,eAAK,MAAM,IAAI,CAAC,QACf,gBAAAA;AAAA,MAAC;AAAA;AAAA,QAEC,WAAU;AAAA,QAET;AAAA;AAAA,MAHI;AAAA,IAIP,CACD,GACH;AAAA,IACA,gBAAAA,KAAC,QAAG,WAAU,iDACX,eAAK,OACR;AAAA,IACA,gBAAAA,KAAC,OAAE,WAAU,iCAAiC,eAAK,aAAY;AAAA,IAC/D,gBAAAC,MAAC,SAAI,WAAU,2BACZ;AAAA,WAAK,OAAO,SACX,gBAAAD;AAAA,QAACF;AAAA,QAAA;AAAA,UACC,KAAK,KAAK,OAAO;AAAA,UACjB,KAAK,KAAK,OAAO;AAAA,UACjB,OAAO;AAAA,UACP,QAAQ;AAAA,UACR,WAAU;AAAA;AAAA,MACZ;AAAA,MAEF,gBAAAG,MAAC,SACC;AAAA,wBAAAD,KAAC,OAAE,WAAU,eAAe,eAAK,OAAO,MAAK;AAAA,QAC7C,gBAAAC,MAAC,OAAE,WAAU,iCACV;AAAA,UAAAF,QAAO,IAAI,KAAK,KAAK,IAAI,GAAG,cAAc;AAAA,UAAE;AAAA,UAAI,KAAK;AAAA,WACxD;AAAA,SACF;AAAA,OACF;AAAA,IACC,KAAK,SACJ,gBAAAC,KAAC,SAAI,WAAU,oDACb,0BAAAA;AAAA,MAACF;AAAA,MAAA;AAAA,QACC,KAAK,KAAK;AAAA,QACV,KAAK,KAAK;AAAA,QACV,MAAI;AAAA,QACJ,WAAU;AAAA,QACV,UAAQ;AAAA;AAAA,IACV,GACF;AAAA,KAEJ;AAEJ;","names":["Image","format","jsx","jsxs"]}
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
export { B as BlogConfig, a as BlogPost, b as BlogPostMeta, PostCard, PostCardProps, PostHeader, PostHeaderProps, c as configureBlog, g as getAllPostSlugs, d as getAllPosts, e as getAllTags, f as getPostBySlug, h as getPostsByTag } from './components/index.mjs';
|
|
2
|
+
import 'react/jsx-runtime';
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
export { B as BlogConfig, a as BlogPost, b as BlogPostMeta, PostCard, PostCardProps, PostHeader, PostHeaderProps, c as configureBlog, g as getAllPostSlugs, d as getAllPosts, e as getAllTags, f as getPostBySlug, h as getPostsByTag } from './components/index.js';
|
|
2
|
+
import 'react/jsx-runtime';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
"use strict";
|
|
3
|
+
var __create = Object.create;
|
|
4
|
+
var __defProp = Object.defineProperty;
|
|
5
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
6
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
8
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
9
|
+
var __export = (target, all) => {
|
|
10
|
+
for (var name in all)
|
|
11
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
12
|
+
};
|
|
13
|
+
var __copyProps = (to, from, except, desc) => {
|
|
14
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
15
|
+
for (let key of __getOwnPropNames(from))
|
|
16
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
17
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
18
|
+
}
|
|
19
|
+
return to;
|
|
20
|
+
};
|
|
21
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
22
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
23
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
24
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
25
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
26
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
27
|
+
mod
|
|
28
|
+
));
|
|
29
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
30
|
+
|
|
31
|
+
// src/index.ts
|
|
32
|
+
var src_exports = {};
|
|
33
|
+
__export(src_exports, {
|
|
34
|
+
PostCard: () => PostCard,
|
|
35
|
+
PostHeader: () => PostHeader,
|
|
36
|
+
configureBlog: () => configureBlog,
|
|
37
|
+
getAllPostSlugs: () => getAllPostSlugs,
|
|
38
|
+
getAllPosts: () => getAllPosts,
|
|
39
|
+
getAllTags: () => getAllTags,
|
|
40
|
+
getPostBySlug: () => getPostBySlug,
|
|
41
|
+
getPostsByTag: () => getPostsByTag
|
|
42
|
+
});
|
|
43
|
+
module.exports = __toCommonJS(src_exports);
|
|
44
|
+
|
|
45
|
+
// src/lib/blog.ts
|
|
46
|
+
var import_fs = __toESM(require("fs"));
|
|
47
|
+
var import_path = __toESM(require("path"));
|
|
48
|
+
var import_gray_matter = __toESM(require("gray-matter"));
|
|
49
|
+
var import_reading_time = __toESM(require("reading-time"));
|
|
50
|
+
var config = {
|
|
51
|
+
contentDirectory: import_path.default.join(process.cwd(), "content/blog"),
|
|
52
|
+
defaultAuthor: { name: "Anonymous" }
|
|
53
|
+
};
|
|
54
|
+
function configureBlog(options) {
|
|
55
|
+
config = { ...config, ...options };
|
|
56
|
+
}
|
|
57
|
+
function getContentDirectory() {
|
|
58
|
+
return config.contentDirectory || import_path.default.join(process.cwd(), "content/blog");
|
|
59
|
+
}
|
|
60
|
+
function getAllPosts() {
|
|
61
|
+
const contentDirectory = getContentDirectory();
|
|
62
|
+
if (!import_fs.default.existsSync(contentDirectory)) {
|
|
63
|
+
return [];
|
|
64
|
+
}
|
|
65
|
+
const files = import_fs.default.readdirSync(contentDirectory);
|
|
66
|
+
const posts = files.filter((file) => file.endsWith(".mdx") || file.endsWith(".md")).map((file) => {
|
|
67
|
+
const slug = file.replace(/\.mdx?$/, "");
|
|
68
|
+
const fullPath = import_path.default.join(contentDirectory, file);
|
|
69
|
+
const fileContents = import_fs.default.readFileSync(fullPath, "utf8");
|
|
70
|
+
const { data, content } = (0, import_gray_matter.default)(fileContents);
|
|
71
|
+
return {
|
|
72
|
+
slug,
|
|
73
|
+
title: data.title || slug,
|
|
74
|
+
description: data.description || "",
|
|
75
|
+
date: data.date || (/* @__PURE__ */ new Date()).toISOString(),
|
|
76
|
+
author: data.author || config.defaultAuthor || { name: "Anonymous" },
|
|
77
|
+
image: data.image,
|
|
78
|
+
tags: data.tags || [],
|
|
79
|
+
readingTime: (0, import_reading_time.default)(content).text
|
|
80
|
+
};
|
|
81
|
+
}).sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime());
|
|
82
|
+
return posts;
|
|
83
|
+
}
|
|
84
|
+
function getPostBySlug(slug) {
|
|
85
|
+
const contentDirectory = getContentDirectory();
|
|
86
|
+
let fullPath = import_path.default.join(contentDirectory, `${slug}.mdx`);
|
|
87
|
+
if (!import_fs.default.existsSync(fullPath)) {
|
|
88
|
+
fullPath = import_path.default.join(contentDirectory, `${slug}.md`);
|
|
89
|
+
}
|
|
90
|
+
if (!import_fs.default.existsSync(fullPath)) {
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
|
+
const fileContents = import_fs.default.readFileSync(fullPath, "utf8");
|
|
94
|
+
const { data, content } = (0, import_gray_matter.default)(fileContents);
|
|
95
|
+
return {
|
|
96
|
+
slug,
|
|
97
|
+
title: data.title || slug,
|
|
98
|
+
description: data.description || "",
|
|
99
|
+
date: data.date || (/* @__PURE__ */ new Date()).toISOString(),
|
|
100
|
+
author: data.author || config.defaultAuthor || { name: "Anonymous" },
|
|
101
|
+
image: data.image,
|
|
102
|
+
tags: data.tags || [],
|
|
103
|
+
content,
|
|
104
|
+
readingTime: (0, import_reading_time.default)(content).text
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
function getAllPostSlugs() {
|
|
108
|
+
const contentDirectory = getContentDirectory();
|
|
109
|
+
if (!import_fs.default.existsSync(contentDirectory)) {
|
|
110
|
+
return [];
|
|
111
|
+
}
|
|
112
|
+
const files = import_fs.default.readdirSync(contentDirectory);
|
|
113
|
+
return files.filter((file) => file.endsWith(".mdx") || file.endsWith(".md")).map((file) => file.replace(/\.mdx?$/, ""));
|
|
114
|
+
}
|
|
115
|
+
function getPostsByTag(tag) {
|
|
116
|
+
return getAllPosts().filter(
|
|
117
|
+
(post) => post.tags?.map((t) => t.toLowerCase()).includes(tag.toLowerCase())
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
function getAllTags() {
|
|
121
|
+
const posts = getAllPosts();
|
|
122
|
+
const tags = /* @__PURE__ */ new Set();
|
|
123
|
+
posts.forEach((post) => {
|
|
124
|
+
post.tags?.forEach((tag) => tags.add(tag));
|
|
125
|
+
});
|
|
126
|
+
return Array.from(tags);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// src/components/post-card.tsx
|
|
130
|
+
var import_link = __toESM(require("next/link"));
|
|
131
|
+
var import_image = __toESM(require("next/image"));
|
|
132
|
+
var import_date_fns = require("date-fns");
|
|
133
|
+
var import_jsx_runtime = require("react/jsx-runtime");
|
|
134
|
+
function PostCard({ post, basePath = "/blog", className }) {
|
|
135
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("article", { className: `group relative flex flex-col space-y-2 ${className}`, children: [
|
|
136
|
+
post.image && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "relative aspect-video overflow-hidden rounded-lg", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
137
|
+
import_image.default,
|
|
138
|
+
{
|
|
139
|
+
src: post.image,
|
|
140
|
+
alt: post.title,
|
|
141
|
+
fill: true,
|
|
142
|
+
className: "object-cover transition-transform group-hover:scale-105"
|
|
143
|
+
}
|
|
144
|
+
) }),
|
|
145
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "flex items-center gap-2", children: post.tags?.slice(0, 2).map((tag) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
146
|
+
"span",
|
|
147
|
+
{
|
|
148
|
+
className: "inline-flex items-center rounded-full bg-secondary px-2.5 py-0.5 text-xs font-medium text-secondary-foreground",
|
|
149
|
+
children: tag
|
|
150
|
+
},
|
|
151
|
+
tag
|
|
152
|
+
)) }),
|
|
153
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("h2", { className: "text-2xl font-semibold tracking-tight", children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_link.default, { href: `${basePath}/${post.slug}`, children: [
|
|
154
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "absolute inset-0" }),
|
|
155
|
+
post.title
|
|
156
|
+
] }) }),
|
|
157
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "line-clamp-2 text-muted-foreground", children: post.description }),
|
|
158
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex items-center gap-4 text-sm text-muted-foreground", children: [
|
|
159
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: (0, import_date_fns.format)(new Date(post.date), "MMM d, yyyy") }),
|
|
160
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: post.readingTime })
|
|
161
|
+
] })
|
|
162
|
+
] });
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// src/components/post-header.tsx
|
|
166
|
+
var import_image2 = __toESM(require("next/image"));
|
|
167
|
+
var import_date_fns2 = require("date-fns");
|
|
168
|
+
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
169
|
+
function PostHeader({ post, className }) {
|
|
170
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("header", { className: `space-y-4 ${className}`, children: [
|
|
171
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "flex items-center gap-2", children: post.tags?.map((tag) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
172
|
+
"span",
|
|
173
|
+
{
|
|
174
|
+
className: "inline-flex items-center rounded-full bg-secondary px-2.5 py-0.5 text-xs font-medium text-secondary-foreground",
|
|
175
|
+
children: tag
|
|
176
|
+
},
|
|
177
|
+
tag
|
|
178
|
+
)) }),
|
|
179
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("h1", { className: "text-4xl font-bold tracking-tight lg:text-5xl", children: post.title }),
|
|
180
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "text-xl text-muted-foreground", children: post.description }),
|
|
181
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex items-center gap-4", children: [
|
|
182
|
+
post.author.image && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
183
|
+
import_image2.default,
|
|
184
|
+
{
|
|
185
|
+
src: post.author.image,
|
|
186
|
+
alt: post.author.name,
|
|
187
|
+
width: 40,
|
|
188
|
+
height: 40,
|
|
189
|
+
className: "rounded-full"
|
|
190
|
+
}
|
|
191
|
+
),
|
|
192
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { children: [
|
|
193
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "font-medium", children: post.author.name }),
|
|
194
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("p", { className: "text-sm text-muted-foreground", children: [
|
|
195
|
+
(0, import_date_fns2.format)(new Date(post.date), "MMMM d, yyyy"),
|
|
196
|
+
" \xB7 ",
|
|
197
|
+
post.readingTime
|
|
198
|
+
] })
|
|
199
|
+
] })
|
|
200
|
+
] }),
|
|
201
|
+
post.image && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "relative aspect-video overflow-hidden rounded-lg", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
202
|
+
import_image2.default,
|
|
203
|
+
{
|
|
204
|
+
src: post.image,
|
|
205
|
+
alt: post.title,
|
|
206
|
+
fill: true,
|
|
207
|
+
className: "object-cover",
|
|
208
|
+
priority: true
|
|
209
|
+
}
|
|
210
|
+
) })
|
|
211
|
+
] });
|
|
212
|
+
}
|
|
213
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
214
|
+
0 && (module.exports = {
|
|
215
|
+
PostCard,
|
|
216
|
+
PostHeader,
|
|
217
|
+
configureBlog,
|
|
218
|
+
getAllPostSlugs,
|
|
219
|
+
getAllPosts,
|
|
220
|
+
getAllTags,
|
|
221
|
+
getPostBySlug,
|
|
222
|
+
getPostsByTag
|
|
223
|
+
});
|
|
224
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/lib/blog.ts","../src/components/post-card.tsx","../src/components/post-header.tsx"],"sourcesContent":["// Blog utilities\nexport {\n getAllPosts,\n getPostBySlug,\n getAllPostSlugs,\n getPostsByTag,\n getAllTags,\n configureBlog,\n} from \"./lib/blog\";\nexport type { BlogPost, BlogPostMeta, BlogConfig } from \"./lib/blog\";\n\n// Components\nexport { PostCard, PostHeader } from \"./components\";\nexport type { PostCardProps, PostHeaderProps } from \"./components\";\n","import fs from \"fs\";\nimport path from \"path\";\nimport matter from \"gray-matter\";\nimport readingTime from \"reading-time\";\n\nexport interface BlogPost {\n slug: string;\n title: string;\n description: string;\n date: string;\n author: {\n name: string;\n image?: string;\n };\n image?: string;\n tags?: string[];\n content: string;\n readingTime: string;\n}\n\nexport interface BlogPostMeta {\n slug: string;\n title: string;\n description: string;\n date: string;\n author: {\n name: string;\n image?: string;\n };\n image?: string;\n tags?: string[];\n readingTime: string;\n}\n\nexport interface BlogConfig {\n contentDirectory?: string;\n defaultAuthor?: {\n name: string;\n image?: string;\n };\n}\n\nlet config: BlogConfig = {\n contentDirectory: path.join(process.cwd(), \"content/blog\"),\n defaultAuthor: { name: \"Anonymous\" },\n};\n\nexport function configureBlog(options: BlogConfig) {\n config = { ...config, ...options };\n}\n\nfunction getContentDirectory(): string {\n return config.contentDirectory || path.join(process.cwd(), \"content/blog\");\n}\n\nexport function getAllPosts(): BlogPostMeta[] {\n const contentDirectory = getContentDirectory();\n\n if (!fs.existsSync(contentDirectory)) {\n return [];\n }\n\n const files = fs.readdirSync(contentDirectory);\n const posts = files\n .filter((file) => file.endsWith(\".mdx\") || file.endsWith(\".md\"))\n .map((file) => {\n const slug = file.replace(/\\.mdx?$/, \"\");\n const fullPath = path.join(contentDirectory, file);\n const fileContents = fs.readFileSync(fullPath, \"utf8\");\n const { data, content } = matter(fileContents);\n\n return {\n slug,\n title: data.title || slug,\n description: data.description || \"\",\n date: data.date || new Date().toISOString(),\n author: data.author || config.defaultAuthor || { name: \"Anonymous\" },\n image: data.image,\n tags: data.tags || [],\n readingTime: readingTime(content).text,\n };\n })\n .sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime());\n\n return posts;\n}\n\nexport function getPostBySlug(slug: string): BlogPost | null {\n const contentDirectory = getContentDirectory();\n\n // Try both .mdx and .md extensions\n let fullPath = path.join(contentDirectory, `${slug}.mdx`);\n if (!fs.existsSync(fullPath)) {\n fullPath = path.join(contentDirectory, `${slug}.md`);\n }\n\n if (!fs.existsSync(fullPath)) {\n return null;\n }\n\n const fileContents = fs.readFileSync(fullPath, \"utf8\");\n const { data, content } = matter(fileContents);\n\n return {\n slug,\n title: data.title || slug,\n description: data.description || \"\",\n date: data.date || new Date().toISOString(),\n author: data.author || config.defaultAuthor || { name: \"Anonymous\" },\n image: data.image,\n tags: data.tags || [],\n content,\n readingTime: readingTime(content).text,\n };\n}\n\nexport function getAllPostSlugs(): string[] {\n const contentDirectory = getContentDirectory();\n\n if (!fs.existsSync(contentDirectory)) {\n return [];\n }\n\n const files = fs.readdirSync(contentDirectory);\n return files\n .filter((file) => file.endsWith(\".mdx\") || file.endsWith(\".md\"))\n .map((file) => file.replace(/\\.mdx?$/, \"\"));\n}\n\nexport function getPostsByTag(tag: string): BlogPostMeta[] {\n return getAllPosts().filter((post) =>\n post.tags?.map((t) => t.toLowerCase()).includes(tag.toLowerCase())\n );\n}\n\nexport function getAllTags(): string[] {\n const posts = getAllPosts();\n const tags = new Set<string>();\n posts.forEach((post) => {\n post.tags?.forEach((tag) => tags.add(tag));\n });\n return Array.from(tags);\n}\n","\"use client\";\n\nimport Link from \"next/link\";\nimport Image from \"next/image\";\nimport { format } from \"date-fns\";\nimport type { BlogPostMeta } from \"../lib/blog\";\n\nexport interface PostCardProps {\n post: BlogPostMeta;\n basePath?: string;\n className?: string;\n}\n\nexport function PostCard({ post, basePath = \"/blog\", className }: PostCardProps) {\n return (\n <article className={`group relative flex flex-col space-y-2 ${className}`}>\n {post.image && (\n <div className=\"relative aspect-video overflow-hidden rounded-lg\">\n <Image\n src={post.image}\n alt={post.title}\n fill\n className=\"object-cover transition-transform group-hover:scale-105\"\n />\n </div>\n )}\n <div className=\"flex items-center gap-2\">\n {post.tags?.slice(0, 2).map((tag) => (\n <span\n key={tag}\n className=\"inline-flex items-center rounded-full bg-secondary px-2.5 py-0.5 text-xs font-medium text-secondary-foreground\"\n >\n {tag}\n </span>\n ))}\n </div>\n <h2 className=\"text-2xl font-semibold tracking-tight\">\n <Link href={`${basePath}/${post.slug}`}>\n <span className=\"absolute inset-0\" />\n {post.title}\n </Link>\n </h2>\n <p className=\"line-clamp-2 text-muted-foreground\">{post.description}</p>\n <div className=\"flex items-center gap-4 text-sm text-muted-foreground\">\n <span>{format(new Date(post.date), \"MMM d, yyyy\")}</span>\n <span>{post.readingTime}</span>\n </div>\n </article>\n );\n}\n","\"use client\";\n\nimport Image from \"next/image\";\nimport { format } from \"date-fns\";\nimport type { BlogPost } from \"../lib/blog\";\n\nexport interface PostHeaderProps {\n post: BlogPost;\n className?: string;\n}\n\nexport function PostHeader({ post, className }: PostHeaderProps) {\n return (\n <header className={`space-y-4 ${className}`}>\n <div className=\"flex items-center gap-2\">\n {post.tags?.map((tag) => (\n <span\n key={tag}\n className=\"inline-flex items-center rounded-full bg-secondary px-2.5 py-0.5 text-xs font-medium text-secondary-foreground\"\n >\n {tag}\n </span>\n ))}\n </div>\n <h1 className=\"text-4xl font-bold tracking-tight lg:text-5xl\">\n {post.title}\n </h1>\n <p className=\"text-xl text-muted-foreground\">{post.description}</p>\n <div className=\"flex items-center gap-4\">\n {post.author.image && (\n <Image\n src={post.author.image}\n alt={post.author.name}\n width={40}\n height={40}\n className=\"rounded-full\"\n />\n )}\n <div>\n <p className=\"font-medium\">{post.author.name}</p>\n <p className=\"text-sm text-muted-foreground\">\n {format(new Date(post.date), \"MMMM d, yyyy\")} · {post.readingTime}\n </p>\n </div>\n </div>\n {post.image && (\n <div className=\"relative aspect-video overflow-hidden rounded-lg\">\n <Image\n src={post.image}\n alt={post.title}\n fill\n className=\"object-cover\"\n priority\n />\n </div>\n )}\n </header>\n );\n}\n"],"mappings":"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","names":["path","fs","matter","readingTime","Image","Link","import_image","import_date_fns","import_jsx_runtime","Image"]}
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
// src/lib/blog.ts
|
|
4
|
+
import fs from "fs";
|
|
5
|
+
import path from "path";
|
|
6
|
+
import matter from "gray-matter";
|
|
7
|
+
import readingTime from "reading-time";
|
|
8
|
+
var config = {
|
|
9
|
+
contentDirectory: path.join(process.cwd(), "content/blog"),
|
|
10
|
+
defaultAuthor: { name: "Anonymous" }
|
|
11
|
+
};
|
|
12
|
+
function configureBlog(options) {
|
|
13
|
+
config = { ...config, ...options };
|
|
14
|
+
}
|
|
15
|
+
function getContentDirectory() {
|
|
16
|
+
return config.contentDirectory || path.join(process.cwd(), "content/blog");
|
|
17
|
+
}
|
|
18
|
+
function getAllPosts() {
|
|
19
|
+
const contentDirectory = getContentDirectory();
|
|
20
|
+
if (!fs.existsSync(contentDirectory)) {
|
|
21
|
+
return [];
|
|
22
|
+
}
|
|
23
|
+
const files = fs.readdirSync(contentDirectory);
|
|
24
|
+
const posts = files.filter((file) => file.endsWith(".mdx") || file.endsWith(".md")).map((file) => {
|
|
25
|
+
const slug = file.replace(/\.mdx?$/, "");
|
|
26
|
+
const fullPath = path.join(contentDirectory, file);
|
|
27
|
+
const fileContents = fs.readFileSync(fullPath, "utf8");
|
|
28
|
+
const { data, content } = matter(fileContents);
|
|
29
|
+
return {
|
|
30
|
+
slug,
|
|
31
|
+
title: data.title || slug,
|
|
32
|
+
description: data.description || "",
|
|
33
|
+
date: data.date || (/* @__PURE__ */ new Date()).toISOString(),
|
|
34
|
+
author: data.author || config.defaultAuthor || { name: "Anonymous" },
|
|
35
|
+
image: data.image,
|
|
36
|
+
tags: data.tags || [],
|
|
37
|
+
readingTime: readingTime(content).text
|
|
38
|
+
};
|
|
39
|
+
}).sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime());
|
|
40
|
+
return posts;
|
|
41
|
+
}
|
|
42
|
+
function getPostBySlug(slug) {
|
|
43
|
+
const contentDirectory = getContentDirectory();
|
|
44
|
+
let fullPath = path.join(contentDirectory, `${slug}.mdx`);
|
|
45
|
+
if (!fs.existsSync(fullPath)) {
|
|
46
|
+
fullPath = path.join(contentDirectory, `${slug}.md`);
|
|
47
|
+
}
|
|
48
|
+
if (!fs.existsSync(fullPath)) {
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
const fileContents = fs.readFileSync(fullPath, "utf8");
|
|
52
|
+
const { data, content } = matter(fileContents);
|
|
53
|
+
return {
|
|
54
|
+
slug,
|
|
55
|
+
title: data.title || slug,
|
|
56
|
+
description: data.description || "",
|
|
57
|
+
date: data.date || (/* @__PURE__ */ new Date()).toISOString(),
|
|
58
|
+
author: data.author || config.defaultAuthor || { name: "Anonymous" },
|
|
59
|
+
image: data.image,
|
|
60
|
+
tags: data.tags || [],
|
|
61
|
+
content,
|
|
62
|
+
readingTime: readingTime(content).text
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
function getAllPostSlugs() {
|
|
66
|
+
const contentDirectory = getContentDirectory();
|
|
67
|
+
if (!fs.existsSync(contentDirectory)) {
|
|
68
|
+
return [];
|
|
69
|
+
}
|
|
70
|
+
const files = fs.readdirSync(contentDirectory);
|
|
71
|
+
return files.filter((file) => file.endsWith(".mdx") || file.endsWith(".md")).map((file) => file.replace(/\.mdx?$/, ""));
|
|
72
|
+
}
|
|
73
|
+
function getPostsByTag(tag) {
|
|
74
|
+
return getAllPosts().filter(
|
|
75
|
+
(post) => post.tags?.map((t) => t.toLowerCase()).includes(tag.toLowerCase())
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
function getAllTags() {
|
|
79
|
+
const posts = getAllPosts();
|
|
80
|
+
const tags = /* @__PURE__ */ new Set();
|
|
81
|
+
posts.forEach((post) => {
|
|
82
|
+
post.tags?.forEach((tag) => tags.add(tag));
|
|
83
|
+
});
|
|
84
|
+
return Array.from(tags);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// src/components/post-card.tsx
|
|
88
|
+
import Link from "next/link";
|
|
89
|
+
import Image from "next/image";
|
|
90
|
+
import { format } from "date-fns";
|
|
91
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
92
|
+
function PostCard({ post, basePath = "/blog", className }) {
|
|
93
|
+
return /* @__PURE__ */ jsxs("article", { className: `group relative flex flex-col space-y-2 ${className}`, children: [
|
|
94
|
+
post.image && /* @__PURE__ */ jsx("div", { className: "relative aspect-video overflow-hidden rounded-lg", children: /* @__PURE__ */ jsx(
|
|
95
|
+
Image,
|
|
96
|
+
{
|
|
97
|
+
src: post.image,
|
|
98
|
+
alt: post.title,
|
|
99
|
+
fill: true,
|
|
100
|
+
className: "object-cover transition-transform group-hover:scale-105"
|
|
101
|
+
}
|
|
102
|
+
) }),
|
|
103
|
+
/* @__PURE__ */ jsx("div", { className: "flex items-center gap-2", children: post.tags?.slice(0, 2).map((tag) => /* @__PURE__ */ jsx(
|
|
104
|
+
"span",
|
|
105
|
+
{
|
|
106
|
+
className: "inline-flex items-center rounded-full bg-secondary px-2.5 py-0.5 text-xs font-medium text-secondary-foreground",
|
|
107
|
+
children: tag
|
|
108
|
+
},
|
|
109
|
+
tag
|
|
110
|
+
)) }),
|
|
111
|
+
/* @__PURE__ */ jsx("h2", { className: "text-2xl font-semibold tracking-tight", children: /* @__PURE__ */ jsxs(Link, { href: `${basePath}/${post.slug}`, children: [
|
|
112
|
+
/* @__PURE__ */ jsx("span", { className: "absolute inset-0" }),
|
|
113
|
+
post.title
|
|
114
|
+
] }) }),
|
|
115
|
+
/* @__PURE__ */ jsx("p", { className: "line-clamp-2 text-muted-foreground", children: post.description }),
|
|
116
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-4 text-sm text-muted-foreground", children: [
|
|
117
|
+
/* @__PURE__ */ jsx("span", { children: format(new Date(post.date), "MMM d, yyyy") }),
|
|
118
|
+
/* @__PURE__ */ jsx("span", { children: post.readingTime })
|
|
119
|
+
] })
|
|
120
|
+
] });
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// src/components/post-header.tsx
|
|
124
|
+
import Image2 from "next/image";
|
|
125
|
+
import { format as format2 } from "date-fns";
|
|
126
|
+
import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
127
|
+
function PostHeader({ post, className }) {
|
|
128
|
+
return /* @__PURE__ */ jsxs2("header", { className: `space-y-4 ${className}`, children: [
|
|
129
|
+
/* @__PURE__ */ jsx2("div", { className: "flex items-center gap-2", children: post.tags?.map((tag) => /* @__PURE__ */ jsx2(
|
|
130
|
+
"span",
|
|
131
|
+
{
|
|
132
|
+
className: "inline-flex items-center rounded-full bg-secondary px-2.5 py-0.5 text-xs font-medium text-secondary-foreground",
|
|
133
|
+
children: tag
|
|
134
|
+
},
|
|
135
|
+
tag
|
|
136
|
+
)) }),
|
|
137
|
+
/* @__PURE__ */ jsx2("h1", { className: "text-4xl font-bold tracking-tight lg:text-5xl", children: post.title }),
|
|
138
|
+
/* @__PURE__ */ jsx2("p", { className: "text-xl text-muted-foreground", children: post.description }),
|
|
139
|
+
/* @__PURE__ */ jsxs2("div", { className: "flex items-center gap-4", children: [
|
|
140
|
+
post.author.image && /* @__PURE__ */ jsx2(
|
|
141
|
+
Image2,
|
|
142
|
+
{
|
|
143
|
+
src: post.author.image,
|
|
144
|
+
alt: post.author.name,
|
|
145
|
+
width: 40,
|
|
146
|
+
height: 40,
|
|
147
|
+
className: "rounded-full"
|
|
148
|
+
}
|
|
149
|
+
),
|
|
150
|
+
/* @__PURE__ */ jsxs2("div", { children: [
|
|
151
|
+
/* @__PURE__ */ jsx2("p", { className: "font-medium", children: post.author.name }),
|
|
152
|
+
/* @__PURE__ */ jsxs2("p", { className: "text-sm text-muted-foreground", children: [
|
|
153
|
+
format2(new Date(post.date), "MMMM d, yyyy"),
|
|
154
|
+
" \xB7 ",
|
|
155
|
+
post.readingTime
|
|
156
|
+
] })
|
|
157
|
+
] })
|
|
158
|
+
] }),
|
|
159
|
+
post.image && /* @__PURE__ */ jsx2("div", { className: "relative aspect-video overflow-hidden rounded-lg", children: /* @__PURE__ */ jsx2(
|
|
160
|
+
Image2,
|
|
161
|
+
{
|
|
162
|
+
src: post.image,
|
|
163
|
+
alt: post.title,
|
|
164
|
+
fill: true,
|
|
165
|
+
className: "object-cover",
|
|
166
|
+
priority: true
|
|
167
|
+
}
|
|
168
|
+
) })
|
|
169
|
+
] });
|
|
170
|
+
}
|
|
171
|
+
export {
|
|
172
|
+
PostCard,
|
|
173
|
+
PostHeader,
|
|
174
|
+
configureBlog,
|
|
175
|
+
getAllPostSlugs,
|
|
176
|
+
getAllPosts,
|
|
177
|
+
getAllTags,
|
|
178
|
+
getPostBySlug,
|
|
179
|
+
getPostsByTag
|
|
180
|
+
};
|
|
181
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/lib/blog.ts","../src/components/post-card.tsx","../src/components/post-header.tsx"],"sourcesContent":["import fs from \"fs\";\nimport path from \"path\";\nimport matter from \"gray-matter\";\nimport readingTime from \"reading-time\";\n\nexport interface BlogPost {\n slug: string;\n title: string;\n description: string;\n date: string;\n author: {\n name: string;\n image?: string;\n };\n image?: string;\n tags?: string[];\n content: string;\n readingTime: string;\n}\n\nexport interface BlogPostMeta {\n slug: string;\n title: string;\n description: string;\n date: string;\n author: {\n name: string;\n image?: string;\n };\n image?: string;\n tags?: string[];\n readingTime: string;\n}\n\nexport interface BlogConfig {\n contentDirectory?: string;\n defaultAuthor?: {\n name: string;\n image?: string;\n };\n}\n\nlet config: BlogConfig = {\n contentDirectory: path.join(process.cwd(), \"content/blog\"),\n defaultAuthor: { name: \"Anonymous\" },\n};\n\nexport function configureBlog(options: BlogConfig) {\n config = { ...config, ...options };\n}\n\nfunction getContentDirectory(): string {\n return config.contentDirectory || path.join(process.cwd(), \"content/blog\");\n}\n\nexport function getAllPosts(): BlogPostMeta[] {\n const contentDirectory = getContentDirectory();\n\n if (!fs.existsSync(contentDirectory)) {\n return [];\n }\n\n const files = fs.readdirSync(contentDirectory);\n const posts = files\n .filter((file) => file.endsWith(\".mdx\") || file.endsWith(\".md\"))\n .map((file) => {\n const slug = file.replace(/\\.mdx?$/, \"\");\n const fullPath = path.join(contentDirectory, file);\n const fileContents = fs.readFileSync(fullPath, \"utf8\");\n const { data, content } = matter(fileContents);\n\n return {\n slug,\n title: data.title || slug,\n description: data.description || \"\",\n date: data.date || new Date().toISOString(),\n author: data.author || config.defaultAuthor || { name: \"Anonymous\" },\n image: data.image,\n tags: data.tags || [],\n readingTime: readingTime(content).text,\n };\n })\n .sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime());\n\n return posts;\n}\n\nexport function getPostBySlug(slug: string): BlogPost | null {\n const contentDirectory = getContentDirectory();\n\n // Try both .mdx and .md extensions\n let fullPath = path.join(contentDirectory, `${slug}.mdx`);\n if (!fs.existsSync(fullPath)) {\n fullPath = path.join(contentDirectory, `${slug}.md`);\n }\n\n if (!fs.existsSync(fullPath)) {\n return null;\n }\n\n const fileContents = fs.readFileSync(fullPath, \"utf8\");\n const { data, content } = matter(fileContents);\n\n return {\n slug,\n title: data.title || slug,\n description: data.description || \"\",\n date: data.date || new Date().toISOString(),\n author: data.author || config.defaultAuthor || { name: \"Anonymous\" },\n image: data.image,\n tags: data.tags || [],\n content,\n readingTime: readingTime(content).text,\n };\n}\n\nexport function getAllPostSlugs(): string[] {\n const contentDirectory = getContentDirectory();\n\n if (!fs.existsSync(contentDirectory)) {\n return [];\n }\n\n const files = fs.readdirSync(contentDirectory);\n return files\n .filter((file) => file.endsWith(\".mdx\") || file.endsWith(\".md\"))\n .map((file) => file.replace(/\\.mdx?$/, \"\"));\n}\n\nexport function getPostsByTag(tag: string): BlogPostMeta[] {\n return getAllPosts().filter((post) =>\n post.tags?.map((t) => t.toLowerCase()).includes(tag.toLowerCase())\n );\n}\n\nexport function getAllTags(): string[] {\n const posts = getAllPosts();\n const tags = new Set<string>();\n posts.forEach((post) => {\n post.tags?.forEach((tag) => tags.add(tag));\n });\n return Array.from(tags);\n}\n","\"use client\";\n\nimport Link from \"next/link\";\nimport Image from \"next/image\";\nimport { format } from \"date-fns\";\nimport type { BlogPostMeta } from \"../lib/blog\";\n\nexport interface PostCardProps {\n post: BlogPostMeta;\n basePath?: string;\n className?: string;\n}\n\nexport function PostCard({ post, basePath = \"/blog\", className }: PostCardProps) {\n return (\n <article className={`group relative flex flex-col space-y-2 ${className}`}>\n {post.image && (\n <div className=\"relative aspect-video overflow-hidden rounded-lg\">\n <Image\n src={post.image}\n alt={post.title}\n fill\n className=\"object-cover transition-transform group-hover:scale-105\"\n />\n </div>\n )}\n <div className=\"flex items-center gap-2\">\n {post.tags?.slice(0, 2).map((tag) => (\n <span\n key={tag}\n className=\"inline-flex items-center rounded-full bg-secondary px-2.5 py-0.5 text-xs font-medium text-secondary-foreground\"\n >\n {tag}\n </span>\n ))}\n </div>\n <h2 className=\"text-2xl font-semibold tracking-tight\">\n <Link href={`${basePath}/${post.slug}`}>\n <span className=\"absolute inset-0\" />\n {post.title}\n </Link>\n </h2>\n <p className=\"line-clamp-2 text-muted-foreground\">{post.description}</p>\n <div className=\"flex items-center gap-4 text-sm text-muted-foreground\">\n <span>{format(new Date(post.date), \"MMM d, yyyy\")}</span>\n <span>{post.readingTime}</span>\n </div>\n </article>\n );\n}\n","\"use client\";\n\nimport Image from \"next/image\";\nimport { format } from \"date-fns\";\nimport type { BlogPost } from \"../lib/blog\";\n\nexport interface PostHeaderProps {\n post: BlogPost;\n className?: string;\n}\n\nexport function PostHeader({ post, className }: PostHeaderProps) {\n return (\n <header className={`space-y-4 ${className}`}>\n <div className=\"flex items-center gap-2\">\n {post.tags?.map((tag) => (\n <span\n key={tag}\n className=\"inline-flex items-center rounded-full bg-secondary px-2.5 py-0.5 text-xs font-medium text-secondary-foreground\"\n >\n {tag}\n </span>\n ))}\n </div>\n <h1 className=\"text-4xl font-bold tracking-tight lg:text-5xl\">\n {post.title}\n </h1>\n <p className=\"text-xl text-muted-foreground\">{post.description}</p>\n <div className=\"flex items-center gap-4\">\n {post.author.image && (\n <Image\n src={post.author.image}\n alt={post.author.name}\n width={40}\n height={40}\n className=\"rounded-full\"\n />\n )}\n <div>\n <p className=\"font-medium\">{post.author.name}</p>\n <p className=\"text-sm text-muted-foreground\">\n {format(new Date(post.date), \"MMMM d, yyyy\")} · {post.readingTime}\n </p>\n </div>\n </div>\n {post.image && (\n <div className=\"relative aspect-video overflow-hidden rounded-lg\">\n <Image\n src={post.image}\n alt={post.title}\n fill\n className=\"object-cover\"\n priority\n />\n </div>\n )}\n </header>\n );\n}\n"],"mappings":"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","names":["Image","format","jsx","jsxs"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@victusvinceere/saas-blog",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "MDX blog module for SaaS Kit",
|
|
5
|
+
"main": "./dist/index.js",
|
|
6
|
+
"module": "./dist/index.mjs",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.mjs",
|
|
12
|
+
"require": "./dist/index.js"
|
|
13
|
+
},
|
|
14
|
+
"./components": {
|
|
15
|
+
"types": "./dist/components/index.d.ts",
|
|
16
|
+
"import": "./dist/components/index.mjs",
|
|
17
|
+
"require": "./dist/components/index.js"
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
"files": [
|
|
21
|
+
"dist",
|
|
22
|
+
"prisma"
|
|
23
|
+
],
|
|
24
|
+
"scripts": {
|
|
25
|
+
"build": "tsup",
|
|
26
|
+
"dev": "tsup --watch",
|
|
27
|
+
"typecheck": "tsc --noEmit",
|
|
28
|
+
"clean": "rm -rf dist"
|
|
29
|
+
},
|
|
30
|
+
"peerDependencies": {
|
|
31
|
+
"@victusvinceere/saas-core": "workspace:*",
|
|
32
|
+
"next": ">=15.0.0",
|
|
33
|
+
"react": ">=19.0.0"
|
|
34
|
+
},
|
|
35
|
+
"dependencies": {
|
|
36
|
+
"date-fns": "^4.1.0",
|
|
37
|
+
"gray-matter": "^4.0.3",
|
|
38
|
+
"next-mdx-remote": "^5.0.0",
|
|
39
|
+
"reading-time": "^1.5.0"
|
|
40
|
+
},
|
|
41
|
+
"devDependencies": {
|
|
42
|
+
"@types/node": "^20",
|
|
43
|
+
"@types/react": "^19",
|
|
44
|
+
"tsup": "^8.0.2",
|
|
45
|
+
"typescript": "^5"
|
|
46
|
+
},
|
|
47
|
+
"keywords": [
|
|
48
|
+
"saas",
|
|
49
|
+
"blog",
|
|
50
|
+
"mdx",
|
|
51
|
+
"react"
|
|
52
|
+
],
|
|
53
|
+
"license": "MIT"
|
|
54
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
// Blog Prisma Schema Extension
|
|
2
|
+
// Adds blog-related models for database-backed blog system
|
|
3
|
+
|
|
4
|
+
// ==================== BLOG MODELS ====================
|
|
5
|
+
|
|
6
|
+
model Post {
|
|
7
|
+
id String @id @default(cuid())
|
|
8
|
+
slug String @unique
|
|
9
|
+
title String
|
|
10
|
+
description String?
|
|
11
|
+
content String @db.Text
|
|
12
|
+
image String?
|
|
13
|
+
published Boolean @default(false)
|
|
14
|
+
publishedAt DateTime? @map("published_at")
|
|
15
|
+
createdAt DateTime @default(now()) @map("created_at")
|
|
16
|
+
updatedAt DateTime @updatedAt @map("updated_at")
|
|
17
|
+
|
|
18
|
+
// SEO fields
|
|
19
|
+
metaTitle String? @map("meta_title")
|
|
20
|
+
metaDescription String? @map("meta_description")
|
|
21
|
+
|
|
22
|
+
// Relations
|
|
23
|
+
categories PostCategory[]
|
|
24
|
+
tags PostTag[]
|
|
25
|
+
|
|
26
|
+
@@map("posts")
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
model Category {
|
|
30
|
+
id String @id @default(cuid())
|
|
31
|
+
name String @unique
|
|
32
|
+
slug String @unique
|
|
33
|
+
description String?
|
|
34
|
+
|
|
35
|
+
posts PostCategory[]
|
|
36
|
+
|
|
37
|
+
@@map("categories")
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
model Tag {
|
|
41
|
+
id String @id @default(cuid())
|
|
42
|
+
name String @unique
|
|
43
|
+
slug String @unique
|
|
44
|
+
|
|
45
|
+
posts PostTag[]
|
|
46
|
+
|
|
47
|
+
@@map("tags")
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
model PostCategory {
|
|
51
|
+
postId String @map("post_id")
|
|
52
|
+
categoryId String @map("category_id")
|
|
53
|
+
|
|
54
|
+
post Post @relation(fields: [postId], references: [id], onDelete: Cascade)
|
|
55
|
+
category Category @relation(fields: [categoryId], references: [id], onDelete: Cascade)
|
|
56
|
+
|
|
57
|
+
@@id([postId, categoryId])
|
|
58
|
+
@@map("post_categories")
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
model PostTag {
|
|
62
|
+
postId String @map("post_id")
|
|
63
|
+
tagId String @map("tag_id")
|
|
64
|
+
|
|
65
|
+
post Post @relation(fields: [postId], references: [id], onDelete: Cascade)
|
|
66
|
+
tag Tag @relation(fields: [tagId], references: [id], onDelete: Cascade)
|
|
67
|
+
|
|
68
|
+
@@id([postId, tagId])
|
|
69
|
+
@@map("post_tags")
|
|
70
|
+
}
|