@jant/core 0.2.17 → 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.
Files changed (81) hide show
  1. package/dist/app.d.ts +1 -0
  2. package/dist/app.d.ts.map +1 -1
  3. package/dist/app.js +319 -115
  4. package/dist/i18n/context.d.ts +2 -2
  5. package/dist/i18n/context.js +1 -1
  6. package/dist/i18n/i18n.d.ts +1 -1
  7. package/dist/i18n/i18n.js +1 -1
  8. package/dist/i18n/index.d.ts +1 -1
  9. package/dist/i18n/index.js +1 -1
  10. package/dist/i18n/locales/en.d.ts.map +1 -1
  11. package/dist/i18n/locales/en.js +1 -1
  12. package/dist/i18n/locales/zh-Hans.d.ts.map +1 -1
  13. package/dist/i18n/locales/zh-Hans.js +1 -1
  14. package/dist/i18n/locales/zh-Hant.d.ts.map +1 -1
  15. package/dist/i18n/locales/zh-Hant.js +1 -1
  16. package/dist/lib/config.d.ts +44 -10
  17. package/dist/lib/config.d.ts.map +1 -1
  18. package/dist/lib/config.js +69 -44
  19. package/dist/lib/constants.d.ts +2 -1
  20. package/dist/lib/constants.d.ts.map +1 -1
  21. package/dist/lib/constants.js +5 -2
  22. package/dist/lib/sse.d.ts +15 -0
  23. package/dist/lib/sse.d.ts.map +1 -1
  24. package/dist/lib/sse.js +13 -0
  25. package/dist/lib/theme.d.ts +44 -0
  26. package/dist/lib/theme.d.ts.map +1 -0
  27. package/dist/lib/theme.js +65 -0
  28. package/dist/routes/dash/appearance.d.ts +13 -0
  29. package/dist/routes/dash/appearance.d.ts.map +1 -0
  30. package/dist/routes/dash/appearance.js +164 -0
  31. package/dist/routes/dash/settings.d.ts.map +1 -1
  32. package/dist/routes/dash/settings.js +38 -37
  33. package/dist/services/settings.d.ts +1 -0
  34. package/dist/services/settings.d.ts.map +1 -1
  35. package/dist/services/settings.js +3 -0
  36. package/dist/theme/color-themes.d.ts +30 -0
  37. package/dist/theme/color-themes.d.ts.map +1 -0
  38. package/dist/theme/color-themes.js +268 -0
  39. package/dist/theme/layouts/BaseLayout.d.ts +5 -0
  40. package/dist/theme/layouts/BaseLayout.d.ts.map +1 -1
  41. package/dist/theme/layouts/BaseLayout.js +70 -3
  42. package/dist/theme/layouts/DashLayout.d.ts +2 -0
  43. package/dist/theme/layouts/DashLayout.d.ts.map +1 -1
  44. package/dist/theme/layouts/DashLayout.js +10 -1
  45. package/dist/theme/layouts/index.d.ts +1 -1
  46. package/dist/theme/layouts/index.d.ts.map +1 -1
  47. package/dist/types.d.ts +53 -1
  48. package/dist/types.d.ts.map +1 -1
  49. package/dist/types.js +52 -0
  50. package/package.json +1 -1
  51. package/src/app.tsx +272 -55
  52. package/src/db/migrations/{0000_solid_moon_knight.sql → 0000_square_wallflower.sql} +3 -3
  53. package/src/db/migrations/meta/0000_snapshot.json +9 -9
  54. package/src/db/migrations/meta/_journal.json +2 -30
  55. package/src/i18n/context.tsx +2 -2
  56. package/src/i18n/i18n.ts +1 -1
  57. package/src/i18n/index.ts +1 -1
  58. package/src/i18n/locales/en.po +328 -252
  59. package/src/i18n/locales/en.ts +1 -1
  60. package/src/i18n/locales/zh-Hans.po +315 -278
  61. package/src/i18n/locales/zh-Hans.ts +1 -1
  62. package/src/i18n/locales/zh-Hant.po +315 -278
  63. package/src/i18n/locales/zh-Hant.ts +1 -1
  64. package/src/lib/config.ts +73 -47
  65. package/src/lib/constants.ts +3 -0
  66. package/src/lib/sse.ts +38 -0
  67. package/src/lib/theme.ts +86 -0
  68. package/src/preset.css +9 -0
  69. package/src/routes/dash/appearance.tsx +180 -0
  70. package/src/routes/dash/settings.tsx +50 -52
  71. package/src/services/settings.ts +5 -0
  72. package/src/styles/components.css +93 -0
  73. package/src/theme/color-themes.ts +321 -0
  74. package/src/theme/layouts/BaseLayout.tsx +61 -1
  75. package/src/theme/layouts/DashLayout.tsx +13 -2
  76. package/src/theme/layouts/index.ts +5 -1
  77. package/src/types.ts +62 -1
  78. package/src/db/migrations/0001_add_search_fts.sql +0 -40
  79. package/src/db/migrations/0002_collection_path.sql +0 -2
  80. package/src/db/migrations/0003_collection_path_nullable.sql +0 -21
  81. package/src/db/migrations/0004_media_uuid.sql +0 -35
@@ -0,0 +1,164 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "hono/jsx/jsx-runtime";
2
+ /**
3
+ * Dashboard Appearance Routes
4
+ */ import { Hono } from "hono";
5
+ import { useLingui as $_useLingui } from "@jant/core/i18n";
6
+ import { DashLayout } from "../../theme/layouts/index.js";
7
+ import { sse } from "../../lib/sse.js";
8
+ import { getSiteName } from "../../lib/config.js";
9
+ import { SETTINGS_KEYS } from "../../lib/constants.js";
10
+ import { getAvailableThemes } from "../../lib/theme.js";
11
+ export const appearanceRoutes = new Hono();
12
+ function ThemeCard({ theme, selected }) {
13
+ const expr = `$theme === '${theme.id}'`;
14
+ const { preview } = theme;
15
+ return /*#__PURE__*/ _jsx("label", {
16
+ class: `block cursor-pointer rounded-lg border overflow-hidden transition-colors ${selected ? "border-primary" : "border-border"}`,
17
+ "data-class:border-primary": expr,
18
+ "data-class:border-border": `$theme !== '${theme.id}'`,
19
+ children: /*#__PURE__*/ _jsxs("div", {
20
+ class: "grid grid-cols-2",
21
+ children: [
22
+ /*#__PURE__*/ _jsxs("div", {
23
+ class: "p-5",
24
+ style: `background-color:${preview.lightBg};color:${preview.lightText}`,
25
+ children: [
26
+ /*#__PURE__*/ _jsx("input", {
27
+ type: "radio",
28
+ name: "theme",
29
+ value: theme.id,
30
+ "data-bind": "theme",
31
+ checked: selected || undefined,
32
+ class: "mb-1"
33
+ }),
34
+ /*#__PURE__*/ _jsx("h3", {
35
+ class: "font-bold text-lg",
36
+ children: theme.name
37
+ }),
38
+ /*#__PURE__*/ _jsxs("p", {
39
+ class: "text-sm mt-2 leading-relaxed",
40
+ children: [
41
+ "This is the ",
42
+ theme.name,
43
+ " theme in light mode. Links",
44
+ " ",
45
+ /*#__PURE__*/ _jsx("a", {
46
+ tabIndex: -1,
47
+ class: "underline",
48
+ style: `color:${preview.lightLink}`,
49
+ children: "look like this"
50
+ }),
51
+ ". We'll show the correct light or dark mode based on your visitor's settings."
52
+ ]
53
+ })
54
+ ]
55
+ }),
56
+ /*#__PURE__*/ _jsxs("div", {
57
+ class: "p-5",
58
+ style: `background-color:${preview.darkBg};color:${preview.darkText}`,
59
+ children: [
60
+ /*#__PURE__*/ _jsx("h3", {
61
+ class: "font-bold text-lg",
62
+ children: theme.name
63
+ }),
64
+ /*#__PURE__*/ _jsxs("p", {
65
+ class: "text-sm mt-2 leading-relaxed",
66
+ children: [
67
+ "This is the ",
68
+ theme.name,
69
+ " theme in dark mode. Links",
70
+ " ",
71
+ /*#__PURE__*/ _jsx("a", {
72
+ tabIndex: -1,
73
+ class: "underline",
74
+ style: `color:${preview.darkLink}`,
75
+ children: "look like this"
76
+ }),
77
+ ". We'll show the correct light or dark mode based on your visitor's settings."
78
+ ]
79
+ })
80
+ ]
81
+ })
82
+ ]
83
+ })
84
+ });
85
+ }
86
+ function AppearanceContent({ themes, currentThemeId }) {
87
+ const { i18n: $__i18n, _: $__ } = $_useLingui();
88
+ const signals = JSON.stringify({
89
+ theme: currentThemeId
90
+ }).replace(/</g, "\\u003c");
91
+ return /*#__PURE__*/ _jsx("div", {
92
+ "data-signals": signals,
93
+ "data-on:change": "@post('/dash/appearance')",
94
+ class: "max-w-3xl",
95
+ children: /*#__PURE__*/ _jsxs("fieldset", {
96
+ children: [
97
+ /*#__PURE__*/ _jsx("legend", {
98
+ class: "text-lg font-semibold",
99
+ children: $__i18n._({
100
+ id: "rFmBG3",
101
+ message: "Color theme"
102
+ })
103
+ }),
104
+ /*#__PURE__*/ _jsx("p", {
105
+ class: "text-sm text-muted-foreground mb-4",
106
+ children: $__i18n._({
107
+ id: "07Epll",
108
+ message: "This will theme both your site and your dashboard. All color themes support dark mode."
109
+ })
110
+ }),
111
+ /*#__PURE__*/ _jsx("div", {
112
+ class: "flex flex-col gap-4",
113
+ children: themes.map((theme)=>/*#__PURE__*/ _jsx(ThemeCard, {
114
+ theme: theme,
115
+ selected: theme.id === currentThemeId
116
+ }, theme.id))
117
+ })
118
+ ]
119
+ })
120
+ });
121
+ }
122
+ // Appearance page
123
+ appearanceRoutes.get("/", async (c)=>{
124
+ const { settings } = c.var.services;
125
+ const siteName = await getSiteName(c);
126
+ const currentThemeId = await settings.get(SETTINGS_KEYS.THEME) ?? "default";
127
+ const themes = getAvailableThemes(c.var.config);
128
+ const saved = c.req.query("saved") !== undefined;
129
+ return c.html(/*#__PURE__*/ _jsx(DashLayout, {
130
+ c: c,
131
+ title: "Appearance",
132
+ siteName: siteName,
133
+ currentPath: "/dash/appearance",
134
+ toast: saved ? {
135
+ message: "Theme saved successfully."
136
+ } : undefined,
137
+ children: /*#__PURE__*/ _jsx(AppearanceContent, {
138
+ themes: themes,
139
+ currentThemeId: currentThemeId
140
+ })
141
+ }));
142
+ });
143
+ // Save theme
144
+ appearanceRoutes.post("/", async (c)=>{
145
+ const body = await c.req.json();
146
+ const { settings } = c.var.services;
147
+ const themes = getAvailableThemes(c.var.config);
148
+ // Validate theme ID
149
+ const validTheme = themes.find((t)=>t.id === body.theme);
150
+ if (!validTheme) {
151
+ return sse(c, async (stream)=>{
152
+ await stream.toast("Invalid theme selected.", "error");
153
+ });
154
+ }
155
+ if (validTheme.id === "default") {
156
+ await settings.remove(SETTINGS_KEYS.THEME);
157
+ } else {
158
+ await settings.set(SETTINGS_KEYS.THEME, validTheme.id);
159
+ }
160
+ // Full page reload to apply the new theme CSS
161
+ return sse(c, async (stream)=>{
162
+ await stream.redirect("/dash/appearance?saved");
163
+ });
164
+ });
@@ -1 +1 @@
1
- {"version":3,"file":"settings.d.ts","sourceRoot":"","sources":["../../../src/routes/dash/settings.tsx"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAE5B,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAC/C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AASjD,KAAK,GAAG,GAAG;IAAE,QAAQ,EAAE,QAAQ,CAAC;IAAC,SAAS,EAAE,YAAY,CAAA;CAAE,CAAC;AAE3D,eAAO,MAAM,cAAc,kDAAkB,CAAC"}
1
+ {"version":3,"file":"settings.d.ts","sourceRoot":"","sources":["../../../src/routes/dash/settings.tsx"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAE5B,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAC/C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAKjD,KAAK,GAAG,GAAG;IAAE,QAAQ,EAAE,QAAQ,CAAC;IAAC,SAAS,EAAE,YAAY,CAAA;CAAE,CAAC;AAE3D,eAAO,MAAM,cAAc,kDAAkB,CAAC"}
@@ -5,9 +5,9 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "hono/jsx/jsx-
5
5
  import { useLingui as $_useLingui } from "@jant/core/i18n";
6
6
  import { DashLayout } from "../../theme/layouts/index.js";
7
7
  import { sse } from "../../lib/sse.js";
8
- import { getSiteName, getSiteDescription, getSiteLanguage } from "../../lib/config.js";
8
+ import { getSiteLanguage, getConfigFallback } from "../../lib/config.js";
9
9
  export const settingsRoutes = new Hono();
10
- function SettingsContent({ siteName, siteDescription, siteLanguage, saved }) {
10
+ function SettingsContent({ siteName, siteDescription, siteLanguage, siteNameFallback, siteDescriptionFallback }) {
11
11
  const { i18n: $__i18n, _: $__ } = $_useLingui();
12
12
  const generalSignals = JSON.stringify({
13
13
  siteName,
@@ -23,17 +23,6 @@ function SettingsContent({ siteName, siteDescription, siteLanguage, saved }) {
23
23
  message: "Settings"
24
24
  })
25
25
  }),
26
- saved && /*#__PURE__*/ _jsx("div", {
27
- id: "settings-saved-toast",
28
- class: "alert mb-4 max-w-lg transition-opacity duration-300",
29
- "data-init": `console.log('[toast] init fired at', Date.now()); history.replaceState({}, '', '/dash/settings'); setTimeout(() => { console.log('[toast] hiding at', Date.now()); const el = document.getElementById('settings-saved-toast'); if (el) { el.style.opacity = '0'; setTimeout(() => el.remove(), 300) } }, 3000)`,
30
- children: /*#__PURE__*/ _jsx("h2", {
31
- children: $__i18n._({
32
- id: "T0bsor",
33
- message: "Settings saved successfully."
34
- })
35
- })
36
- }),
37
26
  /*#__PURE__*/ _jsxs("div", {
38
27
  class: "flex flex-col gap-6 max-w-lg",
39
28
  children: [
@@ -41,9 +30,6 @@ function SettingsContent({ siteName, siteDescription, siteLanguage, saved }) {
41
30
  "data-signals": generalSignals,
42
31
  "data-on:submit__prevent": "@post('/dash/settings')",
43
32
  children: [
44
- /*#__PURE__*/ _jsx("div", {
45
- id: "settings-message"
46
- }),
47
33
  /*#__PURE__*/ _jsxs("div", {
48
34
  class: "card",
49
35
  children: [
@@ -72,7 +58,7 @@ function SettingsContent({ siteName, siteDescription, siteLanguage, saved }) {
72
58
  type: "text",
73
59
  "data-bind": "siteName",
74
60
  class: "input",
75
- required: true
61
+ placeholder: siteNameFallback
76
62
  })
77
63
  ]
78
64
  }),
@@ -90,6 +76,7 @@ function SettingsContent({ siteName, siteDescription, siteLanguage, saved }) {
90
76
  "data-bind": "siteDescription",
91
77
  class: "textarea",
92
78
  rows: 3,
79
+ placeholder: siteDescriptionFallback,
93
80
  children: siteDescription
94
81
  })
95
82
  ]
@@ -145,9 +132,6 @@ function SettingsContent({ siteName, siteDescription, siteLanguage, saved }) {
145
132
  "data-signals": "{currentPassword: '', newPassword: '', confirmPassword: ''}",
146
133
  "data-on:submit__prevent": "@post('/dash/settings/password')",
147
134
  children: [
148
- /*#__PURE__*/ _jsx("div", {
149
- id: "password-message"
150
- }),
151
135
  /*#__PURE__*/ _jsxs("div", {
152
136
  class: "card",
153
137
  children: [
@@ -242,40 +226,57 @@ function SettingsContent({ siteName, siteDescription, siteLanguage, saved }) {
242
226
  }
243
227
  // Settings page
244
228
  settingsRoutes.get("/", async (c)=>{
245
- const siteName = await getSiteName(c);
246
- const siteDescription = await getSiteDescription(c);
229
+ const { settings } = c.var.services;
230
+ // Fetch raw DB values (null if not set)
231
+ const dbSiteName = await settings.get("SITE_NAME");
232
+ const dbSiteDescription = await settings.get("SITE_DESCRIPTION");
247
233
  const siteLanguage = await getSiteLanguage(c);
234
+ // Fallback values (ENV > Default) for placeholders
235
+ const siteNameFallback = getConfigFallback(c, "SITE_NAME");
236
+ const siteDescriptionFallback = getConfigFallback(c, "SITE_DESCRIPTION");
248
237
  const saved = c.req.query("saved") !== undefined;
249
238
  return c.html(/*#__PURE__*/ _jsx(DashLayout, {
250
239
  c: c,
251
240
  title: "Settings",
252
- siteName: siteName,
241
+ siteName: dbSiteName || siteNameFallback,
253
242
  currentPath: "/dash/settings",
243
+ toast: saved ? {
244
+ message: "Settings saved successfully."
245
+ } : undefined,
254
246
  children: /*#__PURE__*/ _jsx(SettingsContent, {
255
- siteName: siteName,
256
- siteDescription: siteDescription,
247
+ siteName: dbSiteName || "",
248
+ siteDescription: dbSiteDescription || "",
257
249
  siteLanguage: siteLanguage,
258
- saved: saved
250
+ siteNameFallback: siteNameFallback,
251
+ siteDescriptionFallback: siteDescriptionFallback
259
252
  })
260
253
  }));
261
254
  });
262
255
  // Update settings
263
256
  settingsRoutes.post("/", async (c)=>{
264
257
  const body = await c.req.json();
265
- const oldLanguage = await c.var.services.settings.get("SITE_LANGUAGE") ?? "en";
266
- await c.var.services.settings.setMany({
267
- SITE_NAME: body.siteName,
268
- SITE_DESCRIPTION: body.siteDescription,
269
- SITE_LANGUAGE: body.siteLanguage
270
- });
258
+ const { settings } = c.var.services;
259
+ const oldLanguage = await settings.get("SITE_LANGUAGE") ?? "en";
260
+ // For text fields: empty = remove from DB (fall back to ENV > Default)
261
+ if (body.siteName.trim()) {
262
+ await settings.set("SITE_NAME", body.siteName.trim());
263
+ } else {
264
+ await settings.remove("SITE_NAME");
265
+ }
266
+ if (body.siteDescription.trim()) {
267
+ await settings.set("SITE_DESCRIPTION", body.siteDescription.trim());
268
+ } else {
269
+ await settings.remove("SITE_DESCRIPTION");
270
+ }
271
+ // Language always has a value from the select
272
+ await settings.set("SITE_LANGUAGE", body.siteLanguage);
271
273
  const languageChanged = oldLanguage !== body.siteLanguage;
272
274
  return sse(c, async (stream)=>{
273
275
  if (languageChanged) {
274
276
  // Language changed - full reload needed to update all UI text
275
277
  await stream.redirect("/dash/settings?saved");
276
278
  } else {
277
- // No language change - show inline success message
278
- await stream.patchElements('<div id="settings-message"><div class="alert mb-4 transition-opacity duration-300" data-init="setTimeout(() => { el.style.opacity = \'0\'; setTimeout(() => el.remove(), 300) }, 3000)"><h2>Settings saved successfully.</h2></div></div>');
279
+ await stream.toast("Settings saved successfully.");
279
280
  }
280
281
  });
281
282
  });
@@ -284,7 +285,7 @@ settingsRoutes.post("/password", async (c)=>{
284
285
  const body = await c.req.json();
285
286
  if (body.newPassword !== body.confirmPassword) {
286
287
  return sse(c, async (stream)=>{
287
- await stream.patchElements('<div id="password-message"><div class="alert-destructive mb-4"><h2>Passwords do not match.</h2></div></div>');
288
+ await stream.toast("Passwords do not match.", "error");
288
289
  });
289
290
  }
290
291
  try {
@@ -298,11 +299,11 @@ settingsRoutes.post("/password", async (c)=>{
298
299
  });
299
300
  } catch {
300
301
  return sse(c, async (stream)=>{
301
- await stream.patchElements('<div id="password-message"><div class="alert-destructive mb-4"><h2>Current password is incorrect.</h2></div></div>');
302
+ await stream.toast("Current password is incorrect.", "error");
302
303
  });
303
304
  }
304
305
  return sse(c, async (stream)=>{
305
- await stream.patchElements('<div id="password-message"><div class="alert mb-4"><h2>Password changed successfully.</h2></div></div>');
306
+ await stream.toast("Password changed successfully.");
306
307
  await stream.patchSignals({
307
308
  currentPassword: "",
308
309
  newPassword: "",
@@ -10,6 +10,7 @@ export interface SettingsService {
10
10
  getAll(): Promise<Record<string, string>>;
11
11
  set(key: SettingsKey, value: string): Promise<void>;
12
12
  setMany(entries: Partial<Record<SettingsKey, string>>): Promise<void>;
13
+ remove(key: SettingsKey): Promise<void>;
13
14
  isOnboardingComplete(): Promise<boolean>;
14
15
  completeOnboarding(): Promise<void>;
15
16
  }
@@ -1 +1 @@
1
- {"version":3,"file":"settings.d.ts","sourceRoot":"","sources":["../../src/services/settings.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAG/C,OAAO,EAGL,KAAK,WAAW,EACjB,MAAM,qBAAqB,CAAC;AAE7B,MAAM,WAAW,eAAe;IAC9B,GAAG,CAAC,GAAG,EAAE,WAAW,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IAC9C,MAAM,IAAI,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IAC1C,GAAG,CAAC,GAAG,EAAE,WAAW,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACpD,OAAO,CAAC,OAAO,EAAE,OAAO,CAAC,MAAM,CAAC,WAAW,EAAE,MAAM,CAAC,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACtE,oBAAoB,IAAI,OAAO,CAAC,OAAO,CAAC,CAAC;IACzC,kBAAkB,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACrC;AAED,wBAAgB,qBAAqB,CAAC,EAAE,EAAE,QAAQ,GAAG,eAAe,CA6DnE"}
1
+ {"version":3,"file":"settings.d.ts","sourceRoot":"","sources":["../../src/services/settings.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAG/C,OAAO,EAGL,KAAK,WAAW,EACjB,MAAM,qBAAqB,CAAC;AAE7B,MAAM,WAAW,eAAe;IAC9B,GAAG,CAAC,GAAG,EAAE,WAAW,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IAC9C,MAAM,IAAI,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IAC1C,GAAG,CAAC,GAAG,EAAE,WAAW,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACpD,OAAO,CAAC,OAAO,EAAE,OAAO,CAAC,MAAM,CAAC,WAAW,EAAE,MAAM,CAAC,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACtE,MAAM,CAAC,GAAG,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACxC,oBAAoB,IAAI,OAAO,CAAC,OAAO,CAAC,CAAC;IACzC,kBAAkB,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACrC;AAED,wBAAgB,qBAAqB,CAAC,EAAE,EAAE,QAAQ,GAAG,eAAe,CAiEnE"}
@@ -34,6 +34,9 @@ export function createSettingsService(db) {
34
34
  }
35
35
  });
36
36
  },
37
+ async remove (key) {
38
+ await db.delete(settings).where(eq(settings.key, key));
39
+ },
37
40
  async setMany (entries) {
38
41
  const timestamp = now();
39
42
  const keys = Object.keys(entries);
@@ -0,0 +1,30 @@
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
+ * A color theme definition with light and dark mode CSS variable overrides.
9
+ */
10
+ export interface ColorTheme {
11
+ /** Stored in DB settings, e.g. "beach" */
12
+ id: string;
13
+ /** Display name, e.g. "Beach" */
14
+ name: string;
15
+ /** CSS variable overrides for :root (light mode) */
16
+ light: Record<string, string>;
17
+ /** CSS variable overrides for .dark (dark mode) */
18
+ dark: Record<string, string>;
19
+ /** Preview colors (hex) for theme picker cards */
20
+ preview: {
21
+ lightBg: string;
22
+ lightText: string;
23
+ lightLink: string;
24
+ darkBg: string;
25
+ darkText: string;
26
+ darkLink: string;
27
+ };
28
+ }
29
+ export declare const BUILTIN_COLOR_THEMES: ColorTheme[];
30
+ //# sourceMappingURL=color-themes.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"color-themes.d.ts","sourceRoot":"","sources":["../../src/theme/color-themes.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH;;GAEG;AACH,MAAM,WAAW,UAAU;IACzB,0CAA0C;IAC1C,EAAE,EAAE,MAAM,CAAC;IACX,iCAAiC;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,oDAAoD;IACpD,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC9B,mDAAmD;IACnD,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC7B,kDAAkD;IAClD,OAAO,EAAE;QACP,OAAO,EAAE,MAAM,CAAC;QAChB,SAAS,EAAE,MAAM,CAAC;QAClB,SAAS,EAAE,MAAM,CAAC;QAClB,MAAM,EAAE,MAAM,CAAC;QACf,QAAQ,EAAE,MAAM,CAAC;QACjB,QAAQ,EAAE,MAAM,CAAC;KAClB,CAAC;CACH;AA2FD,eAAO,MAAM,oBAAoB,EAAE,UAAU,EAyM5C,CAAC"}
@@ -0,0 +1,268 @@
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
+ * A color theme definition with light and dark mode CSS variable overrides.
8
+ */ /**
9
+ * Create a comprehensive color theme from key colors.
10
+ * Derives card, popover, muted, secondary, accent, and sidebar variables.
11
+ */ function defineTheme(opts) {
12
+ const { light, dark } = opts;
13
+ return {
14
+ id: opts.id,
15
+ name: opts.name,
16
+ preview: opts.preview,
17
+ light: {
18
+ "--background": light.bg,
19
+ "--foreground": light.fg,
20
+ "--card": light.bg,
21
+ "--card-foreground": light.fg,
22
+ "--popover": light.bg,
23
+ "--popover-foreground": light.fg,
24
+ "--primary": light.primary,
25
+ "--primary-foreground": light.primaryFg,
26
+ "--secondary": light.muted,
27
+ "--secondary-foreground": light.fg,
28
+ "--muted": light.muted,
29
+ "--muted-foreground": light.mutedFg,
30
+ "--accent": light.muted,
31
+ "--accent-foreground": light.fg,
32
+ "--border": light.border,
33
+ "--input": light.border,
34
+ "--ring": light.primary,
35
+ "--sidebar": light.bg,
36
+ "--sidebar-foreground": light.fg,
37
+ "--sidebar-primary": light.primary,
38
+ "--sidebar-primary-foreground": light.primaryFg,
39
+ "--sidebar-accent": light.muted,
40
+ "--sidebar-accent-foreground": light.fg,
41
+ "--sidebar-border": light.border,
42
+ "--sidebar-ring": light.primary
43
+ },
44
+ dark: {
45
+ "--background": dark.bg,
46
+ "--foreground": dark.fg,
47
+ "--card": dark.bg,
48
+ "--card-foreground": dark.fg,
49
+ "--popover": dark.bg,
50
+ "--popover-foreground": dark.fg,
51
+ "--primary": dark.primary,
52
+ "--primary-foreground": dark.primaryFg,
53
+ "--secondary": dark.muted,
54
+ "--secondary-foreground": dark.fg,
55
+ "--muted": dark.muted,
56
+ "--muted-foreground": dark.mutedFg,
57
+ "--accent": dark.muted,
58
+ "--accent-foreground": dark.fg,
59
+ "--border": dark.border,
60
+ "--input": dark.border,
61
+ "--ring": dark.primary,
62
+ "--sidebar": dark.bg,
63
+ "--sidebar-foreground": dark.fg,
64
+ "--sidebar-primary": dark.primary,
65
+ "--sidebar-primary-foreground": dark.primaryFg,
66
+ "--sidebar-accent": dark.muted,
67
+ "--sidebar-accent-foreground": dark.fg,
68
+ "--sidebar-border": dark.border,
69
+ "--sidebar-ring": dark.primary
70
+ }
71
+ };
72
+ }
73
+ export const BUILTIN_COLOR_THEMES = [
74
+ {
75
+ id: "default",
76
+ name: "Default",
77
+ light: {},
78
+ dark: {},
79
+ preview: {
80
+ lightBg: "#ffffff",
81
+ lightText: "#1e1e1e",
82
+ lightLink: "#1e1e1e",
83
+ darkBg: "#262626",
84
+ darkText: "#fafafa",
85
+ darkLink: "#eaeaea"
86
+ }
87
+ },
88
+ defineTheme({
89
+ id: "beach",
90
+ name: "Beach",
91
+ preview: {
92
+ lightBg: "#f9f3ea",
93
+ lightText: "#3d3527",
94
+ lightLink: "#2d6a59",
95
+ darkBg: "#2d4553",
96
+ darkText: "#e2d6c4",
97
+ darkLink: "#7cc5a2"
98
+ },
99
+ light: {
100
+ bg: "oklch(0.97 0.01 85)",
101
+ fg: "oklch(0.28 0.02 65)",
102
+ primary: "oklch(0.46 0.1 170)",
103
+ primaryFg: "oklch(0.98 0.005 85)",
104
+ muted: "oklch(0.93 0.015 85)",
105
+ mutedFg: "oklch(0.52 0.015 65)",
106
+ border: "oklch(0.88 0.018 85)"
107
+ },
108
+ dark: {
109
+ bg: "oklch(0.27 0.03 210)",
110
+ fg: "oklch(0.88 0.015 80)",
111
+ primary: "oklch(0.72 0.1 165)",
112
+ primaryFg: "oklch(0.22 0.03 210)",
113
+ muted: "oklch(0.33 0.025 210)",
114
+ mutedFg: "oklch(0.65 0.015 80)",
115
+ border: "oklch(0.38 0.02 210)"
116
+ }
117
+ }),
118
+ defineTheme({
119
+ id: "gameboy",
120
+ name: "Gameboy",
121
+ preview: {
122
+ lightBg: "#d3d7c0",
123
+ lightText: "#2b3326",
124
+ lightLink: "#466740",
125
+ darkBg: "#1b1f18",
126
+ darkText: "#a6b09a",
127
+ darkLink: "#6d9660"
128
+ },
129
+ light: {
130
+ bg: "oklch(0.87 0.03 130)",
131
+ fg: "oklch(0.25 0.04 140)",
132
+ primary: "oklch(0.4 0.08 145)",
133
+ primaryFg: "oklch(0.92 0.02 130)",
134
+ muted: "oklch(0.83 0.035 130)",
135
+ mutedFg: "oklch(0.48 0.03 140)",
136
+ border: "oklch(0.79 0.035 130)"
137
+ },
138
+ dark: {
139
+ bg: "oklch(0.18 0.02 140)",
140
+ fg: "oklch(0.78 0.025 130)",
141
+ primary: "oklch(0.6 0.08 145)",
142
+ primaryFg: "oklch(0.15 0.02 140)",
143
+ muted: "oklch(0.24 0.02 140)",
144
+ mutedFg: "oklch(0.58 0.02 130)",
145
+ border: "oklch(0.3 0.02 140)"
146
+ }
147
+ }),
148
+ defineTheme({
149
+ id: "grayscale",
150
+ name: "Grayscale",
151
+ preview: {
152
+ lightBg: "#efefef",
153
+ lightText: "#3a3a3a",
154
+ lightLink: "#555555",
155
+ darkBg: "#1e1e1e",
156
+ darkText: "#c8c8c8",
157
+ darkLink: "#999999"
158
+ },
159
+ light: {
160
+ bg: "oklch(0.96 0 0)",
161
+ fg: "oklch(0.3 0 0)",
162
+ primary: "oklch(0.4 0 0)",
163
+ primaryFg: "oklch(0.96 0 0)",
164
+ muted: "oklch(0.92 0 0)",
165
+ mutedFg: "oklch(0.55 0 0)",
166
+ border: "oklch(0.87 0 0)"
167
+ },
168
+ dark: {
169
+ bg: "oklch(0.18 0 0)",
170
+ fg: "oklch(0.82 0 0)",
171
+ primary: "oklch(0.7 0 0)",
172
+ primaryFg: "oklch(0.18 0 0)",
173
+ muted: "oklch(0.24 0 0)",
174
+ mutedFg: "oklch(0.6 0 0)",
175
+ border: "oklch(0.3 0 0)"
176
+ }
177
+ }),
178
+ defineTheme({
179
+ id: "halloween",
180
+ name: "Halloween",
181
+ preview: {
182
+ lightBg: "#f9f2e3",
183
+ lightText: "#352200",
184
+ lightLink: "#cc5500",
185
+ darkBg: "#1e1000",
186
+ darkText: "#dfc390",
187
+ darkLink: "#ff8c00"
188
+ },
189
+ light: {
190
+ bg: "oklch(0.97 0.015 75)",
191
+ fg: "oklch(0.25 0.04 55)",
192
+ primary: "oklch(0.6 0.2 50)",
193
+ primaryFg: "oklch(0.98 0.01 75)",
194
+ muted: "oklch(0.93 0.02 75)",
195
+ mutedFg: "oklch(0.5 0.025 55)",
196
+ border: "oklch(0.88 0.025 75)"
197
+ },
198
+ dark: {
199
+ bg: "oklch(0.16 0.03 50)",
200
+ fg: "oklch(0.85 0.025 75)",
201
+ primary: "oklch(0.72 0.19 55)",
202
+ primaryFg: "oklch(0.14 0.03 50)",
203
+ muted: "oklch(0.22 0.025 50)",
204
+ mutedFg: "oklch(0.62 0.02 75)",
205
+ border: "oklch(0.28 0.025 50)"
206
+ }
207
+ }),
208
+ defineTheme({
209
+ id: "notepad",
210
+ name: "Notepad",
211
+ preview: {
212
+ lightBg: "#fdfce8",
213
+ lightText: "#333333",
214
+ lightLink: "#2060b8",
215
+ darkBg: "#2a291a",
216
+ darkText: "#d2d2b8",
217
+ darkLink: "#6695cc"
218
+ },
219
+ light: {
220
+ bg: "oklch(0.985 0.018 95)",
221
+ fg: "oklch(0.27 0 0)",
222
+ primary: "oklch(0.5 0.17 260)",
223
+ primaryFg: "oklch(0.985 0.01 95)",
224
+ muted: "oklch(0.94 0.022 95)",
225
+ mutedFg: "oklch(0.52 0 0)",
226
+ border: "oklch(0.88 0.025 95)"
227
+ },
228
+ dark: {
229
+ bg: "oklch(0.2 0.02 90)",
230
+ fg: "oklch(0.87 0.015 95)",
231
+ primary: "oklch(0.65 0.14 260)",
232
+ primaryFg: "oklch(0.98 0.01 95)",
233
+ muted: "oklch(0.26 0.018 90)",
234
+ mutedFg: "oklch(0.62 0.012 95)",
235
+ border: "oklch(0.32 0.018 90)"
236
+ }
237
+ }),
238
+ defineTheme({
239
+ id: "sonnet",
240
+ name: "Sonnet",
241
+ preview: {
242
+ lightBg: "#f7eef5",
243
+ lightText: "#2e1e2c",
244
+ lightLink: "#9845c8",
245
+ darkBg: "#1d1428",
246
+ darkText: "#d4c2d0",
247
+ darkLink: "#c080fc"
248
+ },
249
+ light: {
250
+ bg: "oklch(0.97 0.012 325)",
251
+ fg: "oklch(0.25 0.02 310)",
252
+ primary: "oklch(0.55 0.2 300)",
253
+ primaryFg: "oklch(0.98 0.008 325)",
254
+ muted: "oklch(0.93 0.016 325)",
255
+ mutedFg: "oklch(0.52 0.015 310)",
256
+ border: "oklch(0.88 0.016 325)"
257
+ },
258
+ dark: {
259
+ bg: "oklch(0.18 0.025 300)",
260
+ fg: "oklch(0.87 0.012 325)",
261
+ primary: "oklch(0.72 0.18 300)",
262
+ primaryFg: "oklch(0.98 0.008 325)",
263
+ muted: "oklch(0.24 0.022 300)",
264
+ mutedFg: "oklch(0.62 0.012 325)",
265
+ border: "oklch(0.3 0.022 300)"
266
+ }
267
+ })
268
+ ];