@jant/core 0.3.35 → 0.3.37
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/module-RjUF93sV.js +716 -0
- package/dist/client/assets/native-48B9X9Wg.js +1 -0
- package/dist/client/assets/url-FWFqPJPb.js +1 -0
- package/dist/client/client.css +1 -1
- package/dist/client/client.js +4564 -3013
- package/dist/index.js +12885 -8161
- package/package.json +23 -6
- package/src/__tests__/helpers/app.ts +10 -10
- package/src/__tests__/helpers/db.ts +91 -87
- package/src/app.tsx +157 -31
- 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/{lib → client}/avatar-upload.ts +4 -3
- package/src/{lib → client}/collection-form-bridge.ts +2 -2
- package/src/{ui → client}/components/__tests__/jant-collection-form.test.ts +26 -9
- package/src/client/components/__tests__/jant-compose-dialog.test.ts +1140 -0
- package/src/client/components/__tests__/jant-compose-editor.test.ts +504 -0
- package/src/{ui → client}/components/__tests__/jant-post-form.test.ts +37 -17
- package/src/{ui → client}/components/__tests__/jant-settings-avatar.test.ts +2 -2
- package/src/{ui → client}/components/__tests__/jant-settings-general.test.ts +3 -3
- package/src/client/components/collection-sidebar-types.ts +43 -0
- package/src/{ui → client}/components/collection-types.ts +3 -4
- package/src/client/components/compose-types.ts +174 -0
- package/src/client/components/jant-collection-form.ts +667 -0
- package/src/client/components/jant-collection-sidebar.ts +805 -0
- package/src/client/components/jant-compose-dialog.ts +2161 -0
- package/src/client/components/jant-compose-editor.ts +1813 -0
- package/src/client/components/jant-compose-fullscreen.ts +283 -0
- package/src/client/components/jant-media-lightbox.ts +259 -0
- package/src/{ui → client}/components/jant-nav-manager.ts +97 -298
- package/src/{ui → client}/components/jant-post-form.ts +141 -12
- package/src/client/components/jant-post-menu.ts +1019 -0
- package/src/{ui → client}/components/jant-settings-avatar.ts +3 -3
- package/src/{ui → client}/components/jant-settings-general.ts +38 -4
- package/src/client/components/jant-text-preview.ts +232 -0
- package/src/{ui → client}/components/nav-manager-types.ts +6 -18
- package/src/{ui → client}/components/post-form-template.ts +137 -38
- package/src/{ui → client}/components/post-form-types.ts +15 -4
- package/src/client/compose-bridge.ts +583 -0
- package/src/{lib → client}/image-processor.ts +26 -8
- package/src/client/lazy-slugify.ts +51 -0
- package/src/client/media-metadata.ts +247 -0
- package/src/client/multipart-upload.ts +160 -0
- package/src/{lib → client}/nav-manager-bridge.ts +1 -1
- package/src/{lib → client}/post-form-bridge.ts +53 -2
- package/src/{lib → client}/settings-bridge.ts +3 -15
- package/src/client/thread-context.ts +140 -0
- package/src/client/tiptap/bubble-menu.ts +205 -0
- package/src/client/tiptap/create-editor.ts +86 -0
- package/src/client/tiptap/exitable-marks.ts +73 -0
- package/src/client/tiptap/extensions.ts +65 -0
- package/src/client/tiptap/image-node.ts +482 -0
- package/src/client/tiptap/link-toolbar.ts +371 -0
- package/src/client/tiptap/more-break.ts +50 -0
- package/src/client/tiptap/paste-image.ts +129 -0
- package/src/client/tiptap/slash-commands.ts +438 -0
- package/src/{lib → client}/toast.ts +101 -3
- package/src/client/types/sortablejs.d.ts +44 -0
- package/src/client/upload-with-metadata.ts +54 -0
- package/src/client/video-processor.ts +207 -0
- package/src/client.ts +27 -17
- 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 -140
- 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 +783 -1087
- package/src/i18n/locales/en.ts +1 -1
- package/src/i18n/locales/zh-Hans.po +867 -812
- package/src/i18n/locales/zh-Hans.ts +1 -1
- package/src/i18n/locales/zh-Hant.po +878 -823
- 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__/resolve-config.test.ts +2 -2
- package/src/lib/__tests__/schemas.test.ts +186 -65
- 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__/url.test.ts +2 -2
- package/src/lib/__tests__/view.test.ts +140 -65
- package/src/lib/blurhash-placeholder.ts +102 -0
- package/src/lib/constants.ts +3 -1
- package/src/lib/emoji-catalog.ts +963 -0
- package/src/lib/errors.ts +11 -8
- package/src/lib/feed.ts +77 -31
- 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 +22 -12
- package/src/lib/nanoid.ts +29 -0
- package/src/lib/navigation.ts +1 -1
- package/src/lib/render.tsx +24 -5
- package/src/lib/resolve-config.ts +13 -2
- package/src/lib/schemas.ts +226 -58
- 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 +158 -0
- package/src/lib/theme.ts +11 -8
- package/src/lib/timeline.ts +76 -34
- package/src/lib/tiptap-render.ts +191 -0
- package/src/lib/tiptap-to-markdown.ts +305 -0
- package/src/lib/upload.ts +263 -14
- package/src/lib/url.ts +37 -22
- package/src/lib/view.ts +236 -55
- 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/error-handler.ts +3 -3
- package/src/middleware/onboarding.ts +1 -1
- package/src/middleware/secure-headers.ts +40 -0
- package/src/preset.css +83 -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 +57 -31
- 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 +81 -62
- package/src/routes/api/search.ts +3 -4
- package/src/routes/api/upload-multipart.ts +245 -0
- package/src/routes/api/upload.ts +92 -24
- package/src/routes/auth/__tests__/setup.test.ts +20 -60
- package/src/routes/auth/reset.tsx +5 -4
- package/src/routes/auth/setup.tsx +39 -31
- package/src/routes/auth/signin.tsx +13 -14
- package/src/routes/compose.tsx +27 -63
- package/src/routes/dash/__tests__/settings-avatar.test.ts +44 -9
- package/src/routes/dash/custom-urls.tsx +414 -0
- package/src/routes/dash/settings.tsx +475 -99
- package/src/routes/feed/__tests__/rss.test.ts +22 -23
- package/src/routes/feed/rss.ts +6 -2
- 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 +36 -18
- package/src/routes/pages/archive.tsx +177 -37
- package/src/routes/pages/collection.tsx +43 -14
- package/src/routes/pages/collections.tsx +11 -2
- package/src/routes/pages/featured.tsx +27 -3
- package/src/routes/pages/home.tsx +15 -14
- 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 +800 -230
- package/src/services/__tests__/search.test.ts +67 -10
- package/src/services/__tests__/settings.test.ts +3 -3
- 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 +764 -172
- package/src/services/search.ts +161 -74
- package/src/services/settings.ts +6 -2
- package/src/styles/components.css +293 -62
- package/src/styles/tokens.css +93 -5
- package/src/styles/ui.css +4349 -766
- package/src/types/bindings.ts +8 -0
- package/src/types/config.ts +34 -4
- package/src/types/constants.ts +17 -2
- package/src/types/entities.ts +83 -37
- package/src/types/operations.ts +20 -27
- package/src/types/props.ts +52 -17
- package/src/types/views.ts +48 -24
- package/src/ui/color-themes.ts +133 -23
- package/src/ui/compose/ComposeDialog.tsx +255 -16
- 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 +12 -2
- package/src/ui/dash/appearance/AdvancedContent.tsx +71 -59
- package/src/ui/dash/appearance/ColorThemeContent.tsx +48 -33
- package/src/ui/dash/appearance/FontThemeContent.tsx +65 -73
- package/src/ui/dash/appearance/NavigationContent.tsx +106 -135
- package/src/ui/dash/index.ts +0 -3
- package/src/ui/dash/settings/AccountContent.tsx +87 -146
- 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 +78 -0
- package/src/ui/dash/settings/GeneralContent.tsx +3 -62
- package/src/ui/dash/settings/SessionsContent.tsx +159 -0
- package/src/ui/dash/settings/SettingsRootContent.tsx +266 -0
- 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 +116 -103
- package/src/ui/pages/ArchivePage.tsx +923 -95
- package/src/ui/pages/CollectionPage.tsx +6 -35
- package/src/ui/pages/CollectionsPage.tsx +2 -1
- package/src/ui/pages/ComposePage.tsx +54 -0
- package/src/ui/pages/FeaturedPage.tsx +2 -1
- package/src/ui/pages/HomePage.tsx +1 -1
- package/src/ui/pages/PostPage.tsx +30 -45
- package/src/ui/pages/SearchPage.tsx +182 -38
- package/src/ui/shared/AdminBreadcrumb.tsx +29 -0
- package/src/ui/shared/CollectionsSidebar.tsx +239 -4
- package/src/ui/shared/MediaGallery.tsx +475 -41
- 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/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/meta/0003_snapshot.json +0 -821
- package/src/lib/__tests__/sqid.test.ts +0 -65
- package/src/lib/collections-reorder.ts +0 -28
- package/src/lib/compose-bridge.ts +0 -280
- package/src/lib/media-upload.ts +0 -148
- 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/appearance.tsx +0 -240
- package/src/routes/dash/collections.tsx +0 -211
- package/src/routes/dash/index.tsx +0 -103
- package/src/routes/dash/media.tsx +0 -132
- package/src/routes/dash/pages.tsx +0 -239
- package/src/routes/dash/posts.tsx +0 -334
- package/src/routes/dash/redirects.tsx +0 -257
- 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 -203
- package/src/services/path-registry.ts +0 -160
- package/src/services/redirect.ts +0 -97
- package/src/types/sortablejs.d.ts +0 -29
- package/src/ui/components/__tests__/jant-compose-dialog.test.ts +0 -512
- package/src/ui/components/__tests__/jant-compose-editor.test.ts +0 -272
- package/src/ui/components/compose-types.ts +0 -75
- package/src/ui/components/jant-collection-form.ts +0 -512
- package/src/ui/components/jant-compose-dialog.ts +0 -495
- package/src/ui/components/jant-compose-editor.ts +0 -814
- package/src/ui/dash/PageForm.tsx +0 -185
- package/src/ui/dash/PostList.tsx +0 -95
- package/src/ui/dash/appearance/AppearanceNav.tsx +0 -60
- package/src/ui/dash/collections/CollectionForm.tsx +0 -166
- package/src/ui/dash/collections/CollectionsListContent.tsx +0 -146
- package/src/ui/dash/collections/IconPickerGrid.tsx +0 -50
- package/src/ui/dash/collections/ViewCollectionContent.tsx +0 -103
- package/src/ui/dash/media/MediaListContent.tsx +0 -201
- package/src/ui/dash/media/ViewMediaContent.tsx +0 -208
- package/src/ui/dash/pages/PagesContent.tsx +0 -74
- package/src/ui/dash/posts/PostForm.tsx +0 -248
- package/src/ui/dash/settings/SettingsNav.tsx +0 -52
- package/src/ui/layouts/DashLayout.tsx +0 -165
- package/src/ui/pages/SinglePage.tsx +0 -23
- package/src/ui/shared/ThreadView.tsx +0 -136
- /package/src/{ui → client}/components/settings-types.ts +0 -0
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Avatar settings page — extracted from GeneralContent
|
|
3
|
+
*
|
|
4
|
+
* Wraps the <jant-settings-avatar> Lit component with translated labels.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { useLingui } from "@lingui/react/macro";
|
|
8
|
+
|
|
9
|
+
export function AvatarContent({
|
|
10
|
+
siteAvatarUrl,
|
|
11
|
+
showHeaderAvatar,
|
|
12
|
+
}: {
|
|
13
|
+
siteAvatarUrl: string;
|
|
14
|
+
showHeaderAvatar: boolean;
|
|
15
|
+
}) {
|
|
16
|
+
const { t } = useLingui();
|
|
17
|
+
|
|
18
|
+
const labels = JSON.stringify({
|
|
19
|
+
blogAvatar: t({
|
|
20
|
+
message: "Blog Avatar",
|
|
21
|
+
comment: "@context: Settings section heading for avatar",
|
|
22
|
+
}),
|
|
23
|
+
uploadAvatar: t({
|
|
24
|
+
message: "Upload Avatar",
|
|
25
|
+
comment: "@context: Button to upload avatar image",
|
|
26
|
+
}),
|
|
27
|
+
remove: t({
|
|
28
|
+
message: "Remove",
|
|
29
|
+
comment: "@context: Button to remove the blog avatar",
|
|
30
|
+
}),
|
|
31
|
+
avatarHelp: t({
|
|
32
|
+
message:
|
|
33
|
+
"This is used for your favicon and apple-touch-icon. For best results, upload a square image at least 180x180 pixels.",
|
|
34
|
+
comment: "@context: Help text for avatar upload",
|
|
35
|
+
}),
|
|
36
|
+
displayInHeader: t({
|
|
37
|
+
message: "Display avatar in my site header",
|
|
38
|
+
comment: "@context: Checkbox to show avatar in the site header",
|
|
39
|
+
}),
|
|
40
|
+
processing: t({
|
|
41
|
+
message: "Processing...",
|
|
42
|
+
comment:
|
|
43
|
+
"@context: Avatar upload button text while generating favicon variants",
|
|
44
|
+
}),
|
|
45
|
+
uploading: t({
|
|
46
|
+
message: "Uploading...",
|
|
47
|
+
comment: "@context: Avatar upload button text while uploading",
|
|
48
|
+
}),
|
|
49
|
+
uploadError: t({
|
|
50
|
+
message: "Upload failed. Please try again.",
|
|
51
|
+
comment: "@context: Error message when avatar upload fails",
|
|
52
|
+
}),
|
|
53
|
+
save: t({
|
|
54
|
+
message: "Save",
|
|
55
|
+
comment: "@context: Button to save settings changes",
|
|
56
|
+
}),
|
|
57
|
+
cancel: t({
|
|
58
|
+
message: "Cancel",
|
|
59
|
+
comment: "@context: Button to cancel settings changes",
|
|
60
|
+
}),
|
|
61
|
+
}).replace(/</g, "\\u003c");
|
|
62
|
+
|
|
63
|
+
return (
|
|
64
|
+
<div class="flex flex-col max-w-lg">
|
|
65
|
+
<jant-settings-avatar
|
|
66
|
+
avatar-url={siteAvatarUrl}
|
|
67
|
+
show-in-header={showHeaderAvatar || undefined}
|
|
68
|
+
labels={labels}
|
|
69
|
+
>
|
|
70
|
+
{/* SSR fallback skeleton */}
|
|
71
|
+
<div>
|
|
72
|
+
<h2 class="skel-label" />
|
|
73
|
+
<div class="skel-section-sm" />
|
|
74
|
+
</div>
|
|
75
|
+
</jant-settings-avatar>
|
|
76
|
+
</div>
|
|
77
|
+
);
|
|
78
|
+
}
|
|
@@ -1,18 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* General settings form
|
|
3
3
|
*
|
|
4
|
-
* Server-side template that renders
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* The Lit components <jant-settings-avatar> and <jant-settings-general>
|
|
9
|
-
* handle all form state and rendering. The settings-bridge.ts script
|
|
10
|
-
* handles server communication.
|
|
4
|
+
* Server-side template that renders the <jant-settings-general> Lit
|
|
5
|
+
* component for site name, description, language, timezone, footer, and SEO.
|
|
6
|
+
* The settings-bridge.ts script handles server communication.
|
|
11
7
|
*/
|
|
12
8
|
|
|
13
9
|
import { useLingui } from "@lingui/react/macro";
|
|
14
10
|
import type { TimezoneEntry } from "../../../lib/timezones.js";
|
|
15
|
-
import { SettingsNav } from "./SettingsNav.js";
|
|
16
11
|
|
|
17
12
|
export function GeneralContent({
|
|
18
13
|
siteName,
|
|
@@ -20,8 +15,6 @@ export function GeneralContent({
|
|
|
20
15
|
siteLanguage,
|
|
21
16
|
siteNameFallback,
|
|
22
17
|
siteDescriptionFallback,
|
|
23
|
-
siteAvatarUrl,
|
|
24
|
-
showHeaderAvatar,
|
|
25
18
|
timeZone,
|
|
26
19
|
siteFooter,
|
|
27
20
|
noindex,
|
|
@@ -32,8 +25,6 @@ export function GeneralContent({
|
|
|
32
25
|
siteLanguage: string;
|
|
33
26
|
siteNameFallback: string;
|
|
34
27
|
siteDescriptionFallback: string;
|
|
35
|
-
siteAvatarUrl: string;
|
|
36
|
-
showHeaderAvatar: boolean;
|
|
37
28
|
timeZone: string;
|
|
38
29
|
siteFooter: string;
|
|
39
30
|
noindex: boolean;
|
|
@@ -42,40 +33,6 @@ export function GeneralContent({
|
|
|
42
33
|
const { t } = useLingui();
|
|
43
34
|
|
|
44
35
|
const labels = JSON.stringify({
|
|
45
|
-
blogAvatar: t({
|
|
46
|
-
message: "Blog Avatar",
|
|
47
|
-
comment: "@context: Settings section heading for avatar",
|
|
48
|
-
}),
|
|
49
|
-
uploadAvatar: t({
|
|
50
|
-
message: "Upload Avatar",
|
|
51
|
-
comment: "@context: Button to upload avatar image",
|
|
52
|
-
}),
|
|
53
|
-
remove: t({
|
|
54
|
-
message: "Remove",
|
|
55
|
-
comment: "@context: Button to remove the blog avatar",
|
|
56
|
-
}),
|
|
57
|
-
avatarHelp: t({
|
|
58
|
-
message:
|
|
59
|
-
"This is used for your favicon and apple-touch-icon. For best results, upload a square image at least 180x180 pixels.",
|
|
60
|
-
comment: "@context: Help text for avatar upload",
|
|
61
|
-
}),
|
|
62
|
-
displayInHeader: t({
|
|
63
|
-
message: "Display avatar in my site header",
|
|
64
|
-
comment: "@context: Checkbox to show avatar in the site header",
|
|
65
|
-
}),
|
|
66
|
-
processing: t({
|
|
67
|
-
message: "Processing...",
|
|
68
|
-
comment:
|
|
69
|
-
"@context: Avatar upload button text while generating favicon variants",
|
|
70
|
-
}),
|
|
71
|
-
uploading: t({
|
|
72
|
-
message: "Uploading...",
|
|
73
|
-
comment: "@context: Avatar upload button text while uploading",
|
|
74
|
-
}),
|
|
75
|
-
uploadError: t({
|
|
76
|
-
message: "Upload failed. Please try again.",
|
|
77
|
-
comment: "@context: Error message when avatar upload fails",
|
|
78
|
-
}),
|
|
79
36
|
general: t({
|
|
80
37
|
message: "General",
|
|
81
38
|
comment: "@context: Settings section heading",
|
|
@@ -154,23 +111,7 @@ export function GeneralContent({
|
|
|
154
111
|
|
|
155
112
|
return (
|
|
156
113
|
<>
|
|
157
|
-
<SettingsNav currentTab="general" />
|
|
158
|
-
|
|
159
114
|
<div class="flex flex-col max-w-lg">
|
|
160
|
-
<jant-settings-avatar
|
|
161
|
-
avatar-url={siteAvatarUrl}
|
|
162
|
-
show-in-header={showHeaderAvatar || undefined}
|
|
163
|
-
labels={labels}
|
|
164
|
-
>
|
|
165
|
-
{/* SSR fallback skeleton */}
|
|
166
|
-
<div>
|
|
167
|
-
<h2 class="skel-label" />
|
|
168
|
-
<div class="skel-section-sm" />
|
|
169
|
-
</div>
|
|
170
|
-
</jant-settings-avatar>
|
|
171
|
-
|
|
172
|
-
<hr class="my-8" />
|
|
173
|
-
|
|
174
115
|
<jant-settings-general
|
|
175
116
|
labels={labels}
|
|
176
117
|
timezones={timezonesJson}
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session management: view active sessions and revoke them
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { useLingui } from "@lingui/react/macro";
|
|
6
|
+
import { formatDate } from "../../../lib/time.js";
|
|
7
|
+
|
|
8
|
+
export interface SessionInfo {
|
|
9
|
+
token: string;
|
|
10
|
+
ipAddress: string | null;
|
|
11
|
+
userAgent: string | null;
|
|
12
|
+
createdAt: number;
|
|
13
|
+
isCurrent: boolean;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Parse a user-agent string into a human-readable device description.
|
|
18
|
+
*
|
|
19
|
+
* @param ua - Raw User-Agent header value
|
|
20
|
+
* @returns Short description like "Chrome on macOS" or "Safari on iPhone"
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* ```ts
|
|
24
|
+
* parseDevice("Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) ... Chrome/120")
|
|
25
|
+
* // "Chrome on macOS"
|
|
26
|
+
* ```
|
|
27
|
+
*/
|
|
28
|
+
function parseDevice(ua: string | null): string | null {
|
|
29
|
+
if (!ua) return null;
|
|
30
|
+
|
|
31
|
+
let browser = "Unknown browser";
|
|
32
|
+
if (ua.includes("Firefox/")) browser = "Firefox";
|
|
33
|
+
else if (ua.includes("Edg/")) browser = "Edge";
|
|
34
|
+
else if (ua.includes("OPR/") || ua.includes("Opera/")) browser = "Opera";
|
|
35
|
+
else if (ua.includes("Chrome/") && ua.includes("Safari/")) browser = "Chrome";
|
|
36
|
+
else if (ua.includes("Safari/") && !ua.includes("Chrome/"))
|
|
37
|
+
browser = "Safari";
|
|
38
|
+
else if (ua.includes("curl/")) browser = "curl";
|
|
39
|
+
|
|
40
|
+
let os = "";
|
|
41
|
+
if (ua.includes("iPhone")) os = "iPhone";
|
|
42
|
+
else if (ua.includes("iPad")) os = "iPad";
|
|
43
|
+
else if (ua.includes("Android")) os = "Android";
|
|
44
|
+
else if (ua.includes("Macintosh") || ua.includes("Mac OS X")) os = "macOS";
|
|
45
|
+
else if (ua.includes("Windows")) os = "Windows";
|
|
46
|
+
else if (ua.includes("Linux")) os = "Linux";
|
|
47
|
+
else if (ua.includes("CrOS")) os = "ChromeOS";
|
|
48
|
+
|
|
49
|
+
return os ? `${browser} on ${os}` : browser;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** Monitor icon for desktop sessions */
|
|
53
|
+
const ICON_DESKTOP = `<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="20" height="14" x="2" y="3" rx="2"/><line x1="8" x2="16" y1="21" y2="21"/><line x1="12" x2="12" y1="17" y2="21"/></svg>`;
|
|
54
|
+
|
|
55
|
+
/** Smartphone icon for mobile sessions */
|
|
56
|
+
const ICON_MOBILE = `<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="14" height="20" x="5" y="2" rx="2" ry="2"/><path d="M12 18h.01"/></svg>`;
|
|
57
|
+
|
|
58
|
+
function isMobileUA(ua: string | null): boolean {
|
|
59
|
+
if (!ua) return false;
|
|
60
|
+
return /iPhone|iPad|iPod|Android|Mobile/i.test(ua);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function SessionRow({ session }: { session: SessionInfo }) {
|
|
64
|
+
const { t } = useLingui();
|
|
65
|
+
const device = parseDevice(session.userAgent);
|
|
66
|
+
const icon = isMobileUA(session.userAgent) ? ICON_MOBILE : ICON_DESKTOP;
|
|
67
|
+
|
|
68
|
+
return (
|
|
69
|
+
<div class="py-4 flex items-start gap-4 border-b border-border last:border-b-0">
|
|
70
|
+
<span
|
|
71
|
+
class="text-muted-foreground mt-0.5 shrink-0"
|
|
72
|
+
dangerouslySetInnerHTML={{ __html: icon }}
|
|
73
|
+
/>
|
|
74
|
+
<div class="flex-1 min-w-0">
|
|
75
|
+
<div class="font-medium flex items-center gap-2">
|
|
76
|
+
{device ??
|
|
77
|
+
t({
|
|
78
|
+
message: "Unknown device",
|
|
79
|
+
comment:
|
|
80
|
+
"@context: Fallback label when session device can't be identified",
|
|
81
|
+
})}
|
|
82
|
+
{session.isCurrent && (
|
|
83
|
+
<span class="badge text-xs">
|
|
84
|
+
{t({
|
|
85
|
+
message: "Current",
|
|
86
|
+
comment:
|
|
87
|
+
"@context: Badge indicating the current active session",
|
|
88
|
+
})}
|
|
89
|
+
</span>
|
|
90
|
+
)}
|
|
91
|
+
</div>
|
|
92
|
+
<div class="text-sm text-muted-foreground mt-0.5">
|
|
93
|
+
{session.ipAddress && (
|
|
94
|
+
<>
|
|
95
|
+
<span>{session.ipAddress}</span>
|
|
96
|
+
<span class="mx-2">·</span>
|
|
97
|
+
</>
|
|
98
|
+
)}
|
|
99
|
+
{t({
|
|
100
|
+
message: `Signed in ${formatDate(session.createdAt)}`,
|
|
101
|
+
comment: "@context: Session creation date",
|
|
102
|
+
})}
|
|
103
|
+
</div>
|
|
104
|
+
</div>
|
|
105
|
+
{!session.isCurrent && (
|
|
106
|
+
<button
|
|
107
|
+
type="button"
|
|
108
|
+
class="btn-sm-ghost text-destructive"
|
|
109
|
+
data-on:click__prevent={`@post('/settings/account/sessions/${session.token}/revoke')`}
|
|
110
|
+
>
|
|
111
|
+
{t({
|
|
112
|
+
message: "Revoke",
|
|
113
|
+
comment: "@context: Button to revoke a session",
|
|
114
|
+
})}
|
|
115
|
+
</button>
|
|
116
|
+
)}
|
|
117
|
+
</div>
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export function SessionsContent({ sessions }: { sessions: SessionInfo[] }) {
|
|
122
|
+
const { t } = useLingui();
|
|
123
|
+
|
|
124
|
+
return (
|
|
125
|
+
<div class="flex flex-col gap-6 max-w-2xl">
|
|
126
|
+
<div>
|
|
127
|
+
<h2 class="text-lg font-medium mb-1">
|
|
128
|
+
{t({
|
|
129
|
+
message: "Active Sessions",
|
|
130
|
+
comment: "@context: Settings section heading for active sessions",
|
|
131
|
+
})}
|
|
132
|
+
</h2>
|
|
133
|
+
<p class="text-sm text-muted-foreground mb-4">
|
|
134
|
+
{t({
|
|
135
|
+
message:
|
|
136
|
+
"These devices are currently signed in to your account. Revoke any session you don't recognize.",
|
|
137
|
+
comment: "@context: Description for session management",
|
|
138
|
+
})}
|
|
139
|
+
</p>
|
|
140
|
+
</div>
|
|
141
|
+
|
|
142
|
+
{sessions.length > 0 ? (
|
|
143
|
+
<div class="border border-border rounded-lg px-4">
|
|
144
|
+
{sessions.map((session) => (
|
|
145
|
+
<SessionRow key={session.token} session={session} />
|
|
146
|
+
))}
|
|
147
|
+
</div>
|
|
148
|
+
) : (
|
|
149
|
+
<p class="text-sm text-muted-foreground">
|
|
150
|
+
{t({
|
|
151
|
+
message: "No active sessions found.",
|
|
152
|
+
comment:
|
|
153
|
+
"@context: Empty state when no sessions exist (shouldn't normally appear)",
|
|
154
|
+
})}
|
|
155
|
+
</p>
|
|
156
|
+
)}
|
|
157
|
+
</div>
|
|
158
|
+
);
|
|
159
|
+
}
|
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Settings root page — iOS-style grouped list linking to sub-pages
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { useLingui } from "@lingui/react/macro";
|
|
6
|
+
|
|
7
|
+
/** Chevron right icon shared by all rows */
|
|
8
|
+
function ChevronRight() {
|
|
9
|
+
return (
|
|
10
|
+
<svg
|
|
11
|
+
class="settings-item-chevron"
|
|
12
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
13
|
+
width="16"
|
|
14
|
+
height="16"
|
|
15
|
+
viewBox="0 0 24 24"
|
|
16
|
+
fill="none"
|
|
17
|
+
stroke="currentColor"
|
|
18
|
+
stroke-width="2"
|
|
19
|
+
stroke-linecap="round"
|
|
20
|
+
stroke-linejoin="round"
|
|
21
|
+
>
|
|
22
|
+
<path d="m9 18 6-6-6-6" />
|
|
23
|
+
</svg>
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function SettingsItem({
|
|
28
|
+
href,
|
|
29
|
+
icon,
|
|
30
|
+
color,
|
|
31
|
+
name,
|
|
32
|
+
description,
|
|
33
|
+
}: {
|
|
34
|
+
href: string;
|
|
35
|
+
icon: string;
|
|
36
|
+
color: string;
|
|
37
|
+
name: string;
|
|
38
|
+
description: string;
|
|
39
|
+
}) {
|
|
40
|
+
return (
|
|
41
|
+
<a href={href} class="settings-item">
|
|
42
|
+
<span class="settings-item-icon" style={`background-color:${color}`}>
|
|
43
|
+
<span dangerouslySetInnerHTML={{ __html: icon }} />
|
|
44
|
+
</span>
|
|
45
|
+
<span class="settings-item-text">
|
|
46
|
+
<span class="settings-item-name">{name}</span>
|
|
47
|
+
<span class="settings-item-desc">{description}</span>
|
|
48
|
+
</span>
|
|
49
|
+
<ChevronRight />
|
|
50
|
+
</a>
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Lucide icon SVG paths (16x16, stroke-based)
|
|
55
|
+
const ICONS = {
|
|
56
|
+
settings: `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12.22 2h-.44a2 2 0 0 0-2 2v.18a2 2 0 0 1-1 1.73l-.43.25a2 2 0 0 1-2 0l-.15-.08a2 2 0 0 0-2.73.73l-.22.38a2 2 0 0 0 .73 2.73l.15.1a2 2 0 0 1 1 1.72v.51a2 2 0 0 1-1 1.74l-.15.09a2 2 0 0 0-.73 2.73l.22.38a2 2 0 0 0 2.73.73l.15-.08a2 2 0 0 1 2 0l.43.25a2 2 0 0 1 1 1.73V20a2 2 0 0 0 2 2h.44a2 2 0 0 0 2-2v-.18a2 2 0 0 1 1-1.73l.43-.25a2 2 0 0 1 2 0l.15.08a2 2 0 0 0 2.73-.73l.22-.39a2 2 0 0 0-.73-2.73l-.15-.08a2 2 0 0 1-1-1.74v-.5a2 2 0 0 1 1-1.74l.15-.09a2 2 0 0 0 .73-2.73l-.22-.38a2 2 0 0 0-2.73-.73l-.15.08a2 2 0 0 1-2 0l-.43-.25a2 2 0 0 1-1-1.73V4a2 2 0 0 0-2-2z"/><circle cx="12" cy="12" r="3"/></svg>`,
|
|
57
|
+
image: `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="18" height="18" x="3" y="3" rx="2" ry="2"/><circle cx="9" cy="9" r="2"/><path d="m21 15-3.086-3.086a2 2 0 0 0-2.828 0L6 21"/></svg>`,
|
|
58
|
+
menu: `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="4" x2="20" y1="12" y2="12"/><line x1="4" x2="20" y1="6" y2="6"/><line x1="4" x2="20" y1="18" y2="18"/></svg>`,
|
|
59
|
+
palette: `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="13.5" cy="6.5" r=".5" fill="currentColor"/><circle cx="17.5" cy="10.5" r=".5" fill="currentColor"/><circle cx="8.5" cy="7.5" r=".5" fill="currentColor"/><circle cx="6.5" cy="12.5" r=".5" fill="currentColor"/><path d="M12 2C6.5 2 2 6.5 2 12s4.5 10 10 10c.926 0 1.648-.746 1.648-1.688 0-.437-.18-.835-.437-1.125-.29-.289-.438-.652-.438-1.125a1.64 1.64 0 0 1 1.668-1.668h1.996c3.051 0 5.555-2.503 5.555-5.554C21.965 6.012 17.461 2 12 2z"/></svg>`,
|
|
60
|
+
type: `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="4 7 4 4 20 4 20 7"/><line x1="9" x2="15" y1="20" y2="20"/><line x1="12" x2="12" y1="4" y2="20"/></svg>`,
|
|
61
|
+
code: `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="16 18 22 12 16 6"/><polyline points="8 6 2 12 8 18"/></svg>`,
|
|
62
|
+
arrowRightLeft: `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m16 3 4 4-4 4"/><path d="M20 7H4"/><path d="m8 21-4-4 4-4"/><path d="M4 17h16"/></svg>`,
|
|
63
|
+
lock: `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="18" height="11" x="3" y="11" rx="2" ry="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4"/></svg>`,
|
|
64
|
+
key: `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m15.5 7.5 2.3 2.3a1 1 0 0 0 1.4 0l2.1-2.1a1 1 0 0 0 0-1.4L19 4"/><path d="m21 2-9.6 9.6"/><circle cx="7.5" cy="15.5" r="5.5"/></svg>`,
|
|
65
|
+
shield: `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1z"/></svg>`,
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
// oklch-based colors for icon backgrounds
|
|
69
|
+
const COLORS = {
|
|
70
|
+
blue: "oklch(0.55 0.2 250)",
|
|
71
|
+
purple: "oklch(0.55 0.2 300)",
|
|
72
|
+
green: "oklch(0.55 0.18 155)",
|
|
73
|
+
orange: "oklch(0.6 0.18 55)",
|
|
74
|
+
pink: "oklch(0.6 0.2 350)",
|
|
75
|
+
indigo: "oklch(0.5 0.2 275)",
|
|
76
|
+
amber: "oklch(0.6 0.16 75)",
|
|
77
|
+
teal: "oklch(0.55 0.15 185)",
|
|
78
|
+
gray: "oklch(0.55 0.01 250)",
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
export function SettingsRootContent() {
|
|
82
|
+
const { t } = useLingui();
|
|
83
|
+
|
|
84
|
+
return (
|
|
85
|
+
<div class="settings-root">
|
|
86
|
+
{/* Site */}
|
|
87
|
+
<div>
|
|
88
|
+
<div class="settings-group-label">
|
|
89
|
+
{t({
|
|
90
|
+
message: "Site",
|
|
91
|
+
comment: "@context: Settings group label for site settings",
|
|
92
|
+
})}
|
|
93
|
+
</div>
|
|
94
|
+
<div class="settings-group">
|
|
95
|
+
<SettingsItem
|
|
96
|
+
href="/settings/general"
|
|
97
|
+
icon={ICONS.settings}
|
|
98
|
+
color={COLORS.blue}
|
|
99
|
+
name={t({
|
|
100
|
+
message: "General",
|
|
101
|
+
comment: "@context: Settings item — general settings",
|
|
102
|
+
})}
|
|
103
|
+
description={t({
|
|
104
|
+
message: "Name, description, language",
|
|
105
|
+
comment: "@context: Settings item description for general",
|
|
106
|
+
})}
|
|
107
|
+
/>
|
|
108
|
+
</div>
|
|
109
|
+
</div>
|
|
110
|
+
|
|
111
|
+
{/* Design */}
|
|
112
|
+
<div>
|
|
113
|
+
<div class="settings-group-label">
|
|
114
|
+
{t({
|
|
115
|
+
message: "Design",
|
|
116
|
+
comment: "@context: Settings group label for design settings",
|
|
117
|
+
})}
|
|
118
|
+
</div>
|
|
119
|
+
<div class="settings-group">
|
|
120
|
+
<SettingsItem
|
|
121
|
+
href="/settings/avatar"
|
|
122
|
+
icon={ICONS.image}
|
|
123
|
+
color={COLORS.purple}
|
|
124
|
+
name={t({
|
|
125
|
+
message: "Avatar",
|
|
126
|
+
comment: "@context: Settings item — avatar settings",
|
|
127
|
+
})}
|
|
128
|
+
description={t({
|
|
129
|
+
message: "Favicon and header icon",
|
|
130
|
+
comment: "@context: Settings item description for avatar",
|
|
131
|
+
})}
|
|
132
|
+
/>
|
|
133
|
+
<SettingsItem
|
|
134
|
+
href="/settings/navigation"
|
|
135
|
+
icon={ICONS.menu}
|
|
136
|
+
color={COLORS.green}
|
|
137
|
+
name={t({
|
|
138
|
+
message: "Navigation",
|
|
139
|
+
comment: "@context: Settings item — navigation settings",
|
|
140
|
+
})}
|
|
141
|
+
description={t({
|
|
142
|
+
message: "Header links, featured",
|
|
143
|
+
comment: "@context: Settings item description for navigation",
|
|
144
|
+
})}
|
|
145
|
+
/>
|
|
146
|
+
<SettingsItem
|
|
147
|
+
href="/settings/color-theme"
|
|
148
|
+
icon={ICONS.palette}
|
|
149
|
+
color={COLORS.orange}
|
|
150
|
+
name={t({
|
|
151
|
+
message: "Color Theme",
|
|
152
|
+
comment: "@context: Settings item — color theme settings",
|
|
153
|
+
})}
|
|
154
|
+
description={t({
|
|
155
|
+
message: "Color theme",
|
|
156
|
+
comment: "@context: Settings item description for color theme",
|
|
157
|
+
})}
|
|
158
|
+
/>
|
|
159
|
+
<SettingsItem
|
|
160
|
+
href="/settings/font-theme"
|
|
161
|
+
icon={ICONS.type}
|
|
162
|
+
color={COLORS.pink}
|
|
163
|
+
name={t({
|
|
164
|
+
message: "Font Theme",
|
|
165
|
+
comment: "@context: Settings item — font theme settings",
|
|
166
|
+
})}
|
|
167
|
+
description={t({
|
|
168
|
+
message: "Typography",
|
|
169
|
+
comment: "@context: Settings item description for font theme",
|
|
170
|
+
})}
|
|
171
|
+
/>
|
|
172
|
+
<SettingsItem
|
|
173
|
+
href="/settings/custom-css"
|
|
174
|
+
icon={ICONS.code}
|
|
175
|
+
color={COLORS.indigo}
|
|
176
|
+
name={t({
|
|
177
|
+
message: "Custom CSS",
|
|
178
|
+
comment: "@context: Settings item — custom CSS settings",
|
|
179
|
+
})}
|
|
180
|
+
description={t({
|
|
181
|
+
message: "Custom styling",
|
|
182
|
+
comment: "@context: Settings item description for custom CSS",
|
|
183
|
+
})}
|
|
184
|
+
/>
|
|
185
|
+
</div>
|
|
186
|
+
</div>
|
|
187
|
+
|
|
188
|
+
{/* Advanced */}
|
|
189
|
+
<div>
|
|
190
|
+
<div class="settings-group-label">
|
|
191
|
+
{t({
|
|
192
|
+
message: "Advanced",
|
|
193
|
+
comment: "@context: Settings group label for advanced settings",
|
|
194
|
+
})}
|
|
195
|
+
</div>
|
|
196
|
+
<div class="settings-group">
|
|
197
|
+
<SettingsItem
|
|
198
|
+
href="/settings/custom-urls"
|
|
199
|
+
icon={ICONS.arrowRightLeft}
|
|
200
|
+
color={COLORS.amber}
|
|
201
|
+
name={t({
|
|
202
|
+
message: "Custom URLs",
|
|
203
|
+
comment: "@context: Settings item — custom URL settings",
|
|
204
|
+
})}
|
|
205
|
+
description={t({
|
|
206
|
+
message: "Redirects and custom paths",
|
|
207
|
+
comment: "@context: Settings item description for custom URLs",
|
|
208
|
+
})}
|
|
209
|
+
/>
|
|
210
|
+
<SettingsItem
|
|
211
|
+
href="/settings/api-tokens"
|
|
212
|
+
icon={ICONS.key}
|
|
213
|
+
color={COLORS.teal}
|
|
214
|
+
name={t({
|
|
215
|
+
message: "API Tokens",
|
|
216
|
+
comment: "@context: Settings item — API token settings",
|
|
217
|
+
})}
|
|
218
|
+
description={t({
|
|
219
|
+
message: "Bearer tokens for scripts and automation",
|
|
220
|
+
comment: "@context: Settings item description for API tokens",
|
|
221
|
+
})}
|
|
222
|
+
/>
|
|
223
|
+
</div>
|
|
224
|
+
</div>
|
|
225
|
+
|
|
226
|
+
{/* Account */}
|
|
227
|
+
<div>
|
|
228
|
+
<div class="settings-group-label">
|
|
229
|
+
{t({
|
|
230
|
+
message: "Account",
|
|
231
|
+
comment: "@context: Settings group label for account settings",
|
|
232
|
+
})}
|
|
233
|
+
</div>
|
|
234
|
+
<div class="settings-group">
|
|
235
|
+
<SettingsItem
|
|
236
|
+
href="/settings/account"
|
|
237
|
+
icon={ICONS.shield}
|
|
238
|
+
color={COLORS.gray}
|
|
239
|
+
name={t({
|
|
240
|
+
message: "Account",
|
|
241
|
+
comment: "@context: Settings item — account settings",
|
|
242
|
+
})}
|
|
243
|
+
description={t({
|
|
244
|
+
message: "Sessions, password, export",
|
|
245
|
+
comment: "@context: Settings item description for account",
|
|
246
|
+
})}
|
|
247
|
+
/>
|
|
248
|
+
</div>
|
|
249
|
+
</div>
|
|
250
|
+
|
|
251
|
+
{/* Sign Out */}
|
|
252
|
+
<div class="pt-2 text-center">
|
|
253
|
+
<button
|
|
254
|
+
type="button"
|
|
255
|
+
data-on:click__prevent="@post('/signout')"
|
|
256
|
+
class="text-sm text-destructive hover:text-destructive/80 transition-colors"
|
|
257
|
+
>
|
|
258
|
+
{t({
|
|
259
|
+
message: "Sign Out",
|
|
260
|
+
comment: "@context: Settings link — sign out action",
|
|
261
|
+
})}
|
|
262
|
+
</button>
|
|
263
|
+
</div>
|
|
264
|
+
</div>
|
|
265
|
+
);
|
|
266
|
+
}
|