@jant/core 0.3.27 → 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 +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
|
@@ -1,42 +1,23 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Dashboard Settings Routes
|
|
3
3
|
*
|
|
4
|
-
* Sub-pages: General,
|
|
4
|
+
* Sub-pages: General, Account
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import { Hono } from "hono";
|
|
8
|
+
import { msg } from "@lingui/core/macro";
|
|
8
9
|
import type { Bindings } from "../../types.js";
|
|
9
|
-
import type { AppVariables } from "../../app.js";
|
|
10
|
+
import type { AppVariables } from "../../types/app-context.js";
|
|
10
11
|
import { DashLayout } from "../../ui/layouts/DashLayout.js";
|
|
11
12
|
import { sse, dsRedirect, dsToast } from "../../lib/sse.js";
|
|
13
|
+
import { getI18n } from "../../i18n/index.js";
|
|
12
14
|
import { arrayBufferToBase64 } from "../../lib/favicon.js";
|
|
13
|
-
import {
|
|
14
|
-
getSiteLanguage,
|
|
15
|
-
getSiteName,
|
|
16
|
-
getHomeDefaultView,
|
|
17
|
-
getTimeZone,
|
|
18
|
-
getSiteFooter,
|
|
19
|
-
isNoIndex,
|
|
20
|
-
getConfigFallback,
|
|
21
|
-
} from "../../lib/config.js";
|
|
22
|
-
import { SETTINGS_KEYS } from "../../lib/constants.js";
|
|
23
|
-
import { getAvailableThemes } from "../../lib/theme.js";
|
|
24
|
-
import { getMediaUrl, getPublicUrlForProvider } from "../../lib/image.js";
|
|
25
15
|
import { TIMEZONES } from "../../lib/timezones.js";
|
|
26
|
-
import {
|
|
16
|
+
import { escapeHtml } from "../../lib/html.js";
|
|
17
|
+
import { validateUploadFile, generateStorageKey } from "../../lib/upload.js";
|
|
27
18
|
import { GeneralContent } from "../../ui/dash/settings/GeneralContent.js";
|
|
28
|
-
import { AppearanceContent } from "../../ui/dash/settings/AppearanceContent.js";
|
|
29
19
|
import { AccountContent } from "../../ui/dash/settings/AccountContent.js";
|
|
30
20
|
|
|
31
|
-
/** Escape HTML special characters for safe insertion into HTML strings */
|
|
32
|
-
function escapeHtml(str: string): string {
|
|
33
|
-
return str
|
|
34
|
-
.replace(/&/g, "&")
|
|
35
|
-
.replace(/</g, "<")
|
|
36
|
-
.replace(/>/g, ">")
|
|
37
|
-
.replace(/"/g, """);
|
|
38
|
-
}
|
|
39
|
-
|
|
40
21
|
type Env = { Bindings: Bindings; Variables: AppVariables };
|
|
41
22
|
|
|
42
23
|
export const settingsRoutes = new Hono<Env>();
|
|
@@ -45,41 +26,11 @@ export const settingsRoutes = new Hono<Env>();
|
|
|
45
26
|
// General settings
|
|
46
27
|
// ===========================================================================
|
|
47
28
|
|
|
48
|
-
/** Resolve the avatar storage key to a URL */
|
|
49
|
-
async function resolveAvatarUrl(c: {
|
|
50
|
-
var: { services: AppVariables["services"] };
|
|
51
|
-
env: Bindings;
|
|
52
|
-
}): Promise<string> {
|
|
53
|
-
const avatarKey = await c.var.services.settings.get("SITE_AVATAR");
|
|
54
|
-
if (!avatarKey) return "";
|
|
55
|
-
const publicUrl = getPublicUrlForProvider(
|
|
56
|
-
c.env.STORAGE_DRIVER || "r2",
|
|
57
|
-
c.env.R2_PUBLIC_URL,
|
|
58
|
-
c.env.S3_PUBLIC_URL,
|
|
59
|
-
);
|
|
60
|
-
return getMediaUrl(avatarKey, publicUrl);
|
|
61
|
-
}
|
|
62
|
-
|
|
63
29
|
settingsRoutes.get("/", async (c) => {
|
|
64
|
-
const {
|
|
30
|
+
const { allSettings, appConfig } = c.var;
|
|
65
31
|
|
|
66
|
-
const dbSiteName =
|
|
67
|
-
const dbSiteDescription =
|
|
68
|
-
const [siteLanguage, homeDefaultView, timeZone, siteFooter, noindex] =
|
|
69
|
-
await Promise.all([
|
|
70
|
-
getSiteLanguage(c),
|
|
71
|
-
getHomeDefaultView(c),
|
|
72
|
-
getTimeZone(c),
|
|
73
|
-
getSiteFooter(c),
|
|
74
|
-
isNoIndex(c),
|
|
75
|
-
]);
|
|
76
|
-
|
|
77
|
-
const siteNameFallback = getConfigFallback(c, "SITE_NAME");
|
|
78
|
-
const siteDescriptionFallback = getConfigFallback(c, "SITE_DESCRIPTION");
|
|
79
|
-
|
|
80
|
-
const siteAvatarUrl = await resolveAvatarUrl(c);
|
|
81
|
-
const showHeaderAvatar =
|
|
82
|
-
(await settings.get("SHOW_HEADER_AVATAR")) === "true";
|
|
32
|
+
const dbSiteName = allSettings["SITE_NAME"] ?? "";
|
|
33
|
+
const dbSiteDescription = allSettings["SITE_DESCRIPTION"] ?? "";
|
|
83
34
|
|
|
84
35
|
const saved = c.req.query("saved") !== undefined;
|
|
85
36
|
|
|
@@ -87,22 +38,22 @@ settingsRoutes.get("/", async (c) => {
|
|
|
87
38
|
<DashLayout
|
|
88
39
|
c={c}
|
|
89
40
|
title="Settings"
|
|
90
|
-
siteName={dbSiteName ||
|
|
41
|
+
siteName={dbSiteName || appConfig.fallbacks.siteName}
|
|
91
42
|
currentPath="/dash/settings"
|
|
92
43
|
toast={saved ? { message: "Settings saved successfully." } : undefined}
|
|
93
44
|
>
|
|
94
45
|
<GeneralContent
|
|
95
46
|
siteName={dbSiteName || ""}
|
|
96
47
|
siteDescription={dbSiteDescription || ""}
|
|
97
|
-
siteLanguage={siteLanguage}
|
|
98
|
-
homeDefaultView={homeDefaultView}
|
|
99
|
-
siteNameFallback={
|
|
100
|
-
siteDescriptionFallback={
|
|
101
|
-
siteAvatarUrl={siteAvatarUrl}
|
|
102
|
-
showHeaderAvatar={showHeaderAvatar}
|
|
103
|
-
timeZone={timeZone}
|
|
104
|
-
siteFooter={siteFooter}
|
|
105
|
-
noindex={noindex}
|
|
48
|
+
siteLanguage={appConfig.siteLanguage}
|
|
49
|
+
homeDefaultView={appConfig.homeDefaultView}
|
|
50
|
+
siteNameFallback={appConfig.fallbacks.siteName}
|
|
51
|
+
siteDescriptionFallback={appConfig.fallbacks.siteDescription}
|
|
52
|
+
siteAvatarUrl={appConfig.siteAvatarUrl}
|
|
53
|
+
showHeaderAvatar={appConfig.showHeaderAvatar}
|
|
54
|
+
timeZone={appConfig.timeZone}
|
|
55
|
+
siteFooter={appConfig.siteFooter}
|
|
56
|
+
noindex={appConfig.noindex}
|
|
106
57
|
timezones={TIMEZONES}
|
|
107
58
|
/>
|
|
108
59
|
</DashLayout>,
|
|
@@ -110,9 +61,11 @@ settingsRoutes.get("/", async (c) => {
|
|
|
110
61
|
});
|
|
111
62
|
|
|
112
63
|
settingsRoutes.post("/", async (c) => {
|
|
64
|
+
const i18n = getI18n(c);
|
|
113
65
|
const body = await c.req.json<{
|
|
114
66
|
siteName: string;
|
|
115
67
|
siteDescription: string;
|
|
68
|
+
siteFooter: string;
|
|
116
69
|
siteLanguage: string;
|
|
117
70
|
homeDefaultView: string;
|
|
118
71
|
timeZone: string;
|
|
@@ -120,7 +73,7 @@ settingsRoutes.post("/", async (c) => {
|
|
|
120
73
|
|
|
121
74
|
const { settings } = c.var.services;
|
|
122
75
|
|
|
123
|
-
const oldLanguage =
|
|
76
|
+
const oldLanguage = c.var.allSettings["SITE_LANGUAGE"] ?? "en";
|
|
124
77
|
|
|
125
78
|
if (body.siteName.trim()) {
|
|
126
79
|
await settings.set("SITE_NAME", body.siteName.trim());
|
|
@@ -134,6 +87,13 @@ settingsRoutes.post("/", async (c) => {
|
|
|
134
87
|
await settings.remove("SITE_DESCRIPTION");
|
|
135
88
|
}
|
|
136
89
|
|
|
90
|
+
// Footer
|
|
91
|
+
if (body.siteFooter?.trim()) {
|
|
92
|
+
await settings.set("SITE_FOOTER", body.siteFooter.trim());
|
|
93
|
+
} else {
|
|
94
|
+
await settings.remove("SITE_FOOTER");
|
|
95
|
+
}
|
|
96
|
+
|
|
137
97
|
await settings.set("SITE_LANGUAGE", body.siteLanguage);
|
|
138
98
|
|
|
139
99
|
// Save homepage default view (only store if non-default)
|
|
@@ -151,7 +111,29 @@ settingsRoutes.post("/", async (c) => {
|
|
|
151
111
|
}
|
|
152
112
|
|
|
153
113
|
const languageChanged = oldLanguage !== body.siteLanguage;
|
|
154
|
-
const displayName =
|
|
114
|
+
const displayName =
|
|
115
|
+
body.siteName.trim() || c.var.appConfig.fallbacks.siteName;
|
|
116
|
+
|
|
117
|
+
// ── JSON response mode (used by Lit settings bridge) ──────────────
|
|
118
|
+
const wantsJson = c.req.header("accept")?.includes("application/json");
|
|
119
|
+
if (wantsJson) {
|
|
120
|
+
if (languageChanged) {
|
|
121
|
+
return c.json({
|
|
122
|
+
status: "redirect" as const,
|
|
123
|
+
url: "/dash/settings?saved",
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
return c.json({
|
|
127
|
+
status: "ok" as const,
|
|
128
|
+
toast: i18n._(
|
|
129
|
+
msg({
|
|
130
|
+
message: "Settings saved successfully.",
|
|
131
|
+
comment: "@context: Toast after saving general settings",
|
|
132
|
+
}),
|
|
133
|
+
),
|
|
134
|
+
siteName: displayName,
|
|
135
|
+
});
|
|
136
|
+
}
|
|
155
137
|
|
|
156
138
|
return sse(c, async (stream) => {
|
|
157
139
|
if (languageChanged) {
|
|
@@ -165,10 +147,18 @@ settingsRoutes.post("/", async (c) => {
|
|
|
165
147
|
mode: "inner",
|
|
166
148
|
selector: "title",
|
|
167
149
|
});
|
|
168
|
-
await stream.toast(
|
|
150
|
+
await stream.toast(
|
|
151
|
+
i18n._(
|
|
152
|
+
msg({
|
|
153
|
+
message: "Settings saved successfully.",
|
|
154
|
+
comment: "@context: Toast after saving general settings",
|
|
155
|
+
}),
|
|
156
|
+
),
|
|
157
|
+
);
|
|
169
158
|
await stream.patchSignals({
|
|
170
159
|
_orig_siteName: body.siteName,
|
|
171
160
|
_orig_siteDescription: body.siteDescription,
|
|
161
|
+
_orig_siteFooter: body.siteFooter,
|
|
172
162
|
_orig_siteLanguage: body.siteLanguage,
|
|
173
163
|
_orig_homeDefaultView: body.homeDefaultView,
|
|
174
164
|
_orig_timeZone: body.timeZone,
|
|
@@ -178,26 +168,8 @@ settingsRoutes.post("/", async (c) => {
|
|
|
178
168
|
});
|
|
179
169
|
});
|
|
180
170
|
|
|
181
|
-
settingsRoutes.post("/footer", async (c) => {
|
|
182
|
-
const body = await c.req.json<{ siteFooter: string }>();
|
|
183
|
-
const { settings } = c.var.services;
|
|
184
|
-
|
|
185
|
-
if (body.siteFooter?.trim()) {
|
|
186
|
-
await settings.set("SITE_FOOTER", body.siteFooter.trim());
|
|
187
|
-
} else {
|
|
188
|
-
await settings.remove("SITE_FOOTER");
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
return sse(c, async (stream) => {
|
|
192
|
-
await stream.toast("Footer saved successfully.");
|
|
193
|
-
await stream.patchSignals({
|
|
194
|
-
_orig_siteFooter: body.siteFooter,
|
|
195
|
-
_footerDirty: false,
|
|
196
|
-
});
|
|
197
|
-
});
|
|
198
|
-
});
|
|
199
|
-
|
|
200
171
|
settingsRoutes.post("/seo", async (c) => {
|
|
172
|
+
const i18n = getI18n(c);
|
|
201
173
|
const body = await c.req.json<{ noindex: string }>();
|
|
202
174
|
const { settings } = c.var.services;
|
|
203
175
|
|
|
@@ -210,8 +182,29 @@ settingsRoutes.post("/seo", async (c) => {
|
|
|
210
182
|
await settings.set("NOINDEX", "true");
|
|
211
183
|
}
|
|
212
184
|
|
|
185
|
+
// ── JSON response mode (used by Lit settings bridge) ──────────────
|
|
186
|
+
const wantsJson = c.req.header("accept")?.includes("application/json");
|
|
187
|
+
if (wantsJson) {
|
|
188
|
+
return c.json({
|
|
189
|
+
status: "ok" as const,
|
|
190
|
+
toast: i18n._(
|
|
191
|
+
msg({
|
|
192
|
+
message: "SEO settings saved successfully.",
|
|
193
|
+
comment: "@context: Toast after saving SEO settings",
|
|
194
|
+
}),
|
|
195
|
+
),
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
|
|
213
199
|
return sse(c, async (stream) => {
|
|
214
|
-
await stream.toast(
|
|
200
|
+
await stream.toast(
|
|
201
|
+
i18n._(
|
|
202
|
+
msg({
|
|
203
|
+
message: "SEO settings saved successfully.",
|
|
204
|
+
comment: "@context: Toast after saving SEO settings",
|
|
205
|
+
}),
|
|
206
|
+
),
|
|
207
|
+
);
|
|
215
208
|
await stream.patchSignals({
|
|
216
209
|
_orig_noindex: body.noindex,
|
|
217
210
|
_seoDirty: false,
|
|
@@ -224,41 +217,40 @@ settingsRoutes.post("/seo", async (c) => {
|
|
|
224
217
|
// ===========================================================================
|
|
225
218
|
|
|
226
219
|
settingsRoutes.post("/avatar", async (c) => {
|
|
220
|
+
const i18n = getI18n(c);
|
|
227
221
|
const storage = c.var.storage;
|
|
228
222
|
if (!storage) {
|
|
229
|
-
return dsToast(
|
|
223
|
+
return dsToast(
|
|
224
|
+
i18n._(
|
|
225
|
+
msg({
|
|
226
|
+
message: "Storage not configured.",
|
|
227
|
+
comment: "@context: Error toast when file storage is not set up",
|
|
228
|
+
}),
|
|
229
|
+
),
|
|
230
|
+
"error",
|
|
231
|
+
);
|
|
230
232
|
}
|
|
231
233
|
|
|
232
234
|
const formData = await c.req.formData();
|
|
233
235
|
const file = formData.get("file") as File | null;
|
|
234
236
|
if (!file) {
|
|
235
|
-
return dsToast(
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
];
|
|
245
|
-
if (!allowedTypes.includes(file.type)) {
|
|
246
|
-
return dsToast("File type not allowed.", "error");
|
|
237
|
+
return dsToast(
|
|
238
|
+
i18n._(
|
|
239
|
+
msg({
|
|
240
|
+
message: "No file provided.",
|
|
241
|
+
comment: "@context: Error toast when no file was selected for upload",
|
|
242
|
+
}),
|
|
243
|
+
),
|
|
244
|
+
"error",
|
|
245
|
+
);
|
|
247
246
|
}
|
|
248
247
|
|
|
249
|
-
const
|
|
250
|
-
if (
|
|
251
|
-
return dsToast(
|
|
248
|
+
const uploadError = validateUploadFile(file);
|
|
249
|
+
if (uploadError) {
|
|
250
|
+
return dsToast(uploadError, "error");
|
|
252
251
|
}
|
|
253
252
|
|
|
254
|
-
const {
|
|
255
|
-
const ext = file.name.split(".").pop() || "bin";
|
|
256
|
-
const id = uuidv7();
|
|
257
|
-
const date = new Date();
|
|
258
|
-
const year = date.getUTCFullYear();
|
|
259
|
-
const month = String(date.getUTCMonth() + 1).padStart(2, "0");
|
|
260
|
-
const filename = `${id}.${ext}`;
|
|
261
|
-
const storageKey = `media/${year}/${month}/${filename}`;
|
|
253
|
+
const { id, filename, storageKey } = generateStorageKey(file.name);
|
|
262
254
|
|
|
263
255
|
try {
|
|
264
256
|
await storage.put(storageKey, file.stream(), {
|
|
@@ -272,39 +264,80 @@ settingsRoutes.post("/avatar", async (c) => {
|
|
|
272
264
|
mimeType: file.type,
|
|
273
265
|
size: file.size,
|
|
274
266
|
storageKey,
|
|
275
|
-
provider: c.
|
|
267
|
+
provider: c.var.appConfig.storageDriver,
|
|
276
268
|
});
|
|
277
269
|
|
|
278
270
|
await c.var.services.settings.set("SITE_AVATAR", storageKey);
|
|
279
271
|
|
|
280
|
-
// Store favicon
|
|
272
|
+
// Store favicon ICO as base64 in settings (tiny file, accessed every page load)
|
|
281
273
|
const faviconFile = formData.get("favicon") as File | null;
|
|
282
|
-
const appleTouchFile = formData.get("appleTouch") as File | null;
|
|
283
|
-
|
|
284
274
|
if (faviconFile) {
|
|
285
275
|
const b64 = arrayBufferToBase64(await faviconFile.arrayBuffer());
|
|
286
276
|
await c.var.services.settings.set("SITE_FAVICON_ICO", b64);
|
|
287
277
|
}
|
|
288
278
|
|
|
279
|
+
// Store apple-touch-icon in R2 (180x180 PNG, not tiny enough for base64)
|
|
280
|
+
const appleTouchFile = formData.get("appleTouch") as File | null;
|
|
289
281
|
if (appleTouchFile) {
|
|
290
|
-
const
|
|
291
|
-
await
|
|
282
|
+
const appleTouchKey = "favicon/apple-touch-icon.png";
|
|
283
|
+
await storage.put(
|
|
284
|
+
appleTouchKey,
|
|
285
|
+
new Uint8Array(await appleTouchFile.arrayBuffer()),
|
|
286
|
+
{ contentType: "image/png" },
|
|
287
|
+
);
|
|
288
|
+
await c.var.services.settings.set(
|
|
289
|
+
"SITE_FAVICON_APPLE_TOUCH",
|
|
290
|
+
appleTouchKey,
|
|
291
|
+
);
|
|
292
292
|
}
|
|
293
293
|
|
|
294
|
+
// Set favicon version for cache-busting
|
|
295
|
+
const now = new Date();
|
|
296
|
+
const version =
|
|
297
|
+
String(now.getUTCFullYear()) +
|
|
298
|
+
String(now.getUTCMonth() + 1).padStart(2, "0") +
|
|
299
|
+
String(now.getUTCDate()).padStart(2, "0") +
|
|
300
|
+
String(now.getUTCHours()).padStart(2, "0") +
|
|
301
|
+
String(now.getUTCMinutes()).padStart(2, "0");
|
|
302
|
+
await c.var.services.settings.set("SITE_FAVICON_VERSION", version);
|
|
303
|
+
|
|
294
304
|
return dsRedirect("/dash/settings?saved");
|
|
295
305
|
} catch {
|
|
296
|
-
return dsToast(
|
|
306
|
+
return dsToast(
|
|
307
|
+
i18n._(
|
|
308
|
+
msg({
|
|
309
|
+
message: "Upload failed. Please try again.",
|
|
310
|
+
comment: "@context: Error toast when avatar upload fails",
|
|
311
|
+
}),
|
|
312
|
+
),
|
|
313
|
+
"error",
|
|
314
|
+
);
|
|
297
315
|
}
|
|
298
316
|
});
|
|
299
317
|
|
|
300
318
|
settingsRoutes.post("/avatar/remove", async (c) => {
|
|
319
|
+
const storage = c.var.storage;
|
|
320
|
+
const appleTouchKey = c.var.allSettings["SITE_FAVICON_APPLE_TOUCH"];
|
|
321
|
+
if (storage && appleTouchKey) {
|
|
322
|
+
await storage.delete(appleTouchKey);
|
|
323
|
+
}
|
|
324
|
+
|
|
301
325
|
await c.var.services.settings.remove("SITE_AVATAR");
|
|
302
326
|
await c.var.services.settings.remove("SITE_FAVICON_ICO");
|
|
303
327
|
await c.var.services.settings.remove("SITE_FAVICON_APPLE_TOUCH");
|
|
328
|
+
await c.var.services.settings.remove("SITE_FAVICON_VERSION");
|
|
329
|
+
|
|
330
|
+
// ── JSON response mode (used by Lit settings bridge) ──────────────
|
|
331
|
+
const wantsJson = c.req.header("accept")?.includes("application/json");
|
|
332
|
+
if (wantsJson) {
|
|
333
|
+
return c.json({ status: "redirect" as const, url: "/dash/settings?saved" });
|
|
334
|
+
}
|
|
335
|
+
|
|
304
336
|
return dsRedirect("/dash/settings?saved");
|
|
305
337
|
});
|
|
306
338
|
|
|
307
339
|
settingsRoutes.post("/avatar/display", async (c) => {
|
|
340
|
+
const i18n = getI18n(c);
|
|
308
341
|
const body = await c.req.json<{ showHeaderAvatar: string }>();
|
|
309
342
|
const { settings } = c.var.services;
|
|
310
343
|
|
|
@@ -314,8 +347,29 @@ settingsRoutes.post("/avatar/display", async (c) => {
|
|
|
314
347
|
await settings.remove("SHOW_HEADER_AVATAR");
|
|
315
348
|
}
|
|
316
349
|
|
|
350
|
+
// ── JSON response mode (used by Lit settings bridge) ──────────────
|
|
351
|
+
const wantsJson = c.req.header("accept")?.includes("application/json");
|
|
352
|
+
if (wantsJson) {
|
|
353
|
+
return c.json({
|
|
354
|
+
status: "ok" as const,
|
|
355
|
+
toast: i18n._(
|
|
356
|
+
msg({
|
|
357
|
+
message: "Avatar display setting saved successfully.",
|
|
358
|
+
comment: "@context: Toast after saving avatar display preference",
|
|
359
|
+
}),
|
|
360
|
+
),
|
|
361
|
+
});
|
|
362
|
+
}
|
|
363
|
+
|
|
317
364
|
return sse(c, async (stream) => {
|
|
318
|
-
await stream.toast(
|
|
365
|
+
await stream.toast(
|
|
366
|
+
i18n._(
|
|
367
|
+
msg({
|
|
368
|
+
message: "Avatar display setting saved successfully.",
|
|
369
|
+
comment: "@context: Toast after saving avatar display preference",
|
|
370
|
+
}),
|
|
371
|
+
),
|
|
372
|
+
);
|
|
319
373
|
await stream.patchSignals({
|
|
320
374
|
_orig_showHeaderAvatar: body.showHeaderAvatar,
|
|
321
375
|
_avatarDisplayDirty: false,
|
|
@@ -323,96 +377,12 @@ settingsRoutes.post("/avatar/display", async (c) => {
|
|
|
323
377
|
});
|
|
324
378
|
});
|
|
325
379
|
|
|
326
|
-
// ===========================================================================
|
|
327
|
-
// Appearance
|
|
328
|
-
// ===========================================================================
|
|
329
|
-
|
|
330
|
-
settingsRoutes.get("/appearance", async (c) => {
|
|
331
|
-
const { settings } = c.var.services;
|
|
332
|
-
const siteName = await getSiteName(c);
|
|
333
|
-
const currentThemeId = (await settings.get(SETTINGS_KEYS.THEME)) ?? "default";
|
|
334
|
-
const currentFontThemeId = (await settings.get("FONT_THEME")) ?? "default";
|
|
335
|
-
const customCSS = (await settings.get(SETTINGS_KEYS.CUSTOM_CSS)) ?? "";
|
|
336
|
-
const themes = getAvailableThemes(c.var.config);
|
|
337
|
-
const saved = c.req.query("saved") !== undefined;
|
|
338
|
-
|
|
339
|
-
return c.html(
|
|
340
|
-
<DashLayout
|
|
341
|
-
c={c}
|
|
342
|
-
title="Settings"
|
|
343
|
-
siteName={siteName}
|
|
344
|
-
currentPath="/dash/settings"
|
|
345
|
-
toast={saved ? { message: "Theme saved successfully." } : undefined}
|
|
346
|
-
>
|
|
347
|
-
<AppearanceContent
|
|
348
|
-
themes={themes}
|
|
349
|
-
currentThemeId={currentThemeId}
|
|
350
|
-
fontThemes={BUILTIN_FONT_THEMES}
|
|
351
|
-
currentFontThemeId={currentFontThemeId}
|
|
352
|
-
customCSS={customCSS}
|
|
353
|
-
/>
|
|
354
|
-
</DashLayout>,
|
|
355
|
-
);
|
|
356
|
-
});
|
|
357
|
-
|
|
358
|
-
settingsRoutes.post("/appearance", async (c) => {
|
|
359
|
-
const body = await c.req.json<{ theme: string }>();
|
|
360
|
-
const { settings } = c.var.services;
|
|
361
|
-
const themes = getAvailableThemes(c.var.config);
|
|
362
|
-
|
|
363
|
-
const validTheme = themes.find((t) => t.id === body.theme);
|
|
364
|
-
if (!validTheme) {
|
|
365
|
-
return dsToast("Invalid theme selected.", "error");
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
if (validTheme.id === "default") {
|
|
369
|
-
await settings.remove(SETTINGS_KEYS.THEME);
|
|
370
|
-
} else {
|
|
371
|
-
await settings.set(SETTINGS_KEYS.THEME, validTheme.id);
|
|
372
|
-
}
|
|
373
|
-
|
|
374
|
-
return dsRedirect("/dash/settings/appearance?saved");
|
|
375
|
-
});
|
|
376
|
-
|
|
377
|
-
settingsRoutes.post("/font-theme", async (c) => {
|
|
378
|
-
const body = await c.req.json<{ fontTheme: string }>();
|
|
379
|
-
const { settings } = c.var.services;
|
|
380
|
-
|
|
381
|
-
const validFont = BUILTIN_FONT_THEMES.find((f) => f.id === body.fontTheme);
|
|
382
|
-
if (!validFont) {
|
|
383
|
-
return dsToast("Invalid font theme selected.", "error");
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
if (validFont.id === "default") {
|
|
387
|
-
await settings.remove("FONT_THEME");
|
|
388
|
-
} else {
|
|
389
|
-
await settings.set("FONT_THEME", validFont.id);
|
|
390
|
-
}
|
|
391
|
-
|
|
392
|
-
return dsRedirect("/dash/settings/appearance?saved");
|
|
393
|
-
});
|
|
394
|
-
|
|
395
|
-
settingsRoutes.post("/custom-css", async (c) => {
|
|
396
|
-
const body = await c.req.json<{ customCSS: string }>();
|
|
397
|
-
const { settings } = c.var.services;
|
|
398
|
-
|
|
399
|
-
const css = body.customCSS?.trim() ?? "";
|
|
400
|
-
|
|
401
|
-
if (css) {
|
|
402
|
-
await settings.set(SETTINGS_KEYS.CUSTOM_CSS, css);
|
|
403
|
-
} else {
|
|
404
|
-
await settings.remove(SETTINGS_KEYS.CUSTOM_CSS);
|
|
405
|
-
}
|
|
406
|
-
|
|
407
|
-
return dsToast("Custom CSS saved successfully.");
|
|
408
|
-
});
|
|
409
|
-
|
|
410
380
|
// ===========================================================================
|
|
411
381
|
// Account
|
|
412
382
|
// ===========================================================================
|
|
413
383
|
|
|
414
384
|
settingsRoutes.get("/account", async (c) => {
|
|
415
|
-
const siteName =
|
|
385
|
+
const siteName = c.var.appConfig.siteName;
|
|
416
386
|
const session = await c.var.auth.api.getSession({
|
|
417
387
|
headers: c.req.raw.headers,
|
|
418
388
|
});
|
|
@@ -433,11 +403,20 @@ settingsRoutes.get("/account", async (c) => {
|
|
|
433
403
|
});
|
|
434
404
|
|
|
435
405
|
settingsRoutes.post("/account", async (c) => {
|
|
406
|
+
const i18n = getI18n(c);
|
|
436
407
|
const body = await c.req.json<{ userName: string }>();
|
|
437
408
|
const name = body.userName?.trim();
|
|
438
409
|
|
|
439
410
|
if (!name) {
|
|
440
|
-
return dsToast(
|
|
411
|
+
return dsToast(
|
|
412
|
+
i18n._(
|
|
413
|
+
msg({
|
|
414
|
+
message: "Name is required.",
|
|
415
|
+
comment: "@context: Error toast when display name is empty",
|
|
416
|
+
}),
|
|
417
|
+
),
|
|
418
|
+
"error",
|
|
419
|
+
);
|
|
441
420
|
}
|
|
442
421
|
|
|
443
422
|
try {
|
|
@@ -446,13 +425,29 @@ settingsRoutes.post("/account", async (c) => {
|
|
|
446
425
|
headers: c.req.raw.headers,
|
|
447
426
|
});
|
|
448
427
|
} catch {
|
|
449
|
-
return dsToast(
|
|
428
|
+
return dsToast(
|
|
429
|
+
i18n._(
|
|
430
|
+
msg({
|
|
431
|
+
message: "Failed to update profile.",
|
|
432
|
+
comment: "@context: Error toast when profile update fails",
|
|
433
|
+
}),
|
|
434
|
+
),
|
|
435
|
+
"error",
|
|
436
|
+
);
|
|
450
437
|
}
|
|
451
438
|
|
|
452
|
-
return dsToast(
|
|
439
|
+
return dsToast(
|
|
440
|
+
i18n._(
|
|
441
|
+
msg({
|
|
442
|
+
message: "Profile saved successfully.",
|
|
443
|
+
comment: "@context: Toast after saving user profile",
|
|
444
|
+
}),
|
|
445
|
+
),
|
|
446
|
+
);
|
|
453
447
|
});
|
|
454
448
|
|
|
455
449
|
settingsRoutes.post("/password", async (c) => {
|
|
450
|
+
const i18n = getI18n(c);
|
|
456
451
|
const body = await c.req.json<{
|
|
457
452
|
currentPassword: string;
|
|
458
453
|
newPassword: string;
|
|
@@ -460,7 +455,16 @@ settingsRoutes.post("/password", async (c) => {
|
|
|
460
455
|
}>();
|
|
461
456
|
|
|
462
457
|
if (body.newPassword !== body.confirmPassword) {
|
|
463
|
-
return dsToast(
|
|
458
|
+
return dsToast(
|
|
459
|
+
i18n._(
|
|
460
|
+
msg({
|
|
461
|
+
message: "Passwords do not match.",
|
|
462
|
+
comment:
|
|
463
|
+
"@context: Error toast when new password and confirmation differ",
|
|
464
|
+
}),
|
|
465
|
+
),
|
|
466
|
+
"error",
|
|
467
|
+
);
|
|
464
468
|
}
|
|
465
469
|
|
|
466
470
|
try {
|
|
@@ -473,11 +477,27 @@ settingsRoutes.post("/password", async (c) => {
|
|
|
473
477
|
headers: c.req.raw.headers,
|
|
474
478
|
});
|
|
475
479
|
} catch {
|
|
476
|
-
return dsToast(
|
|
480
|
+
return dsToast(
|
|
481
|
+
i18n._(
|
|
482
|
+
msg({
|
|
483
|
+
message: "Current password is incorrect.",
|
|
484
|
+
comment:
|
|
485
|
+
"@context: Error toast when current password verification fails",
|
|
486
|
+
}),
|
|
487
|
+
),
|
|
488
|
+
"error",
|
|
489
|
+
);
|
|
477
490
|
}
|
|
478
491
|
|
|
479
492
|
return sse(c, async (stream) => {
|
|
480
|
-
await stream.toast(
|
|
493
|
+
await stream.toast(
|
|
494
|
+
i18n._(
|
|
495
|
+
msg({
|
|
496
|
+
message: "Password changed successfully.",
|
|
497
|
+
comment: "@context: Toast after changing account password",
|
|
498
|
+
}),
|
|
499
|
+
),
|
|
500
|
+
);
|
|
481
501
|
await stream.patchSignals({
|
|
482
502
|
currentPassword: "",
|
|
483
503
|
newPassword: "",
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { describe, it, expect } from "vitest";
|
|
2
2
|
import { Hono } from "hono";
|
|
3
3
|
import type { Bindings } from "../../../types.js";
|
|
4
|
-
import type { AppVariables } from "../../../app.js";
|
|
4
|
+
import type { AppVariables } from "../../../types/app-context.js";
|
|
5
5
|
import { createTestDatabase } from "../../../__tests__/helpers/db.js";
|
|
6
6
|
import { createPostService } from "../../../services/post.js";
|
|
7
7
|
import { createSettingsService } from "../../../services/settings.js";
|
|
8
8
|
import { createMediaService } from "../../../services/media.js";
|
|
9
|
+
import { resolveConfig } from "../../../lib/resolve-config.js";
|
|
9
10
|
import { rssRoutes } from "../rss.js";
|
|
10
11
|
|
|
11
12
|
type Env = { Bindings: Bindings; Variables: AppVariables };
|
|
@@ -22,13 +23,16 @@ function createFeedTestApp(envOverrides: Partial<Bindings> = {}) {
|
|
|
22
23
|
const app = new Hono<Env>();
|
|
23
24
|
|
|
24
25
|
app.use("*", async (c, next) => {
|
|
25
|
-
|
|
26
|
+
const env = {
|
|
26
27
|
SITE_URL: "http://localhost:9019",
|
|
27
28
|
...envOverrides,
|
|
28
29
|
} as Bindings;
|
|
30
|
+
c.env = env;
|
|
29
31
|
|
|
30
32
|
c.set("services", services as AppVariables["services"]);
|
|
31
|
-
|
|
33
|
+
const allSettings = await services.settings.getAll();
|
|
34
|
+
c.set("allSettings", allSettings);
|
|
35
|
+
c.set("appConfig", resolveConfig(env, allSettings));
|
|
32
36
|
await next();
|
|
33
37
|
});
|
|
34
38
|
|