@jant/core 0.3.27 → 0.3.29
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/bin/reset-password.js +22 -0
- package/dist/client/client.css +1 -0
- package/dist/client/client.js +31561 -0
- package/dist/index.js +15209 -15
- package/package.json +25 -15
- package/src/__tests__/helpers/app.ts +19 -3
- package/src/__tests__/helpers/db.ts +44 -0
- package/src/__tests__/helpers/lingui-core-macro-mock.ts +33 -0
- package/src/app.tsx +111 -174
- package/src/client.ts +13 -0
- package/src/db/migrations/0007_post_collections_m2m.sql +94 -0
- package/src/db/migrations/0008_add_collection_dividers.sql +8 -0
- package/src/db/migrations/0009_drop_collection_show_divider.sql +2 -0
- package/src/db/migrations/0010_add_performance_indexes.sql +16 -0
- package/src/db/schema.ts +24 -4
- package/src/i18n/locales/en.po +810 -385
- package/src/i18n/locales/en.ts +1 -1
- package/src/i18n/locales/zh-Hans.po +733 -522
- package/src/i18n/locales/zh-Hans.ts +1 -1
- package/src/i18n/locales/zh-Hant.po +733 -522
- package/src/i18n/locales/zh-Hant.ts +1 -1
- package/src/i18n/middleware.ts +7 -11
- package/src/index.ts +1 -1
- package/src/lib/__tests__/icons.test.ts +178 -0
- package/src/lib/__tests__/resolve-config.test.ts +184 -0
- package/src/lib/__tests__/schemas.test.ts +12 -6
- package/src/lib/__tests__/theme.test.ts +62 -0
- package/src/lib/__tests__/timezones.test.ts +1 -1
- package/src/lib/__tests__/url.test.ts +12 -0
- package/src/lib/__tests__/view.test.ts +1 -5
- package/src/lib/avatar-upload.ts +18 -10
- package/src/lib/collection-form-bridge.ts +52 -0
- package/src/lib/collections-reorder.ts +28 -0
- package/src/lib/compose-bridge.ts +251 -0
- package/src/lib/errors.ts +116 -0
- package/src/lib/excerpt.ts +1 -1
- package/src/lib/favicon.ts +3 -5
- package/src/lib/html.ts +22 -0
- package/src/lib/icon-catalog.ts +181 -0
- package/src/lib/icons.ts +202 -0
- package/src/lib/navigation.ts +18 -33
- package/src/lib/pagination.ts +3 -2
- package/src/lib/post-form-bridge.ts +136 -0
- package/src/lib/render.tsx +11 -4
- package/src/lib/resolve-config.ts +157 -0
- package/src/lib/schemas.ts +76 -12
- package/src/lib/settings-bridge.ts +139 -0
- package/src/lib/storage.ts +37 -16
- package/src/lib/theme.ts +5 -7
- package/src/lib/timeline.ts +4 -8
- package/src/lib/toast.ts +134 -0
- package/src/lib/upload.ts +71 -0
- package/src/lib/url.ts +9 -1
- package/src/lib/version.ts +16 -0
- package/src/lib/view.ts +9 -10
- package/src/middleware/__tests__/auth.test.ts +6 -28
- package/src/middleware/__tests__/onboarding.test.ts +1 -1
- package/src/middleware/auth.ts +6 -12
- package/src/middleware/config.ts +51 -0
- package/src/middleware/error-handler.ts +56 -0
- package/src/middleware/onboarding.ts +1 -1
- package/src/preset.css +6 -0
- package/src/routes/__tests__/compose.test.ts +104 -17
- package/src/routes/api/__tests__/collections.test.ts +93 -2
- package/src/routes/api/__tests__/posts.test.ts +2 -1
- package/src/routes/api/__tests__/settings.test.ts +1 -1
- package/src/routes/api/collections.ts +64 -68
- package/src/routes/api/nav-items.ts +21 -59
- package/src/routes/api/pages.ts +18 -46
- package/src/routes/api/posts.ts +64 -86
- package/src/routes/api/search.ts +6 -4
- package/src/routes/api/settings.ts +8 -24
- package/src/routes/api/upload.ts +55 -53
- package/src/routes/auth/__tests__/setup.test.ts +118 -0
- package/src/routes/auth/reset.tsx +17 -66
- package/src/routes/auth/setup.tsx +67 -11
- package/src/routes/auth/signin.tsx +44 -8
- package/src/routes/compose.tsx +194 -0
- package/src/routes/dash/__tests__/font-theme.test.ts +110 -0
- package/src/routes/dash/__tests__/pages.test.ts +2 -2
- package/src/routes/dash/__tests__/settings-avatar.test.ts +23 -12
- package/src/routes/dash/appearance.tsx +173 -0
- package/src/routes/dash/collections.tsx +80 -14
- package/src/routes/dash/index.tsx +12 -14
- package/src/routes/dash/media.tsx +46 -49
- package/src/routes/dash/pages.tsx +85 -37
- package/src/routes/dash/posts.tsx +60 -23
- package/src/routes/dash/redirects.tsx +43 -33
- package/src/routes/dash/settings.tsx +234 -214
- package/src/routes/feed/__tests__/rss.test.ts +7 -3
- package/src/routes/feed/rss.ts +11 -16
- package/src/routes/feed/sitemap.ts +15 -9
- package/src/routes/pages/__tests__/collections.test.ts +9 -8
- package/src/routes/pages/archive.tsx +2 -2
- package/src/routes/pages/collection.tsx +76 -9
- package/src/routes/pages/collections.tsx +3 -1
- package/src/routes/pages/featured.tsx +2 -2
- package/src/routes/pages/home.tsx +3 -3
- package/src/routes/pages/latest.tsx +2 -2
- package/src/routes/pages/page.tsx +2 -2
- package/src/routes/pages/post.tsx +2 -2
- package/src/routes/pages/search.tsx +2 -2
- package/src/services/__tests__/collection.test.ts +324 -34
- package/src/services/__tests__/media.test.ts +1 -1
- package/src/services/__tests__/page.test.ts +116 -1
- package/src/services/auth.ts +88 -0
- package/src/services/collection.ts +169 -30
- package/src/services/index.ts +8 -3
- package/src/services/media.ts +39 -12
- package/src/services/navigation.ts +17 -5
- package/src/services/page.ts +24 -4
- package/src/services/post.ts +87 -19
- package/src/services/search.ts +0 -1
- package/src/services/settings.ts +21 -13
- package/src/style.css +3 -0
- package/src/styles/components.css +42 -1
- package/src/styles/tokens.css +4 -0
- package/src/styles/ui.css +902 -73
- package/src/types/app-context.ts +25 -0
- package/src/types/bindings.ts +1 -0
- package/src/types/config.ts +60 -23
- package/src/types/entities.ts +12 -2
- package/src/types/lingui-react-macro.d.ts +3 -3
- package/src/types/operations.ts +2 -4
- package/src/types/views.ts +1 -3
- package/src/ui/__tests__/font-themes.test.ts +27 -8
- package/src/ui/color-themes.ts +1 -1
- package/src/ui/components/__tests__/jant-collection-form.test.ts +153 -0
- package/src/ui/components/__tests__/jant-compose-dialog.test.ts +512 -0
- package/src/ui/components/__tests__/jant-compose-editor.test.ts +272 -0
- package/src/ui/components/__tests__/jant-post-form.test.ts +172 -0
- package/src/ui/components/__tests__/jant-settings-avatar.test.ts +235 -0
- package/src/ui/components/__tests__/jant-settings-general.test.ts +319 -0
- package/src/ui/components/collection-types.ts +45 -0
- package/src/ui/components/compose-types.ts +75 -0
- package/src/ui/components/jant-collection-form.ts +512 -0
- package/src/ui/components/jant-compose-dialog.ts +494 -0
- package/src/ui/components/jant-compose-editor.ts +799 -0
- package/src/ui/components/jant-post-form.ts +290 -0
- package/src/ui/components/jant-settings-avatar.ts +231 -0
- package/src/ui/components/jant-settings-general.ts +436 -0
- package/src/ui/components/post-form-template.ts +260 -0
- package/src/ui/components/post-form-types.ts +87 -0
- package/src/ui/components/settings-types.ts +62 -0
- package/src/ui/compose/ComposeDialog.tsx +141 -385
- package/src/ui/compose/ComposePrompt.tsx +3 -3
- package/src/ui/dash/PostList.tsx +55 -61
- package/src/ui/dash/appearance/AdvancedContent.tsx +80 -0
- package/src/ui/dash/appearance/AppearanceNav.tsx +56 -0
- package/src/ui/dash/appearance/ColorThemeContent.tsx +129 -0
- package/src/ui/dash/appearance/FontThemeContent.tsx +98 -0
- package/src/ui/dash/collections/CollectionForm.tsx +130 -117
- package/src/ui/dash/collections/CollectionsListContent.tsx +102 -41
- package/src/ui/dash/collections/IconPickerGrid.tsx +50 -0
- package/src/ui/dash/collections/ViewCollectionContent.tsx +14 -3
- package/src/ui/dash/index.ts +1 -1
- package/src/ui/dash/posts/PostForm.tsx +248 -0
- package/src/ui/dash/settings/AccountContent.tsx +69 -80
- package/src/ui/dash/settings/GeneralContent.tsx +159 -478
- package/src/ui/dash/settings/SettingsNav.tsx +4 -4
- package/src/ui/font-themes.ts +115 -32
- package/src/ui/layouts/BaseLayout.tsx +49 -19
- package/src/ui/layouts/DashLayout.tsx +14 -9
- package/src/ui/layouts/SiteLayout.tsx +38 -23
- package/src/ui/pages/CollectionPage.tsx +12 -2
- package/src/ui/pages/CollectionsPage.tsx +27 -27
- package/src/ui/pages/HomePage.tsx +15 -6
- package/src/ui/pages/SearchPage.tsx +1 -2
- package/src/ui/shared/CollectionsSidebar.tsx +59 -0
- package/src/ui/shared/Pagination.tsx +2 -2
- package/dist/app.js +0 -267
- package/dist/auth.js +0 -39
- package/dist/client.js +0 -13
- package/dist/db/index.js +0 -10
- package/dist/db/schema.js +0 -224
- package/dist/i18n/Trans.js +0 -24
- package/dist/i18n/context.js +0 -58
- package/dist/i18n/detect.js +0 -26
- package/dist/i18n/i18n.js +0 -49
- package/dist/i18n/index.js +0 -44
- package/dist/i18n/locales/en.js +0 -1
- package/dist/i18n/locales/zh-Hans.js +0 -1
- package/dist/i18n/locales/zh-Hant.js +0 -1
- package/dist/i18n/locales.js +0 -13
- package/dist/i18n/middleware.js +0 -30
- package/dist/lib/avatar-upload.js +0 -134
- package/dist/lib/config.js +0 -143
- package/dist/lib/constants.js +0 -50
- package/dist/lib/excerpt.js +0 -76
- package/dist/lib/favicon.js +0 -102
- package/dist/lib/feed.js +0 -123
- package/dist/lib/image-processor.js +0 -187
- package/dist/lib/image.js +0 -97
- package/dist/lib/index.js +0 -7
- package/dist/lib/markdown.js +0 -83
- package/dist/lib/media-helpers.js +0 -49
- package/dist/lib/media-upload.js +0 -104
- package/dist/lib/nav-reorder.js +0 -27
- package/dist/lib/navigation.js +0 -79
- package/dist/lib/pagination.js +0 -44
- package/dist/lib/render.js +0 -53
- package/dist/lib/schemas.js +0 -174
- package/dist/lib/sqid.js +0 -72
- package/dist/lib/sse.js +0 -218
- package/dist/lib/storage.js +0 -164
- package/dist/lib/theme.js +0 -65
- package/dist/lib/time.js +0 -159
- package/dist/lib/timeline.js +0 -95
- package/dist/lib/timezones.js +0 -388
- package/dist/lib/url.js +0 -89
- package/dist/lib/view.js +0 -217
- package/dist/middleware/auth.js +0 -52
- package/dist/middleware/onboarding.js +0 -41
- package/dist/routes/api/collections.js +0 -124
- package/dist/routes/api/nav-items.js +0 -104
- package/dist/routes/api/pages.js +0 -91
- package/dist/routes/api/posts.js +0 -218
- package/dist/routes/api/search.js +0 -48
- package/dist/routes/api/settings.js +0 -68
- package/dist/routes/api/upload.js +0 -246
- package/dist/routes/auth/reset.js +0 -221
- package/dist/routes/auth/setup.js +0 -194
- package/dist/routes/auth/signin.js +0 -176
- package/dist/routes/compose.js +0 -48
- package/dist/routes/dash/collections.js +0 -115
- package/dist/routes/dash/index.js +0 -118
- package/dist/routes/dash/media.js +0 -106
- package/dist/routes/dash/pages.js +0 -294
- package/dist/routes/dash/posts.js +0 -244
- package/dist/routes/dash/redirects.js +0 -257
- package/dist/routes/dash/settings.js +0 -379
- package/dist/routes/feed/rss.js +0 -62
- package/dist/routes/feed/sitemap.js +0 -49
- package/dist/routes/pages/archive.js +0 -62
- package/dist/routes/pages/collection.js +0 -34
- package/dist/routes/pages/collections.js +0 -28
- package/dist/routes/pages/featured.js +0 -36
- package/dist/routes/pages/home.js +0 -64
- package/dist/routes/pages/latest.js +0 -45
- package/dist/routes/pages/page.js +0 -68
- package/dist/routes/pages/post.js +0 -44
- package/dist/routes/pages/search.js +0 -54
- package/dist/services/collection.js +0 -109
- package/dist/services/index.js +0 -24
- package/dist/services/media.js +0 -117
- package/dist/services/navigation.js +0 -91
- package/dist/services/page.js +0 -84
- package/dist/services/post.js +0 -229
- package/dist/services/redirect.js +0 -48
- package/dist/services/search.js +0 -67
- package/dist/services/settings.js +0 -68
- package/dist/types/bindings.js +0 -3
- package/dist/types/config.js +0 -147
- package/dist/types/constants.js +0 -27
- package/dist/types/entities.js +0 -3
- package/dist/types/lingui-react-macro.d.js +0 -9
- package/dist/types/operations.js +0 -3
- package/dist/types/props.js +0 -3
- package/dist/types/sortablejs.d.js +0 -5
- package/dist/types/views.js +0 -5
- package/dist/types.js +0 -11
- package/dist/ui/color-themes.js +0 -268
- package/dist/ui/compose/ComposeDialog.js +0 -467
- package/dist/ui/compose/ComposePrompt.js +0 -55
- package/dist/ui/dash/ActionButtons.js +0 -46
- package/dist/ui/dash/CrudPageHeader.js +0 -22
- package/dist/ui/dash/DangerZone.js +0 -36
- package/dist/ui/dash/FormatBadge.js +0 -27
- package/dist/ui/dash/ListItemRow.js +0 -21
- package/dist/ui/dash/PageForm.js +0 -195
- package/dist/ui/dash/PostForm.js +0 -395
- package/dist/ui/dash/PostList.js +0 -83
- package/dist/ui/dash/StatusBadge.js +0 -46
- package/dist/ui/dash/collections/CollectionForm.js +0 -152
- package/dist/ui/dash/collections/CollectionsListContent.js +0 -68
- package/dist/ui/dash/collections/ViewCollectionContent.js +0 -96
- package/dist/ui/dash/index.js +0 -10
- package/dist/ui/dash/media/MediaListContent.js +0 -166
- package/dist/ui/dash/media/ViewMediaContent.js +0 -212
- package/dist/ui/dash/pages/LinkFormContent.js +0 -130
- package/dist/ui/dash/pages/UnifiedPagesContent.js +0 -193
- package/dist/ui/dash/settings/AccountContent.js +0 -209
- package/dist/ui/dash/settings/AppearanceContent.js +0 -259
- package/dist/ui/dash/settings/GeneralContent.js +0 -536
- package/dist/ui/dash/settings/SettingsNav.js +0 -41
- package/dist/ui/feed/LinkCard.js +0 -72
- package/dist/ui/feed/NoteCard.js +0 -58
- package/dist/ui/feed/QuoteCard.js +0 -63
- package/dist/ui/feed/ThreadPreview.js +0 -48
- package/dist/ui/feed/TimelineFeed.js +0 -41
- package/dist/ui/feed/TimelineItem.js +0 -27
- package/dist/ui/font-themes.js +0 -36
- package/dist/ui/layouts/BaseLayout.js +0 -153
- package/dist/ui/layouts/DashLayout.js +0 -141
- package/dist/ui/layouts/SiteLayout.js +0 -169
- package/dist/ui/pages/ArchivePage.js +0 -143
- package/dist/ui/pages/CollectionPage.js +0 -70
- package/dist/ui/pages/CollectionsPage.js +0 -76
- package/dist/ui/pages/FeaturedPage.js +0 -24
- package/dist/ui/pages/HomePage.js +0 -24
- package/dist/ui/pages/PostPage.js +0 -55
- package/dist/ui/pages/SearchPage.js +0 -122
- package/dist/ui/pages/SinglePage.js +0 -23
- package/dist/ui/shared/EmptyState.js +0 -27
- package/dist/ui/shared/MediaGallery.js +0 -35
- package/dist/ui/shared/Pagination.js +0 -195
- package/dist/ui/shared/ThreadView.js +0 -108
- package/dist/ui/shared/index.js +0 -5
- package/dist/vendor/datastar.js +0 -1606
- package/src/lib/__tests__/config.test.ts +0 -192
- package/src/lib/config.ts +0 -167
- package/src/routes/compose.ts +0 -63
- package/src/ui/dash/PostForm.tsx +0 -360
- package/src/ui/dash/settings/AppearanceContent.tsx +0 -254
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jant/core",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.29",
|
|
4
4
|
"description": "A modern, open-source microblogging platform built on Cloudflare Workers",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -8,14 +8,13 @@
|
|
|
8
8
|
"types": "./src/index.ts",
|
|
9
9
|
"default": "./dist/index.js"
|
|
10
10
|
},
|
|
11
|
-
"./i18n":
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
"./preset.css": "./src/preset.css",
|
|
16
|
-
"./client": "./dist/client.js"
|
|
11
|
+
"./i18n": "./src/i18n/index.ts"
|
|
12
|
+
},
|
|
13
|
+
"bin": {
|
|
14
|
+
"jant-reset-password": "bin/reset-password.js"
|
|
17
15
|
},
|
|
18
16
|
"files": [
|
|
17
|
+
"bin",
|
|
19
18
|
"dist",
|
|
20
19
|
"src"
|
|
21
20
|
],
|
|
@@ -25,16 +24,18 @@
|
|
|
25
24
|
"dependencies": {
|
|
26
25
|
"@aws-sdk/client-s3": "^3.987.0",
|
|
27
26
|
"@lingui/core": "^5.9.0",
|
|
27
|
+
"@lingui/react": "^5.9.0",
|
|
28
28
|
"@tailwindcss/typography": "^0.5.19",
|
|
29
29
|
"basecoat-css": "^0.3.10",
|
|
30
30
|
"better-auth": "^1.4.18",
|
|
31
31
|
"drizzle-orm": "^0.45.1",
|
|
32
|
-
"
|
|
32
|
+
"lit": "^3.3.2",
|
|
33
|
+
"lucide-static": "^0.574.0",
|
|
33
34
|
"marked": "^17.0.1",
|
|
35
|
+
"pinyin-pro": "^3.28.0",
|
|
34
36
|
"sortablejs": "^1.15.6",
|
|
35
37
|
"sqids": "^0.3.0",
|
|
36
38
|
"uuidv7": "^1.1.0",
|
|
37
|
-
"vite-ssr-components": "^0.5.2",
|
|
38
39
|
"zod": "^4.3.6"
|
|
39
40
|
},
|
|
40
41
|
"peerDependencies": {
|
|
@@ -42,22 +43,27 @@
|
|
|
42
43
|
"tailwindcss": "^4.0.0"
|
|
43
44
|
},
|
|
44
45
|
"devDependencies": {
|
|
46
|
+
"@cloudflare/vite-plugin": "^1.22.1",
|
|
45
47
|
"@cloudflare/workers-types": "^4.20260131.0",
|
|
46
48
|
"@lingui/cli": "^5.9.0",
|
|
47
49
|
"@lingui/conf": "^5.9.0",
|
|
48
50
|
"@lingui/format-po": "^5.9.0",
|
|
49
51
|
"@lingui/swc-plugin": "^5.10.1",
|
|
50
|
-
"@swc/cli": "^0.6.0",
|
|
51
52
|
"@swc/core": "^1.15.11",
|
|
52
53
|
"@types/better-sqlite3": "^7.6.13",
|
|
53
54
|
"@types/node": "^25.1.0",
|
|
54
55
|
"better-sqlite3": "^12.6.2",
|
|
55
56
|
"drizzle-kit": "^0.31.8",
|
|
56
57
|
"glob": "^13.0.0",
|
|
58
|
+
"happy-dom": "^20.6.3",
|
|
59
|
+
"@tailwindcss/vite": "^4.1.18",
|
|
57
60
|
"tailwindcss": "^4.1.18",
|
|
58
61
|
"tsx": "^4.21.0",
|
|
59
62
|
"typescript": "^5.9.3",
|
|
60
|
-
"
|
|
63
|
+
"unplugin-swc": "^1.5.9",
|
|
64
|
+
"vite": "^7.3.1",
|
|
65
|
+
"vitest": "^4.0.18",
|
|
66
|
+
"wrangler": "^4.61.1"
|
|
61
67
|
},
|
|
62
68
|
"repository": {
|
|
63
69
|
"type": "git",
|
|
@@ -83,14 +89,18 @@
|
|
|
83
89
|
"node": ">=24.0.0"
|
|
84
90
|
},
|
|
85
91
|
"scripts": {
|
|
86
|
-
"build": "pnpm build:lib",
|
|
87
|
-
"build:
|
|
88
|
-
"build:
|
|
89
|
-
"typecheck": "tsc --noEmit",
|
|
92
|
+
"build": "pnpm build:lib && pnpm build:client",
|
|
93
|
+
"build:client": "vite build --config vite.config.client.ts",
|
|
94
|
+
"build:lib": "vite build --config vite.config.worker.ts",
|
|
95
|
+
"typecheck": "tsc -b --noEmit",
|
|
90
96
|
"db:generate": "drizzle-kit generate",
|
|
91
97
|
"i18n:extract": "lingui extract",
|
|
92
98
|
"i18n:compile": "lingui compile --typescript",
|
|
93
99
|
"i18n:build": "pnpm i18n:extract && pnpm i18n:compile",
|
|
100
|
+
"dev": "pnpm db:migrate:local && vite dev",
|
|
101
|
+
"dev:debug": "pnpm db:migrate:local && vite dev --port 19019",
|
|
102
|
+
"db:migrate:local": "yes | wrangler d1 migrations apply DB --local",
|
|
103
|
+
"db:migrate:remote": "wrangler d1 migrations apply DB --remote",
|
|
94
104
|
"test": "vitest run",
|
|
95
105
|
"test:watch": "vitest",
|
|
96
106
|
"test:coverage": "vitest run --coverage"
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
import { Hono } from "hono";
|
|
8
8
|
import type { Bindings } from "../../types.js";
|
|
9
|
-
import type { AppVariables } from "../../app.js";
|
|
9
|
+
import type { AppVariables } from "../../types/app-context.js";
|
|
10
10
|
import { createTestDatabase } from "./db.js";
|
|
11
11
|
import { createPostService } from "../../services/post.js";
|
|
12
12
|
import { createPageService } from "../../services/page.js";
|
|
@@ -16,8 +16,12 @@ import { createMediaService } from "../../services/media.js";
|
|
|
16
16
|
import { createCollectionService } from "../../services/collection.js";
|
|
17
17
|
import { createSearchService } from "../../services/search.js";
|
|
18
18
|
import { createNavItemService } from "../../services/navigation.js";
|
|
19
|
+
import { createAuthService } from "../../services/auth.js";
|
|
19
20
|
import type { Database } from "../../db/index.js";
|
|
20
21
|
import type BetterSqlite3 from "better-sqlite3";
|
|
22
|
+
import { errorHandler } from "../../middleware/error-handler.js";
|
|
23
|
+
import { createI18n } from "../../i18n/i18n.js";
|
|
24
|
+
import { resolveConfig } from "../../lib/resolve-config.js";
|
|
21
25
|
|
|
22
26
|
type Env = { Bindings: Bindings; Variables: AppVariables };
|
|
23
27
|
|
|
@@ -40,19 +44,24 @@ export function createTestApp(options: TestAppOptions = {}) {
|
|
|
40
44
|
// Create a mock D1 for search service
|
|
41
45
|
const mockD1 = createMockD1(sqlite);
|
|
42
46
|
|
|
47
|
+
const settingsService = createSettingsService(db);
|
|
43
48
|
const services = {
|
|
44
49
|
posts: createPostService(db),
|
|
45
50
|
pages: createPageService(db),
|
|
46
|
-
settings:
|
|
51
|
+
settings: settingsService,
|
|
47
52
|
redirects: createRedirectService(db),
|
|
48
53
|
media: createMediaService(db),
|
|
49
54
|
collections: createCollectionService(db),
|
|
50
55
|
search: createSearchService(mockD1),
|
|
51
56
|
navItems: createNavItemService(db),
|
|
57
|
+
auth: createAuthService(db, settingsService),
|
|
52
58
|
};
|
|
53
59
|
|
|
54
60
|
const app = new Hono<Env>();
|
|
55
61
|
|
|
62
|
+
// Global error handler: maps DomainError → HTTP responses
|
|
63
|
+
app.onError(errorHandler);
|
|
64
|
+
|
|
56
65
|
// Inject env bindings and services middleware
|
|
57
66
|
app.use("*", async (c, next) => {
|
|
58
67
|
// Provide mock env bindings so c.env.* works in route handlers
|
|
@@ -61,9 +70,16 @@ export function createTestApp(options: TestAppOptions = {}) {
|
|
|
61
70
|
} as AppVariables["services"] extends never ? never : Bindings;
|
|
62
71
|
|
|
63
72
|
c.set("services", services as AppVariables["services"]);
|
|
64
|
-
|
|
73
|
+
const allSettings = await services.settings.getAll();
|
|
74
|
+
c.set("allSettings", allSettings);
|
|
75
|
+
c.set("appConfig", resolveConfig(c.env, allSettings));
|
|
65
76
|
c.set("storage", null);
|
|
66
77
|
|
|
78
|
+
// i18n (English default for tests)
|
|
79
|
+
const i18n = createI18n("en");
|
|
80
|
+
c.set("lang", "en");
|
|
81
|
+
c.set("i18n", i18n);
|
|
82
|
+
|
|
67
83
|
if (options.authenticated) {
|
|
68
84
|
// Mock auth that always returns a session
|
|
69
85
|
c.set("auth", {
|
|
@@ -89,7 +89,51 @@ export function createTestDatabase(options?: { fts?: boolean }) {
|
|
|
89
89
|
// Apply 0006: rename slug to path on posts
|
|
90
90
|
applyMigration(sqlite, "0006_rename_slug_to_path.sql");
|
|
91
91
|
|
|
92
|
+
// Apply 0007: post_collections M:N junction table
|
|
93
|
+
const m7 = readFileSync(
|
|
94
|
+
resolve(MIGRATIONS_DIR, "0007_post_collections_m2m.sql"),
|
|
95
|
+
"utf-8",
|
|
96
|
+
);
|
|
97
|
+
for (const stmt of m7.split("--> statement-breakpoint")) {
|
|
98
|
+
const trimmed = stmt.trim();
|
|
99
|
+
if (!trimmed) continue;
|
|
100
|
+
// Skip FTS trigger statements if FTS not requested
|
|
101
|
+
const isFts = trimmed.includes("posts_fts");
|
|
102
|
+
if (!options?.fts && isFts) continue;
|
|
103
|
+
try {
|
|
104
|
+
sqlite.exec(trimmed);
|
|
105
|
+
} catch {
|
|
106
|
+
// Ignore DROP TRIGGER failures silently
|
|
107
|
+
if (!trimmed.startsWith("DROP TRIGGER")) {
|
|
108
|
+
throw new Error(`Migration 0007 failed: ${trimmed.slice(0, 100)}`);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Apply 0008: collection_dividers table
|
|
114
|
+
applyMigration(sqlite, "0008_add_collection_dividers.sql");
|
|
115
|
+
|
|
116
|
+
// Apply 0009: drop show_divider column from collections
|
|
117
|
+
applyMigration(sqlite, "0009_drop_collection_show_divider.sql");
|
|
118
|
+
|
|
119
|
+
// Apply 0010: performance indexes
|
|
120
|
+
applyMigration(sqlite, "0010_add_performance_indexes.sql");
|
|
121
|
+
|
|
92
122
|
const db = drizzle(sqlite, { schema });
|
|
93
123
|
|
|
124
|
+
// Polyfill D1 batch() for test compatibility.
|
|
125
|
+
// In production, D1 batch executes statements atomically in a single transaction.
|
|
126
|
+
// In tests, better-sqlite3 is synchronous and single-threaded so sequential
|
|
127
|
+
// execution is effectively atomic.
|
|
128
|
+
Object.defineProperty(db, "batch", {
|
|
129
|
+
value: async (queries: PromiseLike<unknown>[]) => {
|
|
130
|
+
const results = [];
|
|
131
|
+
for (const q of queries) {
|
|
132
|
+
results.push(await q);
|
|
133
|
+
}
|
|
134
|
+
return results;
|
|
135
|
+
},
|
|
136
|
+
});
|
|
137
|
+
|
|
94
138
|
return { db, sqlite };
|
|
95
139
|
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mock for @lingui/core/macro in the Vitest environment.
|
|
3
|
+
*
|
|
4
|
+
* The real module requires the Babel macro ecosystem which is not available
|
|
5
|
+
* under Vitest. This mock replicates the *post-transformation* API surface:
|
|
6
|
+
* `msg()` / `defineMessage()` return a MessageDescriptor whose `id` equals
|
|
7
|
+
* the source `message`. At runtime `i18n._()` falls back to `message` when
|
|
8
|
+
* the ID is not in the catalog, so English source strings propagate through.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import type { MessageDescriptor } from "@lingui/core";
|
|
12
|
+
|
|
13
|
+
type MacroInput =
|
|
14
|
+
| { id?: string; message: string; comment?: string; context?: string }
|
|
15
|
+
| TemplateStringsArray;
|
|
16
|
+
|
|
17
|
+
function toDescriptor(
|
|
18
|
+
input: MacroInput,
|
|
19
|
+
...args: unknown[]
|
|
20
|
+
): MessageDescriptor {
|
|
21
|
+
if (typeof input === "object" && "message" in input) {
|
|
22
|
+
return { id: input.message, message: input.message } as MessageDescriptor;
|
|
23
|
+
}
|
|
24
|
+
// Template literal form: msg`some text`
|
|
25
|
+
const raw = (input as TemplateStringsArray).reduce(
|
|
26
|
+
(acc, str, i) => acc + str + (args[i] ?? ""),
|
|
27
|
+
"",
|
|
28
|
+
);
|
|
29
|
+
return { id: raw, message: raw } as MessageDescriptor;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export const msg = toDescriptor;
|
|
33
|
+
export const defineMessage = toDescriptor;
|
package/src/app.tsx
CHANGED
|
@@ -4,11 +4,10 @@
|
|
|
4
4
|
|
|
5
5
|
import { Hono } from "hono";
|
|
6
6
|
import { createDatabase } from "./db/index.js";
|
|
7
|
-
import { createServices
|
|
8
|
-
import { createAuth
|
|
7
|
+
import { createServices } from "./services/index.js";
|
|
8
|
+
import { createAuth } from "./auth.js";
|
|
9
9
|
import { i18nMiddleware } from "./i18n/index.js";
|
|
10
|
-
import type { Bindings
|
|
11
|
-
import { SETTINGS_KEYS } from "./lib/constants.js";
|
|
10
|
+
import type { Bindings } from "./types.js";
|
|
12
11
|
|
|
13
12
|
// Routes - Auth
|
|
14
13
|
import { setupRoutes } from "./routes/auth/setup.js";
|
|
@@ -34,6 +33,7 @@ import { mediaRoutes as dashMediaRoutes } from "./routes/dash/media.js";
|
|
|
34
33
|
import { settingsRoutes as dashSettingsRoutes } from "./routes/dash/settings.js";
|
|
35
34
|
import { redirectsRoutes as dashRedirectsRoutes } from "./routes/dash/redirects.js";
|
|
36
35
|
import { collectionsRoutes as dashCollectionsRoutes } from "./routes/dash/collections.js";
|
|
36
|
+
import { appearanceRoutes as dashAppearanceRoutes } from "./routes/dash/appearance.js";
|
|
37
37
|
|
|
38
38
|
// Routes - API
|
|
39
39
|
import { postsApiRoutes } from "./routes/api/posts.js";
|
|
@@ -53,152 +53,148 @@ import { sitemapRoutes } from "./routes/feed/sitemap.js";
|
|
|
53
53
|
// Middleware
|
|
54
54
|
import { requireAuth } from "./middleware/auth.js";
|
|
55
55
|
import { requireOnboarding } from "./middleware/onboarding.js";
|
|
56
|
+
import { errorHandler } from "./middleware/error-handler.js";
|
|
57
|
+
import { withConfig } from "./middleware/config.js";
|
|
56
58
|
|
|
57
|
-
import {
|
|
58
|
-
import { createStorageDriver, type StorageDriver } from "./lib/storage.js";
|
|
59
|
-
import { BUILTIN_FONT_THEMES } from "./ui/font-themes.js";
|
|
60
|
-
import { getMediaUrl, getPublicUrlForProvider } from "./lib/image.js";
|
|
59
|
+
import { createStorageDriver } from "./lib/storage.js";
|
|
61
60
|
import { base64ToUint8Array } from "./lib/favicon.js";
|
|
61
|
+
import { type AppVariables, type App } from "./types/app-context.js";
|
|
62
62
|
|
|
63
|
-
|
|
64
|
-
export interface AppVariables {
|
|
65
|
-
services: Services;
|
|
66
|
-
auth: Auth;
|
|
67
|
-
config: JantConfig;
|
|
68
|
-
themeStyle: string;
|
|
69
|
-
customCSS: string;
|
|
70
|
-
isAuthenticated: boolean;
|
|
71
|
-
storage: StorageDriver | null;
|
|
72
|
-
faviconUrl?: string;
|
|
73
|
-
noindex?: boolean;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
export type App = Hono<{ Bindings: Bindings; Variables: AppVariables }>;
|
|
63
|
+
export type { AppVariables, App };
|
|
77
64
|
|
|
78
65
|
/**
|
|
79
66
|
* Create a Jant application
|
|
80
67
|
*
|
|
81
|
-
* @param config - Optional configuration
|
|
82
68
|
* @returns Hono app instance
|
|
83
69
|
*
|
|
84
|
-
* Site settings (name, description, language) should be configured via
|
|
85
|
-
* environment variables (SITE_NAME, SITE_DESCRIPTION, SITE_LANGUAGE).
|
|
86
|
-
* They can also be set in the dashboard, which stores them in the database.
|
|
87
|
-
*
|
|
88
70
|
* @example
|
|
89
71
|
* ```typescript
|
|
90
72
|
* import { createApp } from "@jant/core";
|
|
91
73
|
*
|
|
92
|
-
* export default createApp(
|
|
93
|
-
* cssVariables: { "--card-radius": "0" },
|
|
94
|
-
* });
|
|
74
|
+
* export default createApp();
|
|
95
75
|
* ```
|
|
96
76
|
*/
|
|
97
|
-
export function createApp(
|
|
98
|
-
const resolvedConfig: JantConfig = { ...config };
|
|
99
|
-
|
|
77
|
+
export function createApp(): App {
|
|
100
78
|
const app = new Hono<{ Bindings: Bindings; Variables: AppVariables }>();
|
|
101
79
|
|
|
102
|
-
//
|
|
80
|
+
// Global error handler: maps DomainError → HTTP responses
|
|
81
|
+
app.onError(errorHandler);
|
|
82
|
+
|
|
83
|
+
// Lightweight init — no DB queries
|
|
103
84
|
app.use("*", async (c, next) => {
|
|
85
|
+
if (!c.env.AUTH_SECRET) {
|
|
86
|
+
return c.html(
|
|
87
|
+
`<!DOCTYPE html>
|
|
88
|
+
<html lang="en">
|
|
89
|
+
<head>
|
|
90
|
+
<meta charset="utf-8">
|
|
91
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
92
|
+
<title>Configuration Error</title>
|
|
93
|
+
<style>body{font-family:system-ui,sans-serif;display:flex;justify-content:center;align-items:center;min-height:100vh;margin:0;background:#fafafa;color:#111}div{max-width:480px;text-align:center}h1{font-size:1.25rem;font-weight:600}p{color:#666;line-height:1.6}code{background:#eee;padding:2px 6px;border-radius:4px;font-size:.9em}</style>
|
|
94
|
+
</head>
|
|
95
|
+
<body>
|
|
96
|
+
<div>
|
|
97
|
+
<h1>AUTH_SECRET is not set</h1>
|
|
98
|
+
<p>Set <code>AUTH_SECRET</code> in <code>.dev.vars</code> or <code>wrangler.toml</code> to start Jant.</p>
|
|
99
|
+
</div>
|
|
100
|
+
</body>
|
|
101
|
+
</html>`,
|
|
102
|
+
500,
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
|
|
104
106
|
// Use withSession() to enable D1 Read Replication
|
|
105
107
|
const session = c.env.DB.withSession();
|
|
106
108
|
|
|
107
109
|
// Note: Drizzle ORM doesn't officially support D1DatabaseSession yet (issue #2226)
|
|
108
110
|
// but it works at runtime. We use type assertion as a temporary workaround.
|
|
109
111
|
const db = createDatabase(session as unknown as D1Database);
|
|
110
|
-
|
|
111
|
-
c.set("services", services);
|
|
112
|
-
c.set("config", resolvedConfig);
|
|
112
|
+
c.set("services", createServices(db, session as unknown as D1Database));
|
|
113
113
|
c.set("storage", createStorageDriver(c.env));
|
|
114
114
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
if (c.env.AUTH_SECRET) {
|
|
123
|
-
const baseURL = c.env.SITE_URL || new URL(c.req.url).origin;
|
|
124
|
-
const requestUrl = new URL(c.req.url);
|
|
125
|
-
const auth = createAuth(session as unknown as D1Database, {
|
|
115
|
+
const baseURL = c.env.SITE_URL || new URL(c.req.url).origin;
|
|
116
|
+
const requestUrl = new URL(c.req.url);
|
|
117
|
+
c.set(
|
|
118
|
+
"auth",
|
|
119
|
+
createAuth(session as unknown as D1Database, {
|
|
126
120
|
secret: c.env.AUTH_SECRET,
|
|
127
121
|
baseURL,
|
|
128
122
|
useSecureCookies: requestUrl.protocol === "https:",
|
|
129
|
-
})
|
|
130
|
-
|
|
131
|
-
}
|
|
123
|
+
}),
|
|
124
|
+
);
|
|
132
125
|
|
|
133
126
|
await next();
|
|
134
127
|
});
|
|
135
128
|
|
|
136
|
-
//
|
|
137
|
-
app.use("*", requireOnboarding());
|
|
129
|
+
// --- Routes that don't need config/theme ---
|
|
138
130
|
|
|
139
|
-
//
|
|
140
|
-
app.
|
|
141
|
-
const [themeId, fontThemeId, customCSS, noindexValue, avatarKey] =
|
|
142
|
-
await Promise.all([
|
|
143
|
-
c.var.services.settings.get(SETTINGS_KEYS.THEME),
|
|
144
|
-
c.var.services.settings.get("FONT_THEME"),
|
|
145
|
-
c.var.services.settings.get(SETTINGS_KEYS.CUSTOM_CSS),
|
|
146
|
-
c.var.services.settings.get("NOINDEX"),
|
|
147
|
-
c.var.services.settings.get("SITE_AVATAR"),
|
|
148
|
-
]);
|
|
149
|
-
const themes = getAvailableThemes(resolvedConfig);
|
|
150
|
-
const activeTheme = themeId
|
|
151
|
-
? themes.find((t) => t.id === themeId)
|
|
152
|
-
: undefined;
|
|
153
|
-
|
|
154
|
-
// Build font override CSS variables
|
|
155
|
-
const fontTheme = fontThemeId
|
|
156
|
-
? BUILTIN_FONT_THEMES.find((f) => f.id === fontThemeId)
|
|
157
|
-
: undefined;
|
|
158
|
-
const fontOverrides: Record<string, string> = {};
|
|
159
|
-
if (fontTheme) {
|
|
160
|
-
fontOverrides["--font-body"] = fontTheme.fontFamily;
|
|
161
|
-
}
|
|
131
|
+
// Health check
|
|
132
|
+
app.get("/health", (c) => c.json({ status: "ok" }));
|
|
162
133
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
c.set("customCSS", customCSS ?? "");
|
|
169
|
-
|
|
170
|
-
// Noindex
|
|
171
|
-
c.set("noindex", noindexValue === "true");
|
|
172
|
-
|
|
173
|
-
// Resolve favicon from avatar storage key
|
|
174
|
-
if (avatarKey) {
|
|
175
|
-
const publicUrl = getPublicUrlForProvider(
|
|
176
|
-
c.env.STORAGE_DRIVER || "r2",
|
|
177
|
-
c.env.R2_PUBLIC_URL,
|
|
178
|
-
c.env.S3_PUBLIC_URL,
|
|
179
|
-
);
|
|
180
|
-
c.set("faviconUrl", getMediaUrl(avatarKey, publicUrl));
|
|
134
|
+
// Media files from storage (path matches storage key: media/YYYY/MM/uuid.ext)
|
|
135
|
+
app.get("/media/*", async (c) => {
|
|
136
|
+
const storage = c.var.storage;
|
|
137
|
+
if (!storage) {
|
|
138
|
+
return c.notFound();
|
|
181
139
|
}
|
|
182
140
|
|
|
183
|
-
//
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
headers: c.req.raw.headers,
|
|
189
|
-
});
|
|
190
|
-
isAuthenticated = !!session;
|
|
191
|
-
} catch {
|
|
192
|
-
// Not authenticated
|
|
193
|
-
}
|
|
141
|
+
// The storage key is the full path without the leading "/"
|
|
142
|
+
const storageKey = c.req.path.slice(1);
|
|
143
|
+
const object = await storage.get(storageKey);
|
|
144
|
+
if (!object) {
|
|
145
|
+
return c.notFound();
|
|
194
146
|
}
|
|
195
|
-
c.set("isAuthenticated", isAuthenticated);
|
|
196
147
|
|
|
197
|
-
|
|
148
|
+
const headers = new Headers();
|
|
149
|
+
headers.set(
|
|
150
|
+
"Content-Type",
|
|
151
|
+
object.contentType || "application/octet-stream",
|
|
152
|
+
);
|
|
153
|
+
headers.set("Cache-Control", "public, max-age=31536000, immutable");
|
|
154
|
+
|
|
155
|
+
return new Response(object.body, { headers });
|
|
198
156
|
});
|
|
199
157
|
|
|
200
|
-
//
|
|
201
|
-
app.
|
|
158
|
+
// better-auth handler
|
|
159
|
+
app.all("/api/auth/*", async (c) => {
|
|
160
|
+
return c.var.auth.handler(c.req.raw);
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
// Favicon routes - serve from DB settings (small files, avoids R2 round-trip)
|
|
164
|
+
app.get("/favicon.ico", async (c) => {
|
|
165
|
+
const data = await c.var.services.settings.get("SITE_FAVICON_ICO");
|
|
166
|
+
if (!data) return c.notFound();
|
|
167
|
+
|
|
168
|
+
return new Response(base64ToUint8Array(data), {
|
|
169
|
+
headers: {
|
|
170
|
+
"Content-Type": "image/x-icon",
|
|
171
|
+
"Cache-Control": "public, max-age=86400",
|
|
172
|
+
},
|
|
173
|
+
});
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
app.get("/apple-touch-icon.png", async (c) => {
|
|
177
|
+
const storage = c.var.storage;
|
|
178
|
+
const storageKey = await c.var.services.settings.get(
|
|
179
|
+
"SITE_FAVICON_APPLE_TOUCH",
|
|
180
|
+
);
|
|
181
|
+
if (!storage || !storageKey) return c.notFound();
|
|
182
|
+
|
|
183
|
+
const object = await storage.get(storageKey);
|
|
184
|
+
if (!object) return c.notFound();
|
|
185
|
+
|
|
186
|
+
return new Response(object.body, {
|
|
187
|
+
headers: {
|
|
188
|
+
"Content-Type": "image/png",
|
|
189
|
+
"Cache-Control": "public, max-age=86400",
|
|
190
|
+
},
|
|
191
|
+
});
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
// --- Middleware for all remaining routes ---
|
|
195
|
+
|
|
196
|
+
// Onboarding gate — redirect to /setup if not yet initialized
|
|
197
|
+
app.use("*", requireOnboarding());
|
|
202
198
|
|
|
203
199
|
// Trailing slash redirect (redirect /foo/ to /foo)
|
|
204
200
|
app.use("*", async (c, next) => {
|
|
@@ -226,47 +222,11 @@ export function createApp(config: JantConfig = {}): App {
|
|
|
226
222
|
await next();
|
|
227
223
|
});
|
|
228
224
|
|
|
229
|
-
//
|
|
230
|
-
app.
|
|
231
|
-
|
|
232
|
-
status: "ok",
|
|
233
|
-
auth: c.env.AUTH_SECRET ? "configured" : "missing",
|
|
234
|
-
authSecretLength: c.env.AUTH_SECRET?.length ?? 0,
|
|
235
|
-
}),
|
|
236
|
-
);
|
|
237
|
-
|
|
238
|
-
// Favicon routes - serve from DB settings (small files, avoids R2 round-trip)
|
|
239
|
-
app.get("/favicon.ico", async (c) => {
|
|
240
|
-
const data = await c.var.services.settings.get("SITE_FAVICON_ICO");
|
|
241
|
-
if (!data) return c.notFound();
|
|
242
|
-
|
|
243
|
-
return new Response(base64ToUint8Array(data), {
|
|
244
|
-
headers: {
|
|
245
|
-
"Content-Type": "image/x-icon",
|
|
246
|
-
"Cache-Control": "public, max-age=86400",
|
|
247
|
-
},
|
|
248
|
-
});
|
|
249
|
-
});
|
|
250
|
-
|
|
251
|
-
app.get("/apple-touch-icon.png", async (c) => {
|
|
252
|
-
const data = await c.var.services.settings.get("SITE_FAVICON_APPLE_TOUCH");
|
|
253
|
-
if (!data) return c.notFound();
|
|
254
|
-
|
|
255
|
-
return new Response(base64ToUint8Array(data), {
|
|
256
|
-
headers: {
|
|
257
|
-
"Content-Type": "image/png",
|
|
258
|
-
"Cache-Control": "public, max-age=86400",
|
|
259
|
-
},
|
|
260
|
-
});
|
|
261
|
-
});
|
|
225
|
+
// Config + i18n — loads settings, resolves config/theme
|
|
226
|
+
app.use("*", withConfig());
|
|
227
|
+
app.use("*", i18nMiddleware());
|
|
262
228
|
|
|
263
|
-
//
|
|
264
|
-
app.all("/api/auth/*", async (c) => {
|
|
265
|
-
if (!c.var.auth) {
|
|
266
|
-
return c.json({ error: "Auth not configured. Set AUTH_SECRET." }, 500);
|
|
267
|
-
}
|
|
268
|
-
return c.var.auth.handler(c.req.raw);
|
|
269
|
-
});
|
|
229
|
+
// --- Routes that need config ---
|
|
270
230
|
|
|
271
231
|
// API Routes
|
|
272
232
|
app.route("/api/posts", postsApiRoutes);
|
|
@@ -287,36 +247,13 @@ export function createApp(config: JantConfig = {}): App {
|
|
|
287
247
|
app.route("/dash/pages", dashPagesRoutes);
|
|
288
248
|
app.route("/dash/media", dashMediaRoutes);
|
|
289
249
|
app.route("/dash/settings", dashSettingsRoutes);
|
|
290
|
-
app.route("/dash/
|
|
250
|
+
app.route("/dash/appearance", dashAppearanceRoutes);
|
|
251
|
+
app.route("/dash/settings/redirects", dashRedirectsRoutes);
|
|
291
252
|
app.route("/dash/collections", dashCollectionsRoutes);
|
|
292
253
|
// API routes
|
|
293
254
|
app.route("/api/upload", uploadApiRoutes);
|
|
294
255
|
app.route("/api/search", searchApiRoutes);
|
|
295
256
|
|
|
296
|
-
// Media files from storage (path matches storage key: media/YYYY/MM/uuid.ext)
|
|
297
|
-
app.get("/media/*", async (c) => {
|
|
298
|
-
const storage = c.var.storage;
|
|
299
|
-
if (!storage) {
|
|
300
|
-
return c.notFound();
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
// The storage key is the full path without the leading "/"
|
|
304
|
-
const storageKey = c.req.path.slice(1);
|
|
305
|
-
const object = await storage.get(storageKey);
|
|
306
|
-
if (!object) {
|
|
307
|
-
return c.notFound();
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
const headers = new Headers();
|
|
311
|
-
headers.set(
|
|
312
|
-
"Content-Type",
|
|
313
|
-
object.contentType || "application/octet-stream",
|
|
314
|
-
);
|
|
315
|
-
headers.set("Cache-Control", "public, max-age=31536000, immutable");
|
|
316
|
-
|
|
317
|
-
return new Response(object.body, { headers });
|
|
318
|
-
});
|
|
319
|
-
|
|
320
257
|
// Compose route (auth enforced in route middleware)
|
|
321
258
|
app.route("/compose", composeRoutes);
|
|
322
259
|
|
package/src/client.ts
CHANGED
|
@@ -13,3 +13,16 @@ import "./lib/image-processor.js";
|
|
|
13
13
|
import "./lib/media-upload.js";
|
|
14
14
|
import "./lib/avatar-upload.js";
|
|
15
15
|
import "./lib/nav-reorder.js";
|
|
16
|
+
import "./lib/collections-reorder.js";
|
|
17
|
+
|
|
18
|
+
// Lit Web Components
|
|
19
|
+
import "./ui/components/jant-compose-dialog.js";
|
|
20
|
+
import "./ui/components/jant-compose-editor.js";
|
|
21
|
+
import "./lib/compose-bridge.js";
|
|
22
|
+
import "./ui/components/jant-settings-general.js";
|
|
23
|
+
import "./ui/components/jant-settings-avatar.js";
|
|
24
|
+
import "./lib/settings-bridge.js";
|
|
25
|
+
import "./ui/components/jant-collection-form.js";
|
|
26
|
+
import "./lib/collection-form-bridge.js";
|
|
27
|
+
import "./ui/components/jant-post-form.js";
|
|
28
|
+
import "./lib/post-form-bridge.js";
|