@jant/core 0.3.36 → 0.3.38
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/commands/export.js +1 -1
- package/bin/commands/import-site.js +529 -0
- package/bin/commands/reset-password.js +3 -2
- package/dist/client/assets/heic-to-DIRPI3VF.js +1 -0
- package/dist/client/assets/url-FWFqPJPb.js +1 -0
- package/dist/client/client.css +1 -1
- package/dist/client/client.js +4012 -3276
- package/dist/index.js +10285 -5809
- package/package.json +11 -3
- package/src/__tests__/helpers/app.ts +9 -9
- package/src/__tests__/helpers/db.ts +91 -93
- package/src/app.tsx +157 -27
- package/src/auth.ts +20 -2
- package/src/client/archive-nav.js +187 -0
- package/src/client/audio-player.ts +478 -0
- package/src/client/audio-processor.ts +84 -0
- package/src/client/avatar-upload.ts +3 -2
- package/src/client/components/__tests__/jant-compose-dialog.test.ts +645 -49
- package/src/client/components/__tests__/jant-compose-editor.test.ts +208 -16
- package/src/client/components/__tests__/jant-post-form.test.ts +19 -9
- package/src/client/components/__tests__/jant-settings-avatar.test.ts +2 -2
- package/src/client/components/__tests__/jant-settings-general.test.ts +3 -3
- package/src/client/components/collection-sidebar-types.ts +7 -9
- package/src/client/components/compose-types.ts +101 -4
- package/src/client/components/jant-collection-form.ts +43 -7
- package/src/client/components/jant-collection-sidebar.ts +88 -84
- package/src/client/components/jant-compose-dialog.ts +1655 -219
- package/src/client/components/jant-compose-editor.ts +732 -168
- package/src/client/components/jant-compose-fullscreen.ts +23 -78
- package/src/client/components/jant-media-lightbox.ts +2 -0
- package/src/client/components/jant-nav-manager.ts +24 -284
- package/src/client/components/jant-post-form.ts +89 -9
- package/src/client/components/jant-post-menu.ts +1019 -0
- package/src/client/components/jant-settings-avatar.ts +3 -3
- package/src/client/components/jant-settings-general.ts +38 -4
- package/src/client/components/jant-text-preview.ts +232 -0
- package/src/client/components/nav-manager-types.ts +4 -19
- package/src/client/components/post-form-template.ts +107 -12
- package/src/client/components/post-form-types.ts +11 -4
- package/src/client/compose-bridge.ts +410 -109
- package/src/client/image-processor.ts +26 -8
- package/src/client/media-metadata.ts +247 -0
- package/src/client/multipart-upload.ts +160 -0
- package/src/client/post-form-bridge.ts +52 -1
- package/src/client/settings-bridge.ts +0 -12
- package/src/client/thread-context.ts +140 -0
- package/src/client/tiptap/create-editor.ts +46 -0
- package/src/client/tiptap/extensions.ts +5 -0
- package/src/client/tiptap/image-node.ts +2 -8
- package/src/client/tiptap/paste-image.ts +2 -13
- package/src/client/tiptap/slash-commands.ts +173 -63
- package/src/client/toast.ts +101 -3
- package/src/client/types/sortablejs.d.ts +15 -0
- package/src/client/upload-with-metadata.ts +54 -0
- package/src/client/video-processor.ts +207 -0
- package/src/client.ts +5 -2
- package/src/db/__tests__/migrations.test.ts +118 -0
- package/src/db/index.ts +52 -0
- package/src/db/migrations/0000_baseline.sql +269 -0
- package/src/db/migrations/0001_fts_setup.sql +31 -0
- package/src/db/migrations/meta/0000_snapshot.json +703 -119
- package/src/db/migrations/meta/0001_snapshot.json +1337 -0
- package/src/db/migrations/meta/_journal.json +4 -39
- package/src/db/schema.ts +409 -145
- package/src/i18n/__tests__/detect.test.ts +115 -0
- package/src/i18n/context.tsx +2 -2
- package/src/i18n/detect.ts +85 -1
- package/src/i18n/i18n.ts +1 -1
- package/src/i18n/index.ts +2 -1
- package/src/i18n/locales/en.po +487 -1217
- package/src/i18n/locales/en.ts +1 -1
- package/src/i18n/locales/zh-Hans.po +613 -996
- package/src/i18n/locales/zh-Hans.ts +1 -1
- package/src/i18n/locales/zh-Hant.po +624 -1007
- package/src/i18n/locales/zh-Hant.ts +1 -1
- package/src/i18n/middleware.ts +6 -0
- package/src/index.ts +5 -7
- package/src/lib/__tests__/blurhash-placeholder.test.ts +75 -0
- package/src/lib/__tests__/constants.test.ts +0 -1
- package/src/lib/__tests__/markdown-to-tiptap.test.ts +358 -0
- package/src/lib/__tests__/nanoid.test.ts +26 -0
- package/src/lib/__tests__/schemas.test.ts +181 -63
- package/src/lib/__tests__/slug.test.ts +126 -0
- package/src/lib/__tests__/sse.test.ts +6 -6
- package/src/lib/__tests__/summary.test.ts +264 -0
- package/src/lib/__tests__/theme.test.ts +1 -1
- package/src/lib/__tests__/timeline.test.ts +33 -30
- package/src/lib/__tests__/tiptap-to-markdown.test.ts +346 -0
- package/src/lib/__tests__/view.test.ts +141 -66
- package/src/lib/blurhash-placeholder.ts +102 -0
- package/src/lib/constants.ts +3 -1
- package/src/lib/emoji-catalog.ts +885 -68
- package/src/lib/errors.ts +11 -8
- package/src/lib/feed.ts +78 -32
- package/src/lib/html.ts +2 -1
- package/src/lib/icon-catalog.ts +5033 -1
- package/src/lib/icons.ts +3 -2
- package/src/lib/index.ts +0 -1
- package/src/lib/markdown-to-tiptap.ts +286 -0
- package/src/lib/media-helpers.ts +12 -3
- package/src/lib/nanoid.ts +29 -0
- package/src/lib/navigation.ts +1 -1
- package/src/lib/render.tsx +20 -2
- package/src/lib/resolve-config.ts +6 -2
- package/src/lib/schemas.ts +224 -55
- package/src/lib/search-snippet.ts +34 -0
- package/src/lib/slug.ts +96 -0
- package/src/lib/sse.ts +6 -6
- package/src/lib/storage.ts +115 -7
- package/src/lib/summary.ts +66 -0
- package/src/lib/theme.ts +11 -8
- package/src/lib/timeline.ts +74 -34
- package/src/lib/tiptap-render.ts +5 -10
- package/src/lib/tiptap-to-markdown.ts +305 -0
- package/src/lib/upload.ts +190 -29
- package/src/lib/url.ts +31 -0
- package/src/lib/view.ts +204 -37
- package/src/middleware/__tests__/auth.test.ts +191 -11
- package/src/middleware/__tests__/onboarding.test.ts +12 -10
- package/src/middleware/auth.ts +63 -9
- package/src/middleware/onboarding.ts +1 -1
- package/src/middleware/secure-headers.ts +40 -0
- package/src/preset.css +45 -2
- package/src/routes/__tests__/compose.test.ts +17 -24
- package/src/routes/api/__tests__/collections.test.ts +109 -61
- package/src/routes/api/__tests__/nav-items.test.ts +46 -29
- package/src/routes/api/__tests__/posts.test.ts +132 -68
- package/src/routes/api/__tests__/search.test.ts +15 -2
- package/src/routes/api/__tests__/upload-multipart.test.ts +534 -0
- package/src/routes/api/collections.ts +51 -42
- package/src/routes/api/custom-urls.ts +80 -0
- package/src/routes/api/export.ts +31 -0
- package/src/routes/api/nav-items.ts +23 -19
- package/src/routes/api/posts.ts +43 -39
- package/src/routes/api/search.ts +3 -4
- package/src/routes/api/upload-multipart.ts +245 -0
- package/src/routes/api/upload.ts +85 -19
- package/src/routes/auth/__tests__/setup.test.ts +20 -60
- package/src/routes/auth/setup.tsx +26 -33
- package/src/routes/auth/signin.tsx +3 -7
- package/src/routes/compose.tsx +10 -55
- package/src/routes/dash/__tests__/settings-avatar.test.ts +1 -1
- package/src/routes/dash/custom-urls.tsx +414 -0
- package/src/routes/dash/settings.tsx +304 -232
- package/src/routes/feed/__tests__/rss.test.ts +27 -28
- package/src/routes/feed/rss.ts +6 -4
- package/src/routes/feed/sitemap.ts +2 -12
- package/src/routes/pages/__tests__/collections.test.ts +5 -6
- package/src/routes/pages/__tests__/featured.test.ts +41 -22
- package/src/routes/pages/archive.tsx +175 -39
- package/src/routes/pages/collection.tsx +22 -10
- package/src/routes/pages/collections.tsx +3 -3
- package/src/routes/pages/featured.tsx +28 -4
- package/src/routes/pages/home.tsx +16 -15
- package/src/routes/pages/latest.tsx +1 -11
- package/src/routes/pages/new.tsx +39 -0
- package/src/routes/pages/page.tsx +95 -49
- package/src/routes/pages/search.tsx +1 -1
- package/src/services/__tests__/api-token.test.ts +135 -0
- package/src/services/__tests__/collection.test.ts +275 -227
- package/src/services/__tests__/custom-url.test.ts +213 -0
- package/src/services/__tests__/media.test.ts +162 -22
- package/src/services/__tests__/navigation.test.ts +109 -68
- package/src/services/__tests__/post-timeline.test.ts +205 -32
- package/src/services/__tests__/post.test.ts +713 -234
- package/src/services/__tests__/search.test.ts +67 -10
- package/src/services/api-token.ts +166 -0
- package/src/services/auth.ts +17 -2
- package/src/services/collection.ts +397 -131
- package/src/services/custom-url.ts +188 -0
- package/src/services/export.ts +802 -0
- package/src/services/index.ts +26 -19
- package/src/services/media.ts +100 -22
- package/src/services/navigation.ts +158 -47
- package/src/services/path.ts +339 -0
- package/src/services/post.ts +687 -154
- package/src/services/search.ts +160 -75
- package/src/styles/components.css +58 -7
- package/src/styles/tokens.css +84 -6
- package/src/styles/ui.css +2964 -457
- package/src/types/bindings.ts +4 -1
- package/src/types/config.ts +12 -4
- package/src/types/constants.ts +15 -3
- package/src/types/entities.ts +74 -35
- package/src/types/operations.ts +11 -24
- package/src/types/props.ts +51 -16
- package/src/types/views.ts +45 -22
- package/src/ui/color-themes.ts +133 -23
- package/src/ui/compose/ComposeDialog.tsx +239 -17
- package/src/ui/compose/ComposePrompt.tsx +1 -1
- package/src/ui/dash/CrudPageHeader.tsx +1 -1
- package/src/ui/dash/ListItemRow.tsx +1 -1
- package/src/ui/dash/StatusBadge.tsx +3 -1
- package/src/ui/dash/appearance/AdvancedContent.tsx +22 -1
- package/src/ui/dash/appearance/ColorThemeContent.tsx +22 -2
- package/src/ui/dash/appearance/FontThemeContent.tsx +1 -1
- package/src/ui/dash/appearance/NavigationContent.tsx +5 -45
- package/src/ui/dash/index.ts +0 -3
- package/src/ui/dash/settings/AccountContent.tsx +3 -57
- package/src/ui/dash/settings/AccountMenuContent.tsx +147 -0
- package/src/ui/dash/settings/ApiTokensContent.tsx +232 -0
- package/src/ui/dash/settings/AvatarContent.tsx +8 -0
- package/src/ui/dash/settings/SessionsContent.tsx +159 -0
- package/src/ui/dash/settings/SettingsRootContent.tsx +45 -15
- package/src/ui/feed/LinkCard.tsx +89 -40
- package/src/ui/feed/NoteCard.tsx +39 -25
- package/src/ui/feed/PostStatusBadges.tsx +67 -0
- package/src/ui/feed/QuoteCard.tsx +38 -23
- package/src/ui/feed/ThreadPreview.tsx +90 -26
- package/src/ui/feed/TimelineFeed.tsx +3 -2
- package/src/ui/feed/TimelineItem.tsx +15 -6
- package/src/ui/feed/__tests__/thread-preview.test.ts +107 -0
- package/src/ui/feed/thread-preview-state.ts +61 -0
- package/src/ui/font-themes.ts +2 -2
- package/src/ui/layouts/BaseLayout.tsx +2 -2
- package/src/ui/layouts/SiteLayout.tsx +105 -92
- package/src/ui/pages/ArchivePage.tsx +923 -98
- package/src/ui/pages/ComposePage.tsx +54 -0
- package/src/ui/pages/PostPage.tsx +30 -45
- package/src/ui/pages/SearchPage.tsx +181 -37
- package/src/ui/shared/AdminBreadcrumb.tsx +29 -0
- package/src/ui/shared/CollectionsSidebar.tsx +47 -37
- package/src/ui/shared/MediaGallery.tsx +445 -149
- package/src/ui/shared/PostFooter.tsx +204 -0
- package/src/ui/shared/StarRating.tsx +27 -0
- package/src/ui/shared/__tests__/format-chars.test.ts +35 -0
- package/src/ui/shared/index.ts +0 -1
- package/dist/client/assets/url-8Dj-5CLW.js +0 -1
- package/src/client/media-upload.ts +0 -161
- package/src/client/page-slug-bridge.ts +0 -42
- package/src/db/migrations/0000_square_wallflower.sql +0 -118
- package/src/db/migrations/0001_add_search_fts.sql +0 -34
- package/src/db/migrations/0002_add_media_attachments.sql +0 -3
- package/src/db/migrations/0003_add_navigation_links.sql +0 -8
- package/src/db/migrations/0004_add_storage_provider.sql +0 -3
- package/src/db/migrations/0005_v2_schema_migration.sql +0 -268
- package/src/db/migrations/0006_rename_slug_to_path.sql +0 -5
- package/src/db/migrations/0007_post_collections_m2m.sql +0 -94
- package/src/db/migrations/0008_add_collection_dividers.sql +0 -8
- package/src/db/migrations/0009_drop_collection_show_divider.sql +0 -2
- package/src/db/migrations/0010_add_performance_indexes.sql +0 -16
- package/src/db/migrations/0011_add_path_registry.sql +0 -23
- package/src/db/migrations/0012_add_tiptap_columns.sql +0 -2
- package/src/db/migrations/0013_replace_featured_with_visibility.sql +0 -8
- package/src/db/migrations/meta/0003_snapshot.json +0 -821
- package/src/lib/__tests__/sqid.test.ts +0 -65
- package/src/lib/sqid.ts +0 -79
- package/src/routes/api/__tests__/pages.test.ts +0 -218
- package/src/routes/api/pages.ts +0 -73
- package/src/routes/dash/__tests__/pages.test.ts +0 -226
- package/src/routes/dash/index.tsx +0 -109
- package/src/routes/dash/media.tsx +0 -135
- package/src/routes/dash/pages.tsx +0 -245
- package/src/routes/dash/posts.tsx +0 -338
- package/src/routes/dash/redirects.tsx +0 -263
- package/src/routes/pages/post.tsx +0 -59
- package/src/services/__tests__/page.test.ts +0 -298
- package/src/services/__tests__/path-registry.test.ts +0 -165
- package/src/services/__tests__/redirect.test.ts +0 -159
- package/src/services/page.ts +0 -216
- package/src/services/path-registry.ts +0 -160
- package/src/services/redirect.ts +0 -97
- package/src/ui/dash/PageForm.tsx +0 -187
- package/src/ui/dash/PostList.tsx +0 -95
- package/src/ui/dash/media/MediaListContent.tsx +0 -206
- package/src/ui/dash/media/ViewMediaContent.tsx +0 -208
- package/src/ui/dash/pages/PagesContent.tsx +0 -75
- package/src/ui/dash/posts/PostForm.tsx +0 -260
- package/src/ui/layouts/DashLayout.tsx +0 -247
- package/src/ui/pages/SinglePage.tsx +0 -23
- package/src/ui/shared/ThreadView.tsx +0 -136
|
@@ -1,18 +1,20 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Settings Routes
|
|
3
3
|
*
|
|
4
4
|
* Unified settings hub — root page with iOS-style grouped list,
|
|
5
5
|
* plus sub-pages for General, Avatar, Navigation, Color Theme,
|
|
6
|
-
* Font Theme, Custom CSS, and
|
|
6
|
+
* Font Theme, Custom CSS, Account (Sessions + Password), and API Tokens.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import { Hono } from "hono";
|
|
10
10
|
import { msg } from "@lingui/core/macro";
|
|
11
11
|
import type { Bindings } from "../../types.js";
|
|
12
12
|
import type { AppVariables } from "../../types/app-context.js";
|
|
13
|
-
import { DashLayout } from "../../ui/layouts/DashLayout.js";
|
|
14
13
|
import { sse, dsRedirect, dsToast } from "../../lib/sse.js";
|
|
15
14
|
import { getI18n } from "../../i18n/index.js";
|
|
15
|
+
import { renderPublicPage } from "../../lib/render.js";
|
|
16
|
+
import { getNavigationData } from "../../lib/navigation.js";
|
|
17
|
+
import { AdminBreadcrumb } from "../../ui/shared/AdminBreadcrumb.js";
|
|
16
18
|
import { TIMEZONES } from "../../lib/timezones.js";
|
|
17
19
|
import { escapeHtml } from "../../lib/html.js";
|
|
18
20
|
import { ValidationError } from "../../lib/errors.js";
|
|
@@ -22,11 +24,17 @@ import { BUILTIN_FONT_THEMES } from "../../ui/font-themes.js";
|
|
|
22
24
|
import { SettingsRootContent } from "../../ui/dash/settings/SettingsRootContent.js";
|
|
23
25
|
import { GeneralContent } from "../../ui/dash/settings/GeneralContent.js";
|
|
24
26
|
import { AvatarContent } from "../../ui/dash/settings/AvatarContent.js";
|
|
27
|
+
import { AccountMenuContent } from "../../ui/dash/settings/AccountMenuContent.js";
|
|
25
28
|
import { AccountContent } from "../../ui/dash/settings/AccountContent.js";
|
|
29
|
+
import {
|
|
30
|
+
SessionsContent,
|
|
31
|
+
type SessionInfo,
|
|
32
|
+
} from "../../ui/dash/settings/SessionsContent.js";
|
|
26
33
|
import { NavigationContent } from "../../ui/dash/appearance/NavigationContent.js";
|
|
27
34
|
import { ColorThemeContent } from "../../ui/dash/appearance/ColorThemeContent.js";
|
|
28
35
|
import { FontThemeContent } from "../../ui/dash/appearance/FontThemeContent.js";
|
|
29
36
|
import { AdvancedContent } from "../../ui/dash/appearance/AdvancedContent.js";
|
|
37
|
+
import { ApiTokensContent } from "../../ui/dash/settings/ApiTokensContent.js";
|
|
30
38
|
|
|
31
39
|
type Env = { Bindings: Bindings; Variables: AppVariables };
|
|
32
40
|
|
|
@@ -37,19 +45,13 @@ export const settingsRoutes = new Hono<Env>();
|
|
|
37
45
|
// ===========================================================================
|
|
38
46
|
|
|
39
47
|
settingsRoutes.get("/", async (c) => {
|
|
40
|
-
const
|
|
41
|
-
|
|
42
|
-
return c
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
siteAvatarUrl={c.var.appConfig.siteAvatarUrl}
|
|
48
|
-
currentPath="/dash/settings"
|
|
49
|
-
>
|
|
50
|
-
<SettingsRootContent />
|
|
51
|
-
</DashLayout>,
|
|
52
|
-
);
|
|
48
|
+
const navData = await getNavigationData(c);
|
|
49
|
+
|
|
50
|
+
return renderPublicPage(c, {
|
|
51
|
+
title: `Settings - ${navData.siteName}`,
|
|
52
|
+
navData,
|
|
53
|
+
content: <SettingsRootContent />,
|
|
54
|
+
});
|
|
53
55
|
});
|
|
54
56
|
|
|
55
57
|
// ===========================================================================
|
|
@@ -63,34 +65,33 @@ settingsRoutes.get("/general", async (c) => {
|
|
|
63
65
|
const dbSiteDescription = allSettings["SITE_DESCRIPTION"] ?? "";
|
|
64
66
|
|
|
65
67
|
const saved = c.req.query("saved") !== undefined;
|
|
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
|
-
|
|
93
|
-
);
|
|
68
|
+
const navData = await getNavigationData(c);
|
|
69
|
+
|
|
70
|
+
return renderPublicPage(c, {
|
|
71
|
+
title: `General - ${navData.siteName}`,
|
|
72
|
+
navData,
|
|
73
|
+
toast: saved ? { message: "Settings updated." } : undefined,
|
|
74
|
+
content: (
|
|
75
|
+
<>
|
|
76
|
+
<AdminBreadcrumb
|
|
77
|
+
parent="Settings"
|
|
78
|
+
parentHref="/settings"
|
|
79
|
+
current="General"
|
|
80
|
+
/>
|
|
81
|
+
<GeneralContent
|
|
82
|
+
siteName={dbSiteName || ""}
|
|
83
|
+
siteDescription={dbSiteDescription || ""}
|
|
84
|
+
siteLanguage={appConfig.siteLanguage}
|
|
85
|
+
siteNameFallback={appConfig.fallbacks.siteName}
|
|
86
|
+
siteDescriptionFallback={appConfig.fallbacks.siteDescription}
|
|
87
|
+
timeZone={appConfig.timeZone}
|
|
88
|
+
siteFooter={appConfig.siteFooter}
|
|
89
|
+
noindex={appConfig.noindex}
|
|
90
|
+
timezones={TIMEZONES}
|
|
91
|
+
/>
|
|
92
|
+
</>
|
|
93
|
+
),
|
|
94
|
+
});
|
|
94
95
|
});
|
|
95
96
|
|
|
96
97
|
settingsRoutes.post("/general", async (c) => {
|
|
@@ -111,35 +112,28 @@ settingsRoutes.post("/general", async (c) => {
|
|
|
111
112
|
fallbackSiteName: c.var.appConfig.fallbacks.siteName,
|
|
112
113
|
});
|
|
113
114
|
|
|
115
|
+
// Sync user.name with site name (better-auth requires this field)
|
|
116
|
+
await c.var.auth.api.updateUser({
|
|
117
|
+
body: { name: displayName },
|
|
118
|
+
headers: c.req.raw.headers,
|
|
119
|
+
});
|
|
120
|
+
|
|
114
121
|
// ── JSON response mode (used by Lit settings bridge) ──────────────
|
|
122
|
+
// Always redirect — site name appears in the header/title and a full
|
|
123
|
+
// reload is the simplest way to keep everything in sync.
|
|
115
124
|
const wantsJson = c.req.header("accept")?.includes("application/json");
|
|
116
125
|
if (wantsJson) {
|
|
117
|
-
if (languageChanged) {
|
|
118
|
-
return c.json({
|
|
119
|
-
status: "redirect" as const,
|
|
120
|
-
url: "/dash/settings/general?saved",
|
|
121
|
-
});
|
|
122
|
-
}
|
|
123
126
|
return c.json({
|
|
124
|
-
status: "
|
|
125
|
-
|
|
126
|
-
msg({
|
|
127
|
-
message: "Settings updated.",
|
|
128
|
-
comment: "@context: Toast after saving general settings",
|
|
129
|
-
}),
|
|
130
|
-
),
|
|
131
|
-
siteName: displayName,
|
|
127
|
+
status: "redirect" as const,
|
|
128
|
+
url: "/settings/general?saved",
|
|
132
129
|
});
|
|
133
130
|
}
|
|
134
131
|
|
|
135
132
|
return sse(c, async (stream) => {
|
|
136
133
|
if (languageChanged) {
|
|
137
|
-
await stream.redirect("/
|
|
134
|
+
await stream.redirect("/settings/general?saved");
|
|
138
135
|
} else {
|
|
139
136
|
const escaped = escapeHtml(displayName);
|
|
140
|
-
await stream.patchElements(
|
|
141
|
-
`<a id="site-name" href="/dash" class="font-semibold">${escaped}</a>`,
|
|
142
|
-
);
|
|
143
137
|
await stream.patchElements(`General - ${escaped}`, {
|
|
144
138
|
mode: "inner",
|
|
145
139
|
selector: "title",
|
|
@@ -213,29 +207,27 @@ settingsRoutes.post("/general/seo", async (c) => {
|
|
|
213
207
|
// ===========================================================================
|
|
214
208
|
|
|
215
209
|
settingsRoutes.get("/avatar", async (c) => {
|
|
216
|
-
const siteName = c.var.appConfig.siteName;
|
|
217
210
|
const saved = c.req.query("saved") !== undefined;
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
);
|
|
211
|
+
const navData = await getNavigationData(c);
|
|
212
|
+
|
|
213
|
+
return renderPublicPage(c, {
|
|
214
|
+
title: `Avatar - ${navData.siteName}`,
|
|
215
|
+
navData,
|
|
216
|
+
toast: saved ? { message: "Avatar updated." } : undefined,
|
|
217
|
+
content: (
|
|
218
|
+
<>
|
|
219
|
+
<AdminBreadcrumb
|
|
220
|
+
parent="Settings"
|
|
221
|
+
parentHref="/settings"
|
|
222
|
+
current="Avatar"
|
|
223
|
+
/>
|
|
224
|
+
<AvatarContent
|
|
225
|
+
siteAvatarUrl={c.var.appConfig.siteAvatarUrl}
|
|
226
|
+
showHeaderAvatar={c.var.appConfig.showHeaderAvatar}
|
|
227
|
+
/>
|
|
228
|
+
</>
|
|
229
|
+
),
|
|
230
|
+
});
|
|
239
231
|
});
|
|
240
232
|
|
|
241
233
|
settingsRoutes.post("/avatar", async (c) => {
|
|
@@ -287,7 +279,7 @@ settingsRoutes.post("/avatar", async (c) => {
|
|
|
287
279
|
},
|
|
288
280
|
);
|
|
289
281
|
|
|
290
|
-
return dsRedirect("/
|
|
282
|
+
return dsRedirect("/settings/avatar?saved");
|
|
291
283
|
} catch (e) {
|
|
292
284
|
if (e instanceof ValidationError) {
|
|
293
285
|
return dsToast(e.message, "error");
|
|
@@ -312,11 +304,11 @@ settingsRoutes.post("/avatar/remove", async (c) => {
|
|
|
312
304
|
if (wantsJson) {
|
|
313
305
|
return c.json({
|
|
314
306
|
status: "redirect" as const,
|
|
315
|
-
url: "/
|
|
307
|
+
url: "/settings/avatar?saved",
|
|
316
308
|
});
|
|
317
309
|
}
|
|
318
310
|
|
|
319
|
-
return dsRedirect("/
|
|
311
|
+
return dsRedirect("/settings/avatar?saved");
|
|
320
312
|
});
|
|
321
313
|
|
|
322
314
|
settingsRoutes.post("/avatar/display", async (c) => {
|
|
@@ -365,36 +357,30 @@ settingsRoutes.post("/avatar/display", async (c) => {
|
|
|
365
357
|
// ===========================================================================
|
|
366
358
|
|
|
367
359
|
settingsRoutes.get("/navigation", async (c) => {
|
|
368
|
-
const
|
|
369
|
-
c.var.services.navItems.list(),
|
|
370
|
-
c.var.services.pages.listNotInNav(),
|
|
371
|
-
]);
|
|
372
|
-
const siteName = c.var.appConfig.siteName;
|
|
360
|
+
const navItems = await c.var.services.navItems.list();
|
|
373
361
|
const headerNavMaxVisible = c.var.appConfig.headerNavMaxVisible;
|
|
374
362
|
const homeDefaultView = c.var.appConfig.homeDefaultView;
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
</DashLayout>,
|
|
397
|
-
);
|
|
363
|
+
const navData = await getNavigationData(c);
|
|
364
|
+
|
|
365
|
+
return renderPublicPage(c, {
|
|
366
|
+
title: `Navigation - ${navData.siteName}`,
|
|
367
|
+
navData,
|
|
368
|
+
content: (
|
|
369
|
+
<>
|
|
370
|
+
<AdminBreadcrumb
|
|
371
|
+
parent="Settings"
|
|
372
|
+
parentHref="/settings"
|
|
373
|
+
current="Navigation"
|
|
374
|
+
/>
|
|
375
|
+
<NavigationContent
|
|
376
|
+
navItems={navItems}
|
|
377
|
+
headerNavMaxVisible={headerNavMaxVisible}
|
|
378
|
+
homeDefaultView={homeDefaultView}
|
|
379
|
+
siteName={navData.siteName}
|
|
380
|
+
/>
|
|
381
|
+
</>
|
|
382
|
+
),
|
|
383
|
+
});
|
|
398
384
|
});
|
|
399
385
|
|
|
400
386
|
settingsRoutes.post("/navigation/nav-max-visible", async (c) => {
|
|
@@ -429,30 +415,28 @@ settingsRoutes.post("/navigation/home-default-view", async (c) => {
|
|
|
429
415
|
// ===========================================================================
|
|
430
416
|
|
|
431
417
|
settingsRoutes.get("/color-theme", async (c) => {
|
|
432
|
-
const siteName = c.var.appConfig.siteName;
|
|
433
418
|
const defaultThemeId = c.var.appConfig.fallbacks.defaultTheme;
|
|
434
419
|
const currentThemeId =
|
|
435
420
|
c.var.allSettings[SETTINGS_KEYS.THEME] ?? defaultThemeId;
|
|
436
421
|
const themes = getAvailableThemes();
|
|
437
422
|
const saved = c.req.query("saved") !== undefined;
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
);
|
|
423
|
+
const navData = await getNavigationData(c);
|
|
424
|
+
|
|
425
|
+
return renderPublicPage(c, {
|
|
426
|
+
title: `Color Theme - ${navData.siteName}`,
|
|
427
|
+
navData,
|
|
428
|
+
toast: saved ? { message: "Theme updated." } : undefined,
|
|
429
|
+
content: (
|
|
430
|
+
<>
|
|
431
|
+
<AdminBreadcrumb
|
|
432
|
+
parent="Settings"
|
|
433
|
+
parentHref="/settings"
|
|
434
|
+
current="Color Theme"
|
|
435
|
+
/>
|
|
436
|
+
<ColorThemeContent themes={themes} currentThemeId={currentThemeId} />
|
|
437
|
+
</>
|
|
438
|
+
),
|
|
439
|
+
});
|
|
456
440
|
});
|
|
457
441
|
|
|
458
442
|
settingsRoutes.post("/color-theme", async (c) => {
|
|
@@ -481,7 +465,7 @@ settingsRoutes.post("/color-theme", async (c) => {
|
|
|
481
465
|
await settings.set(SETTINGS_KEYS.THEME, validTheme.id);
|
|
482
466
|
}
|
|
483
467
|
|
|
484
|
-
return dsRedirect("/
|
|
468
|
+
return dsRedirect("/settings/color-theme?saved");
|
|
485
469
|
});
|
|
486
470
|
|
|
487
471
|
// ===========================================================================
|
|
@@ -489,30 +473,28 @@ settingsRoutes.post("/color-theme", async (c) => {
|
|
|
489
473
|
// ===========================================================================
|
|
490
474
|
|
|
491
475
|
settingsRoutes.get("/font-theme", async (c) => {
|
|
492
|
-
const siteName = c.var.appConfig.siteName;
|
|
493
476
|
const currentFontThemeId = c.var.allSettings["FONT_THEME"] ?? "default";
|
|
494
477
|
const saved = c.req.query("saved") !== undefined;
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
);
|
|
478
|
+
const navData = await getNavigationData(c);
|
|
479
|
+
|
|
480
|
+
return renderPublicPage(c, {
|
|
481
|
+
title: `Font Theme - ${navData.siteName}`,
|
|
482
|
+
navData,
|
|
483
|
+
toast: saved ? { message: "Font theme updated." } : undefined,
|
|
484
|
+
content: (
|
|
485
|
+
<>
|
|
486
|
+
<AdminBreadcrumb
|
|
487
|
+
parent="Settings"
|
|
488
|
+
parentHref="/settings"
|
|
489
|
+
current="Font Theme"
|
|
490
|
+
/>
|
|
491
|
+
<FontThemeContent
|
|
492
|
+
fontThemes={BUILTIN_FONT_THEMES}
|
|
493
|
+
currentFontThemeId={currentFontThemeId}
|
|
494
|
+
/>
|
|
495
|
+
</>
|
|
496
|
+
),
|
|
497
|
+
});
|
|
516
498
|
});
|
|
517
499
|
|
|
518
500
|
settingsRoutes.post("/font-theme", async (c) => {
|
|
@@ -540,7 +522,7 @@ settingsRoutes.post("/font-theme", async (c) => {
|
|
|
540
522
|
await settings.set("FONT_THEME", validFont.id);
|
|
541
523
|
}
|
|
542
524
|
|
|
543
|
-
return dsRedirect("/
|
|
525
|
+
return dsRedirect("/settings/font-theme?saved");
|
|
544
526
|
});
|
|
545
527
|
|
|
546
528
|
// ===========================================================================
|
|
@@ -548,25 +530,23 @@ settingsRoutes.post("/font-theme", async (c) => {
|
|
|
548
530
|
// ===========================================================================
|
|
549
531
|
|
|
550
532
|
settingsRoutes.get("/custom-css", async (c) => {
|
|
551
|
-
const siteName = c.var.appConfig.siteName;
|
|
552
533
|
const customCSS = c.var.allSettings[SETTINGS_KEYS.CUSTOM_CSS] ?? "";
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
);
|
|
534
|
+
const navData = await getNavigationData(c);
|
|
535
|
+
|
|
536
|
+
return renderPublicPage(c, {
|
|
537
|
+
title: `Custom CSS - ${navData.siteName}`,
|
|
538
|
+
navData,
|
|
539
|
+
content: (
|
|
540
|
+
<>
|
|
541
|
+
<AdminBreadcrumb
|
|
542
|
+
parent="Settings"
|
|
543
|
+
parentHref="/settings"
|
|
544
|
+
current="Custom CSS"
|
|
545
|
+
/>
|
|
546
|
+
<AdvancedContent customCSS={customCSS} />
|
|
547
|
+
</>
|
|
548
|
+
),
|
|
549
|
+
});
|
|
570
550
|
});
|
|
571
551
|
|
|
572
552
|
settingsRoutes.post("/custom-css", async (c) => {
|
|
@@ -593,81 +573,123 @@ settingsRoutes.post("/custom-css", async (c) => {
|
|
|
593
573
|
});
|
|
594
574
|
|
|
595
575
|
// ===========================================================================
|
|
596
|
-
// Account
|
|
576
|
+
// Account sub-menu
|
|
597
577
|
// ===========================================================================
|
|
598
578
|
|
|
599
579
|
settingsRoutes.get("/account", async (c) => {
|
|
600
|
-
const
|
|
601
|
-
|
|
580
|
+
const navData = await getNavigationData(c);
|
|
581
|
+
|
|
582
|
+
return renderPublicPage(c, {
|
|
583
|
+
title: `Account - ${navData.siteName}`,
|
|
584
|
+
navData,
|
|
585
|
+
content: (
|
|
586
|
+
<>
|
|
587
|
+
<AdminBreadcrumb
|
|
588
|
+
parent="Settings"
|
|
589
|
+
parentHref="/settings"
|
|
590
|
+
current="Account"
|
|
591
|
+
/>
|
|
592
|
+
<AccountMenuContent />
|
|
593
|
+
</>
|
|
594
|
+
),
|
|
595
|
+
});
|
|
596
|
+
});
|
|
597
|
+
|
|
598
|
+
// ===========================================================================
|
|
599
|
+
// Sessions
|
|
600
|
+
// ===========================================================================
|
|
601
|
+
|
|
602
|
+
settingsRoutes.get("/account/sessions", async (c) => {
|
|
603
|
+
const navData = await getNavigationData(c);
|
|
604
|
+
|
|
605
|
+
// Get current session to mark it
|
|
606
|
+
const currentSession = await c.var.auth.api.getSession({
|
|
607
|
+
headers: c.req.raw.headers,
|
|
608
|
+
});
|
|
609
|
+
const currentToken = currentSession?.session?.token ?? "";
|
|
610
|
+
|
|
611
|
+
// List all active sessions
|
|
612
|
+
const rawSessions = await c.var.auth.api.listSessions({
|
|
602
613
|
headers: c.req.raw.headers,
|
|
603
614
|
});
|
|
604
|
-
const userName = session?.user?.name ?? "";
|
|
605
|
-
const saved = c.req.query("saved") !== undefined;
|
|
606
615
|
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
>
|
|
621
|
-
<AccountContent userName={userName} />
|
|
622
|
-
</DashLayout>,
|
|
616
|
+
const sessions: SessionInfo[] = (rawSessions ?? []).map(
|
|
617
|
+
(s: {
|
|
618
|
+
token: string;
|
|
619
|
+
ipAddress?: string | null;
|
|
620
|
+
userAgent?: string | null;
|
|
621
|
+
createdAt: Date;
|
|
622
|
+
}) => ({
|
|
623
|
+
token: s.token,
|
|
624
|
+
ipAddress: s.ipAddress ?? null,
|
|
625
|
+
userAgent: s.userAgent ?? null,
|
|
626
|
+
createdAt: Math.floor(new Date(s.createdAt).getTime() / 1000),
|
|
627
|
+
isCurrent: s.token === currentToken,
|
|
628
|
+
}),
|
|
623
629
|
);
|
|
624
|
-
});
|
|
625
630
|
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
631
|
+
// Sort: current session first, then by creation date descending
|
|
632
|
+
sessions.sort((a, b) => {
|
|
633
|
+
if (a.isCurrent) return -1;
|
|
634
|
+
if (b.isCurrent) return 1;
|
|
635
|
+
return b.createdAt - a.createdAt;
|
|
636
|
+
});
|
|
630
637
|
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
638
|
+
return renderPublicPage(c, {
|
|
639
|
+
title: `Sessions - ${navData.siteName}`,
|
|
640
|
+
navData,
|
|
641
|
+
content: (
|
|
642
|
+
<>
|
|
643
|
+
<AdminBreadcrumb
|
|
644
|
+
parent="Account"
|
|
645
|
+
parentHref="/settings/account"
|
|
646
|
+
current="Sessions"
|
|
647
|
+
/>
|
|
648
|
+
<SessionsContent sessions={sessions} />
|
|
649
|
+
</>
|
|
650
|
+
),
|
|
651
|
+
});
|
|
652
|
+
});
|
|
653
|
+
|
|
654
|
+
settingsRoutes.post("/account/sessions/:token/revoke", async (c) => {
|
|
655
|
+
const token = c.req.param("token");
|
|
642
656
|
|
|
643
657
|
try {
|
|
644
|
-
await c.var.auth.api.
|
|
645
|
-
body: {
|
|
658
|
+
await c.var.auth.api.revokeSession({
|
|
659
|
+
body: { token },
|
|
646
660
|
headers: c.req.raw.headers,
|
|
647
661
|
});
|
|
648
662
|
} catch {
|
|
649
|
-
|
|
650
|
-
i18n._(
|
|
651
|
-
msg({
|
|
652
|
-
message: "Couldn't update your profile. Try again in a moment.",
|
|
653
|
-
comment: "@context: Error toast when profile update fails",
|
|
654
|
-
}),
|
|
655
|
-
),
|
|
656
|
-
"error",
|
|
657
|
-
);
|
|
663
|
+
// Session may already be expired/revoked — still redirect
|
|
658
664
|
}
|
|
659
665
|
|
|
660
|
-
return
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
+
return dsRedirect("/settings/account/sessions");
|
|
667
|
+
});
|
|
668
|
+
|
|
669
|
+
// ===========================================================================
|
|
670
|
+
// Password
|
|
671
|
+
// ===========================================================================
|
|
672
|
+
|
|
673
|
+
settingsRoutes.get("/account/password", async (c) => {
|
|
674
|
+
const navData = await getNavigationData(c);
|
|
675
|
+
|
|
676
|
+
return renderPublicPage(c, {
|
|
677
|
+
title: `Password - ${navData.siteName}`,
|
|
678
|
+
navData,
|
|
679
|
+
content: (
|
|
680
|
+
<>
|
|
681
|
+
<AdminBreadcrumb
|
|
682
|
+
parent="Account"
|
|
683
|
+
parentHref="/settings/account"
|
|
684
|
+
current="Password"
|
|
685
|
+
/>
|
|
686
|
+
<AccountContent />
|
|
687
|
+
</>
|
|
666
688
|
),
|
|
667
|
-
);
|
|
689
|
+
});
|
|
668
690
|
});
|
|
669
691
|
|
|
670
|
-
settingsRoutes.post("/password", async (c) => {
|
|
692
|
+
settingsRoutes.post("/account/password", async (c) => {
|
|
671
693
|
const i18n = getI18n(c);
|
|
672
694
|
const body = await c.req.json<{
|
|
673
695
|
currentPassword: string;
|
|
@@ -727,3 +749,53 @@ settingsRoutes.post("/password", async (c) => {
|
|
|
727
749
|
});
|
|
728
750
|
});
|
|
729
751
|
});
|
|
752
|
+
|
|
753
|
+
// ===========================================================================
|
|
754
|
+
// API Tokens
|
|
755
|
+
// ===========================================================================
|
|
756
|
+
|
|
757
|
+
settingsRoutes.get("/api-tokens", async (c) => {
|
|
758
|
+
const tokens = await c.var.services.apiTokens.list();
|
|
759
|
+
const navData = await getNavigationData(c);
|
|
760
|
+
const siteUrl = c.env.SITE_URL;
|
|
761
|
+
|
|
762
|
+
return renderPublicPage(c, {
|
|
763
|
+
title: `API Tokens - ${navData.siteName}`,
|
|
764
|
+
navData,
|
|
765
|
+
content: (
|
|
766
|
+
<>
|
|
767
|
+
<AdminBreadcrumb
|
|
768
|
+
parent="Settings"
|
|
769
|
+
parentHref="/settings"
|
|
770
|
+
current="API Tokens"
|
|
771
|
+
/>
|
|
772
|
+
<ApiTokensContent tokens={tokens} siteUrl={siteUrl} />
|
|
773
|
+
</>
|
|
774
|
+
),
|
|
775
|
+
});
|
|
776
|
+
});
|
|
777
|
+
|
|
778
|
+
settingsRoutes.post("/api-tokens", async (c) => {
|
|
779
|
+
const body = await c.req.json<{ tokenName: string }>();
|
|
780
|
+
const name = body.tokenName?.trim();
|
|
781
|
+
|
|
782
|
+
if (!name) {
|
|
783
|
+
return dsToast("Token name is required.", "error");
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
const { plaintext } = await c.var.services.apiTokens.create(name);
|
|
787
|
+
|
|
788
|
+
return sse(c, async (stream) => {
|
|
789
|
+
await stream.patchSignals({
|
|
790
|
+
_newPlaintext: plaintext,
|
|
791
|
+
tokenName: "",
|
|
792
|
+
});
|
|
793
|
+
});
|
|
794
|
+
});
|
|
795
|
+
|
|
796
|
+
settingsRoutes.post("/api-tokens/:id/delete", async (c) => {
|
|
797
|
+
const id = c.req.param("id");
|
|
798
|
+
await c.var.services.apiTokens.delete(id);
|
|
799
|
+
|
|
800
|
+
return dsRedirect("/settings/api-tokens");
|
|
801
|
+
});
|