@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
@@ -1,248 +0,0 @@
1
- /**
2
- * Post Form
3
- *
4
- * Server-rendered wrapper that feeds data/labels to `<jant-post-form>`.
5
- * Provides SSR fallback skeleton while Lit hydrates.
6
- */
7
-
8
- import { useLingui } from "@lingui/react/macro";
9
- import type { FC } from "hono/jsx";
10
- import type { Post, Media, Collection } from "../../../types.js";
11
- import {
12
- getMediaUrl,
13
- getImageUrl,
14
- getPublicUrlForProvider,
15
- } from "../../../lib/image.js";
16
- import { renderCollectionIcon } from "../../../lib/icons.js";
17
-
18
- export interface PostFormProps {
19
- post?: Post;
20
- action: string;
21
- mediaAttachments?: Media[];
22
- r2PublicUrl?: string;
23
- imageTransformUrl?: string;
24
- s3PublicUrl?: string;
25
- collections?: Collection[];
26
- postCollectionIds?: number[];
27
- cancelHref?: string;
28
- }
29
-
30
- export const PostForm: FC<PostFormProps> = ({
31
- post,
32
- action,
33
- mediaAttachments = [],
34
- r2PublicUrl,
35
- imageTransformUrl,
36
- s3PublicUrl,
37
- collections = [],
38
- postCollectionIds = [],
39
- cancelHref,
40
- }) => {
41
- const { t } = useLingui();
42
- const isEdit = Boolean(post);
43
-
44
- const labels = JSON.stringify({
45
- formatLabel: t({
46
- message: "Format",
47
- comment: "@context: Post form field - post format",
48
- }),
49
- noteOption: t({
50
- message: "Note",
51
- comment: "@context: Post format option",
52
- }),
53
- linkOption: t({
54
- message: "Link",
55
- comment: "@context: Post format option",
56
- }),
57
- quoteOption: t({
58
- message: "Quote",
59
- comment: "@context: Post format option",
60
- }),
61
- titleLabel: t({
62
- message: "Title (optional)",
63
- comment: "@context: Post form field",
64
- }),
65
- titlePlaceholder: t({
66
- message: "Post title...",
67
- comment: "@context: Post title placeholder",
68
- }),
69
- bodyLabel: t({
70
- message: "Content",
71
- comment: "@context: Post form field",
72
- }),
73
- bodyPlaceholder: t({
74
- message: "What's on your mind?",
75
- comment: "@context: Post content placeholder",
76
- }),
77
- urlLabel: t({
78
- message: "URL (optional)",
79
- comment: "@context: Post form field - source URL",
80
- }),
81
- urlPlaceholder: "https://...",
82
- quoteTextLabel: t({
83
- message: "Quote Text",
84
- comment: "@context: Post form field - quoted text",
85
- }),
86
- quoteTextPlaceholder: t({
87
- message: "The text being quoted...",
88
- comment: "@context: Quote text placeholder",
89
- }),
90
- mediaLabel: t({
91
- message: "Media",
92
- comment: "@context: Post form field - media attachments",
93
- }),
94
- mediaAddButton: t({
95
- message: "Add Media",
96
- comment: "@context: Button to open media picker",
97
- }),
98
- mediaRemoveButton: t({
99
- message: "Remove",
100
- comment: "@context: Remove media attachment button",
101
- }),
102
- mediaEmptyLabel: t({
103
- message: "No media selected yet.",
104
- comment: "@context: Post form media empty state",
105
- }),
106
- statusLabel: t({
107
- message: "Status",
108
- comment: "@context: Post form field",
109
- }),
110
- statusPublished: t({
111
- message: "Published",
112
- comment: "@context: Post status option",
113
- }),
114
- statusDraft: t({
115
- message: "Draft",
116
- comment: "@context: Post status option",
117
- }),
118
- featuredLabel: t({
119
- message: "Featured",
120
- comment: "@context: Post form checkbox - mark as featured",
121
- }),
122
- pinnedLabel: t({
123
- message: "Pinned",
124
- comment: "@context: Post form checkbox - pin to top",
125
- }),
126
- collectionsLabel: t({
127
- message: "Collections (optional)",
128
- comment: "@context: Post form field - assign to collections",
129
- }),
130
- submitLabel: isEdit
131
- ? t({
132
- message: "Update",
133
- comment: "@context: Button to update existing post",
134
- })
135
- : t({
136
- message: "Publish",
137
- comment: "@context: Button to publish new post",
138
- }),
139
- cancelLabel: t({
140
- message: "Cancel",
141
- comment: "@context: Button to cancel form",
142
- }),
143
- mediaDialogTitle: t({
144
- message: "Select Media",
145
- comment: "@context: Media picker dialog title",
146
- }),
147
- mediaDialogDone: t({
148
- message: "Done",
149
- comment: "@context: Close media picker button",
150
- }),
151
- mediaDialogLoading: t({
152
- message: "Loading...",
153
- comment: "@context: Loading state for media picker",
154
- }),
155
- submitSuccessMessage: isEdit
156
- ? t({
157
- message: "Post updated successfully.",
158
- comment: "@context: Toast after editing post",
159
- })
160
- : t({
161
- message: "Post published successfully.",
162
- comment: "@context: Toast after creating post",
163
- }),
164
- submitErrorMessage: t({
165
- message: "Failed to save post. Please try again.",
166
- comment: "@context: Toast when post save fails",
167
- }),
168
- }).replace(/</g, "\\u003c");
169
-
170
- const initial = JSON.stringify({
171
- format: post?.format ?? "note",
172
- title: post?.title ?? "",
173
- body: post?.body ?? "",
174
- url: post?.url ?? "",
175
- quoteText: post?.quoteText ?? "",
176
- status: post?.status ?? "published",
177
- featured: post?.featured === 1,
178
- pinned: post?.pinned === 1,
179
- rating: post?.rating ?? 0,
180
- collectionIds: postCollectionIds,
181
- mediaIds: mediaAttachments.map((m) => m.id),
182
- }).replace(/</g, "\\u003c");
183
-
184
- const media = JSON.stringify(
185
- mediaAttachments.map((m) => {
186
- const pUrl = getPublicUrlForProvider(
187
- m.provider,
188
- r2PublicUrl,
189
- s3PublicUrl,
190
- );
191
- const mediaUrl = getMediaUrl(m.storageKey, pUrl);
192
- const thumbUrl = getImageUrl(mediaUrl, imageTransformUrl, {
193
- width: 150,
194
- quality: 80,
195
- format: "auto",
196
- fit: "cover",
197
- });
198
- return {
199
- id: m.id,
200
- thumbUrl,
201
- alt: m.alt || m.originalName,
202
- };
203
- }),
204
- ).replace(/</g, "\\u003c");
205
-
206
- const collectionOptions = JSON.stringify(
207
- collections.map((col) => ({
208
- id: col.id,
209
- title: col.title,
210
- icon: col.icon,
211
- iconHtml: renderCollectionIcon(col.icon, { size: 18 }),
212
- })),
213
- ).replace(/</g, "\\u003c");
214
-
215
- const cancel = cancelHref ?? "/dash/posts";
216
-
217
- return (
218
- <jant-post-form
219
- labels={labels}
220
- initial={initial}
221
- action={action}
222
- cancel-href={cancel}
223
- media={media}
224
- collections={collectionOptions}
225
- media-picker-url="/dash/media/picker"
226
- is-edit={isEdit ? "true" : undefined}
227
- >
228
- <div class="flex flex-col gap-4 max-w-2xl">
229
- <div class="field">
230
- <div class="label skel-label"></div>
231
- <div class="input skel-input"></div>
232
- </div>
233
- <div class="field">
234
- <div class="label skel-label"></div>
235
- <div class="textarea skel-textarea"></div>
236
- </div>
237
- <div class="field">
238
- <div class="label skel-label"></div>
239
- <div class="input skel-input"></div>
240
- </div>
241
- <div class="flex gap-2">
242
- <div class="btn skel-input min-w-24"></div>
243
- <div class="btn-outline skel-input min-w-20"></div>
244
- </div>
245
- </div>
246
- </jant-post-form>
247
- );
248
- };
@@ -1,52 +0,0 @@
1
- /**
2
- * Settings sub-navigation tabs
3
- */
4
-
5
- import { useLingui } from "@lingui/react/macro";
6
-
7
- export type SettingsTab = "general" | "redirects" | "account";
8
-
9
- export function SettingsNav({ currentTab }: { currentTab: SettingsTab }) {
10
- const { t } = useLingui();
11
-
12
- const tabs: { id: SettingsTab; label: string; href: string }[] = [
13
- {
14
- id: "general",
15
- label: t({
16
- message: "General",
17
- comment: "@context: Settings sub-navigation tab",
18
- }),
19
- href: "/dash/settings",
20
- },
21
- {
22
- id: "redirects",
23
- label: t({
24
- message: "Redirects",
25
- comment: "@context: Settings sub-navigation tab",
26
- }),
27
- href: "/dash/settings/redirects",
28
- },
29
- {
30
- id: "account",
31
- label: t({
32
- message: "Account",
33
- comment: "@context: Settings sub-navigation tab",
34
- }),
35
- href: "/dash/settings/account",
36
- },
37
- ];
38
-
39
- return (
40
- <nav class="dash-subnav">
41
- {tabs.map((tab) => (
42
- <a
43
- key={tab.id}
44
- href={tab.href}
45
- class={tab.id === currentTab ? "active" : ""}
46
- >
47
- {tab.label}
48
- </a>
49
- ))}
50
- </nav>
51
- );
52
- }
@@ -1,165 +0,0 @@
1
- /**
2
- * Dashboard Layout
3
- *
4
- * Layout for admin dashboard pages
5
- */
6
-
7
- import type { FC, PropsWithChildren } from "hono/jsx";
8
- import type { Context } from "hono";
9
- import { useLingui } from "@lingui/react/macro";
10
- import { BaseLayout, type ToastProps } from "./BaseLayout.js";
11
-
12
- export interface DashLayoutProps {
13
- c: Context;
14
- title: string;
15
- siteName: string;
16
- currentPath?: string;
17
- toast?: ToastProps;
18
- }
19
-
20
- function DashLayoutContent({
21
- siteName,
22
- currentPath,
23
- children,
24
- }: PropsWithChildren<Omit<DashLayoutProps, "c" | "title">>) {
25
- const { t } = useLingui();
26
-
27
- const navClass = (match: RegExp) =>
28
- `dash-header-link ${currentPath && match.test(currentPath) ? "dash-header-link-active" : ""}`;
29
-
30
- return (
31
- <div class="min-h-screen">
32
- {/* Header */}
33
- <header class="dash-header">
34
- <div class="container dash-header-inner">
35
- <div class="dash-header-left">
36
- <a id="site-name" href="/dash" class="dash-header-logo">
37
- {siteName}
38
- </a>
39
- <a
40
- href="/"
41
- target="_blank"
42
- rel="noopener noreferrer"
43
- class="dash-header-site-link"
44
- aria-label={t({
45
- message: "View Site",
46
- comment:
47
- "@context: Dashboard header link to view the public site",
48
- })}
49
- >
50
- <svg
51
- xmlns="http://www.w3.org/2000/svg"
52
- width="14"
53
- height="14"
54
- viewBox="0 0 24 24"
55
- fill="none"
56
- stroke="currentColor"
57
- stroke-width="2"
58
- stroke-linecap="round"
59
- stroke-linejoin="round"
60
- >
61
- <path d="M15 3h6v6" />
62
- <path d="M10 14 21 3" />
63
- <path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6" />
64
- </svg>
65
- </a>
66
- </div>
67
-
68
- <nav class="dash-header-nav">
69
- <a href="/dash/pages" class={navClass(/^\/dash\/pages/)}>
70
- {t({
71
- message: "Pages",
72
- comment: "@context: Dashboard navigation - pages management",
73
- })}
74
- </a>
75
- <a href="/dash/appearance" class={navClass(/^\/dash\/appearance/)}>
76
- {t({
77
- message: "Appearance",
78
- comment: "@context: Dashboard navigation - appearance settings",
79
- })}
80
- </a>
81
- <a href="/dash/settings" class={navClass(/^\/dash\/settings/)}>
82
- {t({
83
- message: "Settings",
84
- comment: "@context: Dashboard navigation - site settings",
85
- })}
86
- </a>
87
- </nav>
88
-
89
- <div class="dropdown-menu">
90
- <button
91
- type="button"
92
- id="dash-menu-trigger"
93
- class="dash-header-menu-btn"
94
- aria-haspopup="menu"
95
- aria-controls="dash-menu"
96
- aria-expanded="false"
97
- aria-label={t({
98
- message: "Menu",
99
- comment: "@context: Dashboard header menu button",
100
- })}
101
- >
102
- <svg
103
- xmlns="http://www.w3.org/2000/svg"
104
- width="16"
105
- height="16"
106
- viewBox="0 0 24 24"
107
- fill="currentColor"
108
- >
109
- <circle cx="5" cy="12" r="2" />
110
- <circle cx="12" cy="12" r="2" />
111
- <circle cx="19" cy="12" r="2" />
112
- </svg>
113
- </button>
114
- <div
115
- id="dash-menu-popover"
116
- data-popover
117
- data-align="end"
118
- aria-hidden="true"
119
- >
120
- <div
121
- role="menu"
122
- id="dash-menu"
123
- aria-labelledby="dash-menu-trigger"
124
- >
125
- <a href="/signout" role="menuitem">
126
- {t({
127
- message: "Sign Out",
128
- comment: "@context: Dashboard menu item to sign out",
129
- })}
130
- </a>
131
- </div>
132
- </div>
133
- </div>
134
- </div>
135
- </header>
136
-
137
- {/* Main */}
138
- <div class="container py-8">
139
- <main>{children}</main>
140
- </div>
141
- </div>
142
- );
143
- }
144
-
145
- export const DashLayout: FC<PropsWithChildren<DashLayoutProps>> = ({
146
- c,
147
- title,
148
- siteName,
149
- currentPath,
150
- toast,
151
- children,
152
- }) => {
153
- return (
154
- <BaseLayout
155
- title={`${title} - ${siteName}`}
156
- c={c}
157
- toast={toast}
158
- isAuthenticated={true}
159
- >
160
- <DashLayoutContent siteName={siteName} currentPath={currentPath}>
161
- {children}
162
- </DashLayoutContent>
163
- </BaseLayout>
164
- );
165
- };
@@ -1,23 +0,0 @@
1
- /**
2
- * Single Page (Custom Page)
3
- *
4
- * Custom page view — clean centered content.
5
- */
6
-
7
- import type { FC } from "hono/jsx";
8
- import type { SinglePageProps } from "../../types.js";
9
-
10
- export const SinglePage: FC<SinglePageProps> = ({ page }) => {
11
- return (
12
- <article class="h-entry py-6" data-page="single-page">
13
- {page.title && (
14
- <h1 class="p-name text-2xl font-semibold mb-6">{page.title}</h1>
15
- )}
16
-
17
- <div
18
- class="e-content prose"
19
- dangerouslySetInnerHTML={{ __html: page.bodyHtml || "" }}
20
- />
21
- </article>
22
- );
23
- };
@@ -1,136 +0,0 @@
1
- /**
2
- * Thread View Component
3
- *
4
- * Displays a thread of posts with reply chain visualization
5
- */
6
-
7
- import type { FC } from "hono/jsx";
8
- import { useLingui } from "@lingui/react/macro";
9
- import type { Post } from "../../types.js";
10
- import * as sqid from "../../lib/sqid.js";
11
- import * as time from "../../lib/time.js";
12
-
13
- export interface ThreadViewProps {
14
- /** All posts in the thread, ordered by createdAt */
15
- posts: Post[];
16
- /** ID of the currently viewed post (to highlight) */
17
- currentPostId: number;
18
- }
19
-
20
- const ThreadPost: FC<{
21
- post: Post;
22
- isCurrent: boolean;
23
- isRoot: boolean;
24
- }> = ({ post, isCurrent, isRoot }) => {
25
- const { t } = useLingui();
26
- return (
27
- <article
28
- id={`post-${post.id}`}
29
- class={`h-entry p-4 rounded-lg border ${
30
- isCurrent
31
- ? "border-primary bg-primary/5 ring-2 ring-primary/20"
32
- : "border-border hover:border-muted-foreground/30"
33
- }`}
34
- >
35
- {post.title && (
36
- <h2 class="p-name text-lg font-medium mb-2">
37
- <a
38
- href={`${post.path ? `/${post.path}` : `/p/${sqid.encode(post.id)}`}`}
39
- class="u-url hover:underline"
40
- >
41
- {post.title}
42
- </a>
43
- </h2>
44
- )}
45
-
46
- <div
47
- class="e-content prose prose-sm"
48
- dangerouslySetInnerHTML={{ __html: post.bodyHtml || "" }}
49
- />
50
-
51
- <footer class="mt-3 flex items-center gap-3 text-sm text-muted-foreground">
52
- <time
53
- class="dt-published"
54
- datetime={time.toISOString(post.publishedAt)}
55
- >
56
- {time.formatDate(post.publishedAt)}
57
- </time>
58
- {isRoot && (
59
- <span class="text-xs">
60
- {t({
61
- message: "Thread start",
62
- comment: "@context: Thread view indicator - first post in thread",
63
- })}
64
- </span>
65
- )}
66
- {!isCurrent && (
67
- <a
68
- href={`${post.path ? `/${post.path}` : `/p/${sqid.encode(post.id)}`}`}
69
- class="text-xs hover:underline"
70
- >
71
- {t({
72
- message: "Permalink",
73
- comment: "@context: Link to individual post in thread",
74
- })}
75
- </a>
76
- )}
77
- </footer>
78
- </article>
79
- );
80
- };
81
-
82
- export const ThreadView: FC<ThreadViewProps> = ({ posts, currentPostId }) => {
83
- const { t } = useLingui();
84
- if (posts.length === 0) {
85
- return null;
86
- }
87
-
88
- const rootPost = posts[0];
89
- const isThread = posts.length > 1;
90
-
91
- // Single post, no thread
92
- if (!isThread) {
93
- return (
94
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- Early return for empty array at line 73 guarantees posts[0] exists
95
- <ThreadPost post={rootPost!} isCurrent={true} isRoot={false} />
96
- );
97
- }
98
-
99
- const threadLabel =
100
- posts.length === 1
101
- ? t({
102
- message: "Thread with 1 post",
103
- comment: "@context: Thread view header - single post",
104
- })
105
- : t({
106
- message: "Thread with {count} posts",
107
- comment: "@context: Thread view header - multiple posts",
108
- values: { count: String(posts.length) },
109
- });
110
-
111
- return (
112
- <div class="thread-view">
113
- <div class="mb-4 text-sm text-muted-foreground">{threadLabel}</div>
114
-
115
- <div class="flex flex-col gap-3">
116
- {posts.map((post, index) => (
117
- <div key={post.id} class="relative">
118
- {/* Connection line */}
119
- {index > 0 && (
120
- <div class="absolute left-6 -top-3 w-0.5 h-3 bg-border" />
121
- )}
122
- {index < posts.length - 1 && (
123
- <div class="absolute left-6 -bottom-3 w-0.5 h-3 bg-border" />
124
- )}
125
-
126
- <ThreadPost
127
- post={post}
128
- isCurrent={post.id === currentPostId}
129
- isRoot={index === 0}
130
- />
131
- </div>
132
- ))}
133
- </div>
134
- </div>
135
- );
136
- };
File without changes