@jant/core 0.3.22 → 0.3.24
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.js +23 -5
- package/dist/db/schema.js +72 -47
- package/dist/i18n/locales/en.js +1 -1
- package/dist/i18n/locales/zh-Hans.js +1 -1
- package/dist/i18n/locales/zh-Hant.js +1 -1
- package/dist/index.js +5 -6
- package/dist/lib/constants.js +1 -4
- package/dist/lib/excerpt.js +76 -0
- package/dist/lib/feed.js +18 -7
- package/dist/lib/navigation.js +4 -5
- package/dist/lib/render.js +1 -1
- package/dist/lib/schemas.js +80 -38
- package/dist/lib/theme-components.js +8 -11
- package/dist/lib/time.js +56 -1
- package/dist/lib/timeline.js +119 -0
- package/dist/lib/view.js +62 -73
- package/dist/routes/api/posts.js +29 -35
- package/dist/routes/api/search.js +5 -6
- package/dist/routes/api/upload.js +13 -13
- package/dist/routes/dash/collections.js +22 -40
- package/dist/routes/dash/index.js +2 -2
- package/dist/routes/dash/navigation.js +25 -24
- package/dist/routes/dash/pages.js +42 -57
- package/dist/routes/dash/posts.js +27 -35
- package/dist/routes/feed/rss.js +2 -4
- package/dist/routes/feed/sitemap.js +10 -7
- package/dist/routes/pages/archive.js +12 -11
- package/dist/routes/pages/collection.js +11 -5
- package/dist/routes/pages/home.js +53 -61
- package/dist/routes/pages/page.js +60 -29
- package/dist/routes/pages/post.js +5 -12
- package/dist/routes/pages/search.js +3 -4
- package/dist/services/collection.js +52 -64
- package/dist/services/index.js +5 -3
- package/dist/services/navigation.js +29 -53
- package/dist/services/page.js +80 -0
- package/dist/services/post.js +68 -69
- package/dist/services/search.js +24 -18
- package/dist/theme/components/MediaGallery.js +19 -91
- package/dist/theme/components/PageForm.js +15 -15
- package/dist/theme/components/PostForm.js +136 -129
- package/dist/theme/components/PostList.js +13 -8
- package/dist/theme/components/ThreadView.js +3 -3
- package/dist/theme/components/TypeBadge.js +3 -14
- package/dist/theme/components/VisibilityBadge.js +33 -23
- package/dist/theme/components/index.js +0 -2
- package/dist/theme/index.js +10 -16
- package/dist/theme/layouts/index.js +0 -1
- package/dist/themes/threads/ThreadsSiteLayout.js +172 -0
- package/dist/themes/threads/index.js +81 -0
- package/dist/{theme → themes/threads}/pages/ArchivePage.js +31 -47
- package/dist/themes/threads/pages/CollectionPage.js +65 -0
- package/dist/{theme → themes/threads}/pages/HomePage.js +4 -5
- package/dist/{theme → themes/threads}/pages/PostPage.js +10 -8
- package/dist/{theme → themes/threads}/pages/SearchPage.js +8 -8
- package/dist/{theme → themes/threads}/pages/SinglePage.js +5 -6
- package/dist/{theme/components → themes/threads}/timeline/LinkCard.js +20 -11
- package/dist/themes/threads/timeline/NoteCard.js +53 -0
- package/dist/themes/threads/timeline/QuoteCard.js +59 -0
- package/dist/{theme/components → themes/threads}/timeline/ThreadPreview.js +5 -6
- package/dist/themes/threads/timeline/TimelineFeed.js +58 -0
- package/dist/{theme/components → themes/threads}/timeline/TimelineItem.js +8 -17
- package/dist/themes/threads/timeline/TimelineLoadMore.js +23 -0
- package/dist/themes/threads/timeline/groupByDate.js +22 -0
- package/dist/themes/threads/timeline/timelineMore.js +107 -0
- package/dist/types.js +24 -40
- package/package.json +2 -1
- package/src/__tests__/helpers/app.ts +4 -0
- package/src/__tests__/helpers/db.ts +51 -74
- package/src/app.tsx +27 -6
- package/src/db/migrations/0005_v2_schema_migration.sql +268 -0
- package/src/db/migrations/meta/_journal.json +7 -0
- package/src/db/schema.ts +63 -46
- package/src/i18n/locales/en.po +216 -164
- package/src/i18n/locales/en.ts +1 -1
- package/src/i18n/locales/zh-Hans.po +216 -164
- package/src/i18n/locales/zh-Hans.ts +1 -1
- package/src/i18n/locales/zh-Hant.po +216 -164
- package/src/i18n/locales/zh-Hant.ts +1 -1
- package/src/index.ts +30 -15
- package/src/lib/__tests__/excerpt.test.ts +125 -0
- package/src/lib/__tests__/schemas.test.ts +166 -105
- package/src/lib/__tests__/theme-components.test.ts +4 -25
- package/src/lib/__tests__/time.test.ts +62 -0
- package/src/{routes/api → lib}/__tests__/timeline.test.ts +108 -66
- package/src/lib/__tests__/view.test.ts +217 -67
- package/src/lib/constants.ts +1 -4
- package/src/lib/excerpt.ts +87 -0
- package/src/lib/feed.ts +22 -7
- package/src/lib/navigation.ts +6 -7
- package/src/lib/render.tsx +1 -1
- package/src/lib/schemas.ts +118 -52
- package/src/lib/theme-components.ts +10 -13
- package/src/lib/time.ts +64 -0
- package/src/lib/timeline.ts +170 -0
- package/src/lib/view.ts +81 -83
- package/src/preset.css +45 -0
- package/src/routes/api/__tests__/posts.test.ts +50 -108
- package/src/routes/api/__tests__/search.test.ts +2 -3
- package/src/routes/api/posts.ts +30 -30
- package/src/routes/api/search.ts +4 -4
- package/src/routes/api/upload.ts +16 -6
- package/src/routes/dash/collections.tsx +18 -40
- package/src/routes/dash/index.tsx +2 -2
- package/src/routes/dash/navigation.tsx +27 -26
- package/src/routes/dash/pages.tsx +45 -60
- package/src/routes/dash/posts.tsx +44 -52
- package/src/routes/feed/rss.ts +2 -1
- package/src/routes/feed/sitemap.ts +14 -4
- package/src/routes/pages/archive.tsx +14 -10
- package/src/routes/pages/collection.tsx +17 -6
- package/src/routes/pages/home.tsx +56 -81
- package/src/routes/pages/page.tsx +64 -27
- package/src/routes/pages/post.tsx +5 -14
- package/src/routes/pages/search.tsx +2 -2
- package/src/services/__tests__/collection.test.ts +257 -158
- package/src/services/__tests__/media.test.ts +18 -18
- package/src/services/__tests__/navigation.test.ts +161 -87
- package/src/services/__tests__/post-timeline.test.ts +92 -88
- package/src/services/__tests__/post.test.ts +342 -206
- package/src/services/__tests__/search.test.ts +19 -25
- package/src/services/collection.ts +71 -113
- package/src/services/index.ts +9 -8
- package/src/services/navigation.ts +38 -71
- package/src/services/page.ts +124 -0
- package/src/services/post.ts +93 -103
- package/src/services/search.ts +38 -27
- package/src/styles/components.css +0 -54
- package/src/theme/components/MediaGallery.tsx +27 -96
- package/src/theme/components/PageForm.tsx +21 -21
- package/src/theme/components/PostForm.tsx +122 -118
- package/src/theme/components/PostList.tsx +58 -49
- package/src/theme/components/ThreadView.tsx +6 -3
- package/src/theme/components/TypeBadge.tsx +9 -17
- package/src/theme/components/VisibilityBadge.tsx +40 -23
- package/src/theme/components/index.ts +0 -13
- package/src/theme/index.ts +10 -16
- package/src/theme/layouts/index.ts +0 -1
- package/src/themes/threads/ThreadsSiteLayout.tsx +194 -0
- package/src/themes/threads/index.ts +100 -0
- package/src/{theme → themes/threads}/pages/ArchivePage.tsx +52 -55
- package/src/themes/threads/pages/CollectionPage.tsx +61 -0
- package/src/{theme → themes/threads}/pages/HomePage.tsx +5 -6
- package/src/{theme → themes/threads}/pages/PostPage.tsx +11 -8
- package/src/{theme → themes/threads}/pages/SearchPage.tsx +9 -13
- package/src/themes/threads/pages/SinglePage.tsx +23 -0
- package/src/themes/threads/style.css +336 -0
- package/src/{theme/components → themes/threads}/timeline/LinkCard.tsx +21 -13
- package/src/themes/threads/timeline/NoteCard.tsx +58 -0
- package/src/themes/threads/timeline/QuoteCard.tsx +63 -0
- package/src/{theme/components → themes/threads}/timeline/ThreadPreview.tsx +6 -6
- package/src/themes/threads/timeline/TimelineFeed.tsx +62 -0
- package/src/{theme/components → themes/threads}/timeline/TimelineItem.tsx +9 -20
- package/src/themes/threads/timeline/TimelineLoadMore.tsx +35 -0
- package/src/themes/threads/timeline/groupByDate.ts +30 -0
- package/src/themes/threads/timeline/timelineMore.tsx +130 -0
- package/src/types.ts +242 -98
- package/dist/routes/api/timeline.js +0 -120
- package/dist/theme/components/timeline/ArticleCard.js +0 -46
- package/dist/theme/components/timeline/ImageCard.js +0 -83
- package/dist/theme/components/timeline/NoteCard.js +0 -34
- package/dist/theme/components/timeline/QuoteCard.js +0 -48
- package/dist/theme/components/timeline/TimelineFeed.js +0 -46
- package/dist/theme/components/timeline/index.js +0 -8
- package/dist/theme/layouts/SiteLayout.js +0 -131
- package/dist/theme/pages/CollectionPage.js +0 -63
- package/dist/theme/pages/index.js +0 -11
- package/src/routes/api/timeline.tsx +0 -159
- package/src/theme/components/timeline/ArticleCard.tsx +0 -45
- package/src/theme/components/timeline/ImageCard.tsx +0 -70
- package/src/theme/components/timeline/NoteCard.tsx +0 -34
- package/src/theme/components/timeline/QuoteCard.tsx +0 -48
- package/src/theme/components/timeline/TimelineFeed.tsx +0 -56
- package/src/theme/components/timeline/index.ts +0 -8
- package/src/theme/layouts/SiteLayout.tsx +0 -132
- package/src/theme/pages/CollectionPage.tsx +0 -60
- package/src/theme/pages/SinglePage.tsx +0 -24
- package/src/theme/pages/index.ts +0 -13
package/src/lib/view.ts
CHANGED
|
@@ -1,33 +1,44 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* View Model Conversions
|
|
2
|
+
* View Model Conversions (v2)
|
|
3
3
|
*
|
|
4
4
|
* Transforms raw database models into render-ready View types.
|
|
5
|
-
* Theme components receive only View types
|
|
5
|
+
* Theme components receive only View types -- no lib/ imports needed.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import type { Context } from "hono";
|
|
9
9
|
import type {
|
|
10
10
|
Post,
|
|
11
11
|
PostWithMedia,
|
|
12
|
+
Page,
|
|
12
13
|
Media,
|
|
13
14
|
MediaView,
|
|
14
15
|
PostView,
|
|
15
|
-
|
|
16
|
-
|
|
16
|
+
PageView,
|
|
17
|
+
NavItemView,
|
|
18
|
+
NavItem,
|
|
17
19
|
SearchResult,
|
|
18
20
|
SearchResultView,
|
|
19
21
|
ArchiveGroup,
|
|
22
|
+
Format,
|
|
23
|
+
Status,
|
|
24
|
+
NavItemType,
|
|
20
25
|
} from "../types.js";
|
|
21
26
|
import { encode } from "./sqid.js";
|
|
22
|
-
import {
|
|
27
|
+
import {
|
|
28
|
+
toISOString,
|
|
29
|
+
formatDate,
|
|
30
|
+
formatTime,
|
|
31
|
+
formatRelativeTime,
|
|
32
|
+
} from "./time.js";
|
|
23
33
|
import { getMediaUrl, getImageUrl, getPublicUrlForProvider } from "./image.js";
|
|
34
|
+
import { getHtmlExcerpt } from "./excerpt.js";
|
|
24
35
|
|
|
25
36
|
// =============================================================================
|
|
26
37
|
// Media Context
|
|
27
38
|
// =============================================================================
|
|
28
39
|
|
|
29
40
|
/**
|
|
30
|
-
* Central media config
|
|
41
|
+
* Central media config -- extracted once per request from env.
|
|
31
42
|
*/
|
|
32
43
|
export interface MediaContext {
|
|
33
44
|
r2PublicUrl?: string;
|
|
@@ -40,12 +51,6 @@ export interface MediaContext {
|
|
|
40
51
|
*
|
|
41
52
|
* @param c - Hono context
|
|
42
53
|
* @returns MediaContext with env values
|
|
43
|
-
*
|
|
44
|
-
* @example
|
|
45
|
-
* ```ts
|
|
46
|
-
* const mediaCtx = createMediaContext(c);
|
|
47
|
-
* const postView = toPostView(post, mediaCtx);
|
|
48
|
-
* ```
|
|
49
54
|
*/
|
|
50
55
|
export function createMediaContext(c: Context): MediaContext {
|
|
51
56
|
return {
|
|
@@ -100,25 +105,26 @@ export function toMediaView(media: Media, ctx: MediaContext): MediaView {
|
|
|
100
105
|
* Converts a PostWithMedia to a render-ready PostView.
|
|
101
106
|
*
|
|
102
107
|
* @param post - Post with media attachments from database
|
|
103
|
-
* @param
|
|
108
|
+
* @param _ctx - Media context with URL configuration
|
|
104
109
|
* @returns Render-ready PostView with pre-computed fields
|
|
105
|
-
*
|
|
106
|
-
* @example
|
|
107
|
-
* ```ts
|
|
108
|
-
* const mediaCtx = createMediaContext(c);
|
|
109
|
-
* const postView = toPostView({ ...post, mediaAttachments: [...] }, mediaCtx);
|
|
110
|
-
* ```
|
|
111
110
|
*/
|
|
112
|
-
export function toPostView(post: PostWithMedia,
|
|
113
|
-
const permalink = `/p/${encode(post.id)}`;
|
|
111
|
+
export function toPostView(post: PostWithMedia, _ctx: MediaContext): PostView {
|
|
112
|
+
const permalink = post.slug ? `/${post.slug}` : `/p/${encode(post.id)}`;
|
|
114
113
|
|
|
115
|
-
// Pre-compute excerpt from raw
|
|
114
|
+
// Pre-compute excerpt from raw body
|
|
116
115
|
let excerpt: string | undefined;
|
|
117
|
-
if (post.
|
|
116
|
+
if (post.body) {
|
|
118
117
|
excerpt =
|
|
119
|
-
post.
|
|
120
|
-
|
|
121
|
-
|
|
118
|
+
post.body.length > 160 ? post.body.slice(0, 160) + "..." : post.body;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Pre-compute HTML summary for article-style posts (with title)
|
|
122
|
+
let summaryHtml: string | undefined;
|
|
123
|
+
let summaryHasMore: boolean | undefined;
|
|
124
|
+
if (post.title && post.bodyHtml) {
|
|
125
|
+
const result = getHtmlExcerpt(post.bodyHtml);
|
|
126
|
+
summaryHtml = result.excerpt;
|
|
127
|
+
summaryHasMore = result.hasMore;
|
|
122
128
|
}
|
|
123
129
|
|
|
124
130
|
// Convert media attachments
|
|
@@ -135,31 +141,34 @@ export function toPostView(post: PostWithMedia, ctx: MediaContext): PostView {
|
|
|
135
141
|
return {
|
|
136
142
|
id: post.id,
|
|
137
143
|
permalink,
|
|
144
|
+
slug: post.slug ?? undefined,
|
|
138
145
|
title: post.title ?? undefined,
|
|
139
|
-
|
|
146
|
+
bodyHtml: post.bodyHtml ?? undefined,
|
|
140
147
|
excerpt,
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
148
|
+
summaryHtml,
|
|
149
|
+
summaryHasMore,
|
|
150
|
+
url: post.url ?? undefined,
|
|
151
|
+
quoteText: post.quoteText ?? undefined,
|
|
152
|
+
format: post.format as Format,
|
|
153
|
+
status: post.status as Status,
|
|
154
|
+
featured: post.featured === 1,
|
|
155
|
+
pinned: post.pinned === 1,
|
|
156
|
+
rating: post.rating ?? undefined,
|
|
157
|
+
collectionId: post.collectionId ?? undefined,
|
|
144
158
|
publishedAt: toISOString(post.publishedAt),
|
|
145
159
|
publishedAtFormatted: formatDate(post.publishedAt),
|
|
160
|
+
publishedAtTime: formatTime(post.publishedAt),
|
|
161
|
+
publishedAtRelative: formatRelativeTime(post.publishedAt),
|
|
146
162
|
updatedAt: toISOString(post.updatedAt),
|
|
147
|
-
sourceUrl: post.sourceUrl ?? undefined,
|
|
148
|
-
sourceName: post.sourceName ?? undefined,
|
|
149
|
-
sourceDomain: post.sourceDomain ?? undefined,
|
|
150
163
|
media,
|
|
151
164
|
replyToId: post.replyToId ?? undefined,
|
|
152
165
|
threadRootId: post.threadId ?? undefined,
|
|
153
|
-
|
|
166
|
+
body: post.body ?? undefined,
|
|
154
167
|
};
|
|
155
168
|
}
|
|
156
169
|
|
|
157
170
|
/**
|
|
158
171
|
* Batch converts PostWithMedia[] to PostView[].
|
|
159
|
-
*
|
|
160
|
-
* @param posts - Array of posts with media
|
|
161
|
-
* @param ctx - Media context
|
|
162
|
-
* @returns Array of PostView
|
|
163
172
|
*/
|
|
164
173
|
export function toPostViews(
|
|
165
174
|
posts: PostWithMedia[],
|
|
@@ -170,10 +179,6 @@ export function toPostViews(
|
|
|
170
179
|
|
|
171
180
|
/**
|
|
172
181
|
* Converts a bare Post (no media) to a PostView with empty media array.
|
|
173
|
-
*
|
|
174
|
-
* @param post - Post without media
|
|
175
|
-
* @param ctx - Media context (unused but kept for consistency)
|
|
176
|
-
* @returns PostView with empty media
|
|
177
182
|
*/
|
|
178
183
|
export function toPostViewFromPost(post: Post, ctx: MediaContext): PostView {
|
|
179
184
|
return toPostView({ ...post, mediaAttachments: [] }, ctx);
|
|
@@ -181,10 +186,6 @@ export function toPostViewFromPost(post: Post, ctx: MediaContext): PostView {
|
|
|
181
186
|
|
|
182
187
|
/**
|
|
183
188
|
* Batch converts Post[] (no media) to PostView[].
|
|
184
|
-
*
|
|
185
|
-
* @param posts - Array of posts without media
|
|
186
|
-
* @param ctx - Media context
|
|
187
|
-
* @returns Array of PostView
|
|
188
189
|
*/
|
|
189
190
|
export function toPostViewsFromPosts(
|
|
190
191
|
posts: Post[],
|
|
@@ -193,55 +194,65 @@ export function toPostViewsFromPosts(
|
|
|
193
194
|
return posts.map((p) => toPostViewFromPost(p, ctx));
|
|
194
195
|
}
|
|
195
196
|
|
|
197
|
+
// =============================================================================
|
|
198
|
+
// Page Conversions
|
|
199
|
+
// =============================================================================
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Converts a Page to a render-ready PageView.
|
|
203
|
+
*/
|
|
204
|
+
export function toPageView(page: Page): PageView {
|
|
205
|
+
return {
|
|
206
|
+
id: page.id,
|
|
207
|
+
slug: page.slug,
|
|
208
|
+
title: page.title ?? undefined,
|
|
209
|
+
bodyHtml: page.bodyHtml ?? undefined,
|
|
210
|
+
status: page.status as Status,
|
|
211
|
+
createdAt: toISOString(page.createdAt),
|
|
212
|
+
updatedAt: toISOString(page.updatedAt),
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
|
|
196
216
|
// =============================================================================
|
|
197
217
|
// Navigation Conversions
|
|
198
218
|
// =============================================================================
|
|
199
219
|
|
|
200
220
|
/**
|
|
201
|
-
* Converts a
|
|
202
|
-
*
|
|
203
|
-
* @param link - Raw navigation link from database
|
|
204
|
-
* @param currentPath - Current page path for active state computation
|
|
205
|
-
* @returns NavLinkView with isActive and isExternal pre-computed
|
|
221
|
+
* Converts a NavItem to a NavItemView with pre-computed state.
|
|
206
222
|
*/
|
|
207
|
-
export function
|
|
208
|
-
link: NavigationLink,
|
|
209
|
-
currentPath: string,
|
|
210
|
-
): NavLinkView {
|
|
223
|
+
export function toNavItemView(item: NavItem, currentPath: string): NavItemView {
|
|
211
224
|
const isExternal =
|
|
212
|
-
|
|
225
|
+
item.url.startsWith("http://") || item.url.startsWith("https://");
|
|
213
226
|
|
|
214
227
|
let isActive = false;
|
|
215
228
|
if (!isExternal) {
|
|
216
|
-
if (
|
|
229
|
+
if (item.url === "/") {
|
|
217
230
|
isActive = currentPath === "/";
|
|
218
231
|
} else {
|
|
219
232
|
isActive =
|
|
220
|
-
currentPath ===
|
|
233
|
+
currentPath === item.url || currentPath.startsWith(item.url + "/");
|
|
221
234
|
}
|
|
222
235
|
}
|
|
223
236
|
|
|
224
237
|
return {
|
|
225
|
-
id:
|
|
226
|
-
|
|
227
|
-
|
|
238
|
+
id: item.id,
|
|
239
|
+
type: item.type as NavItemType,
|
|
240
|
+
label: item.label,
|
|
241
|
+
url: item.url,
|
|
242
|
+
pageId: item.pageId ?? undefined,
|
|
228
243
|
isActive,
|
|
229
244
|
isExternal,
|
|
230
245
|
};
|
|
231
246
|
}
|
|
232
247
|
|
|
233
248
|
/**
|
|
234
|
-
* Batch converts
|
|
235
|
-
*
|
|
236
|
-
* @param links - Raw navigation links
|
|
237
|
-
* @param currentPath - Current page path
|
|
238
|
-
* @returns Array of NavLinkView
|
|
249
|
+
* Batch converts NavItem[] to NavItemView[].
|
|
239
250
|
*/
|
|
240
|
-
export function
|
|
241
|
-
|
|
251
|
+
export function toNavItemViews(
|
|
252
|
+
items: NavItem[],
|
|
242
253
|
currentPath: string,
|
|
243
|
-
):
|
|
244
|
-
return
|
|
254
|
+
): NavItemView[] {
|
|
255
|
+
return items.map((item) => toNavItemView(item, currentPath));
|
|
245
256
|
}
|
|
246
257
|
|
|
247
258
|
// =============================================================================
|
|
@@ -250,10 +261,6 @@ export function toNavLinkViews(
|
|
|
250
261
|
|
|
251
262
|
/**
|
|
252
263
|
* Converts a SearchResult to a SearchResultView with PostView.
|
|
253
|
-
*
|
|
254
|
-
* @param result - Raw search result
|
|
255
|
-
* @param ctx - Media context
|
|
256
|
-
* @returns SearchResultView with PostView
|
|
257
264
|
*/
|
|
258
265
|
export function toSearchResultView(
|
|
259
266
|
result: SearchResult,
|
|
@@ -268,10 +275,6 @@ export function toSearchResultView(
|
|
|
268
275
|
|
|
269
276
|
/**
|
|
270
277
|
* Batch converts SearchResult[] to SearchResultView[].
|
|
271
|
-
*
|
|
272
|
-
* @param results - Raw search results
|
|
273
|
-
* @param ctx - Media context
|
|
274
|
-
* @returns Array of SearchResultView
|
|
275
278
|
*/
|
|
276
279
|
export function toSearchResultViews(
|
|
277
280
|
results: SearchResult[],
|
|
@@ -286,10 +289,6 @@ export function toSearchResultViews(
|
|
|
286
289
|
|
|
287
290
|
/**
|
|
288
291
|
* Converts a grouped post map to typed ArchiveGroup[].
|
|
289
|
-
*
|
|
290
|
-
* @param grouped - Map of "YYYY-MM" keys to Post arrays
|
|
291
|
-
* @param ctx - Media context
|
|
292
|
-
* @returns Array of ArchiveGroup with pre-formatted labels
|
|
293
292
|
*/
|
|
294
293
|
export function toArchiveGroups(
|
|
295
294
|
grouped: Map<string, Post[]>,
|
|
@@ -300,7 +299,6 @@ export function toArchiveGroups(
|
|
|
300
299
|
const [year, month] = yearMonth.split("-");
|
|
301
300
|
if (!year || !month) continue;
|
|
302
301
|
|
|
303
|
-
// Format label like "February 2024"
|
|
304
302
|
const date = new Date(parseInt(year, 10), parseInt(month, 10) - 1);
|
|
305
303
|
const label = date.toLocaleDateString("en-US", {
|
|
306
304
|
year: "numeric",
|
package/src/preset.css
CHANGED
|
@@ -8,7 +8,9 @@
|
|
|
8
8
|
@source "./";
|
|
9
9
|
|
|
10
10
|
@import "basecoat-css";
|
|
11
|
+
@plugin "@tailwindcss/typography";
|
|
11
12
|
@import "./styles/components.css";
|
|
13
|
+
@import "./themes/threads/style.css";
|
|
12
14
|
|
|
13
15
|
@theme {
|
|
14
16
|
--radius-default: 0.5rem;
|
|
@@ -22,3 +24,46 @@
|
|
|
22
24
|
.dark {
|
|
23
25
|
--success: oklch(0.627 0.194 149.214);
|
|
24
26
|
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Typography (prose) — integrate with BaseCoat theme colors.
|
|
30
|
+
*
|
|
31
|
+
* The @tailwindcss/typography plugin ships with hardcoded slate values.
|
|
32
|
+
* We override them to use BaseCoat's CSS variables so that:
|
|
33
|
+
* - Light/dark mode switches automatically
|
|
34
|
+
* - Prose text matches the rest of the theme
|
|
35
|
+
* - Links are understated (same color as text, underline only)
|
|
36
|
+
*
|
|
37
|
+
* Design reference: Medium, Substack, NYT — body links inherit text
|
|
38
|
+
* color and rely on underline as the sole affordance.
|
|
39
|
+
*/
|
|
40
|
+
.prose {
|
|
41
|
+
--tw-prose-body: inherit;
|
|
42
|
+
--tw-prose-headings: var(--color-foreground);
|
|
43
|
+
--tw-prose-lead: var(--color-muted-foreground);
|
|
44
|
+
--tw-prose-links: inherit;
|
|
45
|
+
--tw-prose-bold: inherit;
|
|
46
|
+
--tw-prose-counters: var(--color-muted-foreground);
|
|
47
|
+
--tw-prose-bullets: var(--color-muted-foreground);
|
|
48
|
+
--tw-prose-hr: var(--color-border);
|
|
49
|
+
--tw-prose-quotes: var(--color-foreground);
|
|
50
|
+
--tw-prose-quote-borders: var(--color-border);
|
|
51
|
+
--tw-prose-captions: var(--color-muted-foreground);
|
|
52
|
+
--tw-prose-kbd: var(--color-foreground);
|
|
53
|
+
--tw-prose-code: var(--color-foreground);
|
|
54
|
+
--tw-prose-pre-code: var(--color-muted-foreground);
|
|
55
|
+
--tw-prose-pre-bg: var(--color-muted);
|
|
56
|
+
--tw-prose-th-borders: var(--color-border);
|
|
57
|
+
--tw-prose-td-borders: var(--color-border);
|
|
58
|
+
|
|
59
|
+
/* Links: same color as surrounding text, underline only, no bold */
|
|
60
|
+
a {
|
|
61
|
+
font-weight: inherit;
|
|
62
|
+
text-underline-offset: 2px;
|
|
63
|
+
text-decoration-color: var(--color-border);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
a:hover {
|
|
67
|
+
text-decoration-color: currentColor;
|
|
68
|
+
}
|
|
69
|
+
}
|