@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
|
@@ -1,379 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx } from "hono/jsx/jsx-runtime";
|
|
2
|
-
/**
|
|
3
|
-
* Dashboard Settings Routes
|
|
4
|
-
*
|
|
5
|
-
* Sub-pages: General, Appearance, Account
|
|
6
|
-
*/ import { Hono } from "hono";
|
|
7
|
-
import { DashLayout } from "../../ui/layouts/DashLayout.js";
|
|
8
|
-
import { sse, dsRedirect, dsToast } from "../../lib/sse.js";
|
|
9
|
-
import { arrayBufferToBase64 } from "../../lib/favicon.js";
|
|
10
|
-
import { getSiteLanguage, getSiteName, getHomeDefaultView, getTimeZone, getSiteFooter, isNoIndex, getConfigFallback } from "../../lib/config.js";
|
|
11
|
-
import { SETTINGS_KEYS } from "../../lib/constants.js";
|
|
12
|
-
import { getAvailableThemes } from "../../lib/theme.js";
|
|
13
|
-
import { getMediaUrl, getPublicUrlForProvider } from "../../lib/image.js";
|
|
14
|
-
import { TIMEZONES } from "../../lib/timezones.js";
|
|
15
|
-
import { BUILTIN_FONT_THEMES } from "../../ui/font-themes.js";
|
|
16
|
-
import { GeneralContent } from "../../ui/dash/settings/GeneralContent.js";
|
|
17
|
-
import { AppearanceContent } from "../../ui/dash/settings/AppearanceContent.js";
|
|
18
|
-
import { AccountContent } from "../../ui/dash/settings/AccountContent.js";
|
|
19
|
-
/** Escape HTML special characters for safe insertion into HTML strings */ function escapeHtml(str) {
|
|
20
|
-
return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
21
|
-
}
|
|
22
|
-
export const settingsRoutes = new Hono();
|
|
23
|
-
// ===========================================================================
|
|
24
|
-
// General settings
|
|
25
|
-
// ===========================================================================
|
|
26
|
-
/** Resolve the avatar storage key to a URL */ async function resolveAvatarUrl(c) {
|
|
27
|
-
const avatarKey = await c.var.services.settings.get("SITE_AVATAR");
|
|
28
|
-
if (!avatarKey) return "";
|
|
29
|
-
const publicUrl = getPublicUrlForProvider(c.env.STORAGE_DRIVER || "r2", c.env.R2_PUBLIC_URL, c.env.S3_PUBLIC_URL);
|
|
30
|
-
return getMediaUrl(avatarKey, publicUrl);
|
|
31
|
-
}
|
|
32
|
-
settingsRoutes.get("/", async (c)=>{
|
|
33
|
-
const { settings } = c.var.services;
|
|
34
|
-
const dbSiteName = await settings.get("SITE_NAME");
|
|
35
|
-
const dbSiteDescription = await settings.get("SITE_DESCRIPTION");
|
|
36
|
-
const [siteLanguage, homeDefaultView, timeZone, siteFooter, noindex] = await Promise.all([
|
|
37
|
-
getSiteLanguage(c),
|
|
38
|
-
getHomeDefaultView(c),
|
|
39
|
-
getTimeZone(c),
|
|
40
|
-
getSiteFooter(c),
|
|
41
|
-
isNoIndex(c)
|
|
42
|
-
]);
|
|
43
|
-
const siteNameFallback = getConfigFallback(c, "SITE_NAME");
|
|
44
|
-
const siteDescriptionFallback = getConfigFallback(c, "SITE_DESCRIPTION");
|
|
45
|
-
const siteAvatarUrl = await resolveAvatarUrl(c);
|
|
46
|
-
const showHeaderAvatar = await settings.get("SHOW_HEADER_AVATAR") === "true";
|
|
47
|
-
const saved = c.req.query("saved") !== undefined;
|
|
48
|
-
return c.html(/*#__PURE__*/ _jsx(DashLayout, {
|
|
49
|
-
c: c,
|
|
50
|
-
title: "Settings",
|
|
51
|
-
siteName: dbSiteName || siteNameFallback,
|
|
52
|
-
currentPath: "/dash/settings",
|
|
53
|
-
toast: saved ? {
|
|
54
|
-
message: "Settings saved successfully."
|
|
55
|
-
} : undefined,
|
|
56
|
-
children: /*#__PURE__*/ _jsx(GeneralContent, {
|
|
57
|
-
siteName: dbSiteName || "",
|
|
58
|
-
siteDescription: dbSiteDescription || "",
|
|
59
|
-
siteLanguage: siteLanguage,
|
|
60
|
-
homeDefaultView: homeDefaultView,
|
|
61
|
-
siteNameFallback: siteNameFallback,
|
|
62
|
-
siteDescriptionFallback: siteDescriptionFallback,
|
|
63
|
-
siteAvatarUrl: siteAvatarUrl,
|
|
64
|
-
showHeaderAvatar: showHeaderAvatar,
|
|
65
|
-
timeZone: timeZone,
|
|
66
|
-
siteFooter: siteFooter,
|
|
67
|
-
noindex: noindex,
|
|
68
|
-
timezones: TIMEZONES
|
|
69
|
-
})
|
|
70
|
-
}));
|
|
71
|
-
});
|
|
72
|
-
settingsRoutes.post("/", async (c)=>{
|
|
73
|
-
const body = await c.req.json();
|
|
74
|
-
const { settings } = c.var.services;
|
|
75
|
-
const oldLanguage = await settings.get("SITE_LANGUAGE") ?? "en";
|
|
76
|
-
if (body.siteName.trim()) {
|
|
77
|
-
await settings.set("SITE_NAME", body.siteName.trim());
|
|
78
|
-
} else {
|
|
79
|
-
await settings.remove("SITE_NAME");
|
|
80
|
-
}
|
|
81
|
-
if (body.siteDescription.trim()) {
|
|
82
|
-
await settings.set("SITE_DESCRIPTION", body.siteDescription.trim());
|
|
83
|
-
} else {
|
|
84
|
-
await settings.remove("SITE_DESCRIPTION");
|
|
85
|
-
}
|
|
86
|
-
await settings.set("SITE_LANGUAGE", body.siteLanguage);
|
|
87
|
-
// Save homepage default view (only store if non-default)
|
|
88
|
-
if (body.homeDefaultView === "featured") {
|
|
89
|
-
await settings.set("HOME_DEFAULT_VIEW", body.homeDefaultView);
|
|
90
|
-
} else {
|
|
91
|
-
await settings.remove("HOME_DEFAULT_VIEW");
|
|
92
|
-
}
|
|
93
|
-
// Timezone
|
|
94
|
-
if (body.timeZone && body.timeZone !== "UTC") {
|
|
95
|
-
await settings.set("TIME_ZONE", body.timeZone);
|
|
96
|
-
} else {
|
|
97
|
-
await settings.remove("TIME_ZONE");
|
|
98
|
-
}
|
|
99
|
-
const languageChanged = oldLanguage !== body.siteLanguage;
|
|
100
|
-
const displayName = body.siteName.trim() || getConfigFallback(c, "SITE_NAME");
|
|
101
|
-
return sse(c, async (stream)=>{
|
|
102
|
-
if (languageChanged) {
|
|
103
|
-
await stream.redirect("/dash/settings?saved");
|
|
104
|
-
} else {
|
|
105
|
-
const escaped = escapeHtml(displayName);
|
|
106
|
-
await stream.patchElements(`<a id="site-name" href="/dash" class="font-semibold">${escaped}</a>`);
|
|
107
|
-
await stream.patchElements(`Settings - ${escaped}`, {
|
|
108
|
-
mode: "inner",
|
|
109
|
-
selector: "title"
|
|
110
|
-
});
|
|
111
|
-
await stream.toast("Settings saved successfully.");
|
|
112
|
-
await stream.patchSignals({
|
|
113
|
-
_orig_siteName: body.siteName,
|
|
114
|
-
_orig_siteDescription: body.siteDescription,
|
|
115
|
-
_orig_siteLanguage: body.siteLanguage,
|
|
116
|
-
_orig_homeDefaultView: body.homeDefaultView,
|
|
117
|
-
_orig_timeZone: body.timeZone,
|
|
118
|
-
_generalDirty: false
|
|
119
|
-
});
|
|
120
|
-
}
|
|
121
|
-
});
|
|
122
|
-
});
|
|
123
|
-
settingsRoutes.post("/footer", async (c)=>{
|
|
124
|
-
const body = await c.req.json();
|
|
125
|
-
const { settings } = c.var.services;
|
|
126
|
-
if (body.siteFooter?.trim()) {
|
|
127
|
-
await settings.set("SITE_FOOTER", body.siteFooter.trim());
|
|
128
|
-
} else {
|
|
129
|
-
await settings.remove("SITE_FOOTER");
|
|
130
|
-
}
|
|
131
|
-
return sse(c, async (stream)=>{
|
|
132
|
-
await stream.toast("Footer saved successfully.");
|
|
133
|
-
await stream.patchSignals({
|
|
134
|
-
_orig_siteFooter: body.siteFooter,
|
|
135
|
-
_footerDirty: false
|
|
136
|
-
});
|
|
137
|
-
});
|
|
138
|
-
});
|
|
139
|
-
settingsRoutes.post("/seo", async (c)=>{
|
|
140
|
-
const body = await c.req.json();
|
|
141
|
-
const { settings } = c.var.services;
|
|
142
|
-
// Checkbox "noindex" is the allow-indexing signal:
|
|
143
|
-
// checked (value "true") = indexing allowed -> remove NOINDEX
|
|
144
|
-
// unchecked (value "") = indexing blocked -> set NOINDEX=true
|
|
145
|
-
if (body.noindex === "true") {
|
|
146
|
-
await settings.remove("NOINDEX");
|
|
147
|
-
} else {
|
|
148
|
-
await settings.set("NOINDEX", "true");
|
|
149
|
-
}
|
|
150
|
-
return sse(c, async (stream)=>{
|
|
151
|
-
await stream.toast("SEO settings saved successfully.");
|
|
152
|
-
await stream.patchSignals({
|
|
153
|
-
_orig_noindex: body.noindex,
|
|
154
|
-
_seoDirty: false
|
|
155
|
-
});
|
|
156
|
-
});
|
|
157
|
-
});
|
|
158
|
-
// ===========================================================================
|
|
159
|
-
// Avatar upload & removal
|
|
160
|
-
// ===========================================================================
|
|
161
|
-
settingsRoutes.post("/avatar", async (c)=>{
|
|
162
|
-
const storage = c.var.storage;
|
|
163
|
-
if (!storage) {
|
|
164
|
-
return dsToast("Storage not configured.", "error");
|
|
165
|
-
}
|
|
166
|
-
const formData = await c.req.formData();
|
|
167
|
-
const file = formData.get("file");
|
|
168
|
-
if (!file) {
|
|
169
|
-
return dsToast("No file provided.", "error");
|
|
170
|
-
}
|
|
171
|
-
const allowedTypes = [
|
|
172
|
-
"image/jpeg",
|
|
173
|
-
"image/png",
|
|
174
|
-
"image/gif",
|
|
175
|
-
"image/webp",
|
|
176
|
-
"image/svg+xml"
|
|
177
|
-
];
|
|
178
|
-
if (!allowedTypes.includes(file.type)) {
|
|
179
|
-
return dsToast("File type not allowed.", "error");
|
|
180
|
-
}
|
|
181
|
-
const maxSize = 10 * 1024 * 1024;
|
|
182
|
-
if (file.size > maxSize) {
|
|
183
|
-
return dsToast("File too large (max 10MB).", "error");
|
|
184
|
-
}
|
|
185
|
-
const { uuidv7 } = await import("uuidv7");
|
|
186
|
-
const ext = file.name.split(".").pop() || "bin";
|
|
187
|
-
const id = uuidv7();
|
|
188
|
-
const date = new Date();
|
|
189
|
-
const year = date.getUTCFullYear();
|
|
190
|
-
const month = String(date.getUTCMonth() + 1).padStart(2, "0");
|
|
191
|
-
const filename = `${id}.${ext}`;
|
|
192
|
-
const storageKey = `media/${year}/${month}/${filename}`;
|
|
193
|
-
try {
|
|
194
|
-
await storage.put(storageKey, file.stream(), {
|
|
195
|
-
contentType: file.type
|
|
196
|
-
});
|
|
197
|
-
await c.var.services.media.create({
|
|
198
|
-
id,
|
|
199
|
-
filename,
|
|
200
|
-
originalName: file.name,
|
|
201
|
-
mimeType: file.type,
|
|
202
|
-
size: file.size,
|
|
203
|
-
storageKey,
|
|
204
|
-
provider: c.env.STORAGE_DRIVER || "r2"
|
|
205
|
-
});
|
|
206
|
-
await c.var.services.settings.set("SITE_AVATAR", storageKey);
|
|
207
|
-
// Store favicon variants as base64 in settings (small files, accessed every page load)
|
|
208
|
-
const faviconFile = formData.get("favicon");
|
|
209
|
-
const appleTouchFile = formData.get("appleTouch");
|
|
210
|
-
if (faviconFile) {
|
|
211
|
-
const b64 = arrayBufferToBase64(await faviconFile.arrayBuffer());
|
|
212
|
-
await c.var.services.settings.set("SITE_FAVICON_ICO", b64);
|
|
213
|
-
}
|
|
214
|
-
if (appleTouchFile) {
|
|
215
|
-
const b64 = arrayBufferToBase64(await appleTouchFile.arrayBuffer());
|
|
216
|
-
await c.var.services.settings.set("SITE_FAVICON_APPLE_TOUCH", b64);
|
|
217
|
-
}
|
|
218
|
-
return dsRedirect("/dash/settings?saved");
|
|
219
|
-
} catch {
|
|
220
|
-
return dsToast("Upload failed. Please try again.", "error");
|
|
221
|
-
}
|
|
222
|
-
});
|
|
223
|
-
settingsRoutes.post("/avatar/remove", async (c)=>{
|
|
224
|
-
await c.var.services.settings.remove("SITE_AVATAR");
|
|
225
|
-
await c.var.services.settings.remove("SITE_FAVICON_ICO");
|
|
226
|
-
await c.var.services.settings.remove("SITE_FAVICON_APPLE_TOUCH");
|
|
227
|
-
return dsRedirect("/dash/settings?saved");
|
|
228
|
-
});
|
|
229
|
-
settingsRoutes.post("/avatar/display", async (c)=>{
|
|
230
|
-
const body = await c.req.json();
|
|
231
|
-
const { settings } = c.var.services;
|
|
232
|
-
if (body.showHeaderAvatar === "true") {
|
|
233
|
-
await settings.set("SHOW_HEADER_AVATAR", "true");
|
|
234
|
-
} else {
|
|
235
|
-
await settings.remove("SHOW_HEADER_AVATAR");
|
|
236
|
-
}
|
|
237
|
-
return sse(c, async (stream)=>{
|
|
238
|
-
await stream.toast("Avatar display setting saved successfully.");
|
|
239
|
-
await stream.patchSignals({
|
|
240
|
-
_orig_showHeaderAvatar: body.showHeaderAvatar,
|
|
241
|
-
_avatarDisplayDirty: false
|
|
242
|
-
});
|
|
243
|
-
});
|
|
244
|
-
});
|
|
245
|
-
// ===========================================================================
|
|
246
|
-
// Appearance
|
|
247
|
-
// ===========================================================================
|
|
248
|
-
settingsRoutes.get("/appearance", async (c)=>{
|
|
249
|
-
const { settings } = c.var.services;
|
|
250
|
-
const siteName = await getSiteName(c);
|
|
251
|
-
const currentThemeId = await settings.get(SETTINGS_KEYS.THEME) ?? "default";
|
|
252
|
-
const currentFontThemeId = await settings.get("FONT_THEME") ?? "default";
|
|
253
|
-
const customCSS = await settings.get(SETTINGS_KEYS.CUSTOM_CSS) ?? "";
|
|
254
|
-
const themes = getAvailableThemes(c.var.config);
|
|
255
|
-
const saved = c.req.query("saved") !== undefined;
|
|
256
|
-
return c.html(/*#__PURE__*/ _jsx(DashLayout, {
|
|
257
|
-
c: c,
|
|
258
|
-
title: "Settings",
|
|
259
|
-
siteName: siteName,
|
|
260
|
-
currentPath: "/dash/settings",
|
|
261
|
-
toast: saved ? {
|
|
262
|
-
message: "Theme saved successfully."
|
|
263
|
-
} : undefined,
|
|
264
|
-
children: /*#__PURE__*/ _jsx(AppearanceContent, {
|
|
265
|
-
themes: themes,
|
|
266
|
-
currentThemeId: currentThemeId,
|
|
267
|
-
fontThemes: BUILTIN_FONT_THEMES,
|
|
268
|
-
currentFontThemeId: currentFontThemeId,
|
|
269
|
-
customCSS: customCSS
|
|
270
|
-
})
|
|
271
|
-
}));
|
|
272
|
-
});
|
|
273
|
-
settingsRoutes.post("/appearance", async (c)=>{
|
|
274
|
-
const body = await c.req.json();
|
|
275
|
-
const { settings } = c.var.services;
|
|
276
|
-
const themes = getAvailableThemes(c.var.config);
|
|
277
|
-
const validTheme = themes.find((t)=>t.id === body.theme);
|
|
278
|
-
if (!validTheme) {
|
|
279
|
-
return dsToast("Invalid theme selected.", "error");
|
|
280
|
-
}
|
|
281
|
-
if (validTheme.id === "default") {
|
|
282
|
-
await settings.remove(SETTINGS_KEYS.THEME);
|
|
283
|
-
} else {
|
|
284
|
-
await settings.set(SETTINGS_KEYS.THEME, validTheme.id);
|
|
285
|
-
}
|
|
286
|
-
return dsRedirect("/dash/settings/appearance?saved");
|
|
287
|
-
});
|
|
288
|
-
settingsRoutes.post("/font-theme", async (c)=>{
|
|
289
|
-
const body = await c.req.json();
|
|
290
|
-
const { settings } = c.var.services;
|
|
291
|
-
const validFont = BUILTIN_FONT_THEMES.find((f)=>f.id === body.fontTheme);
|
|
292
|
-
if (!validFont) {
|
|
293
|
-
return dsToast("Invalid font theme selected.", "error");
|
|
294
|
-
}
|
|
295
|
-
if (validFont.id === "default") {
|
|
296
|
-
await settings.remove("FONT_THEME");
|
|
297
|
-
} else {
|
|
298
|
-
await settings.set("FONT_THEME", validFont.id);
|
|
299
|
-
}
|
|
300
|
-
return dsRedirect("/dash/settings/appearance?saved");
|
|
301
|
-
});
|
|
302
|
-
settingsRoutes.post("/custom-css", async (c)=>{
|
|
303
|
-
const body = await c.req.json();
|
|
304
|
-
const { settings } = c.var.services;
|
|
305
|
-
const css = body.customCSS?.trim() ?? "";
|
|
306
|
-
if (css) {
|
|
307
|
-
await settings.set(SETTINGS_KEYS.CUSTOM_CSS, css);
|
|
308
|
-
} else {
|
|
309
|
-
await settings.remove(SETTINGS_KEYS.CUSTOM_CSS);
|
|
310
|
-
}
|
|
311
|
-
return dsToast("Custom CSS saved successfully.");
|
|
312
|
-
});
|
|
313
|
-
// ===========================================================================
|
|
314
|
-
// Account
|
|
315
|
-
// ===========================================================================
|
|
316
|
-
settingsRoutes.get("/account", async (c)=>{
|
|
317
|
-
const siteName = await getSiteName(c);
|
|
318
|
-
const session = await c.var.auth.api.getSession({
|
|
319
|
-
headers: c.req.raw.headers
|
|
320
|
-
});
|
|
321
|
-
const userName = session?.user?.name ?? "";
|
|
322
|
-
const saved = c.req.query("saved") !== undefined;
|
|
323
|
-
return c.html(/*#__PURE__*/ _jsx(DashLayout, {
|
|
324
|
-
c: c,
|
|
325
|
-
title: "Settings",
|
|
326
|
-
siteName: siteName,
|
|
327
|
-
currentPath: "/dash/settings",
|
|
328
|
-
toast: saved ? {
|
|
329
|
-
message: "Profile saved successfully."
|
|
330
|
-
} : undefined,
|
|
331
|
-
children: /*#__PURE__*/ _jsx(AccountContent, {
|
|
332
|
-
userName: userName
|
|
333
|
-
})
|
|
334
|
-
}));
|
|
335
|
-
});
|
|
336
|
-
settingsRoutes.post("/account", async (c)=>{
|
|
337
|
-
const body = await c.req.json();
|
|
338
|
-
const name = body.userName?.trim();
|
|
339
|
-
if (!name) {
|
|
340
|
-
return dsToast("Name is required.", "error");
|
|
341
|
-
}
|
|
342
|
-
try {
|
|
343
|
-
await c.var.auth.api.updateUser({
|
|
344
|
-
body: {
|
|
345
|
-
name
|
|
346
|
-
},
|
|
347
|
-
headers: c.req.raw.headers
|
|
348
|
-
});
|
|
349
|
-
} catch {
|
|
350
|
-
return dsToast("Failed to update profile.", "error");
|
|
351
|
-
}
|
|
352
|
-
return dsToast("Profile saved successfully.");
|
|
353
|
-
});
|
|
354
|
-
settingsRoutes.post("/password", async (c)=>{
|
|
355
|
-
const body = await c.req.json();
|
|
356
|
-
if (body.newPassword !== body.confirmPassword) {
|
|
357
|
-
return dsToast("Passwords do not match.", "error");
|
|
358
|
-
}
|
|
359
|
-
try {
|
|
360
|
-
await c.var.auth.api.changePassword({
|
|
361
|
-
body: {
|
|
362
|
-
currentPassword: body.currentPassword,
|
|
363
|
-
newPassword: body.newPassword,
|
|
364
|
-
revokeOtherSessions: false
|
|
365
|
-
},
|
|
366
|
-
headers: c.req.raw.headers
|
|
367
|
-
});
|
|
368
|
-
} catch {
|
|
369
|
-
return dsToast("Current password is incorrect.", "error");
|
|
370
|
-
}
|
|
371
|
-
return sse(c, async (stream)=>{
|
|
372
|
-
await stream.toast("Password changed successfully.");
|
|
373
|
-
await stream.patchSignals({
|
|
374
|
-
currentPassword: "",
|
|
375
|
-
newPassword: "",
|
|
376
|
-
confirmPassword: ""
|
|
377
|
-
});
|
|
378
|
-
});
|
|
379
|
-
});
|
package/dist/routes/feed/rss.js
DELETED
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* RSS Feed Routes
|
|
3
|
-
*/ import { Hono } from "hono";
|
|
4
|
-
import { defaultRssRenderer, defaultAtomRenderer } from "../../lib/feed.js";
|
|
5
|
-
import { getSiteLanguage } from "../../lib/config.js";
|
|
6
|
-
import { buildMediaMap } from "../../lib/media-helpers.js";
|
|
7
|
-
import { createMediaContext, toPostViews } from "../../lib/view.js";
|
|
8
|
-
export const rssRoutes = new Hono();
|
|
9
|
-
/**
|
|
10
|
-
* Build FeedData from the Hono context.
|
|
11
|
-
*/ async function buildFeedData(c) {
|
|
12
|
-
const all = await c.var.services.settings.getAll();
|
|
13
|
-
const siteName = all["SITE_NAME"] ?? "Jant";
|
|
14
|
-
const siteDescription = all["SITE_DESCRIPTION"] ?? "";
|
|
15
|
-
const siteUrl = c.env.SITE_URL;
|
|
16
|
-
const siteLanguage = await getSiteLanguage(c);
|
|
17
|
-
const feedLimit = parseInt(c.env.RSS_FEED_LIMIT ?? "50", 10) || 50;
|
|
18
|
-
const posts = await c.var.services.posts.list({
|
|
19
|
-
status: "published",
|
|
20
|
-
excludeReplies: true,
|
|
21
|
-
limit: feedLimit
|
|
22
|
-
});
|
|
23
|
-
// Batch load media for enclosures
|
|
24
|
-
const postIds = posts.map((p)=>p.id);
|
|
25
|
-
const rawMediaMap = await c.var.services.media.getByPostIds(postIds);
|
|
26
|
-
const mediaCtx = createMediaContext(c);
|
|
27
|
-
const mediaMap = buildMediaMap(rawMediaMap, mediaCtx.r2PublicUrl, mediaCtx.imageTransformUrl, mediaCtx.s3PublicUrl);
|
|
28
|
-
// Transform to PostView[] with media
|
|
29
|
-
const postViews = toPostViews(posts.map((p)=>({
|
|
30
|
-
...p,
|
|
31
|
-
mediaAttachments: mediaMap.get(p.id) ?? []
|
|
32
|
-
})), mediaCtx);
|
|
33
|
-
return {
|
|
34
|
-
siteName,
|
|
35
|
-
siteDescription,
|
|
36
|
-
siteUrl,
|
|
37
|
-
siteLanguage,
|
|
38
|
-
posts: postViews
|
|
39
|
-
};
|
|
40
|
-
}
|
|
41
|
-
// RSS 2.0 Feed - main feed at /feed
|
|
42
|
-
rssRoutes.get("/", async (c)=>{
|
|
43
|
-
const feedData = await buildFeedData(c);
|
|
44
|
-
const renderer = c.var.config.feed?.rss ?? defaultRssRenderer;
|
|
45
|
-
const xml = renderer(feedData);
|
|
46
|
-
return new Response(xml, {
|
|
47
|
-
headers: {
|
|
48
|
-
"Content-Type": "application/rss+xml; charset=utf-8"
|
|
49
|
-
}
|
|
50
|
-
});
|
|
51
|
-
});
|
|
52
|
-
// Atom Feed
|
|
53
|
-
rssRoutes.get("/atom.xml", async (c)=>{
|
|
54
|
-
const feedData = await buildFeedData(c);
|
|
55
|
-
const renderer = c.var.config.feed?.atom ?? defaultAtomRenderer;
|
|
56
|
-
const xml = renderer(feedData);
|
|
57
|
-
return new Response(xml, {
|
|
58
|
-
headers: {
|
|
59
|
-
"Content-Type": "application/atom+xml; charset=utf-8"
|
|
60
|
-
}
|
|
61
|
-
});
|
|
62
|
-
});
|
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Sitemap Routes
|
|
3
|
-
*/ import { Hono } from "hono";
|
|
4
|
-
import { defaultSitemapRenderer } from "../../lib/feed.js";
|
|
5
|
-
import { createMediaContext, toPostViewsFromPosts, toPageView } from "../../lib/view.js";
|
|
6
|
-
export const sitemapRoutes = new Hono();
|
|
7
|
-
// XML Sitemap
|
|
8
|
-
sitemapRoutes.get("/sitemap.xml", async (c)=>{
|
|
9
|
-
const siteUrl = c.env.SITE_URL;
|
|
10
|
-
const posts = await c.var.services.posts.list({
|
|
11
|
-
status: "published",
|
|
12
|
-
excludeReplies: true,
|
|
13
|
-
limit: 1000
|
|
14
|
-
});
|
|
15
|
-
// Fetch published pages
|
|
16
|
-
const allPages = await c.var.services.pages.list();
|
|
17
|
-
const publishedPages = allPages.filter((p)=>p.status === "published");
|
|
18
|
-
// Transform to View Models
|
|
19
|
-
const mediaCtx = createMediaContext(c);
|
|
20
|
-
const postViews = toPostViewsFromPosts(posts, mediaCtx);
|
|
21
|
-
const pageViews = publishedPages.map(toPageView);
|
|
22
|
-
const renderer = c.var.config.feed?.sitemap ?? defaultSitemapRenderer;
|
|
23
|
-
const xml = renderer({
|
|
24
|
-
siteUrl,
|
|
25
|
-
posts: postViews,
|
|
26
|
-
pages: pageViews
|
|
27
|
-
});
|
|
28
|
-
return new Response(xml, {
|
|
29
|
-
headers: {
|
|
30
|
-
"Content-Type": "application/xml; charset=utf-8"
|
|
31
|
-
}
|
|
32
|
-
});
|
|
33
|
-
});
|
|
34
|
-
// robots.txt
|
|
35
|
-
sitemapRoutes.get("/robots.txt", async (c)=>{
|
|
36
|
-
const siteUrl = c.env.SITE_URL;
|
|
37
|
-
const noindex = await c.var.services.settings.get("NOINDEX") === "true";
|
|
38
|
-
const directive = noindex ? "Disallow: /" : "Allow: /";
|
|
39
|
-
const robots = `User-agent: *
|
|
40
|
-
${directive}
|
|
41
|
-
|
|
42
|
-
Sitemap: ${siteUrl}/sitemap.xml
|
|
43
|
-
`;
|
|
44
|
-
return new Response(robots, {
|
|
45
|
-
headers: {
|
|
46
|
-
"Content-Type": "text/plain; charset=utf-8"
|
|
47
|
-
}
|
|
48
|
-
});
|
|
49
|
-
});
|
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx } from "hono/jsx/jsx-runtime";
|
|
2
|
-
/**
|
|
3
|
-
* Archive Page Route
|
|
4
|
-
*
|
|
5
|
-
* Shows all posts, optionally filtered by format or featured status
|
|
6
|
-
*/ import { Hono } from "hono";
|
|
7
|
-
import { FORMATS } from "../../types.js";
|
|
8
|
-
import { ArchivePage } from "../../ui/pages/ArchivePage.js";
|
|
9
|
-
import { getNavigationData } from "../../lib/navigation.js";
|
|
10
|
-
import { renderPublicPage } from "../../lib/render.js";
|
|
11
|
-
import { createMediaContext, toArchiveGroups } from "../../lib/view.js";
|
|
12
|
-
const PAGE_SIZE = 50;
|
|
13
|
-
export const archiveRoutes = new Hono();
|
|
14
|
-
// Archive page - all posts
|
|
15
|
-
archiveRoutes.get("/", async (c)=>{
|
|
16
|
-
const formatParam = c.req.query("format");
|
|
17
|
-
const format = formatParam && FORMATS.includes(formatParam) ? formatParam : undefined;
|
|
18
|
-
const featuredParam = c.req.query("featured");
|
|
19
|
-
const featured = featuredParam === "true" ? true : undefined;
|
|
20
|
-
// Parse cursor
|
|
21
|
-
const cursorParam = c.req.query("cursor");
|
|
22
|
-
const cursor = cursorParam ? parseInt(cursorParam, 10) : undefined;
|
|
23
|
-
const navData = await getNavigationData(c);
|
|
24
|
-
// Fetch one extra to check for more
|
|
25
|
-
const posts = await c.var.services.posts.list({
|
|
26
|
-
format,
|
|
27
|
-
status: "published",
|
|
28
|
-
featured,
|
|
29
|
-
excludeReplies: true,
|
|
30
|
-
cursor,
|
|
31
|
-
limit: PAGE_SIZE + 1
|
|
32
|
-
});
|
|
33
|
-
const hasMore = posts.length > PAGE_SIZE;
|
|
34
|
-
const displayPosts = hasMore ? posts.slice(0, PAGE_SIZE) : posts;
|
|
35
|
-
// Get next cursor
|
|
36
|
-
const nextCursor = hasMore && displayPosts.length > 0 ? displayPosts[displayPosts.length - 1].id : undefined;
|
|
37
|
-
// Group posts by year-month
|
|
38
|
-
const grouped = new Map();
|
|
39
|
-
for (const post of displayPosts){
|
|
40
|
-
const date = new Date(post.publishedAt * 1000);
|
|
41
|
-
const key = `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, "0")}`;
|
|
42
|
-
if (!grouped.has(key)) {
|
|
43
|
-
grouped.set(key, []);
|
|
44
|
-
}
|
|
45
|
-
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- Map.set() above guarantees key exists
|
|
46
|
-
grouped.get(key).push(post);
|
|
47
|
-
}
|
|
48
|
-
// Transform to View Models
|
|
49
|
-
const mediaCtx = createMediaContext(c);
|
|
50
|
-
const groups = toArchiveGroups(grouped, mediaCtx);
|
|
51
|
-
return renderPublicPage(c, {
|
|
52
|
-
title: `Archive - ${navData.siteName}`,
|
|
53
|
-
navData,
|
|
54
|
-
content: /*#__PURE__*/ _jsx(ArchivePage, {
|
|
55
|
-
groups: groups,
|
|
56
|
-
hasMore: hasMore,
|
|
57
|
-
nextCursor: nextCursor,
|
|
58
|
-
format: format,
|
|
59
|
-
featured: featured
|
|
60
|
-
})
|
|
61
|
-
});
|
|
62
|
-
});
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx } from "hono/jsx/jsx-runtime";
|
|
2
|
-
/**
|
|
3
|
-
* Collection Page Route
|
|
4
|
-
*/ import { Hono } from "hono";
|
|
5
|
-
import { CollectionPage } from "../../ui/pages/CollectionPage.js";
|
|
6
|
-
import { getNavigationData } from "../../lib/navigation.js";
|
|
7
|
-
import { renderPublicPage } from "../../lib/render.js";
|
|
8
|
-
import { createMediaContext, toPostViewsFromPosts } from "../../lib/view.js";
|
|
9
|
-
export const collectionRoutes = new Hono();
|
|
10
|
-
collectionRoutes.get("/:slug", async (c)=>{
|
|
11
|
-
const slug = c.req.param("slug");
|
|
12
|
-
const collection = await c.var.services.collections.getBySlug(slug);
|
|
13
|
-
if (!collection) return c.notFound();
|
|
14
|
-
// Fetch posts in this collection
|
|
15
|
-
const posts = await c.var.services.posts.list({
|
|
16
|
-
collectionId: collection.id,
|
|
17
|
-
status: "published",
|
|
18
|
-
excludeReplies: true
|
|
19
|
-
});
|
|
20
|
-
const navData = await getNavigationData(c);
|
|
21
|
-
// Transform to View Models
|
|
22
|
-
const mediaCtx = createMediaContext(c);
|
|
23
|
-
const postViews = toPostViewsFromPosts(posts, mediaCtx);
|
|
24
|
-
return renderPublicPage(c, {
|
|
25
|
-
title: `${collection.title} - ${navData.siteName}`,
|
|
26
|
-
description: collection.description ?? undefined,
|
|
27
|
-
navData,
|
|
28
|
-
content: /*#__PURE__*/ _jsx(CollectionPage, {
|
|
29
|
-
collection: collection,
|
|
30
|
-
posts: postViews,
|
|
31
|
-
hasMore: false
|
|
32
|
-
})
|
|
33
|
-
});
|
|
34
|
-
});
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx } from "hono/jsx/jsx-runtime";
|
|
2
|
-
/**
|
|
3
|
-
* Collections Listing Page Route
|
|
4
|
-
*
|
|
5
|
-
* Lists all collections with their post counts.
|
|
6
|
-
*/ import { Hono } from "hono";
|
|
7
|
-
import { getNavigationData } from "../../lib/navigation.js";
|
|
8
|
-
import { renderPublicPage } from "../../lib/render.js";
|
|
9
|
-
import { CollectionsPage } from "../../ui/pages/CollectionsPage.js";
|
|
10
|
-
export const collectionsPageRoutes = new Hono();
|
|
11
|
-
collectionsPageRoutes.get("/", async (c)=>{
|
|
12
|
-
const [allCollections, postCounts] = await Promise.all([
|
|
13
|
-
c.var.services.collections.list(),
|
|
14
|
-
c.var.services.collections.getPostCounts()
|
|
15
|
-
]);
|
|
16
|
-
const collections = allCollections.map((col)=>({
|
|
17
|
-
...col,
|
|
18
|
-
postCount: postCounts.get(col.id) ?? 0
|
|
19
|
-
}));
|
|
20
|
-
const navData = await getNavigationData(c);
|
|
21
|
-
return renderPublicPage(c, {
|
|
22
|
-
title: `Collections - ${navData.siteName}`,
|
|
23
|
-
navData,
|
|
24
|
-
content: /*#__PURE__*/ _jsx(CollectionsPage, {
|
|
25
|
-
collections: collections
|
|
26
|
-
})
|
|
27
|
-
});
|
|
28
|
-
});
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx } from "hono/jsx/jsx-runtime";
|
|
2
|
-
/**
|
|
3
|
-
* Featured Page Route
|
|
4
|
-
*
|
|
5
|
-
* Shows featured posts as a timeline feed.
|
|
6
|
-
*/ import { Hono } from "hono";
|
|
7
|
-
import { getNavigationData } from "../../lib/navigation.js";
|
|
8
|
-
import { renderPublicPage } from "../../lib/render.js";
|
|
9
|
-
import { createMediaContext, toPostViewsFromPosts } from "../../lib/view.js";
|
|
10
|
-
import { FeaturedPage } from "../../ui/pages/FeaturedPage.js";
|
|
11
|
-
export const featuredRoutes = new Hono();
|
|
12
|
-
featuredRoutes.get("/", async (c)=>{
|
|
13
|
-
const navData = await getNavigationData(c);
|
|
14
|
-
// When homepage already shows featured, redirect to avoid duplicate content
|
|
15
|
-
if (navData.homeDefaultView === "featured") {
|
|
16
|
-
return c.redirect("/", 302);
|
|
17
|
-
}
|
|
18
|
-
const posts = await c.var.services.posts.list({
|
|
19
|
-
featured: true,
|
|
20
|
-
status: "published",
|
|
21
|
-
excludeReplies: true
|
|
22
|
-
});
|
|
23
|
-
const mediaCtx = createMediaContext(c);
|
|
24
|
-
const postViews = toPostViewsFromPosts(posts, mediaCtx);
|
|
25
|
-
// Convert to timeline items (simple — no thread previews)
|
|
26
|
-
const items = postViews.map((post)=>({
|
|
27
|
-
post
|
|
28
|
-
}));
|
|
29
|
-
return renderPublicPage(c, {
|
|
30
|
-
title: `Featured - ${navData.siteName}`,
|
|
31
|
-
navData,
|
|
32
|
-
content: /*#__PURE__*/ _jsx(FeaturedPage, {
|
|
33
|
-
items: items
|
|
34
|
-
})
|
|
35
|
-
});
|
|
36
|
-
});
|