@jant/core 0.3.21 → 0.3.22

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.
Files changed (82) hide show
  1. package/dist/app.js +1 -1
  2. package/dist/index.js +8 -0
  3. package/dist/lib/feed.js +112 -0
  4. package/dist/lib/navigation.js +9 -9
  5. package/dist/lib/render.js +48 -0
  6. package/dist/lib/theme-components.js +18 -18
  7. package/dist/lib/view.js +228 -0
  8. package/dist/routes/api/timeline.js +20 -16
  9. package/dist/routes/feed/rss.js +34 -78
  10. package/dist/routes/feed/sitemap.js +11 -26
  11. package/dist/routes/pages/archive.js +18 -195
  12. package/dist/routes/pages/collection.js +16 -70
  13. package/dist/routes/pages/home.js +25 -47
  14. package/dist/routes/pages/page.js +15 -27
  15. package/dist/routes/pages/post.js +25 -79
  16. package/dist/routes/pages/search.js +20 -130
  17. package/dist/theme/components/MediaGallery.js +10 -10
  18. package/dist/theme/components/index.js +1 -1
  19. package/dist/theme/components/timeline/ArticleCard.js +7 -11
  20. package/dist/theme/components/timeline/ImageCard.js +10 -13
  21. package/dist/theme/components/timeline/LinkCard.js +4 -7
  22. package/dist/theme/components/timeline/NoteCard.js +5 -8
  23. package/dist/theme/components/timeline/QuoteCard.js +3 -6
  24. package/dist/theme/components/timeline/ThreadPreview.js +9 -10
  25. package/dist/theme/components/timeline/TimelineFeed.js +8 -5
  26. package/dist/theme/components/timeline/TimelineItem.js +22 -2
  27. package/dist/theme/components/timeline/index.js +1 -1
  28. package/dist/theme/index.js +6 -3
  29. package/dist/theme/layouts/SiteLayout.js +10 -39
  30. package/dist/theme/pages/ArchivePage.js +157 -0
  31. package/dist/theme/pages/CollectionPage.js +63 -0
  32. package/dist/theme/pages/HomePage.js +26 -0
  33. package/dist/theme/pages/PostPage.js +48 -0
  34. package/dist/theme/pages/SearchPage.js +120 -0
  35. package/dist/theme/pages/SinglePage.js +23 -0
  36. package/dist/theme/pages/index.js +11 -0
  37. package/package.json +2 -1
  38. package/src/app.tsx +1 -1
  39. package/src/i18n/locales/en.po +31 -31
  40. package/src/i18n/locales/zh-Hans.po +31 -31
  41. package/src/i18n/locales/zh-Hant.po +31 -31
  42. package/src/index.ts +51 -2
  43. package/src/lib/__tests__/theme-components.test.ts +33 -14
  44. package/src/lib/__tests__/view.test.ts +375 -0
  45. package/src/lib/feed.ts +148 -0
  46. package/src/lib/navigation.ts +11 -11
  47. package/src/lib/render.tsx +67 -0
  48. package/src/lib/theme-components.ts +27 -35
  49. package/src/lib/view.ts +318 -0
  50. package/src/routes/api/__tests__/timeline.test.ts +3 -3
  51. package/src/routes/api/timeline.tsx +32 -25
  52. package/src/routes/feed/rss.ts +47 -94
  53. package/src/routes/feed/sitemap.ts +8 -30
  54. package/src/routes/pages/archive.tsx +24 -209
  55. package/src/routes/pages/collection.tsx +19 -75
  56. package/src/routes/pages/home.tsx +42 -76
  57. package/src/routes/pages/page.tsx +17 -28
  58. package/src/routes/pages/post.tsx +28 -86
  59. package/src/routes/pages/search.tsx +29 -151
  60. package/src/services/search.ts +2 -8
  61. package/src/theme/components/MediaGallery.tsx +12 -12
  62. package/src/theme/components/index.ts +1 -0
  63. package/src/theme/components/timeline/ArticleCard.tsx +7 -19
  64. package/src/theme/components/timeline/ImageCard.tsx +10 -20
  65. package/src/theme/components/timeline/LinkCard.tsx +4 -11
  66. package/src/theme/components/timeline/NoteCard.tsx +5 -12
  67. package/src/theme/components/timeline/QuoteCard.tsx +3 -10
  68. package/src/theme/components/timeline/ThreadPreview.tsx +5 -5
  69. package/src/theme/components/timeline/TimelineFeed.tsx +7 -3
  70. package/src/theme/components/timeline/TimelineItem.tsx +43 -4
  71. package/src/theme/components/timeline/index.ts +1 -1
  72. package/src/theme/index.ts +7 -3
  73. package/src/theme/layouts/SiteLayout.tsx +25 -77
  74. package/src/theme/layouts/index.ts +2 -1
  75. package/src/theme/pages/ArchivePage.tsx +160 -0
  76. package/src/theme/pages/CollectionPage.tsx +60 -0
  77. package/src/theme/pages/HomePage.tsx +42 -0
  78. package/src/theme/pages/PostPage.tsx +44 -0
  79. package/src/theme/pages/SearchPage.tsx +128 -0
  80. package/src/theme/pages/SinglePage.tsx +24 -0
  81. package/src/theme/pages/index.ts +13 -0
  82. package/src/types.ts +262 -38
package/src/types.ts CHANGED
@@ -291,6 +291,121 @@ export interface UpdateNavigationLink {
291
291
  position?: number;
292
292
  }
293
293
 
294
+ // =============================================================================
295
+ // View Model Types (render-ready, for theme components)
296
+ // =============================================================================
297
+
298
+ /**
299
+ * Render-ready post data for theme components.
300
+ * All fields are pre-computed — no lib/ imports needed.
301
+ */
302
+ export interface PostView {
303
+ // Identity
304
+ id: number;
305
+ /** Pre-computed permalink, e.g. "/p/jR3k" */
306
+ permalink: string;
307
+
308
+ // Content
309
+ title?: string;
310
+ /** Pre-sanitized HTML */
311
+ contentHtml?: string;
312
+ /** Pre-computed excerpt, max 160 chars */
313
+ excerpt?: string;
314
+
315
+ // Metadata
316
+ type: PostType;
317
+ visibility: Visibility;
318
+ /** Custom path for pages, e.g. "/about" */
319
+ path?: string;
320
+
321
+ // Time — pre-formatted
322
+ /** ISO 8601 string */
323
+ publishedAt: string;
324
+ /** Human-readable, e.g. "Feb 1, 2024" */
325
+ publishedAtFormatted: string;
326
+ /** ISO 8601 string */
327
+ updatedAt: string;
328
+
329
+ // Source (for link/quote types)
330
+ sourceUrl?: string;
331
+ sourceName?: string;
332
+ sourceDomain?: string;
333
+
334
+ // Media — URLs pre-computed
335
+ media: MediaView[];
336
+
337
+ // Thread context
338
+ replyToId?: number;
339
+ threadRootId?: number;
340
+
341
+ // Raw content (for forms/editing, not typical theme use)
342
+ content?: string;
343
+ }
344
+
345
+ /**
346
+ * Render-ready media data for theme components.
347
+ * URLs are pre-computed — no lib/ imports needed.
348
+ */
349
+ export interface MediaView {
350
+ id: string;
351
+ /** Full-size URL, pre-computed */
352
+ url: string;
353
+ /** Thumbnail URL, pre-computed */
354
+ thumbnailUrl: string;
355
+ mimeType: string;
356
+ altText?: string;
357
+ width?: number;
358
+ height?: number;
359
+ size?: number;
360
+ }
361
+
362
+ /**
363
+ * Render-ready navigation link for theme components.
364
+ * Active/external state pre-computed.
365
+ */
366
+ export interface NavLinkView {
367
+ id: number;
368
+ label: string;
369
+ url: string;
370
+ /** Pre-computed based on currentPath */
371
+ isActive: boolean;
372
+ /** Pre-computed: starts with http(s):// */
373
+ isExternal: boolean;
374
+ }
375
+
376
+ /**
377
+ * Render-ready search result for theme components.
378
+ */
379
+ export interface SearchResultView {
380
+ post: PostView;
381
+ rank: number;
382
+ snippet?: string;
383
+ }
384
+
385
+ /**
386
+ * Render-ready timeline item for theme components.
387
+ */
388
+ export interface TimelineItemView {
389
+ post: PostView;
390
+ threadPreview?: {
391
+ replies: PostView[];
392
+ totalReplyCount: number;
393
+ };
394
+ }
395
+
396
+ /**
397
+ * Typed archive group with pre-formatted label.
398
+ */
399
+ export interface ArchiveGroup {
400
+ /** e.g. "2024" */
401
+ year: string;
402
+ /** e.g. "02" */
403
+ month: string;
404
+ /** Pre-formatted, e.g. "February 2024" */
405
+ label: string;
406
+ posts: PostView[];
407
+ }
408
+
294
409
  // =============================================================================
295
410
  // Configuration Types
296
411
  // =============================================================================
@@ -299,35 +414,93 @@ import type { FC, PropsWithChildren } from "hono/jsx";
299
414
  import type { ColorTheme } from "./theme/color-themes.js";
300
415
 
301
416
  /**
302
- * Props for overridable theme components
417
+ * Search result from FTS5
303
418
  */
304
- export interface BaseLayoutProps extends PropsWithChildren {
305
- title?: string;
306
- description?: string;
419
+ export interface SearchResult {
420
+ post: Post;
421
+ /** FTS5 rank score (lower is better) */
422
+ rank: number;
423
+ /** Highlighted snippet from content */
424
+ snippet?: string;
307
425
  }
308
426
 
309
- export interface PostCardProps {
310
- post: Post;
311
- showExcerpt?: boolean;
312
- showDate?: boolean;
427
+ // =============================================================================
428
+ // Site Layout Props
429
+ // =============================================================================
430
+
431
+ export interface SiteLayoutProps {
432
+ siteName: string;
433
+ links: NavLinkView[];
434
+ currentPath: string;
313
435
  }
314
436
 
315
- export interface PostListProps {
316
- posts: Post[];
317
- emptyMessage?: string;
437
+ // =============================================================================
438
+ // Page-Level Props
439
+ // =============================================================================
440
+
441
+ /** Props for the home page component */
442
+ export interface HomePageProps {
443
+ items: TimelineItemView[];
444
+ hasMore: boolean;
445
+ nextCursor?: number;
446
+ theme?: ThemeComponents;
318
447
  }
319
448
 
320
- export interface PaginationProps {
321
- currentPage: number;
322
- totalPages: number;
323
- basePath: string;
449
+ /** Props for the single post page component */
450
+ export interface PostPageProps {
451
+ post: PostView;
452
+ theme?: ThemeComponents;
324
453
  }
325
454
 
326
- export interface EmptyStateProps {
327
- title: string;
328
- description?: string;
329
- actionLabel?: string;
330
- actionHref?: string;
455
+ /** Props for the custom page component */
456
+ export interface SinglePageProps {
457
+ page: PostView;
458
+ theme?: ThemeComponents;
459
+ }
460
+
461
+ /** Props for the archive page component */
462
+ export interface ArchivePageProps {
463
+ groups: ArchiveGroup[];
464
+ hasMore: boolean;
465
+ nextCursor?: number;
466
+ type?: PostType;
467
+ theme?: ThemeComponents;
468
+ }
469
+
470
+ /** Props for the search page component */
471
+ export interface SearchPageProps {
472
+ query: string;
473
+ results: SearchResultView[];
474
+ error?: string;
475
+ hasMore: boolean;
476
+ page: number;
477
+ theme?: ThemeComponents;
478
+ }
479
+
480
+ /** Props for the collection page component */
481
+ export interface CollectionPageProps {
482
+ collection: Collection;
483
+ posts: PostView[];
484
+ theme?: ThemeComponents;
485
+ }
486
+
487
+ // =============================================================================
488
+ // Feed Data Types
489
+ // =============================================================================
490
+
491
+ /** Data passed to RSS/Atom feed renderers */
492
+ export interface FeedData {
493
+ siteName: string;
494
+ siteDescription: string;
495
+ siteUrl: string;
496
+ siteLanguage: string;
497
+ posts: PostView[];
498
+ }
499
+
500
+ /** Data passed to sitemap renderers */
501
+ export interface SitemapData {
502
+ siteUrl: string;
503
+ posts: PostView[];
331
504
  }
332
505
 
333
506
  // =============================================================================
@@ -336,42 +509,42 @@ export interface EmptyStateProps {
336
509
 
337
510
  /** Props for per-type timeline cards */
338
511
  export interface TimelineCardProps {
339
- post: PostWithMedia;
512
+ post: PostView;
340
513
  compact?: boolean;
341
514
  }
342
515
 
343
516
  /** Props for thread inline preview */
344
517
  export interface ThreadPreviewProps {
345
- rootPost: PostWithMedia;
346
- previewReplies: PostWithMedia[];
518
+ rootPost: PostView;
519
+ previewReplies: PostView[];
347
520
  totalReplyCount: number;
348
- }
349
-
350
- /** Data structure for a single timeline item */
351
- export interface TimelineItemData {
352
- post: PostWithMedia;
353
- threadPreview?: {
354
- replies: PostWithMedia[];
355
- totalReplyCount: number;
356
- };
521
+ theme?: ThemeComponents;
357
522
  }
358
523
 
359
524
  /** Props for the timeline feed wrapper */
360
525
  export interface TimelineFeedProps {
361
- items: TimelineItemData[];
526
+ items: TimelineItemView[];
362
527
  hasMore: boolean;
363
528
  nextCursor?: number;
529
+ theme?: ThemeComponents;
364
530
  }
365
531
 
366
532
  /**
367
533
  * Theme component overrides
368
534
  */
369
535
  export interface ThemeComponents {
370
- BaseLayout?: FC<BaseLayoutProps>;
371
- PostCard?: FC<PostCardProps>;
372
- PostList?: FC<PostListProps>;
373
- Pagination?: FC<PaginationProps>;
374
- EmptyState?: FC<EmptyStateProps>;
536
+ // Layout
537
+ SiteLayout?: FC<PropsWithChildren<SiteLayoutProps>>;
538
+
539
+ // Pages
540
+ HomePage?: FC<HomePageProps>;
541
+ PostPage?: FC<PostPageProps>;
542
+ SinglePage?: FC<SinglePageProps>;
543
+ ArchivePage?: FC<ArchivePageProps>;
544
+ SearchPage?: FC<SearchPageProps>;
545
+ CollectionPage?: FC<CollectionPageProps>;
546
+
547
+ // Timeline sub-components
375
548
  NoteCard?: FC<TimelineCardProps>;
376
549
  ArticleCard?: FC<TimelineCardProps>;
377
550
  LinkCard?: FC<TimelineCardProps>;
@@ -379,6 +552,48 @@ export interface ThemeComponents {
379
552
  ImageCard?: FC<TimelineCardProps>;
380
553
  ThreadPreview?: FC<ThreadPreviewProps>;
381
554
  TimelineFeed?: FC<TimelineFeedProps>;
555
+
556
+ // Shared sub-components (re-exported real prop types from component files)
557
+ Pagination?: FC<PaginationComponentProps>;
558
+ PagePagination?: FC<PagePaginationComponentProps>;
559
+ EmptyState?: FC<EmptyStateComponentProps>;
560
+ MediaGallery?: FC<MediaGalleryComponentProps>;
561
+ }
562
+
563
+ /**
564
+ * Real component prop types (re-exported from component files via index.ts).
565
+ * These are provided here as aliases to avoid circular imports in types.ts.
566
+ * The canonical definitions live in the component files.
567
+ */
568
+
569
+ /** @see Pagination component in theme/components/Pagination.tsx */
570
+ export interface PaginationComponentProps {
571
+ baseUrl: string;
572
+ hasMore: boolean;
573
+ nextCursor?: number | string;
574
+ prevCursor?: number | string;
575
+ cursorParam?: string;
576
+ }
577
+
578
+ /** @see PagePagination component in theme/components/Pagination.tsx */
579
+ export interface PagePaginationComponentProps {
580
+ baseUrl: string;
581
+ currentPage: number;
582
+ hasMore: boolean;
583
+ pageParam?: string;
584
+ }
585
+
586
+ /** @see EmptyState component in theme/components/EmptyState.tsx */
587
+ export interface EmptyStateComponentProps {
588
+ message: string;
589
+ ctaText?: string;
590
+ ctaHref?: string;
591
+ centered?: boolean;
592
+ }
593
+
594
+ /** @see MediaGallery component in theme/components/MediaGallery.tsx */
595
+ export interface MediaGalleryComponentProps {
596
+ attachments: MediaView[];
382
597
  }
383
598
 
384
599
  /**
@@ -389,6 +604,15 @@ export interface JantTheme {
389
604
  name?: string;
390
605
  /** Component overrides */
391
606
  components?: ThemeComponents;
607
+ /** Feed renderer overrides (RSS, Atom, Sitemap) */
608
+ feed?: {
609
+ /** Custom RSS 2.0 renderer — returns XML string */
610
+ rss?: (data: FeedData) => string;
611
+ /** Custom Atom renderer — returns XML string */
612
+ atom?: (data: FeedData) => string;
613
+ /** Custom Sitemap renderer — returns XML string */
614
+ sitemap?: (data: SitemapData) => string;
615
+ };
392
616
  /** CSS variable overrides (highest priority, always applied) */
393
617
  cssVariables?: Record<string, string>;
394
618
  /** Replace built-in color themes with a custom list */