@fullstackdatasolutions/articles 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/README.md +324 -0
- package/dist/index.cjs +1027 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +232 -0
- package/dist/index.d.ts +232 -0
- package/dist/index.js +974 -0
- package/dist/index.js.map +1 -0
- package/dist/server.cjs +732 -0
- package/dist/server.cjs.map +1 -0
- package/dist/server.d.cts +127 -0
- package/dist/server.d.ts +127 -0
- package/dist/server.js +684 -0
- package/dist/server.js.map +1 -0
- package/package.json +82 -0
- package/src/ArticleCard.tsx +63 -0
- package/src/ArticleCategoryGrid.tsx +73 -0
- package/src/ArticleSchemas.tsx +82 -0
- package/src/ArticleSearchBar.tsx +50 -0
- package/src/ArticlesHero.tsx +33 -0
- package/src/ArticlesPage.tsx +167 -0
- package/src/CategoryArticlesPage.tsx +84 -0
- package/src/CommentForm.tsx +98 -0
- package/src/CommentItem.tsx +123 -0
- package/src/CommentThread.tsx +40 -0
- package/src/CommentsSection.tsx +84 -0
- package/src/FeaturedArticle.tsx +63 -0
- package/src/LatestArticles.tsx +42 -0
- package/src/LatestArticlesSection.tsx +68 -0
- package/src/__tests__/ArticleCard.test.tsx +78 -0
- package/src/__tests__/ArticleCategoryGrid.test.tsx +98 -0
- package/src/__tests__/ArticleSchemas.test.tsx +130 -0
- package/src/__tests__/ArticleSearchBar.test.tsx +79 -0
- package/src/__tests__/ArticlesHero.test.tsx +38 -0
- package/src/__tests__/ArticlesPage.test.tsx +155 -0
- package/src/__tests__/CategoryArticlesPage.test.tsx +156 -0
- package/src/__tests__/CommentForm.test.tsx +80 -0
- package/src/__tests__/CommentItem.test.tsx +149 -0
- package/src/__tests__/CommentsSection.test.tsx +118 -0
- package/src/__tests__/FeaturedArticle.test.tsx +85 -0
- package/src/__tests__/LatestArticles.test.tsx +157 -0
- package/src/__tests__/LatestArticlesSection.test.tsx +105 -0
- package/src/__tests__/jest-dom.d.ts +1 -0
- package/src/__tests__/seoUtils.test.ts +217 -0
- package/src/__tests__/useArticles.test.ts +207 -0
- package/src/articleTypes.ts +21 -0
- package/src/articlesConfig.ts +98 -0
- package/src/commentTypes.ts +14 -0
- package/src/index.ts +35 -0
- package/src/markdown.ts +314 -0
- package/src/seoUtils.ts +155 -0
- package/src/server-articles.ts +265 -0
- package/src/server.ts +25 -0
- package/src/useArticles.ts +93 -0
package/README.md
ADDED
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
# @fullstackdatasolutions/articles
|
|
2
|
+
|
|
3
|
+
A self-contained Next.js articles library providing components, server utilities, SEO helpers, and a comments system. Drop it into any Next.js + Tailwind CSS v4 app.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Quick start
|
|
8
|
+
|
|
9
|
+
### 1. Install
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npm install @fullstackdatasolutions/articles
|
|
13
|
+
# or
|
|
14
|
+
pnpm add @fullstackdatasolutions/articles
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
### 2. Add the Tailwind source path
|
|
18
|
+
|
|
19
|
+
In Tailwind CSS v4, you add a `@source` directive to your global CSS file — **not** a `tailwind.config.ts`. This tells Tailwind to scan the package's source files so all utility classes are generated.
|
|
20
|
+
|
|
21
|
+
```css
|
|
22
|
+
/* app/globals.css */
|
|
23
|
+
@import "tailwindcss";
|
|
24
|
+
|
|
25
|
+
@source "./node_modules/@fullstackdatasolutions/articles/src";
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
The package components use Tailwind semantic tokens (`bg-card`, `text-muted-foreground`, `border-border`, etc.) that shadcn/ui and v0-generated apps already define — so the styling integrates automatically with your existing theme.
|
|
29
|
+
|
|
30
|
+
### 3. Add TypeScript path aliases
|
|
31
|
+
|
|
32
|
+
If you're using TypeScript and want IDE autocomplete without a pre-built `dist/`, add these paths to `tsconfig.json`. This is **required in a monorepo** (where the package is a workspace dep) and optional when installed from npm (the published `dist/` files work without it).
|
|
33
|
+
|
|
34
|
+
```json
|
|
35
|
+
// tsconfig.json
|
|
36
|
+
{
|
|
37
|
+
"compilerOptions": {
|
|
38
|
+
"paths": {
|
|
39
|
+
"@fullstackdatasolutions/articles": ["./node_modules/@fullstackdatasolutions/articles/src/index.ts"],
|
|
40
|
+
"@fullstackdatasolutions/articles/server": ["./node_modules/@fullstackdatasolutions/articles/src/server.ts"]
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### 4. Create your site config
|
|
47
|
+
|
|
48
|
+
```ts
|
|
49
|
+
// config/articles.ts
|
|
50
|
+
import type { ArticlesConfig } from '@fullstackdatasolutions/articles'
|
|
51
|
+
|
|
52
|
+
export const siteConfig: ArticlesConfig = {
|
|
53
|
+
siteUrl: 'https://yoursite.com',
|
|
54
|
+
siteName: 'Your Site',
|
|
55
|
+
pageSize: 6,
|
|
56
|
+
}
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### 5. Wire up the route files
|
|
60
|
+
|
|
61
|
+
```tsx
|
|
62
|
+
// app/articles/page.tsx
|
|
63
|
+
import { ArticlesPage } from '@fullstackdatasolutions/articles'
|
|
64
|
+
import { siteConfig } from '@/config/articles'
|
|
65
|
+
export default function Page() {
|
|
66
|
+
return <ArticlesPage config={siteConfig} />
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// app/articles/[slug]/page.tsx
|
|
70
|
+
import { ArticlePage, generateArticleStaticParams, generateArticleMetadata } from '@fullstackdatasolutions/articles/server'
|
|
71
|
+
import { siteConfig } from '@/config/articles'
|
|
72
|
+
export const generateStaticParams = () => generateArticleStaticParams()
|
|
73
|
+
export const generateMetadata = ({ params }: { params: { slug: string } }) =>
|
|
74
|
+
generateArticleMetadata(params.slug, siteConfig)
|
|
75
|
+
export default function Page({ params }: { params: { slug: string } }) {
|
|
76
|
+
return <ArticlePage slug={params.slug} config={siteConfig} />
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// app/articles/category/[category]/page.tsx
|
|
80
|
+
import { CategoryArticlesPage, generateCategoryStaticParams, generateCategoryMetadata } from '@fullstackdatasolutions/articles/server'
|
|
81
|
+
import { siteConfig } from '@/config/articles'
|
|
82
|
+
export const generateStaticParams = () => generateCategoryStaticParams()
|
|
83
|
+
export const generateMetadata = ({ params }: { params: { category: string } }) =>
|
|
84
|
+
generateCategoryMetadata(params.category, siteConfig)
|
|
85
|
+
export default function Page({ params }: { params: { category: string } }) {
|
|
86
|
+
return <CategoryArticlesPage category={params.category} config={siteConfig} />
|
|
87
|
+
}
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### 6. Copy the API routes
|
|
91
|
+
|
|
92
|
+
These cannot live inside the package — Next.js file-based routing requires them in your app. Copy these files from the reference implementation:
|
|
93
|
+
|
|
94
|
+
```
|
|
95
|
+
app/api/articles/route.ts
|
|
96
|
+
app/api/articles/[slug]/comments/route.ts
|
|
97
|
+
app/api/articles/[slug]/comments/[commentId]/route.ts
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
The route implementations import from `@fullstackdatasolutions/articles/server`. Do not put custom business logic in them; they should stay as thin wrappers.
|
|
101
|
+
|
|
102
|
+
### 7. Add your content
|
|
103
|
+
|
|
104
|
+
Create `public/articles/[slug]/article.md` for each article. Required frontmatter:
|
|
105
|
+
|
|
106
|
+
```markdown
|
|
107
|
+
---
|
|
108
|
+
title: Your Article Title
|
|
109
|
+
excerpt: A one-sentence summary shown in cards and meta descriptions.
|
|
110
|
+
author: Jane Doe
|
|
111
|
+
tags: [campaigns, strategy]
|
|
112
|
+
---
|
|
113
|
+
|
|
114
|
+
Article body in Markdown...
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
---
|
|
118
|
+
|
|
119
|
+
## `ArticlesConfig` reference
|
|
120
|
+
|
|
121
|
+
| Field | Type | Default | Description |
|
|
122
|
+
|---|---|---|---|
|
|
123
|
+
| `siteUrl` | `string` | — | Canonical base URL. Used in metadata and JSON-LD. |
|
|
124
|
+
| `siteName` | `string` | — | Site name in title tags and JSON-LD. |
|
|
125
|
+
| `pageSize` | `number` | `6` | Articles per page / per "Load more" click. |
|
|
126
|
+
| `categoriesPageSize` | `number` | `8` | Category cards before "Load more categories". |
|
|
127
|
+
| `layout` | `ArticlesSection[]` | see below | Ordered sections to render. Omit a key to hide it. |
|
|
128
|
+
| `theme` | `ArticlesTheme` | — | CSS custom-property overrides for colors and fonts. |
|
|
129
|
+
| `categoryDescriptions` | `Record<string, string \| CategoryDescription>` | — | Short/long text per category slug. |
|
|
130
|
+
| `comments` | `CommentsConfig` | — | Comments feature config. Omit to disable entirely. |
|
|
131
|
+
|
|
132
|
+
**Default layout order:** `['hero', 'search', 'featured', 'latest', 'categories']`
|
|
133
|
+
|
|
134
|
+
To add a newsletter section, pass `layout: ['hero','search','featured','latest','categories','newsletter']` — the library renders `null` for `'newsletter'` so you can append your own component after `<ArticlesPage />`.
|
|
135
|
+
|
|
136
|
+
---
|
|
137
|
+
|
|
138
|
+
## Theming
|
|
139
|
+
|
|
140
|
+
All theme values are applied as CSS custom properties on a wrapper `<div>`. Components reference them through Tailwind so no extra CSS file is needed.
|
|
141
|
+
|
|
142
|
+
```ts
|
|
143
|
+
theme: {
|
|
144
|
+
fontFamily: "'Inter', sans-serif",
|
|
145
|
+
headerColor: '#111827',
|
|
146
|
+
textColor: '#6b7280',
|
|
147
|
+
backgroundColor: '#ffffff',
|
|
148
|
+
linkColor: '#4f46e5',
|
|
149
|
+
linkHoverColor: '#4338ca',
|
|
150
|
+
borderRadius: '0.5rem',
|
|
151
|
+
}
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
All fields are optional. Omitted fields fall back to your Tailwind theme tokens.
|
|
155
|
+
|
|
156
|
+
---
|
|
157
|
+
|
|
158
|
+
## Comments setup
|
|
159
|
+
|
|
160
|
+
### 1. Enable in config
|
|
161
|
+
|
|
162
|
+
```ts
|
|
163
|
+
export const siteConfig: ArticlesConfig = {
|
|
164
|
+
// ...
|
|
165
|
+
comments: {
|
|
166
|
+
enabled: true,
|
|
167
|
+
perArticleOverride: {
|
|
168
|
+
'sensitive-article': false, // opt individual articles out
|
|
169
|
+
},
|
|
170
|
+
},
|
|
171
|
+
}
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
### 2. Copy the comment API routes
|
|
175
|
+
|
|
176
|
+
```
|
|
177
|
+
app/api/articles/[slug]/comments/route.ts ← GET list, POST new comment
|
|
178
|
+
app/api/articles/[slug]/comments/[commentId]/route.ts ← DELETE (soft)
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
### 3. Requirements
|
|
182
|
+
|
|
183
|
+
- **NextAuth** session — users must be signed in to post. Unauthenticated visitors see a sign-in prompt.
|
|
184
|
+
- **PostgreSQL** — comments are stored via Prisma. Run the migration from the reference app's `backend/` directory.
|
|
185
|
+
|
|
186
|
+
---
|
|
187
|
+
|
|
188
|
+
## SEO utilities
|
|
189
|
+
|
|
190
|
+
```ts
|
|
191
|
+
// app/articles/[slug]/page.tsx
|
|
192
|
+
import { generateArticleMetadata } from '@fullstackdatasolutions/articles/server'
|
|
193
|
+
export const generateMetadata = ({ params }) =>
|
|
194
|
+
generateArticleMetadata(params.slug, siteConfig)
|
|
195
|
+
// Produces: title, description, full OpenGraph, Twitter Card, canonical URL
|
|
196
|
+
|
|
197
|
+
// app/articles/category/[category]/page.tsx
|
|
198
|
+
import { generateCategoryMetadata } from '@fullstackdatasolutions/articles/server'
|
|
199
|
+
export const generateMetadata = ({ params }) =>
|
|
200
|
+
generateCategoryMetadata(params.category, siteConfig)
|
|
201
|
+
|
|
202
|
+
// app/sitemap.ts
|
|
203
|
+
import { getArticleSitemapEntries } from '@fullstackdatasolutions/articles/server'
|
|
204
|
+
export default async function sitemap() {
|
|
205
|
+
return getArticleSitemapEntries('https://yoursite.com')
|
|
206
|
+
// Returns both article entries (priority 0.8) and category entries (priority 0.7)
|
|
207
|
+
}
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
JSON-LD structured data (`ArticleSchema`, `BreadcrumbSchema`, `CollectionPageSchema`) is rendered automatically inside the library components — no extra wiring required.
|
|
211
|
+
|
|
212
|
+
---
|
|
213
|
+
|
|
214
|
+
## Article frontmatter reference
|
|
215
|
+
|
|
216
|
+
| Field | Type | Required | Description |
|
|
217
|
+
|---|---|---|---|
|
|
218
|
+
| `title` | `string` | yes | Article title |
|
|
219
|
+
| `excerpt` | `string` | yes | One-sentence summary for cards and meta |
|
|
220
|
+
| `author` | `string` | no | Defaults to `'Andrew Blase'` |
|
|
221
|
+
| `tags` | `string[]` | no | Used as categories. First tag = primary category. |
|
|
222
|
+
| `date` | `YYYY-MM-DD` | no | Omit to publish immediately. Future dates hide until that date. |
|
|
223
|
+
| `featuredImage` | `string` | no | Filename relative to the article directory. Auto-detected if omitted. |
|
|
224
|
+
|
|
225
|
+
---
|
|
226
|
+
|
|
227
|
+
## Server utilities
|
|
228
|
+
|
|
229
|
+
Import from `@fullstackdatasolutions/articles/server` — these use `fs`/`path` and must never be imported in client components.
|
|
230
|
+
|
|
231
|
+
```ts
|
|
232
|
+
import {
|
|
233
|
+
getAllArticles,
|
|
234
|
+
getArticleMetadata,
|
|
235
|
+
getArticlesByCategory,
|
|
236
|
+
getAllCategories,
|
|
237
|
+
getAvailableArticleSlugs,
|
|
238
|
+
getAdjacentArticles,
|
|
239
|
+
searchArticles,
|
|
240
|
+
categoryToSlug,
|
|
241
|
+
getArticleSitemapEntries,
|
|
242
|
+
generateArticleMetadata,
|
|
243
|
+
generateCategoryMetadata,
|
|
244
|
+
} from '@fullstackdatasolutions/articles/server'
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
---
|
|
248
|
+
|
|
249
|
+
## Using in a monorepo (workspace)
|
|
250
|
+
|
|
251
|
+
If the package is a `workspace:*` dependency in a pnpm/npm monorepo, point TypeScript directly at the source so no build step is needed during local development:
|
|
252
|
+
|
|
253
|
+
```json
|
|
254
|
+
// tsconfig.json — consuming app
|
|
255
|
+
{
|
|
256
|
+
"compilerOptions": {
|
|
257
|
+
"paths": {
|
|
258
|
+
"@fullstackdatasolutions/articles": ["../packages/articles/src/index.ts"],
|
|
259
|
+
"@fullstackdatasolutions/articles/server": ["../packages/articles/src/server.ts"]
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
And point the Tailwind `@source` at the workspace path:
|
|
266
|
+
|
|
267
|
+
```css
|
|
268
|
+
/* app/globals.css */
|
|
269
|
+
@source "../../packages/articles/src";
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
The relative path from your CSS file to `packages/articles/src` will vary by your monorepo layout.
|
|
273
|
+
|
|
274
|
+
---
|
|
275
|
+
|
|
276
|
+
## Adding to your app's `AGENTS.md`
|
|
277
|
+
|
|
278
|
+
Paste this block into your app's `AGENTS.md` so AI assistants know how the library works:
|
|
279
|
+
|
|
280
|
+
```markdown
|
|
281
|
+
## Articles library
|
|
282
|
+
|
|
283
|
+
This app uses `@fullstackdatasolutions/articles` for the articles section.
|
|
284
|
+
|
|
285
|
+
### Key files
|
|
286
|
+
- `config/articles.ts` — all library behaviour (layout, theme, page size, comments) is controlled here
|
|
287
|
+
- `app/api/articles/` — thin API route wrappers; do not add business logic here
|
|
288
|
+
- `public/articles/[slug]/article.md` — one directory per article
|
|
289
|
+
|
|
290
|
+
### CSS (Tailwind v4)
|
|
291
|
+
The package ships its source TypeScript files so Tailwind can scan them.
|
|
292
|
+
`app/globals.css` contains:
|
|
293
|
+
@source "./node_modules/@fullstackdatasolutions/articles/src";
|
|
294
|
+
If this line is missing, component classes (layout, images, line-clamp) will not render correctly.
|
|
295
|
+
|
|
296
|
+
### Rules
|
|
297
|
+
- Change `config/articles.ts` first; only edit library source if config cannot express the need.
|
|
298
|
+
- When adding a new article: create `public/articles/[slug]/article.md` with title, excerpt, author, tags.
|
|
299
|
+
- API routes are copied from the library — update the package version and re-copy if the library API changes.
|
|
300
|
+
- Full library docs: `node_modules/@fullstackdatasolutions/articles/README.md`
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
---
|
|
304
|
+
|
|
305
|
+
## Publishing
|
|
306
|
+
|
|
307
|
+
The package publishes to npm automatically when you push a tag:
|
|
308
|
+
|
|
309
|
+
```bash
|
|
310
|
+
git tag articles-v0.2.0
|
|
311
|
+
git push origin articles-v0.2.0
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
GitHub Actions runs typecheck → test → build → `npm publish --access public`. Requires an `NPM_TOKEN` secret in the repo settings.
|
|
315
|
+
|
|
316
|
+
---
|
|
317
|
+
|
|
318
|
+
## Upgrading
|
|
319
|
+
|
|
320
|
+
Follow semver. Breaking changes (major) include a migration note in the changelog.
|
|
321
|
+
|
|
322
|
+
- **Patch** — bug fixes only, safe to update
|
|
323
|
+
- **Minor** — new config options or exports, backwards-compatible
|
|
324
|
+
- **Major** — `ArticlesConfig` shape or component prop changes; check migration notes
|