@xexr/blog-platform 1.0.9 → 1.0.10

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.
@@ -86,6 +86,46 @@ function formatReadingTime(seconds: number): string {
86
86
  return `${minutes} min read`;
87
87
  }
88
88
 
89
+ function formatCreditUrl(url: string): string {
90
+ try {
91
+ return new URL(url).hostname.replace(/^www\./, "");
92
+ } catch {
93
+ return url;
94
+ }
95
+ }
96
+
97
+ function FeaturedImageCredit({
98
+ creditText,
99
+ creditUrl,
100
+ }: {
101
+ creditText?: string;
102
+ creditUrl?: string;
103
+ }) {
104
+ if (!creditText && !creditUrl) {
105
+ return null;
106
+ }
107
+
108
+ const label = creditText ?? formatCreditUrl(creditUrl ?? "");
109
+
110
+ return (
111
+ <span>
112
+ Credit:{" "}
113
+ {creditUrl ? (
114
+ <a
115
+ href={creditUrl}
116
+ target="_blank"
117
+ rel="noreferrer"
118
+ className="underline underline-offset-2 transition-colors hover:text-[var(--blog-text-accent)]"
119
+ >
120
+ {label}
121
+ </a>
122
+ ) : (
123
+ label
124
+ )}
125
+ </span>
126
+ );
127
+ }
128
+
89
129
  export default function ArticlePage({
90
130
  article,
91
131
  className,
@@ -109,6 +149,11 @@ export default function ArticlePage({
109
149
 
110
150
  // Use the featured image URL provided by Bloggy (hosted on Bloggy's domain)
111
151
  const imageUrl = article.featuredImage;
152
+ const hasFeaturedImageMeta = Boolean(
153
+ article.featuredImageCaption ??
154
+ article.featuredImageCreditText ??
155
+ article.featuredImageCreditUrl,
156
+ );
112
157
 
113
158
  return (
114
159
  <article
@@ -140,6 +185,45 @@ export default function ArticlePage({
140
185
  </header>
141
186
  )}
142
187
 
188
+ <style>{`
189
+ .article-content figure > p {
190
+ margin-top: 0 !important;
191
+ margin-bottom: 0 !important;
192
+ }
193
+
194
+ .article-content figure figcaption {
195
+ margin-top: 0.5rem !important;
196
+ text-align: center;
197
+ font-size: 0.875rem !important;
198
+ line-height: 1.35 !important;
199
+ color: hsl(var(--muted-foreground));
200
+ }
201
+
202
+ .article-content figure figcaption .blog-image-credit {
203
+ display: block;
204
+ margin-top: 0.25rem;
205
+ font-size: 0.75rem;
206
+ line-height: 1.25;
207
+ letter-spacing: 0.025em;
208
+ opacity: 0.8;
209
+ }
210
+
211
+ .article-content figure figcaption > span:not(.blog-image-credit) {
212
+ margin-left: 0.5rem;
213
+ padding-left: 0.5rem;
214
+ border-left: 1px solid var(--blog-accent-border);
215
+ font-size: 0.75rem;
216
+ letter-spacing: 0.025em;
217
+ opacity: 0.8;
218
+ }
219
+
220
+ .article-content figure figcaption a {
221
+ color: inherit;
222
+ text-decoration-line: underline;
223
+ text-underline-offset: 0.18em;
224
+ }
225
+ `}</style>
226
+
143
227
  {/* Article Header */}
144
228
  <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">
145
229
  {/* Title */}
@@ -237,18 +321,38 @@ export default function ArticlePage({
237
321
 
238
322
  {/* Featured Image */}
239
323
  {imageUrl && (
240
- <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">
241
- <Image
242
- src={imageUrl}
243
- alt={
244
- article.featuredImageAlt ?? `Featured image for ${article.title}`
245
- }
246
- className="h-full w-full object-cover"
247
- width={800}
248
- height={450}
249
- priority
250
- />
251
- </div>
324
+ <figure className="article-featured-image mx-auto my-8 w-full max-w-4xl sm:my-12 lg:my-16">
325
+ <div className="aspect-[16/9] overflow-hidden rounded-3xl border border-[var(--blog-accent-border)] shadow-[0_24px_80px_rgb(168_85_247_/_0.14)]">
326
+ <Image
327
+ src={imageUrl}
328
+ alt={
329
+ article.featuredImageAlt ??
330
+ `Featured image for ${article.title}`
331
+ }
332
+ className="h-full w-full object-cover"
333
+ width={800}
334
+ height={450}
335
+ priority
336
+ />
337
+ </div>
338
+
339
+ {hasFeaturedImageMeta && (
340
+ <figcaption className="text-muted-foreground mt-3 space-y-1 px-2 text-center text-sm leading-relaxed sm:px-0">
341
+ {article.featuredImageCaption && (
342
+ <p>{article.featuredImageCaption}</p>
343
+ )}
344
+ {(article.featuredImageCreditText ??
345
+ article.featuredImageCreditUrl) && (
346
+ <p className="text-xs">
347
+ <FeaturedImageCredit
348
+ creditText={article.featuredImageCreditText}
349
+ creditUrl={article.featuredImageCreditUrl}
350
+ />
351
+ </p>
352
+ )}
353
+ </figcaption>
354
+ )}
355
+ </figure>
252
356
  )}
253
357
 
254
358
  {/* Article Content */}
@@ -265,7 +369,7 @@ export default function ArticlePage({
265
369
 
266
370
  <div
267
371
  id="article-body"
268
- 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"
372
+ 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)] [&_figure>p]:!my-0 [&_figcaption]:!mt-2 [&_figcaption]:text-center [&_figcaption]:!text-sm [&_figcaption]:!leading-snug [&_figcaption]:text-muted-foreground [&_figcaption_.blog-image-credit]:mt-1 [&_figcaption_.blog-image-credit]:block [&_figcaption_.blog-image-credit]:text-xs [&_figcaption_.blog-image-credit]:tracking-wide [&_figcaption_.blog-image-credit]:text-muted-foreground/80 [&_figcaption>span:not(.blog-image-credit)]:ml-2 [&_figcaption>span:not(.blog-image-credit)]:border-l [&_figcaption>span:not(.blog-image-credit)]:border-[var(--blog-accent-border)] [&_figcaption>span:not(.blog-image-credit)]:pl-2 [&_figcaption>span:not(.blog-image-credit)]:text-xs [&_figcaption>span:not(.blog-image-credit)]:tracking-wide [&_figcaption>span:not(.blog-image-credit)]:text-muted-foreground/80 max-w-none"
269
373
  >
270
374
  <MDXRemote
271
375
  source={article.content}
package/dist/index.d.mts CHANGED
@@ -894,6 +894,63 @@ declare const bpArticles: drizzle_orm_sqlite_core.SQLiteTableWithColumns<{
894
894
  }, {}, {
895
895
  length: number | undefined;
896
896
  }>;
897
+ featuredImageCaption: drizzle_orm_sqlite_core.SQLiteColumn<{
898
+ name: "featured_image_caption";
899
+ tableName: "bp_articles";
900
+ dataType: "string";
901
+ columnType: "SQLiteText";
902
+ data: string;
903
+ driverParam: string;
904
+ notNull: false;
905
+ hasDefault: false;
906
+ isPrimaryKey: false;
907
+ isAutoincrement: false;
908
+ hasRuntimeDefault: false;
909
+ enumValues: [string, ...string[]];
910
+ baseColumn: never;
911
+ identity: undefined;
912
+ generated: undefined;
913
+ }, {}, {
914
+ length: number | undefined;
915
+ }>;
916
+ featuredImageCreditText: drizzle_orm_sqlite_core.SQLiteColumn<{
917
+ name: "featured_image_credit_text";
918
+ tableName: "bp_articles";
919
+ dataType: "string";
920
+ columnType: "SQLiteText";
921
+ data: string;
922
+ driverParam: string;
923
+ notNull: false;
924
+ hasDefault: false;
925
+ isPrimaryKey: false;
926
+ isAutoincrement: false;
927
+ hasRuntimeDefault: false;
928
+ enumValues: [string, ...string[]];
929
+ baseColumn: never;
930
+ identity: undefined;
931
+ generated: undefined;
932
+ }, {}, {
933
+ length: number | undefined;
934
+ }>;
935
+ featuredImageCreditUrl: drizzle_orm_sqlite_core.SQLiteColumn<{
936
+ name: "featured_image_credit_url";
937
+ tableName: "bp_articles";
938
+ dataType: "string";
939
+ columnType: "SQLiteText";
940
+ data: string;
941
+ driverParam: string;
942
+ notNull: false;
943
+ hasDefault: false;
944
+ isPrimaryKey: false;
945
+ isAutoincrement: false;
946
+ hasRuntimeDefault: false;
947
+ enumValues: [string, ...string[]];
948
+ baseColumn: never;
949
+ identity: undefined;
950
+ generated: undefined;
951
+ }, {}, {
952
+ length: number | undefined;
953
+ }>;
897
954
  authorJson: drizzle_orm_sqlite_core.SQLiteColumn<{
898
955
  name: "author_json";
899
956
  tableName: "bp_articles";
package/dist/index.d.ts CHANGED
@@ -894,6 +894,63 @@ declare const bpArticles: drizzle_orm_sqlite_core.SQLiteTableWithColumns<{
894
894
  }, {}, {
895
895
  length: number | undefined;
896
896
  }>;
897
+ featuredImageCaption: drizzle_orm_sqlite_core.SQLiteColumn<{
898
+ name: "featured_image_caption";
899
+ tableName: "bp_articles";
900
+ dataType: "string";
901
+ columnType: "SQLiteText";
902
+ data: string;
903
+ driverParam: string;
904
+ notNull: false;
905
+ hasDefault: false;
906
+ isPrimaryKey: false;
907
+ isAutoincrement: false;
908
+ hasRuntimeDefault: false;
909
+ enumValues: [string, ...string[]];
910
+ baseColumn: never;
911
+ identity: undefined;
912
+ generated: undefined;
913
+ }, {}, {
914
+ length: number | undefined;
915
+ }>;
916
+ featuredImageCreditText: drizzle_orm_sqlite_core.SQLiteColumn<{
917
+ name: "featured_image_credit_text";
918
+ tableName: "bp_articles";
919
+ dataType: "string";
920
+ columnType: "SQLiteText";
921
+ data: string;
922
+ driverParam: string;
923
+ notNull: false;
924
+ hasDefault: false;
925
+ isPrimaryKey: false;
926
+ isAutoincrement: false;
927
+ hasRuntimeDefault: false;
928
+ enumValues: [string, ...string[]];
929
+ baseColumn: never;
930
+ identity: undefined;
931
+ generated: undefined;
932
+ }, {}, {
933
+ length: number | undefined;
934
+ }>;
935
+ featuredImageCreditUrl: drizzle_orm_sqlite_core.SQLiteColumn<{
936
+ name: "featured_image_credit_url";
937
+ tableName: "bp_articles";
938
+ dataType: "string";
939
+ columnType: "SQLiteText";
940
+ data: string;
941
+ driverParam: string;
942
+ notNull: false;
943
+ hasDefault: false;
944
+ isPrimaryKey: false;
945
+ isAutoincrement: false;
946
+ hasRuntimeDefault: false;
947
+ enumValues: [string, ...string[]];
948
+ baseColumn: never;
949
+ identity: undefined;
950
+ generated: undefined;
951
+ }, {}, {
952
+ length: number | undefined;
953
+ }>;
897
954
  authorJson: drizzle_orm_sqlite_core.SQLiteColumn<{
898
955
  name: "author_json";
899
956
  tableName: "bp_articles";