@jant/core 0.3.36 → 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 (271) hide show
  1. package/bin/commands/export.js +1 -1
  2. package/bin/commands/import-site.js +529 -0
  3. package/bin/commands/reset-password.js +3 -2
  4. package/dist/client/assets/heic-to-DIRPI3VF.js +1 -0
  5. package/dist/client/assets/url-FWFqPJPb.js +1 -0
  6. package/dist/client/client.css +1 -1
  7. package/dist/client/client.js +4012 -3276
  8. package/dist/index.js +10285 -5809
  9. package/package.json +11 -3
  10. package/src/__tests__/helpers/app.ts +9 -9
  11. package/src/__tests__/helpers/db.ts +91 -93
  12. package/src/app.tsx +157 -27
  13. package/src/auth.ts +20 -2
  14. package/src/client/archive-nav.js +187 -0
  15. package/src/client/audio-player.ts +478 -0
  16. package/src/client/audio-processor.ts +84 -0
  17. package/src/client/avatar-upload.ts +3 -2
  18. package/src/client/components/__tests__/jant-compose-dialog.test.ts +645 -49
  19. package/src/client/components/__tests__/jant-compose-editor.test.ts +208 -16
  20. package/src/client/components/__tests__/jant-post-form.test.ts +19 -9
  21. package/src/client/components/__tests__/jant-settings-avatar.test.ts +2 -2
  22. package/src/client/components/__tests__/jant-settings-general.test.ts +3 -3
  23. package/src/client/components/collection-sidebar-types.ts +7 -9
  24. package/src/client/components/compose-types.ts +101 -4
  25. package/src/client/components/jant-collection-form.ts +43 -7
  26. package/src/client/components/jant-collection-sidebar.ts +88 -84
  27. package/src/client/components/jant-compose-dialog.ts +1655 -219
  28. package/src/client/components/jant-compose-editor.ts +732 -168
  29. package/src/client/components/jant-compose-fullscreen.ts +23 -78
  30. package/src/client/components/jant-media-lightbox.ts +2 -0
  31. package/src/client/components/jant-nav-manager.ts +24 -284
  32. package/src/client/components/jant-post-form.ts +89 -9
  33. package/src/client/components/jant-post-menu.ts +1019 -0
  34. package/src/client/components/jant-settings-avatar.ts +3 -3
  35. package/src/client/components/jant-settings-general.ts +38 -4
  36. package/src/client/components/jant-text-preview.ts +232 -0
  37. package/src/client/components/nav-manager-types.ts +4 -19
  38. package/src/client/components/post-form-template.ts +107 -12
  39. package/src/client/components/post-form-types.ts +11 -4
  40. package/src/client/compose-bridge.ts +410 -109
  41. package/src/client/image-processor.ts +26 -8
  42. package/src/client/media-metadata.ts +247 -0
  43. package/src/client/multipart-upload.ts +160 -0
  44. package/src/client/post-form-bridge.ts +52 -1
  45. package/src/client/settings-bridge.ts +0 -12
  46. package/src/client/thread-context.ts +140 -0
  47. package/src/client/tiptap/create-editor.ts +46 -0
  48. package/src/client/tiptap/extensions.ts +5 -0
  49. package/src/client/tiptap/image-node.ts +2 -8
  50. package/src/client/tiptap/paste-image.ts +2 -13
  51. package/src/client/tiptap/slash-commands.ts +173 -63
  52. package/src/client/toast.ts +101 -3
  53. package/src/client/types/sortablejs.d.ts +15 -0
  54. package/src/client/upload-with-metadata.ts +54 -0
  55. package/src/client/video-processor.ts +207 -0
  56. package/src/client.ts +5 -2
  57. package/src/db/__tests__/migrations.test.ts +118 -0
  58. package/src/db/index.ts +52 -0
  59. package/src/db/migrations/0000_baseline.sql +269 -0
  60. package/src/db/migrations/0001_fts_setup.sql +31 -0
  61. package/src/db/migrations/meta/0000_snapshot.json +703 -119
  62. package/src/db/migrations/meta/0001_snapshot.json +1337 -0
  63. package/src/db/migrations/meta/_journal.json +4 -39
  64. package/src/db/schema.ts +409 -145
  65. package/src/i18n/__tests__/detect.test.ts +115 -0
  66. package/src/i18n/context.tsx +2 -2
  67. package/src/i18n/detect.ts +85 -1
  68. package/src/i18n/i18n.ts +1 -1
  69. package/src/i18n/index.ts +2 -1
  70. package/src/i18n/locales/en.po +487 -1217
  71. package/src/i18n/locales/en.ts +1 -1
  72. package/src/i18n/locales/zh-Hans.po +613 -996
  73. package/src/i18n/locales/zh-Hans.ts +1 -1
  74. package/src/i18n/locales/zh-Hant.po +624 -1007
  75. package/src/i18n/locales/zh-Hant.ts +1 -1
  76. package/src/i18n/middleware.ts +6 -0
  77. package/src/index.ts +5 -7
  78. package/src/lib/__tests__/blurhash-placeholder.test.ts +75 -0
  79. package/src/lib/__tests__/constants.test.ts +0 -1
  80. package/src/lib/__tests__/markdown-to-tiptap.test.ts +358 -0
  81. package/src/lib/__tests__/nanoid.test.ts +26 -0
  82. package/src/lib/__tests__/schemas.test.ts +181 -63
  83. package/src/lib/__tests__/slug.test.ts +126 -0
  84. package/src/lib/__tests__/sse.test.ts +6 -6
  85. package/src/lib/__tests__/summary.test.ts +264 -0
  86. package/src/lib/__tests__/theme.test.ts +1 -1
  87. package/src/lib/__tests__/timeline.test.ts +33 -30
  88. package/src/lib/__tests__/tiptap-to-markdown.test.ts +346 -0
  89. package/src/lib/__tests__/view.test.ts +141 -66
  90. package/src/lib/blurhash-placeholder.ts +102 -0
  91. package/src/lib/constants.ts +3 -1
  92. package/src/lib/emoji-catalog.ts +885 -68
  93. package/src/lib/errors.ts +11 -8
  94. package/src/lib/feed.ts +78 -32
  95. package/src/lib/html.ts +2 -1
  96. package/src/lib/icon-catalog.ts +5033 -1
  97. package/src/lib/icons.ts +3 -2
  98. package/src/lib/index.ts +0 -1
  99. package/src/lib/markdown-to-tiptap.ts +286 -0
  100. package/src/lib/media-helpers.ts +12 -3
  101. package/src/lib/nanoid.ts +29 -0
  102. package/src/lib/navigation.ts +1 -1
  103. package/src/lib/render.tsx +20 -2
  104. package/src/lib/resolve-config.ts +6 -2
  105. package/src/lib/schemas.ts +224 -55
  106. package/src/lib/search-snippet.ts +34 -0
  107. package/src/lib/slug.ts +96 -0
  108. package/src/lib/sse.ts +6 -6
  109. package/src/lib/storage.ts +115 -7
  110. package/src/lib/summary.ts +66 -0
  111. package/src/lib/theme.ts +11 -8
  112. package/src/lib/timeline.ts +74 -34
  113. package/src/lib/tiptap-render.ts +5 -10
  114. package/src/lib/tiptap-to-markdown.ts +305 -0
  115. package/src/lib/upload.ts +190 -29
  116. package/src/lib/url.ts +31 -0
  117. package/src/lib/view.ts +204 -37
  118. package/src/middleware/__tests__/auth.test.ts +191 -11
  119. package/src/middleware/__tests__/onboarding.test.ts +12 -10
  120. package/src/middleware/auth.ts +63 -9
  121. package/src/middleware/onboarding.ts +1 -1
  122. package/src/middleware/secure-headers.ts +40 -0
  123. package/src/preset.css +45 -2
  124. package/src/routes/__tests__/compose.test.ts +17 -24
  125. package/src/routes/api/__tests__/collections.test.ts +109 -61
  126. package/src/routes/api/__tests__/nav-items.test.ts +46 -29
  127. package/src/routes/api/__tests__/posts.test.ts +132 -68
  128. package/src/routes/api/__tests__/search.test.ts +15 -2
  129. package/src/routes/api/__tests__/upload-multipart.test.ts +534 -0
  130. package/src/routes/api/collections.ts +51 -42
  131. package/src/routes/api/custom-urls.ts +80 -0
  132. package/src/routes/api/export.ts +31 -0
  133. package/src/routes/api/nav-items.ts +23 -19
  134. package/src/routes/api/posts.ts +43 -39
  135. package/src/routes/api/search.ts +3 -4
  136. package/src/routes/api/upload-multipart.ts +245 -0
  137. package/src/routes/api/upload.ts +85 -19
  138. package/src/routes/auth/__tests__/setup.test.ts +20 -60
  139. package/src/routes/auth/setup.tsx +26 -33
  140. package/src/routes/auth/signin.tsx +3 -7
  141. package/src/routes/compose.tsx +10 -55
  142. package/src/routes/dash/__tests__/settings-avatar.test.ts +1 -1
  143. package/src/routes/dash/custom-urls.tsx +414 -0
  144. package/src/routes/dash/settings.tsx +304 -232
  145. package/src/routes/feed/__tests__/rss.test.ts +27 -28
  146. package/src/routes/feed/rss.ts +6 -4
  147. package/src/routes/feed/sitemap.ts +2 -12
  148. package/src/routes/pages/__tests__/collections.test.ts +5 -6
  149. package/src/routes/pages/__tests__/featured.test.ts +41 -22
  150. package/src/routes/pages/archive.tsx +175 -39
  151. package/src/routes/pages/collection.tsx +22 -10
  152. package/src/routes/pages/collections.tsx +3 -3
  153. package/src/routes/pages/featured.tsx +28 -4
  154. package/src/routes/pages/home.tsx +16 -15
  155. package/src/routes/pages/latest.tsx +1 -11
  156. package/src/routes/pages/new.tsx +39 -0
  157. package/src/routes/pages/page.tsx +95 -49
  158. package/src/routes/pages/search.tsx +1 -1
  159. package/src/services/__tests__/api-token.test.ts +135 -0
  160. package/src/services/__tests__/collection.test.ts +275 -227
  161. package/src/services/__tests__/custom-url.test.ts +213 -0
  162. package/src/services/__tests__/media.test.ts +162 -22
  163. package/src/services/__tests__/navigation.test.ts +109 -68
  164. package/src/services/__tests__/post-timeline.test.ts +205 -32
  165. package/src/services/__tests__/post.test.ts +713 -234
  166. package/src/services/__tests__/search.test.ts +67 -10
  167. package/src/services/api-token.ts +166 -0
  168. package/src/services/auth.ts +17 -2
  169. package/src/services/collection.ts +397 -131
  170. package/src/services/custom-url.ts +188 -0
  171. package/src/services/export.ts +802 -0
  172. package/src/services/index.ts +26 -19
  173. package/src/services/media.ts +100 -22
  174. package/src/services/navigation.ts +158 -47
  175. package/src/services/path.ts +339 -0
  176. package/src/services/post.ts +687 -154
  177. package/src/services/search.ts +160 -75
  178. package/src/styles/components.css +58 -7
  179. package/src/styles/tokens.css +84 -6
  180. package/src/styles/ui.css +2964 -457
  181. package/src/types/bindings.ts +4 -1
  182. package/src/types/config.ts +12 -4
  183. package/src/types/constants.ts +15 -3
  184. package/src/types/entities.ts +74 -35
  185. package/src/types/operations.ts +11 -24
  186. package/src/types/props.ts +51 -16
  187. package/src/types/views.ts +45 -22
  188. package/src/ui/color-themes.ts +133 -23
  189. package/src/ui/compose/ComposeDialog.tsx +239 -17
  190. package/src/ui/compose/ComposePrompt.tsx +1 -1
  191. package/src/ui/dash/CrudPageHeader.tsx +1 -1
  192. package/src/ui/dash/ListItemRow.tsx +1 -1
  193. package/src/ui/dash/StatusBadge.tsx +3 -1
  194. package/src/ui/dash/appearance/AdvancedContent.tsx +22 -1
  195. package/src/ui/dash/appearance/ColorThemeContent.tsx +22 -2
  196. package/src/ui/dash/appearance/FontThemeContent.tsx +1 -1
  197. package/src/ui/dash/appearance/NavigationContent.tsx +5 -45
  198. package/src/ui/dash/index.ts +0 -3
  199. package/src/ui/dash/settings/AccountContent.tsx +3 -57
  200. package/src/ui/dash/settings/AccountMenuContent.tsx +147 -0
  201. package/src/ui/dash/settings/ApiTokensContent.tsx +232 -0
  202. package/src/ui/dash/settings/AvatarContent.tsx +8 -0
  203. package/src/ui/dash/settings/SessionsContent.tsx +159 -0
  204. package/src/ui/dash/settings/SettingsRootContent.tsx +45 -15
  205. package/src/ui/feed/LinkCard.tsx +89 -40
  206. package/src/ui/feed/NoteCard.tsx +39 -25
  207. package/src/ui/feed/PostStatusBadges.tsx +67 -0
  208. package/src/ui/feed/QuoteCard.tsx +38 -23
  209. package/src/ui/feed/ThreadPreview.tsx +90 -26
  210. package/src/ui/feed/TimelineFeed.tsx +3 -2
  211. package/src/ui/feed/TimelineItem.tsx +15 -6
  212. package/src/ui/feed/__tests__/thread-preview.test.ts +107 -0
  213. package/src/ui/feed/thread-preview-state.ts +61 -0
  214. package/src/ui/font-themes.ts +2 -2
  215. package/src/ui/layouts/BaseLayout.tsx +2 -2
  216. package/src/ui/layouts/SiteLayout.tsx +105 -92
  217. package/src/ui/pages/ArchivePage.tsx +923 -98
  218. package/src/ui/pages/ComposePage.tsx +54 -0
  219. package/src/ui/pages/PostPage.tsx +30 -45
  220. package/src/ui/pages/SearchPage.tsx +181 -37
  221. package/src/ui/shared/AdminBreadcrumb.tsx +29 -0
  222. package/src/ui/shared/CollectionsSidebar.tsx +47 -37
  223. package/src/ui/shared/MediaGallery.tsx +445 -149
  224. package/src/ui/shared/PostFooter.tsx +204 -0
  225. package/src/ui/shared/StarRating.tsx +27 -0
  226. package/src/ui/shared/__tests__/format-chars.test.ts +35 -0
  227. package/src/ui/shared/index.ts +0 -1
  228. package/dist/client/assets/url-8Dj-5CLW.js +0 -1
  229. package/src/client/media-upload.ts +0 -161
  230. package/src/client/page-slug-bridge.ts +0 -42
  231. package/src/db/migrations/0000_square_wallflower.sql +0 -118
  232. package/src/db/migrations/0001_add_search_fts.sql +0 -34
  233. package/src/db/migrations/0002_add_media_attachments.sql +0 -3
  234. package/src/db/migrations/0003_add_navigation_links.sql +0 -8
  235. package/src/db/migrations/0004_add_storage_provider.sql +0 -3
  236. package/src/db/migrations/0005_v2_schema_migration.sql +0 -268
  237. package/src/db/migrations/0006_rename_slug_to_path.sql +0 -5
  238. package/src/db/migrations/0007_post_collections_m2m.sql +0 -94
  239. package/src/db/migrations/0008_add_collection_dividers.sql +0 -8
  240. package/src/db/migrations/0009_drop_collection_show_divider.sql +0 -2
  241. package/src/db/migrations/0010_add_performance_indexes.sql +0 -16
  242. package/src/db/migrations/0011_add_path_registry.sql +0 -23
  243. package/src/db/migrations/0012_add_tiptap_columns.sql +0 -2
  244. package/src/db/migrations/0013_replace_featured_with_visibility.sql +0 -8
  245. package/src/db/migrations/meta/0003_snapshot.json +0 -821
  246. package/src/lib/__tests__/sqid.test.ts +0 -65
  247. package/src/lib/sqid.ts +0 -79
  248. package/src/routes/api/__tests__/pages.test.ts +0 -218
  249. package/src/routes/api/pages.ts +0 -73
  250. package/src/routes/dash/__tests__/pages.test.ts +0 -226
  251. package/src/routes/dash/index.tsx +0 -109
  252. package/src/routes/dash/media.tsx +0 -135
  253. package/src/routes/dash/pages.tsx +0 -245
  254. package/src/routes/dash/posts.tsx +0 -338
  255. package/src/routes/dash/redirects.tsx +0 -263
  256. package/src/routes/pages/post.tsx +0 -59
  257. package/src/services/__tests__/page.test.ts +0 -298
  258. package/src/services/__tests__/path-registry.test.ts +0 -165
  259. package/src/services/__tests__/redirect.test.ts +0 -159
  260. package/src/services/page.ts +0 -216
  261. package/src/services/path-registry.ts +0 -160
  262. package/src/services/redirect.ts +0 -97
  263. package/src/ui/dash/PageForm.tsx +0 -187
  264. package/src/ui/dash/PostList.tsx +0 -95
  265. package/src/ui/dash/media/MediaListContent.tsx +0 -206
  266. package/src/ui/dash/media/ViewMediaContent.tsx +0 -208
  267. package/src/ui/dash/pages/PagesContent.tsx +0 -75
  268. package/src/ui/dash/posts/PostForm.tsx +0 -260
  269. package/src/ui/layouts/DashLayout.tsx +0 -247
  270. package/src/ui/pages/SinglePage.tsx +0 -23
  271. package/src/ui/shared/ThreadView.tsx +0 -136
@@ -88,7 +88,7 @@ export function ColorThemeContent({
88
88
  return (
89
89
  <div
90
90
  data-signals={themeSignals}
91
- data-on:change="@post('/dash/settings/color-theme')"
91
+ data-on:change="@post('/settings/color-theme')"
92
92
  class="max-w-3xl"
93
93
  >
94
94
  <fieldset>
@@ -101,8 +101,28 @@ export function ColorThemeContent({
101
101
  <p class="text-sm text-muted-foreground mb-4">
102
102
  {t({
103
103
  message:
104
- "This will theme both your site and your dashboard. All color themes support dark mode.",
104
+ "Applies to your entire site, including admin pages. All themes support dark mode.",
105
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
+ >
116
+ {t({
117
+ message: "Custom CSS",
118
+ comment:
119
+ "@context: Link to Custom CSS settings from color theme page",
120
+ })}
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",
106
126
  })}
107
127
  </p>
108
128
 
@@ -20,7 +20,7 @@ export function FontThemeContent({
20
20
  /</g,
21
21
  "\\u003c",
22
22
  )}
23
- data-on:change="@post('/dash/settings/font-theme')"
23
+ data-on:change="@post('/settings/font-theme')"
24
24
  class="max-w-3xl"
25
25
  >
26
26
  <fieldset>
@@ -3,7 +3,7 @@
3
3
  */
4
4
 
5
5
  import { useLingui } from "@lingui/react/macro";
6
- import type { NavItem, Page, SystemNavKey } from "../../../types.js";
6
+ import type { NavItem, SystemNavKey } from "../../../types.js";
7
7
  import { SYSTEM_NAV_KEYS } from "../../../types.js";
8
8
  import type {
9
9
  NavManagerLabels,
@@ -15,7 +15,7 @@ import type {
15
15
 
16
16
  const SYSTEM_DESCRIPTIONS: Record<SystemNavKey, string> = {
17
17
  rss: "Add a link to your RSS feed",
18
- dashboard: "Shows 'Dashboard' when logged in, 'Sign in' when logged out",
18
+ settings: "Shows 'Settings' when logged in, 'Sign in' when logged out",
19
19
  collections: "Link to your collections page",
20
20
  archive: "Link to the post archive",
21
21
  };
@@ -26,13 +26,11 @@ const SYSTEM_DESCRIPTIONS: Record<SystemNavKey, string> = {
26
26
 
27
27
  export function NavigationContent({
28
28
  navItems,
29
- availablePages,
30
29
  headerNavMaxVisible,
31
30
  homeDefaultView,
32
31
  siteName,
33
32
  }: {
34
33
  navItems: NavItem[];
35
- availablePages: Page[];
36
34
  headerNavMaxVisible: number;
37
35
  homeDefaultView: string;
38
36
  siteName: string;
@@ -45,7 +43,6 @@ export function NavigationContent({
45
43
  type: item.type,
46
44
  label: item.label,
47
45
  url: item.url,
48
- pageId: item.pageId,
49
46
  }));
50
47
 
51
48
  // Build system nav config array for the Lit component
@@ -58,13 +55,6 @@ export function NavigationContent({
58
55
  description: SYSTEM_DESCRIPTIONS[key],
59
56
  }));
60
57
 
61
- // Serialize available pages for the Lit component
62
- const pagesData = availablePages.map((page) => ({
63
- id: page.id,
64
- title: page.title,
65
- slug: page.slug,
66
- }));
67
-
68
58
  const labels: NavManagerLabels = {
69
59
  preview: t({
70
60
  message: "Navigation Preview",
@@ -76,10 +66,9 @@ export function NavigationContent({
76
66
  }),
77
67
  emptyState: t({
78
68
  message:
79
- "No navigation items yet. Add pages, links, or enable system items below.",
69
+ "No navigation items yet. Add links or enable system items below.",
80
70
  comment: "@context: Empty state for navigation items",
81
71
  }),
82
- page: t({ message: "page", comment: "@context: Nav item type badge" }),
83
72
  link: t({ message: "link", comment: "@context: Nav item type badge" }),
84
73
  system: t({
85
74
  message: "system",
@@ -102,13 +91,9 @@ export function NavigationContent({
102
91
  message: "Delete",
103
92
  comment: "@context: Delete nav item",
104
93
  }),
105
- editPage: t({
106
- message: "Edit Page",
107
- comment: "@context: Link to edit the page",
108
- }),
109
94
  remove: t({
110
95
  message: "Remove",
111
- comment: "@context: Remove page from navigation",
96
+ comment: "@context: Remove system item from navigation",
112
97
  }),
113
98
  orderSaved: t({
114
99
  message: "Order saved",
@@ -132,29 +117,13 @@ export function NavigationContent({
132
117
  }),
133
118
  systemLinksDescription: t({
134
119
  message:
135
- "Toggle built-in navigation items. Enabled items appear in your navigation alongside pages and links.",
120
+ "Toggle built-in navigation items. Enabled items appear in your navigation alongside links.",
136
121
  comment: "@context: Description for system nav toggles",
137
122
  }),
138
- addPageToNavigation: t({
139
- message: "Add page to navigation",
140
- comment: "@context: Section heading for adding page to nav",
141
- }),
142
123
  addCustomLinkToNavigation: t({
143
124
  message: "Add custom link to navigation",
144
125
  comment: "@context: Section heading for adding custom link to nav",
145
126
  }),
146
- choosePage: t({
147
- message: "Choose a page…",
148
- comment: "@context: Placeholder for page select combobox trigger",
149
- }),
150
- searchPages: t({
151
- message: "Search pages…",
152
- comment: "@context: Placeholder for page search input in combobox",
153
- }),
154
- noPagesFound: t({
155
- message: "No matching pages.",
156
- comment: "@context: Empty state when page search has no results",
157
- }),
158
127
  addLink: t({
159
128
  message: "Add Link",
160
129
  comment: "@context: Button and heading for adding custom link",
@@ -163,14 +132,6 @@ export function NavigationContent({
163
132
  message: "Add a custom link to any URL",
164
133
  comment: "@context: Description in link popover form",
165
134
  }),
166
- allPagesInNav: t({
167
- message: "All pages are already in navigation.",
168
- comment: "@context: Message when no pages available to add",
169
- }),
170
- createPage: t({
171
- message: "Create page",
172
- comment: "@context: Link at bottom of page combobox to create a new page",
173
- }),
174
135
  urlPlaceholder: "/archive or https://...",
175
136
  maxVisibleLinks: t({
176
137
  message: "Links shown in header",
@@ -222,7 +183,6 @@ export function NavigationContent({
222
183
  items={escapeJson(itemsData)}
223
184
  labels={escapeJson(labels)}
224
185
  system-nav-items={escapeJson(systemNavData)}
225
- available-pages={escapeJson(pagesData)}
226
186
  site-name={siteName}
227
187
  max-visible={headerNavMaxVisible}
228
188
  home-default-view={homeDefaultView}
@@ -4,7 +4,4 @@ export { DangerZone, type DangerZoneProps } from "./DangerZone.js";
4
4
  export { EmptyState, type EmptyStateProps } from "../shared/EmptyState.js";
5
5
  export { FormatBadge, type FormatBadgeProps } from "./FormatBadge.js";
6
6
  export { ListItemRow, type ListItemRowProps } from "./ListItemRow.js";
7
- export { PageForm, type PageFormProps } from "./PageForm.js";
8
- export { PostForm, type PostFormProps } from "./posts/PostForm.js";
9
- export { PostList, type PostListProps } from "./PostList.js";
10
7
  export { StatusBadge, type StatusBadgeProps } from "./StatusBadge.js";
@@ -1,71 +1,17 @@
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
6
 
7
- export function AccountContent({ userName }: { userName: string }) {
7
+ export function AccountContent() {
8
8
  const { t } = useLingui();
9
9
 
10
- const profileSignals = JSON.stringify({ userName }).replace(/</g, "\\u003c");
11
-
12
10
  return (
13
11
  <div class="flex flex-col max-w-lg">
14
- <form
15
- data-signals={profileSignals}
16
- data-on:submit__prevent="@post('/dash/settings/account')"
17
- data-indicator="_profileLoading"
18
- >
19
- <h2 class="text-lg font-semibold mb-4">
20
- {t({
21
- message: "Profile",
22
- comment: "@context: Account settings section heading",
23
- })}
24
- </h2>
25
- <div class="flex flex-col gap-4">
26
- <div class="field">
27
- <label class="label">
28
- {t({
29
- message: "Name",
30
- comment: "@context: Account settings form field",
31
- })}
32
- </label>
33
- <input type="text" data-bind="userName" class="input" required />
34
- </div>
35
- </div>
36
-
37
- <button
38
- type="submit"
39
- class="btn mt-4"
40
- data-attr:disabled="$_profileLoading"
41
- >
42
- <svg
43
- data-show="$_profileLoading"
44
- style="display:none"
45
- class="animate-spin size-4"
46
- xmlns="http://www.w3.org/2000/svg"
47
- viewBox="0 0 24 24"
48
- fill="none"
49
- stroke="currentColor"
50
- stroke-width="2"
51
- stroke-linecap="round"
52
- stroke-linejoin="round"
53
- role="status"
54
- >
55
- <path d="M21 12a9 9 0 1 1-6.219-8.56" />
56
- </svg>
57
- {t({
58
- message: "Save Profile",
59
- comment: "@context: Button to save profile",
60
- })}
61
- </button>
62
- </form>
63
-
64
- <hr class="my-8" />
65
-
66
12
  <form
67
13
  data-signals="{currentPassword: '', newPassword: '', confirmPassword: ''}"
68
- data-on:submit__prevent="@post('/dash/settings/password')"
14
+ data-on:submit__prevent="@post('/settings/account/password')"
69
15
  data-indicator="_passwordLoading"
70
16
  >
71
17
  <h2 class="text-lg font-semibold mb-4">
@@ -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
+ }
@@ -50,6 +50,14 @@ export function AvatarContent({
50
50
  message: "Upload failed. Please try again.",
51
51
  comment: "@context: Error message when avatar upload fails",
52
52
  }),
53
+ save: t({
54
+ message: "Save",
55
+ comment: "@context: Button to save settings changes",
56
+ }),
57
+ cancel: t({
58
+ message: "Cancel",
59
+ comment: "@context: Button to cancel settings changes",
60
+ }),
53
61
  }).replace(/</g, "\\u003c");
54
62
 
55
63
  return (