@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
package/dist/app.d.ts
CHANGED
package/dist/app.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"app.d.ts","sourceRoot":"","sources":["../src/app.tsx"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAG5B,OAAO,EAAkB,KAAK,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AACpE,OAAO,EAAc,KAAK,IAAI,EAAE,MAAM,WAAW,CAAC;AAGlD,OAAO,KAAK,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"app.d.ts","sourceRoot":"","sources":["../src/app.tsx"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAG5B,OAAO,EAAkB,KAAK,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AACpE,OAAO,EAAc,KAAK,IAAI,EAAE,MAAM,WAAW,CAAC;AAGlD,OAAO,KAAK,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAwCvD,MAAM,WAAW,YAAY;IAC3B,QAAQ,EAAE,QAAQ,CAAC;IACnB,IAAI,EAAE,IAAI,CAAC;IACX,MAAM,EAAE,UAAU,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,MAAM,GAAG,GAAG,IAAI,CAAC;IAAE,QAAQ,EAAE,QAAQ,CAAC;IAAC,SAAS,EAAE,YAAY,CAAA;CAAE,CAAC,CAAC;AAExE;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,SAAS,CAAC,MAAM,GAAE,UAAe,GAAG,GAAG,CAyoBtD"}
|
package/dist/app.js
CHANGED
|
@@ -7,6 +7,8 @@ import { createServices } from "./services/index.js";
|
|
|
7
7
|
import { createAuth } from "./auth.js";
|
|
8
8
|
import { i18nMiddleware } from "./i18n/index.js";
|
|
9
9
|
import { useLingui as $_useLingui } from "@jant/core/i18n";
|
|
10
|
+
import { SETTINGS_KEYS } from "./lib/constants.js";
|
|
11
|
+
import { hashPassword } from "better-auth/crypto";
|
|
10
12
|
// Routes - Pages
|
|
11
13
|
import { homeRoutes } from "./routes/pages/home.js";
|
|
12
14
|
import { postRoutes } from "./routes/pages/post.js";
|
|
@@ -22,6 +24,7 @@ import { mediaRoutes as dashMediaRoutes } from "./routes/dash/media.js";
|
|
|
22
24
|
import { settingsRoutes as dashSettingsRoutes } from "./routes/dash/settings.js";
|
|
23
25
|
import { redirectsRoutes as dashRedirectsRoutes } from "./routes/dash/redirects.js";
|
|
24
26
|
import { collectionsRoutes as dashCollectionsRoutes } from "./routes/dash/collections.js";
|
|
27
|
+
import { appearanceRoutes as dashAppearanceRoutes } from "./routes/dash/appearance.js";
|
|
25
28
|
// Routes - API
|
|
26
29
|
import { postsApiRoutes } from "./routes/api/posts.js";
|
|
27
30
|
import { uploadApiRoutes } from "./routes/api/upload.js";
|
|
@@ -33,7 +36,8 @@ import { sitemapRoutes } from "./routes/feed/sitemap.js";
|
|
|
33
36
|
import { requireAuth } from "./middleware/auth.js";
|
|
34
37
|
// Layouts for auth pages
|
|
35
38
|
import { BaseLayout } from "./theme/layouts/index.js";
|
|
36
|
-
import {
|
|
39
|
+
import { dsRedirect, dsToast } from "./lib/sse.js";
|
|
40
|
+
import { getAvailableThemes, buildThemeStyle } from "./lib/theme.js";
|
|
37
41
|
/**
|
|
38
42
|
* Create a Jant application
|
|
39
43
|
*
|
|
@@ -75,6 +79,15 @@ import { sse } from "./lib/sse.js";
|
|
|
75
79
|
}
|
|
76
80
|
await next();
|
|
77
81
|
});
|
|
82
|
+
// Theme middleware - resolve active color theme and build CSS
|
|
83
|
+
app.use("*", async (c, next)=>{
|
|
84
|
+
const themeId = await c.var.services.settings.get(SETTINGS_KEYS.THEME);
|
|
85
|
+
const themes = getAvailableThemes(config);
|
|
86
|
+
const activeTheme = themeId ? themes.find((t)=>t.id === themeId) : undefined;
|
|
87
|
+
const themeStyle = buildThemeStyle(activeTheme, config.theme?.cssVariables);
|
|
88
|
+
c.set("themeStyle", themeStyle);
|
|
89
|
+
await next();
|
|
90
|
+
});
|
|
78
91
|
// i18n middleware
|
|
79
92
|
app.use("*", i18nMiddleware());
|
|
80
93
|
// Trailing slash redirect (redirect /foo/ to /foo)
|
|
@@ -134,112 +147,85 @@ import { sse } from "./lib/sse.js";
|
|
|
134
147
|
}),
|
|
135
148
|
/*#__PURE__*/ _jsx("p", {
|
|
136
149
|
children: $__i18n._({
|
|
137
|
-
id: "
|
|
138
|
-
message: "
|
|
150
|
+
id: "GX2VMa",
|
|
151
|
+
message: "Create your admin account."
|
|
139
152
|
})
|
|
140
153
|
})
|
|
141
154
|
]
|
|
142
155
|
}),
|
|
143
|
-
/*#__PURE__*/
|
|
144
|
-
children:
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
"
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
class: "label",
|
|
158
|
-
children: $__i18n._({
|
|
159
|
-
id: "SJmfuf",
|
|
160
|
-
message: "Site Name"
|
|
161
|
-
})
|
|
162
|
-
}),
|
|
163
|
-
/*#__PURE__*/ _jsx("input", {
|
|
164
|
-
type: "text",
|
|
165
|
-
"data-bind": "siteName",
|
|
166
|
-
class: "input",
|
|
167
|
-
required: true,
|
|
168
|
-
placeholder: $__i18n._({
|
|
169
|
-
id: "HfyyXl",
|
|
170
|
-
message: "My Blog"
|
|
171
|
-
})
|
|
156
|
+
/*#__PURE__*/ _jsx("section", {
|
|
157
|
+
children: /*#__PURE__*/ _jsxs("form", {
|
|
158
|
+
"data-signals": "{name: '', email: '', password: ''}",
|
|
159
|
+
"data-on:submit__prevent": "@post('/setup')",
|
|
160
|
+
class: "flex flex-col gap-4",
|
|
161
|
+
children: [
|
|
162
|
+
/*#__PURE__*/ _jsxs("div", {
|
|
163
|
+
class: "field",
|
|
164
|
+
children: [
|
|
165
|
+
/*#__PURE__*/ _jsx("label", {
|
|
166
|
+
class: "label",
|
|
167
|
+
children: $__i18n._({
|
|
168
|
+
id: "/Rj5P4",
|
|
169
|
+
message: "Your Name"
|
|
172
170
|
})
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
})
|
|
192
|
-
]
|
|
193
|
-
}),
|
|
194
|
-
/*#__PURE__*/ _jsxs("div", {
|
|
195
|
-
class: "field",
|
|
196
|
-
children: [
|
|
197
|
-
/*#__PURE__*/ _jsx("label", {
|
|
198
|
-
class: "label",
|
|
199
|
-
children: $__i18n._({
|
|
200
|
-
id: "O3oNi5",
|
|
201
|
-
message: "Email"
|
|
202
|
-
})
|
|
203
|
-
}),
|
|
204
|
-
/*#__PURE__*/ _jsx("input", {
|
|
205
|
-
type: "email",
|
|
206
|
-
"data-bind": "email",
|
|
207
|
-
class: "input",
|
|
208
|
-
required: true,
|
|
209
|
-
placeholder: "you@example.com"
|
|
171
|
+
}),
|
|
172
|
+
/*#__PURE__*/ _jsx("input", {
|
|
173
|
+
type: "text",
|
|
174
|
+
"data-bind": "name",
|
|
175
|
+
class: "input",
|
|
176
|
+
required: true,
|
|
177
|
+
placeholder: "John Doe"
|
|
178
|
+
})
|
|
179
|
+
]
|
|
180
|
+
}),
|
|
181
|
+
/*#__PURE__*/ _jsxs("div", {
|
|
182
|
+
class: "field",
|
|
183
|
+
children: [
|
|
184
|
+
/*#__PURE__*/ _jsx("label", {
|
|
185
|
+
class: "label",
|
|
186
|
+
children: $__i18n._({
|
|
187
|
+
id: "O3oNi5",
|
|
188
|
+
message: "Email"
|
|
210
189
|
})
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
190
|
+
}),
|
|
191
|
+
/*#__PURE__*/ _jsx("input", {
|
|
192
|
+
type: "email",
|
|
193
|
+
"data-bind": "email",
|
|
194
|
+
class: "input",
|
|
195
|
+
required: true,
|
|
196
|
+
placeholder: "you@example.com"
|
|
197
|
+
})
|
|
198
|
+
]
|
|
199
|
+
}),
|
|
200
|
+
/*#__PURE__*/ _jsxs("div", {
|
|
201
|
+
class: "field",
|
|
202
|
+
children: [
|
|
203
|
+
/*#__PURE__*/ _jsx("label", {
|
|
204
|
+
class: "label",
|
|
205
|
+
children: $__i18n._({
|
|
206
|
+
id: "8ZsakT",
|
|
207
|
+
message: "Password"
|
|
229
208
|
})
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
message: "Complete Setup"
|
|
209
|
+
}),
|
|
210
|
+
/*#__PURE__*/ _jsx("input", {
|
|
211
|
+
type: "password",
|
|
212
|
+
"data-bind": "password",
|
|
213
|
+
class: "input",
|
|
214
|
+
required: true,
|
|
215
|
+
minLength: 8
|
|
238
216
|
})
|
|
217
|
+
]
|
|
218
|
+
}),
|
|
219
|
+
/*#__PURE__*/ _jsx("button", {
|
|
220
|
+
type: "submit",
|
|
221
|
+
class: "btn",
|
|
222
|
+
children: $__i18n._({
|
|
223
|
+
id: "EGwzOK",
|
|
224
|
+
message: "Complete Setup"
|
|
239
225
|
})
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
226
|
+
})
|
|
227
|
+
]
|
|
228
|
+
})
|
|
243
229
|
})
|
|
244
230
|
]
|
|
245
231
|
})
|
|
@@ -259,21 +245,15 @@ import { sse } from "./lib/sse.js";
|
|
|
259
245
|
const isComplete = await c.var.services.settings.isOnboardingComplete();
|
|
260
246
|
if (isComplete) return c.redirect("/");
|
|
261
247
|
const body = await c.req.json();
|
|
262
|
-
const {
|
|
263
|
-
if (!
|
|
264
|
-
return
|
|
265
|
-
await stream.patchElements('<div id="setup-message"><div class="alert-destructive mb-4"><h2>All fields are required</h2></div></div>');
|
|
266
|
-
});
|
|
248
|
+
const { name, email, password } = body;
|
|
249
|
+
if (!name || !email || !password) {
|
|
250
|
+
return dsToast("All fields are required", "error");
|
|
267
251
|
}
|
|
268
252
|
if (password.length < 8) {
|
|
269
|
-
return
|
|
270
|
-
await stream.patchElements('<div id="setup-message"><div class="alert-destructive mb-4"><h2>Password must be at least 8 characters</h2></div></div>');
|
|
271
|
-
});
|
|
253
|
+
return dsToast("Password must be at least 8 characters", "error");
|
|
272
254
|
}
|
|
273
255
|
if (!c.var.auth) {
|
|
274
|
-
return
|
|
275
|
-
await stream.patchElements('<div id="setup-message"><div class="alert-destructive mb-4"><h2>AUTH_SECRET not configured</h2></div></div>');
|
|
276
|
-
});
|
|
256
|
+
return dsToast("AUTH_SECRET not configured", "error");
|
|
277
257
|
}
|
|
278
258
|
try {
|
|
279
259
|
const signUpResponse = await c.var.auth.api.signUpEmail({
|
|
@@ -284,24 +264,14 @@ import { sse } from "./lib/sse.js";
|
|
|
284
264
|
}
|
|
285
265
|
});
|
|
286
266
|
if (!signUpResponse || "error" in signUpResponse) {
|
|
287
|
-
return
|
|
288
|
-
await stream.patchElements('<div id="setup-message"><div class="alert-destructive mb-4"><h2>Failed to create account</h2></div></div>');
|
|
289
|
-
});
|
|
267
|
+
return dsToast("Failed to create account", "error");
|
|
290
268
|
}
|
|
291
|
-
await c.var.services.settings.setMany({
|
|
292
|
-
SITE_NAME: siteName,
|
|
293
|
-
SITE_LANGUAGE: "en"
|
|
294
|
-
});
|
|
295
269
|
await c.var.services.settings.completeOnboarding();
|
|
296
|
-
return
|
|
297
|
-
await stream.redirect("/signin");
|
|
298
|
-
});
|
|
270
|
+
return dsRedirect("/signin?setup");
|
|
299
271
|
} catch (err) {
|
|
300
272
|
// eslint-disable-next-line no-console -- Error logging is intentional
|
|
301
273
|
console.error("Setup error:", err);
|
|
302
|
-
return
|
|
303
|
-
await stream.patchElements('<div id="setup-message"><div class="alert-destructive mb-4"><h2>Failed to create account</h2></div></div>');
|
|
304
|
-
});
|
|
274
|
+
return dsToast("Failed to create account", "error");
|
|
305
275
|
}
|
|
306
276
|
});
|
|
307
277
|
// Signin page component
|
|
@@ -326,9 +296,6 @@ import { sse } from "./lib/sse.js";
|
|
|
326
296
|
}),
|
|
327
297
|
/*#__PURE__*/ _jsxs("section", {
|
|
328
298
|
children: [
|
|
329
|
-
/*#__PURE__*/ _jsx("div", {
|
|
330
|
-
id: "signin-message"
|
|
331
|
-
}),
|
|
332
299
|
demoEmail && demoPassword && /*#__PURE__*/ _jsx("p", {
|
|
333
300
|
class: "text-muted-foreground text-sm mb-4",
|
|
334
301
|
children: $__i18n._({
|
|
@@ -395,9 +362,22 @@ import { sse } from "./lib/sse.js";
|
|
|
395
362
|
};
|
|
396
363
|
// Signin page
|
|
397
364
|
app.get("/signin", async (c)=>{
|
|
365
|
+
const isSetup = c.req.query("setup") !== undefined;
|
|
366
|
+
const isReset = c.req.query("reset") !== undefined;
|
|
367
|
+
let toast;
|
|
368
|
+
if (isSetup) {
|
|
369
|
+
toast = {
|
|
370
|
+
message: "Account created successfully. Please sign in."
|
|
371
|
+
};
|
|
372
|
+
} else if (isReset) {
|
|
373
|
+
toast = {
|
|
374
|
+
message: "Password reset successfully. Please sign in."
|
|
375
|
+
};
|
|
376
|
+
}
|
|
398
377
|
return c.html(/*#__PURE__*/ _jsx(BaseLayout, {
|
|
399
378
|
title: "Sign In - Jant",
|
|
400
379
|
c: c,
|
|
380
|
+
toast: toast,
|
|
401
381
|
children: /*#__PURE__*/ _jsx(SigninContent, {
|
|
402
382
|
demoEmail: c.env.DEMO_EMAIL,
|
|
403
383
|
demoPassword: c.env.DEMO_PASSWORD
|
|
@@ -406,9 +386,7 @@ import { sse } from "./lib/sse.js";
|
|
|
406
386
|
});
|
|
407
387
|
app.post("/signin", async (c)=>{
|
|
408
388
|
if (!c.var.auth) {
|
|
409
|
-
return
|
|
410
|
-
await stream.patchElements('<div id="signin-message"><div class="alert-destructive mb-4"><h2>Auth not configured</h2></div></div>');
|
|
411
|
-
});
|
|
389
|
+
return dsToast("Auth not configured", "error");
|
|
412
390
|
}
|
|
413
391
|
const body = await c.req.json();
|
|
414
392
|
const { email, password } = body;
|
|
@@ -425,9 +403,7 @@ import { sse } from "./lib/sse.js";
|
|
|
425
403
|
});
|
|
426
404
|
const response = await c.var.auth.handler(signInRequest);
|
|
427
405
|
if (!response.ok) {
|
|
428
|
-
return
|
|
429
|
-
await stream.patchElements('<div id="signin-message"><div class="alert-destructive mb-4"><h2>Invalid email or password</h2></div></div>');
|
|
430
|
-
});
|
|
406
|
+
return dsToast("Invalid email or password", "error");
|
|
431
407
|
}
|
|
432
408
|
// Forward Set-Cookie headers from auth response
|
|
433
409
|
const cookieHeaders = {};
|
|
@@ -435,17 +411,13 @@ import { sse } from "./lib/sse.js";
|
|
|
435
411
|
if (setCookie) {
|
|
436
412
|
cookieHeaders["Set-Cookie"] = setCookie;
|
|
437
413
|
}
|
|
438
|
-
return
|
|
439
|
-
await stream.redirect("/dash");
|
|
440
|
-
}, {
|
|
414
|
+
return dsRedirect("/dash", {
|
|
441
415
|
headers: cookieHeaders
|
|
442
416
|
});
|
|
443
417
|
} catch (err) {
|
|
444
418
|
// eslint-disable-next-line no-console -- Error logging is intentional
|
|
445
419
|
console.error("Signin error:", err);
|
|
446
|
-
return
|
|
447
|
-
await stream.patchElements('<div id="signin-message"><div class="alert-destructive mb-4"><h2>Invalid email or password</h2></div></div>');
|
|
448
|
-
});
|
|
420
|
+
return dsToast("Invalid email or password", "error");
|
|
449
421
|
}
|
|
450
422
|
});
|
|
451
423
|
app.get("/signout", async (c)=>{
|
|
@@ -460,6 +432,203 @@ import { sse } from "./lib/sse.js";
|
|
|
460
432
|
}
|
|
461
433
|
return c.redirect("/");
|
|
462
434
|
});
|
|
435
|
+
// Password reset via one-time token
|
|
436
|
+
const ResetContent = ({ token })=>{
|
|
437
|
+
const { i18n: $__i18n, _: $__ } = $_useLingui();
|
|
438
|
+
const signals = JSON.stringify({
|
|
439
|
+
password: "",
|
|
440
|
+
confirmPassword: "",
|
|
441
|
+
token
|
|
442
|
+
}).replace(/</g, "\\u003c");
|
|
443
|
+
return /*#__PURE__*/ _jsx("div", {
|
|
444
|
+
class: "min-h-screen flex items-center justify-center",
|
|
445
|
+
children: /*#__PURE__*/ _jsxs("div", {
|
|
446
|
+
class: "card max-w-md w-full",
|
|
447
|
+
children: [
|
|
448
|
+
/*#__PURE__*/ _jsxs("header", {
|
|
449
|
+
children: [
|
|
450
|
+
/*#__PURE__*/ _jsx("h2", {
|
|
451
|
+
children: $__i18n._({
|
|
452
|
+
id: "KbS2K9",
|
|
453
|
+
message: "Reset Password"
|
|
454
|
+
})
|
|
455
|
+
}),
|
|
456
|
+
/*#__PURE__*/ _jsx("p", {
|
|
457
|
+
children: $__i18n._({
|
|
458
|
+
id: "hWOZIv",
|
|
459
|
+
message: "Enter your new password."
|
|
460
|
+
})
|
|
461
|
+
})
|
|
462
|
+
]
|
|
463
|
+
}),
|
|
464
|
+
/*#__PURE__*/ _jsx("section", {
|
|
465
|
+
children: /*#__PURE__*/ _jsxs("form", {
|
|
466
|
+
"data-signals": signals,
|
|
467
|
+
"data-on:submit__prevent": "@post('/reset')",
|
|
468
|
+
class: "flex flex-col gap-4",
|
|
469
|
+
children: [
|
|
470
|
+
/*#__PURE__*/ _jsxs("div", {
|
|
471
|
+
class: "field",
|
|
472
|
+
children: [
|
|
473
|
+
/*#__PURE__*/ _jsx("label", {
|
|
474
|
+
class: "label",
|
|
475
|
+
children: $__i18n._({
|
|
476
|
+
id: "7vhWI8",
|
|
477
|
+
message: "New Password"
|
|
478
|
+
})
|
|
479
|
+
}),
|
|
480
|
+
/*#__PURE__*/ _jsx("input", {
|
|
481
|
+
type: "password",
|
|
482
|
+
"data-bind": "password",
|
|
483
|
+
class: "input",
|
|
484
|
+
required: true,
|
|
485
|
+
minLength: 8,
|
|
486
|
+
autocomplete: "new-password"
|
|
487
|
+
})
|
|
488
|
+
]
|
|
489
|
+
}),
|
|
490
|
+
/*#__PURE__*/ _jsxs("div", {
|
|
491
|
+
class: "field",
|
|
492
|
+
children: [
|
|
493
|
+
/*#__PURE__*/ _jsx("label", {
|
|
494
|
+
class: "label",
|
|
495
|
+
children: $__i18n._({
|
|
496
|
+
id: "p2/GCq",
|
|
497
|
+
message: "Confirm Password"
|
|
498
|
+
})
|
|
499
|
+
}),
|
|
500
|
+
/*#__PURE__*/ _jsx("input", {
|
|
501
|
+
type: "password",
|
|
502
|
+
"data-bind": "confirmPassword",
|
|
503
|
+
class: "input",
|
|
504
|
+
required: true,
|
|
505
|
+
minLength: 8,
|
|
506
|
+
autocomplete: "new-password"
|
|
507
|
+
})
|
|
508
|
+
]
|
|
509
|
+
}),
|
|
510
|
+
/*#__PURE__*/ _jsx("button", {
|
|
511
|
+
type: "submit",
|
|
512
|
+
class: "btn",
|
|
513
|
+
children: $__i18n._({
|
|
514
|
+
id: "KbS2K9",
|
|
515
|
+
message: "Reset Password"
|
|
516
|
+
})
|
|
517
|
+
})
|
|
518
|
+
]
|
|
519
|
+
})
|
|
520
|
+
})
|
|
521
|
+
]
|
|
522
|
+
})
|
|
523
|
+
});
|
|
524
|
+
};
|
|
525
|
+
const ResetErrorContent = ()=>{
|
|
526
|
+
const { i18n: $__i18n, _: $__ } = $_useLingui();
|
|
527
|
+
return /*#__PURE__*/ _jsx("div", {
|
|
528
|
+
class: "min-h-screen flex items-center justify-center",
|
|
529
|
+
children: /*#__PURE__*/ _jsxs("div", {
|
|
530
|
+
class: "card max-w-md w-full",
|
|
531
|
+
children: [
|
|
532
|
+
/*#__PURE__*/ _jsx("header", {
|
|
533
|
+
children: /*#__PURE__*/ _jsx("h2", {
|
|
534
|
+
children: $__i18n._({
|
|
535
|
+
id: "7aECQB",
|
|
536
|
+
message: "Invalid or Expired Link"
|
|
537
|
+
})
|
|
538
|
+
})
|
|
539
|
+
}),
|
|
540
|
+
/*#__PURE__*/ _jsx("section", {
|
|
541
|
+
children: /*#__PURE__*/ _jsx("p", {
|
|
542
|
+
class: "text-muted-foreground",
|
|
543
|
+
children: $__i18n._({
|
|
544
|
+
id: "GbVAnd",
|
|
545
|
+
message: "This password reset link is invalid or has expired. Please generate a new one."
|
|
546
|
+
})
|
|
547
|
+
})
|
|
548
|
+
})
|
|
549
|
+
]
|
|
550
|
+
})
|
|
551
|
+
});
|
|
552
|
+
};
|
|
553
|
+
app.get("/reset", async (c)=>{
|
|
554
|
+
const token = c.req.query("token");
|
|
555
|
+
if (!token) {
|
|
556
|
+
return c.html(/*#__PURE__*/ _jsx(BaseLayout, {
|
|
557
|
+
title: "Reset Password - Jant",
|
|
558
|
+
c: c,
|
|
559
|
+
children: /*#__PURE__*/ _jsx(ResetErrorContent, {})
|
|
560
|
+
}));
|
|
561
|
+
}
|
|
562
|
+
const stored = await c.var.services.settings.get(SETTINGS_KEYS.PASSWORD_RESET_TOKEN);
|
|
563
|
+
if (!stored) {
|
|
564
|
+
return c.html(/*#__PURE__*/ _jsx(BaseLayout, {
|
|
565
|
+
title: "Reset Password - Jant",
|
|
566
|
+
c: c,
|
|
567
|
+
children: /*#__PURE__*/ _jsx(ResetErrorContent, {})
|
|
568
|
+
}));
|
|
569
|
+
}
|
|
570
|
+
const separatorIndex = stored.lastIndexOf(":");
|
|
571
|
+
const storedToken = stored.substring(0, separatorIndex);
|
|
572
|
+
const expiry = parseInt(stored.substring(separatorIndex + 1), 10);
|
|
573
|
+
const now = Math.floor(Date.now() / 1000);
|
|
574
|
+
if (token !== storedToken || now > expiry) {
|
|
575
|
+
return c.html(/*#__PURE__*/ _jsx(BaseLayout, {
|
|
576
|
+
title: "Reset Password - Jant",
|
|
577
|
+
c: c,
|
|
578
|
+
children: /*#__PURE__*/ _jsx(ResetErrorContent, {})
|
|
579
|
+
}));
|
|
580
|
+
}
|
|
581
|
+
return c.html(/*#__PURE__*/ _jsx(BaseLayout, {
|
|
582
|
+
title: "Reset Password - Jant",
|
|
583
|
+
c: c,
|
|
584
|
+
children: /*#__PURE__*/ _jsx(ResetContent, {
|
|
585
|
+
token: token
|
|
586
|
+
})
|
|
587
|
+
}));
|
|
588
|
+
});
|
|
589
|
+
app.post("/reset", async (c)=>{
|
|
590
|
+
const body = await c.req.json();
|
|
591
|
+
const { password, confirmPassword, token } = body;
|
|
592
|
+
// Validate token
|
|
593
|
+
const stored = await c.var.services.settings.get(SETTINGS_KEYS.PASSWORD_RESET_TOKEN);
|
|
594
|
+
if (!stored) {
|
|
595
|
+
return dsToast("Invalid or expired reset link.", "error");
|
|
596
|
+
}
|
|
597
|
+
const separatorIndex = stored.lastIndexOf(":");
|
|
598
|
+
const storedToken = stored.substring(0, separatorIndex);
|
|
599
|
+
const expiry = parseInt(stored.substring(separatorIndex + 1), 10);
|
|
600
|
+
const now = Math.floor(Date.now() / 1000);
|
|
601
|
+
if (token !== storedToken || now > expiry) {
|
|
602
|
+
return dsToast("Invalid or expired reset link.", "error");
|
|
603
|
+
}
|
|
604
|
+
// Validate passwords
|
|
605
|
+
if (!password || password.length < 8) {
|
|
606
|
+
return dsToast("Password must be at least 8 characters.", "error");
|
|
607
|
+
}
|
|
608
|
+
if (password !== confirmPassword) {
|
|
609
|
+
return dsToast("Passwords do not match.", "error");
|
|
610
|
+
}
|
|
611
|
+
try {
|
|
612
|
+
const hashedPassword = await hashPassword(password);
|
|
613
|
+
const db = c.env.DB.withSession();
|
|
614
|
+
// Get admin user
|
|
615
|
+
const userResult = await db.prepare("SELECT id FROM user LIMIT 1").first();
|
|
616
|
+
if (!userResult) {
|
|
617
|
+
return dsToast("No user account found.", "error");
|
|
618
|
+
}
|
|
619
|
+
// Update password
|
|
620
|
+
await db.prepare("UPDATE account SET password = ? WHERE user_id = ? AND provider_id = 'credential'").bind(hashedPassword, userResult.id).run();
|
|
621
|
+
// Delete all sessions
|
|
622
|
+
await db.prepare("DELETE FROM session WHERE user_id = ?").bind(userResult.id).run();
|
|
623
|
+
// Delete the reset token
|
|
624
|
+
await c.var.services.settings.remove(SETTINGS_KEYS.PASSWORD_RESET_TOKEN);
|
|
625
|
+
return dsRedirect("/signin?reset");
|
|
626
|
+
} catch (err) {
|
|
627
|
+
// eslint-disable-next-line no-console -- Error logging is intentional
|
|
628
|
+
console.error("Password reset error:", err);
|
|
629
|
+
return dsToast("Failed to reset password.", "error");
|
|
630
|
+
}
|
|
631
|
+
});
|
|
463
632
|
// Dashboard routes (protected)
|
|
464
633
|
app.use("/dash/*", requireAuth());
|
|
465
634
|
app.route("/dash", dashIndexRoutes);
|
|
@@ -469,6 +638,7 @@ import { sse } from "./lib/sse.js";
|
|
|
469
638
|
app.route("/dash/settings", dashSettingsRoutes);
|
|
470
639
|
app.route("/dash/redirects", dashRedirectsRoutes);
|
|
471
640
|
app.route("/dash/collections", dashCollectionsRoutes);
|
|
641
|
+
app.route("/dash/appearance", dashAppearanceRoutes);
|
|
472
642
|
// API routes
|
|
473
643
|
app.route("/api/upload", uploadApiRoutes);
|
|
474
644
|
app.route("/api/search", searchApiRoutes);
|
package/dist/client.js
CHANGED
package/dist/i18n/context.d.ts
CHANGED
|
@@ -21,7 +21,7 @@ type TranslationDescriptor = {
|
|
|
21
21
|
*
|
|
22
22
|
* @example
|
|
23
23
|
* ```tsx
|
|
24
|
-
* import { I18nProvider } from "
|
|
24
|
+
* import { I18nProvider } from "../i18n/index.js";
|
|
25
25
|
*
|
|
26
26
|
* return c.html(
|
|
27
27
|
* <I18nProvider c={c}>
|
|
@@ -41,7 +41,7 @@ export declare const I18nProvider: FC<I18nProviderProps>;
|
|
|
41
41
|
* @example
|
|
42
42
|
* ```tsx
|
|
43
43
|
* import { t } from "@lingui/core/macro";
|
|
44
|
-
* import { useLingui } from "
|
|
44
|
+
* import { useLingui } from "../i18n/index.js";
|
|
45
45
|
*
|
|
46
46
|
* function MyComponent() {
|
|
47
47
|
* const { t: _ } = useLingui(); // Use _ to avoid conflict with macro
|
package/dist/i18n/context.js
CHANGED
|
@@ -22,7 +22,7 @@ export const I18nProvider = ({ c, children })=>{
|
|
|
22
22
|
* @example
|
|
23
23
|
* ```tsx
|
|
24
24
|
* import { t } from "@lingui/core/macro";
|
|
25
|
-
* import { useLingui } from "
|
|
25
|
+
* import { useLingui } from "../i18n/index.js";
|
|
26
26
|
*
|
|
27
27
|
* function MyComponent() {
|
|
28
28
|
* const { t: _ } = useLingui(); // Use _ to avoid conflict with macro
|
package/dist/i18n/i18n.d.ts
CHANGED
|
@@ -21,7 +21,7 @@ export declare function createI18n(locale: Locale): I18n;
|
|
|
21
21
|
*
|
|
22
22
|
* @example
|
|
23
23
|
* import { msg } from "@lingui/core/macro";
|
|
24
|
-
* import { getI18n } from "
|
|
24
|
+
* import { getI18n } from "../i18n/index.js";
|
|
25
25
|
*
|
|
26
26
|
* const i18n = getI18n(c);
|
|
27
27
|
* const title = i18n._(msg({ message: "Dashboard", comment: "@context: Page title" }));
|
package/dist/i18n/i18n.js
CHANGED
|
@@ -40,7 +40,7 @@ const catalogZhHant = {
|
|
|
40
40
|
*
|
|
41
41
|
* @example
|
|
42
42
|
* import { msg } from "@lingui/core/macro";
|
|
43
|
-
* import { getI18n } from "
|
|
43
|
+
* import { getI18n } from "../i18n/index.js";
|
|
44
44
|
*
|
|
45
45
|
* const i18n = getI18n(c);
|
|
46
46
|
* const title = i18n._(msg({ message: "Dashboard", comment: "@context: Page title" }));
|
package/dist/i18n/index.d.ts
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
*
|
|
10
10
|
* Usage:
|
|
11
11
|
* ```tsx
|
|
12
|
-
* import { useLingui, Trans, I18nProvider } from "
|
|
12
|
+
* import { useLingui, Trans, I18nProvider } from "../i18n/index.js";
|
|
13
13
|
*
|
|
14
14
|
* // Wrap your app in I18nProvider (automatically done by BaseLayout when c is provided)
|
|
15
15
|
* c.html(
|