@jant/core 0.3.24 → 0.3.26

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 (277) hide show
  1. package/dist/app.js +101 -571
  2. package/dist/client.js +1 -0
  3. package/dist/db/schema.js +1 -1
  4. package/dist/i18n/locales/en.js +1 -1
  5. package/dist/i18n/locales/zh-Hans.js +1 -1
  6. package/dist/i18n/locales/zh-Hant.js +1 -1
  7. package/dist/index.js +3 -9
  8. package/dist/lib/avatar-upload.js +134 -0
  9. package/dist/lib/config.js +39 -0
  10. package/dist/lib/constants.js +10 -9
  11. package/dist/lib/favicon.js +102 -0
  12. package/dist/lib/image.js +13 -17
  13. package/dist/lib/media-helpers.js +2 -2
  14. package/dist/lib/nav-reorder.js +1 -1
  15. package/dist/lib/navigation.js +48 -3
  16. package/dist/lib/pagination.js +44 -0
  17. package/dist/lib/render.js +16 -11
  18. package/dist/lib/schemas.js +34 -3
  19. package/dist/lib/theme.js +4 -4
  20. package/dist/lib/timeline.js +24 -48
  21. package/dist/lib/timezones.js +388 -0
  22. package/dist/lib/view.js +3 -3
  23. package/dist/routes/api/collections.js +124 -0
  24. package/dist/routes/api/nav-items.js +104 -0
  25. package/dist/routes/api/pages.js +91 -0
  26. package/dist/routes/api/posts.js +3 -3
  27. package/dist/routes/api/search.js +2 -2
  28. package/dist/routes/api/settings.js +68 -0
  29. package/dist/routes/api/upload.js +3 -3
  30. package/dist/routes/auth/reset.js +221 -0
  31. package/dist/routes/auth/setup.js +194 -0
  32. package/dist/routes/auth/signin.js +176 -0
  33. package/dist/routes/compose.js +48 -0
  34. package/dist/routes/dash/collections.js +24 -416
  35. package/dist/routes/dash/index.js +1 -1
  36. package/dist/routes/dash/media.js +13 -393
  37. package/dist/routes/dash/pages.js +112 -86
  38. package/dist/routes/dash/posts.js +3 -5
  39. package/dist/routes/dash/redirects.js +20 -14
  40. package/dist/routes/dash/settings.js +213 -518
  41. package/dist/routes/feed/rss.js +4 -3
  42. package/dist/routes/feed/sitemap.js +5 -3
  43. package/dist/routes/pages/archive.js +3 -6
  44. package/dist/routes/pages/collection.js +3 -6
  45. package/dist/routes/pages/collections.js +28 -0
  46. package/dist/routes/pages/featured.js +36 -0
  47. package/dist/routes/pages/home.js +33 -49
  48. package/dist/routes/pages/latest.js +45 -0
  49. package/dist/routes/pages/page.js +29 -32
  50. package/dist/routes/pages/post.js +3 -6
  51. package/dist/routes/pages/search.js +3 -6
  52. package/dist/services/page.js +5 -1
  53. package/dist/services/post.js +45 -31
  54. package/dist/services/search.js +1 -1
  55. package/dist/types/bindings.js +3 -0
  56. package/dist/types/config.js +147 -0
  57. package/dist/types/constants.js +27 -0
  58. package/dist/types/entities.js +3 -0
  59. package/dist/types/operations.js +3 -0
  60. package/dist/types/props.js +3 -0
  61. package/dist/types/views.js +5 -0
  62. package/dist/types.js +8 -111
  63. package/dist/{theme → ui}/color-themes.js +33 -33
  64. package/dist/ui/compose/ComposeDialog.js +467 -0
  65. package/dist/ui/compose/ComposePrompt.js +55 -0
  66. package/dist/{theme/components/TypeBadge.js → ui/dash/FormatBadge.js} +1 -2
  67. package/dist/{theme/components → ui/dash}/PageForm.js +21 -15
  68. package/dist/{theme/components → ui/dash}/PostForm.js +22 -43
  69. package/dist/{theme/components → ui/dash}/PostList.js +6 -6
  70. package/dist/{theme/components/VisibilityBadge.js → ui/dash/StatusBadge.js} +1 -2
  71. package/dist/ui/dash/collections/CollectionForm.js +152 -0
  72. package/dist/ui/dash/collections/CollectionsListContent.js +68 -0
  73. package/dist/ui/dash/collections/ViewCollectionContent.js +96 -0
  74. package/dist/{theme/components → ui/dash}/index.js +3 -6
  75. package/dist/ui/dash/media/MediaListContent.js +166 -0
  76. package/dist/ui/dash/media/ViewMediaContent.js +212 -0
  77. package/dist/ui/dash/pages/LinkFormContent.js +130 -0
  78. package/dist/ui/dash/pages/UnifiedPagesContent.js +193 -0
  79. package/dist/ui/dash/settings/AccountContent.js +209 -0
  80. package/dist/ui/dash/settings/AppearanceContent.js +259 -0
  81. package/dist/ui/dash/settings/GeneralContent.js +536 -0
  82. package/dist/ui/dash/settings/SettingsNav.js +41 -0
  83. package/dist/{themes/threads/timeline → ui/feed}/LinkCard.js +6 -2
  84. package/dist/{themes/threads/timeline → ui/feed}/NoteCard.js +11 -6
  85. package/dist/{themes/threads/timeline → ui/feed}/QuoteCard.js +10 -6
  86. package/dist/{themes/threads/timeline → ui/feed}/ThreadPreview.js +7 -9
  87. package/dist/ui/feed/TimelineFeed.js +41 -0
  88. package/dist/ui/feed/TimelineItem.js +27 -0
  89. package/dist/ui/font-themes.js +36 -0
  90. package/dist/{theme → ui}/layouts/BaseLayout.js +34 -2
  91. package/dist/{theme → ui}/layouts/DashLayout.js +0 -8
  92. package/dist/ui/layouts/SiteLayout.js +169 -0
  93. package/dist/{themes/threads → ui}/pages/ArchivePage.js +16 -14
  94. package/dist/{themes/threads → ui}/pages/CollectionPage.js +6 -1
  95. package/dist/ui/pages/CollectionsPage.js +76 -0
  96. package/dist/ui/pages/FeaturedPage.js +24 -0
  97. package/dist/ui/pages/HomePage.js +24 -0
  98. package/dist/{themes/threads → ui}/pages/PostPage.js +13 -8
  99. package/dist/{themes/threads → ui}/pages/SearchPage.js +9 -7
  100. package/dist/{themes/threads → ui}/pages/SinglePage.js +3 -2
  101. package/dist/{theme/components → ui/shared}/MediaGallery.js +1 -1
  102. package/dist/{theme/components → ui/shared}/Pagination.js +41 -2
  103. package/dist/{theme/components → ui/shared}/ThreadView.js +2 -2
  104. package/dist/ui/shared/index.js +5 -0
  105. package/package.json +1 -9
  106. package/src/__tests__/helpers/db.ts +3 -0
  107. package/src/app.tsx +131 -561
  108. package/src/client.ts +1 -0
  109. package/src/db/migrations/0006_rename_slug_to_path.sql +5 -0
  110. package/src/db/migrations/meta/_journal.json +7 -0
  111. package/src/db/schema.ts +1 -1
  112. package/src/i18n/locales/en.po +477 -261
  113. package/src/i18n/locales/en.ts +1 -1
  114. package/src/i18n/locales/zh-Hans.po +477 -261
  115. package/src/i18n/locales/zh-Hans.ts +1 -1
  116. package/src/i18n/locales/zh-Hant.po +477 -261
  117. package/src/i18n/locales/zh-Hant.ts +1 -1
  118. package/src/index.ts +7 -36
  119. package/src/lib/__tests__/config.test.ts +192 -0
  120. package/src/lib/__tests__/favicon.test.ts +151 -0
  121. package/src/lib/__tests__/image.test.ts +2 -6
  122. package/src/lib/__tests__/schemas.test.ts +60 -19
  123. package/src/lib/__tests__/timeline.test.ts +45 -81
  124. package/src/lib/__tests__/timezones.test.ts +61 -0
  125. package/src/lib/__tests__/view.test.ts +15 -9
  126. package/src/lib/avatar-upload.ts +165 -0
  127. package/src/lib/config.ts +47 -0
  128. package/src/lib/constants.ts +19 -10
  129. package/src/lib/favicon.ts +115 -0
  130. package/src/lib/image.ts +13 -21
  131. package/src/lib/media-helpers.ts +2 -2
  132. package/src/lib/nav-reorder.ts +1 -1
  133. package/src/lib/navigation.ts +73 -4
  134. package/src/lib/pagination.ts +50 -0
  135. package/src/lib/render.tsx +22 -15
  136. package/src/lib/schemas.ts +47 -6
  137. package/src/lib/theme.ts +5 -5
  138. package/src/lib/timeline.ts +28 -57
  139. package/src/lib/timezones.ts +325 -0
  140. package/src/lib/view.ts +3 -3
  141. package/src/preset.css +2 -1
  142. package/src/routes/__tests__/compose.test.ts +199 -0
  143. package/src/routes/api/__tests__/collections.test.ts +249 -0
  144. package/src/routes/api/__tests__/nav-items.test.ts +222 -0
  145. package/src/routes/api/__tests__/pages.test.ts +218 -0
  146. package/src/routes/api/__tests__/settings.test.ts +132 -0
  147. package/src/routes/api/collections.ts +143 -0
  148. package/src/routes/api/nav-items.ts +115 -0
  149. package/src/routes/api/pages.ts +101 -0
  150. package/src/routes/api/posts.ts +3 -3
  151. package/src/routes/api/search.ts +2 -2
  152. package/src/routes/api/settings.ts +91 -0
  153. package/src/routes/api/upload.ts +2 -3
  154. package/src/routes/auth/reset.tsx +239 -0
  155. package/src/routes/auth/setup.tsx +189 -0
  156. package/src/routes/auth/signin.tsx +163 -0
  157. package/src/routes/compose.ts +63 -0
  158. package/src/routes/dash/__tests__/pages.test.ts +225 -0
  159. package/src/routes/dash/__tests__/settings-avatar.test.ts +89 -0
  160. package/src/routes/dash/collections.tsx +18 -367
  161. package/src/routes/dash/index.tsx +1 -1
  162. package/src/routes/dash/media.tsx +13 -415
  163. package/src/routes/dash/pages.tsx +131 -98
  164. package/src/routes/dash/posts.tsx +3 -7
  165. package/src/routes/dash/redirects.tsx +22 -16
  166. package/src/routes/dash/settings.tsx +265 -478
  167. package/src/routes/feed/__tests__/rss.test.ts +141 -0
  168. package/src/routes/feed/rss.ts +5 -3
  169. package/src/routes/feed/sitemap.ts +5 -3
  170. package/src/routes/pages/__tests__/collections.test.ts +94 -0
  171. package/src/routes/pages/__tests__/featured.test.ts +94 -0
  172. package/src/routes/pages/archive.tsx +2 -6
  173. package/src/routes/pages/collection.tsx +2 -6
  174. package/src/routes/pages/collections.tsx +36 -0
  175. package/src/routes/pages/featured.tsx +44 -0
  176. package/src/routes/pages/home.tsx +30 -53
  177. package/src/routes/pages/latest.tsx +59 -0
  178. package/src/routes/pages/page.tsx +28 -30
  179. package/src/routes/pages/post.tsx +2 -5
  180. package/src/routes/pages/search.tsx +2 -6
  181. package/src/services/__tests__/page.test.ts +106 -0
  182. package/src/services/__tests__/post.test.ts +114 -15
  183. package/src/services/page.ts +13 -1
  184. package/src/services/post.ts +58 -40
  185. package/src/services/search.ts +2 -2
  186. package/src/styles/components.css +0 -65
  187. package/src/styles/tokens.css +47 -0
  188. package/src/styles/ui.css +475 -0
  189. package/src/types/bindings.ts +30 -0
  190. package/src/types/config.ts +183 -0
  191. package/src/types/constants.ts +26 -0
  192. package/src/types/entities.ts +109 -0
  193. package/src/types/operations.ts +88 -0
  194. package/src/types/props.ts +115 -0
  195. package/src/types/views.ts +172 -0
  196. package/src/types.ts +8 -774
  197. package/src/ui/__tests__/font-themes.test.ts +34 -0
  198. package/src/{theme → ui}/color-themes.ts +34 -34
  199. package/src/ui/compose/ComposeDialog.tsx +414 -0
  200. package/src/ui/compose/ComposePrompt.tsx +55 -0
  201. package/src/{theme/components/TypeBadge.tsx → ui/dash/FormatBadge.tsx} +2 -3
  202. package/src/{theme/components → ui/dash}/PageForm.tsx +25 -19
  203. package/src/{theme/components → ui/dash}/PostForm.tsx +26 -45
  204. package/src/{theme/components → ui/dash}/PostList.tsx +7 -7
  205. package/src/{theme/components/VisibilityBadge.tsx → ui/dash/StatusBadge.tsx} +2 -3
  206. package/src/ui/dash/collections/CollectionForm.tsx +153 -0
  207. package/src/ui/dash/collections/CollectionsListContent.tsx +85 -0
  208. package/src/ui/dash/collections/ViewCollectionContent.tsx +92 -0
  209. package/src/ui/dash/index.ts +10 -0
  210. package/src/ui/dash/media/MediaListContent.tsx +201 -0
  211. package/src/ui/dash/media/ViewMediaContent.tsx +208 -0
  212. package/src/ui/dash/pages/LinkFormContent.tsx +119 -0
  213. package/src/ui/dash/pages/UnifiedPagesContent.tsx +203 -0
  214. package/src/ui/dash/settings/AccountContent.tsx +176 -0
  215. package/src/ui/dash/settings/AppearanceContent.tsx +254 -0
  216. package/src/ui/dash/settings/GeneralContent.tsx +533 -0
  217. package/src/ui/dash/settings/SettingsNav.tsx +56 -0
  218. package/src/{themes/threads/timeline → ui/feed}/LinkCard.tsx +9 -4
  219. package/src/{themes/threads/timeline → ui/feed}/NoteCard.tsx +13 -8
  220. package/src/{themes/threads/timeline → ui/feed}/QuoteCard.tsx +13 -8
  221. package/src/{themes/threads/timeline → ui/feed}/ThreadPreview.tsx +7 -8
  222. package/src/ui/feed/TimelineFeed.tsx +49 -0
  223. package/src/ui/feed/TimelineItem.tsx +45 -0
  224. package/src/ui/font-themes.ts +54 -0
  225. package/src/{theme → ui}/layouts/BaseLayout.tsx +28 -1
  226. package/src/{theme → ui}/layouts/DashLayout.tsx +0 -10
  227. package/src/ui/layouts/SiteLayout.tsx +164 -0
  228. package/src/{themes/threads → ui}/pages/ArchivePage.tsx +22 -17
  229. package/src/{themes/threads → ui}/pages/CollectionPage.tsx +14 -5
  230. package/src/ui/pages/CollectionsPage.tsx +73 -0
  231. package/src/ui/pages/FeaturedPage.tsx +31 -0
  232. package/src/{themes/threads → ui}/pages/HomePage.tsx +11 -15
  233. package/src/{themes/threads → ui}/pages/PostPage.tsx +23 -14
  234. package/src/{themes/threads → ui}/pages/SearchPage.tsx +13 -11
  235. package/src/{themes/threads → ui}/pages/SinglePage.tsx +4 -4
  236. package/src/{theme/components → ui/shared}/MediaGallery.tsx +1 -1
  237. package/src/{theme/components → ui/shared}/Pagination.tsx +67 -4
  238. package/src/{theme/components → ui/shared}/ThreadView.tsx +2 -2
  239. package/src/ui/shared/__tests__/pagination.test.ts +46 -0
  240. package/src/ui/shared/index.ts +12 -0
  241. package/bin/jant.js +0 -185
  242. package/dist/lib/theme-components.js +0 -46
  243. package/dist/routes/dash/navigation.js +0 -289
  244. package/dist/theme/index.js +0 -18
  245. package/dist/theme/layouts/index.js +0 -2
  246. package/dist/themes/threads/ThreadsSiteLayout.js +0 -172
  247. package/dist/themes/threads/index.js +0 -81
  248. package/dist/themes/threads/pages/HomePage.js +0 -25
  249. package/dist/themes/threads/timeline/TimelineFeed.js +0 -58
  250. package/dist/themes/threads/timeline/TimelineItem.js +0 -36
  251. package/dist/themes/threads/timeline/TimelineLoadMore.js +0 -23
  252. package/dist/themes/threads/timeline/groupByDate.js +0 -22
  253. package/dist/themes/threads/timeline/timelineMore.js +0 -107
  254. package/src/lib/__tests__/theme-components.test.ts +0 -105
  255. package/src/lib/theme-components.ts +0 -65
  256. package/src/routes/dash/navigation.tsx +0 -317
  257. package/src/theme/components/index.ts +0 -23
  258. package/src/theme/index.ts +0 -22
  259. package/src/theme/layouts/index.ts +0 -7
  260. package/src/themes/threads/ThreadsSiteLayout.tsx +0 -194
  261. package/src/themes/threads/index.ts +0 -100
  262. package/src/themes/threads/style.css +0 -336
  263. package/src/themes/threads/timeline/TimelineFeed.tsx +0 -62
  264. package/src/themes/threads/timeline/TimelineItem.tsx +0 -67
  265. package/src/themes/threads/timeline/TimelineLoadMore.tsx +0 -35
  266. package/src/themes/threads/timeline/groupByDate.ts +0 -30
  267. package/src/themes/threads/timeline/timelineMore.tsx +0 -130
  268. /package/dist/{theme/components → ui/dash}/ActionButtons.js +0 -0
  269. /package/dist/{theme/components → ui/dash}/CrudPageHeader.js +0 -0
  270. /package/dist/{theme/components → ui/dash}/DangerZone.js +0 -0
  271. /package/dist/{theme/components → ui/dash}/ListItemRow.js +0 -0
  272. /package/dist/{theme/components → ui/shared}/EmptyState.js +0 -0
  273. /package/src/{theme/components → ui/dash}/ActionButtons.tsx +0 -0
  274. /package/src/{theme/components → ui/dash}/CrudPageHeader.tsx +0 -0
  275. /package/src/{theme/components → ui/dash}/DangerZone.tsx +0 -0
  276. /package/src/{theme/components → ui/dash}/ListItemRow.tsx +0 -0
  277. /package/src/{theme/components → ui/shared}/EmptyState.tsx +0 -0
@@ -1,533 +1,49 @@
1
- import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "hono/jsx/jsx-runtime";
1
+ import { jsx as _jsx } from "hono/jsx/jsx-runtime";
2
2
  /**
3
3
  * Dashboard Settings Routes
4
4
  *
5
5
  * Sub-pages: General, Appearance, Account
6
6
  */ import { Hono } from "hono";
7
- import { useLingui as $_useLingui } from "@jant/core/i18n";
8
- import { DashLayout } from "../../theme/layouts/index.js";
7
+ import { DashLayout } from "../../ui/layouts/DashLayout.js";
9
8
  import { sse, dsRedirect, dsToast } from "../../lib/sse.js";
10
- import { getSiteLanguage, getSiteName, getConfigFallback } from "../../lib/config.js";
9
+ import { arrayBufferToBase64 } from "../../lib/favicon.js";
10
+ import { getSiteLanguage, getSiteName, getHomeDefaultView, getTimeZone, getSiteFooter, isNoIndex, getConfigFallback } from "../../lib/config.js";
11
11
  import { SETTINGS_KEYS } from "../../lib/constants.js";
12
12
  import { getAvailableThemes } from "../../lib/theme.js";
13
+ import { getMediaUrl, getPublicUrlForProvider } from "../../lib/image.js";
14
+ import { TIMEZONES } from "../../lib/timezones.js";
15
+ import { BUILTIN_FONT_THEMES } from "../../ui/font-themes.js";
16
+ import { GeneralContent } from "../../ui/dash/settings/GeneralContent.js";
17
+ import { AppearanceContent } from "../../ui/dash/settings/AppearanceContent.js";
18
+ import { AccountContent } from "../../ui/dash/settings/AccountContent.js";
13
19
  /** Escape HTML special characters for safe insertion into HTML strings */ function escapeHtml(str) {
14
20
  return str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
15
21
  }
16
22
  export const settingsRoutes = new Hono();
17
- function SettingsNav({ currentTab }) {
18
- const { i18n: $__i18n, _: $__ } = $_useLingui();
19
- const tabs = [
20
- {
21
- id: "general",
22
- label: $__i18n._({
23
- id: "Weq9zb",
24
- message: "General"
25
- }),
26
- href: "/dash/settings"
27
- },
28
- {
29
- id: "appearance",
30
- label: $__i18n._({
31
- id: "aAIQg2",
32
- message: "Appearance"
33
- }),
34
- href: "/dash/settings/appearance"
35
- },
36
- {
37
- id: "account",
38
- label: $__i18n._({
39
- id: "AeXO77",
40
- message: "Account"
41
- }),
42
- href: "/dash/settings/account"
43
- }
44
- ];
45
- return /*#__PURE__*/ _jsx("nav", {
46
- class: "flex gap-1 mb-6",
47
- children: tabs.map((tab)=>/*#__PURE__*/ _jsx("a", {
48
- href: tab.href,
49
- class: `px-3 py-2 text-sm rounded-md ${tab.id === currentTab ? "bg-accent text-accent-foreground font-medium" : "text-muted-foreground hover:bg-accent hover:text-accent-foreground"}`,
50
- children: tab.label
51
- }, tab.id))
52
- });
53
- }
54
- // ---------------------------------------------------------------------------
55
- // General tab
56
- // ---------------------------------------------------------------------------
57
- function GeneralContent({ siteName, siteDescription, siteLanguage, siteNameFallback, siteDescriptionFallback }) {
58
- const { i18n: $__i18n, _: $__ } = $_useLingui();
59
- const generalSignals = JSON.stringify({
60
- siteName,
61
- siteDescription,
62
- siteLanguage
63
- }).replace(/</g, "\\u003c");
64
- return /*#__PURE__*/ _jsxs(_Fragment, {
65
- children: [
66
- /*#__PURE__*/ _jsx("h1", {
67
- class: "text-2xl font-semibold mb-2",
68
- children: $__i18n._({
69
- id: "Tz0i8g",
70
- message: "Settings"
71
- })
72
- }),
73
- /*#__PURE__*/ _jsx(SettingsNav, {
74
- currentTab: "general"
75
- }),
76
- /*#__PURE__*/ _jsx("div", {
77
- class: "flex flex-col gap-6 max-w-lg",
78
- children: /*#__PURE__*/ _jsxs("form", {
79
- "data-signals": generalSignals,
80
- "data-on:submit__prevent": "@post('/dash/settings')",
81
- "data-indicator": "_loading",
82
- children: [
83
- /*#__PURE__*/ _jsxs("div", {
84
- class: "card",
85
- children: [
86
- /*#__PURE__*/ _jsx("header", {
87
- children: /*#__PURE__*/ _jsx("h2", {
88
- children: $__i18n._({
89
- id: "Weq9zb",
90
- message: "General"
91
- })
92
- })
93
- }),
94
- /*#__PURE__*/ _jsxs("section", {
95
- class: "flex flex-col gap-4",
96
- children: [
97
- /*#__PURE__*/ _jsxs("div", {
98
- class: "field",
99
- children: [
100
- /*#__PURE__*/ _jsx("label", {
101
- class: "label",
102
- children: $__i18n._({
103
- id: "SJmfuf",
104
- message: "Site Name"
105
- })
106
- }),
107
- /*#__PURE__*/ _jsx("input", {
108
- type: "text",
109
- "data-bind": "siteName",
110
- class: "input",
111
- placeholder: siteNameFallback
112
- })
113
- ]
114
- }),
115
- /*#__PURE__*/ _jsxs("div", {
116
- class: "field",
117
- children: [
118
- /*#__PURE__*/ _jsx("label", {
119
- class: "label",
120
- children: $__i18n._({
121
- id: "u2f7vd",
122
- message: "Site Description"
123
- })
124
- }),
125
- /*#__PURE__*/ _jsx("textarea", {
126
- "data-bind": "siteDescription",
127
- class: "textarea",
128
- rows: 3,
129
- placeholder: siteDescriptionFallback,
130
- children: siteDescription
131
- })
132
- ]
133
- }),
134
- /*#__PURE__*/ _jsxs("div", {
135
- class: "field",
136
- children: [
137
- /*#__PURE__*/ _jsx("label", {
138
- class: "label",
139
- children: $__i18n._({
140
- id: "vXIe7J",
141
- message: "Language"
142
- })
143
- }),
144
- /*#__PURE__*/ _jsxs("select", {
145
- "data-bind": "siteLanguage",
146
- class: "select",
147
- children: [
148
- /*#__PURE__*/ _jsx("option", {
149
- value: "en",
150
- selected: siteLanguage === "en",
151
- children: "English"
152
- }),
153
- /*#__PURE__*/ _jsx("option", {
154
- value: "zh-Hans",
155
- selected: siteLanguage === "zh-Hans",
156
- children: "简体中文"
157
- }),
158
- /*#__PURE__*/ _jsx("option", {
159
- value: "zh-Hant",
160
- selected: siteLanguage === "zh-Hant",
161
- children: "繁體中文"
162
- })
163
- ]
164
- })
165
- ]
166
- })
167
- ]
168
- })
169
- ]
170
- }),
171
- /*#__PURE__*/ _jsxs("button", {
172
- type: "submit",
173
- class: "btn mt-4",
174
- "data-attr-disabled": "$_loading",
175
- children: [
176
- /*#__PURE__*/ _jsx("span", {
177
- "data-show": "!$_loading",
178
- children: $__i18n._({
179
- id: "UGT5vp",
180
- message: "Save Settings"
181
- })
182
- }),
183
- /*#__PURE__*/ _jsx("span", {
184
- "data-show": "$_loading",
185
- children: $__i18n._({
186
- id: "k1ifdL",
187
- message: "Processing..."
188
- })
189
- })
190
- ]
191
- })
192
- ]
193
- })
194
- })
195
- ]
196
- });
197
- }
198
- // ---------------------------------------------------------------------------
199
- // Appearance tab
200
- // ---------------------------------------------------------------------------
201
- function ThemeCard({ theme, selected }) {
202
- const expr = `$theme === '${theme.id}'`;
203
- const { preview } = theme;
204
- return /*#__PURE__*/ _jsx("label", {
205
- class: `block cursor-pointer rounded-lg border overflow-hidden transition-colors ${selected ? "border-primary" : "border-border"}`,
206
- "data-class:border-primary": expr,
207
- "data-class:border-border": `$theme !== '${theme.id}'`,
208
- children: /*#__PURE__*/ _jsxs("div", {
209
- class: "grid grid-cols-2",
210
- children: [
211
- /*#__PURE__*/ _jsxs("div", {
212
- class: "p-5",
213
- style: `background-color:${preview.lightBg};color:${preview.lightText}`,
214
- children: [
215
- /*#__PURE__*/ _jsx("input", {
216
- type: "radio",
217
- name: "theme",
218
- value: theme.id,
219
- "data-bind": "theme",
220
- checked: selected || undefined,
221
- class: "mb-1"
222
- }),
223
- /*#__PURE__*/ _jsx("h3", {
224
- class: "font-bold text-lg",
225
- children: theme.name
226
- }),
227
- /*#__PURE__*/ _jsxs("p", {
228
- class: "text-sm mt-2 leading-relaxed",
229
- children: [
230
- "This is the ",
231
- theme.name,
232
- " theme in light mode. Links",
233
- " ",
234
- /*#__PURE__*/ _jsx("a", {
235
- tabIndex: -1,
236
- class: "underline",
237
- style: `color:${preview.lightLink}`,
238
- children: "look like this"
239
- }),
240
- ". We'll show the correct light or dark mode based on your visitor's settings."
241
- ]
242
- })
243
- ]
244
- }),
245
- /*#__PURE__*/ _jsxs("div", {
246
- class: "p-5",
247
- style: `background-color:${preview.darkBg};color:${preview.darkText}`,
248
- children: [
249
- /*#__PURE__*/ _jsx("h3", {
250
- class: "font-bold text-lg",
251
- children: theme.name
252
- }),
253
- /*#__PURE__*/ _jsxs("p", {
254
- class: "text-sm mt-2 leading-relaxed",
255
- children: [
256
- "This is the ",
257
- theme.name,
258
- " theme in dark mode. Links",
259
- " ",
260
- /*#__PURE__*/ _jsx("a", {
261
- tabIndex: -1,
262
- class: "underline",
263
- style: `color:${preview.darkLink}`,
264
- children: "look like this"
265
- }),
266
- ". We'll show the correct light or dark mode based on your visitor's settings."
267
- ]
268
- })
269
- ]
270
- })
271
- ]
272
- })
273
- });
274
- }
275
- function AppearanceContent({ themes, currentThemeId }) {
276
- const { i18n: $__i18n, _: $__ } = $_useLingui();
277
- const signals = JSON.stringify({
278
- theme: currentThemeId
279
- }).replace(/</g, "\\u003c");
280
- return /*#__PURE__*/ _jsxs(_Fragment, {
281
- children: [
282
- /*#__PURE__*/ _jsx("h1", {
283
- class: "text-2xl font-semibold mb-2",
284
- children: $__i18n._({
285
- id: "Tz0i8g",
286
- message: "Settings"
287
- })
288
- }),
289
- /*#__PURE__*/ _jsx(SettingsNav, {
290
- currentTab: "appearance"
291
- }),
292
- /*#__PURE__*/ _jsx("div", {
293
- "data-signals": signals,
294
- "data-on:change": "@post('/dash/settings/appearance')",
295
- class: "max-w-3xl",
296
- children: /*#__PURE__*/ _jsxs("fieldset", {
297
- children: [
298
- /*#__PURE__*/ _jsx("legend", {
299
- class: "text-lg font-semibold",
300
- children: $__i18n._({
301
- id: "rFmBG3",
302
- message: "Color theme"
303
- })
304
- }),
305
- /*#__PURE__*/ _jsx("p", {
306
- class: "text-sm text-muted-foreground mb-4",
307
- children: $__i18n._({
308
- id: "07Epll",
309
- message: "This will theme both your site and your dashboard. All color themes support dark mode."
310
- })
311
- }),
312
- /*#__PURE__*/ _jsx("div", {
313
- class: "flex flex-col gap-4",
314
- children: themes.map((theme)=>/*#__PURE__*/ _jsx(ThemeCard, {
315
- theme: theme,
316
- selected: theme.id === currentThemeId
317
- }, theme.id))
318
- })
319
- ]
320
- })
321
- })
322
- ]
323
- });
324
- }
325
- // ---------------------------------------------------------------------------
326
- // Account tab
327
- // ---------------------------------------------------------------------------
328
- function AccountContent({ userName }) {
329
- const { i18n: $__i18n, _: $__ } = $_useLingui();
330
- const profileSignals = JSON.stringify({
331
- userName
332
- }).replace(/</g, "\\u003c");
333
- return /*#__PURE__*/ _jsxs(_Fragment, {
334
- children: [
335
- /*#__PURE__*/ _jsx("h1", {
336
- class: "text-2xl font-semibold mb-2",
337
- children: $__i18n._({
338
- id: "Tz0i8g",
339
- message: "Settings"
340
- })
341
- }),
342
- /*#__PURE__*/ _jsx(SettingsNav, {
343
- currentTab: "account"
344
- }),
345
- /*#__PURE__*/ _jsxs("div", {
346
- class: "flex flex-col gap-6 max-w-lg",
347
- children: [
348
- /*#__PURE__*/ _jsxs("form", {
349
- "data-signals": profileSignals,
350
- "data-on:submit__prevent": "@post('/dash/settings/account')",
351
- "data-indicator": "_profileLoading",
352
- children: [
353
- /*#__PURE__*/ _jsxs("div", {
354
- class: "card",
355
- children: [
356
- /*#__PURE__*/ _jsx("header", {
357
- children: /*#__PURE__*/ _jsx("h2", {
358
- children: $__i18n._({
359
- id: "vERlcd",
360
- message: "Profile"
361
- })
362
- })
363
- }),
364
- /*#__PURE__*/ _jsx("section", {
365
- class: "flex flex-col gap-4",
366
- children: /*#__PURE__*/ _jsxs("div", {
367
- class: "field",
368
- children: [
369
- /*#__PURE__*/ _jsx("label", {
370
- class: "label",
371
- children: $__i18n._({
372
- id: "6YtxFj",
373
- message: "Name"
374
- })
375
- }),
376
- /*#__PURE__*/ _jsx("input", {
377
- type: "text",
378
- "data-bind": "userName",
379
- class: "input",
380
- required: true
381
- })
382
- ]
383
- })
384
- })
385
- ]
386
- }),
387
- /*#__PURE__*/ _jsxs("button", {
388
- type: "submit",
389
- class: "btn mt-4",
390
- "data-attr-disabled": "$_profileLoading",
391
- children: [
392
- /*#__PURE__*/ _jsx("span", {
393
- "data-show": "!$_profileLoading",
394
- children: $__i18n._({
395
- id: "ssqvZi",
396
- message: "Save Profile"
397
- })
398
- }),
399
- /*#__PURE__*/ _jsx("span", {
400
- "data-show": "$_profileLoading",
401
- children: $__i18n._({
402
- id: "k1ifdL",
403
- message: "Processing..."
404
- })
405
- })
406
- ]
407
- })
408
- ]
409
- }),
410
- /*#__PURE__*/ _jsxs("form", {
411
- "data-signals": "{currentPassword: '', newPassword: '', confirmPassword: ''}",
412
- "data-on:submit__prevent": "@post('/dash/settings/password')",
413
- "data-indicator": "_passwordLoading",
414
- children: [
415
- /*#__PURE__*/ _jsxs("div", {
416
- class: "card",
417
- children: [
418
- /*#__PURE__*/ _jsx("header", {
419
- children: /*#__PURE__*/ _jsx("h2", {
420
- children: $__i18n._({
421
- id: "VhMDMg",
422
- message: "Change Password"
423
- })
424
- })
425
- }),
426
- /*#__PURE__*/ _jsxs("section", {
427
- class: "flex flex-col gap-4",
428
- children: [
429
- /*#__PURE__*/ _jsxs("div", {
430
- class: "field",
431
- children: [
432
- /*#__PURE__*/ _jsx("label", {
433
- class: "label",
434
- children: $__i18n._({
435
- id: "DCKkhU",
436
- message: "Current Password"
437
- })
438
- }),
439
- /*#__PURE__*/ _jsx("input", {
440
- type: "password",
441
- "data-bind": "currentPassword",
442
- class: "input",
443
- required: true,
444
- autocomplete: "current-password"
445
- })
446
- ]
447
- }),
448
- /*#__PURE__*/ _jsxs("div", {
449
- class: "field",
450
- children: [
451
- /*#__PURE__*/ _jsx("label", {
452
- class: "label",
453
- children: $__i18n._({
454
- id: "7vhWI8",
455
- message: "New Password"
456
- })
457
- }),
458
- /*#__PURE__*/ _jsx("input", {
459
- type: "password",
460
- "data-bind": "newPassword",
461
- class: "input",
462
- required: true,
463
- minlength: 8,
464
- autocomplete: "new-password"
465
- })
466
- ]
467
- }),
468
- /*#__PURE__*/ _jsxs("div", {
469
- class: "field",
470
- children: [
471
- /*#__PURE__*/ _jsx("label", {
472
- class: "label",
473
- children: $__i18n._({
474
- id: "yjkELF",
475
- message: "Confirm New Password"
476
- })
477
- }),
478
- /*#__PURE__*/ _jsx("input", {
479
- type: "password",
480
- "data-bind": "confirmPassword",
481
- class: "input",
482
- required: true,
483
- minlength: 8,
484
- autocomplete: "new-password"
485
- })
486
- ]
487
- })
488
- ]
489
- })
490
- ]
491
- }),
492
- /*#__PURE__*/ _jsxs("button", {
493
- type: "submit",
494
- class: "btn mt-4",
495
- "data-attr-disabled": "$_passwordLoading",
496
- children: [
497
- /*#__PURE__*/ _jsx("span", {
498
- "data-show": "!$_passwordLoading",
499
- children: $__i18n._({
500
- id: "VhMDMg",
501
- message: "Change Password"
502
- })
503
- }),
504
- /*#__PURE__*/ _jsx("span", {
505
- "data-show": "$_passwordLoading",
506
- children: $__i18n._({
507
- id: "k1ifdL",
508
- message: "Processing..."
509
- })
510
- })
511
- ]
512
- })
513
- ]
514
- })
515
- ]
516
- })
517
- ]
518
- });
519
- }
520
23
  // ===========================================================================
521
- // Route handlers
24
+ // General settings
522
25
  // ===========================================================================
523
- // General settings page
26
+ /** Resolve the avatar storage key to a URL */ async function resolveAvatarUrl(c) {
27
+ const avatarKey = await c.var.services.settings.get("SITE_AVATAR");
28
+ if (!avatarKey) return "";
29
+ const publicUrl = getPublicUrlForProvider(c.env.STORAGE_DRIVER || "r2", c.env.R2_PUBLIC_URL, c.env.S3_PUBLIC_URL);
30
+ return getMediaUrl(avatarKey, publicUrl);
31
+ }
524
32
  settingsRoutes.get("/", async (c)=>{
525
33
  const { settings } = c.var.services;
526
34
  const dbSiteName = await settings.get("SITE_NAME");
527
35
  const dbSiteDescription = await settings.get("SITE_DESCRIPTION");
528
- const siteLanguage = await getSiteLanguage(c);
36
+ const [siteLanguage, homeDefaultView, timeZone, siteFooter, noindex] = await Promise.all([
37
+ getSiteLanguage(c),
38
+ getHomeDefaultView(c),
39
+ getTimeZone(c),
40
+ getSiteFooter(c),
41
+ isNoIndex(c)
42
+ ]);
529
43
  const siteNameFallback = getConfigFallback(c, "SITE_NAME");
530
44
  const siteDescriptionFallback = getConfigFallback(c, "SITE_DESCRIPTION");
45
+ const siteAvatarUrl = await resolveAvatarUrl(c);
46
+ const showHeaderAvatar = await settings.get("SHOW_HEADER_AVATAR") === "true";
531
47
  const saved = c.req.query("saved") !== undefined;
532
48
  return c.html(/*#__PURE__*/ _jsx(DashLayout, {
533
49
  c: c,
@@ -541,12 +57,18 @@ settingsRoutes.get("/", async (c)=>{
541
57
  siteName: dbSiteName || "",
542
58
  siteDescription: dbSiteDescription || "",
543
59
  siteLanguage: siteLanguage,
60
+ homeDefaultView: homeDefaultView,
544
61
  siteNameFallback: siteNameFallback,
545
- siteDescriptionFallback: siteDescriptionFallback
62
+ siteDescriptionFallback: siteDescriptionFallback,
63
+ siteAvatarUrl: siteAvatarUrl,
64
+ showHeaderAvatar: showHeaderAvatar,
65
+ timeZone: timeZone,
66
+ siteFooter: siteFooter,
67
+ noindex: noindex,
68
+ timezones: TIMEZONES
546
69
  })
547
70
  }));
548
71
  });
549
- // Save general settings
550
72
  settingsRoutes.post("/", async (c)=>{
551
73
  const body = await c.req.json();
552
74
  const { settings } = c.var.services;
@@ -562,6 +84,18 @@ settingsRoutes.post("/", async (c)=>{
562
84
  await settings.remove("SITE_DESCRIPTION");
563
85
  }
564
86
  await settings.set("SITE_LANGUAGE", body.siteLanguage);
87
+ // Save homepage default view (only store if non-default)
88
+ if (body.homeDefaultView === "featured") {
89
+ await settings.set("HOME_DEFAULT_VIEW", body.homeDefaultView);
90
+ } else {
91
+ await settings.remove("HOME_DEFAULT_VIEW");
92
+ }
93
+ // Timezone
94
+ if (body.timeZone && body.timeZone !== "UTC") {
95
+ await settings.set("TIME_ZONE", body.timeZone);
96
+ } else {
97
+ await settings.remove("TIME_ZONE");
98
+ }
565
99
  const languageChanged = oldLanguage !== body.siteLanguage;
566
100
  const displayName = body.siteName.trim() || getConfigFallback(c, "SITE_NAME");
567
101
  return sse(c, async (stream)=>{
@@ -575,14 +109,148 @@ settingsRoutes.post("/", async (c)=>{
575
109
  selector: "title"
576
110
  });
577
111
  await stream.toast("Settings saved successfully.");
112
+ await stream.patchSignals({
113
+ _orig_siteName: body.siteName,
114
+ _orig_siteDescription: body.siteDescription,
115
+ _orig_siteLanguage: body.siteLanguage,
116
+ _orig_homeDefaultView: body.homeDefaultView,
117
+ _orig_timeZone: body.timeZone,
118
+ _generalDirty: false
119
+ });
120
+ }
121
+ });
122
+ });
123
+ settingsRoutes.post("/footer", async (c)=>{
124
+ const body = await c.req.json();
125
+ const { settings } = c.var.services;
126
+ if (body.siteFooter?.trim()) {
127
+ await settings.set("SITE_FOOTER", body.siteFooter.trim());
128
+ } else {
129
+ await settings.remove("SITE_FOOTER");
130
+ }
131
+ return sse(c, async (stream)=>{
132
+ await stream.toast("Footer saved successfully.");
133
+ await stream.patchSignals({
134
+ _orig_siteFooter: body.siteFooter,
135
+ _footerDirty: false
136
+ });
137
+ });
138
+ });
139
+ settingsRoutes.post("/seo", async (c)=>{
140
+ const body = await c.req.json();
141
+ const { settings } = c.var.services;
142
+ // Checkbox "noindex" is the allow-indexing signal:
143
+ // checked (value "true") = indexing allowed -> remove NOINDEX
144
+ // unchecked (value "") = indexing blocked -> set NOINDEX=true
145
+ if (body.noindex === "true") {
146
+ await settings.remove("NOINDEX");
147
+ } else {
148
+ await settings.set("NOINDEX", "true");
149
+ }
150
+ return sse(c, async (stream)=>{
151
+ await stream.toast("SEO settings saved successfully.");
152
+ await stream.patchSignals({
153
+ _orig_noindex: body.noindex,
154
+ _seoDirty: false
155
+ });
156
+ });
157
+ });
158
+ // ===========================================================================
159
+ // Avatar upload & removal
160
+ // ===========================================================================
161
+ settingsRoutes.post("/avatar", async (c)=>{
162
+ const storage = c.var.storage;
163
+ if (!storage) {
164
+ return dsToast("Storage not configured.", "error");
165
+ }
166
+ const formData = await c.req.formData();
167
+ const file = formData.get("file");
168
+ if (!file) {
169
+ return dsToast("No file provided.", "error");
170
+ }
171
+ const allowedTypes = [
172
+ "image/jpeg",
173
+ "image/png",
174
+ "image/gif",
175
+ "image/webp",
176
+ "image/svg+xml"
177
+ ];
178
+ if (!allowedTypes.includes(file.type)) {
179
+ return dsToast("File type not allowed.", "error");
180
+ }
181
+ const maxSize = 10 * 1024 * 1024;
182
+ if (file.size > maxSize) {
183
+ return dsToast("File too large (max 10MB).", "error");
184
+ }
185
+ const { uuidv7 } = await import("uuidv7");
186
+ const ext = file.name.split(".").pop() || "bin";
187
+ const id = uuidv7();
188
+ const date = new Date();
189
+ const year = date.getUTCFullYear();
190
+ const month = String(date.getUTCMonth() + 1).padStart(2, "0");
191
+ const filename = `${id}.${ext}`;
192
+ const storageKey = `media/${year}/${month}/${filename}`;
193
+ try {
194
+ await storage.put(storageKey, file.stream(), {
195
+ contentType: file.type
196
+ });
197
+ await c.var.services.media.create({
198
+ id,
199
+ filename,
200
+ originalName: file.name,
201
+ mimeType: file.type,
202
+ size: file.size,
203
+ storageKey,
204
+ provider: c.env.STORAGE_DRIVER || "r2"
205
+ });
206
+ await c.var.services.settings.set("SITE_AVATAR", storageKey);
207
+ // Store favicon variants as base64 in settings (small files, accessed every page load)
208
+ const faviconFile = formData.get("favicon");
209
+ const appleTouchFile = formData.get("appleTouch");
210
+ if (faviconFile) {
211
+ const b64 = arrayBufferToBase64(await faviconFile.arrayBuffer());
212
+ await c.var.services.settings.set("SITE_FAVICON_ICO", b64);
213
+ }
214
+ if (appleTouchFile) {
215
+ const b64 = arrayBufferToBase64(await appleTouchFile.arrayBuffer());
216
+ await c.var.services.settings.set("SITE_FAVICON_APPLE_TOUCH", b64);
578
217
  }
218
+ return dsRedirect("/dash/settings?saved");
219
+ } catch {
220
+ return dsToast("Upload failed. Please try again.", "error");
221
+ }
222
+ });
223
+ settingsRoutes.post("/avatar/remove", async (c)=>{
224
+ await c.var.services.settings.remove("SITE_AVATAR");
225
+ await c.var.services.settings.remove("SITE_FAVICON_ICO");
226
+ await c.var.services.settings.remove("SITE_FAVICON_APPLE_TOUCH");
227
+ return dsRedirect("/dash/settings?saved");
228
+ });
229
+ settingsRoutes.post("/avatar/display", async (c)=>{
230
+ const body = await c.req.json();
231
+ const { settings } = c.var.services;
232
+ if (body.showHeaderAvatar === "true") {
233
+ await settings.set("SHOW_HEADER_AVATAR", "true");
234
+ } else {
235
+ await settings.remove("SHOW_HEADER_AVATAR");
236
+ }
237
+ return sse(c, async (stream)=>{
238
+ await stream.toast("Avatar display setting saved successfully.");
239
+ await stream.patchSignals({
240
+ _orig_showHeaderAvatar: body.showHeaderAvatar,
241
+ _avatarDisplayDirty: false
242
+ });
579
243
  });
580
244
  });
581
- // Appearance page
245
+ // ===========================================================================
246
+ // Appearance
247
+ // ===========================================================================
582
248
  settingsRoutes.get("/appearance", async (c)=>{
583
249
  const { settings } = c.var.services;
584
250
  const siteName = await getSiteName(c);
585
251
  const currentThemeId = await settings.get(SETTINGS_KEYS.THEME) ?? "default";
252
+ const currentFontThemeId = await settings.get("FONT_THEME") ?? "default";
253
+ const customCSS = await settings.get(SETTINGS_KEYS.CUSTOM_CSS) ?? "";
586
254
  const themes = getAvailableThemes(c.var.config);
587
255
  const saved = c.req.query("saved") !== undefined;
588
256
  return c.html(/*#__PURE__*/ _jsx(DashLayout, {
@@ -595,11 +263,13 @@ settingsRoutes.get("/appearance", async (c)=>{
595
263
  } : undefined,
596
264
  children: /*#__PURE__*/ _jsx(AppearanceContent, {
597
265
  themes: themes,
598
- currentThemeId: currentThemeId
266
+ currentThemeId: currentThemeId,
267
+ fontThemes: BUILTIN_FONT_THEMES,
268
+ currentFontThemeId: currentFontThemeId,
269
+ customCSS: customCSS
599
270
  })
600
271
  }));
601
272
  });
602
- // Save theme
603
273
  settingsRoutes.post("/appearance", async (c)=>{
604
274
  const body = await c.req.json();
605
275
  const { settings } = c.var.services;
@@ -615,7 +285,34 @@ settingsRoutes.post("/appearance", async (c)=>{
615
285
  }
616
286
  return dsRedirect("/dash/settings/appearance?saved");
617
287
  });
618
- // Account page
288
+ settingsRoutes.post("/font-theme", async (c)=>{
289
+ const body = await c.req.json();
290
+ const { settings } = c.var.services;
291
+ const validFont = BUILTIN_FONT_THEMES.find((f)=>f.id === body.fontTheme);
292
+ if (!validFont) {
293
+ return dsToast("Invalid font theme selected.", "error");
294
+ }
295
+ if (validFont.id === "default") {
296
+ await settings.remove("FONT_THEME");
297
+ } else {
298
+ await settings.set("FONT_THEME", validFont.id);
299
+ }
300
+ return dsRedirect("/dash/settings/appearance?saved");
301
+ });
302
+ settingsRoutes.post("/custom-css", async (c)=>{
303
+ const body = await c.req.json();
304
+ const { settings } = c.var.services;
305
+ const css = body.customCSS?.trim() ?? "";
306
+ if (css) {
307
+ await settings.set(SETTINGS_KEYS.CUSTOM_CSS, css);
308
+ } else {
309
+ await settings.remove(SETTINGS_KEYS.CUSTOM_CSS);
310
+ }
311
+ return dsToast("Custom CSS saved successfully.");
312
+ });
313
+ // ===========================================================================
314
+ // Account
315
+ // ===========================================================================
619
316
  settingsRoutes.get("/account", async (c)=>{
620
317
  const siteName = await getSiteName(c);
621
318
  const session = await c.var.auth.api.getSession({
@@ -636,7 +333,6 @@ settingsRoutes.get("/account", async (c)=>{
636
333
  })
637
334
  }));
638
335
  });
639
- // Save account profile
640
336
  settingsRoutes.post("/account", async (c)=>{
641
337
  const body = await c.req.json();
642
338
  const name = body.userName?.trim();
@@ -655,7 +351,6 @@ settingsRoutes.post("/account", async (c)=>{
655
351
  }
656
352
  return dsToast("Profile saved successfully.");
657
353
  });
658
- // Change password
659
354
  settingsRoutes.post("/password", async (c)=>{
660
355
  const body = await c.req.json();
661
356
  if (body.newPassword !== body.confirmPassword) {