@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
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";
@@ -43,12 +46,14 @@ import { requireAuth } from "./middleware/auth.js";
43
46
  // Layouts for auth pages
44
47
  import { BaseLayout } from "./theme/layouts/index.js";
45
48
  import { sse } 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,34 +268,27 @@ 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) {
277
+ if (!name || !email || !password) {
281
278
  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
- );
279
+ await stream.toast("All fields are required", "error");
285
280
  });
286
281
  }
287
282
 
288
283
  if (password.length < 8) {
289
284
  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
- );
285
+ await stream.toast("Password must be at least 8 characters", "error");
293
286
  });
294
287
  }
295
288
 
296
289
  if (!c.var.auth) {
297
290
  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
- );
291
+ await stream.toast("AUTH_SECRET not configured", "error");
301
292
  });
302
293
  }
303
294
 
@@ -308,28 +299,20 @@ export function createApp(config: JantConfig = {}): App {
308
299
 
309
300
  if (!signUpResponse || "error" in signUpResponse) {
310
301
  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
- );
302
+ await stream.toast("Failed to create account", "error");
314
303
  });
315
304
  }
316
305
 
317
- await c.var.services.settings.setMany({
318
- SITE_NAME: siteName,
319
- SITE_LANGUAGE: "en",
320
- });
321
306
  await c.var.services.settings.completeOnboarding();
322
307
 
323
308
  return sse(c, async (stream) => {
324
- await stream.redirect("/signin");
309
+ await stream.redirect("/signin?setup");
325
310
  });
326
311
  } catch (err) {
327
312
  // eslint-disable-next-line no-console -- Error logging is intentional
328
313
  console.error("Setup error:", err);
329
314
  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
- );
315
+ await stream.toast("Failed to create account", "error");
333
316
  });
334
317
  }
335
318
  });
@@ -357,7 +340,6 @@ export function createApp(config: JantConfig = {}): App {
357
340
  </h2>
358
341
  </header>
359
342
  <section>
360
- <div id="signin-message"></div>
361
343
  {demoEmail && demoPassword && (
362
344
  <p class="text-muted-foreground text-sm mb-4">
363
345
  {t({
@@ -410,8 +392,17 @@ export function createApp(config: JantConfig = {}): App {
410
392
 
411
393
  // Signin page
412
394
  app.get("/signin", async (c) => {
395
+ const isSetup = c.req.query("setup") !== undefined;
396
+ const isReset = c.req.query("reset") !== undefined;
397
+ let toast: { message: string } | undefined;
398
+ if (isSetup) {
399
+ toast = { message: "Account created successfully. Please sign in." };
400
+ } else if (isReset) {
401
+ toast = { message: "Password reset successfully. Please sign in." };
402
+ }
403
+
413
404
  return c.html(
414
- <BaseLayout title="Sign In - Jant" c={c}>
405
+ <BaseLayout title="Sign In - Jant" c={c} toast={toast}>
415
406
  <SigninContent
416
407
  demoEmail={c.env.DEMO_EMAIL}
417
408
  demoPassword={c.env.DEMO_PASSWORD}
@@ -423,9 +414,7 @@ export function createApp(config: JantConfig = {}): App {
423
414
  app.post("/signin", async (c) => {
424
415
  if (!c.var.auth) {
425
416
  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
- );
417
+ await stream.toast("Auth not configured", "error");
429
418
  });
430
419
  }
431
420
 
@@ -446,9 +435,7 @@ export function createApp(config: JantConfig = {}): App {
446
435
 
447
436
  if (!response.ok) {
448
437
  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
- );
438
+ await stream.toast("Invalid email or password", "error");
452
439
  });
453
440
  }
454
441
 
@@ -470,9 +457,7 @@ export function createApp(config: JantConfig = {}): App {
470
457
  // eslint-disable-next-line no-console -- Error logging is intentional
471
458
  console.error("Signin error:", err);
472
459
  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
- );
460
+ await stream.toast("Invalid email or password", "error");
476
461
  });
477
462
  }
478
463
  });
@@ -488,6 +473,237 @@ export function createApp(config: JantConfig = {}): App {
488
473
  return c.redirect("/");
489
474
  });
490
475
 
476
+ // Password reset via one-time token
477
+ const ResetContent: FC<{ token: string }> = ({ token }) => {
478
+ const { t } = useLingui();
479
+ const signals = JSON.stringify({
480
+ password: "",
481
+ confirmPassword: "",
482
+ token,
483
+ }).replace(/</g, "\\u003c");
484
+
485
+ return (
486
+ <div class="min-h-screen flex items-center justify-center">
487
+ <div class="card max-w-md w-full">
488
+ <header>
489
+ <h2>
490
+ {t({
491
+ message: "Reset Password",
492
+ comment: "@context: Password reset page heading",
493
+ })}
494
+ </h2>
495
+ <p>
496
+ {t({
497
+ message: "Enter your new password.",
498
+ comment: "@context: Password reset page description",
499
+ })}
500
+ </p>
501
+ </header>
502
+ <section>
503
+ <form
504
+ data-signals={signals}
505
+ data-on:submit__prevent="@post('/reset')"
506
+ class="flex flex-col gap-4"
507
+ >
508
+ <div class="field">
509
+ <label class="label">
510
+ {t({
511
+ message: "New Password",
512
+ comment: "@context: Password reset form field",
513
+ })}
514
+ </label>
515
+ <input
516
+ type="password"
517
+ data-bind="password"
518
+ class="input"
519
+ required
520
+ minLength={8}
521
+ autocomplete="new-password"
522
+ />
523
+ </div>
524
+ <div class="field">
525
+ <label class="label">
526
+ {t({
527
+ message: "Confirm Password",
528
+ comment: "@context: Password reset form field",
529
+ })}
530
+ </label>
531
+ <input
532
+ type="password"
533
+ data-bind="confirmPassword"
534
+ class="input"
535
+ required
536
+ minLength={8}
537
+ autocomplete="new-password"
538
+ />
539
+ </div>
540
+ <button type="submit" class="btn">
541
+ {t({
542
+ message: "Reset Password",
543
+ comment: "@context: Password reset form submit button",
544
+ })}
545
+ </button>
546
+ </form>
547
+ </section>
548
+ </div>
549
+ </div>
550
+ );
551
+ };
552
+
553
+ const ResetErrorContent: FC = () => {
554
+ const { t } = useLingui();
555
+
556
+ return (
557
+ <div class="min-h-screen flex items-center justify-center">
558
+ <div class="card max-w-md w-full">
559
+ <header>
560
+ <h2>
561
+ {t({
562
+ message: "Invalid or Expired Link",
563
+ comment: "@context: Password reset error heading",
564
+ })}
565
+ </h2>
566
+ </header>
567
+ <section>
568
+ <p class="text-muted-foreground">
569
+ {t({
570
+ message:
571
+ "This password reset link is invalid or has expired. Please generate a new one.",
572
+ comment: "@context: Password reset error description",
573
+ })}
574
+ </p>
575
+ </section>
576
+ </div>
577
+ </div>
578
+ );
579
+ };
580
+
581
+ app.get("/reset", async (c) => {
582
+ const token = c.req.query("token");
583
+ if (!token) {
584
+ return c.html(
585
+ <BaseLayout title="Reset Password - Jant" c={c}>
586
+ <ResetErrorContent />
587
+ </BaseLayout>,
588
+ );
589
+ }
590
+
591
+ const stored = await c.var.services.settings.get(
592
+ SETTINGS_KEYS.PASSWORD_RESET_TOKEN,
593
+ );
594
+ if (!stored) {
595
+ return c.html(
596
+ <BaseLayout title="Reset Password - Jant" c={c}>
597
+ <ResetErrorContent />
598
+ </BaseLayout>,
599
+ );
600
+ }
601
+
602
+ const separatorIndex = stored.lastIndexOf(":");
603
+ const storedToken = stored.substring(0, separatorIndex);
604
+ const expiry = parseInt(stored.substring(separatorIndex + 1), 10);
605
+ const now = Math.floor(Date.now() / 1000);
606
+
607
+ if (token !== storedToken || now > expiry) {
608
+ return c.html(
609
+ <BaseLayout title="Reset Password - Jant" c={c}>
610
+ <ResetErrorContent />
611
+ </BaseLayout>,
612
+ );
613
+ }
614
+
615
+ return c.html(
616
+ <BaseLayout title="Reset Password - Jant" c={c}>
617
+ <ResetContent token={token} />
618
+ </BaseLayout>,
619
+ );
620
+ });
621
+
622
+ app.post("/reset", async (c) => {
623
+ const body = await c.req.json<{
624
+ password: string;
625
+ confirmPassword: string;
626
+ token: string;
627
+ }>();
628
+ const { password, confirmPassword, token } = body;
629
+
630
+ // Validate token
631
+ const stored = await c.var.services.settings.get(
632
+ SETTINGS_KEYS.PASSWORD_RESET_TOKEN,
633
+ );
634
+ if (!stored) {
635
+ return sse(c, async (stream) => {
636
+ await stream.toast("Invalid or expired reset link.", "error");
637
+ });
638
+ }
639
+
640
+ const separatorIndex = stored.lastIndexOf(":");
641
+ const storedToken = stored.substring(0, separatorIndex);
642
+ const expiry = parseInt(stored.substring(separatorIndex + 1), 10);
643
+ const now = Math.floor(Date.now() / 1000);
644
+
645
+ if (token !== storedToken || now > expiry) {
646
+ return sse(c, async (stream) => {
647
+ await stream.toast("Invalid or expired reset link.", "error");
648
+ });
649
+ }
650
+
651
+ // Validate passwords
652
+ if (!password || password.length < 8) {
653
+ return sse(c, async (stream) => {
654
+ await stream.toast("Password must be at least 8 characters.", "error");
655
+ });
656
+ }
657
+
658
+ if (password !== confirmPassword) {
659
+ return sse(c, async (stream) => {
660
+ await stream.toast("Passwords do not match.", "error");
661
+ });
662
+ }
663
+
664
+ try {
665
+ const hashedPassword = await hashPassword(password);
666
+ const db = c.env.DB.withSession() as unknown as D1Database;
667
+
668
+ // Get admin user
669
+ const userResult = await db
670
+ .prepare("SELECT id FROM user LIMIT 1")
671
+ .first<{ id: string }>();
672
+ if (!userResult) {
673
+ return sse(c, async (stream) => {
674
+ await stream.toast("No user account found.", "error");
675
+ });
676
+ }
677
+
678
+ // Update password
679
+ await db
680
+ .prepare(
681
+ "UPDATE account SET password = ? WHERE user_id = ? AND provider_id = 'credential'",
682
+ )
683
+ .bind(hashedPassword, userResult.id)
684
+ .run();
685
+
686
+ // Delete all sessions
687
+ await db
688
+ .prepare("DELETE FROM session WHERE user_id = ?")
689
+ .bind(userResult.id)
690
+ .run();
691
+
692
+ // Delete the reset token
693
+ await c.var.services.settings.remove(SETTINGS_KEYS.PASSWORD_RESET_TOKEN);
694
+
695
+ return sse(c, async (stream) => {
696
+ await stream.redirect("/signin?reset");
697
+ });
698
+ } catch (err) {
699
+ // eslint-disable-next-line no-console -- Error logging is intentional
700
+ console.error("Password reset error:", err);
701
+ return sse(c, async (stream) => {
702
+ await stream.toast("Failed to reset password.", "error");
703
+ });
704
+ }
705
+ });
706
+
491
707
  // Dashboard routes (protected)
492
708
  app.use("/dash/*", requireAuth());
493
709
  app.route("/dash", dashIndexRoutes);
@@ -497,6 +713,7 @@ export function createApp(config: JantConfig = {}): App {
497
713
  app.route("/dash/settings", dashSettingsRoutes);
498
714
  app.route("/dash/redirects", dashRedirectsRoutes);
499
715
  app.route("/dash/collections", dashCollectionsRoutes);
716
+ app.route("/dash/appearance", dashAppearanceRoutes);
500
717
 
501
718
  // API routes
502
719
  app.route("/api/upload", uploadApiRoutes);
@@ -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(