@jant/core 0.3.36 → 0.3.38

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 (271) hide show
  1. package/bin/commands/export.js +1 -1
  2. package/bin/commands/import-site.js +529 -0
  3. package/bin/commands/reset-password.js +3 -2
  4. package/dist/client/assets/heic-to-DIRPI3VF.js +1 -0
  5. package/dist/client/assets/url-FWFqPJPb.js +1 -0
  6. package/dist/client/client.css +1 -1
  7. package/dist/client/client.js +4012 -3276
  8. package/dist/index.js +10285 -5809
  9. package/package.json +11 -3
  10. package/src/__tests__/helpers/app.ts +9 -9
  11. package/src/__tests__/helpers/db.ts +91 -93
  12. package/src/app.tsx +157 -27
  13. package/src/auth.ts +20 -2
  14. package/src/client/archive-nav.js +187 -0
  15. package/src/client/audio-player.ts +478 -0
  16. package/src/client/audio-processor.ts +84 -0
  17. package/src/client/avatar-upload.ts +3 -2
  18. package/src/client/components/__tests__/jant-compose-dialog.test.ts +645 -49
  19. package/src/client/components/__tests__/jant-compose-editor.test.ts +208 -16
  20. package/src/client/components/__tests__/jant-post-form.test.ts +19 -9
  21. package/src/client/components/__tests__/jant-settings-avatar.test.ts +2 -2
  22. package/src/client/components/__tests__/jant-settings-general.test.ts +3 -3
  23. package/src/client/components/collection-sidebar-types.ts +7 -9
  24. package/src/client/components/compose-types.ts +101 -4
  25. package/src/client/components/jant-collection-form.ts +43 -7
  26. package/src/client/components/jant-collection-sidebar.ts +88 -84
  27. package/src/client/components/jant-compose-dialog.ts +1655 -219
  28. package/src/client/components/jant-compose-editor.ts +732 -168
  29. package/src/client/components/jant-compose-fullscreen.ts +23 -78
  30. package/src/client/components/jant-media-lightbox.ts +2 -0
  31. package/src/client/components/jant-nav-manager.ts +24 -284
  32. package/src/client/components/jant-post-form.ts +89 -9
  33. package/src/client/components/jant-post-menu.ts +1019 -0
  34. package/src/client/components/jant-settings-avatar.ts +3 -3
  35. package/src/client/components/jant-settings-general.ts +38 -4
  36. package/src/client/components/jant-text-preview.ts +232 -0
  37. package/src/client/components/nav-manager-types.ts +4 -19
  38. package/src/client/components/post-form-template.ts +107 -12
  39. package/src/client/components/post-form-types.ts +11 -4
  40. package/src/client/compose-bridge.ts +410 -109
  41. package/src/client/image-processor.ts +26 -8
  42. package/src/client/media-metadata.ts +247 -0
  43. package/src/client/multipart-upload.ts +160 -0
  44. package/src/client/post-form-bridge.ts +52 -1
  45. package/src/client/settings-bridge.ts +0 -12
  46. package/src/client/thread-context.ts +140 -0
  47. package/src/client/tiptap/create-editor.ts +46 -0
  48. package/src/client/tiptap/extensions.ts +5 -0
  49. package/src/client/tiptap/image-node.ts +2 -8
  50. package/src/client/tiptap/paste-image.ts +2 -13
  51. package/src/client/tiptap/slash-commands.ts +173 -63
  52. package/src/client/toast.ts +101 -3
  53. package/src/client/types/sortablejs.d.ts +15 -0
  54. package/src/client/upload-with-metadata.ts +54 -0
  55. package/src/client/video-processor.ts +207 -0
  56. package/src/client.ts +5 -2
  57. package/src/db/__tests__/migrations.test.ts +118 -0
  58. package/src/db/index.ts +52 -0
  59. package/src/db/migrations/0000_baseline.sql +269 -0
  60. package/src/db/migrations/0001_fts_setup.sql +31 -0
  61. package/src/db/migrations/meta/0000_snapshot.json +703 -119
  62. package/src/db/migrations/meta/0001_snapshot.json +1337 -0
  63. package/src/db/migrations/meta/_journal.json +4 -39
  64. package/src/db/schema.ts +409 -145
  65. package/src/i18n/__tests__/detect.test.ts +115 -0
  66. package/src/i18n/context.tsx +2 -2
  67. package/src/i18n/detect.ts +85 -1
  68. package/src/i18n/i18n.ts +1 -1
  69. package/src/i18n/index.ts +2 -1
  70. package/src/i18n/locales/en.po +487 -1217
  71. package/src/i18n/locales/en.ts +1 -1
  72. package/src/i18n/locales/zh-Hans.po +613 -996
  73. package/src/i18n/locales/zh-Hans.ts +1 -1
  74. package/src/i18n/locales/zh-Hant.po +624 -1007
  75. package/src/i18n/locales/zh-Hant.ts +1 -1
  76. package/src/i18n/middleware.ts +6 -0
  77. package/src/index.ts +5 -7
  78. package/src/lib/__tests__/blurhash-placeholder.test.ts +75 -0
  79. package/src/lib/__tests__/constants.test.ts +0 -1
  80. package/src/lib/__tests__/markdown-to-tiptap.test.ts +358 -0
  81. package/src/lib/__tests__/nanoid.test.ts +26 -0
  82. package/src/lib/__tests__/schemas.test.ts +181 -63
  83. package/src/lib/__tests__/slug.test.ts +126 -0
  84. package/src/lib/__tests__/sse.test.ts +6 -6
  85. package/src/lib/__tests__/summary.test.ts +264 -0
  86. package/src/lib/__tests__/theme.test.ts +1 -1
  87. package/src/lib/__tests__/timeline.test.ts +33 -30
  88. package/src/lib/__tests__/tiptap-to-markdown.test.ts +346 -0
  89. package/src/lib/__tests__/view.test.ts +141 -66
  90. package/src/lib/blurhash-placeholder.ts +102 -0
  91. package/src/lib/constants.ts +3 -1
  92. package/src/lib/emoji-catalog.ts +885 -68
  93. package/src/lib/errors.ts +11 -8
  94. package/src/lib/feed.ts +78 -32
  95. package/src/lib/html.ts +2 -1
  96. package/src/lib/icon-catalog.ts +5033 -1
  97. package/src/lib/icons.ts +3 -2
  98. package/src/lib/index.ts +0 -1
  99. package/src/lib/markdown-to-tiptap.ts +286 -0
  100. package/src/lib/media-helpers.ts +12 -3
  101. package/src/lib/nanoid.ts +29 -0
  102. package/src/lib/navigation.ts +1 -1
  103. package/src/lib/render.tsx +20 -2
  104. package/src/lib/resolve-config.ts +6 -2
  105. package/src/lib/schemas.ts +224 -55
  106. package/src/lib/search-snippet.ts +34 -0
  107. package/src/lib/slug.ts +96 -0
  108. package/src/lib/sse.ts +6 -6
  109. package/src/lib/storage.ts +115 -7
  110. package/src/lib/summary.ts +66 -0
  111. package/src/lib/theme.ts +11 -8
  112. package/src/lib/timeline.ts +74 -34
  113. package/src/lib/tiptap-render.ts +5 -10
  114. package/src/lib/tiptap-to-markdown.ts +305 -0
  115. package/src/lib/upload.ts +190 -29
  116. package/src/lib/url.ts +31 -0
  117. package/src/lib/view.ts +204 -37
  118. package/src/middleware/__tests__/auth.test.ts +191 -11
  119. package/src/middleware/__tests__/onboarding.test.ts +12 -10
  120. package/src/middleware/auth.ts +63 -9
  121. package/src/middleware/onboarding.ts +1 -1
  122. package/src/middleware/secure-headers.ts +40 -0
  123. package/src/preset.css +45 -2
  124. package/src/routes/__tests__/compose.test.ts +17 -24
  125. package/src/routes/api/__tests__/collections.test.ts +109 -61
  126. package/src/routes/api/__tests__/nav-items.test.ts +46 -29
  127. package/src/routes/api/__tests__/posts.test.ts +132 -68
  128. package/src/routes/api/__tests__/search.test.ts +15 -2
  129. package/src/routes/api/__tests__/upload-multipart.test.ts +534 -0
  130. package/src/routes/api/collections.ts +51 -42
  131. package/src/routes/api/custom-urls.ts +80 -0
  132. package/src/routes/api/export.ts +31 -0
  133. package/src/routes/api/nav-items.ts +23 -19
  134. package/src/routes/api/posts.ts +43 -39
  135. package/src/routes/api/search.ts +3 -4
  136. package/src/routes/api/upload-multipart.ts +245 -0
  137. package/src/routes/api/upload.ts +85 -19
  138. package/src/routes/auth/__tests__/setup.test.ts +20 -60
  139. package/src/routes/auth/setup.tsx +26 -33
  140. package/src/routes/auth/signin.tsx +3 -7
  141. package/src/routes/compose.tsx +10 -55
  142. package/src/routes/dash/__tests__/settings-avatar.test.ts +1 -1
  143. package/src/routes/dash/custom-urls.tsx +414 -0
  144. package/src/routes/dash/settings.tsx +304 -232
  145. package/src/routes/feed/__tests__/rss.test.ts +27 -28
  146. package/src/routes/feed/rss.ts +6 -4
  147. package/src/routes/feed/sitemap.ts +2 -12
  148. package/src/routes/pages/__tests__/collections.test.ts +5 -6
  149. package/src/routes/pages/__tests__/featured.test.ts +41 -22
  150. package/src/routes/pages/archive.tsx +175 -39
  151. package/src/routes/pages/collection.tsx +22 -10
  152. package/src/routes/pages/collections.tsx +3 -3
  153. package/src/routes/pages/featured.tsx +28 -4
  154. package/src/routes/pages/home.tsx +16 -15
  155. package/src/routes/pages/latest.tsx +1 -11
  156. package/src/routes/pages/new.tsx +39 -0
  157. package/src/routes/pages/page.tsx +95 -49
  158. package/src/routes/pages/search.tsx +1 -1
  159. package/src/services/__tests__/api-token.test.ts +135 -0
  160. package/src/services/__tests__/collection.test.ts +275 -227
  161. package/src/services/__tests__/custom-url.test.ts +213 -0
  162. package/src/services/__tests__/media.test.ts +162 -22
  163. package/src/services/__tests__/navigation.test.ts +109 -68
  164. package/src/services/__tests__/post-timeline.test.ts +205 -32
  165. package/src/services/__tests__/post.test.ts +713 -234
  166. package/src/services/__tests__/search.test.ts +67 -10
  167. package/src/services/api-token.ts +166 -0
  168. package/src/services/auth.ts +17 -2
  169. package/src/services/collection.ts +397 -131
  170. package/src/services/custom-url.ts +188 -0
  171. package/src/services/export.ts +802 -0
  172. package/src/services/index.ts +26 -19
  173. package/src/services/media.ts +100 -22
  174. package/src/services/navigation.ts +158 -47
  175. package/src/services/path.ts +339 -0
  176. package/src/services/post.ts +687 -154
  177. package/src/services/search.ts +160 -75
  178. package/src/styles/components.css +58 -7
  179. package/src/styles/tokens.css +84 -6
  180. package/src/styles/ui.css +2964 -457
  181. package/src/types/bindings.ts +4 -1
  182. package/src/types/config.ts +12 -4
  183. package/src/types/constants.ts +15 -3
  184. package/src/types/entities.ts +74 -35
  185. package/src/types/operations.ts +11 -24
  186. package/src/types/props.ts +51 -16
  187. package/src/types/views.ts +45 -22
  188. package/src/ui/color-themes.ts +133 -23
  189. package/src/ui/compose/ComposeDialog.tsx +239 -17
  190. package/src/ui/compose/ComposePrompt.tsx +1 -1
  191. package/src/ui/dash/CrudPageHeader.tsx +1 -1
  192. package/src/ui/dash/ListItemRow.tsx +1 -1
  193. package/src/ui/dash/StatusBadge.tsx +3 -1
  194. package/src/ui/dash/appearance/AdvancedContent.tsx +22 -1
  195. package/src/ui/dash/appearance/ColorThemeContent.tsx +22 -2
  196. package/src/ui/dash/appearance/FontThemeContent.tsx +1 -1
  197. package/src/ui/dash/appearance/NavigationContent.tsx +5 -45
  198. package/src/ui/dash/index.ts +0 -3
  199. package/src/ui/dash/settings/AccountContent.tsx +3 -57
  200. package/src/ui/dash/settings/AccountMenuContent.tsx +147 -0
  201. package/src/ui/dash/settings/ApiTokensContent.tsx +232 -0
  202. package/src/ui/dash/settings/AvatarContent.tsx +8 -0
  203. package/src/ui/dash/settings/SessionsContent.tsx +159 -0
  204. package/src/ui/dash/settings/SettingsRootContent.tsx +45 -15
  205. package/src/ui/feed/LinkCard.tsx +89 -40
  206. package/src/ui/feed/NoteCard.tsx +39 -25
  207. package/src/ui/feed/PostStatusBadges.tsx +67 -0
  208. package/src/ui/feed/QuoteCard.tsx +38 -23
  209. package/src/ui/feed/ThreadPreview.tsx +90 -26
  210. package/src/ui/feed/TimelineFeed.tsx +3 -2
  211. package/src/ui/feed/TimelineItem.tsx +15 -6
  212. package/src/ui/feed/__tests__/thread-preview.test.ts +107 -0
  213. package/src/ui/feed/thread-preview-state.ts +61 -0
  214. package/src/ui/font-themes.ts +2 -2
  215. package/src/ui/layouts/BaseLayout.tsx +2 -2
  216. package/src/ui/layouts/SiteLayout.tsx +105 -92
  217. package/src/ui/pages/ArchivePage.tsx +923 -98
  218. package/src/ui/pages/ComposePage.tsx +54 -0
  219. package/src/ui/pages/PostPage.tsx +30 -45
  220. package/src/ui/pages/SearchPage.tsx +181 -37
  221. package/src/ui/shared/AdminBreadcrumb.tsx +29 -0
  222. package/src/ui/shared/CollectionsSidebar.tsx +47 -37
  223. package/src/ui/shared/MediaGallery.tsx +445 -149
  224. package/src/ui/shared/PostFooter.tsx +204 -0
  225. package/src/ui/shared/StarRating.tsx +27 -0
  226. package/src/ui/shared/__tests__/format-chars.test.ts +35 -0
  227. package/src/ui/shared/index.ts +0 -1
  228. package/dist/client/assets/url-8Dj-5CLW.js +0 -1
  229. package/src/client/media-upload.ts +0 -161
  230. package/src/client/page-slug-bridge.ts +0 -42
  231. package/src/db/migrations/0000_square_wallflower.sql +0 -118
  232. package/src/db/migrations/0001_add_search_fts.sql +0 -34
  233. package/src/db/migrations/0002_add_media_attachments.sql +0 -3
  234. package/src/db/migrations/0003_add_navigation_links.sql +0 -8
  235. package/src/db/migrations/0004_add_storage_provider.sql +0 -3
  236. package/src/db/migrations/0005_v2_schema_migration.sql +0 -268
  237. package/src/db/migrations/0006_rename_slug_to_path.sql +0 -5
  238. package/src/db/migrations/0007_post_collections_m2m.sql +0 -94
  239. package/src/db/migrations/0008_add_collection_dividers.sql +0 -8
  240. package/src/db/migrations/0009_drop_collection_show_divider.sql +0 -2
  241. package/src/db/migrations/0010_add_performance_indexes.sql +0 -16
  242. package/src/db/migrations/0011_add_path_registry.sql +0 -23
  243. package/src/db/migrations/0012_add_tiptap_columns.sql +0 -2
  244. package/src/db/migrations/0013_replace_featured_with_visibility.sql +0 -8
  245. package/src/db/migrations/meta/0003_snapshot.json +0 -821
  246. package/src/lib/__tests__/sqid.test.ts +0 -65
  247. package/src/lib/sqid.ts +0 -79
  248. package/src/routes/api/__tests__/pages.test.ts +0 -218
  249. package/src/routes/api/pages.ts +0 -73
  250. package/src/routes/dash/__tests__/pages.test.ts +0 -226
  251. package/src/routes/dash/index.tsx +0 -109
  252. package/src/routes/dash/media.tsx +0 -135
  253. package/src/routes/dash/pages.tsx +0 -245
  254. package/src/routes/dash/posts.tsx +0 -338
  255. package/src/routes/dash/redirects.tsx +0 -263
  256. package/src/routes/pages/post.tsx +0 -59
  257. package/src/services/__tests__/page.test.ts +0 -298
  258. package/src/services/__tests__/path-registry.test.ts +0 -165
  259. package/src/services/__tests__/redirect.test.ts +0 -159
  260. package/src/services/page.ts +0 -216
  261. package/src/services/path-registry.ts +0 -160
  262. package/src/services/redirect.ts +0 -97
  263. package/src/ui/dash/PageForm.tsx +0 -187
  264. package/src/ui/dash/PostList.tsx +0 -95
  265. package/src/ui/dash/media/MediaListContent.tsx +0 -206
  266. package/src/ui/dash/media/ViewMediaContent.tsx +0 -208
  267. package/src/ui/dash/pages/PagesContent.tsx +0 -75
  268. package/src/ui/dash/posts/PostForm.tsx +0 -260
  269. package/src/ui/layouts/DashLayout.tsx +0 -247
  270. package/src/ui/pages/SinglePage.tsx +0 -23
  271. package/src/ui/shared/ThreadView.tsx +0 -136
@@ -12,6 +12,7 @@ export interface Bindings {
12
12
  IMAGE_TRANSFORM_URL?: string;
13
13
  DEMO_EMAIL?: string;
14
14
  DEMO_PASSWORD?: string;
15
+ DEV_API_TOKEN?: string;
15
16
  // Timeline
16
17
  PAGE_SIZE?: string;
17
18
  // Site configuration (optional - can be overridden in DB)
@@ -28,10 +29,12 @@ export interface Bindings {
28
29
  S3_REGION?: string;
29
30
  S3_PUBLIC_URL?: string;
30
31
  // Upload
31
- UPLOAD_MAX_FILE_SIZE?: string;
32
+ UPLOAD_MAX_FILE_SIZE_MB?: string;
32
33
  // Summary extraction
33
34
  SUMMARY_MAX_PARAGRAPHS?: string;
34
35
  SUMMARY_MAX_CHARS?: string;
36
+ // Slug generation
37
+ SLUG_ID_LENGTH?: string;
35
38
  // RSS feed
36
39
  RSS_FEED_LIMIT?: string;
37
40
  }
@@ -15,7 +15,7 @@
15
15
  * - envOnly: true -> Environment-only (ENV > Default)
16
16
  */
17
17
  export const CONFIG_FIELDS = {
18
- // User-configurable (can be modified in dashboard)
18
+ // User-configurable (can be modified in settings)
19
19
  SITE_NAME: {
20
20
  defaultValue: "Jant",
21
21
  envOnly: false,
@@ -98,7 +98,7 @@ export const CONFIG_FIELDS = {
98
98
  defaultValue: "",
99
99
  envOnly: true,
100
100
  },
101
- UPLOAD_MAX_FILE_SIZE: {
101
+ UPLOAD_MAX_FILE_SIZE_MB: {
102
102
  defaultValue: "500",
103
103
  envOnly: true,
104
104
  },
@@ -110,8 +110,12 @@ export const CONFIG_FIELDS = {
110
110
  defaultValue: "500",
111
111
  envOnly: true,
112
112
  },
113
+ SLUG_ID_LENGTH: {
114
+ defaultValue: "5",
115
+ envOnly: true,
116
+ },
113
117
 
114
- // Internal settings (DB-only, not configurable via env or dashboard)
118
+ // Internal settings (DB-only, not configurable via env or settings UI)
115
119
  THEME: {
116
120
  defaultValue: "",
117
121
  envOnly: false,
@@ -221,6 +225,10 @@ export interface AppConfig {
221
225
  pageSize: number;
222
226
  rssFeedLimit: number;
223
227
 
228
+ // Slug (ENV only)
229
+ /** Length of random IDs used in auto-generated slugs. Defaults to 5. */
230
+ slugIdLength: number;
231
+
224
232
  // Demo (ENV only)
225
233
  demoEmail: string;
226
234
  demoPassword: string;
@@ -238,7 +246,7 @@ export interface AppConfig {
238
246
  siteAvatarUrl: string;
239
247
  faviconVersion: string;
240
248
 
241
- // Dashboard form placeholders (ENV > Default, without DB)
249
+ // Settings form placeholders (ENV > Default, without DB)
242
250
  fallbacks: {
243
251
  siteName: string;
244
252
  siteDescription: string;
@@ -8,7 +8,7 @@ export type Format = (typeof FORMATS)[number];
8
8
  export const STATUSES = ["draft", "published"] as const;
9
9
  export type Status = (typeof STATUSES)[number];
10
10
 
11
- export const VISIBILITIES = ["listed", "featured", "unlisted"] as const;
11
+ export const VISIBILITIES = ["public", "unlisted", "private"] as const;
12
12
  export type Visibility = (typeof VISIBILITIES)[number];
13
13
 
14
14
  export const SORT_ORDERS = [
@@ -19,12 +19,12 @@ export const SORT_ORDERS = [
19
19
  ] as const;
20
20
  export type SortOrder = (typeof SORT_ORDERS)[number];
21
21
 
22
- export const NAV_ITEM_TYPES = ["page", "link", "system"] as const;
22
+ export const NAV_ITEM_TYPES = ["link", "system"] as const;
23
23
  export type NavItemType = (typeof NAV_ITEM_TYPES)[number];
24
24
 
25
25
  export const SYSTEM_NAV_KEYS = {
26
26
  rss: { defaultLabel: "RSS", url: "/feed" },
27
- dashboard: { defaultLabel: "Dashboard", url: "/dash" },
27
+ settings: { defaultLabel: "Settings", url: "/settings" },
28
28
  collections: { defaultLabel: "Collections", url: "/c" },
29
29
  archive: { defaultLabel: "Archive", url: "/archive" },
30
30
  } as const;
@@ -33,5 +33,17 @@ export type SystemNavKey = keyof typeof SYSTEM_NAV_KEYS;
33
33
  export const MAX_MEDIA_ATTACHMENTS = 20;
34
34
  export const MAX_PINNED_POSTS = 3;
35
35
 
36
+ export const MEDIA_KINDS = [
37
+ "image",
38
+ "video",
39
+ "audio",
40
+ "text",
41
+ "document",
42
+ ] as const;
43
+ export type MediaKind = (typeof MEDIA_KINDS)[number];
44
+
36
45
  export const STORAGE_DRIVERS = ["r2", "s3"] as const;
37
46
  export type StorageDriver = (typeof STORAGE_DRIVERS)[number];
47
+
48
+ export const PATH_KINDS = ["slug", "alias", "redirect"] as const;
49
+ export type PathKind = (typeof PATH_KINDS)[number];
@@ -8,44 +8,38 @@ import type {
8
8
  Visibility,
9
9
  SortOrder,
10
10
  NavItemType,
11
+ MediaKind,
12
+ PathKind,
11
13
  } from "./constants.js";
12
14
 
13
15
  export interface Post {
14
- id: number;
16
+ id: string;
15
17
  format: Format;
16
18
  status: Status;
17
19
  visibility: Visibility;
18
- pinned: number; // 0 | 1
19
- path: string | null;
20
+ pinnedAt: number | null;
21
+ featuredAt: number | null;
22
+ slug: string;
20
23
  title: string | null;
21
24
  url: string | null;
22
25
  body: string | null;
23
26
  bodyHtml: string | null;
27
+ bodyText: string | null;
24
28
  quoteText: string | null;
25
29
  summary: string | null;
26
30
  rating: number | null;
27
- replyToId: number | null;
28
- threadId: number | null;
31
+ replyToId: string | null;
32
+ threadId: string;
29
33
  deletedAt: number | null;
30
- publishedAt: number;
31
- createdAt: number;
32
- updatedAt: number;
33
- }
34
-
35
- export interface Page {
36
- id: number;
37
- slug: string;
38
- title: string | null;
39
- body: string | null;
40
- bodyHtml: string | null;
41
- status: Status;
34
+ publishedAt: number | null;
35
+ lastActivityAt: number;
42
36
  createdAt: number;
43
37
  updatedAt: number;
44
38
  }
45
39
 
46
40
  export interface Media {
47
41
  id: string; // UUIDv7
48
- postId: number | null;
42
+ postId: string | null;
49
43
  filename: string;
50
44
  originalName: string;
51
45
  mimeType: string;
@@ -55,9 +49,15 @@ export interface Media {
55
49
  width: number | null;
56
50
  height: number | null;
57
51
  alt: string | null;
58
- position: number;
52
+ position: string;
59
53
  blurhash: string | null;
54
+ waveform: string | null;
55
+ posterKey: string | null;
56
+ summary: string | null;
57
+ chars: number | null;
58
+ mediaKind: MediaKind;
60
59
  createdAt: number;
60
+ updatedAt: number;
61
61
  }
62
62
 
63
63
  export interface MediaAttachment {
@@ -66,10 +66,16 @@ export interface MediaAttachment {
66
66
  previewUrl: string;
67
67
  alt: string | null;
68
68
  blurhash: string | null;
69
+ waveform: string | null;
70
+ posterUrl: string | null;
69
71
  width: number | null;
70
72
  height: number | null;
71
- position: number;
73
+ position: string;
72
74
  mimeType: string;
75
+ originalName: string;
76
+ size: number;
77
+ summary: string | null;
78
+ chars: number | null;
73
79
  }
74
80
 
75
81
  export interface PostWithMedia extends Post {
@@ -77,46 +83,62 @@ export interface PostWithMedia extends Post {
77
83
  }
78
84
 
79
85
  export interface Collection {
80
- id: number;
86
+ id: string;
81
87
  slug: string;
82
88
  title: string;
83
89
  description: string | null;
84
90
  icon: string | null;
85
91
  sortOrder: SortOrder;
86
- position: number;
87
92
  createdAt: number;
88
93
  updatedAt: number;
89
94
  }
90
95
 
91
- export interface CollectionDivider {
92
- id: number;
93
- position: number;
96
+ export type SidebarItemType = "collection" | "divider";
97
+
98
+ export interface SidebarItem {
99
+ id: string;
100
+ type: SidebarItemType;
101
+ collectionId: string | null;
102
+ position: string;
94
103
  createdAt: number;
95
104
  updatedAt: number;
96
105
  }
97
106
 
98
107
  export interface PostCollection {
99
- postId: number;
100
- collectionId: number;
108
+ postId: string;
109
+ collectionId: string;
101
110
  }
102
111
 
103
112
  export interface NavItem {
104
- id: number;
113
+ id: string;
105
114
  type: NavItemType;
106
115
  label: string;
107
116
  url: string;
108
- pageId: number | null;
109
- position: number;
117
+ position: string;
110
118
  createdAt: number;
111
119
  updatedAt: number;
112
120
  }
113
121
 
114
- export interface Redirect {
115
- id: number;
116
- fromPath: string;
117
- toPath: string;
118
- type: 301 | 302;
122
+ export interface CustomUrl {
123
+ id: string;
124
+ path: string;
125
+ targetType: "post" | "collection" | "redirect";
126
+ targetId: string | null;
127
+ toPath: string | null;
128
+ redirectType: 301 | 302 | null;
129
+ createdAt: number;
130
+ }
131
+
132
+ export interface PathRecord {
133
+ id: string;
134
+ path: string;
135
+ kind: PathKind;
136
+ postId: string | null;
137
+ collectionId: string | null;
138
+ redirectToPath: string | null;
139
+ redirectType: 301 | 302 | null;
119
140
  createdAt: number;
141
+ updatedAt: number;
120
142
  }
121
143
 
122
144
  export interface Setting {
@@ -124,3 +146,20 @@ export interface Setting {
124
146
  value: string;
125
147
  updatedAt: number;
126
148
  }
149
+
150
+ export interface ApiToken {
151
+ id: string;
152
+ name: string;
153
+ prefix: string;
154
+ lastUsedAt: number | null;
155
+ createdAt: number;
156
+ updatedAt: number;
157
+ }
158
+
159
+ /** Latest-reply context for a thread root, used in timeline display */
160
+ export interface ThreadTimelineContext {
161
+ latestReply: Post;
162
+ /** Parent of latestReply, only if it's not the root */
163
+ parentReply: Post | null;
164
+ totalReplyCount: number;
165
+ }
@@ -15,14 +15,17 @@ export interface CreatePost {
15
15
  status?: Status;
16
16
  visibility?: Visibility;
17
17
  pinned?: boolean;
18
+ featured?: boolean;
19
+ slug?: string;
18
20
  path?: string;
19
21
  title?: string;
20
22
  url?: string;
21
23
  body?: string;
24
+ bodyMarkdown?: string;
22
25
  quoteText?: string;
23
26
  rating?: number;
24
- collectionIds?: number[];
25
- replyToId?: number;
27
+ collectionIds?: string[];
28
+ replyToId?: string;
26
29
  publishedAt?: number;
27
30
  mediaIds?: string[];
28
31
  }
@@ -32,45 +35,31 @@ export interface UpdatePost {
32
35
  status?: Status;
33
36
  visibility?: Visibility;
34
37
  pinned?: boolean;
35
- path?: string | null;
38
+ featured?: boolean;
39
+ slug?: string;
36
40
  title?: string | null;
37
41
  url?: string | null;
38
42
  body?: string | null;
43
+ bodyMarkdown?: string | null;
39
44
  quoteText?: string | null;
40
45
  rating?: number | null;
41
- collectionIds?: number[];
46
+ collectionIds?: string[];
42
47
  publishedAt?: number;
43
48
  mediaIds?: string[];
44
49
  }
45
50
 
46
- export interface CreatePage {
47
- slug: string;
48
- title?: string;
49
- body?: string;
50
- status?: Status;
51
- }
52
-
53
- export interface UpdatePage {
54
- slug?: string;
55
- title?: string | null;
56
- body?: string | null;
57
- status?: Status;
58
- }
59
-
60
51
  export interface CreateNavItem {
61
52
  type: NavItemType;
62
53
  label: string;
63
54
  url: string;
64
- pageId?: number;
65
- position?: number;
55
+ position?: string;
66
56
  }
67
57
 
68
58
  export interface UpdateNavItem {
69
59
  type?: NavItemType;
70
60
  label?: string;
71
61
  url?: string;
72
- pageId?: number | null;
73
- position?: number;
62
+ position?: string;
74
63
  }
75
64
 
76
65
  export interface CreateCollection {
@@ -79,7 +68,6 @@ export interface CreateCollection {
79
68
  description?: string;
80
69
  icon?: string;
81
70
  sortOrder?: SortOrder;
82
- position?: number;
83
71
  }
84
72
 
85
73
  export interface UpdateCollection {
@@ -88,5 +76,4 @@ export interface UpdateCollection {
88
76
  description?: string | null;
89
77
  icon?: string | null;
90
78
  sortOrder?: SortOrder;
91
- position?: number;
92
79
  }
@@ -2,11 +2,10 @@
2
2
  * Page-Level Props & Feed Data Types
3
3
  */
4
4
 
5
- import type { Format, Visibility } from "./constants.js";
5
+ import type { Format, MediaKind } from "./constants.js";
6
6
  import type { Collection } from "./entities.js";
7
7
  import type {
8
8
  PostView,
9
- PageView,
10
9
  TimelineItemView,
11
10
  SearchResultView,
12
11
  ArchiveGroup,
@@ -19,7 +18,6 @@ import type {
19
18
  /** Props for the home page component */
20
19
  export interface HomePageProps {
21
20
  items: TimelineItemView[];
22
- pinnedItems: PostView[];
23
21
  currentPage: number;
24
22
  totalPages: number;
25
23
  }
@@ -27,11 +25,7 @@ export interface HomePageProps {
27
25
  /** Props for the single post page component */
28
26
  export interface PostPageProps {
29
27
  post: PostView;
30
- }
31
-
32
- /** Props for the custom page component */
33
- export interface SinglePageProps {
34
- page: PageView;
28
+ threadPosts?: PostView[];
35
29
  }
36
30
 
37
31
  /** Props for the featured page component */
@@ -39,13 +33,35 @@ export interface FeaturedPageProps {
39
33
  items: TimelineItemView[];
40
34
  }
41
35
 
36
+ /** Visibility filter values for the archive page (includes "featured" as a virtual value). */
37
+ export type ArchiveVisibility = "public" | "unlisted" | "private" | "featured";
38
+
39
+ /** View mode for the archive page. */
40
+ export type ArchiveView = "grid" | "list";
41
+
42
+ /** Filters currently active on the archive page */
43
+ export interface ArchiveFilters {
44
+ year?: number;
45
+ collectionSlug?: string;
46
+ collectionTitle?: string;
47
+ collectionIcon?: string | null;
48
+ format?: Format;
49
+ mediaKinds?: MediaKind[];
50
+ hasMedia?: boolean;
51
+ hasTitle?: boolean;
52
+ visibility?: ArchiveVisibility;
53
+ view?: ArchiveView;
54
+ }
55
+
42
56
  /** Props for the archive page component */
43
57
  export interface ArchivePageProps {
44
58
  groups: ArchiveGroup[];
45
- hasMore: boolean;
46
- nextCursor?: number;
47
- format?: Format;
48
- visibility?: Visibility;
59
+ currentPage: number;
60
+ totalPages: number;
61
+ filters: ArchiveFilters;
62
+ availableYears: number[];
63
+ availableCollections: { slug: string; title: string; icon: string | null }[];
64
+ isAuthenticated: boolean;
49
65
  }
50
66
 
51
67
  /** Props for the search page component */
@@ -62,7 +78,7 @@ export interface CollectionPageProps {
62
78
  collection: Collection;
63
79
  items: TimelineItemView[];
64
80
  hasMore: boolean;
65
- nextCursor?: number;
81
+ nextCursor?: string;
66
82
  }
67
83
 
68
84
  /** Props for the collections list page component */
@@ -87,23 +103,42 @@ export interface FeedData {
87
103
  export interface SitemapData {
88
104
  siteUrl: string;
89
105
  posts: PostView[];
90
- pages: PageView[];
91
106
  }
92
107
 
93
108
  // =============================================================================
94
109
  // Timeline Types
95
110
  // =============================================================================
96
111
 
112
+ /**
113
+ * Display mode for timeline cards.
114
+ * - `compact` — condensed view for constrained contexts
115
+ * - `feed` — standard timeline card (default)
116
+ * - `detail` — full single-post page view
117
+ */
118
+ export type CardMode = "compact" | "feed" | "detail";
119
+
120
+ export interface PostFooterDisplayOptions {
121
+ hideActions?: boolean;
122
+ }
123
+
124
+ export interface TimelineCardDisplayOptions {
125
+ hideStatusBadges?: boolean;
126
+ hideRating?: boolean;
127
+ footer?: PostFooterDisplayOptions;
128
+ }
129
+
97
130
  /** Props for per-type timeline cards */
98
131
  export interface TimelineCardProps {
99
132
  post: PostView;
100
- compact?: boolean;
133
+ mode?: CardMode;
134
+ display?: TimelineCardDisplayOptions;
101
135
  }
102
136
 
103
137
  /** Props for thread inline preview */
104
138
  export interface ThreadPreviewProps {
105
139
  rootPost: PostView;
106
- previewReplies: PostView[];
140
+ latestReply: PostView;
141
+ parentReply?: PostView;
107
142
  totalReplyCount: number;
108
143
  }
109
144
 
@@ -5,17 +5,29 @@
5
5
  import type { Format, Status, Visibility, NavItemType } from "./constants.js";
6
6
  import type { Post, Collection } from "./entities.js";
7
7
 
8
+ /**
9
+ * Render-ready collection tag for display in post footers.
10
+ * Pre-computed at the viewmodel layer -- no lib/ imports needed.
11
+ */
12
+ export interface CollectionTagView {
13
+ slug: string;
14
+ title: string;
15
+ /** Pre-rendered icon HTML (SVG or emoji span) */
16
+ iconHtml?: string;
17
+ }
18
+
8
19
  /**
9
20
  * Render-ready post data for theme components.
10
21
  * All fields are pre-computed -- no lib/ imports needed.
11
22
  */
12
23
  export interface PostView {
13
24
  // Identity
14
- id: number;
15
- /** Pre-computed permalink: "/{path}" if path set, otherwise "/p/{sqid}" */
25
+ /** UUIDv7 identifier */
26
+ id: string;
27
+ /** Pre-computed permalink: "/{slug}" */
16
28
  permalink: string;
17
- /** Custom URL path, if set. Supports multi-level paths (e.g. "2024/my-post") */
18
- path?: string;
29
+ /** Post slug */
30
+ slug: string;
19
31
 
20
32
  // Content
21
33
  title?: string;
@@ -37,6 +49,7 @@ export interface PostView {
37
49
  status: Status;
38
50
  visibility: Visibility;
39
51
  pinned: boolean;
52
+ featured: boolean;
40
53
  rating?: number;
41
54
 
42
55
  // Time -- pre-formatted
@@ -54,27 +67,23 @@ export interface PostView {
54
67
  // Media -- URLs pre-computed
55
68
  media: MediaView[];
56
69
 
70
+ // Collections this post belongs to
71
+ collections: CollectionTagView[];
72
+
57
73
  // Thread context
58
- replyToId?: number;
59
- threadRootId?: number;
74
+ /** UUIDv7 of the parent post */
75
+ replyToId?: string;
76
+ /** UUIDv7 of the thread root post */
77
+ threadRootId?: string;
78
+ /** Permalink of the thread root post (for "in thread" link on featured replies) */
79
+ threadRootPermalink?: string;
80
+ /** Whether this post is the last (most recent) in its thread. Controls reply button visibility. */
81
+ isLastInThread: boolean;
60
82
 
61
83
  // Raw content (for forms/editing, not typical theme use)
62
84
  body?: string;
63
85
  }
64
86
 
65
- /**
66
- * Render-ready page data for theme components.
67
- */
68
- export interface PageView {
69
- id: number;
70
- slug: string;
71
- title?: string;
72
- bodyHtml?: string;
73
- status: Status;
74
- createdAt: string;
75
- updatedAt: string;
76
- }
77
-
78
87
  /**
79
88
  * Render-ready media data for theme components.
80
89
  * URLs are pre-computed -- no lib/ imports needed.
@@ -90,6 +99,12 @@ export interface MediaView {
90
99
  width?: number;
91
100
  height?: number;
92
101
  size?: number;
102
+ blurhash?: string;
103
+ waveform?: string;
104
+ posterUrl?: string;
105
+ originalName?: string;
106
+ summary?: string;
107
+ chars?: number;
93
108
  }
94
109
 
95
110
  /**
@@ -97,11 +112,11 @@ export interface MediaView {
97
112
  * Active/external state pre-computed.
98
113
  */
99
114
  export interface NavItemView {
100
- id: number;
115
+ /** UUIDv7 identifier */
116
+ id: string;
101
117
  type: NavItemType;
102
118
  label: string;
103
119
  url: string;
104
- pageId?: number;
105
120
  /** Pre-computed based on currentPath */
106
121
  isActive: boolean;
107
122
  /** Pre-computed: starts with http(s):// */
@@ -125,7 +140,12 @@ export interface SearchResult {
125
140
  export interface SearchResultView {
126
141
  post: PostView;
127
142
  rank: number;
143
+ /** FTS5 snippet from body_text column with <mark> tags */
128
144
  snippet?: string;
145
+ /** Title with matched query terms wrapped in <mark> */
146
+ titleHighlighted?: string;
147
+ /** quoteText (truncated) with matched query terms wrapped in <mark> */
148
+ quoteHighlighted?: string;
129
149
  }
130
150
 
131
151
  /**
@@ -134,7 +154,8 @@ export interface SearchResultView {
134
154
  export interface TimelineItemView {
135
155
  post: PostView;
136
156
  threadPreview?: {
137
- replies: PostView[];
157
+ latestReply: PostView;
158
+ parentReply?: PostView;
138
159
  totalReplyCount: number;
139
160
  };
140
161
  }
@@ -168,4 +189,6 @@ export interface SiteLayoutProps {
168
189
  siteFooterHtml?: string;
169
190
  sidebar?: import("hono/jsx").Child;
170
191
  uploadMaxFileSize?: number;
192
+ showComposeDialog?: boolean;
193
+ showHeader?: boolean;
171
194
  }