@jant/core 0.2.17 → 0.2.19
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.
- package/dist/app.d.ts +1 -0
- package/dist/app.d.ts.map +1 -1
- package/dist/app.js +307 -137
- package/dist/client.js +1 -0
- package/dist/i18n/context.d.ts +2 -2
- package/dist/i18n/context.js +1 -1
- package/dist/i18n/i18n.d.ts +1 -1
- package/dist/i18n/i18n.js +1 -1
- package/dist/i18n/index.d.ts +1 -1
- package/dist/i18n/index.js +1 -1
- package/dist/i18n/locales/en.d.ts.map +1 -1
- package/dist/i18n/locales/en.js +1 -1
- package/dist/i18n/locales/zh-Hans.d.ts.map +1 -1
- package/dist/i18n/locales/zh-Hans.js +1 -1
- package/dist/i18n/locales/zh-Hant.d.ts.map +1 -1
- package/dist/i18n/locales/zh-Hant.js +1 -1
- package/dist/lib/config.d.ts +44 -10
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/config.js +69 -44
- package/dist/lib/constants.d.ts +2 -1
- package/dist/lib/constants.d.ts.map +1 -1
- package/dist/lib/constants.js +5 -2
- package/dist/lib/image-processor.js +0 -4
- package/dist/lib/media-upload.js +104 -0
- package/dist/lib/sse.d.ts +82 -13
- package/dist/lib/sse.d.ts.map +1 -1
- package/dist/lib/sse.js +115 -17
- package/dist/lib/theme.d.ts +44 -0
- package/dist/lib/theme.d.ts.map +1 -0
- package/dist/lib/theme.js +65 -0
- package/dist/routes/api/upload.js +16 -18
- package/dist/routes/dash/appearance.d.ts +13 -0
- package/dist/routes/dash/appearance.d.ts.map +1 -0
- package/dist/routes/dash/appearance.js +160 -0
- package/dist/routes/dash/collections.js +5 -13
- package/dist/routes/dash/media.js +17 -167
- package/dist/routes/dash/pages.js +4 -10
- package/dist/routes/dash/posts.js +4 -10
- package/dist/routes/dash/redirects.js +3 -7
- package/dist/routes/dash/settings.d.ts.map +1 -1
- package/dist/routes/dash/settings.js +52 -42
- package/dist/services/settings.d.ts +1 -0
- package/dist/services/settings.d.ts.map +1 -1
- package/dist/services/settings.js +3 -0
- package/dist/theme/color-themes.d.ts +30 -0
- package/dist/theme/color-themes.d.ts.map +1 -0
- package/dist/theme/color-themes.js +268 -0
- package/dist/theme/layouts/BaseLayout.d.ts +5 -0
- package/dist/theme/layouts/BaseLayout.d.ts.map +1 -1
- package/dist/theme/layouts/BaseLayout.js +70 -3
- package/dist/theme/layouts/DashLayout.d.ts +2 -0
- package/dist/theme/layouts/DashLayout.d.ts.map +1 -1
- package/dist/theme/layouts/DashLayout.js +11 -1
- package/dist/theme/layouts/index.d.ts +1 -1
- package/dist/theme/layouts/index.d.ts.map +1 -1
- package/dist/types.d.ts +53 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +52 -0
- package/package.json +1 -1
- package/src/app.tsx +260 -81
- package/src/client.ts +1 -0
- package/src/db/migrations/{0000_solid_moon_knight.sql → 0000_square_wallflower.sql} +3 -3
- package/src/db/migrations/meta/0000_snapshot.json +9 -9
- package/src/db/migrations/meta/_journal.json +2 -30
- package/src/i18n/context.tsx +2 -2
- package/src/i18n/i18n.ts +1 -1
- package/src/i18n/index.ts +1 -1
- package/src/i18n/locales/en.po +328 -252
- package/src/i18n/locales/en.ts +1 -1
- package/src/i18n/locales/zh-Hans.po +315 -278
- package/src/i18n/locales/zh-Hans.ts +1 -1
- package/src/i18n/locales/zh-Hant.po +315 -278
- package/src/i18n/locales/zh-Hant.ts +1 -1
- package/src/lib/config.ts +73 -47
- package/src/lib/constants.ts +3 -0
- package/src/lib/image-processor.ts +0 -7
- package/src/lib/media-upload.ts +148 -0
- package/src/lib/sse.ts +156 -16
- package/src/lib/theme.ts +86 -0
- package/src/preset.css +9 -0
- package/src/routes/api/upload.ts +12 -18
- package/src/routes/dash/appearance.tsx +176 -0
- package/src/routes/dash/collections.tsx +5 -13
- package/src/routes/dash/media.tsx +16 -165
- package/src/routes/dash/pages.tsx +4 -10
- package/src/routes/dash/posts.tsx +4 -10
- package/src/routes/dash/redirects.tsx +3 -7
- package/src/routes/dash/settings.tsx +71 -55
- package/src/services/settings.ts +5 -0
- package/src/styles/components.css +93 -0
- package/src/theme/color-themes.ts +321 -0
- package/src/theme/layouts/BaseLayout.tsx +61 -1
- package/src/theme/layouts/DashLayout.tsx +14 -3
- package/src/theme/layouts/index.ts +5 -1
- package/src/types.ts +62 -1
- package/src/db/migrations/0001_add_search_fts.sql +0 -40
- package/src/db/migrations/0002_collection_path.sql +0 -2
- package/src/db/migrations/0003_collection_path_nullable.sql +0 -21
- package/src/db/migrations/0004_media_uuid.sql +0 -35
|
@@ -0,0 +1,321 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Built-in Color Themes
|
|
3
|
+
*
|
|
4
|
+
* Each theme defines CSS variable overrides for light and dark modes,
|
|
5
|
+
* plus preview colors for the theme picker UI.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* A color theme definition with light and dark mode CSS variable overrides.
|
|
10
|
+
*/
|
|
11
|
+
export interface ColorTheme {
|
|
12
|
+
/** Stored in DB settings, e.g. "beach" */
|
|
13
|
+
id: string;
|
|
14
|
+
/** Display name, e.g. "Beach" */
|
|
15
|
+
name: string;
|
|
16
|
+
/** CSS variable overrides for :root (light mode) */
|
|
17
|
+
light: Record<string, string>;
|
|
18
|
+
/** CSS variable overrides for .dark (dark mode) */
|
|
19
|
+
dark: Record<string, string>;
|
|
20
|
+
/** Preview colors (hex) for theme picker cards */
|
|
21
|
+
preview: {
|
|
22
|
+
lightBg: string;
|
|
23
|
+
lightText: string;
|
|
24
|
+
lightLink: string;
|
|
25
|
+
darkBg: string;
|
|
26
|
+
darkText: string;
|
|
27
|
+
darkLink: string;
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Create a comprehensive color theme from key colors.
|
|
33
|
+
* Derives card, popover, muted, secondary, accent, and sidebar variables.
|
|
34
|
+
*/
|
|
35
|
+
function defineTheme(opts: {
|
|
36
|
+
id: string;
|
|
37
|
+
name: string;
|
|
38
|
+
preview: ColorTheme["preview"];
|
|
39
|
+
light: {
|
|
40
|
+
bg: string;
|
|
41
|
+
fg: string;
|
|
42
|
+
primary: string;
|
|
43
|
+
primaryFg: string;
|
|
44
|
+
muted: string;
|
|
45
|
+
mutedFg: string;
|
|
46
|
+
border: string;
|
|
47
|
+
};
|
|
48
|
+
dark: {
|
|
49
|
+
bg: string;
|
|
50
|
+
fg: string;
|
|
51
|
+
primary: string;
|
|
52
|
+
primaryFg: string;
|
|
53
|
+
muted: string;
|
|
54
|
+
mutedFg: string;
|
|
55
|
+
border: string;
|
|
56
|
+
};
|
|
57
|
+
}): ColorTheme {
|
|
58
|
+
const { light, dark } = opts;
|
|
59
|
+
return {
|
|
60
|
+
id: opts.id,
|
|
61
|
+
name: opts.name,
|
|
62
|
+
preview: opts.preview,
|
|
63
|
+
light: {
|
|
64
|
+
"--background": light.bg,
|
|
65
|
+
"--foreground": light.fg,
|
|
66
|
+
"--card": light.bg,
|
|
67
|
+
"--card-foreground": light.fg,
|
|
68
|
+
"--popover": light.bg,
|
|
69
|
+
"--popover-foreground": light.fg,
|
|
70
|
+
"--primary": light.primary,
|
|
71
|
+
"--primary-foreground": light.primaryFg,
|
|
72
|
+
"--secondary": light.muted,
|
|
73
|
+
"--secondary-foreground": light.fg,
|
|
74
|
+
"--muted": light.muted,
|
|
75
|
+
"--muted-foreground": light.mutedFg,
|
|
76
|
+
"--accent": light.muted,
|
|
77
|
+
"--accent-foreground": light.fg,
|
|
78
|
+
"--border": light.border,
|
|
79
|
+
"--input": light.border,
|
|
80
|
+
"--ring": light.primary,
|
|
81
|
+
"--sidebar": light.bg,
|
|
82
|
+
"--sidebar-foreground": light.fg,
|
|
83
|
+
"--sidebar-primary": light.primary,
|
|
84
|
+
"--sidebar-primary-foreground": light.primaryFg,
|
|
85
|
+
"--sidebar-accent": light.muted,
|
|
86
|
+
"--sidebar-accent-foreground": light.fg,
|
|
87
|
+
"--sidebar-border": light.border,
|
|
88
|
+
"--sidebar-ring": light.primary,
|
|
89
|
+
},
|
|
90
|
+
dark: {
|
|
91
|
+
"--background": dark.bg,
|
|
92
|
+
"--foreground": dark.fg,
|
|
93
|
+
"--card": dark.bg,
|
|
94
|
+
"--card-foreground": dark.fg,
|
|
95
|
+
"--popover": dark.bg,
|
|
96
|
+
"--popover-foreground": dark.fg,
|
|
97
|
+
"--primary": dark.primary,
|
|
98
|
+
"--primary-foreground": dark.primaryFg,
|
|
99
|
+
"--secondary": dark.muted,
|
|
100
|
+
"--secondary-foreground": dark.fg,
|
|
101
|
+
"--muted": dark.muted,
|
|
102
|
+
"--muted-foreground": dark.mutedFg,
|
|
103
|
+
"--accent": dark.muted,
|
|
104
|
+
"--accent-foreground": dark.fg,
|
|
105
|
+
"--border": dark.border,
|
|
106
|
+
"--input": dark.border,
|
|
107
|
+
"--ring": dark.primary,
|
|
108
|
+
"--sidebar": dark.bg,
|
|
109
|
+
"--sidebar-foreground": dark.fg,
|
|
110
|
+
"--sidebar-primary": dark.primary,
|
|
111
|
+
"--sidebar-primary-foreground": dark.primaryFg,
|
|
112
|
+
"--sidebar-accent": dark.muted,
|
|
113
|
+
"--sidebar-accent-foreground": dark.fg,
|
|
114
|
+
"--sidebar-border": dark.border,
|
|
115
|
+
"--sidebar-ring": dark.primary,
|
|
116
|
+
},
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export const BUILTIN_COLOR_THEMES: ColorTheme[] = [
|
|
121
|
+
{
|
|
122
|
+
id: "default",
|
|
123
|
+
name: "Default",
|
|
124
|
+
light: {},
|
|
125
|
+
dark: {},
|
|
126
|
+
preview: {
|
|
127
|
+
lightBg: "#ffffff",
|
|
128
|
+
lightText: "#1e1e1e",
|
|
129
|
+
lightLink: "#1e1e1e",
|
|
130
|
+
darkBg: "#262626",
|
|
131
|
+
darkText: "#fafafa",
|
|
132
|
+
darkLink: "#eaeaea",
|
|
133
|
+
},
|
|
134
|
+
},
|
|
135
|
+
|
|
136
|
+
defineTheme({
|
|
137
|
+
id: "beach",
|
|
138
|
+
name: "Beach",
|
|
139
|
+
preview: {
|
|
140
|
+
lightBg: "#f9f3ea",
|
|
141
|
+
lightText: "#3d3527",
|
|
142
|
+
lightLink: "#2d6a59",
|
|
143
|
+
darkBg: "#2d4553",
|
|
144
|
+
darkText: "#e2d6c4",
|
|
145
|
+
darkLink: "#7cc5a2",
|
|
146
|
+
},
|
|
147
|
+
light: {
|
|
148
|
+
bg: "oklch(0.97 0.01 85)",
|
|
149
|
+
fg: "oklch(0.28 0.02 65)",
|
|
150
|
+
primary: "oklch(0.46 0.1 170)",
|
|
151
|
+
primaryFg: "oklch(0.98 0.005 85)",
|
|
152
|
+
muted: "oklch(0.93 0.015 85)",
|
|
153
|
+
mutedFg: "oklch(0.52 0.015 65)",
|
|
154
|
+
border: "oklch(0.88 0.018 85)",
|
|
155
|
+
},
|
|
156
|
+
dark: {
|
|
157
|
+
bg: "oklch(0.27 0.03 210)",
|
|
158
|
+
fg: "oklch(0.88 0.015 80)",
|
|
159
|
+
primary: "oklch(0.72 0.1 165)",
|
|
160
|
+
primaryFg: "oklch(0.22 0.03 210)",
|
|
161
|
+
muted: "oklch(0.33 0.025 210)",
|
|
162
|
+
mutedFg: "oklch(0.65 0.015 80)",
|
|
163
|
+
border: "oklch(0.38 0.02 210)",
|
|
164
|
+
},
|
|
165
|
+
}),
|
|
166
|
+
|
|
167
|
+
defineTheme({
|
|
168
|
+
id: "gameboy",
|
|
169
|
+
name: "Gameboy",
|
|
170
|
+
preview: {
|
|
171
|
+
lightBg: "#d3d7c0",
|
|
172
|
+
lightText: "#2b3326",
|
|
173
|
+
lightLink: "#466740",
|
|
174
|
+
darkBg: "#1b1f18",
|
|
175
|
+
darkText: "#a6b09a",
|
|
176
|
+
darkLink: "#6d9660",
|
|
177
|
+
},
|
|
178
|
+
light: {
|
|
179
|
+
bg: "oklch(0.87 0.03 130)",
|
|
180
|
+
fg: "oklch(0.25 0.04 140)",
|
|
181
|
+
primary: "oklch(0.4 0.08 145)",
|
|
182
|
+
primaryFg: "oklch(0.92 0.02 130)",
|
|
183
|
+
muted: "oklch(0.83 0.035 130)",
|
|
184
|
+
mutedFg: "oklch(0.48 0.03 140)",
|
|
185
|
+
border: "oklch(0.79 0.035 130)",
|
|
186
|
+
},
|
|
187
|
+
dark: {
|
|
188
|
+
bg: "oklch(0.18 0.02 140)",
|
|
189
|
+
fg: "oklch(0.78 0.025 130)",
|
|
190
|
+
primary: "oklch(0.6 0.08 145)",
|
|
191
|
+
primaryFg: "oklch(0.15 0.02 140)",
|
|
192
|
+
muted: "oklch(0.24 0.02 140)",
|
|
193
|
+
mutedFg: "oklch(0.58 0.02 130)",
|
|
194
|
+
border: "oklch(0.3 0.02 140)",
|
|
195
|
+
},
|
|
196
|
+
}),
|
|
197
|
+
|
|
198
|
+
defineTheme({
|
|
199
|
+
id: "grayscale",
|
|
200
|
+
name: "Grayscale",
|
|
201
|
+
preview: {
|
|
202
|
+
lightBg: "#efefef",
|
|
203
|
+
lightText: "#3a3a3a",
|
|
204
|
+
lightLink: "#555555",
|
|
205
|
+
darkBg: "#1e1e1e",
|
|
206
|
+
darkText: "#c8c8c8",
|
|
207
|
+
darkLink: "#999999",
|
|
208
|
+
},
|
|
209
|
+
light: {
|
|
210
|
+
bg: "oklch(0.96 0 0)",
|
|
211
|
+
fg: "oklch(0.3 0 0)",
|
|
212
|
+
primary: "oklch(0.4 0 0)",
|
|
213
|
+
primaryFg: "oklch(0.96 0 0)",
|
|
214
|
+
muted: "oklch(0.92 0 0)",
|
|
215
|
+
mutedFg: "oklch(0.55 0 0)",
|
|
216
|
+
border: "oklch(0.87 0 0)",
|
|
217
|
+
},
|
|
218
|
+
dark: {
|
|
219
|
+
bg: "oklch(0.18 0 0)",
|
|
220
|
+
fg: "oklch(0.82 0 0)",
|
|
221
|
+
primary: "oklch(0.7 0 0)",
|
|
222
|
+
primaryFg: "oklch(0.18 0 0)",
|
|
223
|
+
muted: "oklch(0.24 0 0)",
|
|
224
|
+
mutedFg: "oklch(0.6 0 0)",
|
|
225
|
+
border: "oklch(0.3 0 0)",
|
|
226
|
+
},
|
|
227
|
+
}),
|
|
228
|
+
|
|
229
|
+
defineTheme({
|
|
230
|
+
id: "halloween",
|
|
231
|
+
name: "Halloween",
|
|
232
|
+
preview: {
|
|
233
|
+
lightBg: "#f9f2e3",
|
|
234
|
+
lightText: "#352200",
|
|
235
|
+
lightLink: "#cc5500",
|
|
236
|
+
darkBg: "#1e1000",
|
|
237
|
+
darkText: "#dfc390",
|
|
238
|
+
darkLink: "#ff8c00",
|
|
239
|
+
},
|
|
240
|
+
light: {
|
|
241
|
+
bg: "oklch(0.97 0.015 75)",
|
|
242
|
+
fg: "oklch(0.25 0.04 55)",
|
|
243
|
+
primary: "oklch(0.6 0.2 50)",
|
|
244
|
+
primaryFg: "oklch(0.98 0.01 75)",
|
|
245
|
+
muted: "oklch(0.93 0.02 75)",
|
|
246
|
+
mutedFg: "oklch(0.5 0.025 55)",
|
|
247
|
+
border: "oklch(0.88 0.025 75)",
|
|
248
|
+
},
|
|
249
|
+
dark: {
|
|
250
|
+
bg: "oklch(0.16 0.03 50)",
|
|
251
|
+
fg: "oklch(0.85 0.025 75)",
|
|
252
|
+
primary: "oklch(0.72 0.19 55)",
|
|
253
|
+
primaryFg: "oklch(0.14 0.03 50)",
|
|
254
|
+
muted: "oklch(0.22 0.025 50)",
|
|
255
|
+
mutedFg: "oklch(0.62 0.02 75)",
|
|
256
|
+
border: "oklch(0.28 0.025 50)",
|
|
257
|
+
},
|
|
258
|
+
}),
|
|
259
|
+
|
|
260
|
+
defineTheme({
|
|
261
|
+
id: "notepad",
|
|
262
|
+
name: "Notepad",
|
|
263
|
+
preview: {
|
|
264
|
+
lightBg: "#fdfce8",
|
|
265
|
+
lightText: "#333333",
|
|
266
|
+
lightLink: "#2060b8",
|
|
267
|
+
darkBg: "#2a291a",
|
|
268
|
+
darkText: "#d2d2b8",
|
|
269
|
+
darkLink: "#6695cc",
|
|
270
|
+
},
|
|
271
|
+
light: {
|
|
272
|
+
bg: "oklch(0.985 0.018 95)",
|
|
273
|
+
fg: "oklch(0.27 0 0)",
|
|
274
|
+
primary: "oklch(0.5 0.17 260)",
|
|
275
|
+
primaryFg: "oklch(0.985 0.01 95)",
|
|
276
|
+
muted: "oklch(0.94 0.022 95)",
|
|
277
|
+
mutedFg: "oklch(0.52 0 0)",
|
|
278
|
+
border: "oklch(0.88 0.025 95)",
|
|
279
|
+
},
|
|
280
|
+
dark: {
|
|
281
|
+
bg: "oklch(0.2 0.02 90)",
|
|
282
|
+
fg: "oklch(0.87 0.015 95)",
|
|
283
|
+
primary: "oklch(0.65 0.14 260)",
|
|
284
|
+
primaryFg: "oklch(0.98 0.01 95)",
|
|
285
|
+
muted: "oklch(0.26 0.018 90)",
|
|
286
|
+
mutedFg: "oklch(0.62 0.012 95)",
|
|
287
|
+
border: "oklch(0.32 0.018 90)",
|
|
288
|
+
},
|
|
289
|
+
}),
|
|
290
|
+
|
|
291
|
+
defineTheme({
|
|
292
|
+
id: "sonnet",
|
|
293
|
+
name: "Sonnet",
|
|
294
|
+
preview: {
|
|
295
|
+
lightBg: "#f7eef5",
|
|
296
|
+
lightText: "#2e1e2c",
|
|
297
|
+
lightLink: "#9845c8",
|
|
298
|
+
darkBg: "#1d1428",
|
|
299
|
+
darkText: "#d4c2d0",
|
|
300
|
+
darkLink: "#c080fc",
|
|
301
|
+
},
|
|
302
|
+
light: {
|
|
303
|
+
bg: "oklch(0.97 0.012 325)",
|
|
304
|
+
fg: "oklch(0.25 0.02 310)",
|
|
305
|
+
primary: "oklch(0.55 0.2 300)",
|
|
306
|
+
primaryFg: "oklch(0.98 0.008 325)",
|
|
307
|
+
muted: "oklch(0.93 0.016 325)",
|
|
308
|
+
mutedFg: "oklch(0.52 0.015 310)",
|
|
309
|
+
border: "oklch(0.88 0.016 325)",
|
|
310
|
+
},
|
|
311
|
+
dark: {
|
|
312
|
+
bg: "oklch(0.18 0.025 300)",
|
|
313
|
+
fg: "oklch(0.87 0.012 325)",
|
|
314
|
+
primary: "oklch(0.72 0.18 300)",
|
|
315
|
+
primaryFg: "oklch(0.98 0.008 325)",
|
|
316
|
+
muted: "oklch(0.24 0.022 300)",
|
|
317
|
+
mutedFg: "oklch(0.62 0.012 325)",
|
|
318
|
+
border: "oklch(0.3 0.022 300)",
|
|
319
|
+
},
|
|
320
|
+
}),
|
|
321
|
+
];
|
|
@@ -12,11 +12,17 @@ import type { Context } from "hono";
|
|
|
12
12
|
import { Script, Link, ViteClient } from "vite-ssr-components/hono";
|
|
13
13
|
import { I18nProvider } from "../../i18n/index.js";
|
|
14
14
|
|
|
15
|
+
export interface ToastProps {
|
|
16
|
+
message: string;
|
|
17
|
+
type?: "success" | "error";
|
|
18
|
+
}
|
|
19
|
+
|
|
15
20
|
export interface BaseLayoutProps {
|
|
16
21
|
title: string;
|
|
17
22
|
description?: string;
|
|
18
23
|
lang?: string;
|
|
19
24
|
c?: Context;
|
|
25
|
+
toast?: ToastProps;
|
|
20
26
|
}
|
|
21
27
|
|
|
22
28
|
export const BaseLayout: FC<PropsWithChildren<BaseLayoutProps>> = ({
|
|
@@ -24,6 +30,7 @@ export const BaseLayout: FC<PropsWithChildren<BaseLayoutProps>> = ({
|
|
|
24
30
|
description,
|
|
25
31
|
lang,
|
|
26
32
|
c,
|
|
33
|
+
toast,
|
|
27
34
|
children,
|
|
28
35
|
}) => {
|
|
29
36
|
// Read lang from Hono context if available, otherwise use prop or default
|
|
@@ -32,6 +39,9 @@ export const BaseLayout: FC<PropsWithChildren<BaseLayoutProps>> = ({
|
|
|
32
39
|
// Automatically wrap with I18nProvider if Context is provided
|
|
33
40
|
const content = c ? <I18nProvider c={c}>{children}</I18nProvider> : children;
|
|
34
41
|
|
|
42
|
+
// Read theme style from Hono context if available
|
|
43
|
+
const themeStyle = c ? c.get("themeStyle") : undefined;
|
|
44
|
+
|
|
35
45
|
return (
|
|
36
46
|
<html lang={resolvedLang}>
|
|
37
47
|
<head>
|
|
@@ -41,9 +51,59 @@ export const BaseLayout: FC<PropsWithChildren<BaseLayoutProps>> = ({
|
|
|
41
51
|
{description && <meta name="description" content={description} />}
|
|
42
52
|
<ViteClient />
|
|
43
53
|
<Link href="/src/style.css" rel="stylesheet" />
|
|
54
|
+
{themeStyle && <style>{themeStyle}</style>}
|
|
44
55
|
<Script src="/src/client.ts" />
|
|
45
56
|
</head>
|
|
46
|
-
<body class="bg-background text-foreground antialiased">
|
|
57
|
+
<body class="bg-background text-foreground antialiased">
|
|
58
|
+
{content}
|
|
59
|
+
<div id="toast-container" class="toast-container">
|
|
60
|
+
{toast && (
|
|
61
|
+
<div
|
|
62
|
+
class={`toast ${toast.type === "error" ? "toast-error" : "toast-success"}`}
|
|
63
|
+
data-init="history.replaceState({}, '', location.pathname); setTimeout(() => { el.classList.add('toast-out'); el.addEventListener('animationend', () => el.remove()) }, 3000)"
|
|
64
|
+
>
|
|
65
|
+
{toast.type === "error" ? (
|
|
66
|
+
<svg
|
|
67
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
68
|
+
fill="none"
|
|
69
|
+
viewBox="0 0 24 24"
|
|
70
|
+
stroke-width="2"
|
|
71
|
+
stroke="currentColor"
|
|
72
|
+
>
|
|
73
|
+
<circle cx="12" cy="12" r="10" />
|
|
74
|
+
<path d="m15 9-6 6M9 9l6 6" />
|
|
75
|
+
</svg>
|
|
76
|
+
) : (
|
|
77
|
+
<svg
|
|
78
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
79
|
+
fill="none"
|
|
80
|
+
viewBox="0 0 24 24"
|
|
81
|
+
stroke-width="2"
|
|
82
|
+
stroke="currentColor"
|
|
83
|
+
>
|
|
84
|
+
<circle cx="12" cy="12" r="10" />
|
|
85
|
+
<path d="m9 12 2 2 4-4" />
|
|
86
|
+
</svg>
|
|
87
|
+
)}
|
|
88
|
+
<span>{toast.message}</span>
|
|
89
|
+
<button
|
|
90
|
+
class="toast-close"
|
|
91
|
+
data-on:click="el.closest('.toast').classList.add('toast-out'); el.closest('.toast').addEventListener('animationend', () => el.closest('.toast').remove())"
|
|
92
|
+
>
|
|
93
|
+
<svg
|
|
94
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
95
|
+
fill="none"
|
|
96
|
+
viewBox="0 0 24 24"
|
|
97
|
+
stroke-width="2"
|
|
98
|
+
stroke="currentColor"
|
|
99
|
+
>
|
|
100
|
+
<path d="M18 6 6 18M6 6l12 12" />
|
|
101
|
+
</svg>
|
|
102
|
+
</button>
|
|
103
|
+
</div>
|
|
104
|
+
)}
|
|
105
|
+
</div>
|
|
106
|
+
</body>
|
|
47
107
|
</html>
|
|
48
108
|
);
|
|
49
109
|
};
|
|
@@ -7,13 +7,14 @@
|
|
|
7
7
|
import type { FC, PropsWithChildren } from "hono/jsx";
|
|
8
8
|
import type { Context } from "hono";
|
|
9
9
|
import { useLingui } from "@lingui/react/macro";
|
|
10
|
-
import { BaseLayout } from "./BaseLayout.js";
|
|
10
|
+
import { BaseLayout, type ToastProps } from "./BaseLayout.js";
|
|
11
11
|
|
|
12
12
|
export interface DashLayoutProps {
|
|
13
13
|
c: Context;
|
|
14
14
|
title: string;
|
|
15
15
|
siteName: string;
|
|
16
16
|
currentPath?: string;
|
|
17
|
+
toast?: ToastProps;
|
|
17
18
|
}
|
|
18
19
|
|
|
19
20
|
function DashLayoutContent({
|
|
@@ -41,7 +42,7 @@ function DashLayoutContent({
|
|
|
41
42
|
{/* Header */}
|
|
42
43
|
<header class="border-b bg-card">
|
|
43
44
|
<div class="container flex h-14 items-center justify-between">
|
|
44
|
-
<a href="/dash" class="font-semibold">
|
|
45
|
+
<a id="site-name" href="/dash" class="font-semibold">
|
|
45
46
|
{siteName}
|
|
46
47
|
</a>
|
|
47
48
|
<nav class="flex items-center gap-4">
|
|
@@ -134,6 +135,15 @@ function DashLayoutContent({
|
|
|
134
135
|
comment: "@context: Dashboard navigation - site settings",
|
|
135
136
|
})}
|
|
136
137
|
</a>
|
|
138
|
+
<a
|
|
139
|
+
href="/dash/appearance"
|
|
140
|
+
class={navClass("/dash/appearance", /^\/dash\/appearance/)}
|
|
141
|
+
>
|
|
142
|
+
{t({
|
|
143
|
+
message: "Appearance",
|
|
144
|
+
comment: "@context: Dashboard navigation - appearance settings",
|
|
145
|
+
})}
|
|
146
|
+
</a>
|
|
137
147
|
</nav>
|
|
138
148
|
</aside>
|
|
139
149
|
|
|
@@ -149,10 +159,11 @@ export const DashLayout: FC<PropsWithChildren<DashLayoutProps>> = ({
|
|
|
149
159
|
title,
|
|
150
160
|
siteName,
|
|
151
161
|
currentPath,
|
|
162
|
+
toast,
|
|
152
163
|
children,
|
|
153
164
|
}) => {
|
|
154
165
|
return (
|
|
155
|
-
<BaseLayout title={`${title} - ${siteName}`} c={c}>
|
|
166
|
+
<BaseLayout title={`${title} - ${siteName}`} c={c} toast={toast}>
|
|
156
167
|
<DashLayoutContent siteName={siteName} currentPath={currentPath}>
|
|
157
168
|
{children}
|
|
158
169
|
</DashLayoutContent>
|
package/src/types.ts
CHANGED
|
@@ -43,6 +43,64 @@ export interface Bindings {
|
|
|
43
43
|
SITE_LANGUAGE?: string;
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
+
// =============================================================================
|
|
47
|
+
// Configuration System
|
|
48
|
+
// =============================================================================
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Configuration Registry - Single Source of Truth
|
|
52
|
+
*
|
|
53
|
+
* All available configuration fields with their metadata.
|
|
54
|
+
* Add new fields here, and they'll automatically work everywhere.
|
|
55
|
+
*
|
|
56
|
+
* Priority logic:
|
|
57
|
+
* - envOnly: false → User-configurable (DB > ENV > Default)
|
|
58
|
+
* - envOnly: true → Environment-only (ENV > Default)
|
|
59
|
+
*/
|
|
60
|
+
export const CONFIG_FIELDS = {
|
|
61
|
+
// User-configurable (can be modified in dashboard)
|
|
62
|
+
SITE_NAME: {
|
|
63
|
+
defaultValue: "Jant",
|
|
64
|
+
envOnly: false,
|
|
65
|
+
},
|
|
66
|
+
SITE_DESCRIPTION: {
|
|
67
|
+
defaultValue: "A microblog powered by Jant",
|
|
68
|
+
envOnly: false,
|
|
69
|
+
},
|
|
70
|
+
SITE_LANGUAGE: {
|
|
71
|
+
defaultValue: "en",
|
|
72
|
+
envOnly: false,
|
|
73
|
+
},
|
|
74
|
+
|
|
75
|
+
// Environment-only (deployment/infrastructure config)
|
|
76
|
+
SITE_URL: {
|
|
77
|
+
defaultValue: "",
|
|
78
|
+
envOnly: true,
|
|
79
|
+
},
|
|
80
|
+
AUTH_SECRET: {
|
|
81
|
+
defaultValue: "",
|
|
82
|
+
envOnly: true,
|
|
83
|
+
},
|
|
84
|
+
R2_PUBLIC_URL: {
|
|
85
|
+
defaultValue: "",
|
|
86
|
+
envOnly: true,
|
|
87
|
+
},
|
|
88
|
+
IMAGE_TRANSFORM_URL: {
|
|
89
|
+
defaultValue: "",
|
|
90
|
+
envOnly: true,
|
|
91
|
+
},
|
|
92
|
+
DEMO_EMAIL: {
|
|
93
|
+
defaultValue: "",
|
|
94
|
+
envOnly: true,
|
|
95
|
+
},
|
|
96
|
+
DEMO_PASSWORD: {
|
|
97
|
+
defaultValue: "",
|
|
98
|
+
envOnly: true,
|
|
99
|
+
},
|
|
100
|
+
} as const;
|
|
101
|
+
|
|
102
|
+
export type ConfigKey = keyof typeof CONFIG_FIELDS;
|
|
103
|
+
|
|
46
104
|
// =============================================================================
|
|
47
105
|
// Entity Types
|
|
48
106
|
// =============================================================================
|
|
@@ -141,6 +199,7 @@ export interface UpdatePost {
|
|
|
141
199
|
// =============================================================================
|
|
142
200
|
|
|
143
201
|
import type { FC, PropsWithChildren } from "hono/jsx";
|
|
202
|
+
import type { ColorTheme } from "./theme/color-themes.js";
|
|
144
203
|
|
|
145
204
|
/**
|
|
146
205
|
* Props for overridable theme components
|
|
@@ -193,8 +252,10 @@ export interface JantTheme {
|
|
|
193
252
|
name?: string;
|
|
194
253
|
/** Component overrides */
|
|
195
254
|
components?: ThemeComponents;
|
|
196
|
-
/** CSS variable overrides */
|
|
255
|
+
/** CSS variable overrides (highest priority, always applied) */
|
|
197
256
|
cssVariables?: Record<string, string>;
|
|
257
|
+
/** Replace built-in color themes with a custom list */
|
|
258
|
+
colorThemes?: ColorTheme[];
|
|
198
259
|
}
|
|
199
260
|
|
|
200
261
|
/**
|
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
-- FTS5 Full-Text Search for posts
|
|
2
|
-
-- This creates a virtual table that indexes post titles and content
|
|
3
|
-
|
|
4
|
-
CREATE VIRTUAL TABLE IF NOT EXISTS posts_fts USING fts5(
|
|
5
|
-
title,
|
|
6
|
-
content,
|
|
7
|
-
content='posts',
|
|
8
|
-
content_rowid='id'
|
|
9
|
-
);
|
|
10
|
-
--> statement-breakpoint
|
|
11
|
-
|
|
12
|
-
-- Populate FTS table with existing posts
|
|
13
|
-
INSERT INTO posts_fts(rowid, title, content)
|
|
14
|
-
SELECT id, COALESCE(title, ''), COALESCE(content, '') FROM posts WHERE deleted_at IS NULL;
|
|
15
|
-
--> statement-breakpoint
|
|
16
|
-
|
|
17
|
-
-- Trigger to keep FTS in sync on INSERT
|
|
18
|
-
CREATE TRIGGER posts_fts_insert AFTER INSERT ON posts
|
|
19
|
-
WHEN NEW.deleted_at IS NULL
|
|
20
|
-
BEGIN
|
|
21
|
-
INSERT INTO posts_fts(rowid, title, content)
|
|
22
|
-
VALUES (NEW.id, COALESCE(NEW.title, ''), COALESCE(NEW.content, ''));
|
|
23
|
-
END;
|
|
24
|
-
--> statement-breakpoint
|
|
25
|
-
|
|
26
|
-
-- Trigger to keep FTS in sync on UPDATE
|
|
27
|
-
CREATE TRIGGER posts_fts_update AFTER UPDATE ON posts
|
|
28
|
-
BEGIN
|
|
29
|
-
DELETE FROM posts_fts WHERE rowid = OLD.id;
|
|
30
|
-
INSERT INTO posts_fts(rowid, title, content)
|
|
31
|
-
SELECT NEW.id, COALESCE(NEW.title, ''), COALESCE(NEW.content, '')
|
|
32
|
-
WHERE NEW.deleted_at IS NULL;
|
|
33
|
-
END;
|
|
34
|
-
--> statement-breakpoint
|
|
35
|
-
|
|
36
|
-
-- Trigger to remove from FTS on DELETE
|
|
37
|
-
CREATE TRIGGER posts_fts_delete AFTER DELETE ON posts
|
|
38
|
-
BEGIN
|
|
39
|
-
DELETE FROM posts_fts WHERE rowid = OLD.id;
|
|
40
|
-
END;
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
-- Make collections.path nullable
|
|
2
|
-
-- SQLite doesn't support ALTER COLUMN, so we need to recreate the table
|
|
3
|
-
|
|
4
|
-
PRAGMA foreign_keys=OFF;
|
|
5
|
-
|
|
6
|
-
CREATE TABLE collections_new (
|
|
7
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
|
|
8
|
-
path TEXT UNIQUE,
|
|
9
|
-
title TEXT NOT NULL,
|
|
10
|
-
description TEXT,
|
|
11
|
-
created_at INTEGER NOT NULL,
|
|
12
|
-
updated_at INTEGER NOT NULL
|
|
13
|
-
);
|
|
14
|
-
|
|
15
|
-
INSERT INTO collections_new SELECT * FROM collections;
|
|
16
|
-
|
|
17
|
-
DROP TABLE collections;
|
|
18
|
-
|
|
19
|
-
ALTER TABLE collections_new RENAME TO collections;
|
|
20
|
-
|
|
21
|
-
PRAGMA foreign_keys=ON;
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
-- Migration: Change media.id from integer to UUIDv7 (text)
|
|
2
|
-
-- SQLite doesn't support altering primary key types, so we recreate the table
|
|
3
|
-
|
|
4
|
-
-- Create new table with text id
|
|
5
|
-
CREATE TABLE media_new (
|
|
6
|
-
id TEXT PRIMARY KEY NOT NULL,
|
|
7
|
-
post_id INTEGER REFERENCES posts(id),
|
|
8
|
-
filename TEXT NOT NULL,
|
|
9
|
-
original_name TEXT NOT NULL,
|
|
10
|
-
mime_type TEXT NOT NULL,
|
|
11
|
-
size INTEGER NOT NULL,
|
|
12
|
-
r2_key TEXT NOT NULL,
|
|
13
|
-
width INTEGER,
|
|
14
|
-
height INTEGER,
|
|
15
|
-
alt TEXT,
|
|
16
|
-
created_at INTEGER NOT NULL
|
|
17
|
-
);
|
|
18
|
-
|
|
19
|
-
-- Migrate existing data (generate UUIDv7-like ids from old integer ids)
|
|
20
|
-
-- For existing data, we use a deterministic format based on timestamp + old id
|
|
21
|
-
INSERT INTO media_new (id, post_id, filename, original_name, mime_type, size, r2_key, width, height, alt, created_at)
|
|
22
|
-
SELECT
|
|
23
|
-
printf('%08x-%04x-7%03x-%04x-%012x',
|
|
24
|
-
created_at,
|
|
25
|
-
(id >> 16) & 0xFFFF,
|
|
26
|
-
id & 0x0FFF,
|
|
27
|
-
0x8000 | (RANDOM() & 0x3FFF),
|
|
28
|
-
ABS(RANDOM())
|
|
29
|
-
) as id,
|
|
30
|
-
post_id, filename, original_name, mime_type, size, r2_key, width, height, alt, created_at
|
|
31
|
-
FROM media;
|
|
32
|
-
|
|
33
|
-
-- Drop old table and rename new one
|
|
34
|
-
DROP TABLE media;
|
|
35
|
-
ALTER TABLE media_new RENAME TO media;
|