@jant/core 0.3.35 → 0.3.37

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 (307) 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/module-RjUF93sV.js +716 -0
  6. package/dist/client/assets/native-48B9X9Wg.js +1 -0
  7. package/dist/client/assets/url-FWFqPJPb.js +1 -0
  8. package/dist/client/client.css +1 -1
  9. package/dist/client/client.js +4564 -3013
  10. package/dist/index.js +12885 -8161
  11. package/package.json +23 -6
  12. package/src/__tests__/helpers/app.ts +10 -10
  13. package/src/__tests__/helpers/db.ts +91 -87
  14. package/src/app.tsx +157 -31
  15. package/src/auth.ts +20 -2
  16. package/src/client/archive-nav.js +187 -0
  17. package/src/client/audio-player.ts +478 -0
  18. package/src/client/audio-processor.ts +84 -0
  19. package/src/{lib → client}/avatar-upload.ts +4 -3
  20. package/src/{lib → client}/collection-form-bridge.ts +2 -2
  21. package/src/{ui → client}/components/__tests__/jant-collection-form.test.ts +26 -9
  22. package/src/client/components/__tests__/jant-compose-dialog.test.ts +1140 -0
  23. package/src/client/components/__tests__/jant-compose-editor.test.ts +504 -0
  24. package/src/{ui → client}/components/__tests__/jant-post-form.test.ts +37 -17
  25. package/src/{ui → client}/components/__tests__/jant-settings-avatar.test.ts +2 -2
  26. package/src/{ui → client}/components/__tests__/jant-settings-general.test.ts +3 -3
  27. package/src/client/components/collection-sidebar-types.ts +43 -0
  28. package/src/{ui → client}/components/collection-types.ts +3 -4
  29. package/src/client/components/compose-types.ts +174 -0
  30. package/src/client/components/jant-collection-form.ts +667 -0
  31. package/src/client/components/jant-collection-sidebar.ts +805 -0
  32. package/src/client/components/jant-compose-dialog.ts +2161 -0
  33. package/src/client/components/jant-compose-editor.ts +1813 -0
  34. package/src/client/components/jant-compose-fullscreen.ts +283 -0
  35. package/src/client/components/jant-media-lightbox.ts +259 -0
  36. package/src/{ui → client}/components/jant-nav-manager.ts +97 -298
  37. package/src/{ui → client}/components/jant-post-form.ts +141 -12
  38. package/src/client/components/jant-post-menu.ts +1019 -0
  39. package/src/{ui → client}/components/jant-settings-avatar.ts +3 -3
  40. package/src/{ui → client}/components/jant-settings-general.ts +38 -4
  41. package/src/client/components/jant-text-preview.ts +232 -0
  42. package/src/{ui → client}/components/nav-manager-types.ts +6 -18
  43. package/src/{ui → client}/components/post-form-template.ts +137 -38
  44. package/src/{ui → client}/components/post-form-types.ts +15 -4
  45. package/src/client/compose-bridge.ts +583 -0
  46. package/src/{lib → client}/image-processor.ts +26 -8
  47. package/src/client/lazy-slugify.ts +51 -0
  48. package/src/client/media-metadata.ts +247 -0
  49. package/src/client/multipart-upload.ts +160 -0
  50. package/src/{lib → client}/nav-manager-bridge.ts +1 -1
  51. package/src/{lib → client}/post-form-bridge.ts +53 -2
  52. package/src/{lib → client}/settings-bridge.ts +3 -15
  53. package/src/client/thread-context.ts +140 -0
  54. package/src/client/tiptap/bubble-menu.ts +205 -0
  55. package/src/client/tiptap/create-editor.ts +86 -0
  56. package/src/client/tiptap/exitable-marks.ts +73 -0
  57. package/src/client/tiptap/extensions.ts +65 -0
  58. package/src/client/tiptap/image-node.ts +482 -0
  59. package/src/client/tiptap/link-toolbar.ts +371 -0
  60. package/src/client/tiptap/more-break.ts +50 -0
  61. package/src/client/tiptap/paste-image.ts +129 -0
  62. package/src/client/tiptap/slash-commands.ts +438 -0
  63. package/src/{lib → client}/toast.ts +101 -3
  64. package/src/client/types/sortablejs.d.ts +44 -0
  65. package/src/client/upload-with-metadata.ts +54 -0
  66. package/src/client/video-processor.ts +207 -0
  67. package/src/client.ts +27 -17
  68. package/src/db/__tests__/migrations.test.ts +118 -0
  69. package/src/db/index.ts +52 -0
  70. package/src/db/migrations/0000_baseline.sql +269 -0
  71. package/src/db/migrations/0001_fts_setup.sql +31 -0
  72. package/src/db/migrations/meta/0000_snapshot.json +703 -119
  73. package/src/db/migrations/meta/0001_snapshot.json +1337 -0
  74. package/src/db/migrations/meta/_journal.json +4 -39
  75. package/src/db/schema.ts +409 -140
  76. package/src/i18n/__tests__/detect.test.ts +115 -0
  77. package/src/i18n/context.tsx +2 -2
  78. package/src/i18n/detect.ts +85 -1
  79. package/src/i18n/i18n.ts +1 -1
  80. package/src/i18n/index.ts +2 -1
  81. package/src/i18n/locales/en.po +783 -1087
  82. package/src/i18n/locales/en.ts +1 -1
  83. package/src/i18n/locales/zh-Hans.po +867 -812
  84. package/src/i18n/locales/zh-Hans.ts +1 -1
  85. package/src/i18n/locales/zh-Hant.po +878 -823
  86. package/src/i18n/locales/zh-Hant.ts +1 -1
  87. package/src/i18n/middleware.ts +6 -0
  88. package/src/index.ts +5 -7
  89. package/src/lib/__tests__/blurhash-placeholder.test.ts +75 -0
  90. package/src/lib/__tests__/constants.test.ts +0 -1
  91. package/src/lib/__tests__/markdown-to-tiptap.test.ts +358 -0
  92. package/src/lib/__tests__/nanoid.test.ts +26 -0
  93. package/src/lib/__tests__/resolve-config.test.ts +2 -2
  94. package/src/lib/__tests__/schemas.test.ts +186 -65
  95. package/src/lib/__tests__/slug.test.ts +126 -0
  96. package/src/lib/__tests__/sse.test.ts +6 -6
  97. package/src/lib/__tests__/summary.test.ts +264 -0
  98. package/src/lib/__tests__/theme.test.ts +1 -1
  99. package/src/lib/__tests__/timeline.test.ts +33 -30
  100. package/src/lib/__tests__/tiptap-to-markdown.test.ts +346 -0
  101. package/src/lib/__tests__/url.test.ts +2 -2
  102. package/src/lib/__tests__/view.test.ts +140 -65
  103. package/src/lib/blurhash-placeholder.ts +102 -0
  104. package/src/lib/constants.ts +3 -1
  105. package/src/lib/emoji-catalog.ts +963 -0
  106. package/src/lib/errors.ts +11 -8
  107. package/src/lib/feed.ts +77 -31
  108. package/src/lib/html.ts +2 -1
  109. package/src/lib/icon-catalog.ts +5033 -1
  110. package/src/lib/icons.ts +3 -2
  111. package/src/lib/index.ts +0 -1
  112. package/src/lib/markdown-to-tiptap.ts +286 -0
  113. package/src/lib/media-helpers.ts +22 -12
  114. package/src/lib/nanoid.ts +29 -0
  115. package/src/lib/navigation.ts +1 -1
  116. package/src/lib/render.tsx +24 -5
  117. package/src/lib/resolve-config.ts +13 -2
  118. package/src/lib/schemas.ts +226 -58
  119. package/src/lib/search-snippet.ts +34 -0
  120. package/src/lib/slug.ts +96 -0
  121. package/src/lib/sse.ts +6 -6
  122. package/src/lib/storage.ts +115 -7
  123. package/src/lib/summary.ts +158 -0
  124. package/src/lib/theme.ts +11 -8
  125. package/src/lib/timeline.ts +76 -34
  126. package/src/lib/tiptap-render.ts +191 -0
  127. package/src/lib/tiptap-to-markdown.ts +305 -0
  128. package/src/lib/upload.ts +263 -14
  129. package/src/lib/url.ts +37 -22
  130. package/src/lib/view.ts +236 -55
  131. package/src/middleware/__tests__/auth.test.ts +191 -11
  132. package/src/middleware/__tests__/onboarding.test.ts +12 -10
  133. package/src/middleware/auth.ts +63 -9
  134. package/src/middleware/error-handler.ts +3 -3
  135. package/src/middleware/onboarding.ts +1 -1
  136. package/src/middleware/secure-headers.ts +40 -0
  137. package/src/preset.css +83 -2
  138. package/src/routes/__tests__/compose.test.ts +17 -24
  139. package/src/routes/api/__tests__/collections.test.ts +109 -61
  140. package/src/routes/api/__tests__/nav-items.test.ts +46 -29
  141. package/src/routes/api/__tests__/posts.test.ts +132 -68
  142. package/src/routes/api/__tests__/search.test.ts +15 -2
  143. package/src/routes/api/__tests__/upload-multipart.test.ts +534 -0
  144. package/src/routes/api/collections.ts +57 -31
  145. package/src/routes/api/custom-urls.ts +80 -0
  146. package/src/routes/api/export.ts +31 -0
  147. package/src/routes/api/nav-items.ts +23 -19
  148. package/src/routes/api/posts.ts +81 -62
  149. package/src/routes/api/search.ts +3 -4
  150. package/src/routes/api/upload-multipart.ts +245 -0
  151. package/src/routes/api/upload.ts +92 -24
  152. package/src/routes/auth/__tests__/setup.test.ts +20 -60
  153. package/src/routes/auth/reset.tsx +5 -4
  154. package/src/routes/auth/setup.tsx +39 -31
  155. package/src/routes/auth/signin.tsx +13 -14
  156. package/src/routes/compose.tsx +27 -63
  157. package/src/routes/dash/__tests__/settings-avatar.test.ts +44 -9
  158. package/src/routes/dash/custom-urls.tsx +414 -0
  159. package/src/routes/dash/settings.tsx +475 -99
  160. package/src/routes/feed/__tests__/rss.test.ts +22 -23
  161. package/src/routes/feed/rss.ts +6 -2
  162. package/src/routes/feed/sitemap.ts +2 -12
  163. package/src/routes/pages/__tests__/collections.test.ts +5 -6
  164. package/src/routes/pages/__tests__/featured.test.ts +36 -18
  165. package/src/routes/pages/archive.tsx +177 -37
  166. package/src/routes/pages/collection.tsx +43 -14
  167. package/src/routes/pages/collections.tsx +11 -2
  168. package/src/routes/pages/featured.tsx +27 -3
  169. package/src/routes/pages/home.tsx +15 -14
  170. package/src/routes/pages/latest.tsx +1 -11
  171. package/src/routes/pages/new.tsx +39 -0
  172. package/src/routes/pages/page.tsx +95 -49
  173. package/src/routes/pages/search.tsx +1 -1
  174. package/src/services/__tests__/api-token.test.ts +135 -0
  175. package/src/services/__tests__/collection.test.ts +275 -227
  176. package/src/services/__tests__/custom-url.test.ts +213 -0
  177. package/src/services/__tests__/media.test.ts +162 -22
  178. package/src/services/__tests__/navigation.test.ts +109 -68
  179. package/src/services/__tests__/post-timeline.test.ts +205 -32
  180. package/src/services/__tests__/post.test.ts +800 -230
  181. package/src/services/__tests__/search.test.ts +67 -10
  182. package/src/services/__tests__/settings.test.ts +3 -3
  183. package/src/services/api-token.ts +166 -0
  184. package/src/services/auth.ts +17 -2
  185. package/src/services/collection.ts +397 -131
  186. package/src/services/custom-url.ts +188 -0
  187. package/src/services/export.ts +802 -0
  188. package/src/services/index.ts +26 -19
  189. package/src/services/media.ts +100 -22
  190. package/src/services/navigation.ts +158 -47
  191. package/src/services/path.ts +339 -0
  192. package/src/services/post.ts +764 -172
  193. package/src/services/search.ts +161 -74
  194. package/src/services/settings.ts +6 -2
  195. package/src/styles/components.css +293 -62
  196. package/src/styles/tokens.css +93 -5
  197. package/src/styles/ui.css +4349 -766
  198. package/src/types/bindings.ts +8 -0
  199. package/src/types/config.ts +34 -4
  200. package/src/types/constants.ts +17 -2
  201. package/src/types/entities.ts +83 -37
  202. package/src/types/operations.ts +20 -27
  203. package/src/types/props.ts +52 -17
  204. package/src/types/views.ts +48 -24
  205. package/src/ui/color-themes.ts +133 -23
  206. package/src/ui/compose/ComposeDialog.tsx +255 -16
  207. package/src/ui/compose/ComposePrompt.tsx +1 -1
  208. package/src/ui/dash/CrudPageHeader.tsx +1 -1
  209. package/src/ui/dash/ListItemRow.tsx +1 -1
  210. package/src/ui/dash/StatusBadge.tsx +12 -2
  211. package/src/ui/dash/appearance/AdvancedContent.tsx +71 -59
  212. package/src/ui/dash/appearance/ColorThemeContent.tsx +48 -33
  213. package/src/ui/dash/appearance/FontThemeContent.tsx +65 -73
  214. package/src/ui/dash/appearance/NavigationContent.tsx +106 -135
  215. package/src/ui/dash/index.ts +0 -3
  216. package/src/ui/dash/settings/AccountContent.tsx +87 -146
  217. package/src/ui/dash/settings/AccountMenuContent.tsx +147 -0
  218. package/src/ui/dash/settings/ApiTokensContent.tsx +232 -0
  219. package/src/ui/dash/settings/AvatarContent.tsx +78 -0
  220. package/src/ui/dash/settings/GeneralContent.tsx +3 -62
  221. package/src/ui/dash/settings/SessionsContent.tsx +159 -0
  222. package/src/ui/dash/settings/SettingsRootContent.tsx +266 -0
  223. package/src/ui/feed/LinkCard.tsx +89 -40
  224. package/src/ui/feed/NoteCard.tsx +39 -25
  225. package/src/ui/feed/PostStatusBadges.tsx +67 -0
  226. package/src/ui/feed/QuoteCard.tsx +38 -23
  227. package/src/ui/feed/ThreadPreview.tsx +90 -26
  228. package/src/ui/feed/TimelineFeed.tsx +3 -2
  229. package/src/ui/feed/TimelineItem.tsx +15 -6
  230. package/src/ui/feed/__tests__/thread-preview.test.ts +107 -0
  231. package/src/ui/feed/thread-preview-state.ts +61 -0
  232. package/src/ui/font-themes.ts +2 -2
  233. package/src/ui/layouts/BaseLayout.tsx +2 -2
  234. package/src/ui/layouts/SiteLayout.tsx +116 -103
  235. package/src/ui/pages/ArchivePage.tsx +923 -95
  236. package/src/ui/pages/CollectionPage.tsx +6 -35
  237. package/src/ui/pages/CollectionsPage.tsx +2 -1
  238. package/src/ui/pages/ComposePage.tsx +54 -0
  239. package/src/ui/pages/FeaturedPage.tsx +2 -1
  240. package/src/ui/pages/HomePage.tsx +1 -1
  241. package/src/ui/pages/PostPage.tsx +30 -45
  242. package/src/ui/pages/SearchPage.tsx +182 -38
  243. package/src/ui/shared/AdminBreadcrumb.tsx +29 -0
  244. package/src/ui/shared/CollectionsSidebar.tsx +239 -4
  245. package/src/ui/shared/MediaGallery.tsx +475 -41
  246. package/src/ui/shared/PostFooter.tsx +204 -0
  247. package/src/ui/shared/StarRating.tsx +27 -0
  248. package/src/ui/shared/__tests__/format-chars.test.ts +35 -0
  249. package/src/ui/shared/index.ts +0 -1
  250. package/src/db/migrations/0000_square_wallflower.sql +0 -118
  251. package/src/db/migrations/0001_add_search_fts.sql +0 -34
  252. package/src/db/migrations/0002_add_media_attachments.sql +0 -3
  253. package/src/db/migrations/0003_add_navigation_links.sql +0 -8
  254. package/src/db/migrations/0004_add_storage_provider.sql +0 -3
  255. package/src/db/migrations/0005_v2_schema_migration.sql +0 -268
  256. package/src/db/migrations/0006_rename_slug_to_path.sql +0 -5
  257. package/src/db/migrations/0007_post_collections_m2m.sql +0 -94
  258. package/src/db/migrations/0008_add_collection_dividers.sql +0 -8
  259. package/src/db/migrations/0009_drop_collection_show_divider.sql +0 -2
  260. package/src/db/migrations/0010_add_performance_indexes.sql +0 -16
  261. package/src/db/migrations/0011_add_path_registry.sql +0 -23
  262. package/src/db/migrations/meta/0003_snapshot.json +0 -821
  263. package/src/lib/__tests__/sqid.test.ts +0 -65
  264. package/src/lib/collections-reorder.ts +0 -28
  265. package/src/lib/compose-bridge.ts +0 -280
  266. package/src/lib/media-upload.ts +0 -148
  267. package/src/lib/sqid.ts +0 -79
  268. package/src/routes/api/__tests__/pages.test.ts +0 -218
  269. package/src/routes/api/pages.ts +0 -73
  270. package/src/routes/dash/__tests__/pages.test.ts +0 -226
  271. package/src/routes/dash/appearance.tsx +0 -240
  272. package/src/routes/dash/collections.tsx +0 -211
  273. package/src/routes/dash/index.tsx +0 -103
  274. package/src/routes/dash/media.tsx +0 -132
  275. package/src/routes/dash/pages.tsx +0 -239
  276. package/src/routes/dash/posts.tsx +0 -334
  277. package/src/routes/dash/redirects.tsx +0 -257
  278. package/src/routes/pages/post.tsx +0 -59
  279. package/src/services/__tests__/page.test.ts +0 -298
  280. package/src/services/__tests__/path-registry.test.ts +0 -165
  281. package/src/services/__tests__/redirect.test.ts +0 -159
  282. package/src/services/page.ts +0 -203
  283. package/src/services/path-registry.ts +0 -160
  284. package/src/services/redirect.ts +0 -97
  285. package/src/types/sortablejs.d.ts +0 -29
  286. package/src/ui/components/__tests__/jant-compose-dialog.test.ts +0 -512
  287. package/src/ui/components/__tests__/jant-compose-editor.test.ts +0 -272
  288. package/src/ui/components/compose-types.ts +0 -75
  289. package/src/ui/components/jant-collection-form.ts +0 -512
  290. package/src/ui/components/jant-compose-dialog.ts +0 -495
  291. package/src/ui/components/jant-compose-editor.ts +0 -814
  292. package/src/ui/dash/PageForm.tsx +0 -185
  293. package/src/ui/dash/PostList.tsx +0 -95
  294. package/src/ui/dash/appearance/AppearanceNav.tsx +0 -60
  295. package/src/ui/dash/collections/CollectionForm.tsx +0 -166
  296. package/src/ui/dash/collections/CollectionsListContent.tsx +0 -146
  297. package/src/ui/dash/collections/IconPickerGrid.tsx +0 -50
  298. package/src/ui/dash/collections/ViewCollectionContent.tsx +0 -103
  299. package/src/ui/dash/media/MediaListContent.tsx +0 -201
  300. package/src/ui/dash/media/ViewMediaContent.tsx +0 -208
  301. package/src/ui/dash/pages/PagesContent.tsx +0 -74
  302. package/src/ui/dash/posts/PostForm.tsx +0 -248
  303. package/src/ui/dash/settings/SettingsNav.tsx +0 -52
  304. package/src/ui/layouts/DashLayout.tsx +0 -165
  305. package/src/ui/pages/SinglePage.tsx +0 -23
  306. package/src/ui/shared/ThreadView.tsx +0 -136
  307. /package/src/{ui → client}/components/settings-types.ts +0 -0
@@ -0,0 +1,204 @@
1
+ /**
2
+ * Post Footer
3
+ *
4
+ * Shared footer for all post cards (feed + detail page).
5
+ * Shows timestamp, collection tags, reply button, and menu trigger.
6
+ */
7
+
8
+ import type { FC } from "hono/jsx";
9
+ import { useLingui } from "@lingui/react/macro";
10
+ import type {
11
+ PostView,
12
+ CollectionTagView,
13
+ PostFooterDisplayOptions,
14
+ } from "../../types.js";
15
+ import { sanitizeUrl } from "../../lib/url.js";
16
+
17
+ interface PostFooterProps {
18
+ post: PostView;
19
+ /** Detail page variant: border-top, shows permalink */
20
+ detail?: boolean;
21
+ display?: PostFooterDisplayOptions;
22
+ }
23
+
24
+ const CollectionTags: FC<{ collections: CollectionTagView[] }> = ({
25
+ collections,
26
+ }) => {
27
+ if (collections.length === 0) return null;
28
+
29
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- length checked above
30
+ const first = collections[0]!;
31
+ const rest = collections.slice(1);
32
+
33
+ return (
34
+ <span class="post-collection-tags">
35
+ <span class="post-collection-sep" aria-hidden="true">
36
+ &middot;
37
+ </span>
38
+ <a href={`/c/${first.slug}`} class="post-collection-tag">
39
+ {first.iconHtml && (
40
+ <span
41
+ class="post-collection-icon"
42
+ dangerouslySetInnerHTML={{ __html: first.iconHtml }}
43
+ />
44
+ )}
45
+ {first.title}
46
+ </a>
47
+ {rest.length > 0 && (
48
+ <span class="post-collection-more-wrap">
49
+ <button
50
+ type="button"
51
+ class="post-collection-more"
52
+ data-collection-popover-trigger
53
+ >
54
+ and {rest.length} more
55
+ </button>
56
+ <div class="post-collection-popover" data-collection-popover>
57
+ {collections.map((c) => (
58
+ <a
59
+ key={c.slug}
60
+ href={`/c/${c.slug}`}
61
+ class="post-collection-popover-item"
62
+ >
63
+ {c.iconHtml && (
64
+ <span
65
+ class="post-collection-icon"
66
+ dangerouslySetInnerHTML={{ __html: c.iconHtml }}
67
+ />
68
+ )}
69
+ {c.title}
70
+ </a>
71
+ ))}
72
+ </div>
73
+ </span>
74
+ )}
75
+ </span>
76
+ );
77
+ };
78
+
79
+ export const PostFooter: FC<PostFooterProps> = ({ post, detail, display }) => {
80
+ const { t } = useLingui();
81
+ const safeExternalUrl =
82
+ post.format === "link" && post.url ? sanitizeUrl(post.url) : "";
83
+
84
+ return (
85
+ <footer
86
+ class={`post-menu-footer${detail ? " post-footer-detail" : ""}`}
87
+ data-post-meta
88
+ >
89
+ <div class="post-footer-meta">
90
+ {detail ? (
91
+ <time
92
+ class="dt-published"
93
+ datetime={post.publishedAt}
94
+ title={`${post.publishedAtFormatted} ${post.publishedAtTime} UTC`}
95
+ >
96
+ {post.publishedAtFormatted}
97
+ </time>
98
+ ) : (
99
+ <a
100
+ href={post.permalink}
101
+ class="u-url text-xs text-muted-foreground hover:underline"
102
+ >
103
+ <time
104
+ class="dt-published"
105
+ datetime={post.publishedAt}
106
+ title={`${post.publishedAtFormatted} ${post.publishedAtTime} UTC`}
107
+ >
108
+ {post.publishedAtFormatted}
109
+ </time>
110
+ </a>
111
+ )}
112
+ {safeExternalUrl && (
113
+ <a
114
+ href={safeExternalUrl}
115
+ class="post-footer-external-link"
116
+ target="_blank"
117
+ rel="noopener noreferrer"
118
+ aria-label="Open external link"
119
+ >
120
+ <svg
121
+ xmlns="http://www.w3.org/2000/svg"
122
+ viewBox="0 0 24 24"
123
+ fill="none"
124
+ stroke="currentColor"
125
+ stroke-width="2"
126
+ stroke-linecap="round"
127
+ stroke-linejoin="round"
128
+ >
129
+ <path d="M7 17 17 7" />
130
+ <path d="M9 7h8v8" />
131
+ </svg>
132
+ </a>
133
+ )}
134
+ {detail && (
135
+ <a href={post.permalink} class="u-url ml-4">
136
+ {t({
137
+ message: "Permalink",
138
+ comment: "@context: Link to permanent URL of post",
139
+ })}
140
+ </a>
141
+ )}
142
+ {post.threadRootPermalink && (
143
+ <span class="post-collection-sep" aria-hidden="true">
144
+ &middot;
145
+ </span>
146
+ )}
147
+ {post.threadRootPermalink && (
148
+ <a
149
+ href={post.threadRootPermalink}
150
+ class="text-xs text-muted-foreground hover:underline"
151
+ >
152
+ In thread &rarr;
153
+ </a>
154
+ )}
155
+ <CollectionTags collections={post.collections} />
156
+ </div>
157
+ {!display?.hideActions && (
158
+ <div class="post-menu-actions">
159
+ {post.isLastInThread && (
160
+ <button
161
+ type="button"
162
+ class="reply-trigger"
163
+ aria-label="Reply"
164
+ data-reply-trigger
165
+ >
166
+ <svg
167
+ xmlns="http://www.w3.org/2000/svg"
168
+ width="14"
169
+ height="14"
170
+ viewBox="0 0 24 24"
171
+ fill="none"
172
+ stroke="currentColor"
173
+ stroke-width="2"
174
+ stroke-linecap="round"
175
+ stroke-linejoin="round"
176
+ >
177
+ <polyline points="9 17 4 12 9 7" />
178
+ <path d="M20 18v-2a4 4 0 0 0-4-4H4" />
179
+ </svg>
180
+ </button>
181
+ )}
182
+ <button
183
+ type="button"
184
+ class="post-menu-trigger"
185
+ aria-label="More actions"
186
+ data-post-menu-trigger
187
+ >
188
+ <svg
189
+ xmlns="http://www.w3.org/2000/svg"
190
+ width="16"
191
+ height="16"
192
+ viewBox="0 0 24 24"
193
+ fill="currentColor"
194
+ >
195
+ <circle cx="5" cy="12" r="2" />
196
+ <circle cx="12" cy="12" r="2" />
197
+ <circle cx="19" cy="12" r="2" />
198
+ </svg>
199
+ </button>
200
+ </div>
201
+ )}
202
+ </footer>
203
+ );
204
+ };
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Star Rating (read-only)
3
+ *
4
+ * Displays a 1-5 star rating inline. Returns null when no rating is set.
5
+ */
6
+
7
+ import type { FC } from "hono/jsx";
8
+
9
+ interface StarRatingProps {
10
+ rating?: number;
11
+ }
12
+
13
+ export const StarRating: FC<StarRatingProps> = ({ rating }) => {
14
+ if (!rating || rating <= 0) return null;
15
+
16
+ const stars = [1, 2, 3, 4, 5];
17
+
18
+ return (
19
+ <div class="post-rating" aria-label={`${rating} out of 5`}>
20
+ {stars.map((n) => (
21
+ <span class={n <= rating ? "post-star-filled" : "post-star-empty"}>
22
+
23
+ </span>
24
+ ))}
25
+ </div>
26
+ );
27
+ };
@@ -0,0 +1,35 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { formatChars } from "../MediaGallery.js";
3
+
4
+ describe("formatChars", () => {
5
+ it("shows raw count below 1000", () => {
6
+ expect(formatChars(0)).toBe("0 chars");
7
+ expect(formatChars(1)).toBe("1 chars");
8
+ expect(formatChars(500)).toBe("500 chars");
9
+ expect(formatChars(999)).toBe("999 chars");
10
+ });
11
+
12
+ it("formats thousands without trailing .0", () => {
13
+ expect(formatChars(1000)).toBe("1k chars");
14
+ expect(formatChars(4000)).toBe("4k chars");
15
+ expect(formatChars(4023)).toBe("4k chars");
16
+ expect(formatChars(10000)).toBe("10k chars");
17
+ expect(formatChars(100000)).toBe("100k chars");
18
+ });
19
+
20
+ it("keeps meaningful decimal in thousands", () => {
21
+ expect(formatChars(1500)).toBe("1.5k chars");
22
+ expect(formatChars(4500)).toBe("4.5k chars");
23
+ expect(formatChars(12300)).toBe("12.3k chars");
24
+ });
25
+
26
+ it("formats millions without trailing .0", () => {
27
+ expect(formatChars(1000000)).toBe("1M chars");
28
+ expect(formatChars(2000000)).toBe("2M chars");
29
+ });
30
+
31
+ it("keeps meaningful decimal in millions", () => {
32
+ expect(formatChars(1500000)).toBe("1.5M chars");
33
+ expect(formatChars(2300000)).toBe("2.3M chars");
34
+ });
35
+ });
@@ -9,4 +9,3 @@ export {
9
9
  type PagePaginationProps,
10
10
  } from "./Pagination.js";
11
11
  export { getPageNumbers } from "../../lib/pagination.js";
12
- export { ThreadView, type ThreadViewProps } from "./ThreadView.js";
@@ -1,118 +0,0 @@
1
- CREATE TABLE `account` (
2
- `id` text PRIMARY KEY NOT NULL,
3
- `account_id` text NOT NULL,
4
- `provider_id` text NOT NULL,
5
- `user_id` text NOT NULL,
6
- `access_token` text,
7
- `refresh_token` text,
8
- `id_token` text,
9
- `access_token_expires_at` integer,
10
- `refresh_token_expires_at` integer,
11
- `scope` text,
12
- `password` text,
13
- `created_at` integer NOT NULL,
14
- `updated_at` integer NOT NULL,
15
- FOREIGN KEY (`user_id`) REFERENCES `user`(`id`) ON UPDATE no action ON DELETE no action
16
- );
17
- --> statement-breakpoint
18
- CREATE TABLE `collections` (
19
- `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
20
- `path` text,
21
- `title` text NOT NULL,
22
- `description` text,
23
- `created_at` integer NOT NULL,
24
- `updated_at` integer NOT NULL
25
- );
26
- --> statement-breakpoint
27
- CREATE UNIQUE INDEX `collections_path_unique` ON `collections` (`path`);--> statement-breakpoint
28
- CREATE TABLE `media` (
29
- `id` text PRIMARY KEY NOT NULL,
30
- `post_id` integer,
31
- `filename` text NOT NULL,
32
- `original_name` text NOT NULL,
33
- `mime_type` text NOT NULL,
34
- `size` integer NOT NULL,
35
- `r2_key` text NOT NULL,
36
- `width` integer,
37
- `height` integer,
38
- `alt` text,
39
- `created_at` integer NOT NULL,
40
- FOREIGN KEY (`post_id`) REFERENCES `posts`(`id`) ON UPDATE no action ON DELETE no action
41
- );
42
- --> statement-breakpoint
43
- CREATE TABLE `post_collections` (
44
- `post_id` integer NOT NULL,
45
- `collection_id` integer NOT NULL,
46
- `added_at` integer NOT NULL,
47
- PRIMARY KEY(`post_id`, `collection_id`),
48
- FOREIGN KEY (`post_id`) REFERENCES `posts`(`id`) ON UPDATE no action ON DELETE no action,
49
- FOREIGN KEY (`collection_id`) REFERENCES `collections`(`id`) ON UPDATE no action ON DELETE no action
50
- );
51
- --> statement-breakpoint
52
- CREATE TABLE `posts` (
53
- `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
54
- `type` text NOT NULL,
55
- `visibility` text DEFAULT 'quiet' NOT NULL,
56
- `title` text,
57
- `path` text,
58
- `content` text,
59
- `content_html` text,
60
- `source_url` text,
61
- `source_name` text,
62
- `source_domain` text,
63
- `reply_to_id` integer,
64
- `thread_id` integer,
65
- `deleted_at` integer,
66
- `published_at` integer NOT NULL,
67
- `created_at` integer NOT NULL,
68
- `updated_at` integer NOT NULL
69
- );
70
- --> statement-breakpoint
71
- CREATE TABLE `redirects` (
72
- `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
73
- `from_path` text NOT NULL,
74
- `to_path` text NOT NULL,
75
- `type` integer DEFAULT 301 NOT NULL,
76
- `created_at` integer NOT NULL
77
- );
78
- --> statement-breakpoint
79
- CREATE UNIQUE INDEX `redirects_from_path_unique` ON `redirects` (`from_path`);--> statement-breakpoint
80
- CREATE TABLE `session` (
81
- `id` text PRIMARY KEY NOT NULL,
82
- `expires_at` integer NOT NULL,
83
- `token` text NOT NULL,
84
- `created_at` integer NOT NULL,
85
- `updated_at` integer NOT NULL,
86
- `ip_address` text,
87
- `user_agent` text,
88
- `user_id` text NOT NULL,
89
- FOREIGN KEY (`user_id`) REFERENCES `user`(`id`) ON UPDATE no action ON DELETE no action
90
- );
91
- --> statement-breakpoint
92
- CREATE UNIQUE INDEX `session_token_unique` ON `session` (`token`);--> statement-breakpoint
93
- CREATE TABLE `settings` (
94
- `key` text PRIMARY KEY NOT NULL,
95
- `value` text NOT NULL,
96
- `updated_at` integer NOT NULL
97
- );
98
- --> statement-breakpoint
99
- CREATE TABLE `user` (
100
- `id` text PRIMARY KEY NOT NULL,
101
- `name` text NOT NULL,
102
- `email` text NOT NULL,
103
- `email_verified` integer DEFAULT false NOT NULL,
104
- `image` text,
105
- `role` text DEFAULT 'admin',
106
- `created_at` integer NOT NULL,
107
- `updated_at` integer NOT NULL
108
- );
109
- --> statement-breakpoint
110
- CREATE UNIQUE INDEX `user_email_unique` ON `user` (`email`);--> statement-breakpoint
111
- CREATE TABLE `verification` (
112
- `id` text PRIMARY KEY NOT NULL,
113
- `identifier` text NOT NULL,
114
- `value` text NOT NULL,
115
- `expires_at` integer NOT NULL,
116
- `created_at` integer,
117
- `updated_at` integer
118
- );
@@ -1,34 +0,0 @@
1
- -- FTS5 virtual table for full-text search (trigram tokenizer for CJK support)
2
- CREATE VIRTUAL TABLE IF NOT EXISTS posts_fts USING fts5(
3
- title,
4
- content,
5
- content='posts',
6
- content_rowid='id',
7
- tokenize='trigram'
8
- );
9
-
10
- -- Populate FTS with existing posts
11
- INSERT INTO posts_fts(rowid, title, content)
12
- SELECT id, COALESCE(title, ''), COALESCE(content, '')
13
- FROM posts WHERE deleted_at IS NULL;
14
-
15
- -- Trigger: sync FTS on INSERT
16
- CREATE TRIGGER posts_fts_insert AFTER INSERT ON posts
17
- WHEN NEW.deleted_at IS NULL
18
- BEGIN
19
- INSERT INTO posts_fts(rowid, title, content)
20
- VALUES (NEW.id, COALESCE(NEW.title, ''), COALESCE(NEW.content, ''));
21
- END;
22
-
23
- -- Trigger: sync FTS on UPDATE
24
- CREATE TRIGGER posts_fts_update AFTER UPDATE ON posts BEGIN
25
- DELETE FROM posts_fts WHERE rowid = OLD.id;
26
- INSERT INTO posts_fts(rowid, title, content)
27
- SELECT NEW.id, COALESCE(NEW.title, ''), COALESCE(NEW.content, '')
28
- WHERE NEW.deleted_at IS NULL;
29
- END;
30
-
31
- -- Trigger: sync FTS on DELETE
32
- CREATE TRIGGER posts_fts_delete AFTER DELETE ON posts BEGIN
33
- DELETE FROM posts_fts WHERE rowid = OLD.id;
34
- END;
@@ -1,3 +0,0 @@
1
- ALTER TABLE `media` ADD COLUMN `position` integer NOT NULL DEFAULT 0;
2
- --> statement-breakpoint
3
- ALTER TABLE `media` ADD COLUMN `blurhash` text;
@@ -1,8 +0,0 @@
1
- CREATE TABLE `navigation_links` (
2
- `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
3
- `label` text NOT NULL,
4
- `url` text NOT NULL,
5
- `position` integer DEFAULT 0 NOT NULL,
6
- `created_at` integer NOT NULL,
7
- `updated_at` integer NOT NULL
8
- );
@@ -1,3 +0,0 @@
1
- ALTER TABLE `media` ADD COLUMN `provider` text NOT NULL DEFAULT 'r2';
2
- --> statement-breakpoint
3
- ALTER TABLE `media` RENAME COLUMN `r2_key` TO `storage_key`;