@jant/core 0.3.25 → 0.3.26

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 (131) hide show
  1. package/dist/app.js +67 -562
  2. package/dist/client.js +1 -0
  3. package/dist/i18n/locales/en.js +1 -1
  4. package/dist/i18n/locales/zh-Hans.js +1 -1
  5. package/dist/i18n/locales/zh-Hant.js +1 -1
  6. package/dist/lib/avatar-upload.js +134 -0
  7. package/dist/lib/config.js +39 -0
  8. package/dist/lib/constants.js +10 -10
  9. package/dist/lib/favicon.js +102 -0
  10. package/dist/lib/image.js +13 -17
  11. package/dist/lib/media-helpers.js +2 -2
  12. package/dist/lib/navigation.js +23 -3
  13. package/dist/lib/render.js +10 -1
  14. package/dist/lib/schemas.js +31 -0
  15. package/dist/lib/timezones.js +388 -0
  16. package/dist/lib/view.js +1 -1
  17. package/dist/routes/api/posts.js +1 -1
  18. package/dist/routes/api/upload.js +3 -3
  19. package/dist/routes/auth/reset.js +221 -0
  20. package/dist/routes/auth/setup.js +194 -0
  21. package/dist/routes/auth/signin.js +176 -0
  22. package/dist/routes/dash/collections.js +23 -415
  23. package/dist/routes/dash/media.js +12 -392
  24. package/dist/routes/dash/pages.js +7 -330
  25. package/dist/routes/dash/redirects.js +18 -12
  26. package/dist/routes/dash/settings.js +198 -577
  27. package/dist/routes/feed/rss.js +2 -1
  28. package/dist/routes/feed/sitemap.js +4 -2
  29. package/dist/routes/pages/featured.js +5 -1
  30. package/dist/routes/pages/home.js +26 -1
  31. package/dist/routes/pages/latest.js +45 -0
  32. package/dist/services/post.js +30 -50
  33. package/dist/types/bindings.js +3 -0
  34. package/dist/types/config.js +147 -0
  35. package/dist/types/constants.js +27 -0
  36. package/dist/types/entities.js +3 -0
  37. package/dist/types/operations.js +3 -0
  38. package/dist/types/props.js +3 -0
  39. package/dist/types/views.js +5 -0
  40. package/dist/types.js +8 -111
  41. package/dist/ui/color-themes.js +33 -33
  42. package/dist/ui/compose/ComposeDialog.js +36 -21
  43. package/dist/ui/dash/PageForm.js +21 -15
  44. package/dist/ui/dash/PostForm.js +22 -16
  45. package/dist/ui/dash/collections/CollectionForm.js +152 -0
  46. package/dist/ui/dash/collections/CollectionsListContent.js +68 -0
  47. package/dist/ui/dash/collections/ViewCollectionContent.js +96 -0
  48. package/dist/ui/dash/media/MediaListContent.js +166 -0
  49. package/dist/ui/dash/media/ViewMediaContent.js +212 -0
  50. package/dist/ui/dash/pages/LinkFormContent.js +130 -0
  51. package/dist/ui/dash/pages/UnifiedPagesContent.js +193 -0
  52. package/dist/ui/dash/settings/AccountContent.js +209 -0
  53. package/dist/ui/dash/settings/AppearanceContent.js +259 -0
  54. package/dist/ui/dash/settings/GeneralContent.js +536 -0
  55. package/dist/ui/dash/settings/SettingsNav.js +41 -0
  56. package/dist/ui/font-themes.js +36 -0
  57. package/dist/ui/layouts/BaseLayout.js +24 -2
  58. package/dist/ui/layouts/SiteLayout.js +47 -19
  59. package/package.json +1 -1
  60. package/src/app.tsx +93 -553
  61. package/src/client.ts +1 -0
  62. package/src/i18n/locales/en.po +240 -175
  63. package/src/i18n/locales/en.ts +1 -1
  64. package/src/i18n/locales/zh-Hans.po +240 -175
  65. package/src/i18n/locales/zh-Hans.ts +1 -1
  66. package/src/i18n/locales/zh-Hant.po +240 -175
  67. package/src/i18n/locales/zh-Hant.ts +1 -1
  68. package/src/lib/__tests__/config.test.ts +192 -0
  69. package/src/lib/__tests__/favicon.test.ts +151 -0
  70. package/src/lib/__tests__/image.test.ts +2 -6
  71. package/src/lib/__tests__/timezones.test.ts +61 -0
  72. package/src/lib/__tests__/view.test.ts +2 -2
  73. package/src/lib/avatar-upload.ts +165 -0
  74. package/src/lib/config.ts +47 -0
  75. package/src/lib/constants.ts +19 -11
  76. package/src/lib/favicon.ts +115 -0
  77. package/src/lib/image.ts +13 -21
  78. package/src/lib/media-helpers.ts +2 -2
  79. package/src/lib/navigation.ts +33 -2
  80. package/src/lib/render.tsx +15 -1
  81. package/src/lib/schemas.ts +39 -0
  82. package/src/lib/timezones.ts +325 -0
  83. package/src/lib/view.ts +1 -1
  84. package/src/routes/api/posts.ts +1 -1
  85. package/src/routes/api/upload.ts +2 -3
  86. package/src/routes/auth/reset.tsx +239 -0
  87. package/src/routes/auth/setup.tsx +189 -0
  88. package/src/routes/auth/signin.tsx +163 -0
  89. package/src/routes/dash/__tests__/settings-avatar.test.ts +89 -0
  90. package/src/routes/dash/collections.tsx +17 -366
  91. package/src/routes/dash/media.tsx +12 -414
  92. package/src/routes/dash/pages.tsx +8 -348
  93. package/src/routes/dash/redirects.tsx +20 -14
  94. package/src/routes/dash/settings.tsx +243 -534
  95. package/src/routes/feed/__tests__/rss.test.ts +141 -0
  96. package/src/routes/feed/rss.ts +3 -1
  97. package/src/routes/feed/sitemap.ts +4 -2
  98. package/src/routes/pages/featured.tsx +7 -1
  99. package/src/routes/pages/home.tsx +25 -2
  100. package/src/routes/pages/latest.tsx +59 -0
  101. package/src/services/post.ts +34 -66
  102. package/src/styles/components.css +0 -65
  103. package/src/styles/tokens.css +1 -1
  104. package/src/styles/ui.css +24 -40
  105. package/src/types/bindings.ts +30 -0
  106. package/src/types/config.ts +183 -0
  107. package/src/types/constants.ts +26 -0
  108. package/src/types/entities.ts +109 -0
  109. package/src/types/operations.ts +88 -0
  110. package/src/types/props.ts +115 -0
  111. package/src/types/views.ts +172 -0
  112. package/src/types.ts +8 -644
  113. package/src/ui/__tests__/font-themes.test.ts +34 -0
  114. package/src/ui/color-themes.ts +34 -34
  115. package/src/ui/compose/ComposeDialog.tsx +40 -21
  116. package/src/ui/dash/PageForm.tsx +25 -19
  117. package/src/ui/dash/PostForm.tsx +26 -20
  118. package/src/ui/dash/collections/CollectionForm.tsx +153 -0
  119. package/src/ui/dash/collections/CollectionsListContent.tsx +85 -0
  120. package/src/ui/dash/collections/ViewCollectionContent.tsx +92 -0
  121. package/src/ui/dash/media/MediaListContent.tsx +201 -0
  122. package/src/ui/dash/media/ViewMediaContent.tsx +208 -0
  123. package/src/ui/dash/pages/LinkFormContent.tsx +119 -0
  124. package/src/ui/dash/pages/UnifiedPagesContent.tsx +203 -0
  125. package/src/ui/dash/settings/AccountContent.tsx +176 -0
  126. package/src/ui/dash/settings/AppearanceContent.tsx +254 -0
  127. package/src/ui/dash/settings/GeneralContent.tsx +533 -0
  128. package/src/ui/dash/settings/SettingsNav.tsx +56 -0
  129. package/src/ui/font-themes.ts +54 -0
  130. package/src/ui/layouts/BaseLayout.tsx +17 -0
  131. package/src/ui/layouts/SiteLayout.tsx +45 -31
@@ -0,0 +1,194 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "hono/jsx/jsx-runtime";
2
+ /**
3
+ * Setup Routes
4
+ *
5
+ * Initial admin account creation during first-time setup.
6
+ */ import { Hono } from "hono";
7
+ import { useLingui as $_useLingui } from "@jant/core/i18n";
8
+ import { BaseLayout } from "../../ui/layouts/BaseLayout.js";
9
+ import { dsRedirect, dsToast } from "../../lib/sse.js";
10
+ import { SetupSchema } from "../../lib/schemas.js";
11
+ import { mapIanaToTimezone } from "../../lib/timezones.js";
12
+ const SetupContent = ()=>{
13
+ const { i18n: $__i18n, _: $__ } = $_useLingui();
14
+ return /*#__PURE__*/ _jsx("div", {
15
+ class: "min-h-screen flex items-center justify-center",
16
+ children: /*#__PURE__*/ _jsxs("div", {
17
+ class: "card max-w-md w-full",
18
+ children: [
19
+ /*#__PURE__*/ _jsxs("header", {
20
+ children: [
21
+ /*#__PURE__*/ _jsx("h2", {
22
+ children: $__i18n._({
23
+ id: "GorKul",
24
+ message: "Welcome to Jant"
25
+ })
26
+ }),
27
+ /*#__PURE__*/ _jsx("p", {
28
+ children: $__i18n._({
29
+ id: "GX2VMa",
30
+ message: "Create your admin account."
31
+ })
32
+ })
33
+ ]
34
+ }),
35
+ /*#__PURE__*/ _jsx("section", {
36
+ children: /*#__PURE__*/ _jsxs("form", {
37
+ "data-signals": "{name: '', email: '', password: '', _timezone: ''}",
38
+ "data-init": "$_timezone = Intl.DateTimeFormat().resolvedOptions().timeZone || ''",
39
+ "data-on:submit__prevent": "@post('/setup')",
40
+ "data-indicator": "_loading",
41
+ class: "flex flex-col gap-4",
42
+ children: [
43
+ /*#__PURE__*/ _jsxs("div", {
44
+ class: "field",
45
+ children: [
46
+ /*#__PURE__*/ _jsx("label", {
47
+ class: "label",
48
+ children: $__i18n._({
49
+ id: "/Rj5P4",
50
+ message: "Your Name"
51
+ })
52
+ }),
53
+ /*#__PURE__*/ _jsx("input", {
54
+ type: "text",
55
+ "data-bind": "name",
56
+ class: "input",
57
+ required: true,
58
+ placeholder: "John Doe"
59
+ })
60
+ ]
61
+ }),
62
+ /*#__PURE__*/ _jsxs("div", {
63
+ class: "field",
64
+ children: [
65
+ /*#__PURE__*/ _jsx("label", {
66
+ class: "label",
67
+ children: $__i18n._({
68
+ id: "O3oNi5",
69
+ message: "Email"
70
+ })
71
+ }),
72
+ /*#__PURE__*/ _jsx("input", {
73
+ type: "email",
74
+ "data-bind": "email",
75
+ class: "input",
76
+ required: true,
77
+ placeholder: "you@example.com"
78
+ })
79
+ ]
80
+ }),
81
+ /*#__PURE__*/ _jsxs("div", {
82
+ class: "field",
83
+ children: [
84
+ /*#__PURE__*/ _jsx("label", {
85
+ class: "label",
86
+ children: $__i18n._({
87
+ id: "8ZsakT",
88
+ message: "Password"
89
+ })
90
+ }),
91
+ /*#__PURE__*/ _jsx("input", {
92
+ type: "password",
93
+ "data-bind": "password",
94
+ class: "input",
95
+ required: true,
96
+ minLength: 8
97
+ })
98
+ ]
99
+ }),
100
+ /*#__PURE__*/ _jsxs("button", {
101
+ type: "submit",
102
+ class: "btn",
103
+ "data-attr:disabled": "$_loading",
104
+ children: [
105
+ /*#__PURE__*/ _jsx("svg", {
106
+ "data-show": "$_loading",
107
+ style: "display:none",
108
+ class: "animate-spin size-4",
109
+ xmlns: "http://www.w3.org/2000/svg",
110
+ viewBox: "0 0 24 24",
111
+ fill: "none",
112
+ stroke: "currentColor",
113
+ "stroke-width": "2",
114
+ "stroke-linecap": "round",
115
+ "stroke-linejoin": "round",
116
+ role: "status",
117
+ children: /*#__PURE__*/ _jsx("path", {
118
+ d: "M21 12a9 9 0 1 1-6.219-8.56"
119
+ })
120
+ }),
121
+ $__i18n._({
122
+ id: "EGwzOK",
123
+ message: "Complete Setup"
124
+ })
125
+ ]
126
+ })
127
+ ]
128
+ })
129
+ })
130
+ ]
131
+ })
132
+ });
133
+ };
134
+ export const setupRoutes = new Hono();
135
+ setupRoutes.get("/setup", async (c)=>{
136
+ const isComplete = await c.var.services.settings.isOnboardingComplete();
137
+ if (isComplete) return c.redirect("/");
138
+ return c.html(/*#__PURE__*/ _jsx(BaseLayout, {
139
+ title: "Setup - Jant",
140
+ c: c,
141
+ children: /*#__PURE__*/ _jsx(SetupContent, {})
142
+ }));
143
+ });
144
+ setupRoutes.post("/setup", async (c)=>{
145
+ const isComplete = await c.var.services.settings.isOnboardingComplete();
146
+ if (isComplete) return c.redirect("/");
147
+ const body = await c.req.json();
148
+ const parsed = SetupSchema.safeParse(body);
149
+ const browserTimezone = body._timezone;
150
+ if (!parsed.success) {
151
+ const msg = parsed.error.errors[0]?.message ?? "Invalid input";
152
+ return dsToast(msg, "error");
153
+ }
154
+ const { name, email, password } = parsed.data;
155
+ if (!c.var.auth) {
156
+ return dsToast("AUTH_SECRET not configured", "error");
157
+ }
158
+ try {
159
+ const signUpResponse = await c.var.auth.api.signUpEmail({
160
+ body: {
161
+ name,
162
+ email,
163
+ password
164
+ }
165
+ });
166
+ if (!signUpResponse || "error" in signUpResponse) {
167
+ return dsToast("Failed to create account", "error");
168
+ }
169
+ await c.var.services.settings.completeOnboarding();
170
+ // Save auto-detected timezone
171
+ if (browserTimezone) {
172
+ const tz = mapIanaToTimezone(browserTimezone);
173
+ if (tz !== "UTC") {
174
+ await c.var.services.settings.set("TIME_ZONE", tz);
175
+ }
176
+ }
177
+ // Seed default navigation items
178
+ await c.var.services.navItems.create({
179
+ type: "link",
180
+ label: "Featured",
181
+ url: "/featured"
182
+ });
183
+ await c.var.services.navItems.create({
184
+ type: "link",
185
+ label: "Collections",
186
+ url: "/collections"
187
+ });
188
+ return dsRedirect("/signin?setup");
189
+ } catch (err) {
190
+ // eslint-disable-next-line no-console -- Error logging is intentional
191
+ console.error("Setup error:", err);
192
+ return dsToast("Failed to create account", "error");
193
+ }
194
+ });
@@ -0,0 +1,176 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "hono/jsx/jsx-runtime";
2
+ /**
3
+ * Sign-in / Sign-out Routes
4
+ */ import { Hono } from "hono";
5
+ import { useLingui as $_useLingui } from "@jant/core/i18n";
6
+ import { BaseLayout } from "../../ui/layouts/BaseLayout.js";
7
+ import { dsRedirect, dsToast } from "../../lib/sse.js";
8
+ import { SigninSchema } from "../../lib/schemas.js";
9
+ const SigninContent = ({ demoEmail, demoPassword })=>{
10
+ const { i18n: $__i18n, _: $__ } = $_useLingui();
11
+ const signals = JSON.stringify({
12
+ email: demoEmail || "",
13
+ password: demoPassword || ""
14
+ }).replace(/</g, "\\u003c");
15
+ return /*#__PURE__*/ _jsx("div", {
16
+ class: "min-h-screen flex items-center justify-center",
17
+ children: /*#__PURE__*/ _jsxs("div", {
18
+ class: "card max-w-md w-full",
19
+ children: [
20
+ /*#__PURE__*/ _jsx("header", {
21
+ children: /*#__PURE__*/ _jsx("h2", {
22
+ children: $__i18n._({
23
+ id: "n1ekoW",
24
+ message: "Sign In"
25
+ })
26
+ })
27
+ }),
28
+ /*#__PURE__*/ _jsxs("section", {
29
+ children: [
30
+ demoEmail && demoPassword && /*#__PURE__*/ _jsx("p", {
31
+ class: "text-muted-foreground text-sm mb-4",
32
+ children: $__i18n._({
33
+ id: "er8+x7",
34
+ message: "Demo account pre-filled. Just click Sign In."
35
+ })
36
+ }),
37
+ /*#__PURE__*/ _jsxs("form", {
38
+ "data-signals": signals,
39
+ "data-on:submit__prevent": "@post('/signin')",
40
+ "data-indicator": "_loading",
41
+ class: "flex flex-col gap-4",
42
+ children: [
43
+ /*#__PURE__*/ _jsxs("div", {
44
+ class: "field",
45
+ children: [
46
+ /*#__PURE__*/ _jsx("label", {
47
+ class: "label",
48
+ children: $__i18n._({
49
+ id: "O3oNi5",
50
+ message: "Email"
51
+ })
52
+ }),
53
+ /*#__PURE__*/ _jsx("input", {
54
+ type: "email",
55
+ "data-bind": "email",
56
+ class: "input",
57
+ required: true
58
+ })
59
+ ]
60
+ }),
61
+ /*#__PURE__*/ _jsxs("div", {
62
+ class: "field",
63
+ children: [
64
+ /*#__PURE__*/ _jsx("label", {
65
+ class: "label",
66
+ children: $__i18n._({
67
+ id: "8ZsakT",
68
+ message: "Password"
69
+ })
70
+ }),
71
+ /*#__PURE__*/ _jsx("input", {
72
+ type: "password",
73
+ "data-bind": "password",
74
+ class: "input",
75
+ required: true
76
+ })
77
+ ]
78
+ }),
79
+ /*#__PURE__*/ _jsxs("button", {
80
+ type: "submit",
81
+ class: "btn",
82
+ "data-attr:disabled": "$_loading",
83
+ children: [
84
+ /*#__PURE__*/ _jsx("svg", {
85
+ "data-show": "$_loading",
86
+ style: "display:none",
87
+ class: "animate-spin size-4",
88
+ xmlns: "http://www.w3.org/2000/svg",
89
+ viewBox: "0 0 24 24",
90
+ fill: "none",
91
+ stroke: "currentColor",
92
+ "stroke-width": "2",
93
+ "stroke-linecap": "round",
94
+ "stroke-linejoin": "round",
95
+ role: "status",
96
+ children: /*#__PURE__*/ _jsx("path", {
97
+ d: "M21 12a9 9 0 1 1-6.219-8.56"
98
+ })
99
+ }),
100
+ $__i18n._({
101
+ id: "n1ekoW",
102
+ message: "Sign In"
103
+ })
104
+ ]
105
+ })
106
+ ]
107
+ })
108
+ ]
109
+ })
110
+ ]
111
+ })
112
+ });
113
+ };
114
+ export const signinRoutes = new Hono();
115
+ signinRoutes.get("/signin", async (c)=>{
116
+ const isSetup = c.req.query("setup") !== undefined;
117
+ const isReset = c.req.query("reset") !== undefined;
118
+ let toast;
119
+ if (isSetup) {
120
+ toast = {
121
+ message: "Account created successfully. Please sign in."
122
+ };
123
+ } else if (isReset) {
124
+ toast = {
125
+ message: "Password reset successfully. Please sign in."
126
+ };
127
+ }
128
+ return c.html(/*#__PURE__*/ _jsx(BaseLayout, {
129
+ title: "Sign In - Jant",
130
+ c: c,
131
+ toast: toast,
132
+ children: /*#__PURE__*/ _jsx(SigninContent, {
133
+ demoEmail: c.env.DEMO_EMAIL,
134
+ demoPassword: c.env.DEMO_PASSWORD
135
+ })
136
+ }));
137
+ });
138
+ signinRoutes.post("/signin", async (c)=>{
139
+ if (!c.var.auth) {
140
+ return dsToast("Auth not configured", "error");
141
+ }
142
+ const body = await c.req.json();
143
+ const parsed = SigninSchema.safeParse(body);
144
+ if (!parsed.success) {
145
+ const msg = parsed.error.errors[0]?.message ?? "Invalid input";
146
+ return dsToast(msg, "error");
147
+ }
148
+ const { email, password } = parsed.data;
149
+ try {
150
+ const { headers } = await c.var.auth.api.signInEmail({
151
+ returnHeaders: true,
152
+ body: {
153
+ email,
154
+ password
155
+ },
156
+ headers: c.req.raw.headers
157
+ });
158
+ return dsRedirect("/dash", {
159
+ headers
160
+ });
161
+ } catch {
162
+ return dsToast("Invalid email or password", "error");
163
+ }
164
+ });
165
+ signinRoutes.get("/signout", async (c)=>{
166
+ if (c.var.auth) {
167
+ try {
168
+ await c.var.auth.api.signOut({
169
+ headers: c.req.raw.headers
170
+ });
171
+ } catch {
172
+ // Ignore signout errors
173
+ }
174
+ }
175
+ return c.redirect("/");
176
+ });