@xexr/blog-platform 1.0.3 → 1.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/app/blog/[slug]/page.tsx +87 -26
- package/dist/index.d.mts +8 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +8 -8
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +8 -8
- package/dist/index.mjs.map +1 -1
- package/package.json +9 -8
|
@@ -7,6 +7,7 @@ import { cn } from "../../../lib/utils";
|
|
|
7
7
|
import { ArrowLeft, ArrowUpRight, Mail, Share2 } from "lucide-react";
|
|
8
8
|
import { Badge } from "../../../components/ui/badge";
|
|
9
9
|
import { Card, CardContent } from "../../../components/ui/card";
|
|
10
|
+
import { ExpandableArticleImage } from "../../../components/ExpandableArticleImage";
|
|
10
11
|
import { Separator } from "../../../components/ui/separator";
|
|
11
12
|
import {
|
|
12
13
|
generateCompleteArticleStructuredData,
|
|
@@ -33,10 +34,14 @@ interface ArticlePageProps {
|
|
|
33
34
|
previousArticle?: {
|
|
34
35
|
url: string;
|
|
35
36
|
title: string;
|
|
37
|
+
featuredImage?: string;
|
|
38
|
+
featuredImageAlt?: string;
|
|
36
39
|
};
|
|
37
40
|
nextArticle?: {
|
|
38
41
|
url: string;
|
|
39
42
|
title: string;
|
|
43
|
+
featuredImage?: string;
|
|
44
|
+
featuredImageAlt?: string;
|
|
40
45
|
};
|
|
41
46
|
};
|
|
42
47
|
/** Public path where this blog is mounted, e.g. "/blog" or "/new-blog". */
|
|
@@ -124,13 +129,13 @@ export default function ArticlePage({
|
|
|
124
129
|
{/* Article Header */}
|
|
125
130
|
<header className="article-header mx-auto mb-6 max-w-4xl space-y-4 text-center sm:mb-8 sm:space-y-6 lg:mb-12 lg:space-y-8">
|
|
126
131
|
{/* Title */}
|
|
127
|
-
<h1 className="article-title text-
|
|
132
|
+
<h1 className="article-title bg-linear-to-br from-[var(--blog-accent-hover)] via-[var(--blog-text-accent)] to-fuchsia-200 bg-clip-text px-4 text-2xl leading-tight font-light tracking-tight text-balance text-transparent sm:px-0 sm:text-3xl md:text-4xl lg:text-5xl xl:text-6xl">
|
|
128
133
|
{article.title}
|
|
129
134
|
</h1>
|
|
130
135
|
|
|
131
136
|
{/* Excerpt */}
|
|
132
137
|
{article.excerpt && (
|
|
133
|
-
<p className="article-excerpt text-muted-foreground mx-auto mt-6 max-w-
|
|
138
|
+
<p className="article-excerpt text-muted-foreground mx-auto mt-6 max-w-[42rem] px-4 text-lg leading-relaxed text-balance sm:mt-8 sm:max-w-[46rem] sm:px-0 sm:text-xl md:max-w-[52rem] md:text-2xl lg:max-w-4xl lg:text-2xl">
|
|
134
139
|
{article.excerpt}
|
|
135
140
|
</p>
|
|
136
141
|
)}
|
|
@@ -182,7 +187,7 @@ export default function ArticlePage({
|
|
|
182
187
|
|
|
183
188
|
{/* Featured Image */}
|
|
184
189
|
{imageUrl && (
|
|
185
|
-
<div className="article-featured-image mx-auto my-8 aspect-[16/9] w-full max-w-4xl overflow-hidden rounded-
|
|
190
|
+
<div className="article-featured-image mx-auto my-8 aspect-[16/9] w-full max-w-4xl overflow-hidden rounded-3xl border border-[var(--blog-accent-border)] shadow-[0_24px_80px_rgb(168_85_247_/_0.14)] sm:my-12 lg:my-16">
|
|
186
191
|
<Image
|
|
187
192
|
src={imageUrl}
|
|
188
193
|
alt={
|
|
@@ -197,12 +202,19 @@ export default function ArticlePage({
|
|
|
197
202
|
)}
|
|
198
203
|
|
|
199
204
|
{/* Article Content */}
|
|
200
|
-
<div
|
|
205
|
+
<div
|
|
206
|
+
id="article-content"
|
|
207
|
+
className="article-content mx-auto grid max-w-6xl grid-cols-1 gap-8 lg:grid-cols-[minmax(0,1fr)_14rem] lg:gap-12"
|
|
208
|
+
>
|
|
201
209
|
<div className="min-w-0">
|
|
202
210
|
<ArticleTableOfContents items={tocItems} variant="mobile" />
|
|
203
211
|
|
|
204
|
-
<div className="prose prose-sm sm:prose-base lg:prose-lg xl:prose-xl prose-gray dark:prose-invert prose-headings:scroll-mt-20 prose-headings:font-light prose-headings:text-balance prose-headings:text-[var(--blog-accent)] prose-h1:text-2xl sm:prose-h1:text-3xl lg:prose-h1:text-4xl prose-h2:text-xl sm:prose-h2:text-2xl lg:prose-h2:text-3xl prose-h3:text-lg sm:prose-h3:text-xl lg:prose-h3:text-2xl prose-p:leading-relaxed prose-p:text-pretty prose-
|
|
205
|
-
<MDXRemote
|
|
212
|
+
<div className="prose prose-sm sm:prose-base lg:prose-lg xl:prose-xl prose-gray dark:prose-invert prose-headings:scroll-mt-20 prose-headings:font-light prose-headings:text-balance prose-headings:text-[var(--blog-text-accent)] prose-h1:text-2xl sm:prose-h1:text-3xl lg:prose-h1:text-4xl prose-h2:text-xl sm:prose-h2:text-2xl lg:prose-h2:text-3xl prose-h3:text-lg sm:prose-h3:text-xl lg:prose-h3:text-2xl prose-p:leading-relaxed prose-p:text-pretty prose-a:text-[var(--blog-text-accent)] prose-a:transition-colors hover:prose-a:text-[var(--blog-text-accent-hover)] max-w-none">
|
|
213
|
+
<MDXRemote
|
|
214
|
+
source={article.content}
|
|
215
|
+
options={articleMdxOptions}
|
|
216
|
+
components={{ img: ExpandableArticleImage }}
|
|
217
|
+
/>
|
|
206
218
|
</div>
|
|
207
219
|
</div>
|
|
208
220
|
|
|
@@ -271,11 +283,20 @@ export default function ArticlePage({
|
|
|
271
283
|
{navigation?.previousArticle && (
|
|
272
284
|
<Card className="group hover:border-[var(--blog-accent-border)] hover:bg-[var(--blog-accent-bg)] cursor-pointer transition-colors">
|
|
273
285
|
<Link href={navigation.previousArticle.url} className="block">
|
|
286
|
+
{navigation.previousArticle.featuredImage && (
|
|
287
|
+
<ArticleCardImage
|
|
288
|
+
src={navigation.previousArticle.featuredImage}
|
|
289
|
+
alt={
|
|
290
|
+
navigation.previousArticle.featuredImageAlt ??
|
|
291
|
+
`Featured image for ${navigation.previousArticle.title}`
|
|
292
|
+
}
|
|
293
|
+
/>
|
|
294
|
+
)}
|
|
274
295
|
<CardContent className="p-4">
|
|
275
296
|
<div className="text-muted-foreground mb-2 text-sm font-medium">
|
|
276
297
|
← Previous Article
|
|
277
298
|
</div>
|
|
278
|
-
<div className="text-foreground group-hover:text-[var(--blog-accent)] transition-colors">
|
|
299
|
+
<div className="text-foreground group-hover:text-[var(--blog-text-accent)] transition-colors">
|
|
279
300
|
{navigation.previousArticle.title}
|
|
280
301
|
</div>
|
|
281
302
|
</CardContent>
|
|
@@ -290,11 +311,20 @@ export default function ArticlePage({
|
|
|
290
311
|
)}
|
|
291
312
|
>
|
|
292
313
|
<Link href={navigation.nextArticle.url} className="block">
|
|
314
|
+
{navigation.nextArticle.featuredImage && (
|
|
315
|
+
<ArticleCardImage
|
|
316
|
+
src={navigation.nextArticle.featuredImage}
|
|
317
|
+
alt={
|
|
318
|
+
navigation.nextArticle.featuredImageAlt ??
|
|
319
|
+
`Featured image for ${navigation.nextArticle.title}`
|
|
320
|
+
}
|
|
321
|
+
/>
|
|
322
|
+
)}
|
|
293
323
|
<CardContent className="p-4 md:text-right">
|
|
294
324
|
<div className="text-muted-foreground mb-2 text-sm font-medium">
|
|
295
325
|
Next Article →
|
|
296
326
|
</div>
|
|
297
|
-
<div className="text-foreground group-hover:text-[var(--blog-accent)] transition-colors">
|
|
327
|
+
<div className="text-foreground group-hover:text-[var(--blog-text-accent)] transition-colors">
|
|
298
328
|
{navigation.nextArticle.title}
|
|
299
329
|
</div>
|
|
300
330
|
</CardContent>
|
|
@@ -342,7 +372,7 @@ function ArticleTableOfContents({
|
|
|
342
372
|
<li key={item.id} className={item.level === 3 ? "pl-4" : undefined}>
|
|
343
373
|
<a
|
|
344
374
|
href={`#${item.id}`}
|
|
345
|
-
className="text-muted-foreground hover:text-[var(--blog-accent)] block leading-snug transition-colors"
|
|
375
|
+
className="text-muted-foreground hover:text-[var(--blog-text-accent)] block leading-snug transition-colors"
|
|
346
376
|
>
|
|
347
377
|
{item.text}
|
|
348
378
|
</a>
|
|
@@ -413,8 +443,8 @@ function ArticleShareLinks({ title, url }: { title: string; url: string }) {
|
|
|
413
443
|
const encodedUrl = encodeURIComponent(url);
|
|
414
444
|
|
|
415
445
|
return (
|
|
416
|
-
<div className="flex flex-wrap items-center gap-3">
|
|
417
|
-
<span className="text-muted-foreground inline-flex items-center gap-2 text-sm font-medium">
|
|
446
|
+
<div className="flex flex-wrap items-center justify-center gap-2 sm:gap-3">
|
|
447
|
+
<span className="text-muted-foreground inline-flex basis-full items-center justify-center gap-2 text-sm font-medium sm:basis-auto">
|
|
418
448
|
<Share2 className="size-4" aria-hidden="true" />
|
|
419
449
|
Share
|
|
420
450
|
</span>
|
|
@@ -422,24 +452,30 @@ function ArticleShareLinks({ title, url }: { title: string; url: string }) {
|
|
|
422
452
|
href={`https://x.com/intent/tweet?text=${encodedTitle}&url=${encodedUrl}`}
|
|
423
453
|
target="_blank"
|
|
424
454
|
rel="noopener noreferrer"
|
|
425
|
-
|
|
455
|
+
aria-label="Share on X"
|
|
456
|
+
className="text-muted-foreground hover:border-[var(--blog-accent-border)] hover:bg-[var(--blog-accent-bg)] hover:text-foreground inline-flex size-9 items-center justify-center rounded-full border border-border grayscale transition-colors"
|
|
426
457
|
>
|
|
427
|
-
|
|
458
|
+
<span className="text-sm font-semibold" aria-hidden="true">
|
|
459
|
+
X
|
|
460
|
+
</span>
|
|
428
461
|
</a>
|
|
429
462
|
<a
|
|
430
463
|
href={`https://www.linkedin.com/sharing/share-offsite/?url=${encodedUrl}`}
|
|
431
464
|
target="_blank"
|
|
432
465
|
rel="noopener noreferrer"
|
|
433
|
-
|
|
466
|
+
aria-label="Share on LinkedIn"
|
|
467
|
+
className="text-muted-foreground hover:border-[var(--blog-accent-border)] hover:bg-[var(--blog-accent-bg)] hover:text-foreground inline-flex size-9 items-center justify-center rounded-full border border-border grayscale transition-colors"
|
|
434
468
|
>
|
|
435
|
-
|
|
469
|
+
<span className="text-sm font-bold" aria-hidden="true">
|
|
470
|
+
in
|
|
471
|
+
</span>
|
|
436
472
|
</a>
|
|
437
473
|
<a
|
|
438
474
|
href={`mailto:?subject=${encodedTitle}&body=${encodedUrl}`}
|
|
439
|
-
|
|
475
|
+
aria-label="Share by email"
|
|
476
|
+
className="text-muted-foreground hover:border-[var(--blog-accent-border)] hover:bg-[var(--blog-accent-bg)] hover:text-foreground inline-flex size-9 items-center justify-center rounded-full border border-border grayscale transition-colors"
|
|
440
477
|
>
|
|
441
|
-
<Mail className="size-
|
|
442
|
-
Email
|
|
478
|
+
<Mail className="size-4" aria-hidden="true" />
|
|
443
479
|
</a>
|
|
444
480
|
</div>
|
|
445
481
|
);
|
|
@@ -462,16 +498,27 @@ function RelatedArticles({
|
|
|
462
498
|
<Link
|
|
463
499
|
key={article.id}
|
|
464
500
|
href={buildBlogPath(basePath, article.slug)}
|
|
465
|
-
className="group rounded-2xl border border-border
|
|
501
|
+
className="group overflow-hidden rounded-2xl border border-border transition-colors hover:border-[var(--blog-accent-border)] hover:bg-[var(--blog-accent-bg)] hover:no-underline"
|
|
466
502
|
>
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
503
|
+
{article.featuredImage && (
|
|
504
|
+
<ArticleCardImage
|
|
505
|
+
src={article.featuredImage}
|
|
506
|
+
alt={
|
|
507
|
+
article.featuredImageAlt ??
|
|
508
|
+
`Featured image for ${article.title}`
|
|
509
|
+
}
|
|
510
|
+
/>
|
|
474
511
|
)}
|
|
512
|
+
<div className="p-4">
|
|
513
|
+
<h3 className="text-foreground group-hover:text-[var(--blog-text-accent)] line-clamp-3 font-semibold transition-colors">
|
|
514
|
+
{article.title}
|
|
515
|
+
</h3>
|
|
516
|
+
{article.excerpt && (
|
|
517
|
+
<p className="text-muted-foreground mt-2 line-clamp-3 text-sm leading-relaxed">
|
|
518
|
+
{article.excerpt}
|
|
519
|
+
</p>
|
|
520
|
+
)}
|
|
521
|
+
</div>
|
|
475
522
|
</Link>
|
|
476
523
|
))}
|
|
477
524
|
</div>
|
|
@@ -479,6 +526,20 @@ function RelatedArticles({
|
|
|
479
526
|
);
|
|
480
527
|
}
|
|
481
528
|
|
|
529
|
+
function ArticleCardImage({ src, alt }: { src: string; alt: string }) {
|
|
530
|
+
return (
|
|
531
|
+
<div className="aspect-[16/9] overflow-hidden border-b border-border bg-[var(--blog-accent-bg)]">
|
|
532
|
+
<Image
|
|
533
|
+
src={src}
|
|
534
|
+
alt={alt}
|
|
535
|
+
width={480}
|
|
536
|
+
height={270}
|
|
537
|
+
className="h-full w-full object-cover transition-transform duration-500 group-hover:scale-105"
|
|
538
|
+
/>
|
|
539
|
+
</div>
|
|
540
|
+
);
|
|
541
|
+
}
|
|
542
|
+
|
|
482
543
|
/**
|
|
483
544
|
* Generate metadata for the article page for SEO optimization.
|
|
484
545
|
* This function should be used in the Next.js metadata API.
|
package/dist/index.d.mts
CHANGED
|
@@ -13,6 +13,8 @@ import { LibSQLDatabase } from 'drizzle-orm/libsql';
|
|
|
13
13
|
interface BlogTheme {
|
|
14
14
|
accentColor?: string;
|
|
15
15
|
accentHoverColor?: string;
|
|
16
|
+
textAccentColor?: string;
|
|
17
|
+
textAccentHoverColor?: string;
|
|
16
18
|
accentBackgroundColor?: string;
|
|
17
19
|
accentBorderColor?: string;
|
|
18
20
|
}
|
|
@@ -30,6 +32,8 @@ interface ArticleDateMeta {
|
|
|
30
32
|
type BlogThemeStyle = CSSProperties & {
|
|
31
33
|
"--blog-accent": string;
|
|
32
34
|
"--blog-accent-hover": string;
|
|
35
|
+
"--blog-text-accent": string;
|
|
36
|
+
"--blog-text-accent-hover": string;
|
|
33
37
|
"--blog-accent-bg": string;
|
|
34
38
|
"--blog-accent-border": string;
|
|
35
39
|
};
|
|
@@ -107,10 +111,14 @@ interface ArticlePageProps {
|
|
|
107
111
|
previousArticle?: {
|
|
108
112
|
url: string;
|
|
109
113
|
title: string;
|
|
114
|
+
featuredImage?: string;
|
|
115
|
+
featuredImageAlt?: string;
|
|
110
116
|
};
|
|
111
117
|
nextArticle?: {
|
|
112
118
|
url: string;
|
|
113
119
|
title: string;
|
|
120
|
+
featuredImage?: string;
|
|
121
|
+
featuredImageAlt?: string;
|
|
114
122
|
};
|
|
115
123
|
};
|
|
116
124
|
/** Public path where this blog is mounted, e.g. "/blog" or "/new-blog". */
|
package/dist/index.d.ts
CHANGED
|
@@ -13,6 +13,8 @@ import { LibSQLDatabase } from 'drizzle-orm/libsql';
|
|
|
13
13
|
interface BlogTheme {
|
|
14
14
|
accentColor?: string;
|
|
15
15
|
accentHoverColor?: string;
|
|
16
|
+
textAccentColor?: string;
|
|
17
|
+
textAccentHoverColor?: string;
|
|
16
18
|
accentBackgroundColor?: string;
|
|
17
19
|
accentBorderColor?: string;
|
|
18
20
|
}
|
|
@@ -30,6 +32,8 @@ interface ArticleDateMeta {
|
|
|
30
32
|
type BlogThemeStyle = CSSProperties & {
|
|
31
33
|
"--blog-accent": string;
|
|
32
34
|
"--blog-accent-hover": string;
|
|
35
|
+
"--blog-text-accent": string;
|
|
36
|
+
"--blog-text-accent-hover": string;
|
|
33
37
|
"--blog-accent-bg": string;
|
|
34
38
|
"--blog-accent-border": string;
|
|
35
39
|
};
|
|
@@ -107,10 +111,14 @@ interface ArticlePageProps {
|
|
|
107
111
|
previousArticle?: {
|
|
108
112
|
url: string;
|
|
109
113
|
title: string;
|
|
114
|
+
featuredImage?: string;
|
|
115
|
+
featuredImageAlt?: string;
|
|
110
116
|
};
|
|
111
117
|
nextArticle?: {
|
|
112
118
|
url: string;
|
|
113
119
|
title: string;
|
|
120
|
+
featuredImage?: string;
|
|
121
|
+
featuredImageAlt?: string;
|
|
114
122
|
};
|
|
115
123
|
};
|
|
116
124
|
/** Public path where this blog is mounted, e.g. "/blog" or "/new-blog". */
|