@jant/core 0.2.16 → 0.2.18
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 +5 -1
- package/dist/app.d.ts.map +1 -1
- package/dist/app.js +332 -119
- 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/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/lib/config.d.ts +83 -0
- package/dist/lib/config.d.ts.map +1 -0
- package/dist/lib/config.js +104 -0
- 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/sse.d.ts +15 -0
- package/dist/lib/sse.d.ts.map +1 -1
- package/dist/lib/sse.js +13 -0
- 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/dash/appearance.d.ts +13 -0
- package/dist/routes/dash/appearance.d.ts.map +1 -0
- package/dist/routes/dash/appearance.js +164 -0
- package/dist/routes/dash/collections.d.ts.map +1 -1
- package/dist/routes/dash/collections.js +5 -4
- package/dist/routes/dash/index.d.ts.map +1 -1
- package/dist/routes/dash/index.js +2 -1
- package/dist/routes/dash/media.d.ts.map +1 -1
- package/dist/routes/dash/media.js +3 -2
- package/dist/routes/dash/pages.d.ts.map +1 -1
- package/dist/routes/dash/pages.js +5 -4
- package/dist/routes/dash/posts.d.ts.map +1 -1
- package/dist/routes/dash/posts.js +5 -4
- package/dist/routes/dash/redirects.d.ts.map +1 -1
- package/dist/routes/dash/redirects.js +3 -2
- package/dist/routes/dash/settings.d.ts.map +1 -1
- package/dist/routes/dash/settings.js +39 -38
- package/dist/routes/pages/archive.d.ts.map +1 -1
- package/dist/routes/pages/archive.js +2 -1
- package/dist/routes/pages/collection.d.ts.map +1 -1
- package/dist/routes/pages/collection.js +2 -1
- package/dist/routes/pages/home.d.ts.map +1 -1
- package/dist/routes/pages/home.js +2 -1
- package/dist/routes/pages/page.d.ts.map +1 -1
- package/dist/routes/pages/page.js +2 -1
- package/dist/routes/pages/post.d.ts.map +1 -1
- package/dist/routes/pages/post.js +2 -1
- package/dist/routes/pages/search.d.ts.map +1 -1
- package/dist/routes/pages/search.js +2 -1
- 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 +10 -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 +64 -32
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +52 -0
- package/package.json +1 -1
- package/src/app.tsx +286 -59
- 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/index.ts +0 -2
- package/src/lib/config.ts +120 -0
- package/src/lib/constants.ts +3 -0
- package/src/lib/sse.ts +38 -0
- package/src/lib/theme.ts +86 -0
- package/src/preset.css +9 -0
- package/src/routes/dash/appearance.tsx +180 -0
- package/src/routes/dash/collections.tsx +5 -4
- package/src/routes/dash/index.tsx +2 -1
- package/src/routes/dash/media.tsx +3 -2
- package/src/routes/dash/pages.tsx +5 -4
- package/src/routes/dash/posts.tsx +5 -4
- package/src/routes/dash/redirects.tsx +3 -2
- package/src/routes/dash/settings.tsx +51 -49
- package/src/routes/pages/archive.tsx +2 -1
- package/src/routes/pages/collection.tsx +2 -1
- package/src/routes/pages/home.tsx +2 -1
- package/src/routes/pages/page.tsx +2 -1
- package/src/routes/pages/post.tsx +2 -1
- package/src/routes/pages/search.tsx +2 -1
- 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 +13 -2
- package/src/theme/layouts/index.ts +5 -1
- package/src/types.ts +74 -34
- 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({
|
|
@@ -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
|
@@ -37,8 +37,70 @@ export interface Bindings {
|
|
|
37
37
|
IMAGE_TRANSFORM_URL?: string;
|
|
38
38
|
DEMO_EMAIL?: string;
|
|
39
39
|
DEMO_PASSWORD?: string;
|
|
40
|
+
// Site configuration (optional - can be overridden in DB)
|
|
41
|
+
SITE_NAME?: string;
|
|
42
|
+
SITE_DESCRIPTION?: string;
|
|
43
|
+
SITE_LANGUAGE?: string;
|
|
40
44
|
}
|
|
41
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
|
+
|
|
42
104
|
// =============================================================================
|
|
43
105
|
// Entity Types
|
|
44
106
|
// =============================================================================
|
|
@@ -137,6 +199,7 @@ export interface UpdatePost {
|
|
|
137
199
|
// =============================================================================
|
|
138
200
|
|
|
139
201
|
import type { FC, PropsWithChildren } from "hono/jsx";
|
|
202
|
+
import type { ColorTheme } from "./theme/color-themes.js";
|
|
140
203
|
|
|
141
204
|
/**
|
|
142
205
|
* Props for overridable theme components
|
|
@@ -189,46 +252,23 @@ export interface JantTheme {
|
|
|
189
252
|
name?: string;
|
|
190
253
|
/** Component overrides */
|
|
191
254
|
components?: ThemeComponents;
|
|
192
|
-
/** CSS variable overrides */
|
|
255
|
+
/** CSS variable overrides (highest priority, always applied) */
|
|
193
256
|
cssVariables?: Record<string, string>;
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
/**
|
|
197
|
-
* Site configuration
|
|
198
|
-
*/
|
|
199
|
-
export interface SiteConfig {
|
|
200
|
-
/** Site name */
|
|
201
|
-
name?: string;
|
|
202
|
-
/** Site description */
|
|
203
|
-
description?: string;
|
|
204
|
-
/** Default language */
|
|
205
|
-
language?: string;
|
|
206
|
-
/** Site URL (usually set via env) */
|
|
207
|
-
url?: string;
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
/**
|
|
211
|
-
* Feature toggles
|
|
212
|
-
*/
|
|
213
|
-
export interface FeatureConfig {
|
|
214
|
-
/** Enable search (default: true) */
|
|
215
|
-
search?: boolean;
|
|
216
|
-
/** Enable RSS feed (default: true) */
|
|
217
|
-
rss?: boolean;
|
|
218
|
-
/** Enable sitemap (default: true) */
|
|
219
|
-
sitemap?: boolean;
|
|
220
|
-
/** Enable i18n (default: true) */
|
|
221
|
-
i18n?: boolean;
|
|
257
|
+
/** Replace built-in color themes with a custom list */
|
|
258
|
+
colorThemes?: ColorTheme[];
|
|
222
259
|
}
|
|
223
260
|
|
|
224
261
|
/**
|
|
225
262
|
* Main Jant configuration
|
|
263
|
+
*
|
|
264
|
+
* Configuration Philosophy:
|
|
265
|
+
* - Use environment variables for runtime config (API keys, feature flags, site settings)
|
|
266
|
+
* - Use code config (this object) for compile-time customization (theme components)
|
|
267
|
+
*
|
|
268
|
+
* Site-level settings (name, description, language) are configured via
|
|
269
|
+
* environment variables, not here. See lib/config.ts for details.
|
|
226
270
|
*/
|
|
227
271
|
export interface JantConfig {
|
|
228
|
-
/**
|
|
229
|
-
site?: SiteConfig;
|
|
230
|
-
/** Theme configuration */
|
|
272
|
+
/** Theme configuration (components, CSS overrides) */
|
|
231
273
|
theme?: JantTheme;
|
|
232
|
-
/** Feature toggles */
|
|
233
|
-
features?: FeatureConfig;
|
|
234
274
|
}
|
|
@@ -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;
|