@jant/core 0.3.26 → 0.3.28
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/client/client.css +1 -0
- package/dist/client/client.js +31561 -0
- package/dist/index.js +15209 -15
- package/package.json +21 -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 +112 -173
- package/src/auth.ts +4 -1
- 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 -265
- package/dist/auth.js +0 -36
- 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
|
@@ -4,10 +4,9 @@
|
|
|
4
4
|
|
|
5
5
|
import { Hono } from "hono";
|
|
6
6
|
import type { Bindings } from "../../types.js";
|
|
7
|
-
import type { AppVariables } from "../../app.js";
|
|
7
|
+
import type { AppVariables } from "../../types/app-context.js";
|
|
8
8
|
import { DashLayout } from "../../ui/layouts/DashLayout.js";
|
|
9
9
|
import { dsRedirect } from "../../lib/sse.js";
|
|
10
|
-
import { getSiteName } from "../../lib/config.js";
|
|
11
10
|
import {
|
|
12
11
|
getMediaUrl,
|
|
13
12
|
getImageUrl,
|
|
@@ -22,8 +21,8 @@ export const mediaRoutes = new Hono<Env>();
|
|
|
22
21
|
|
|
23
22
|
// List media
|
|
24
23
|
mediaRoutes.get("/", async (c) => {
|
|
25
|
-
const mediaList = await c.var.services.media.list(100);
|
|
26
|
-
const siteName =
|
|
24
|
+
const mediaList = await c.var.services.media.list({ limit: 100 });
|
|
25
|
+
const siteName = c.var.appConfig.siteName;
|
|
27
26
|
|
|
28
27
|
return c.html(
|
|
29
28
|
<DashLayout
|
|
@@ -34,9 +33,9 @@ mediaRoutes.get("/", async (c) => {
|
|
|
34
33
|
>
|
|
35
34
|
<MediaListContent
|
|
36
35
|
mediaList={mediaList}
|
|
37
|
-
r2PublicUrl={c.
|
|
38
|
-
imageTransformUrl={c.
|
|
39
|
-
s3PublicUrl={c.
|
|
36
|
+
r2PublicUrl={c.var.appConfig.r2PublicUrl}
|
|
37
|
+
imageTransformUrl={c.var.appConfig.imageTransformUrl}
|
|
38
|
+
s3PublicUrl={c.var.appConfig.s3PublicUrl}
|
|
40
39
|
/>
|
|
41
40
|
</DashLayout>,
|
|
42
41
|
);
|
|
@@ -45,10 +44,13 @@ mediaRoutes.get("/", async (c) => {
|
|
|
45
44
|
// Media picker (returns HTML fragment for PostForm dialog)
|
|
46
45
|
// Must be defined before /:id to avoid "picker" matching as an ID
|
|
47
46
|
mediaRoutes.get("/picker", async (c) => {
|
|
48
|
-
const mediaList = await c.var.services.media.list(
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
47
|
+
const mediaList = await c.var.services.media.list({
|
|
48
|
+
limit: 100,
|
|
49
|
+
mimePrefix: "image/",
|
|
50
|
+
});
|
|
51
|
+
const r2PublicUrl = c.var.appConfig.r2PublicUrl;
|
|
52
|
+
const imageTransformUrl = c.var.appConfig.imageTransformUrl;
|
|
53
|
+
const s3PublicUrl = c.var.appConfig.s3PublicUrl;
|
|
52
54
|
|
|
53
55
|
if (mediaList.length === 0) {
|
|
54
56
|
return c.html(
|
|
@@ -60,40 +62,35 @@ mediaRoutes.get("/picker", async (c) => {
|
|
|
60
62
|
|
|
61
63
|
return c.html(
|
|
62
64
|
<>
|
|
63
|
-
{mediaList
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
loading="lazy"
|
|
93
|
-
/>
|
|
94
|
-
</button>
|
|
95
|
-
);
|
|
96
|
-
})}
|
|
65
|
+
{mediaList.map((m) => {
|
|
66
|
+
const pUrl = getPublicUrlForProvider(
|
|
67
|
+
m.provider,
|
|
68
|
+
r2PublicUrl,
|
|
69
|
+
s3PublicUrl,
|
|
70
|
+
);
|
|
71
|
+
const url = getMediaUrl(m.storageKey, pUrl);
|
|
72
|
+
const thumbUrl = getImageUrl(url, imageTransformUrl, {
|
|
73
|
+
width: 150,
|
|
74
|
+
quality: 80,
|
|
75
|
+
format: "auto",
|
|
76
|
+
fit: "cover",
|
|
77
|
+
});
|
|
78
|
+
return (
|
|
79
|
+
<button
|
|
80
|
+
key={m.id}
|
|
81
|
+
type="button"
|
|
82
|
+
class="aspect-square rounded-lg overflow-hidden border-2 hover:border-primary cursor-pointer transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary"
|
|
83
|
+
data-media-id={m.id}
|
|
84
|
+
>
|
|
85
|
+
<img
|
|
86
|
+
src={thumbUrl}
|
|
87
|
+
alt={m.alt || m.originalName}
|
|
88
|
+
class="w-full h-full object-cover"
|
|
89
|
+
loading="lazy"
|
|
90
|
+
/>
|
|
91
|
+
</button>
|
|
92
|
+
);
|
|
93
|
+
})}
|
|
97
94
|
</>,
|
|
98
95
|
);
|
|
99
96
|
});
|
|
@@ -104,7 +101,7 @@ mediaRoutes.get("/:id", async (c) => {
|
|
|
104
101
|
const media = await c.var.services.media.getById(id);
|
|
105
102
|
if (!media) return c.notFound();
|
|
106
103
|
|
|
107
|
-
const siteName =
|
|
104
|
+
const siteName = c.var.appConfig.siteName;
|
|
108
105
|
|
|
109
106
|
return c.html(
|
|
110
107
|
<DashLayout
|
|
@@ -115,9 +112,9 @@ mediaRoutes.get("/:id", async (c) => {
|
|
|
115
112
|
>
|
|
116
113
|
<ViewMediaContent
|
|
117
114
|
media={media}
|
|
118
|
-
r2PublicUrl={c.
|
|
119
|
-
imageTransformUrl={c.
|
|
120
|
-
s3PublicUrl={c.
|
|
115
|
+
r2PublicUrl={c.var.appConfig.r2PublicUrl}
|
|
116
|
+
imageTransformUrl={c.var.appConfig.imageTransformUrl}
|
|
117
|
+
s3PublicUrl={c.var.appConfig.s3PublicUrl}
|
|
121
118
|
/>
|
|
122
119
|
</DashLayout>,
|
|
123
120
|
);
|
|
@@ -5,15 +5,17 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import { Hono } from "hono";
|
|
8
|
+
import { msg } from "@lingui/core/macro";
|
|
8
9
|
import { useLingui } from "@lingui/react/macro";
|
|
9
10
|
import type { Bindings, Page } from "../../types.js";
|
|
10
|
-
import type { AppVariables } from "../../app.js";
|
|
11
|
+
import type { AppVariables } from "../../types/app-context.js";
|
|
11
12
|
import { DashLayout } from "../../ui/layouts/DashLayout.js";
|
|
12
13
|
import { PageForm, ActionButtons, DangerZone } from "../../ui/dash/index.js";
|
|
13
14
|
import { dsRedirect, dsToast } from "../../lib/sse.js";
|
|
14
|
-
import {
|
|
15
|
+
import { CreatePageSchema } from "../../lib/schemas.js";
|
|
15
16
|
import { UnifiedPagesContent } from "../../ui/dash/pages/UnifiedPagesContent.js";
|
|
16
17
|
import { LinkFormContent } from "../../ui/dash/pages/LinkFormContent.js";
|
|
18
|
+
import { getI18n } from "../../i18n/index.js";
|
|
17
19
|
|
|
18
20
|
type Env = { Bindings: Bindings; Variables: AppVariables };
|
|
19
21
|
|
|
@@ -109,7 +111,7 @@ pagesRoutes.get("/", async (c) => {
|
|
|
109
111
|
c.var.services.navItems.list(),
|
|
110
112
|
c.var.services.pages.listNotInNav(),
|
|
111
113
|
]);
|
|
112
|
-
const siteName =
|
|
114
|
+
const siteName = c.var.appConfig.siteName;
|
|
113
115
|
|
|
114
116
|
return c.html(
|
|
115
117
|
<DashLayout
|
|
@@ -124,7 +126,7 @@ pagesRoutes.get("/", async (c) => {
|
|
|
124
126
|
});
|
|
125
127
|
|
|
126
128
|
pagesRoutes.get("/new", async (c) => {
|
|
127
|
-
const siteName =
|
|
129
|
+
const siteName = c.var.appConfig.siteName;
|
|
128
130
|
return c.html(
|
|
129
131
|
<DashLayout
|
|
130
132
|
c={c}
|
|
@@ -138,7 +140,7 @@ pagesRoutes.get("/new", async (c) => {
|
|
|
138
140
|
});
|
|
139
141
|
|
|
140
142
|
pagesRoutes.get("/links/new", async (c) => {
|
|
141
|
-
const siteName =
|
|
143
|
+
const siteName = c.var.appConfig.siteName;
|
|
142
144
|
return c.html(
|
|
143
145
|
<DashLayout
|
|
144
146
|
c={c}
|
|
@@ -152,9 +154,18 @@ pagesRoutes.get("/links/new", async (c) => {
|
|
|
152
154
|
});
|
|
153
155
|
|
|
154
156
|
pagesRoutes.post("/links", async (c) => {
|
|
157
|
+
const i18n = getI18n(c);
|
|
155
158
|
const body = await c.req.json<{ label: string; url: string }>();
|
|
156
159
|
if (!body.label || !body.url) {
|
|
157
|
-
return dsToast(
|
|
160
|
+
return dsToast(
|
|
161
|
+
i18n._(
|
|
162
|
+
msg({
|
|
163
|
+
message: "Label and URL are required",
|
|
164
|
+
comment: "@context: Error toast when nav link fields are empty",
|
|
165
|
+
}),
|
|
166
|
+
),
|
|
167
|
+
"error",
|
|
168
|
+
);
|
|
158
169
|
}
|
|
159
170
|
|
|
160
171
|
await c.var.services.navItems.create({
|
|
@@ -166,12 +177,28 @@ pagesRoutes.post("/links", async (c) => {
|
|
|
166
177
|
});
|
|
167
178
|
|
|
168
179
|
pagesRoutes.post("/reorder", async (c) => {
|
|
180
|
+
const i18n = getI18n(c);
|
|
169
181
|
const body = await c.req.json<{ ids: number[] }>();
|
|
170
182
|
if (!Array.isArray(body.ids)) {
|
|
171
|
-
return dsToast(
|
|
183
|
+
return dsToast(
|
|
184
|
+
i18n._(
|
|
185
|
+
msg({
|
|
186
|
+
message: "Invalid request",
|
|
187
|
+
comment: "@context: Error toast when reorder request is malformed",
|
|
188
|
+
}),
|
|
189
|
+
),
|
|
190
|
+
"error",
|
|
191
|
+
);
|
|
172
192
|
}
|
|
173
193
|
await c.var.services.navItems.reorder(body.ids);
|
|
174
|
-
return dsToast(
|
|
194
|
+
return dsToast(
|
|
195
|
+
i18n._(
|
|
196
|
+
msg({
|
|
197
|
+
message: "Order saved",
|
|
198
|
+
comment: "@context: Toast after saving navigation item order",
|
|
199
|
+
}),
|
|
200
|
+
),
|
|
201
|
+
);
|
|
175
202
|
});
|
|
176
203
|
|
|
177
204
|
pagesRoutes.get("/links/:id/edit", async (c) => {
|
|
@@ -181,7 +208,7 @@ pagesRoutes.get("/links/:id/edit", async (c) => {
|
|
|
181
208
|
const item = await c.var.services.navItems.getById(id);
|
|
182
209
|
if (!item) return c.notFound();
|
|
183
210
|
|
|
184
|
-
const siteName =
|
|
211
|
+
const siteName = c.var.appConfig.siteName;
|
|
185
212
|
return c.html(
|
|
186
213
|
<DashLayout
|
|
187
214
|
c={c}
|
|
@@ -195,12 +222,21 @@ pagesRoutes.get("/links/:id/edit", async (c) => {
|
|
|
195
222
|
});
|
|
196
223
|
|
|
197
224
|
pagesRoutes.post("/links/:id", async (c) => {
|
|
225
|
+
const i18n = getI18n(c);
|
|
198
226
|
const id = parseInt(c.req.param("id"), 10);
|
|
199
227
|
if (isNaN(id)) return c.notFound();
|
|
200
228
|
|
|
201
229
|
const body = await c.req.json<{ label: string; url: string }>();
|
|
202
230
|
if (!body.label || !body.url) {
|
|
203
|
-
return dsToast(
|
|
231
|
+
return dsToast(
|
|
232
|
+
i18n._(
|
|
233
|
+
msg({
|
|
234
|
+
message: "Label and URL are required",
|
|
235
|
+
comment: "@context: Error toast when nav link fields are empty",
|
|
236
|
+
}),
|
|
237
|
+
),
|
|
238
|
+
"error",
|
|
239
|
+
);
|
|
204
240
|
}
|
|
205
241
|
|
|
206
242
|
const updated = await c.var.services.navItems.update(id, {
|
|
@@ -221,18 +257,26 @@ pagesRoutes.post("/links/:id/delete", async (c) => {
|
|
|
221
257
|
});
|
|
222
258
|
|
|
223
259
|
pagesRoutes.post("/", async (c) => {
|
|
224
|
-
const
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
260
|
+
const i18n = getI18n(c);
|
|
261
|
+
const raw = await c.req.json();
|
|
262
|
+
const parsed = CreatePageSchema.safeParse(raw);
|
|
263
|
+
if (!parsed.success) {
|
|
264
|
+
const errorMsg =
|
|
265
|
+
parsed.error.issues[0]?.message ??
|
|
266
|
+
i18n._(
|
|
267
|
+
msg({
|
|
268
|
+
message: "Invalid input",
|
|
269
|
+
comment: "@context: Fallback validation error for page form",
|
|
270
|
+
}),
|
|
271
|
+
);
|
|
272
|
+
return dsToast(errorMsg, "error");
|
|
273
|
+
}
|
|
230
274
|
|
|
231
275
|
const page = await c.var.services.pages.create({
|
|
232
|
-
title:
|
|
233
|
-
body:
|
|
234
|
-
status:
|
|
235
|
-
slug:
|
|
276
|
+
title: parsed.data.title,
|
|
277
|
+
body: parsed.data.body,
|
|
278
|
+
status: parsed.data.status,
|
|
279
|
+
slug: parsed.data.slug,
|
|
236
280
|
});
|
|
237
281
|
|
|
238
282
|
return dsRedirect(`/dash/pages/${page.id}`);
|
|
@@ -258,11 +302,7 @@ pagesRoutes.post("/:id/remove-from-nav", async (c) => {
|
|
|
258
302
|
const pageId = parseInt(c.req.param("id"), 10);
|
|
259
303
|
if (isNaN(pageId)) return c.notFound();
|
|
260
304
|
|
|
261
|
-
|
|
262
|
-
const navItem = navItems.find((item) => item.pageId === pageId);
|
|
263
|
-
if (navItem) {
|
|
264
|
-
await c.var.services.navItems.delete(navItem.id);
|
|
265
|
-
}
|
|
305
|
+
await c.var.services.navItems.deleteByPageId(pageId);
|
|
266
306
|
return dsRedirect("/dash/pages");
|
|
267
307
|
});
|
|
268
308
|
|
|
@@ -273,7 +313,7 @@ pagesRoutes.get("/:id", async (c) => {
|
|
|
273
313
|
const page = await c.var.services.pages.getById(id);
|
|
274
314
|
if (!page) return c.notFound();
|
|
275
315
|
|
|
276
|
-
const siteName =
|
|
316
|
+
const siteName = c.var.appConfig.siteName;
|
|
277
317
|
return c.html(
|
|
278
318
|
<DashLayout
|
|
279
319
|
c={c}
|
|
@@ -293,7 +333,7 @@ pagesRoutes.get("/:id/edit", async (c) => {
|
|
|
293
333
|
const page = await c.var.services.pages.getById(id);
|
|
294
334
|
if (!page) return c.notFound();
|
|
295
335
|
|
|
296
|
-
const siteName =
|
|
336
|
+
const siteName = c.var.appConfig.siteName;
|
|
297
337
|
return c.html(
|
|
298
338
|
<DashLayout
|
|
299
339
|
c={c}
|
|
@@ -307,21 +347,29 @@ pagesRoutes.get("/:id/edit", async (c) => {
|
|
|
307
347
|
});
|
|
308
348
|
|
|
309
349
|
pagesRoutes.post("/:id", async (c) => {
|
|
350
|
+
const i18n = getI18n(c);
|
|
310
351
|
const id = parseInt(c.req.param("id"), 10);
|
|
311
352
|
if (isNaN(id)) return c.notFound();
|
|
312
353
|
|
|
313
|
-
const
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
354
|
+
const raw = await c.req.json();
|
|
355
|
+
const parsed = CreatePageSchema.safeParse(raw);
|
|
356
|
+
if (!parsed.success) {
|
|
357
|
+
const errorMsg =
|
|
358
|
+
parsed.error.issues[0]?.message ??
|
|
359
|
+
i18n._(
|
|
360
|
+
msg({
|
|
361
|
+
message: "Invalid input",
|
|
362
|
+
comment: "@context: Fallback validation error for page form",
|
|
363
|
+
}),
|
|
364
|
+
);
|
|
365
|
+
return dsToast(errorMsg, "error");
|
|
366
|
+
}
|
|
319
367
|
|
|
320
368
|
await c.var.services.pages.update(id, {
|
|
321
|
-
title:
|
|
322
|
-
body:
|
|
323
|
-
status:
|
|
324
|
-
slug:
|
|
369
|
+
title: parsed.data.title,
|
|
370
|
+
body: parsed.data.body,
|
|
371
|
+
status: parsed.data.status,
|
|
372
|
+
slug: parsed.data.slug,
|
|
325
373
|
});
|
|
326
374
|
|
|
327
375
|
return dsRedirect(`/dash/pages/${id}`);
|
|
@@ -1,12 +1,17 @@
|
|
|
1
|
-
import { getSiteName } from "../../lib/config.js";
|
|
2
1
|
/**
|
|
3
2
|
* Dashboard Posts Routes
|
|
4
3
|
*/
|
|
5
4
|
|
|
6
5
|
import { Hono } from "hono";
|
|
7
6
|
import { useLingui } from "@lingui/react/macro";
|
|
8
|
-
import type {
|
|
9
|
-
|
|
7
|
+
import type {
|
|
8
|
+
Bindings,
|
|
9
|
+
Post,
|
|
10
|
+
PostView,
|
|
11
|
+
Media,
|
|
12
|
+
Collection,
|
|
13
|
+
} from "../../types.js";
|
|
14
|
+
import type { AppVariables } from "../../types/app-context.js";
|
|
10
15
|
import { DashLayout } from "../../ui/layouts/DashLayout.js";
|
|
11
16
|
import {
|
|
12
17
|
PostForm,
|
|
@@ -16,12 +21,17 @@ import {
|
|
|
16
21
|
} from "../../ui/dash/index.js";
|
|
17
22
|
import * as sqid from "../../lib/sqid.js";
|
|
18
23
|
import { dsRedirect } from "../../lib/sse.js";
|
|
24
|
+
import {
|
|
25
|
+
toPostViewsFromPosts,
|
|
26
|
+
toPostViewFromPost,
|
|
27
|
+
createMediaContext,
|
|
28
|
+
} from "../../lib/view.js";
|
|
19
29
|
|
|
20
30
|
type Env = { Bindings: Bindings; Variables: AppVariables };
|
|
21
31
|
|
|
22
32
|
export const postsRoutes = new Hono<Env>();
|
|
23
33
|
|
|
24
|
-
function PostsListContent({ posts }: { posts:
|
|
34
|
+
function PostsListContent({ posts }: { posts: PostView[] }) {
|
|
25
35
|
const { t } = useLingui();
|
|
26
36
|
return (
|
|
27
37
|
<>
|
|
@@ -55,7 +65,11 @@ postsRoutes.get("/", async (c) => {
|
|
|
55
65
|
const posts = await c.var.services.posts.list({
|
|
56
66
|
excludeReplies: true,
|
|
57
67
|
});
|
|
58
|
-
const siteName =
|
|
68
|
+
const siteName = c.var.appConfig.siteName;
|
|
69
|
+
const postViews = toPostViewsFromPosts(
|
|
70
|
+
posts,
|
|
71
|
+
createMediaContext(c.var.appConfig),
|
|
72
|
+
);
|
|
59
73
|
|
|
60
74
|
return c.html(
|
|
61
75
|
<DashLayout
|
|
@@ -64,14 +78,14 @@ postsRoutes.get("/", async (c) => {
|
|
|
64
78
|
siteName={siteName}
|
|
65
79
|
currentPath="/dash/posts"
|
|
66
80
|
>
|
|
67
|
-
<PostsListContent posts={
|
|
81
|
+
<PostsListContent posts={postViews} />
|
|
68
82
|
</DashLayout>,
|
|
69
83
|
);
|
|
70
84
|
});
|
|
71
85
|
|
|
72
86
|
// New post form
|
|
73
87
|
postsRoutes.get("/new", async (c) => {
|
|
74
|
-
const siteName =
|
|
88
|
+
const siteName = c.var.appConfig.siteName;
|
|
75
89
|
const collections = await c.var.services.collections.list();
|
|
76
90
|
|
|
77
91
|
return c.html(
|
|
@@ -88,6 +102,7 @@ postsRoutes.get("/new", async (c) => {
|
|
|
88
102
|
|
|
89
103
|
// Create post
|
|
90
104
|
postsRoutes.post("/", async (c) => {
|
|
105
|
+
const wantsJson = c.req.header("Accept")?.includes("application/json");
|
|
91
106
|
const body = await c.req.json<{
|
|
92
107
|
format: string;
|
|
93
108
|
title?: string;
|
|
@@ -98,7 +113,7 @@ postsRoutes.post("/", async (c) => {
|
|
|
98
113
|
url?: string;
|
|
99
114
|
quoteText?: string;
|
|
100
115
|
rating?: number;
|
|
101
|
-
|
|
116
|
+
collectionIds?: number[];
|
|
102
117
|
mediaIds?: string[];
|
|
103
118
|
}>();
|
|
104
119
|
|
|
@@ -112,7 +127,7 @@ postsRoutes.post("/", async (c) => {
|
|
|
112
127
|
url: body.url || undefined,
|
|
113
128
|
quoteText: body.quoteText || undefined,
|
|
114
129
|
rating: body.rating || undefined,
|
|
115
|
-
|
|
130
|
+
collectionIds: body.collectionIds?.length ? body.collectionIds : undefined,
|
|
116
131
|
});
|
|
117
132
|
|
|
118
133
|
// Attach media if provided
|
|
@@ -120,16 +135,20 @@ postsRoutes.post("/", async (c) => {
|
|
|
120
135
|
await c.var.services.media.attachToPost(post.id, body.mediaIds);
|
|
121
136
|
}
|
|
122
137
|
|
|
123
|
-
|
|
138
|
+
const redirectUrl = `/dash/posts/${sqid.encode(post.id)}`;
|
|
139
|
+
if (wantsJson) {
|
|
140
|
+
return c.json({ status: "redirect" as const, url: redirectUrl });
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
return dsRedirect(redirectUrl);
|
|
124
144
|
});
|
|
125
145
|
|
|
126
|
-
function ViewPostContent({ post }: { post:
|
|
146
|
+
function ViewPostContent({ post }: { post: PostView }) {
|
|
127
147
|
const { t } = useLingui();
|
|
128
148
|
const defaultTitle = t({
|
|
129
149
|
message: "Post",
|
|
130
150
|
comment: "@context: Default post title",
|
|
131
151
|
});
|
|
132
|
-
const permalink = post.path ? `/${post.path}` : `/p/${sqid.encode(post.id)}`;
|
|
133
152
|
|
|
134
153
|
return (
|
|
135
154
|
<>
|
|
@@ -141,7 +160,7 @@ function ViewPostContent({ post }: { post: Post }) {
|
|
|
141
160
|
message: "Edit",
|
|
142
161
|
comment: "@context: Button to edit post",
|
|
143
162
|
})}
|
|
144
|
-
viewHref={permalink}
|
|
163
|
+
viewHref={post.permalink}
|
|
145
164
|
viewLabel={t({
|
|
146
165
|
message: "View",
|
|
147
166
|
comment: "@context: Button to view post",
|
|
@@ -168,6 +187,7 @@ function EditPostContent({
|
|
|
168
187
|
imageTransformUrl,
|
|
169
188
|
s3PublicUrl,
|
|
170
189
|
collections,
|
|
190
|
+
postCollectionIds,
|
|
171
191
|
}: {
|
|
172
192
|
post: Post;
|
|
173
193
|
mediaAttachments: Media[];
|
|
@@ -175,6 +195,7 @@ function EditPostContent({
|
|
|
175
195
|
imageTransformUrl?: string;
|
|
176
196
|
s3PublicUrl?: string;
|
|
177
197
|
collections: Collection[];
|
|
198
|
+
postCollectionIds: number[];
|
|
178
199
|
}) {
|
|
179
200
|
const { t } = useLingui();
|
|
180
201
|
return (
|
|
@@ -190,6 +211,8 @@ function EditPostContent({
|
|
|
190
211
|
imageTransformUrl={imageTransformUrl}
|
|
191
212
|
s3PublicUrl={s3PublicUrl}
|
|
192
213
|
collections={collections}
|
|
214
|
+
postCollectionIds={postCollectionIds}
|
|
215
|
+
cancelHref={`/dash/posts/${sqid.encode(post.id)}`}
|
|
193
216
|
/>
|
|
194
217
|
</>
|
|
195
218
|
);
|
|
@@ -203,8 +226,12 @@ postsRoutes.get("/:id", async (c) => {
|
|
|
203
226
|
const post = await c.var.services.posts.getById(id);
|
|
204
227
|
if (!post) return c.notFound();
|
|
205
228
|
|
|
206
|
-
const siteName =
|
|
229
|
+
const siteName = c.var.appConfig.siteName;
|
|
207
230
|
const pageTitle = post.title || "Post";
|
|
231
|
+
const postView = toPostViewFromPost(
|
|
232
|
+
post,
|
|
233
|
+
createMediaContext(c.var.appConfig),
|
|
234
|
+
);
|
|
208
235
|
|
|
209
236
|
return c.html(
|
|
210
237
|
<DashLayout
|
|
@@ -213,7 +240,7 @@ postsRoutes.get("/:id", async (c) => {
|
|
|
213
240
|
siteName={siteName}
|
|
214
241
|
currentPath="/dash/posts"
|
|
215
242
|
>
|
|
216
|
-
<ViewPostContent post={
|
|
243
|
+
<ViewPostContent post={postView} />
|
|
217
244
|
</DashLayout>,
|
|
218
245
|
);
|
|
219
246
|
});
|
|
@@ -226,12 +253,14 @@ postsRoutes.get("/:id/edit", async (c) => {
|
|
|
226
253
|
const post = await c.var.services.posts.getById(id);
|
|
227
254
|
if (!post) return c.notFound();
|
|
228
255
|
|
|
229
|
-
const siteName =
|
|
256
|
+
const siteName = c.var.appConfig.siteName;
|
|
230
257
|
const mediaAttachments = await c.var.services.media.getByPostId(post.id);
|
|
231
|
-
const r2PublicUrl = c.
|
|
232
|
-
const
|
|
233
|
-
|
|
234
|
-
|
|
258
|
+
const { r2PublicUrl, imageTransformUrl, s3PublicUrl } = c.var.appConfig;
|
|
259
|
+
const [collections, postCollections] = await Promise.all([
|
|
260
|
+
c.var.services.collections.list(),
|
|
261
|
+
c.var.services.collections.getCollectionsByPostId(post.id),
|
|
262
|
+
]);
|
|
263
|
+
const postCollectionIds = postCollections.map((c) => c.id);
|
|
235
264
|
|
|
236
265
|
return c.html(
|
|
237
266
|
<DashLayout
|
|
@@ -247,6 +276,7 @@ postsRoutes.get("/:id/edit", async (c) => {
|
|
|
247
276
|
imageTransformUrl={imageTransformUrl}
|
|
248
277
|
s3PublicUrl={s3PublicUrl}
|
|
249
278
|
collections={collections}
|
|
279
|
+
postCollectionIds={postCollectionIds}
|
|
250
280
|
/>
|
|
251
281
|
</DashLayout>,
|
|
252
282
|
);
|
|
@@ -257,6 +287,8 @@ postsRoutes.post("/:id", async (c) => {
|
|
|
257
287
|
const id = sqid.decode(c.req.param("id"));
|
|
258
288
|
if (!id) return c.notFound();
|
|
259
289
|
|
|
290
|
+
const wantsJson = c.req.header("Accept")?.includes("application/json");
|
|
291
|
+
|
|
260
292
|
const body = await c.req.json<{
|
|
261
293
|
format: string;
|
|
262
294
|
title?: string;
|
|
@@ -267,7 +299,7 @@ postsRoutes.post("/:id", async (c) => {
|
|
|
267
299
|
url?: string;
|
|
268
300
|
quoteText?: string;
|
|
269
301
|
rating?: number;
|
|
270
|
-
|
|
302
|
+
collectionIds?: number[];
|
|
271
303
|
mediaIds?: string[];
|
|
272
304
|
}>();
|
|
273
305
|
|
|
@@ -281,7 +313,7 @@ postsRoutes.post("/:id", async (c) => {
|
|
|
281
313
|
url: body.url || null,
|
|
282
314
|
quoteText: body.quoteText || null,
|
|
283
315
|
rating: body.rating || null,
|
|
284
|
-
|
|
316
|
+
collectionIds: body.collectionIds ?? [],
|
|
285
317
|
});
|
|
286
318
|
|
|
287
319
|
// Update media attachments if provided
|
|
@@ -289,7 +321,12 @@ postsRoutes.post("/:id", async (c) => {
|
|
|
289
321
|
await c.var.services.media.attachToPost(id, body.mediaIds);
|
|
290
322
|
}
|
|
291
323
|
|
|
292
|
-
|
|
324
|
+
const redirectUrl = `/dash/posts/${sqid.encode(id)}`;
|
|
325
|
+
if (wantsJson) {
|
|
326
|
+
return c.json({ status: "redirect" as const, url: redirectUrl });
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
return dsRedirect(redirectUrl);
|
|
293
330
|
});
|
|
294
331
|
|
|
295
332
|
// Delete post
|