@jant/core 0.3.25 → 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 (131) hide show
  1. package/dist/app.js +67 -562
  2. package/dist/client.js +1 -0
  3. package/dist/i18n/locales/en.js +1 -1
  4. package/dist/i18n/locales/zh-Hans.js +1 -1
  5. package/dist/i18n/locales/zh-Hant.js +1 -1
  6. package/dist/lib/avatar-upload.js +134 -0
  7. package/dist/lib/config.js +39 -0
  8. package/dist/lib/constants.js +10 -10
  9. package/dist/lib/favicon.js +102 -0
  10. package/dist/lib/image.js +13 -17
  11. package/dist/lib/media-helpers.js +2 -2
  12. package/dist/lib/navigation.js +23 -3
  13. package/dist/lib/render.js +10 -1
  14. package/dist/lib/schemas.js +31 -0
  15. package/dist/lib/timezones.js +388 -0
  16. package/dist/lib/view.js +1 -1
  17. package/dist/routes/api/posts.js +1 -1
  18. package/dist/routes/api/upload.js +3 -3
  19. package/dist/routes/auth/reset.js +221 -0
  20. package/dist/routes/auth/setup.js +194 -0
  21. package/dist/routes/auth/signin.js +176 -0
  22. package/dist/routes/dash/collections.js +23 -415
  23. package/dist/routes/dash/media.js +12 -392
  24. package/dist/routes/dash/pages.js +7 -330
  25. package/dist/routes/dash/redirects.js +18 -12
  26. package/dist/routes/dash/settings.js +198 -577
  27. package/dist/routes/feed/rss.js +2 -1
  28. package/dist/routes/feed/sitemap.js +4 -2
  29. package/dist/routes/pages/featured.js +5 -1
  30. package/dist/routes/pages/home.js +26 -1
  31. package/dist/routes/pages/latest.js +45 -0
  32. package/dist/services/post.js +30 -50
  33. package/dist/types/bindings.js +3 -0
  34. package/dist/types/config.js +147 -0
  35. package/dist/types/constants.js +27 -0
  36. package/dist/types/entities.js +3 -0
  37. package/dist/types/operations.js +3 -0
  38. package/dist/types/props.js +3 -0
  39. package/dist/types/views.js +5 -0
  40. package/dist/types.js +8 -111
  41. package/dist/ui/color-themes.js +33 -33
  42. package/dist/ui/compose/ComposeDialog.js +36 -21
  43. package/dist/ui/dash/PageForm.js +21 -15
  44. package/dist/ui/dash/PostForm.js +22 -16
  45. package/dist/ui/dash/collections/CollectionForm.js +152 -0
  46. package/dist/ui/dash/collections/CollectionsListContent.js +68 -0
  47. package/dist/ui/dash/collections/ViewCollectionContent.js +96 -0
  48. package/dist/ui/dash/media/MediaListContent.js +166 -0
  49. package/dist/ui/dash/media/ViewMediaContent.js +212 -0
  50. package/dist/ui/dash/pages/LinkFormContent.js +130 -0
  51. package/dist/ui/dash/pages/UnifiedPagesContent.js +193 -0
  52. package/dist/ui/dash/settings/AccountContent.js +209 -0
  53. package/dist/ui/dash/settings/AppearanceContent.js +259 -0
  54. package/dist/ui/dash/settings/GeneralContent.js +536 -0
  55. package/dist/ui/dash/settings/SettingsNav.js +41 -0
  56. package/dist/ui/font-themes.js +36 -0
  57. package/dist/ui/layouts/BaseLayout.js +24 -2
  58. package/dist/ui/layouts/SiteLayout.js +47 -19
  59. package/package.json +1 -1
  60. package/src/app.tsx +93 -553
  61. package/src/client.ts +1 -0
  62. package/src/i18n/locales/en.po +240 -175
  63. package/src/i18n/locales/en.ts +1 -1
  64. package/src/i18n/locales/zh-Hans.po +240 -175
  65. package/src/i18n/locales/zh-Hans.ts +1 -1
  66. package/src/i18n/locales/zh-Hant.po +240 -175
  67. package/src/i18n/locales/zh-Hant.ts +1 -1
  68. package/src/lib/__tests__/config.test.ts +192 -0
  69. package/src/lib/__tests__/favicon.test.ts +151 -0
  70. package/src/lib/__tests__/image.test.ts +2 -6
  71. package/src/lib/__tests__/timezones.test.ts +61 -0
  72. package/src/lib/__tests__/view.test.ts +2 -2
  73. package/src/lib/avatar-upload.ts +165 -0
  74. package/src/lib/config.ts +47 -0
  75. package/src/lib/constants.ts +19 -11
  76. package/src/lib/favicon.ts +115 -0
  77. package/src/lib/image.ts +13 -21
  78. package/src/lib/media-helpers.ts +2 -2
  79. package/src/lib/navigation.ts +33 -2
  80. package/src/lib/render.tsx +15 -1
  81. package/src/lib/schemas.ts +39 -0
  82. package/src/lib/timezones.ts +325 -0
  83. package/src/lib/view.ts +1 -1
  84. package/src/routes/api/posts.ts +1 -1
  85. package/src/routes/api/upload.ts +2 -3
  86. package/src/routes/auth/reset.tsx +239 -0
  87. package/src/routes/auth/setup.tsx +189 -0
  88. package/src/routes/auth/signin.tsx +163 -0
  89. package/src/routes/dash/__tests__/settings-avatar.test.ts +89 -0
  90. package/src/routes/dash/collections.tsx +17 -366
  91. package/src/routes/dash/media.tsx +12 -414
  92. package/src/routes/dash/pages.tsx +8 -348
  93. package/src/routes/dash/redirects.tsx +20 -14
  94. package/src/routes/dash/settings.tsx +243 -534
  95. package/src/routes/feed/__tests__/rss.test.ts +141 -0
  96. package/src/routes/feed/rss.ts +3 -1
  97. package/src/routes/feed/sitemap.ts +4 -2
  98. package/src/routes/pages/featured.tsx +7 -1
  99. package/src/routes/pages/home.tsx +25 -2
  100. package/src/routes/pages/latest.tsx +59 -0
  101. package/src/services/post.ts +34 -66
  102. package/src/styles/components.css +0 -65
  103. package/src/styles/tokens.css +1 -1
  104. package/src/styles/ui.css +24 -40
  105. package/src/types/bindings.ts +30 -0
  106. package/src/types/config.ts +183 -0
  107. package/src/types/constants.ts +26 -0
  108. package/src/types/entities.ts +109 -0
  109. package/src/types/operations.ts +88 -0
  110. package/src/types/props.ts +115 -0
  111. package/src/types/views.ts +172 -0
  112. package/src/types.ts +8 -644
  113. package/src/ui/__tests__/font-themes.test.ts +34 -0
  114. package/src/ui/color-themes.ts +34 -34
  115. package/src/ui/compose/ComposeDialog.tsx +40 -21
  116. package/src/ui/dash/PageForm.tsx +25 -19
  117. package/src/ui/dash/PostForm.tsx +26 -20
  118. package/src/ui/dash/collections/CollectionForm.tsx +153 -0
  119. package/src/ui/dash/collections/CollectionsListContent.tsx +85 -0
  120. package/src/ui/dash/collections/ViewCollectionContent.tsx +92 -0
  121. package/src/ui/dash/media/MediaListContent.tsx +201 -0
  122. package/src/ui/dash/media/ViewMediaContent.tsx +208 -0
  123. package/src/ui/dash/pages/LinkFormContent.tsx +119 -0
  124. package/src/ui/dash/pages/UnifiedPagesContent.tsx +203 -0
  125. package/src/ui/dash/settings/AccountContent.tsx +176 -0
  126. package/src/ui/dash/settings/AppearanceContent.tsx +254 -0
  127. package/src/ui/dash/settings/GeneralContent.tsx +533 -0
  128. package/src/ui/dash/settings/SettingsNav.tsx +56 -0
  129. package/src/ui/font-themes.ts +54 -0
  130. package/src/ui/layouts/BaseLayout.tsx +17 -0
  131. package/src/ui/layouts/SiteLayout.tsx +45 -31
@@ -0,0 +1,536 @@
1
+ /**
2
+ * General settings form
3
+ */ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "hono/jsx/jsx-runtime";
4
+ import { useLingui as $_useLingui } from "@jant/core/i18n";
5
+ import { SettingsNav } from "./SettingsNav.js";
6
+ /**
7
+ * Build data-signals JSON with `_orig_<key>` duplicates for cancel/reset.
8
+ * Private `_orig_*` signals store original values so Cancel can revert.
9
+ * The `dirty` signal tracks whether the user has made any changes.
10
+ */ function buildSignals(fields, dirty) {
11
+ const signals = {};
12
+ for (const [key, value] of Object.entries(fields)){
13
+ signals[key] = value;
14
+ signals[`_orig_${key}`] = value;
15
+ }
16
+ signals[dirty] = false;
17
+ return JSON.stringify(signals).replace(/</g, "\\u003c");
18
+ }
19
+ /** Spinner SVG shown inside buttons during loading */ function Spinner({ show }) {
20
+ return /*#__PURE__*/ _jsx("svg", {
21
+ "data-show": show,
22
+ style: "display:none",
23
+ class: "animate-spin size-4",
24
+ xmlns: "http://www.w3.org/2000/svg",
25
+ viewBox: "0 0 24 24",
26
+ fill: "none",
27
+ stroke: "currentColor",
28
+ "stroke-width": "2",
29
+ "stroke-linecap": "round",
30
+ "stroke-linejoin": "round",
31
+ role: "status",
32
+ children: /*#__PURE__*/ _jsx("path", {
33
+ d: "M21 12a9 9 0 1 1-6.219-8.56"
34
+ })
35
+ });
36
+ }
37
+ /**
38
+ * Save + Cancel button pair.
39
+ * Both are disabled when no changes (`!dirty`) or during loading.
40
+ * Cancel resets all signals to originals and clears dirty.
41
+ */ function FormActions({ indicator, dirty, fields }) {
42
+ const { i18n: $__i18n, _: $__ } = $_useLingui();
43
+ const resetExpr = [
44
+ ...fields.map((f)=>`$${f} = $_orig_${f}`),
45
+ `$${dirty} = false`
46
+ ].join("; ");
47
+ return /*#__PURE__*/ _jsxs("div", {
48
+ class: "flex gap-2 mt-4",
49
+ children: [
50
+ /*#__PURE__*/ _jsxs("button", {
51
+ type: "submit",
52
+ class: "btn",
53
+ disabled: true,
54
+ "data-attr:disabled": `$${indicator} || !$${dirty}`,
55
+ children: [
56
+ /*#__PURE__*/ _jsx(Spinner, {
57
+ show: `$${indicator}`
58
+ }),
59
+ $__i18n._({
60
+ id: "tfDRzk",
61
+ message: "Save"
62
+ })
63
+ ]
64
+ }),
65
+ /*#__PURE__*/ _jsx("button", {
66
+ type: "button",
67
+ class: "btn-outline",
68
+ disabled: true,
69
+ "data-attr:disabled": `$${indicator} || !$${dirty}`,
70
+ "data-on:click": resetExpr,
71
+ children: $__i18n._({
72
+ id: "dEgA5A",
73
+ message: "Cancel"
74
+ })
75
+ })
76
+ ]
77
+ });
78
+ }
79
+ export function GeneralContent({ siteName, siteDescription, siteLanguage, homeDefaultView, siteNameFallback, siteDescriptionFallback, siteAvatarUrl, showHeaderAvatar, timeZone, siteFooter, noindex, timezones }) {
80
+ const { i18n: $__i18n, _: $__ } = $_useLingui();
81
+ const generalSignals = buildSignals({
82
+ siteName,
83
+ siteDescription,
84
+ siteLanguage,
85
+ homeDefaultView,
86
+ timeZone
87
+ }, "_generalDirty");
88
+ const footerSignals = buildSignals({
89
+ siteFooter
90
+ }, "_footerDirty");
91
+ const seoSignals = buildSignals({
92
+ noindex: noindex ? "" : "true"
93
+ }, "_seoDirty");
94
+ const avatarSignals = buildSignals({
95
+ showHeaderAvatar: showHeaderAvatar ? "true" : ""
96
+ }, "_avatarDisplayDirty");
97
+ return /*#__PURE__*/ _jsxs(_Fragment, {
98
+ children: [
99
+ /*#__PURE__*/ _jsx("h1", {
100
+ class: "text-2xl font-semibold mb-2",
101
+ children: $__i18n._({
102
+ id: "Tz0i8g",
103
+ message: "Settings"
104
+ })
105
+ }),
106
+ /*#__PURE__*/ _jsx(SettingsNav, {
107
+ currentTab: "general"
108
+ }),
109
+ /*#__PURE__*/ _jsxs("div", {
110
+ class: "flex flex-col gap-6 max-w-lg",
111
+ children: [
112
+ /*#__PURE__*/ _jsxs("div", {
113
+ class: "card",
114
+ children: [
115
+ /*#__PURE__*/ _jsx("header", {
116
+ children: /*#__PURE__*/ _jsx("h2", {
117
+ children: $__i18n._({
118
+ id: "PZ7HJ8",
119
+ message: "Blog Avatar"
120
+ })
121
+ })
122
+ }),
123
+ /*#__PURE__*/ _jsxs("section", {
124
+ class: "flex flex-col gap-4",
125
+ children: [
126
+ /*#__PURE__*/ _jsxs("div", {
127
+ class: "flex items-center gap-4",
128
+ children: [
129
+ siteAvatarUrl ? /*#__PURE__*/ _jsx("img", {
130
+ src: siteAvatarUrl,
131
+ alt: "",
132
+ class: "rounded-full object-cover",
133
+ style: "width:64px;height:64px"
134
+ }) : /*#__PURE__*/ _jsx("div", {
135
+ class: "rounded-full bg-muted flex items-center justify-center text-muted-foreground",
136
+ style: "width:64px;height:64px",
137
+ children: /*#__PURE__*/ _jsxs("svg", {
138
+ xmlns: "http://www.w3.org/2000/svg",
139
+ width: "24",
140
+ height: "24",
141
+ viewBox: "0 0 24 24",
142
+ fill: "none",
143
+ stroke: "currentColor",
144
+ "stroke-width": "2",
145
+ "stroke-linecap": "round",
146
+ "stroke-linejoin": "round",
147
+ children: [
148
+ /*#__PURE__*/ _jsx("rect", {
149
+ width: "18",
150
+ height: "18",
151
+ x: "3",
152
+ y: "3",
153
+ rx: "2",
154
+ ry: "2"
155
+ }),
156
+ /*#__PURE__*/ _jsx("circle", {
157
+ cx: "9",
158
+ cy: "9",
159
+ r: "2"
160
+ }),
161
+ /*#__PURE__*/ _jsx("path", {
162
+ d: "m21 15-3.086-3.086a2 2 0 0 0-2.828 0L6 21"
163
+ })
164
+ ]
165
+ })
166
+ }),
167
+ /*#__PURE__*/ _jsxs("div", {
168
+ class: "flex flex-col gap-2",
169
+ children: [
170
+ /*#__PURE__*/ _jsx("form", {
171
+ action: "/dash/settings/avatar",
172
+ method: "post",
173
+ enctype: "multipart/form-data",
174
+ class: "inline",
175
+ children: /*#__PURE__*/ _jsxs("label", {
176
+ class: "btn text-sm cursor-pointer",
177
+ children: [
178
+ $__i18n._({
179
+ id: "jUV7CU",
180
+ message: "Upload Avatar"
181
+ }),
182
+ /*#__PURE__*/ _jsx("input", {
183
+ type: "file",
184
+ name: "file",
185
+ accept: "image/jpeg,image/png,image/gif,image/webp,image/svg+xml",
186
+ class: "hidden",
187
+ "data-avatar-upload": true,
188
+ "data-text-processing": $__i18n._({
189
+ id: "k1ifdL",
190
+ message: "Processing..."
191
+ }),
192
+ "data-text-uploading": $__i18n._({
193
+ id: "GxkJXS",
194
+ message: "Uploading..."
195
+ }),
196
+ "data-text-error": $__i18n._({
197
+ id: "pZq3aX",
198
+ message: "Upload failed. Please try again."
199
+ })
200
+ })
201
+ ]
202
+ })
203
+ }),
204
+ siteAvatarUrl && /*#__PURE__*/ _jsx("form", {
205
+ "data-on:submit__prevent": "@post('/dash/settings/avatar/remove')",
206
+ "data-indicator": "_removeAvatarLoading",
207
+ children: /*#__PURE__*/ _jsx("button", {
208
+ type: "submit",
209
+ class: "btn-outline text-sm",
210
+ "data-attr:disabled": "$_removeAvatarLoading",
211
+ children: $__i18n._({
212
+ id: "t/YqKh",
213
+ message: "Remove"
214
+ })
215
+ })
216
+ })
217
+ ]
218
+ })
219
+ ]
220
+ }),
221
+ /*#__PURE__*/ _jsx("p", {
222
+ class: "text-sm text-muted-foreground",
223
+ children: $__i18n._({
224
+ id: "dmCcPs",
225
+ message: "This is used for your favicon and apple-touch-icon. For best results, upload a square image at least 180x180 pixels."
226
+ })
227
+ }),
228
+ /*#__PURE__*/ _jsxs("form", {
229
+ "data-signals": avatarSignals,
230
+ "data-on:submit__prevent": "@post('/dash/settings/avatar/display')",
231
+ "data-indicator": "_avatarDisplayLoading",
232
+ children: [
233
+ /*#__PURE__*/ _jsxs("label", {
234
+ class: "flex items-center gap-2 cursor-pointer",
235
+ children: [
236
+ /*#__PURE__*/ _jsx("input", {
237
+ type: "checkbox",
238
+ class: "checkbox",
239
+ "data-bind": "showHeaderAvatar",
240
+ "data-on:change": "$_avatarDisplayDirty = true",
241
+ checked: showHeaderAvatar || undefined,
242
+ value: "true"
243
+ }),
244
+ /*#__PURE__*/ _jsx("span", {
245
+ children: $__i18n._({
246
+ id: "Uj/btJ",
247
+ message: "Display avatar in my site header"
248
+ })
249
+ })
250
+ ]
251
+ }),
252
+ /*#__PURE__*/ _jsx(FormActions, {
253
+ indicator: "_avatarDisplayLoading",
254
+ dirty: "_avatarDisplayDirty",
255
+ fields: [
256
+ "showHeaderAvatar"
257
+ ]
258
+ })
259
+ ]
260
+ })
261
+ ]
262
+ })
263
+ ]
264
+ }),
265
+ /*#__PURE__*/ _jsx("form", {
266
+ "data-signals": generalSignals,
267
+ "data-on:submit__prevent": "@post('/dash/settings')",
268
+ "data-indicator": "_generalLoading",
269
+ children: /*#__PURE__*/ _jsxs("div", {
270
+ class: "card",
271
+ children: [
272
+ /*#__PURE__*/ _jsx("header", {
273
+ children: /*#__PURE__*/ _jsx("h2", {
274
+ children: $__i18n._({
275
+ id: "Weq9zb",
276
+ message: "General"
277
+ })
278
+ })
279
+ }),
280
+ /*#__PURE__*/ _jsxs("section", {
281
+ class: "flex flex-col gap-4",
282
+ children: [
283
+ /*#__PURE__*/ _jsxs("div", {
284
+ class: "field",
285
+ children: [
286
+ /*#__PURE__*/ _jsx("label", {
287
+ class: "label",
288
+ children: $__i18n._({
289
+ id: "SJmfuf",
290
+ message: "Site Name"
291
+ })
292
+ }),
293
+ /*#__PURE__*/ _jsx("input", {
294
+ type: "text",
295
+ "data-bind": "siteName",
296
+ "data-on:input": "$_generalDirty = true",
297
+ class: "input",
298
+ placeholder: siteNameFallback
299
+ })
300
+ ]
301
+ }),
302
+ /*#__PURE__*/ _jsxs("div", {
303
+ class: "field",
304
+ children: [
305
+ /*#__PURE__*/ _jsx("label", {
306
+ class: "label",
307
+ children: $__i18n._({
308
+ id: "anibOb",
309
+ message: "About this blog"
310
+ })
311
+ }),
312
+ /*#__PURE__*/ _jsx("textarea", {
313
+ "data-bind": "siteDescription",
314
+ "data-on:input": "$_generalDirty = true",
315
+ class: "textarea",
316
+ rows: 3,
317
+ placeholder: siteDescriptionFallback,
318
+ children: siteDescription
319
+ }),
320
+ /*#__PURE__*/ _jsx("p", {
321
+ class: "text-sm text-muted-foreground mt-1",
322
+ children: $__i18n._({
323
+ id: "N0APCr",
324
+ message: "This is displayed above your blog posts on your default home page. This is also used for the meta description on your home page."
325
+ })
326
+ })
327
+ ]
328
+ }),
329
+ /*#__PURE__*/ _jsxs("div", {
330
+ class: "field",
331
+ children: [
332
+ /*#__PURE__*/ _jsx("label", {
333
+ class: "label",
334
+ children: $__i18n._({
335
+ id: "vXIe7J",
336
+ message: "Language"
337
+ })
338
+ }),
339
+ /*#__PURE__*/ _jsxs("select", {
340
+ "data-bind": "siteLanguage",
341
+ "data-on:change": "$_generalDirty = true",
342
+ class: "select",
343
+ children: [
344
+ /*#__PURE__*/ _jsx("option", {
345
+ value: "en",
346
+ selected: siteLanguage === "en",
347
+ children: "English"
348
+ }),
349
+ /*#__PURE__*/ _jsx("option", {
350
+ value: "zh-Hans",
351
+ selected: siteLanguage === "zh-Hans",
352
+ children: "简体中文"
353
+ }),
354
+ /*#__PURE__*/ _jsx("option", {
355
+ value: "zh-Hant",
356
+ selected: siteLanguage === "zh-Hant",
357
+ children: "繁體中文"
358
+ })
359
+ ]
360
+ })
361
+ ]
362
+ }),
363
+ /*#__PURE__*/ _jsxs("div", {
364
+ class: "field",
365
+ children: [
366
+ /*#__PURE__*/ _jsx("label", {
367
+ class: "label",
368
+ children: $__i18n._({
369
+ id: "TNFigk",
370
+ message: "Default Homepage View"
371
+ })
372
+ }),
373
+ /*#__PURE__*/ _jsxs("select", {
374
+ "data-bind": "homeDefaultView",
375
+ "data-on:change": "$_generalDirty = true",
376
+ class: "select",
377
+ children: [
378
+ /*#__PURE__*/ _jsx("option", {
379
+ value: "latest",
380
+ selected: homeDefaultView === "latest",
381
+ children: $__i18n._({
382
+ id: "wL3cK8",
383
+ message: "Latest"
384
+ })
385
+ }),
386
+ /*#__PURE__*/ _jsx("option", {
387
+ value: "featured",
388
+ selected: homeDefaultView === "featured",
389
+ children: $__i18n._({
390
+ id: "FkMol5",
391
+ message: "Featured"
392
+ })
393
+ })
394
+ ]
395
+ })
396
+ ]
397
+ }),
398
+ /*#__PURE__*/ _jsxs("div", {
399
+ class: "field",
400
+ children: [
401
+ /*#__PURE__*/ _jsx("label", {
402
+ class: "label",
403
+ children: $__i18n._({
404
+ id: "RxsRD6",
405
+ message: "Time Zone"
406
+ })
407
+ }),
408
+ /*#__PURE__*/ _jsx("select", {
409
+ "data-bind": "timeZone",
410
+ "data-on:change": "$_generalDirty = true",
411
+ class: "select",
412
+ children: timezones.map((tz)=>/*#__PURE__*/ _jsx("option", {
413
+ value: tz.value,
414
+ selected: timeZone === tz.value,
415
+ children: tz.label
416
+ }, tz.value))
417
+ })
418
+ ]
419
+ }),
420
+ /*#__PURE__*/ _jsx(FormActions, {
421
+ indicator: "_generalLoading",
422
+ dirty: "_generalDirty",
423
+ fields: [
424
+ "siteName",
425
+ "siteDescription",
426
+ "siteLanguage",
427
+ "homeDefaultView",
428
+ "timeZone"
429
+ ]
430
+ })
431
+ ]
432
+ })
433
+ ]
434
+ })
435
+ }),
436
+ /*#__PURE__*/ _jsx("form", {
437
+ "data-signals": footerSignals,
438
+ "data-on:submit__prevent": "@post('/dash/settings/footer')",
439
+ "data-indicator": "_footerLoading",
440
+ children: /*#__PURE__*/ _jsxs("div", {
441
+ class: "card",
442
+ children: [
443
+ /*#__PURE__*/ _jsx("header", {
444
+ children: /*#__PURE__*/ _jsx("h2", {
445
+ children: $__i18n._({
446
+ id: "2cFU6q",
447
+ message: "Site Footer"
448
+ })
449
+ })
450
+ }),
451
+ /*#__PURE__*/ _jsxs("section", {
452
+ class: "flex flex-col gap-4",
453
+ children: [
454
+ /*#__PURE__*/ _jsx("textarea", {
455
+ "data-bind": "siteFooter",
456
+ "data-on:input": "$_footerDirty = true",
457
+ class: "textarea font-mono text-sm",
458
+ rows: 4,
459
+ placeholder: $__i18n._({
460
+ id: "jVUmOK",
461
+ message: "Markdown supported"
462
+ }),
463
+ children: siteFooter
464
+ }),
465
+ /*#__PURE__*/ _jsx("p", {
466
+ class: "text-sm text-muted-foreground",
467
+ children: $__i18n._({
468
+ id: "ZUpE9/",
469
+ message: "This is displayed at the bottom of all of your posts and pages. Markdown is supported."
470
+ })
471
+ }),
472
+ /*#__PURE__*/ _jsx(FormActions, {
473
+ indicator: "_footerLoading",
474
+ dirty: "_footerDirty",
475
+ fields: [
476
+ "siteFooter"
477
+ ]
478
+ })
479
+ ]
480
+ })
481
+ ]
482
+ })
483
+ }),
484
+ /*#__PURE__*/ _jsx("form", {
485
+ "data-signals": seoSignals,
486
+ "data-on:submit__prevent": "@post('/dash/settings/seo')",
487
+ "data-indicator": "_seoLoading",
488
+ children: /*#__PURE__*/ _jsxs("div", {
489
+ class: "card",
490
+ children: [
491
+ /*#__PURE__*/ _jsx("header", {
492
+ children: /*#__PURE__*/ _jsx("h2", {
493
+ children: $__i18n._({
494
+ id: "4Ml90q",
495
+ message: "SEO"
496
+ })
497
+ })
498
+ }),
499
+ /*#__PURE__*/ _jsxs("section", {
500
+ children: [
501
+ /*#__PURE__*/ _jsxs("label", {
502
+ class: "flex items-center gap-2 cursor-pointer",
503
+ children: [
504
+ /*#__PURE__*/ _jsx("input", {
505
+ type: "checkbox",
506
+ class: "checkbox",
507
+ "data-bind": "noindex",
508
+ "data-on:change": "$_seoDirty = true",
509
+ checked: !noindex || undefined,
510
+ value: "true"
511
+ }),
512
+ /*#__PURE__*/ _jsx("span", {
513
+ children: $__i18n._({
514
+ id: "Ui5/i3",
515
+ message: "It's OK for search engines to index my site"
516
+ })
517
+ })
518
+ ]
519
+ }),
520
+ /*#__PURE__*/ _jsx(FormActions, {
521
+ indicator: "_seoLoading",
522
+ dirty: "_seoDirty",
523
+ fields: [
524
+ "noindex"
525
+ ]
526
+ })
527
+ ]
528
+ })
529
+ ]
530
+ })
531
+ })
532
+ ]
533
+ })
534
+ ]
535
+ });
536
+ }
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Settings sub-navigation tabs
3
+ */ import { jsx as _jsx } from "hono/jsx/jsx-runtime";
4
+ import { useLingui as $_useLingui } from "@jant/core/i18n";
5
+ export function SettingsNav({ currentTab }) {
6
+ const { i18n: $__i18n, _: $__ } = $_useLingui();
7
+ const tabs = [
8
+ {
9
+ id: "general",
10
+ label: $__i18n._({
11
+ id: "Weq9zb",
12
+ message: "General"
13
+ }),
14
+ href: "/dash/settings"
15
+ },
16
+ {
17
+ id: "appearance",
18
+ label: $__i18n._({
19
+ id: "aAIQg2",
20
+ message: "Appearance"
21
+ }),
22
+ href: "/dash/settings/appearance"
23
+ },
24
+ {
25
+ id: "account",
26
+ label: $__i18n._({
27
+ id: "AeXO77",
28
+ message: "Account"
29
+ }),
30
+ href: "/dash/settings/account"
31
+ }
32
+ ];
33
+ return /*#__PURE__*/ _jsx("nav", {
34
+ class: "flex gap-1 mb-6",
35
+ children: tabs.map((tab)=>/*#__PURE__*/ _jsx("a", {
36
+ href: tab.href,
37
+ 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"}`,
38
+ children: tab.label
39
+ }, tab.id))
40
+ });
41
+ }
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Built-in Font Themes
3
+ *
4
+ * System-font-only presets — no external font loading required.
5
+ */ /**
6
+ * A font theme definition with display metadata.
7
+ */ export const BUILTIN_FONT_THEMES = [
8
+ {
9
+ id: "default",
10
+ name: "System Default",
11
+ // 现代系统字体栈:先英文,后 Mac/iOS 中文,再 Win 中文
12
+ fontFamily: 'system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "Source Han Sans CN", sans-serif',
13
+ description: "与你的操作系统保持一致,最稳定的阅读体验"
14
+ },
15
+ {
16
+ id: "serif",
17
+ name: "Classic Serif",
18
+ // Charter 是 Apple 系统自带的极品衬线体
19
+ fontFamily: 'Charter, "Bitstream Charter", "Sitka Text", Georgia, "Songti SC", "Source Han Serif CN", "STSong", "SimSun", serif',
20
+ description: "传统的衬线体,适合深度长文阅读"
21
+ },
22
+ {
23
+ id: "humanist",
24
+ name: "Humanist",
25
+ // Optima 具有书法韵味,Candara 是 Windows 上的优质人文体
26
+ fontFamily: 'Optima, Candara, "Noto Sans", "Segoe UI", Roboto, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", sans-serif',
27
+ description: "温润如玉的字体风格,兼具现代感与书法美感"
28
+ },
29
+ {
30
+ id: "mono",
31
+ name: "Monospace",
32
+ // 优先使用 JetBrains Mono 或 SF Mono
33
+ fontFamily: '"JetBrains Mono", "SF Mono", Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", "PingFang SC", "Microsoft YaHei", monospace',
34
+ description: "等宽字体,适合技术内容或代码展示"
35
+ }
36
+ ];