@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.
Files changed (99) hide show
  1. package/dist/app.d.ts +1 -0
  2. package/dist/app.d.ts.map +1 -1
  3. package/dist/app.js +307 -137
  4. package/dist/client.js +1 -0
  5. package/dist/i18n/context.d.ts +2 -2
  6. package/dist/i18n/context.js +1 -1
  7. package/dist/i18n/i18n.d.ts +1 -1
  8. package/dist/i18n/i18n.js +1 -1
  9. package/dist/i18n/index.d.ts +1 -1
  10. package/dist/i18n/index.js +1 -1
  11. package/dist/i18n/locales/en.d.ts.map +1 -1
  12. package/dist/i18n/locales/en.js +1 -1
  13. package/dist/i18n/locales/zh-Hans.d.ts.map +1 -1
  14. package/dist/i18n/locales/zh-Hans.js +1 -1
  15. package/dist/i18n/locales/zh-Hant.d.ts.map +1 -1
  16. package/dist/i18n/locales/zh-Hant.js +1 -1
  17. package/dist/lib/config.d.ts +44 -10
  18. package/dist/lib/config.d.ts.map +1 -1
  19. package/dist/lib/config.js +69 -44
  20. package/dist/lib/constants.d.ts +2 -1
  21. package/dist/lib/constants.d.ts.map +1 -1
  22. package/dist/lib/constants.js +5 -2
  23. package/dist/lib/image-processor.js +0 -4
  24. package/dist/lib/media-upload.js +104 -0
  25. package/dist/lib/sse.d.ts +82 -13
  26. package/dist/lib/sse.d.ts.map +1 -1
  27. package/dist/lib/sse.js +115 -17
  28. package/dist/lib/theme.d.ts +44 -0
  29. package/dist/lib/theme.d.ts.map +1 -0
  30. package/dist/lib/theme.js +65 -0
  31. package/dist/routes/api/upload.js +16 -18
  32. package/dist/routes/dash/appearance.d.ts +13 -0
  33. package/dist/routes/dash/appearance.d.ts.map +1 -0
  34. package/dist/routes/dash/appearance.js +160 -0
  35. package/dist/routes/dash/collections.js +5 -13
  36. package/dist/routes/dash/media.js +17 -167
  37. package/dist/routes/dash/pages.js +4 -10
  38. package/dist/routes/dash/posts.js +4 -10
  39. package/dist/routes/dash/redirects.js +3 -7
  40. package/dist/routes/dash/settings.d.ts.map +1 -1
  41. package/dist/routes/dash/settings.js +52 -42
  42. package/dist/services/settings.d.ts +1 -0
  43. package/dist/services/settings.d.ts.map +1 -1
  44. package/dist/services/settings.js +3 -0
  45. package/dist/theme/color-themes.d.ts +30 -0
  46. package/dist/theme/color-themes.d.ts.map +1 -0
  47. package/dist/theme/color-themes.js +268 -0
  48. package/dist/theme/layouts/BaseLayout.d.ts +5 -0
  49. package/dist/theme/layouts/BaseLayout.d.ts.map +1 -1
  50. package/dist/theme/layouts/BaseLayout.js +70 -3
  51. package/dist/theme/layouts/DashLayout.d.ts +2 -0
  52. package/dist/theme/layouts/DashLayout.d.ts.map +1 -1
  53. package/dist/theme/layouts/DashLayout.js +11 -1
  54. package/dist/theme/layouts/index.d.ts +1 -1
  55. package/dist/theme/layouts/index.d.ts.map +1 -1
  56. package/dist/types.d.ts +53 -1
  57. package/dist/types.d.ts.map +1 -1
  58. package/dist/types.js +52 -0
  59. package/package.json +1 -1
  60. package/src/app.tsx +260 -81
  61. package/src/client.ts +1 -0
  62. package/src/db/migrations/{0000_solid_moon_knight.sql → 0000_square_wallflower.sql} +3 -3
  63. package/src/db/migrations/meta/0000_snapshot.json +9 -9
  64. package/src/db/migrations/meta/_journal.json +2 -30
  65. package/src/i18n/context.tsx +2 -2
  66. package/src/i18n/i18n.ts +1 -1
  67. package/src/i18n/index.ts +1 -1
  68. package/src/i18n/locales/en.po +328 -252
  69. package/src/i18n/locales/en.ts +1 -1
  70. package/src/i18n/locales/zh-Hans.po +315 -278
  71. package/src/i18n/locales/zh-Hans.ts +1 -1
  72. package/src/i18n/locales/zh-Hant.po +315 -278
  73. package/src/i18n/locales/zh-Hant.ts +1 -1
  74. package/src/lib/config.ts +73 -47
  75. package/src/lib/constants.ts +3 -0
  76. package/src/lib/image-processor.ts +0 -7
  77. package/src/lib/media-upload.ts +148 -0
  78. package/src/lib/sse.ts +156 -16
  79. package/src/lib/theme.ts +86 -0
  80. package/src/preset.css +9 -0
  81. package/src/routes/api/upload.ts +12 -18
  82. package/src/routes/dash/appearance.tsx +176 -0
  83. package/src/routes/dash/collections.tsx +5 -13
  84. package/src/routes/dash/media.tsx +16 -165
  85. package/src/routes/dash/pages.tsx +4 -10
  86. package/src/routes/dash/posts.tsx +4 -10
  87. package/src/routes/dash/redirects.tsx +3 -7
  88. package/src/routes/dash/settings.tsx +71 -55
  89. package/src/services/settings.ts +5 -0
  90. package/src/styles/components.css +93 -0
  91. package/src/theme/color-themes.ts +321 -0
  92. package/src/theme/layouts/BaseLayout.tsx +61 -1
  93. package/src/theme/layouts/DashLayout.tsx +14 -3
  94. package/src/theme/layouts/index.ts +5 -1
  95. package/src/types.ts +62 -1
  96. package/src/db/migrations/0001_add_search_fts.sql +0 -40
  97. package/src/db/migrations/0002_collection_path.sql +0 -2
  98. package/src/db/migrations/0003_collection_path_nullable.sql +0 -21
  99. package/src/db/migrations/0004_media_uuid.sql +0 -35
package/src/app.tsx CHANGED
@@ -10,6 +10,8 @@ import { createAuth, type Auth } from "./auth.js";
10
10
  import { i18nMiddleware } from "./i18n/index.js";
11
11
  import { useLingui } from "@lingui/react/macro";
12
12
  import type { Bindings, JantConfig } from "./types.js";
13
+ import { SETTINGS_KEYS } from "./lib/constants.js";
14
+ import { hashPassword } from "better-auth/crypto";
13
15
 
14
16
  // Routes - Pages
15
17
  import { homeRoutes } from "./routes/pages/home.js";
@@ -27,6 +29,7 @@ import { mediaRoutes as dashMediaRoutes } from "./routes/dash/media.js";
27
29
  import { settingsRoutes as dashSettingsRoutes } from "./routes/dash/settings.js";
28
30
  import { redirectsRoutes as dashRedirectsRoutes } from "./routes/dash/redirects.js";
29
31
  import { collectionsRoutes as dashCollectionsRoutes } from "./routes/dash/collections.js";
32
+ import { appearanceRoutes as dashAppearanceRoutes } from "./routes/dash/appearance.js";
30
33
 
31
34
  // Routes - API
32
35
  import { postsApiRoutes } from "./routes/api/posts.js";
@@ -42,13 +45,15 @@ import { requireAuth } from "./middleware/auth.js";
42
45
 
43
46
  // Layouts for auth pages
44
47
  import { BaseLayout } from "./theme/layouts/index.js";
45
- import { sse } from "./lib/sse.js";
48
+ import { dsRedirect, dsToast } from "./lib/sse.js";
49
+ import { getAvailableThemes, buildThemeStyle } from "./lib/theme.js";
46
50
 
47
51
  // Extend Hono's context variables
48
52
  export interface AppVariables {
49
53
  services: Services;
50
54
  auth: Auth;
51
55
  config: JantConfig;
56
+ themeStyle: string;
52
57
  }
53
58
 
54
59
  export type App = Hono<{ Bindings: Bindings; Variables: AppVariables }>;
@@ -100,6 +105,18 @@ export function createApp(config: JantConfig = {}): App {
100
105
  await next();
101
106
  });
102
107
 
108
+ // Theme middleware - resolve active color theme and build CSS
109
+ app.use("*", async (c, next) => {
110
+ const themeId = await c.var.services.settings.get(SETTINGS_KEYS.THEME);
111
+ const themes = getAvailableThemes(config);
112
+ const activeTheme = themeId
113
+ ? themes.find((t) => t.id === themeId)
114
+ : undefined;
115
+ const themeStyle = buildThemeStyle(activeTheme, config.theme?.cssVariables);
116
+ c.set("themeStyle", themeStyle);
117
+ await next();
118
+ });
119
+
103
120
  // i18n middleware
104
121
  app.use("*", i18nMiddleware());
105
122
 
@@ -165,36 +182,17 @@ export function createApp(config: JantConfig = {}): App {
165
182
  </h2>
166
183
  <p>
167
184
  {t({
168
- message: "Let's set up your site.",
185
+ message: "Create your admin account.",
169
186
  comment: "@context: Setup page description",
170
187
  })}
171
188
  </p>
172
189
  </header>
173
190
  <section>
174
- <div id="setup-message"></div>
175
191
  <form
176
- data-signals="{siteName: '', name: '', email: '', password: ''}"
192
+ data-signals="{name: '', email: '', password: ''}"
177
193
  data-on:submit__prevent="@post('/setup')"
178
194
  class="flex flex-col gap-4"
179
195
  >
180
- <div class="field">
181
- <label class="label">
182
- {t({
183
- message: "Site Name",
184
- comment: "@context: Setup form field - site name",
185
- })}
186
- </label>
187
- <input
188
- type="text"
189
- data-bind="siteName"
190
- class="input"
191
- required
192
- placeholder={t({
193
- message: "My Blog",
194
- comment: "@context: Setup site name placeholder",
195
- })}
196
- />
197
- </div>
198
196
  <div class="field">
199
197
  <label class="label">
200
198
  {t({
@@ -270,35 +268,22 @@ export function createApp(config: JantConfig = {}): App {
270
268
  if (isComplete) return c.redirect("/");
271
269
 
272
270
  const body = await c.req.json<{
273
- siteName: string;
274
271
  name: string;
275
272
  email: string;
276
273
  password: string;
277
274
  }>();
278
- const { siteName, name, email, password } = body;
275
+ const { name, email, password } = body;
279
276
 
280
- if (!siteName || !name || !email || !password) {
281
- return sse(c, async (stream) => {
282
- await stream.patchElements(
283
- '<div id="setup-message"><div class="alert-destructive mb-4"><h2>All fields are required</h2></div></div>',
284
- );
285
- });
277
+ if (!name || !email || !password) {
278
+ return dsToast("All fields are required", "error");
286
279
  }
287
280
 
288
281
  if (password.length < 8) {
289
- return sse(c, async (stream) => {
290
- await stream.patchElements(
291
- '<div id="setup-message"><div class="alert-destructive mb-4"><h2>Password must be at least 8 characters</h2></div></div>',
292
- );
293
- });
282
+ return dsToast("Password must be at least 8 characters", "error");
294
283
  }
295
284
 
296
285
  if (!c.var.auth) {
297
- return sse(c, async (stream) => {
298
- await stream.patchElements(
299
- '<div id="setup-message"><div class="alert-destructive mb-4"><h2>AUTH_SECRET not configured</h2></div></div>',
300
- );
301
- });
286
+ return dsToast("AUTH_SECRET not configured", "error");
302
287
  }
303
288
 
304
289
  try {
@@ -307,30 +292,16 @@ export function createApp(config: JantConfig = {}): App {
307
292
  });
308
293
 
309
294
  if (!signUpResponse || "error" in signUpResponse) {
310
- return sse(c, async (stream) => {
311
- await stream.patchElements(
312
- '<div id="setup-message"><div class="alert-destructive mb-4"><h2>Failed to create account</h2></div></div>',
313
- );
314
- });
295
+ return dsToast("Failed to create account", "error");
315
296
  }
316
297
 
317
- await c.var.services.settings.setMany({
318
- SITE_NAME: siteName,
319
- SITE_LANGUAGE: "en",
320
- });
321
298
  await c.var.services.settings.completeOnboarding();
322
299
 
323
- return sse(c, async (stream) => {
324
- await stream.redirect("/signin");
325
- });
300
+ return dsRedirect("/signin?setup");
326
301
  } catch (err) {
327
302
  // eslint-disable-next-line no-console -- Error logging is intentional
328
303
  console.error("Setup error:", err);
329
- return sse(c, async (stream) => {
330
- await stream.patchElements(
331
- '<div id="setup-message"><div class="alert-destructive mb-4"><h2>Failed to create account</h2></div></div>',
332
- );
333
- });
304
+ return dsToast("Failed to create account", "error");
334
305
  }
335
306
  });
336
307
 
@@ -357,7 +328,6 @@ export function createApp(config: JantConfig = {}): App {
357
328
  </h2>
358
329
  </header>
359
330
  <section>
360
- <div id="signin-message"></div>
361
331
  {demoEmail && demoPassword && (
362
332
  <p class="text-muted-foreground text-sm mb-4">
363
333
  {t({
@@ -410,8 +380,17 @@ export function createApp(config: JantConfig = {}): App {
410
380
 
411
381
  // Signin page
412
382
  app.get("/signin", async (c) => {
383
+ const isSetup = c.req.query("setup") !== undefined;
384
+ const isReset = c.req.query("reset") !== undefined;
385
+ let toast: { message: string } | undefined;
386
+ if (isSetup) {
387
+ toast = { message: "Account created successfully. Please sign in." };
388
+ } else if (isReset) {
389
+ toast = { message: "Password reset successfully. Please sign in." };
390
+ }
391
+
413
392
  return c.html(
414
- <BaseLayout title="Sign In - Jant" c={c}>
393
+ <BaseLayout title="Sign In - Jant" c={c} toast={toast}>
415
394
  <SigninContent
416
395
  demoEmail={c.env.DEMO_EMAIL}
417
396
  demoPassword={c.env.DEMO_PASSWORD}
@@ -422,11 +401,7 @@ export function createApp(config: JantConfig = {}): App {
422
401
 
423
402
  app.post("/signin", async (c) => {
424
403
  if (!c.var.auth) {
425
- return sse(c, async (stream) => {
426
- await stream.patchElements(
427
- '<div id="signin-message"><div class="alert-destructive mb-4"><h2>Auth not configured</h2></div></div>',
428
- );
429
- });
404
+ return dsToast("Auth not configured", "error");
430
405
  }
431
406
 
432
407
  const body = await c.req.json<{ email: string; password: string }>();
@@ -445,11 +420,7 @@ export function createApp(config: JantConfig = {}): App {
445
420
  const response = await c.var.auth.handler(signInRequest);
446
421
 
447
422
  if (!response.ok) {
448
- return sse(c, async (stream) => {
449
- await stream.patchElements(
450
- '<div id="signin-message"><div class="alert-destructive mb-4"><h2>Invalid email or password</h2></div></div>',
451
- );
452
- });
423
+ return dsToast("Invalid email or password", "error");
453
424
  }
454
425
 
455
426
  // Forward Set-Cookie headers from auth response
@@ -459,21 +430,11 @@ export function createApp(config: JantConfig = {}): App {
459
430
  cookieHeaders["Set-Cookie"] = setCookie;
460
431
  }
461
432
 
462
- return sse(
463
- c,
464
- async (stream) => {
465
- await stream.redirect("/dash");
466
- },
467
- { headers: cookieHeaders },
468
- );
433
+ return dsRedirect("/dash", { headers: cookieHeaders });
469
434
  } catch (err) {
470
435
  // eslint-disable-next-line no-console -- Error logging is intentional
471
436
  console.error("Signin error:", err);
472
- return sse(c, async (stream) => {
473
- await stream.patchElements(
474
- '<div id="signin-message"><div class="alert-destructive mb-4"><h2>Invalid email or password</h2></div></div>',
475
- );
476
- });
437
+ return dsToast("Invalid email or password", "error");
477
438
  }
478
439
  });
479
440
 
@@ -488,6 +449,223 @@ export function createApp(config: JantConfig = {}): App {
488
449
  return c.redirect("/");
489
450
  });
490
451
 
452
+ // Password reset via one-time token
453
+ const ResetContent: FC<{ token: string }> = ({ token }) => {
454
+ const { t } = useLingui();
455
+ const signals = JSON.stringify({
456
+ password: "",
457
+ confirmPassword: "",
458
+ token,
459
+ }).replace(/</g, "\\u003c");
460
+
461
+ return (
462
+ <div class="min-h-screen flex items-center justify-center">
463
+ <div class="card max-w-md w-full">
464
+ <header>
465
+ <h2>
466
+ {t({
467
+ message: "Reset Password",
468
+ comment: "@context: Password reset page heading",
469
+ })}
470
+ </h2>
471
+ <p>
472
+ {t({
473
+ message: "Enter your new password.",
474
+ comment: "@context: Password reset page description",
475
+ })}
476
+ </p>
477
+ </header>
478
+ <section>
479
+ <form
480
+ data-signals={signals}
481
+ data-on:submit__prevent="@post('/reset')"
482
+ class="flex flex-col gap-4"
483
+ >
484
+ <div class="field">
485
+ <label class="label">
486
+ {t({
487
+ message: "New Password",
488
+ comment: "@context: Password reset form field",
489
+ })}
490
+ </label>
491
+ <input
492
+ type="password"
493
+ data-bind="password"
494
+ class="input"
495
+ required
496
+ minLength={8}
497
+ autocomplete="new-password"
498
+ />
499
+ </div>
500
+ <div class="field">
501
+ <label class="label">
502
+ {t({
503
+ message: "Confirm Password",
504
+ comment: "@context: Password reset form field",
505
+ })}
506
+ </label>
507
+ <input
508
+ type="password"
509
+ data-bind="confirmPassword"
510
+ class="input"
511
+ required
512
+ minLength={8}
513
+ autocomplete="new-password"
514
+ />
515
+ </div>
516
+ <button type="submit" class="btn">
517
+ {t({
518
+ message: "Reset Password",
519
+ comment: "@context: Password reset form submit button",
520
+ })}
521
+ </button>
522
+ </form>
523
+ </section>
524
+ </div>
525
+ </div>
526
+ );
527
+ };
528
+
529
+ const ResetErrorContent: FC = () => {
530
+ const { t } = useLingui();
531
+
532
+ return (
533
+ <div class="min-h-screen flex items-center justify-center">
534
+ <div class="card max-w-md w-full">
535
+ <header>
536
+ <h2>
537
+ {t({
538
+ message: "Invalid or Expired Link",
539
+ comment: "@context: Password reset error heading",
540
+ })}
541
+ </h2>
542
+ </header>
543
+ <section>
544
+ <p class="text-muted-foreground">
545
+ {t({
546
+ message:
547
+ "This password reset link is invalid or has expired. Please generate a new one.",
548
+ comment: "@context: Password reset error description",
549
+ })}
550
+ </p>
551
+ </section>
552
+ </div>
553
+ </div>
554
+ );
555
+ };
556
+
557
+ app.get("/reset", async (c) => {
558
+ const token = c.req.query("token");
559
+ if (!token) {
560
+ return c.html(
561
+ <BaseLayout title="Reset Password - Jant" c={c}>
562
+ <ResetErrorContent />
563
+ </BaseLayout>,
564
+ );
565
+ }
566
+
567
+ const stored = await c.var.services.settings.get(
568
+ SETTINGS_KEYS.PASSWORD_RESET_TOKEN,
569
+ );
570
+ if (!stored) {
571
+ return c.html(
572
+ <BaseLayout title="Reset Password - Jant" c={c}>
573
+ <ResetErrorContent />
574
+ </BaseLayout>,
575
+ );
576
+ }
577
+
578
+ const separatorIndex = stored.lastIndexOf(":");
579
+ const storedToken = stored.substring(0, separatorIndex);
580
+ const expiry = parseInt(stored.substring(separatorIndex + 1), 10);
581
+ const now = Math.floor(Date.now() / 1000);
582
+
583
+ if (token !== storedToken || now > expiry) {
584
+ return c.html(
585
+ <BaseLayout title="Reset Password - Jant" c={c}>
586
+ <ResetErrorContent />
587
+ </BaseLayout>,
588
+ );
589
+ }
590
+
591
+ return c.html(
592
+ <BaseLayout title="Reset Password - Jant" c={c}>
593
+ <ResetContent token={token} />
594
+ </BaseLayout>,
595
+ );
596
+ });
597
+
598
+ app.post("/reset", async (c) => {
599
+ const body = await c.req.json<{
600
+ password: string;
601
+ confirmPassword: string;
602
+ token: string;
603
+ }>();
604
+ const { password, confirmPassword, token } = body;
605
+
606
+ // Validate token
607
+ const stored = await c.var.services.settings.get(
608
+ SETTINGS_KEYS.PASSWORD_RESET_TOKEN,
609
+ );
610
+ if (!stored) {
611
+ return dsToast("Invalid or expired reset link.", "error");
612
+ }
613
+
614
+ const separatorIndex = stored.lastIndexOf(":");
615
+ const storedToken = stored.substring(0, separatorIndex);
616
+ const expiry = parseInt(stored.substring(separatorIndex + 1), 10);
617
+ const now = Math.floor(Date.now() / 1000);
618
+
619
+ if (token !== storedToken || now > expiry) {
620
+ return dsToast("Invalid or expired reset link.", "error");
621
+ }
622
+
623
+ // Validate passwords
624
+ if (!password || password.length < 8) {
625
+ return dsToast("Password must be at least 8 characters.", "error");
626
+ }
627
+
628
+ if (password !== confirmPassword) {
629
+ return dsToast("Passwords do not match.", "error");
630
+ }
631
+
632
+ try {
633
+ const hashedPassword = await hashPassword(password);
634
+ const db = c.env.DB.withSession() as unknown as D1Database;
635
+
636
+ // Get admin user
637
+ const userResult = await db
638
+ .prepare("SELECT id FROM user LIMIT 1")
639
+ .first<{ id: string }>();
640
+ if (!userResult) {
641
+ return dsToast("No user account found.", "error");
642
+ }
643
+
644
+ // Update password
645
+ await db
646
+ .prepare(
647
+ "UPDATE account SET password = ? WHERE user_id = ? AND provider_id = 'credential'",
648
+ )
649
+ .bind(hashedPassword, userResult.id)
650
+ .run();
651
+
652
+ // Delete all sessions
653
+ await db
654
+ .prepare("DELETE FROM session WHERE user_id = ?")
655
+ .bind(userResult.id)
656
+ .run();
657
+
658
+ // Delete the reset token
659
+ await c.var.services.settings.remove(SETTINGS_KEYS.PASSWORD_RESET_TOKEN);
660
+
661
+ return dsRedirect("/signin?reset");
662
+ } catch (err) {
663
+ // eslint-disable-next-line no-console -- Error logging is intentional
664
+ console.error("Password reset error:", err);
665
+ return dsToast("Failed to reset password.", "error");
666
+ }
667
+ });
668
+
491
669
  // Dashboard routes (protected)
492
670
  app.use("/dash/*", requireAuth());
493
671
  app.route("/dash", dashIndexRoutes);
@@ -497,6 +675,7 @@ export function createApp(config: JantConfig = {}): App {
497
675
  app.route("/dash/settings", dashSettingsRoutes);
498
676
  app.route("/dash/redirects", dashRedirectsRoutes);
499
677
  app.route("/dash/collections", dashCollectionsRoutes);
678
+ app.route("/dash/appearance", dashAppearanceRoutes);
500
679
 
501
680
  // API routes
502
681
  app.route("/api/upload", uploadApiRoutes);
package/src/client.ts CHANGED
@@ -10,3 +10,4 @@
10
10
  import "./vendor/datastar.js";
11
11
  import "basecoat-css/all";
12
12
  import "./lib/image-processor.js";
13
+ import "./lib/media-upload.js";
@@ -17,16 +17,16 @@ CREATE TABLE `account` (
17
17
  --> statement-breakpoint
18
18
  CREATE TABLE `collections` (
19
19
  `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
20
- `slug` text NOT NULL,
20
+ `path` text,
21
21
  `title` text NOT NULL,
22
22
  `description` text,
23
23
  `created_at` integer NOT NULL,
24
24
  `updated_at` integer NOT NULL
25
25
  );
26
26
  --> statement-breakpoint
27
- CREATE UNIQUE INDEX `collections_slug_unique` ON `collections` (`slug`);--> statement-breakpoint
27
+ CREATE UNIQUE INDEX `collections_path_unique` ON `collections` (`path`);--> statement-breakpoint
28
28
  CREATE TABLE `media` (
29
- `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
29
+ `id` text PRIMARY KEY NOT NULL,
30
30
  `post_id` integer,
31
31
  `filename` text NOT NULL,
32
32
  `original_name` text NOT NULL,
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": "6",
3
3
  "dialect": "sqlite",
4
- "id": "d32a99d8-a262-45b7-99e4-bc81bbb6f305",
4
+ "id": "3d04f8ef-088d-43f4-9fe9-55ffa2d7a73f",
5
5
  "prevId": "00000000-0000-0000-0000-000000000000",
6
6
  "tables": {
7
7
  "account": {
@@ -125,11 +125,11 @@
125
125
  "notNull": true,
126
126
  "autoincrement": true
127
127
  },
128
- "slug": {
129
- "name": "slug",
128
+ "path": {
129
+ "name": "path",
130
130
  "type": "text",
131
131
  "primaryKey": false,
132
- "notNull": true,
132
+ "notNull": false,
133
133
  "autoincrement": false
134
134
  },
135
135
  "title": {
@@ -162,9 +162,9 @@
162
162
  }
163
163
  },
164
164
  "indexes": {
165
- "collections_slug_unique": {
166
- "name": "collections_slug_unique",
167
- "columns": ["slug"],
165
+ "collections_path_unique": {
166
+ "name": "collections_path_unique",
167
+ "columns": ["path"],
168
168
  "isUnique": true
169
169
  }
170
170
  },
@@ -178,10 +178,10 @@
178
178
  "columns": {
179
179
  "id": {
180
180
  "name": "id",
181
- "type": "integer",
181
+ "type": "text",
182
182
  "primaryKey": true,
183
183
  "notNull": true,
184
- "autoincrement": true
184
+ "autoincrement": false
185
185
  },
186
186
  "post_id": {
187
187
  "name": "post_id",
@@ -5,36 +5,8 @@
5
5
  {
6
6
  "idx": 0,
7
7
  "version": "6",
8
- "when": 1769858252020,
9
- "tag": "0000_solid_moon_knight",
10
- "breakpoints": true
11
- },
12
- {
13
- "idx": 1,
14
- "version": "6",
15
- "when": 1769859000000,
16
- "tag": "0001_add_search_fts",
17
- "breakpoints": true
18
- },
19
- {
20
- "idx": 2,
21
- "version": "6",
22
- "when": 1769860000000,
23
- "tag": "0002_collection_path",
24
- "breakpoints": true
25
- },
26
- {
27
- "idx": 3,
28
- "version": "6",
29
- "when": 1769861000000,
30
- "tag": "0003_collection_path_nullable",
31
- "breakpoints": true
32
- },
33
- {
34
- "idx": 4,
35
- "version": "6",
36
- "when": 1770024000000,
37
- "tag": "0004_media_uuid",
8
+ "when": 1770564499811,
9
+ "tag": "0000_square_wallflower",
38
10
  "breakpoints": true
39
11
  }
40
12
  ]
@@ -28,7 +28,7 @@ let currentI18n: I18n | null = null;
28
28
  *
29
29
  * @example
30
30
  * ```tsx
31
- * import { I18nProvider } from "@/i18n";
31
+ * import { I18nProvider } from "../i18n/index.js";
32
32
  *
33
33
  * return c.html(
34
34
  * <I18nProvider c={c}>
@@ -56,7 +56,7 @@ export const I18nProvider: FC<I18nProviderProps> = ({ c, children }) => {
56
56
  * @example
57
57
  * ```tsx
58
58
  * import { t } from "@lingui/core/macro";
59
- * import { useLingui } from "@/i18n";
59
+ * import { useLingui } from "../i18n/index.js";
60
60
  *
61
61
  * function MyComponent() {
62
62
  * const { t: _ } = useLingui(); // Use _ to avoid conflict with macro
package/src/i18n/i18n.ts CHANGED
@@ -48,7 +48,7 @@ export function createI18n(locale: Locale): I18n {
48
48
  *
49
49
  * @example
50
50
  * import { msg } from "@lingui/core/macro";
51
- * import { getI18n } from "@/i18n";
51
+ * import { getI18n } from "../i18n/index.js";
52
52
  *
53
53
  * const i18n = getI18n(c);
54
54
  * const title = i18n._(msg({ message: "Dashboard", comment: "@context: Page title" }));
package/src/i18n/index.ts CHANGED
@@ -9,7 +9,7 @@
9
9
  *
10
10
  * Usage:
11
11
  * ```tsx
12
- * import { useLingui, Trans, I18nProvider } from "@/i18n";
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(