@jant/core 0.3.25 → 0.3.26
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 +67 -562
- package/dist/client.js +1 -0
- 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/lib/avatar-upload.js +134 -0
- package/dist/lib/config.js +39 -0
- package/dist/lib/constants.js +10 -10
- package/dist/lib/favicon.js +102 -0
- package/dist/lib/image.js +13 -17
- package/dist/lib/media-helpers.js +2 -2
- package/dist/lib/navigation.js +23 -3
- package/dist/lib/render.js +10 -1
- package/dist/lib/schemas.js +31 -0
- package/dist/lib/timezones.js +388 -0
- package/dist/lib/view.js +1 -1
- package/dist/routes/api/posts.js +1 -1
- package/dist/routes/api/upload.js +3 -3
- package/dist/routes/auth/reset.js +221 -0
- package/dist/routes/auth/setup.js +194 -0
- package/dist/routes/auth/signin.js +176 -0
- package/dist/routes/dash/collections.js +23 -415
- package/dist/routes/dash/media.js +12 -392
- package/dist/routes/dash/pages.js +7 -330
- package/dist/routes/dash/redirects.js +18 -12
- package/dist/routes/dash/settings.js +198 -577
- package/dist/routes/feed/rss.js +2 -1
- package/dist/routes/feed/sitemap.js +4 -2
- package/dist/routes/pages/featured.js +5 -1
- package/dist/routes/pages/home.js +26 -1
- package/dist/routes/pages/latest.js +45 -0
- package/dist/services/post.js +30 -50
- package/dist/types/bindings.js +3 -0
- package/dist/types/config.js +147 -0
- package/dist/types/constants.js +27 -0
- package/dist/types/entities.js +3 -0
- package/dist/types/operations.js +3 -0
- package/dist/types/props.js +3 -0
- package/dist/types/views.js +5 -0
- package/dist/types.js +8 -111
- package/dist/ui/color-themes.js +33 -33
- package/dist/ui/compose/ComposeDialog.js +36 -21
- package/dist/ui/dash/PageForm.js +21 -15
- package/dist/ui/dash/PostForm.js +22 -16
- package/dist/ui/dash/collections/CollectionForm.js +152 -0
- package/dist/ui/dash/collections/CollectionsListContent.js +68 -0
- package/dist/ui/dash/collections/ViewCollectionContent.js +96 -0
- package/dist/ui/dash/media/MediaListContent.js +166 -0
- package/dist/ui/dash/media/ViewMediaContent.js +212 -0
- package/dist/ui/dash/pages/LinkFormContent.js +130 -0
- package/dist/ui/dash/pages/UnifiedPagesContent.js +193 -0
- package/dist/ui/dash/settings/AccountContent.js +209 -0
- package/dist/ui/dash/settings/AppearanceContent.js +259 -0
- package/dist/ui/dash/settings/GeneralContent.js +536 -0
- package/dist/ui/dash/settings/SettingsNav.js +41 -0
- package/dist/ui/font-themes.js +36 -0
- package/dist/ui/layouts/BaseLayout.js +24 -2
- package/dist/ui/layouts/SiteLayout.js +47 -19
- package/package.json +1 -1
- package/src/app.tsx +93 -553
- package/src/client.ts +1 -0
- package/src/i18n/locales/en.po +240 -175
- package/src/i18n/locales/en.ts +1 -1
- package/src/i18n/locales/zh-Hans.po +240 -175
- package/src/i18n/locales/zh-Hans.ts +1 -1
- package/src/i18n/locales/zh-Hant.po +240 -175
- package/src/i18n/locales/zh-Hant.ts +1 -1
- package/src/lib/__tests__/config.test.ts +192 -0
- package/src/lib/__tests__/favicon.test.ts +151 -0
- package/src/lib/__tests__/image.test.ts +2 -6
- package/src/lib/__tests__/timezones.test.ts +61 -0
- package/src/lib/__tests__/view.test.ts +2 -2
- package/src/lib/avatar-upload.ts +165 -0
- package/src/lib/config.ts +47 -0
- package/src/lib/constants.ts +19 -11
- package/src/lib/favicon.ts +115 -0
- package/src/lib/image.ts +13 -21
- package/src/lib/media-helpers.ts +2 -2
- package/src/lib/navigation.ts +33 -2
- package/src/lib/render.tsx +15 -1
- package/src/lib/schemas.ts +39 -0
- package/src/lib/timezones.ts +325 -0
- package/src/lib/view.ts +1 -1
- package/src/routes/api/posts.ts +1 -1
- package/src/routes/api/upload.ts +2 -3
- package/src/routes/auth/reset.tsx +239 -0
- package/src/routes/auth/setup.tsx +189 -0
- package/src/routes/auth/signin.tsx +163 -0
- package/src/routes/dash/__tests__/settings-avatar.test.ts +89 -0
- package/src/routes/dash/collections.tsx +17 -366
- package/src/routes/dash/media.tsx +12 -414
- package/src/routes/dash/pages.tsx +8 -348
- package/src/routes/dash/redirects.tsx +20 -14
- package/src/routes/dash/settings.tsx +243 -534
- package/src/routes/feed/__tests__/rss.test.ts +141 -0
- package/src/routes/feed/rss.ts +3 -1
- package/src/routes/feed/sitemap.ts +4 -2
- package/src/routes/pages/featured.tsx +7 -1
- package/src/routes/pages/home.tsx +25 -2
- package/src/routes/pages/latest.tsx +59 -0
- package/src/services/post.ts +34 -66
- package/src/styles/components.css +0 -65
- package/src/styles/tokens.css +1 -1
- package/src/styles/ui.css +24 -40
- package/src/types/bindings.ts +30 -0
- package/src/types/config.ts +183 -0
- package/src/types/constants.ts +26 -0
- package/src/types/entities.ts +109 -0
- package/src/types/operations.ts +88 -0
- package/src/types/props.ts +115 -0
- package/src/types/views.ts +172 -0
- package/src/types.ts +8 -644
- package/src/ui/__tests__/font-themes.test.ts +34 -0
- package/src/ui/color-themes.ts +34 -34
- package/src/ui/compose/ComposeDialog.tsx +40 -21
- package/src/ui/dash/PageForm.tsx +25 -19
- package/src/ui/dash/PostForm.tsx +26 -20
- package/src/ui/dash/collections/CollectionForm.tsx +153 -0
- package/src/ui/dash/collections/CollectionsListContent.tsx +85 -0
- package/src/ui/dash/collections/ViewCollectionContent.tsx +92 -0
- package/src/ui/dash/media/MediaListContent.tsx +201 -0
- package/src/ui/dash/media/ViewMediaContent.tsx +208 -0
- package/src/ui/dash/pages/LinkFormContent.tsx +119 -0
- package/src/ui/dash/pages/UnifiedPagesContent.tsx +203 -0
- package/src/ui/dash/settings/AccountContent.tsx +176 -0
- package/src/ui/dash/settings/AppearanceContent.tsx +254 -0
- package/src/ui/dash/settings/GeneralContent.tsx +533 -0
- package/src/ui/dash/settings/SettingsNav.tsx +56 -0
- package/src/ui/font-themes.ts +54 -0
- package/src/ui/layouts/BaseLayout.tsx +17 -0
- package/src/ui/layouts/SiteLayout.tsx +45 -31
package/src/types.ts
CHANGED
|
@@ -1,649 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Jant Type Definitions (v2)
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
// =============================================================================
|
|
6
|
-
// Content Types
|
|
7
|
-
// =============================================================================
|
|
8
|
-
|
|
9
|
-
export const FORMATS = ["note", "link", "quote"] as const;
|
|
10
|
-
export type Format = (typeof FORMATS)[number];
|
|
11
|
-
|
|
12
|
-
export const STATUSES = ["draft", "published"] as const;
|
|
13
|
-
export type Status = (typeof STATUSES)[number];
|
|
14
|
-
|
|
15
|
-
export const SORT_ORDERS = [
|
|
16
|
-
"newest",
|
|
17
|
-
"oldest",
|
|
18
|
-
"rating_desc",
|
|
19
|
-
"rating_asc",
|
|
20
|
-
] as const;
|
|
21
|
-
export type SortOrder = (typeof SORT_ORDERS)[number];
|
|
22
|
-
|
|
23
|
-
export const NAV_ITEM_TYPES = ["page", "link"] as const;
|
|
24
|
-
export type NavItemType = (typeof NAV_ITEM_TYPES)[number];
|
|
25
|
-
|
|
26
|
-
export const MAX_MEDIA_ATTACHMENTS = 20;
|
|
27
|
-
export const MAX_PINNED_POSTS = 3;
|
|
28
|
-
|
|
29
|
-
export const STORAGE_DRIVERS = ["r2", "s3"] as const;
|
|
30
|
-
export type StorageDriver = (typeof STORAGE_DRIVERS)[number];
|
|
31
|
-
|
|
32
|
-
// =============================================================================
|
|
33
|
-
// Cloudflare Bindings
|
|
34
|
-
// =============================================================================
|
|
35
|
-
|
|
36
|
-
export interface Bindings {
|
|
37
|
-
DB: D1Database;
|
|
38
|
-
R2?: R2Bucket;
|
|
39
|
-
SITE_URL: string;
|
|
40
|
-
AUTH_SECRET?: string;
|
|
41
|
-
R2_PUBLIC_URL?: string;
|
|
42
|
-
IMAGE_TRANSFORM_URL?: string;
|
|
43
|
-
DEMO_EMAIL?: string;
|
|
44
|
-
DEMO_PASSWORD?: string;
|
|
45
|
-
// Timeline
|
|
46
|
-
PAGE_SIZE?: string;
|
|
47
|
-
// Site configuration (optional - can be overridden in DB)
|
|
48
|
-
SITE_NAME?: string;
|
|
49
|
-
SITE_DESCRIPTION?: string;
|
|
50
|
-
SITE_LANGUAGE?: string;
|
|
51
|
-
// S3-compatible storage (alternative to R2)
|
|
52
|
-
STORAGE_DRIVER?: string;
|
|
53
|
-
S3_ENDPOINT?: string;
|
|
54
|
-
S3_BUCKET?: string;
|
|
55
|
-
S3_ACCESS_KEY_ID?: string;
|
|
56
|
-
S3_SECRET_ACCESS_KEY?: string;
|
|
57
|
-
S3_REGION?: string;
|
|
58
|
-
S3_PUBLIC_URL?: string;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
// =============================================================================
|
|
62
|
-
// Configuration System
|
|
63
|
-
// =============================================================================
|
|
64
|
-
|
|
65
|
-
/**
|
|
66
|
-
* Configuration Registry - Single Source of Truth
|
|
67
|
-
*
|
|
68
|
-
* All available configuration fields with their metadata.
|
|
69
|
-
* Add new fields here, and they'll automatically work everywhere.
|
|
70
3
|
*
|
|
71
|
-
*
|
|
72
|
-
* - envOnly: false -> User-configurable (DB > ENV > Default)
|
|
73
|
-
* - envOnly: true -> Environment-only (ENV > Default)
|
|
74
|
-
*/
|
|
75
|
-
export const CONFIG_FIELDS = {
|
|
76
|
-
// User-configurable (can be modified in dashboard)
|
|
77
|
-
SITE_NAME: {
|
|
78
|
-
defaultValue: "Jant",
|
|
79
|
-
envOnly: false,
|
|
80
|
-
},
|
|
81
|
-
SITE_DESCRIPTION: {
|
|
82
|
-
defaultValue: "A microblog powered by Jant",
|
|
83
|
-
envOnly: false,
|
|
84
|
-
},
|
|
85
|
-
SITE_LANGUAGE: {
|
|
86
|
-
defaultValue: "en",
|
|
87
|
-
envOnly: false,
|
|
88
|
-
},
|
|
89
|
-
|
|
90
|
-
// Environment-only (deployment/infrastructure config)
|
|
91
|
-
SITE_URL: {
|
|
92
|
-
defaultValue: "",
|
|
93
|
-
envOnly: true,
|
|
94
|
-
},
|
|
95
|
-
AUTH_SECRET: {
|
|
96
|
-
defaultValue: "",
|
|
97
|
-
envOnly: true,
|
|
98
|
-
},
|
|
99
|
-
R2_PUBLIC_URL: {
|
|
100
|
-
defaultValue: "",
|
|
101
|
-
envOnly: true,
|
|
102
|
-
},
|
|
103
|
-
IMAGE_TRANSFORM_URL: {
|
|
104
|
-
defaultValue: "",
|
|
105
|
-
envOnly: true,
|
|
106
|
-
},
|
|
107
|
-
DEMO_EMAIL: {
|
|
108
|
-
defaultValue: "",
|
|
109
|
-
envOnly: true,
|
|
110
|
-
},
|
|
111
|
-
DEMO_PASSWORD: {
|
|
112
|
-
defaultValue: "",
|
|
113
|
-
envOnly: true,
|
|
114
|
-
},
|
|
115
|
-
PAGE_SIZE: {
|
|
116
|
-
defaultValue: "20",
|
|
117
|
-
envOnly: true,
|
|
118
|
-
},
|
|
119
|
-
STORAGE_DRIVER: {
|
|
120
|
-
defaultValue: "r2",
|
|
121
|
-
envOnly: true,
|
|
122
|
-
},
|
|
123
|
-
S3_ENDPOINT: {
|
|
124
|
-
defaultValue: "",
|
|
125
|
-
envOnly: true,
|
|
126
|
-
},
|
|
127
|
-
S3_BUCKET: {
|
|
128
|
-
defaultValue: "",
|
|
129
|
-
envOnly: true,
|
|
130
|
-
},
|
|
131
|
-
S3_ACCESS_KEY_ID: {
|
|
132
|
-
defaultValue: "",
|
|
133
|
-
envOnly: true,
|
|
134
|
-
},
|
|
135
|
-
S3_SECRET_ACCESS_KEY: {
|
|
136
|
-
defaultValue: "",
|
|
137
|
-
envOnly: true,
|
|
138
|
-
},
|
|
139
|
-
S3_REGION: {
|
|
140
|
-
defaultValue: "auto",
|
|
141
|
-
envOnly: true,
|
|
142
|
-
},
|
|
143
|
-
S3_PUBLIC_URL: {
|
|
144
|
-
defaultValue: "",
|
|
145
|
-
envOnly: true,
|
|
146
|
-
},
|
|
147
|
-
} as const;
|
|
148
|
-
|
|
149
|
-
export type ConfigKey = keyof typeof CONFIG_FIELDS;
|
|
150
|
-
|
|
151
|
-
// =============================================================================
|
|
152
|
-
// Entity Types
|
|
153
|
-
// =============================================================================
|
|
154
|
-
|
|
155
|
-
export interface Post {
|
|
156
|
-
id: number;
|
|
157
|
-
format: Format;
|
|
158
|
-
status: Status;
|
|
159
|
-
featured: number; // 0 | 1
|
|
160
|
-
pinned: number; // 0 | 1
|
|
161
|
-
path: string | null;
|
|
162
|
-
title: string | null;
|
|
163
|
-
url: string | null;
|
|
164
|
-
body: string | null;
|
|
165
|
-
bodyHtml: string | null;
|
|
166
|
-
quoteText: string | null;
|
|
167
|
-
rating: number | null;
|
|
168
|
-
collectionId: number | null;
|
|
169
|
-
replyToId: number | null;
|
|
170
|
-
threadId: number | null;
|
|
171
|
-
deletedAt: number | null;
|
|
172
|
-
publishedAt: number;
|
|
173
|
-
createdAt: number;
|
|
174
|
-
updatedAt: number;
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
export interface Page {
|
|
178
|
-
id: number;
|
|
179
|
-
slug: string;
|
|
180
|
-
title: string | null;
|
|
181
|
-
body: string | null;
|
|
182
|
-
bodyHtml: string | null;
|
|
183
|
-
status: Status;
|
|
184
|
-
createdAt: number;
|
|
185
|
-
updatedAt: number;
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
export interface Media {
|
|
189
|
-
id: string; // UUIDv7
|
|
190
|
-
postId: number | null;
|
|
191
|
-
filename: string;
|
|
192
|
-
originalName: string;
|
|
193
|
-
mimeType: string;
|
|
194
|
-
size: number;
|
|
195
|
-
storageKey: string;
|
|
196
|
-
provider: string;
|
|
197
|
-
width: number | null;
|
|
198
|
-
height: number | null;
|
|
199
|
-
alt: string | null;
|
|
200
|
-
position: number;
|
|
201
|
-
blurhash: string | null;
|
|
202
|
-
createdAt: number;
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
export interface MediaAttachment {
|
|
206
|
-
id: string;
|
|
207
|
-
url: string;
|
|
208
|
-
previewUrl: string;
|
|
209
|
-
alt: string | null;
|
|
210
|
-
blurhash: string | null;
|
|
211
|
-
width: number | null;
|
|
212
|
-
height: number | null;
|
|
213
|
-
position: number;
|
|
214
|
-
mimeType: string;
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
export interface PostWithMedia extends Post {
|
|
218
|
-
mediaAttachments: MediaAttachment[];
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
export interface Collection {
|
|
222
|
-
id: number;
|
|
223
|
-
slug: string;
|
|
224
|
-
title: string;
|
|
225
|
-
description: string | null;
|
|
226
|
-
icon: string | null;
|
|
227
|
-
sortOrder: SortOrder;
|
|
228
|
-
position: number;
|
|
229
|
-
showDivider: number; // 0 | 1
|
|
230
|
-
createdAt: number;
|
|
231
|
-
updatedAt: number;
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
export interface NavItem {
|
|
235
|
-
id: number;
|
|
236
|
-
type: NavItemType;
|
|
237
|
-
label: string;
|
|
238
|
-
url: string;
|
|
239
|
-
pageId: number | null;
|
|
240
|
-
position: number;
|
|
241
|
-
createdAt: number;
|
|
242
|
-
updatedAt: number;
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
export interface Redirect {
|
|
246
|
-
id: number;
|
|
247
|
-
fromPath: string;
|
|
248
|
-
toPath: string;
|
|
249
|
-
type: 301 | 302;
|
|
250
|
-
createdAt: number;
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
export interface Setting {
|
|
254
|
-
key: string;
|
|
255
|
-
value: string;
|
|
256
|
-
updatedAt: number;
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
// =============================================================================
|
|
260
|
-
// Operation Types
|
|
261
|
-
// =============================================================================
|
|
262
|
-
|
|
263
|
-
export interface CreatePost {
|
|
264
|
-
format: Format;
|
|
265
|
-
status?: Status;
|
|
266
|
-
featured?: boolean;
|
|
267
|
-
pinned?: boolean;
|
|
268
|
-
path?: string;
|
|
269
|
-
title?: string;
|
|
270
|
-
url?: string;
|
|
271
|
-
body?: string;
|
|
272
|
-
quoteText?: string;
|
|
273
|
-
rating?: number;
|
|
274
|
-
collectionId?: number;
|
|
275
|
-
replyToId?: number;
|
|
276
|
-
publishedAt?: number;
|
|
277
|
-
mediaIds?: string[];
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
export interface UpdatePost {
|
|
281
|
-
format?: Format;
|
|
282
|
-
status?: Status;
|
|
283
|
-
featured?: boolean;
|
|
284
|
-
pinned?: boolean;
|
|
285
|
-
path?: string | null;
|
|
286
|
-
title?: string | null;
|
|
287
|
-
url?: string | null;
|
|
288
|
-
body?: string | null;
|
|
289
|
-
quoteText?: string | null;
|
|
290
|
-
rating?: number | null;
|
|
291
|
-
collectionId?: number | null;
|
|
292
|
-
publishedAt?: number;
|
|
293
|
-
mediaIds?: string[];
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
export interface CreatePage {
|
|
297
|
-
slug: string;
|
|
298
|
-
title?: string;
|
|
299
|
-
body?: string;
|
|
300
|
-
status?: Status;
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
export interface UpdatePage {
|
|
304
|
-
slug?: string;
|
|
305
|
-
title?: string | null;
|
|
306
|
-
body?: string | null;
|
|
307
|
-
status?: Status;
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
export interface CreateNavItem {
|
|
311
|
-
type: NavItemType;
|
|
312
|
-
label: string;
|
|
313
|
-
url: string;
|
|
314
|
-
pageId?: number;
|
|
315
|
-
position?: number;
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
export interface UpdateNavItem {
|
|
319
|
-
type?: NavItemType;
|
|
320
|
-
label?: string;
|
|
321
|
-
url?: string;
|
|
322
|
-
pageId?: number | null;
|
|
323
|
-
position?: number;
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
export interface CreateCollection {
|
|
327
|
-
slug: string;
|
|
328
|
-
title: string;
|
|
329
|
-
description?: string;
|
|
330
|
-
icon?: string;
|
|
331
|
-
sortOrder?: SortOrder;
|
|
332
|
-
position?: number;
|
|
333
|
-
showDivider?: boolean;
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
export interface UpdateCollection {
|
|
337
|
-
slug?: string;
|
|
338
|
-
title?: string;
|
|
339
|
-
description?: string | null;
|
|
340
|
-
icon?: string | null;
|
|
341
|
-
sortOrder?: SortOrder;
|
|
342
|
-
position?: number;
|
|
343
|
-
showDivider?: boolean;
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
// =============================================================================
|
|
347
|
-
// View Model Types (render-ready, for theme components)
|
|
348
|
-
// =============================================================================
|
|
349
|
-
|
|
350
|
-
/**
|
|
351
|
-
* Render-ready post data for theme components.
|
|
352
|
-
* All fields are pre-computed -- no lib/ imports needed.
|
|
353
|
-
*/
|
|
354
|
-
export interface PostView {
|
|
355
|
-
// Identity
|
|
356
|
-
id: number;
|
|
357
|
-
/** Pre-computed permalink: "/{path}" if path set, otherwise "/p/{sqid}" */
|
|
358
|
-
permalink: string;
|
|
359
|
-
/** Custom URL path, if set. Supports multi-level paths (e.g. "2024/my-post") */
|
|
360
|
-
path?: string;
|
|
361
|
-
|
|
362
|
-
// Content
|
|
363
|
-
title?: string;
|
|
364
|
-
/** Pre-sanitized HTML */
|
|
365
|
-
bodyHtml?: string;
|
|
366
|
-
/** Pre-computed excerpt, max 160 chars */
|
|
367
|
-
excerpt?: string;
|
|
368
|
-
/** HTML excerpt for article previews (paragraph-aware, ~500 chars) */
|
|
369
|
-
summaryHtml?: string;
|
|
370
|
-
/** Whether summaryHtml was truncated (content continues beyond excerpt) */
|
|
371
|
-
summaryHasMore?: boolean;
|
|
372
|
-
/** URL for link/quote formats */
|
|
373
|
-
url?: string;
|
|
374
|
-
/** Quoted text for quote format */
|
|
375
|
-
quoteText?: string;
|
|
376
|
-
|
|
377
|
-
// Metadata
|
|
378
|
-
format: Format;
|
|
379
|
-
status: Status;
|
|
380
|
-
featured: boolean;
|
|
381
|
-
pinned: boolean;
|
|
382
|
-
rating?: number;
|
|
383
|
-
|
|
384
|
-
// Collection
|
|
385
|
-
collectionId?: number;
|
|
386
|
-
|
|
387
|
-
// Time -- pre-formatted
|
|
388
|
-
/** ISO 8601 string */
|
|
389
|
-
publishedAt: string;
|
|
390
|
-
/** Human-readable, e.g. "Feb 1, 2024" */
|
|
391
|
-
publishedAtFormatted: string;
|
|
392
|
-
/** 24-hour time, e.g. "23:05" */
|
|
393
|
-
publishedAtTime: string;
|
|
394
|
-
/** Short relative time, e.g. "5m", "3h", "2d", "Feb 1" */
|
|
395
|
-
publishedAtRelative: string;
|
|
396
|
-
/** ISO 8601 string */
|
|
397
|
-
updatedAt: string;
|
|
398
|
-
|
|
399
|
-
// Media -- URLs pre-computed
|
|
400
|
-
media: MediaView[];
|
|
401
|
-
|
|
402
|
-
// Thread context
|
|
403
|
-
replyToId?: number;
|
|
404
|
-
threadRootId?: number;
|
|
405
|
-
|
|
406
|
-
// Raw content (for forms/editing, not typical theme use)
|
|
407
|
-
body?: string;
|
|
408
|
-
}
|
|
409
|
-
|
|
410
|
-
/**
|
|
411
|
-
* Render-ready page data for theme components.
|
|
412
|
-
*/
|
|
413
|
-
export interface PageView {
|
|
414
|
-
id: number;
|
|
415
|
-
slug: string;
|
|
416
|
-
title?: string;
|
|
417
|
-
bodyHtml?: string;
|
|
418
|
-
status: Status;
|
|
419
|
-
createdAt: string;
|
|
420
|
-
updatedAt: string;
|
|
421
|
-
}
|
|
422
|
-
|
|
423
|
-
/**
|
|
424
|
-
* Render-ready media data for theme components.
|
|
425
|
-
* URLs are pre-computed -- no lib/ imports needed.
|
|
426
|
-
*/
|
|
427
|
-
export interface MediaView {
|
|
428
|
-
id: string;
|
|
429
|
-
/** Full-size URL, pre-computed */
|
|
430
|
-
url: string;
|
|
431
|
-
/** Thumbnail URL, pre-computed */
|
|
432
|
-
thumbnailUrl: string;
|
|
433
|
-
mimeType: string;
|
|
434
|
-
altText?: string;
|
|
435
|
-
width?: number;
|
|
436
|
-
height?: number;
|
|
437
|
-
size?: number;
|
|
438
|
-
}
|
|
439
|
-
|
|
440
|
-
/**
|
|
441
|
-
* Render-ready navigation item for theme components.
|
|
442
|
-
* Active/external state pre-computed.
|
|
443
|
-
*/
|
|
444
|
-
export interface NavItemView {
|
|
445
|
-
id: number;
|
|
446
|
-
type: NavItemType;
|
|
447
|
-
label: string;
|
|
448
|
-
url: string;
|
|
449
|
-
pageId?: number;
|
|
450
|
-
/** Pre-computed based on currentPath */
|
|
451
|
-
isActive: boolean;
|
|
452
|
-
/** Pre-computed: starts with http(s):// */
|
|
453
|
-
isExternal: boolean;
|
|
454
|
-
}
|
|
455
|
-
|
|
456
|
-
/**
|
|
457
|
-
* Render-ready search result for theme components.
|
|
458
|
-
*/
|
|
459
|
-
export interface SearchResultView {
|
|
460
|
-
post: PostView;
|
|
461
|
-
rank: number;
|
|
462
|
-
snippet?: string;
|
|
463
|
-
}
|
|
464
|
-
|
|
465
|
-
/**
|
|
466
|
-
* Render-ready timeline item for theme components.
|
|
467
|
-
*/
|
|
468
|
-
export interface TimelineItemView {
|
|
469
|
-
post: PostView;
|
|
470
|
-
threadPreview?: {
|
|
471
|
-
replies: PostView[];
|
|
472
|
-
totalReplyCount: number;
|
|
473
|
-
};
|
|
474
|
-
}
|
|
475
|
-
|
|
476
|
-
/**
|
|
477
|
-
* Typed archive group with pre-formatted label.
|
|
478
|
-
*/
|
|
479
|
-
export interface ArchiveGroup {
|
|
480
|
-
/** e.g. "2024" */
|
|
481
|
-
year: string;
|
|
482
|
-
/** e.g. "02" */
|
|
483
|
-
month: string;
|
|
484
|
-
/** Pre-formatted, e.g. "February 2024" */
|
|
485
|
-
label: string;
|
|
486
|
-
posts: PostView[];
|
|
487
|
-
}
|
|
488
|
-
|
|
489
|
-
// =============================================================================
|
|
490
|
-
// Page-Based Pagination Types
|
|
491
|
-
// =============================================================================
|
|
492
|
-
|
|
493
|
-
// =============================================================================
|
|
494
|
-
// Configuration Types
|
|
495
|
-
// =============================================================================
|
|
496
|
-
|
|
497
|
-
import type { ColorTheme } from "./ui/color-themes.js";
|
|
498
|
-
|
|
499
|
-
/**
|
|
500
|
-
* Search result from FTS5
|
|
4
|
+
* Barrel re-export from focused submodules.
|
|
501
5
|
*/
|
|
502
|
-
export interface SearchResult {
|
|
503
|
-
post: Post;
|
|
504
|
-
/** FTS5 rank score (lower is better) */
|
|
505
|
-
rank: number;
|
|
506
|
-
/** Highlighted snippet from content */
|
|
507
|
-
snippet?: string;
|
|
508
|
-
}
|
|
509
|
-
|
|
510
|
-
// =============================================================================
|
|
511
|
-
// Site Layout Props
|
|
512
|
-
// =============================================================================
|
|
513
|
-
|
|
514
|
-
export interface SiteLayoutProps {
|
|
515
|
-
siteName: string;
|
|
516
|
-
siteDescription?: string;
|
|
517
|
-
links: NavItemView[];
|
|
518
|
-
currentPath: string;
|
|
519
|
-
isAuthenticated?: boolean;
|
|
520
|
-
collections?: Collection[];
|
|
521
|
-
}
|
|
522
|
-
|
|
523
|
-
// =============================================================================
|
|
524
|
-
// Page-Level Props
|
|
525
|
-
// =============================================================================
|
|
526
|
-
|
|
527
|
-
/** Props for the home page component */
|
|
528
|
-
export interface HomePageProps {
|
|
529
|
-
items: TimelineItemView[];
|
|
530
|
-
pinnedItems: PostView[];
|
|
531
|
-
currentPage: number;
|
|
532
|
-
totalPages: number;
|
|
533
|
-
}
|
|
534
|
-
|
|
535
|
-
/** Props for the single post page component */
|
|
536
|
-
export interface PostPageProps {
|
|
537
|
-
post: PostView;
|
|
538
|
-
}
|
|
539
|
-
|
|
540
|
-
/** Props for the custom page component */
|
|
541
|
-
export interface SinglePageProps {
|
|
542
|
-
page: PageView;
|
|
543
|
-
}
|
|
544
|
-
|
|
545
|
-
/** Props for the featured page component */
|
|
546
|
-
export interface FeaturedPageProps {
|
|
547
|
-
items: TimelineItemView[];
|
|
548
|
-
}
|
|
549
|
-
|
|
550
|
-
/** Props for the archive page component */
|
|
551
|
-
export interface ArchivePageProps {
|
|
552
|
-
groups: ArchiveGroup[];
|
|
553
|
-
hasMore: boolean;
|
|
554
|
-
nextCursor?: number;
|
|
555
|
-
format?: Format;
|
|
556
|
-
featured?: boolean;
|
|
557
|
-
}
|
|
558
|
-
|
|
559
|
-
/** Props for the search page component */
|
|
560
|
-
export interface SearchPageProps {
|
|
561
|
-
query: string;
|
|
562
|
-
results: SearchResultView[];
|
|
563
|
-
error?: string;
|
|
564
|
-
hasMore: boolean;
|
|
565
|
-
page: number;
|
|
566
|
-
}
|
|
567
|
-
|
|
568
|
-
/** Props for the single collection page component */
|
|
569
|
-
export interface CollectionPageProps {
|
|
570
|
-
collection: Collection;
|
|
571
|
-
posts: PostView[];
|
|
572
|
-
hasMore: boolean;
|
|
573
|
-
nextCursor?: number;
|
|
574
|
-
}
|
|
575
|
-
|
|
576
|
-
/** Props for the collections list page component */
|
|
577
|
-
export interface CollectionsPageProps {
|
|
578
|
-
collections: (Collection & { postCount: number })[];
|
|
579
|
-
}
|
|
580
6
|
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
export
|
|
587
|
-
|
|
588
|
-
siteDescription: string;
|
|
589
|
-
siteUrl: string;
|
|
590
|
-
siteLanguage: string;
|
|
591
|
-
posts: PostView[];
|
|
592
|
-
}
|
|
593
|
-
|
|
594
|
-
/** Data passed to sitemap renderers */
|
|
595
|
-
export interface SitemapData {
|
|
596
|
-
siteUrl: string;
|
|
597
|
-
posts: PostView[];
|
|
598
|
-
pages: PageView[];
|
|
599
|
-
}
|
|
600
|
-
|
|
601
|
-
// =============================================================================
|
|
602
|
-
// Timeline Types
|
|
603
|
-
// =============================================================================
|
|
604
|
-
|
|
605
|
-
/** Props for per-type timeline cards */
|
|
606
|
-
export interface TimelineCardProps {
|
|
607
|
-
post: PostView;
|
|
608
|
-
compact?: boolean;
|
|
609
|
-
}
|
|
610
|
-
|
|
611
|
-
/** Props for thread inline preview */
|
|
612
|
-
export interface ThreadPreviewProps {
|
|
613
|
-
rootPost: PostView;
|
|
614
|
-
previewReplies: PostView[];
|
|
615
|
-
totalReplyCount: number;
|
|
616
|
-
}
|
|
617
|
-
|
|
618
|
-
/** Props for the timeline feed wrapper */
|
|
619
|
-
export interface TimelineFeedProps {
|
|
620
|
-
items: TimelineItemView[];
|
|
621
|
-
currentPage?: number;
|
|
622
|
-
totalPages?: number;
|
|
623
|
-
}
|
|
624
|
-
|
|
625
|
-
/**
|
|
626
|
-
* Main Jant configuration
|
|
627
|
-
*
|
|
628
|
-
* Configuration Philosophy:
|
|
629
|
-
* - Use environment variables for runtime config (API keys, feature flags, site settings)
|
|
630
|
-
* - Use code config (this object) for CSS customization and feed overrides
|
|
631
|
-
*
|
|
632
|
-
* Site-level settings (name, description, language) are configured via
|
|
633
|
-
* environment variables, not here. See lib/config.ts for details.
|
|
634
|
-
*/
|
|
635
|
-
export interface JantConfig {
|
|
636
|
-
/** CSS variable overrides (highest priority after custom CSS) */
|
|
637
|
-
cssVariables?: Record<string, string>;
|
|
638
|
-
/** Replace built-in color themes with custom list */
|
|
639
|
-
colorThemes?: ColorTheme[];
|
|
640
|
-
/** Custom feed renderers */
|
|
641
|
-
feed?: {
|
|
642
|
-
/** Custom RSS 2.0 renderer -- returns XML string */
|
|
643
|
-
rss?: (data: FeedData) => string;
|
|
644
|
-
/** Custom Atom renderer -- returns XML string */
|
|
645
|
-
atom?: (data: FeedData) => string;
|
|
646
|
-
/** Custom Sitemap renderer -- returns XML string */
|
|
647
|
-
sitemap?: (data: SitemapData) => string;
|
|
648
|
-
};
|
|
649
|
-
}
|
|
7
|
+
export * from "./types/constants.js";
|
|
8
|
+
export * from "./types/bindings.js";
|
|
9
|
+
export * from "./types/config.js";
|
|
10
|
+
export * from "./types/entities.js";
|
|
11
|
+
export * from "./types/operations.js";
|
|
12
|
+
export * from "./types/views.js";
|
|
13
|
+
export * from "./types/props.js";
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { BUILTIN_FONT_THEMES } from "../font-themes.js";
|
|
3
|
+
|
|
4
|
+
describe("BUILTIN_FONT_THEMES", () => {
|
|
5
|
+
it("contains 4 themes", () => {
|
|
6
|
+
expect(BUILTIN_FONT_THEMES).toHaveLength(4);
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
it("has 'default' as the first theme", () => {
|
|
10
|
+
expect(BUILTIN_FONT_THEMES[0].id).toBe("default");
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
it("each theme has required fields", () => {
|
|
14
|
+
for (const theme of BUILTIN_FONT_THEMES) {
|
|
15
|
+
expect(theme.id).toBeTruthy();
|
|
16
|
+
expect(theme.name).toBeTruthy();
|
|
17
|
+
expect(theme.fontFamily).toBeTruthy();
|
|
18
|
+
expect(theme.description).toBeTruthy();
|
|
19
|
+
}
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it("has no duplicate IDs", () => {
|
|
23
|
+
const ids = BUILTIN_FONT_THEMES.map((t) => t.id);
|
|
24
|
+
expect(new Set(ids).size).toBe(ids.length);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it("includes expected theme IDs", () => {
|
|
28
|
+
const ids = BUILTIN_FONT_THEMES.map((t) => t.id);
|
|
29
|
+
expect(ids).toContain("default");
|
|
30
|
+
expect(ids).toContain("serif");
|
|
31
|
+
expect(ids).toContain("humanist");
|
|
32
|
+
expect(ids).toContain("mono");
|
|
33
|
+
});
|
|
34
|
+
});
|