@levino/shipyard-blog 0.6.3 → 0.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -135,38 +135,118 @@ const tagBaseUrl = i18n
135
135
 
136
136
  <Layout title={blogConfig.blogTitle}>
137
137
  <div class="max-w-7xl mx-auto">
138
- <h1 class="text-4xl font-bold mb-8">{blogConfig.blogTitle}</h1>
139
- <div class="space-y-8">
138
+ <h1 class="text-4xl font-bold mb-10">{blogConfig.blogTitle}</h1>
139
+ <div class="space-y-6">
140
140
  {
141
- paginatedEntries.map((entry) => {
141
+ paginatedEntries.map((entry, index) => {
142
142
  const readingTime = showReadingTime && entry.body
143
143
  ? getReadingTime(entry.body)
144
144
  : undefined
145
145
  const tags = entry.data.tags ?? []
146
+ const postLink = `/${getBlogPostLink(entry.id, Astro.currentLocale)}`
147
+ const hasImage = !!entry.data.image
148
+ const isFirst = index === 0
146
149
 
147
- return (
148
- <article class="border-b border-base-300 pb-8 last:border-b-0" data-testid="blog-post-item">
149
- <a class="block no-underline hover:text-primary transition-colors" href={`/${getBlogPostLink(entry.id, Astro.currentLocale)}`} data-testid="blog-post-link">
150
+ return isFirst && hasImage ? (
151
+ /* Featured first post — full-width card with image */
152
+ <article class="card bg-base-100 shadow-sm" data-testid="blog-post-item">
153
+ <figure>
154
+ <a href={postLink} data-testid="blog-post-link">
155
+ <img
156
+ src={entry.data.image}
157
+ alt={entry.data.title}
158
+ class="w-full object-cover"
159
+ loading="eager"
160
+ />
161
+ </a>
162
+ </figure>
163
+ <div class="card-body">
150
164
  {entry.data.draft && (
151
- <span class="badge badge-warning mb-2">Draft</span>
165
+ <span class="badge badge-warning">Draft</span>
152
166
  )}
153
- <h2 class="text-2xl font-semibold my-0">{entry.data.title}</h2>
154
- </a>
155
- <div class="flex flex-wrap items-center gap-2 text-sm text-base-content/60 mt-2">
156
- <time datetime={entry.data.date.toISOString()}>{formatDate(entry.data.date)}</time>
157
- {readingTime && (
158
- <>
159
- <span>·</span>
160
- <BlogReadingTime text={readingTime.text} />
161
- </>
167
+ <div class="flex flex-wrap items-center gap-2 text-sm text-base-content/60">
168
+ <time datetime={entry.data.date.toISOString()}>{formatDate(entry.data.date)}</time>
169
+ {readingTime && (
170
+ <>
171
+ <span>&middot;</span>
172
+ <BlogReadingTime text={readingTime.text} />
173
+ </>
174
+ )}
175
+ </div>
176
+ <a class="no-underline" href={postLink}>
177
+ <h2 class="card-title text-2xl md:text-3xl">{entry.data.title}</h2>
178
+ </a>
179
+ <p class="text-base-content/70">{entry.data.description}</p>
180
+ {tags.length > 0 && (
181
+ <div class="card-actions">
182
+ <BlogTags tags={tags} baseUrl={tagBaseUrl} />
183
+ </div>
184
+ )}
185
+ </div>
186
+ </article>
187
+ ) : hasImage ? (
188
+ /* Post with image — side card */
189
+ <article class="card card-side bg-base-100 shadow-sm" data-testid="blog-post-item">
190
+ <figure class="hidden w-48 flex-shrink-0 sm:block">
191
+ <a href={postLink}>
192
+ <img
193
+ src={entry.data.image}
194
+ alt={entry.data.title}
195
+ class="h-full w-full object-cover"
196
+ loading="lazy"
197
+ />
198
+ </a>
199
+ </figure>
200
+ <div class="card-body">
201
+ {entry.data.draft && (
202
+ <span class="badge badge-warning">Draft</span>
203
+ )}
204
+ <div class="flex flex-wrap items-center gap-2 text-sm text-base-content/60">
205
+ <time datetime={entry.data.date.toISOString()}>{formatDate(entry.data.date)}</time>
206
+ {readingTime && (
207
+ <>
208
+ <span>&middot;</span>
209
+ <BlogReadingTime text={readingTime.text} />
210
+ </>
211
+ )}
212
+ </div>
213
+ <a class="no-underline" href={postLink} data-testid="blog-post-link">
214
+ <h2 class="card-title">{entry.data.title}</h2>
215
+ </a>
216
+ <p class="line-clamp-2 text-sm text-base-content/70">{entry.data.description}</p>
217
+ {tags.length > 0 && (
218
+ <div class="card-actions">
219
+ <BlogTags tags={tags} baseUrl={tagBaseUrl} />
220
+ </div>
162
221
  )}
163
222
  </div>
164
- <p class="mt-2 text-base-content/80">{entry.data.description}</p>
165
- {tags.length > 0 && (
166
- <div class="mt-3">
167
- <BlogTags tags={tags} baseUrl={tagBaseUrl} />
223
+ </article>
224
+ ) : (
225
+ /* Post without image — simple card */
226
+ <article class="card bg-base-100 shadow-sm" data-testid="blog-post-item">
227
+ <div class="card-body">
228
+ {entry.data.draft && (
229
+ <span class="badge badge-warning">Draft</span>
230
+ )}
231
+ <div class="flex flex-wrap items-center gap-2 text-sm text-base-content/60">
232
+ <time datetime={entry.data.date.toISOString()}>{formatDate(entry.data.date)}</time>
233
+ {readingTime && (
234
+ <>
235
+ <span>&middot;</span>
236
+ <BlogReadingTime text={readingTime.text} />
237
+ </>
238
+ )}
168
239
  </div>
169
- )}
240
+ <a class="no-underline" href={postLink} data-testid="blog-post-link">
241
+ <h2 class="card-title">{entry.data.title}</h2>
242
+ </a>
243
+ <p class="line-clamp-2 text-sm text-base-content/70">{entry.data.description}</p>
244
+ {tags.length > 0 && (
245
+ <div class="card-actions">
246
+ <BlogTags tags={tags} baseUrl={tagBaseUrl} />
247
+ </div>
248
+ )}
249
+ </div>
170
250
  </article>
171
251
  )
172
252
  })
@@ -115,15 +115,37 @@ const showRightEllipsis =
115
115
 
116
116
  <Layout title="Blog">
117
117
  <div class="max-w-7xl mx-auto">
118
- <div class="prose max-w-none">
118
+ <div class="space-y-6">
119
119
  {
120
- paginatedEntries.map(async (entry) => (
121
- <a class="block my-8 no-underline" href={`/${getBlogPostLink(entry.id, Astro.currentLocale)}`}>
122
- <div class="text-sm">{formatDate(entry.data.date)}</div>
123
- <h2 class="my-0">{entry.data.title}</h2>
124
- <p>{entry.data.description}</p>
125
- </a>
126
- ))
120
+ paginatedEntries.map(async (entry) => {
121
+ const postLink = `/${getBlogPostLink(entry.id, Astro.currentLocale)}`
122
+ const hasImage = !!entry.data.image
123
+ return (
124
+ <article class:list={["card bg-base-100 shadow-sm", { "card-side": hasImage }]} data-testid="blog-post-item">
125
+ {hasImage && (
126
+ <figure class="hidden w-48 flex-shrink-0 sm:block">
127
+ <a href={postLink}>
128
+ <img
129
+ src={entry.data.image}
130
+ alt={entry.data.title}
131
+ class="h-full w-full object-cover"
132
+ loading="lazy"
133
+ />
134
+ </a>
135
+ </figure>
136
+ )}
137
+ <div class="card-body">
138
+ <div class="text-sm text-base-content/60">
139
+ <time datetime={entry.data.date.toISOString()}>{formatDate(entry.data.date)}</time>
140
+ </div>
141
+ <a class="no-underline" href={postLink} data-testid="blog-post-link">
142
+ <h2 class="card-title">{entry.data.title}</h2>
143
+ </a>
144
+ <p class="line-clamp-2 text-sm text-base-content/70">{entry.data.description}</p>
145
+ </div>
146
+ </article>
147
+ )
148
+ })
127
149
  }
128
150
  </div>
129
151
 
@@ -17,11 +17,11 @@ const { tags, baseUrl = '/blog/tags' } = Astro.props
17
17
  ---
18
18
 
19
19
  {tags.length > 0 && (
20
- <div class="flex flex-wrap gap-2">
20
+ <div class="flex flex-wrap gap-1.5">
21
21
  {tags.map((tag) => (
22
22
  <a
23
23
  href={`${baseUrl}/${encodeURIComponent(tag.toLowerCase())}`}
24
- class="badge badge-outline hover:badge-primary transition-colors"
24
+ class="badge badge-soft badge-sm no-underline hover:badge-primary"
25
25
  >
26
26
  {tag}
27
27
  </a>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@levino/shipyard-blog",
3
- "version": "0.6.3",
3
+ "version": "0.7.1",
4
4
  "description": "Blog plugin for shipyard with pagination, sidebar, and git metadata",
5
5
  "type": "module",
6
6
  "main": "src/index.ts",
@@ -31,7 +31,7 @@
31
31
  "license": "MIT",
32
32
  "homepage": "https://shipyard.levinkeller.de",
33
33
  "dependencies": {
34
- "@levino/shipyard-base": "^0.7.0",
34
+ "@levino/shipyard-base": "^0.7.1",
35
35
  "ramda": "^0.31",
36
36
  "yaml": "^2.0.0"
37
37
  },