@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
@@ -15,9 +15,22 @@ import { useLingui } from "@lingui/react/macro";
15
15
 
16
16
  export interface ComposeDialogProps {
17
17
  collections?: Collection[];
18
+ uploadMaxFileSize?: number;
18
19
  }
19
20
 
20
- export const ComposeDialog: FC<ComposeDialogProps> = ({ collections }) => {
21
+ export interface ComposeFormProps extends ComposeDialogProps {
22
+ pageMode?: boolean;
23
+ closeHref?: string;
24
+ autoRestoreDraft?: boolean;
25
+ }
26
+
27
+ export const ComposeForm: FC<ComposeFormProps> = ({
28
+ collections,
29
+ uploadMaxFileSize,
30
+ pageMode = false,
31
+ closeHref,
32
+ autoRestoreDraft = false,
33
+ }) => {
21
34
  const { t } = useLingui();
22
35
 
23
36
  const labels = JSON.stringify({
@@ -70,7 +83,7 @@ export const ComposeDialog: FC<ComposeDialogProps> = ({ collections }) => {
70
83
  comment: "@context: Compose quote source link placeholder",
71
84
  }),
72
85
  attachedText: t({
73
- message: "Attached Text",
86
+ message: "Text attachment",
74
87
  comment: "@context: Attached text panel title",
75
88
  }),
76
89
  attachedTextPlaceholder: t({
@@ -90,9 +103,13 @@ export const ComposeDialog: FC<ComposeDialogProps> = ({ collections }) => {
90
103
  message: "Media",
91
104
  comment: "@context: Compose toolbar - media tooltip",
92
105
  }),
93
- score: t({
94
- message: "Score",
95
- comment: "@context: Compose toolbar - score tooltip",
106
+ rate: t({
107
+ message: "Rate",
108
+ comment: "@context: Compose toolbar - rate tooltip",
109
+ }),
110
+ emoji: t({
111
+ message: "Emoji",
112
+ comment: "@context: Compose toolbar - emoji picker tooltip",
96
113
  }),
97
114
  title: t({
98
115
  message: "Title",
@@ -107,8 +124,14 @@ export const ComposeDialog: FC<ComposeDialogProps> = ({ collections }) => {
107
124
  comment: "@context: Compose collection combobox search placeholder",
108
125
  }),
109
126
  noCollections: t({
110
- message: "No collections found.",
111
- comment: "@context: Compose collection combobox empty state",
127
+ message: "No matching collections.",
128
+ comment:
129
+ "@context: Compose collection combobox empty state when search has no results",
130
+ }),
131
+ emptyCollections: t({
132
+ message: "Create a collection to get started.",
133
+ comment:
134
+ "@context: Compose collection combobox empty state when no collections exist",
112
135
  }),
113
136
  post: t({
114
137
  message: "Post",
@@ -127,7 +150,7 @@ export const ComposeDialog: FC<ComposeDialogProps> = ({ collections }) => {
127
150
  comment: "@context: Alt text textarea placeholder",
128
151
  }),
129
152
  altHint: t({
130
- message: "Alt text improves accessibility",
153
+ message: "Helps screen readers describe the image",
131
154
  comment: "@context: Hint text in alt text panel",
132
155
  }),
133
156
  addMore: t({
@@ -142,6 +165,203 @@ export const ComposeDialog: FC<ComposeDialogProps> = ({ collections }) => {
142
165
  message: "Published!",
143
166
  comment: "@context: Toast shown after successful deferred publish",
144
167
  }),
168
+ view: t({
169
+ message: "View",
170
+ comment: "@context: Toast action button to view the published post",
171
+ }),
172
+ retryAll: t({
173
+ message: "Tap to retry",
174
+ comment:
175
+ "@context: Label on failed upload overlay button, tells user tapping retries the upload",
176
+ }),
177
+ editPost: t({
178
+ message: "Edit post",
179
+ comment: "@context: Compose dialog header title in edit mode",
180
+ }),
181
+ update: t({
182
+ message: "Done",
183
+ comment: "@context: Compose button - update existing post",
184
+ }),
185
+ confirmCloseTitle: t({
186
+ message: "Save to drafts?",
187
+ comment: "@context: Confirm close action sheet title",
188
+ }),
189
+ confirmCloseSubtitle: t({
190
+ message: "Save to drafts to edit and post at a later time.",
191
+ comment: "@context: Confirm close action sheet subtitle",
192
+ }),
193
+ confirmCloseSave: t({
194
+ message: "Save",
195
+ comment: "@context: Confirm close action sheet - save draft button",
196
+ }),
197
+ confirmCloseCancel: t({
198
+ message: "Cancel",
199
+ comment:
200
+ "@context: Confirm close action sheet - cancel and return to editor",
201
+ }),
202
+ confirmCloseDiscard: t({
203
+ message: "Don't save",
204
+ comment: "@context: Confirm close action sheet - discard button",
205
+ }),
206
+ confirmEditTitle: t({
207
+ message: "You have unsaved changes",
208
+ comment:
209
+ "@context: Confirm close action sheet title when editing a published post",
210
+ }),
211
+ confirmEditSubtitle: t({
212
+ message: "Do you want to publish your changes or discard them?",
213
+ comment:
214
+ "@context: Confirm close action sheet subtitle when editing a published post",
215
+ }),
216
+ confirmEditPublish: t({
217
+ message: "Publish",
218
+ comment:
219
+ "@context: Confirm close action sheet - publish update button for editing published post",
220
+ }),
221
+ confirmEditDiscard: t({
222
+ message: "Discard",
223
+ comment:
224
+ "@context: Confirm close action sheet - discard changes button for editing published post",
225
+ }),
226
+ drafts: t({ message: "Drafts", comment: "@context: Drafts panel title" }),
227
+ draftsEmpty: t({
228
+ message: "No drafts yet. Save a draft to find it here.",
229
+ comment: "@context: Drafts panel empty state",
230
+ }),
231
+ deleteDraft: t({
232
+ message: "Delete Draft",
233
+ comment: "@context: Draft item action",
234
+ }),
235
+ draftDeleted: t({
236
+ message: "Draft deleted.",
237
+ comment: "@context: Toast after draft deletion",
238
+ }),
239
+ publishFailedDraft: t({
240
+ message: "Couldn't publish. Saved as draft.",
241
+ comment:
242
+ "@context: Toast when publish fails and post is auto-saved as draft",
243
+ }),
244
+ uploadFailedDraft: t({
245
+ message: "Some uploads failed. Saved as draft.",
246
+ comment:
247
+ "@context: Toast when uploads fail and post is auto-saved as draft",
248
+ }),
249
+ reply: t({
250
+ message: "Reply",
251
+ comment: "@context: Compose button - reply to post",
252
+ }),
253
+ publishFeatured: t({
254
+ message: "Post as Featured",
255
+ comment:
256
+ "@context: Compose dropdown option - publish post and mark it as featured",
257
+ }),
258
+ publishUnlisted: t({
259
+ message: "Post Unlisted",
260
+ comment:
261
+ "@context: Compose dropdown option - publish post with unlisted visibility, hidden from main feed",
262
+ }),
263
+ publishPrivate: t({
264
+ message: "Post as Private",
265
+ comment:
266
+ "@context: Compose dropdown option - publish post visible only when logged in",
267
+ }),
268
+ showMore: t({
269
+ message: "Show more",
270
+ comment: "@context: Expand reply context",
271
+ }),
272
+ showLess: t({
273
+ message: "Show less",
274
+ comment: "@context: Collapse reply context",
275
+ }),
276
+ addCollection: t({
277
+ message: "Add Collection",
278
+ comment: "@context: Action to create a new collection from compose",
279
+ }),
280
+ collectionCountLabel: t({
281
+ message: "%name% + %count% more",
282
+ comment:
283
+ "@context: Compose collection trigger label when multiple collections selected. %name% is the first collection name, %count% is how many more",
284
+ }),
285
+ draftRestored: t({
286
+ message: "Draft restored.",
287
+ comment:
288
+ "@context: Toast shown when a local draft is restored on compose open",
289
+ }),
290
+ collectionFormLabels: {
291
+ titleLabel: t({
292
+ message: "Title",
293
+ comment: "@context: Collection form field",
294
+ }),
295
+ titlePlaceholder: t({
296
+ message: "My Collection",
297
+ comment: "@context: Collection title placeholder",
298
+ }),
299
+ slugLabel: t({
300
+ message: "Slug",
301
+ comment: "@context: Collection form field",
302
+ }),
303
+ slugHelp: t({
304
+ message:
305
+ "URL-safe identifier (lowercase, numbers, hyphens). For CJK titles, slug will be auto-generated on the server.",
306
+ comment: "@context: Collection path help text",
307
+ }),
308
+ descriptionLabel: t({
309
+ message: "Description (optional)",
310
+ comment: "@context: Collection form field",
311
+ }),
312
+ descriptionPlaceholder: t({
313
+ message: "What's this collection about?",
314
+ comment: "@context: Collection description placeholder",
315
+ }),
316
+ removeIcon: t({
317
+ message: "Remove",
318
+ comment: "@context: Button to remove icon",
319
+ }),
320
+ iconsTab: t({
321
+ message: "Icons",
322
+ comment: "@context: Icon picker tab label",
323
+ }),
324
+ emojisTab: t({
325
+ message: "Emojis",
326
+ comment: "@context: Emoji picker tab label",
327
+ }),
328
+ searchIconsPlaceholder: t({
329
+ message: "Search icons...",
330
+ comment: "@context: Icon picker search placeholder",
331
+ }),
332
+ searchEmojisPlaceholder: t({
333
+ message: "Search emojis...",
334
+ comment: "@context: Emoji picker search placeholder",
335
+ }),
336
+ sortOrderLabel: t({
337
+ message: "Sort Order",
338
+ comment: "@context: Collection form field",
339
+ }),
340
+ sortNewest: t({
341
+ message: "Newest first",
342
+ comment: "@context: Collection sort order option",
343
+ }),
344
+ sortOldest: t({
345
+ message: "Oldest first",
346
+ comment: "@context: Collection sort order option",
347
+ }),
348
+ sortRatingDesc: t({
349
+ message: "Highest rated",
350
+ comment: "@context: Collection sort order option",
351
+ }),
352
+ sortRatingAsc: t({
353
+ message: "Lowest rated",
354
+ comment: "@context: Collection sort order option",
355
+ }),
356
+ submitLabel: t({
357
+ message: "Save",
358
+ comment: "@context: Button to save collection",
359
+ }),
360
+ cancelLabel: t({
361
+ message: "Cancel",
362
+ comment: "@context: Button to cancel form",
363
+ }),
364
+ },
145
365
  }).replace(/</g, "\\u003c");
146
366
 
147
367
  const collectionsJson = JSON.stringify(
@@ -152,19 +372,38 @@ export const ComposeDialog: FC<ComposeDialogProps> = ({ collections }) => {
152
372
  })),
153
373
  ).replace(/</g, "\\u003c");
154
374
 
375
+ return (
376
+ <jant-compose-dialog
377
+ collections={collectionsJson}
378
+ labels={labels}
379
+ upload-max-file-size={uploadMaxFileSize ?? 500}
380
+ {...(pageMode ? { "page-mode": "" } : {})}
381
+ {...(closeHref ? { "close-href": closeHref } : {})}
382
+ {...(autoRestoreDraft ? { "auto-restore-draft": "" } : {})}
383
+ >
384
+ {/* SSR fallback skeleton */}
385
+ <div class="compose-dialog-inner">
386
+ <div class="compose-dialog-header" />
387
+ <div class="compose-body skel-section-md" />
388
+ </div>
389
+ </jant-compose-dialog>
390
+ );
391
+ };
392
+
393
+ export const ComposeDialog: FC<ComposeDialogProps> = ({
394
+ collections,
395
+ uploadMaxFileSize,
396
+ }) => {
155
397
  return (
156
398
  <dialog
157
399
  id="compose-dialog"
158
400
  class="compose-dialog"
159
- onclick="event.target === this && this.close()"
401
+ data-on:click="evt.target === el && el.querySelector('jant-compose-dialog')?.requestClose()"
160
402
  >
161
- <jant-compose-dialog collections={collectionsJson} labels={labels}>
162
- {/* SSR fallback skeleton */}
163
- <div class="compose-dialog-inner">
164
- <div class="compose-dialog-header" />
165
- <div class="compose-body skel-section-md" />
166
- </div>
167
- </jant-compose-dialog>
403
+ <ComposeForm
404
+ collections={collections}
405
+ uploadMaxFileSize={uploadMaxFileSize}
406
+ />
168
407
  </dialog>
169
408
  );
170
409
  };
@@ -16,7 +16,7 @@ export const ComposePrompt: FC = () => {
16
16
  <button
17
17
  type="button"
18
18
  class="compose-prompt-trigger"
19
- onclick="const d=document.getElementById('compose-dialog');d.showModal();d.querySelector('jant-compose-editor')?.focusInput()"
19
+ data-on:click="document.getElementById('compose-dialog').querySelector('jant-compose-dialog')?.restoreLocalDraft(); document.getElementById('compose-dialog').showModal(); document.getElementById('compose-dialog').querySelector('jant-compose-editor')?.focusInput()"
20
20
  >
21
21
  <span class="compose-prompt-avatar">
22
22
  <svg
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * CRUD Page Header Component
3
3
  *
4
- * Provides consistent header layout for dashboard CRUD list pages
4
+ * Provides consistent header layout for admin CRUD list pages
5
5
  * with title and primary action button
6
6
  */
7
7
 
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * List Item Row Component
3
3
  *
4
- * Provides consistent layout for list items in dashboard CRUD pages.
4
+ * Provides consistent layout for list items in admin CRUD pages.
5
5
  * Handles responsive spacing, overflow, and action button placement.
6
6
  */
7
7
 
@@ -1,21 +1,23 @@
1
1
  /**
2
2
  * Status Badge Component
3
3
  *
4
- * Displays badges for post status, featured, and pinned state.
4
+ * Displays badges for post status, visibility, and pinned state.
5
5
  */
6
6
 
7
7
  import type { FC } from "hono/jsx";
8
8
  import { useLingui } from "@lingui/react/macro";
9
- import type { Status } from "../../types.js";
9
+ import type { Status, Visibility } from "../../types.js";
10
10
 
11
11
  export interface StatusBadgeProps {
12
12
  status: Status;
13
+ visibility?: Visibility;
13
14
  featured?: boolean;
14
15
  pinned?: boolean;
15
16
  }
16
17
 
17
18
  export const StatusBadge: FC<StatusBadgeProps> = ({
18
19
  status,
20
+ visibility,
19
21
  featured,
20
22
  pinned,
21
23
  }) => {
@@ -48,6 +50,14 @@ export const StatusBadge: FC<StatusBadgeProps> = ({
48
50
  })}
49
51
  </span>
50
52
  )}
53
+ {visibility === "unlisted" && (
54
+ <span class="badge-outline">
55
+ {t({
56
+ message: "Unlisted",
57
+ comment: "@context: Post badge - unlisted",
58
+ })}
59
+ </span>
60
+ )}
51
61
  {pinned && (
52
62
  <span class="badge-outline">
53
63
  {t({
@@ -3,7 +3,9 @@
3
3
  */
4
4
 
5
5
  import { useLingui } from "@lingui/react/macro";
6
- import { AppearanceNav } from "./AppearanceNav.js";
6
+
7
+ const THEMING_DOCS_URL =
8
+ "https://github.com/jant-me/jant/blob/main/docs/theming.md";
7
9
 
8
10
  export function AdvancedContent({ customCSS }: { customCSS: string }) {
9
11
  const { t } = useLingui();
@@ -11,67 +13,77 @@ export function AdvancedContent({ customCSS }: { customCSS: string }) {
11
13
  const cssSignals = JSON.stringify({ customCSS }).replace(/</g, "\\u003c");
12
14
 
13
15
  return (
14
- <>
15
- <AppearanceNav currentTab="advanced" />
16
-
17
- <form
18
- data-signals={cssSignals}
19
- data-on:submit__prevent="@post('/dash/appearance/custom-css')"
20
- data-indicator="_cssLoading"
21
- class="max-w-3xl"
22
- >
23
- <fieldset>
24
- <legend class="text-lg font-semibold">
25
- {t({
26
- message: "Custom CSS",
27
- comment: "@context: Appearance settings heading for custom CSS",
28
- })}
29
- </legend>
30
- <p class="text-sm text-muted-foreground mb-4">
16
+ <form
17
+ data-signals={cssSignals}
18
+ data-on:submit__prevent="@post('/settings/custom-css')"
19
+ data-indicator="_cssLoading"
20
+ class="max-w-3xl"
21
+ >
22
+ <fieldset>
23
+ <legend class="text-lg font-semibold">
24
+ {t({
25
+ message: "Custom CSS",
26
+ comment: "@context: Appearance settings heading for custom CSS",
27
+ })}
28
+ </legend>
29
+ <p class="text-sm text-muted-foreground mb-4">
30
+ {t({
31
+ message:
32
+ "Add custom CSS to override any styles. Use data attributes like [data-page], [data-post], [data-format] to target specific elements.",
33
+ comment: "@context: Custom CSS settings description",
34
+ })}{" "}
35
+ <a
36
+ href={THEMING_DOCS_URL}
37
+ target="_blank"
38
+ rel="noopener noreferrer"
39
+ class="underline hover:text-foreground transition-colors"
40
+ >
31
41
  {t({
32
- message:
33
- "Add custom CSS to override any styles. Use data attributes like [data-page], [data-post], [data-format] to target specific elements.",
34
- comment: "@context: Custom CSS settings description",
42
+ message: "Theming guide",
43
+ comment:
44
+ "@context: Link to theming documentation on Custom CSS page",
35
45
  })}
36
- </p>
37
- <textarea
38
- data-bind="customCSS"
39
- class="textarea font-mono text-sm min-h-32"
40
- rows={8}
41
- placeholder={t({
42
- message: "/* Your custom CSS here */",
43
- comment: "@context: Custom CSS textarea placeholder",
44
- })}
45
- >
46
- {customCSS}
47
- </textarea>
48
- </fieldset>
49
- <button
50
- type="submit"
51
- class="btn mt-4"
52
- data-attr:disabled="$_cssLoading"
53
- >
54
- <svg
55
- data-show="$_cssLoading"
56
- style="display:none"
57
- class="animate-spin size-4"
58
- xmlns="http://www.w3.org/2000/svg"
59
- viewBox="0 0 24 24"
60
- fill="none"
61
- stroke="currentColor"
62
- stroke-width="2"
63
- stroke-linecap="round"
64
- stroke-linejoin="round"
65
- role="status"
66
- >
67
- <path d="M21 12a9 9 0 1 1-6.219-8.56" />
68
- </svg>
46
+ </a>
47
+ {" — "}
69
48
  {t({
70
- message: "Save CSS",
71
- comment: "@context: Button to save custom CSS",
49
+ message: "available CSS variables, data attributes, and examples.",
50
+ comment:
51
+ "@context: Description after theming guide link on Custom CSS page",
72
52
  })}
73
- </button>
74
- </form>
75
- </>
53
+ </p>
54
+ <textarea
55
+ data-bind="customCSS"
56
+ class="textarea font-mono text-sm min-h-32"
57
+ rows={8}
58
+ placeholder={t({
59
+ message: "/* Your custom CSS here */",
60
+ comment: "@context: Custom CSS textarea placeholder",
61
+ })}
62
+ >
63
+ {customCSS}
64
+ </textarea>
65
+ </fieldset>
66
+ <button type="submit" class="btn mt-4" data-attr:disabled="$_cssLoading">
67
+ <svg
68
+ data-show="$_cssLoading"
69
+ style="display:none"
70
+ class="animate-spin size-4"
71
+ xmlns="http://www.w3.org/2000/svg"
72
+ viewBox="0 0 24 24"
73
+ fill="none"
74
+ stroke="currentColor"
75
+ stroke-width="2"
76
+ stroke-linecap="round"
77
+ stroke-linejoin="round"
78
+ role="status"
79
+ >
80
+ <path d="M21 12a9 9 0 1 1-6.219-8.56" />
81
+ </svg>
82
+ {t({
83
+ message: "Save CSS",
84
+ comment: "@context: Button to save custom CSS",
85
+ })}
86
+ </button>
87
+ </form>
76
88
  );
77
89
  }
@@ -4,7 +4,6 @@
4
4
 
5
5
  import { useLingui } from "@lingui/react/macro";
6
6
  import type { ColorTheme } from "../../color-themes.js";
7
- import { AppearanceNav } from "./AppearanceNav.js";
8
7
 
9
8
  function ThemeCard({
10
9
  theme,
@@ -87,40 +86,56 @@ export function ColorThemeContent({
87
86
  );
88
87
 
89
88
  return (
90
- <>
91
- <AppearanceNav currentTab="color" />
92
-
93
- <div
94
- data-signals={themeSignals}
95
- data-on:change="@post('/dash/appearance/color')"
96
- class="max-w-3xl"
97
- >
98
- <fieldset>
99
- <legend class="text-lg font-semibold">
100
- {t({
101
- message: "Color theme",
102
- comment: "@context: Appearance settings heading",
103
- })}
104
- </legend>
105
- <p class="text-sm text-muted-foreground mb-4">
89
+ <div
90
+ data-signals={themeSignals}
91
+ data-on:change="@post('/settings/color-theme')"
92
+ class="max-w-3xl"
93
+ >
94
+ <fieldset>
95
+ <legend class="text-lg font-semibold">
96
+ {t({
97
+ message: "Color theme",
98
+ comment: "@context: Appearance settings heading",
99
+ })}
100
+ </legend>
101
+ <p class="text-sm text-muted-foreground mb-4">
102
+ {t({
103
+ message:
104
+ "Applies to your entire site, including admin pages. All themes support dark mode.",
105
+ comment: "@context: Appearance settings description",
106
+ })}{" "}
107
+ {t({
108
+ message: "Want more control?",
109
+ comment:
110
+ "@context: Prefix before Custom CSS link on color theme page",
111
+ })}{" "}
112
+ <a
113
+ href="/settings/custom-css"
114
+ class="underline hover:text-foreground transition-colors"
115
+ >
106
116
  {t({
107
- message:
108
- "This will theme both your site and your dashboard. All color themes support dark mode.",
109
- comment: "@context: Appearance settings description",
117
+ message: "Custom CSS",
118
+ comment:
119
+ "@context: Link to Custom CSS settings from color theme page",
110
120
  })}
111
- </p>
121
+ </a>{" "}
122
+ {t({
123
+ message: "lets you override any theme variable.",
124
+ comment:
125
+ "@context: Suffix after Custom CSS link on color theme page",
126
+ })}
127
+ </p>
112
128
 
113
- <div class="flex flex-col gap-4">
114
- {themes.map((theme) => (
115
- <ThemeCard
116
- key={theme.id}
117
- theme={theme}
118
- selected={theme.id === currentThemeId}
119
- />
120
- ))}
121
- </div>
122
- </fieldset>
123
- </div>
124
- </>
129
+ <div class="flex flex-col gap-4">
130
+ {themes.map((theme) => (
131
+ <ThemeCard
132
+ key={theme.id}
133
+ theme={theme}
134
+ selected={theme.id === currentThemeId}
135
+ />
136
+ ))}
137
+ </div>
138
+ </fieldset>
139
+ </div>
125
140
  );
126
141
  }