@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,162 +1,103 @@
1
1
  /**
2
- * Account settings: profile + password change forms
2
+ * Password settings: change sign-in password
3
3
  */
4
4
 
5
5
  import { useLingui } from "@lingui/react/macro";
6
- import { SettingsNav } from "./SettingsNav.js";
7
6
 
8
- export function AccountContent({ userName }: { userName: string }) {
7
+ export function AccountContent() {
9
8
  const { t } = useLingui();
10
9
 
11
- const profileSignals = JSON.stringify({ userName }).replace(/</g, "\\u003c");
12
-
13
10
  return (
14
- <>
15
- <SettingsNav currentTab="account" />
16
-
17
- <div class="flex flex-col max-w-lg">
18
- <form
19
- data-signals={profileSignals}
20
- data-on:submit__prevent="@post('/dash/settings/account')"
21
- data-indicator="_profileLoading"
22
- >
23
- <h2 class="text-lg font-semibold mb-4">
24
- {t({
25
- message: "Profile",
26
- comment: "@context: Account settings section heading",
27
- })}
28
- </h2>
29
- <div class="flex flex-col gap-4">
30
- <div class="field">
31
- <label class="label">
32
- {t({
33
- message: "Name",
34
- comment: "@context: Account settings form field",
35
- })}
36
- </label>
37
- <input type="text" data-bind="userName" class="input" required />
38
- </div>
11
+ <div class="flex flex-col max-w-lg">
12
+ <form
13
+ data-signals="{currentPassword: '', newPassword: '', confirmPassword: ''}"
14
+ data-on:submit__prevent="@post('/settings/account/password')"
15
+ data-indicator="_passwordLoading"
16
+ >
17
+ <h2 class="text-lg font-semibold mb-4">
18
+ {t({
19
+ message: "Change Password",
20
+ comment: "@context: Settings section heading",
21
+ })}
22
+ </h2>
23
+ <div class="flex flex-col gap-4">
24
+ <div class="field">
25
+ <label class="label">
26
+ {t({
27
+ message: "Current Password",
28
+ comment: "@context: Password form field",
29
+ })}
30
+ </label>
31
+ <input
32
+ type="password"
33
+ data-bind="currentPassword"
34
+ class="input"
35
+ required
36
+ autocomplete="current-password"
37
+ />
39
38
  </div>
40
39
 
41
- <button
42
- type="submit"
43
- class="btn mt-4"
44
- data-attr:disabled="$_profileLoading"
45
- >
46
- <svg
47
- data-show="$_profileLoading"
48
- style="display:none"
49
- class="animate-spin size-4"
50
- xmlns="http://www.w3.org/2000/svg"
51
- viewBox="0 0 24 24"
52
- fill="none"
53
- stroke="currentColor"
54
- stroke-width="2"
55
- stroke-linecap="round"
56
- stroke-linejoin="round"
57
- role="status"
58
- >
59
- <path d="M21 12a9 9 0 1 1-6.219-8.56" />
60
- </svg>
61
- {t({
62
- message: "Save Profile",
63
- comment: "@context: Button to save profile",
64
- })}
65
- </button>
66
- </form>
67
-
68
- <hr class="my-8" />
69
-
70
- <form
71
- data-signals="{currentPassword: '', newPassword: '', confirmPassword: ''}"
72
- data-on:submit__prevent="@post('/dash/settings/password')"
73
- data-indicator="_passwordLoading"
74
- >
75
- <h2 class="text-lg font-semibold mb-4">
76
- {t({
77
- message: "Change Password",
78
- comment: "@context: Settings section heading",
79
- })}
80
- </h2>
81
- <div class="flex flex-col gap-4">
82
- <div class="field">
83
- <label class="label">
84
- {t({
85
- message: "Current Password",
86
- comment: "@context: Password form field",
87
- })}
88
- </label>
89
- <input
90
- type="password"
91
- data-bind="currentPassword"
92
- class="input"
93
- required
94
- autocomplete="current-password"
95
- />
96
- </div>
97
-
98
- <div class="field">
99
- <label class="label">
100
- {t({
101
- message: "New Password",
102
- comment: "@context: Password form field",
103
- })}
104
- </label>
105
- <input
106
- type="password"
107
- data-bind="newPassword"
108
- class="input"
109
- required
110
- minlength={8}
111
- autocomplete="new-password"
112
- />
113
- </div>
40
+ <div class="field">
41
+ <label class="label">
42
+ {t({
43
+ message: "New Password",
44
+ comment: "@context: Password form field",
45
+ })}
46
+ </label>
47
+ <input
48
+ type="password"
49
+ data-bind="newPassword"
50
+ class="input"
51
+ required
52
+ minlength={8}
53
+ autocomplete="new-password"
54
+ />
55
+ </div>
114
56
 
115
- <div class="field">
116
- <label class="label">
117
- {t({
118
- message: "Confirm New Password",
119
- comment: "@context: Password form field",
120
- })}
121
- </label>
122
- <input
123
- type="password"
124
- data-bind="confirmPassword"
125
- class="input"
126
- required
127
- minlength={8}
128
- autocomplete="new-password"
129
- />
130
- </div>
57
+ <div class="field">
58
+ <label class="label">
59
+ {t({
60
+ message: "Confirm New Password",
61
+ comment: "@context: Password form field",
62
+ })}
63
+ </label>
64
+ <input
65
+ type="password"
66
+ data-bind="confirmPassword"
67
+ class="input"
68
+ required
69
+ minlength={8}
70
+ autocomplete="new-password"
71
+ />
131
72
  </div>
73
+ </div>
132
74
 
133
- <button
134
- type="submit"
135
- class="btn mt-4"
136
- data-attr:disabled="$_passwordLoading"
75
+ <button
76
+ type="submit"
77
+ class="btn mt-4"
78
+ data-attr:disabled="$_passwordLoading"
79
+ >
80
+ <svg
81
+ data-show="$_passwordLoading"
82
+ style="display:none"
83
+ class="animate-spin size-4"
84
+ xmlns="http://www.w3.org/2000/svg"
85
+ viewBox="0 0 24 24"
86
+ fill="none"
87
+ stroke="currentColor"
88
+ stroke-width="2"
89
+ stroke-linecap="round"
90
+ stroke-linejoin="round"
91
+ role="status"
137
92
  >
138
- <svg
139
- data-show="$_passwordLoading"
140
- style="display:none"
141
- class="animate-spin size-4"
142
- xmlns="http://www.w3.org/2000/svg"
143
- viewBox="0 0 24 24"
144
- fill="none"
145
- stroke="currentColor"
146
- stroke-width="2"
147
- stroke-linecap="round"
148
- stroke-linejoin="round"
149
- role="status"
150
- >
151
- <path d="M21 12a9 9 0 1 1-6.219-8.56" />
152
- </svg>
153
- {t({
154
- message: "Change Password",
155
- comment: "@context: Button to change password",
156
- })}
157
- </button>
158
- </form>
159
- </div>
160
- </>
93
+ <path d="M21 12a9 9 0 1 1-6.219-8.56" />
94
+ </svg>
95
+ {t({
96
+ message: "Change Password",
97
+ comment: "@context: Button to change password",
98
+ })}
99
+ </button>
100
+ </form>
101
+ </div>
161
102
  );
162
103
  }
@@ -0,0 +1,147 @@
1
+ /**
2
+ * Account settings sub-menu — lists Sessions and Password options
3
+ */
4
+
5
+ import { useLingui } from "@lingui/react/macro";
6
+
7
+ /** Chevron right icon */
8
+ function ChevronRight() {
9
+ return (
10
+ <svg
11
+ class="settings-item-chevron"
12
+ xmlns="http://www.w3.org/2000/svg"
13
+ width="16"
14
+ height="16"
15
+ viewBox="0 0 24 24"
16
+ fill="none"
17
+ stroke="currentColor"
18
+ stroke-width="2"
19
+ stroke-linecap="round"
20
+ stroke-linejoin="round"
21
+ >
22
+ <path d="m9 18 6-6-6-6" />
23
+ </svg>
24
+ );
25
+ }
26
+
27
+ function AccountMenuItem({
28
+ href,
29
+ icon,
30
+ color,
31
+ name,
32
+ description,
33
+ }: {
34
+ href: string;
35
+ icon: string;
36
+ color: string;
37
+ name: string;
38
+ description: string;
39
+ }) {
40
+ return (
41
+ <a href={href} class="settings-item">
42
+ <span class="settings-item-icon" style={`background-color:${color}`}>
43
+ <span dangerouslySetInnerHTML={{ __html: icon }} />
44
+ </span>
45
+ <span class="settings-item-text">
46
+ <span class="settings-item-name">{name}</span>
47
+ <span class="settings-item-desc">{description}</span>
48
+ </span>
49
+ <ChevronRight />
50
+ </a>
51
+ );
52
+ }
53
+
54
+ const ICONS = {
55
+ monitor: `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="20" height="14" x="2" y="3" rx="2"/><line x1="8" x2="16" y1="21" y2="21"/><line x1="12" x2="12" y1="17" y2="21"/></svg>`,
56
+ lock: `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="18" height="11" x="3" y="11" rx="2" ry="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4"/></svg>`,
57
+ download: `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" x2="12" y1="15" y2="3"/></svg>`,
58
+ };
59
+
60
+ const COLORS = {
61
+ teal: "oklch(0.55 0.15 185)",
62
+ gray: "oklch(0.55 0.01 250)",
63
+ green: "oklch(0.55 0.18 155)",
64
+ };
65
+
66
+ export function AccountMenuContent() {
67
+ const { t } = useLingui();
68
+
69
+ return (
70
+ <div class="settings-root">
71
+ <div>
72
+ <div class="settings-group">
73
+ <AccountMenuItem
74
+ href="/settings/account/sessions"
75
+ icon={ICONS.monitor}
76
+ color={COLORS.teal}
77
+ name={t({
78
+ message: "Sessions",
79
+ comment: "@context: Settings item — session management",
80
+ })}
81
+ description={t({
82
+ message: "Manage active sign-in sessions",
83
+ comment: "@context: Settings item description for sessions",
84
+ })}
85
+ />
86
+ <AccountMenuItem
87
+ href="/settings/account/password"
88
+ icon={ICONS.lock}
89
+ color={COLORS.gray}
90
+ name={t({
91
+ message: "Password",
92
+ comment: "@context: Settings item — password settings",
93
+ })}
94
+ description={t({
95
+ message: "Change your sign-in password",
96
+ comment:
97
+ "@context: Settings item description for password change",
98
+ })}
99
+ />
100
+ </div>
101
+ </div>
102
+
103
+ {/* Data */}
104
+ <div>
105
+ <div class="settings-group-label">
106
+ {t({
107
+ message: "Data",
108
+ comment: "@context: Settings group label for data export/import",
109
+ })}
110
+ </div>
111
+ <div class="settings-group">
112
+ <form
113
+ method="post"
114
+ action="/api/export/zola"
115
+ class="settings-export-form"
116
+ >
117
+ <button type="submit" class="settings-item">
118
+ <span
119
+ class="settings-item-icon"
120
+ style={`background-color:${COLORS.green}`}
121
+ >
122
+ <span dangerouslySetInnerHTML={{ __html: ICONS.download }} />
123
+ </span>
124
+ <span class="settings-item-text">
125
+ <span class="settings-item-name">
126
+ {t({
127
+ message: "Export Site",
128
+ comment:
129
+ "@context: Settings item — export site as Zola ZIP",
130
+ })}
131
+ </span>
132
+ <span class="settings-item-desc">
133
+ {t({
134
+ message: "Download as a Zola static site (.zip)",
135
+ comment:
136
+ "@context: Settings item description for site export",
137
+ })}
138
+ </span>
139
+ </span>
140
+ <ChevronRight />
141
+ </button>
142
+ </form>
143
+ </div>
144
+ </div>
145
+ </div>
146
+ );
147
+ }
@@ -0,0 +1,232 @@
1
+ /**
2
+ * API Tokens Settings Page
3
+ *
4
+ * Manage Bearer tokens for programmatic API access.
5
+ * Tokens are shown only once at creation — after that, only the prefix is visible.
6
+ */
7
+
8
+ import { useLingui } from "@lingui/react/macro";
9
+ import type { ApiToken } from "../../../types/entities.js";
10
+ import { formatDate } from "../../../lib/time.js";
11
+
12
+ const API_DOCS_URL = "https://github.com/jant-me/jant/blob/main/docs/API.md";
13
+
14
+ function TokenRow({ token }: { token: ApiToken }) {
15
+ const { t } = useLingui();
16
+
17
+ return (
18
+ <div class="py-4 flex items-start gap-4 border-b border-border last:border-b-0">
19
+ <div class="flex-1 min-w-0">
20
+ <div class="font-medium">{token.name}</div>
21
+ <div class="text-sm text-muted-foreground mt-0.5">
22
+ <code class="text-xs bg-muted px-1.5 py-0.5 rounded">
23
+ jnt_{token.prefix}...
24
+ </code>
25
+ <span class="mx-2">&middot;</span>
26
+ {t({
27
+ message: `Created ${formatDate(token.createdAt)}`,
28
+ comment: "@context: Token creation date",
29
+ })}
30
+ {token.lastUsedAt && (
31
+ <>
32
+ <span class="mx-2">&middot;</span>
33
+ {t({
34
+ message: `Last used ${formatDate(token.lastUsedAt)}`,
35
+ comment: "@context: Token last used date",
36
+ })}
37
+ </>
38
+ )}
39
+ </div>
40
+ </div>
41
+ <button
42
+ type="button"
43
+ class="btn-sm-ghost text-destructive"
44
+ data-on:click__prevent={`confirm('${t({ message: "Revoke this token? Any scripts using it will stop working.", comment: "@context: Confirm dialog for revoking API token" })}') && @post('/settings/api-tokens/${token.id}/delete')`}
45
+ >
46
+ {t({
47
+ message: "Revoke",
48
+ comment: "@context: Button to revoke API token",
49
+ })}
50
+ </button>
51
+ </div>
52
+ );
53
+ }
54
+
55
+ export function ApiTokensContent({
56
+ tokens,
57
+ siteUrl,
58
+ }: {
59
+ tokens: ApiToken[];
60
+ siteUrl: string;
61
+ }) {
62
+ const { t } = useLingui();
63
+
64
+ return (
65
+ <div
66
+ class="flex flex-col gap-8 max-w-2xl"
67
+ data-signals="{tokenName: '', _tokenLoading: false, _newPlaintext: '', _tokenCopied: false}"
68
+ >
69
+ {/* New token alert — shown after creation via signal patch */}
70
+ <div data-show="$_newPlaintext" style="display:none">
71
+ <div class="alert" role="alert">
72
+ <strong>
73
+ {t({
74
+ message: "Copy your token now — it won't be shown again.",
75
+ comment: "@context: Warning to copy newly created API token",
76
+ })}
77
+ </strong>
78
+ <section class="mt-2">
79
+ <div class="flex items-center gap-2">
80
+ <code
81
+ class="bg-muted px-3 py-2 rounded break-all select-all flex-1 text-sm"
82
+ data-text="$_newPlaintext"
83
+ >
84
+ {" "}
85
+ </code>
86
+ <button
87
+ type="button"
88
+ class="btn-sm-outline shrink-0"
89
+ data-on:click="navigator.clipboard.writeText($_newPlaintext); $_tokenCopied = true"
90
+ data-text={`$_tokenCopied ? '${t({ message: "Copied", comment: "@context: Feedback after copying API token" })}' : '${t({ message: "Copy Token", comment: "@context: Button to copy API token to clipboard" })}'`}
91
+ >
92
+ {t({
93
+ message: "Copy Token",
94
+ comment: "@context: Button to copy API token to clipboard",
95
+ })}
96
+ </button>
97
+ </div>
98
+ </section>
99
+ </div>
100
+ </div>
101
+
102
+ {/* Generate token form */}
103
+ <div>
104
+ <h2 class="text-lg font-medium mb-4">
105
+ {t({
106
+ message: "API Tokens",
107
+ comment: "@context: Settings section heading",
108
+ })}
109
+ </h2>
110
+ <p class="text-sm text-muted-foreground mb-4">
111
+ {t({
112
+ message:
113
+ "Tokens let you access the API from scripts, shortcuts, and other tools without signing in.",
114
+ comment: "@context: API tokens description",
115
+ })}
116
+ </p>
117
+ <form
118
+ data-on:submit__prevent="@post('/settings/api-tokens')"
119
+ data-indicator="_tokenLoading"
120
+ class="flex gap-2 items-end"
121
+ >
122
+ <div class="field flex-1">
123
+ <label class="label" for="tokenName">
124
+ {t({
125
+ message: "Token name",
126
+ comment: "@context: API token name field label",
127
+ })}
128
+ </label>
129
+ <input
130
+ type="text"
131
+ id="tokenName"
132
+ data-bind="tokenName"
133
+ class="input"
134
+ placeholder={t({
135
+ message: "e.g. iOS Shortcuts",
136
+ comment: "@context: Placeholder for API token name input",
137
+ })}
138
+ required
139
+ />
140
+ </div>
141
+ <button
142
+ type="submit"
143
+ class="btn"
144
+ data-attr:disabled="$_tokenLoading || !$tokenName.trim()"
145
+ >
146
+ {t({
147
+ message: "Generate Token",
148
+ comment: "@context: Button to create new API token",
149
+ })}
150
+ </button>
151
+ </form>
152
+ </div>
153
+
154
+ {/* Token list */}
155
+ {tokens.length > 0 && (
156
+ <div>
157
+ <h3 class="text-sm font-medium text-muted-foreground uppercase tracking-wide mb-2">
158
+ {t({
159
+ message: "Active Tokens",
160
+ comment: "@context: Heading for list of active API tokens",
161
+ })}
162
+ </h3>
163
+ <div class="border border-border rounded-lg px-4">
164
+ {tokens.map((token) => (
165
+ <TokenRow key={token.id} token={token} />
166
+ ))}
167
+ </div>
168
+ </div>
169
+ )}
170
+
171
+ {/* Usage examples */}
172
+ <div>
173
+ <h3 class="text-sm font-medium text-muted-foreground uppercase tracking-wide mb-2">
174
+ {t({
175
+ message: "Usage",
176
+ comment: "@context: Heading for API token usage examples",
177
+ })}
178
+ </h3>
179
+ <div class="flex flex-col gap-3 text-sm">
180
+ <div>
181
+ <div class="text-muted-foreground mb-1">
182
+ {t({
183
+ message: "Create a post with curl:",
184
+ comment: "@context: Label for curl example",
185
+ })}
186
+ </div>
187
+ <pre class="bg-muted px-3 py-2 rounded text-xs overflow-x-auto">
188
+ <code>
189
+ {`curl -X POST ${siteUrl}/api/posts \\
190
+ -H "Authorization: Bearer jnt_YOUR_TOKEN" \\
191
+ -H "Content-Type: application/json" \\
192
+ -d '{"format":"note","body":"Hello from the API"}'`}
193
+ </code>
194
+ </pre>
195
+ </div>
196
+ <div>
197
+ <div class="text-muted-foreground mb-1">
198
+ {t({
199
+ message: "List posts:",
200
+ comment: "@context: Label for list posts curl example",
201
+ })}
202
+ </div>
203
+ <pre class="bg-muted px-3 py-2 rounded text-xs overflow-x-auto">
204
+ <code>
205
+ {`curl ${siteUrl}/api/posts \\
206
+ -H "Authorization: Bearer jnt_YOUR_TOKEN"`}
207
+ </code>
208
+ </pre>
209
+ </div>
210
+ <p class="text-muted-foreground">
211
+ <a
212
+ href={API_DOCS_URL}
213
+ target="_blank"
214
+ rel="noopener noreferrer"
215
+ class="underline hover:text-foreground transition-colors"
216
+ >
217
+ {t({
218
+ message: "API reference",
219
+ comment: "@context: Link to API documentation",
220
+ })}
221
+ </a>
222
+ {" — "}
223
+ {t({
224
+ message: "all available endpoints and request formats.",
225
+ comment: "@context: Description after API reference link",
226
+ })}
227
+ </p>
228
+ </div>
229
+ </div>
230
+ </div>
231
+ );
232
+ }