@jant/core 0.3.26 → 0.3.28
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/client/client.css +1 -0
- package/dist/client/client.js +31561 -0
- package/dist/index.js +15209 -15
- package/package.json +21 -15
- package/src/__tests__/helpers/app.ts +19 -3
- package/src/__tests__/helpers/db.ts +44 -0
- package/src/__tests__/helpers/lingui-core-macro-mock.ts +33 -0
- package/src/app.tsx +112 -173
- package/src/auth.ts +4 -1
- package/src/client.ts +13 -0
- package/src/db/migrations/0007_post_collections_m2m.sql +94 -0
- package/src/db/migrations/0008_add_collection_dividers.sql +8 -0
- package/src/db/migrations/0009_drop_collection_show_divider.sql +2 -0
- package/src/db/migrations/0010_add_performance_indexes.sql +16 -0
- package/src/db/schema.ts +24 -4
- package/src/i18n/locales/en.po +810 -385
- package/src/i18n/locales/en.ts +1 -1
- package/src/i18n/locales/zh-Hans.po +733 -522
- package/src/i18n/locales/zh-Hans.ts +1 -1
- package/src/i18n/locales/zh-Hant.po +733 -522
- package/src/i18n/locales/zh-Hant.ts +1 -1
- package/src/i18n/middleware.ts +7 -11
- package/src/index.ts +1 -1
- package/src/lib/__tests__/icons.test.ts +178 -0
- package/src/lib/__tests__/resolve-config.test.ts +184 -0
- package/src/lib/__tests__/schemas.test.ts +12 -6
- package/src/lib/__tests__/theme.test.ts +62 -0
- package/src/lib/__tests__/timezones.test.ts +1 -1
- package/src/lib/__tests__/url.test.ts +12 -0
- package/src/lib/__tests__/view.test.ts +1 -5
- package/src/lib/avatar-upload.ts +18 -10
- package/src/lib/collection-form-bridge.ts +52 -0
- package/src/lib/collections-reorder.ts +28 -0
- package/src/lib/compose-bridge.ts +251 -0
- package/src/lib/errors.ts +116 -0
- package/src/lib/excerpt.ts +1 -1
- package/src/lib/favicon.ts +3 -5
- package/src/lib/html.ts +22 -0
- package/src/lib/icon-catalog.ts +181 -0
- package/src/lib/icons.ts +202 -0
- package/src/lib/navigation.ts +18 -33
- package/src/lib/pagination.ts +3 -2
- package/src/lib/post-form-bridge.ts +136 -0
- package/src/lib/render.tsx +11 -4
- package/src/lib/resolve-config.ts +157 -0
- package/src/lib/schemas.ts +76 -12
- package/src/lib/settings-bridge.ts +139 -0
- package/src/lib/storage.ts +37 -16
- package/src/lib/theme.ts +5 -7
- package/src/lib/timeline.ts +4 -8
- package/src/lib/toast.ts +134 -0
- package/src/lib/upload.ts +71 -0
- package/src/lib/url.ts +9 -1
- package/src/lib/version.ts +16 -0
- package/src/lib/view.ts +9 -10
- package/src/middleware/__tests__/auth.test.ts +6 -28
- package/src/middleware/__tests__/onboarding.test.ts +1 -1
- package/src/middleware/auth.ts +6 -12
- package/src/middleware/config.ts +51 -0
- package/src/middleware/error-handler.ts +56 -0
- package/src/middleware/onboarding.ts +1 -1
- package/src/preset.css +6 -0
- package/src/routes/__tests__/compose.test.ts +104 -17
- package/src/routes/api/__tests__/collections.test.ts +93 -2
- package/src/routes/api/__tests__/posts.test.ts +2 -1
- package/src/routes/api/__tests__/settings.test.ts +1 -1
- package/src/routes/api/collections.ts +64 -68
- package/src/routes/api/nav-items.ts +21 -59
- package/src/routes/api/pages.ts +18 -46
- package/src/routes/api/posts.ts +64 -86
- package/src/routes/api/search.ts +6 -4
- package/src/routes/api/settings.ts +8 -24
- package/src/routes/api/upload.ts +55 -53
- package/src/routes/auth/__tests__/setup.test.ts +118 -0
- package/src/routes/auth/reset.tsx +17 -66
- package/src/routes/auth/setup.tsx +67 -11
- package/src/routes/auth/signin.tsx +44 -8
- package/src/routes/compose.tsx +194 -0
- package/src/routes/dash/__tests__/font-theme.test.ts +110 -0
- package/src/routes/dash/__tests__/pages.test.ts +2 -2
- package/src/routes/dash/__tests__/settings-avatar.test.ts +23 -12
- package/src/routes/dash/appearance.tsx +173 -0
- package/src/routes/dash/collections.tsx +80 -14
- package/src/routes/dash/index.tsx +12 -14
- package/src/routes/dash/media.tsx +46 -49
- package/src/routes/dash/pages.tsx +85 -37
- package/src/routes/dash/posts.tsx +60 -23
- package/src/routes/dash/redirects.tsx +43 -33
- package/src/routes/dash/settings.tsx +234 -214
- package/src/routes/feed/__tests__/rss.test.ts +7 -3
- package/src/routes/feed/rss.ts +11 -16
- package/src/routes/feed/sitemap.ts +15 -9
- package/src/routes/pages/__tests__/collections.test.ts +9 -8
- package/src/routes/pages/archive.tsx +2 -2
- package/src/routes/pages/collection.tsx +76 -9
- package/src/routes/pages/collections.tsx +3 -1
- package/src/routes/pages/featured.tsx +2 -2
- package/src/routes/pages/home.tsx +3 -3
- package/src/routes/pages/latest.tsx +2 -2
- package/src/routes/pages/page.tsx +2 -2
- package/src/routes/pages/post.tsx +2 -2
- package/src/routes/pages/search.tsx +2 -2
- package/src/services/__tests__/collection.test.ts +324 -34
- package/src/services/__tests__/media.test.ts +1 -1
- package/src/services/__tests__/page.test.ts +116 -1
- package/src/services/auth.ts +88 -0
- package/src/services/collection.ts +169 -30
- package/src/services/index.ts +8 -3
- package/src/services/media.ts +39 -12
- package/src/services/navigation.ts +17 -5
- package/src/services/page.ts +24 -4
- package/src/services/post.ts +87 -19
- package/src/services/search.ts +0 -1
- package/src/services/settings.ts +21 -13
- package/src/style.css +3 -0
- package/src/styles/components.css +42 -1
- package/src/styles/tokens.css +4 -0
- package/src/styles/ui.css +902 -73
- package/src/types/app-context.ts +25 -0
- package/src/types/bindings.ts +1 -0
- package/src/types/config.ts +60 -23
- package/src/types/entities.ts +12 -2
- package/src/types/lingui-react-macro.d.ts +3 -3
- package/src/types/operations.ts +2 -4
- package/src/types/views.ts +1 -3
- package/src/ui/__tests__/font-themes.test.ts +27 -8
- package/src/ui/color-themes.ts +1 -1
- package/src/ui/components/__tests__/jant-collection-form.test.ts +153 -0
- package/src/ui/components/__tests__/jant-compose-dialog.test.ts +512 -0
- package/src/ui/components/__tests__/jant-compose-editor.test.ts +272 -0
- package/src/ui/components/__tests__/jant-post-form.test.ts +172 -0
- package/src/ui/components/__tests__/jant-settings-avatar.test.ts +235 -0
- package/src/ui/components/__tests__/jant-settings-general.test.ts +319 -0
- package/src/ui/components/collection-types.ts +45 -0
- package/src/ui/components/compose-types.ts +75 -0
- package/src/ui/components/jant-collection-form.ts +512 -0
- package/src/ui/components/jant-compose-dialog.ts +494 -0
- package/src/ui/components/jant-compose-editor.ts +799 -0
- package/src/ui/components/jant-post-form.ts +290 -0
- package/src/ui/components/jant-settings-avatar.ts +231 -0
- package/src/ui/components/jant-settings-general.ts +436 -0
- package/src/ui/components/post-form-template.ts +260 -0
- package/src/ui/components/post-form-types.ts +87 -0
- package/src/ui/components/settings-types.ts +62 -0
- package/src/ui/compose/ComposeDialog.tsx +141 -385
- package/src/ui/compose/ComposePrompt.tsx +3 -3
- package/src/ui/dash/PostList.tsx +55 -61
- package/src/ui/dash/appearance/AdvancedContent.tsx +80 -0
- package/src/ui/dash/appearance/AppearanceNav.tsx +56 -0
- package/src/ui/dash/appearance/ColorThemeContent.tsx +129 -0
- package/src/ui/dash/appearance/FontThemeContent.tsx +98 -0
- package/src/ui/dash/collections/CollectionForm.tsx +130 -117
- package/src/ui/dash/collections/CollectionsListContent.tsx +102 -41
- package/src/ui/dash/collections/IconPickerGrid.tsx +50 -0
- package/src/ui/dash/collections/ViewCollectionContent.tsx +14 -3
- package/src/ui/dash/index.ts +1 -1
- package/src/ui/dash/posts/PostForm.tsx +248 -0
- package/src/ui/dash/settings/AccountContent.tsx +69 -80
- package/src/ui/dash/settings/GeneralContent.tsx +159 -478
- package/src/ui/dash/settings/SettingsNav.tsx +4 -4
- package/src/ui/font-themes.ts +115 -32
- package/src/ui/layouts/BaseLayout.tsx +49 -19
- package/src/ui/layouts/DashLayout.tsx +14 -9
- package/src/ui/layouts/SiteLayout.tsx +38 -23
- package/src/ui/pages/CollectionPage.tsx +12 -2
- package/src/ui/pages/CollectionsPage.tsx +27 -27
- package/src/ui/pages/HomePage.tsx +15 -6
- package/src/ui/pages/SearchPage.tsx +1 -2
- package/src/ui/shared/CollectionsSidebar.tsx +59 -0
- package/src/ui/shared/Pagination.tsx +2 -2
- package/dist/app.js +0 -265
- package/dist/auth.js +0 -36
- package/dist/client.js +0 -13
- package/dist/db/index.js +0 -10
- package/dist/db/schema.js +0 -224
- package/dist/i18n/Trans.js +0 -24
- package/dist/i18n/context.js +0 -58
- package/dist/i18n/detect.js +0 -26
- package/dist/i18n/i18n.js +0 -49
- package/dist/i18n/index.js +0 -44
- package/dist/i18n/locales/en.js +0 -1
- package/dist/i18n/locales/zh-Hans.js +0 -1
- package/dist/i18n/locales/zh-Hant.js +0 -1
- package/dist/i18n/locales.js +0 -13
- package/dist/i18n/middleware.js +0 -30
- package/dist/lib/avatar-upload.js +0 -134
- package/dist/lib/config.js +0 -143
- package/dist/lib/constants.js +0 -50
- package/dist/lib/excerpt.js +0 -76
- package/dist/lib/favicon.js +0 -102
- package/dist/lib/feed.js +0 -123
- package/dist/lib/image-processor.js +0 -187
- package/dist/lib/image.js +0 -97
- package/dist/lib/index.js +0 -7
- package/dist/lib/markdown.js +0 -83
- package/dist/lib/media-helpers.js +0 -49
- package/dist/lib/media-upload.js +0 -104
- package/dist/lib/nav-reorder.js +0 -27
- package/dist/lib/navigation.js +0 -79
- package/dist/lib/pagination.js +0 -44
- package/dist/lib/render.js +0 -53
- package/dist/lib/schemas.js +0 -174
- package/dist/lib/sqid.js +0 -72
- package/dist/lib/sse.js +0 -218
- package/dist/lib/storage.js +0 -164
- package/dist/lib/theme.js +0 -65
- package/dist/lib/time.js +0 -159
- package/dist/lib/timeline.js +0 -95
- package/dist/lib/timezones.js +0 -388
- package/dist/lib/url.js +0 -89
- package/dist/lib/view.js +0 -217
- package/dist/middleware/auth.js +0 -52
- package/dist/middleware/onboarding.js +0 -41
- package/dist/routes/api/collections.js +0 -124
- package/dist/routes/api/nav-items.js +0 -104
- package/dist/routes/api/pages.js +0 -91
- package/dist/routes/api/posts.js +0 -218
- package/dist/routes/api/search.js +0 -48
- package/dist/routes/api/settings.js +0 -68
- package/dist/routes/api/upload.js +0 -246
- package/dist/routes/auth/reset.js +0 -221
- package/dist/routes/auth/setup.js +0 -194
- package/dist/routes/auth/signin.js +0 -176
- package/dist/routes/compose.js +0 -48
- package/dist/routes/dash/collections.js +0 -115
- package/dist/routes/dash/index.js +0 -118
- package/dist/routes/dash/media.js +0 -106
- package/dist/routes/dash/pages.js +0 -294
- package/dist/routes/dash/posts.js +0 -244
- package/dist/routes/dash/redirects.js +0 -257
- package/dist/routes/dash/settings.js +0 -379
- package/dist/routes/feed/rss.js +0 -62
- package/dist/routes/feed/sitemap.js +0 -49
- package/dist/routes/pages/archive.js +0 -62
- package/dist/routes/pages/collection.js +0 -34
- package/dist/routes/pages/collections.js +0 -28
- package/dist/routes/pages/featured.js +0 -36
- package/dist/routes/pages/home.js +0 -64
- package/dist/routes/pages/latest.js +0 -45
- package/dist/routes/pages/page.js +0 -68
- package/dist/routes/pages/post.js +0 -44
- package/dist/routes/pages/search.js +0 -54
- package/dist/services/collection.js +0 -109
- package/dist/services/index.js +0 -24
- package/dist/services/media.js +0 -117
- package/dist/services/navigation.js +0 -91
- package/dist/services/page.js +0 -84
- package/dist/services/post.js +0 -229
- package/dist/services/redirect.js +0 -48
- package/dist/services/search.js +0 -67
- package/dist/services/settings.js +0 -68
- package/dist/types/bindings.js +0 -3
- package/dist/types/config.js +0 -147
- package/dist/types/constants.js +0 -27
- package/dist/types/entities.js +0 -3
- package/dist/types/lingui-react-macro.d.js +0 -9
- package/dist/types/operations.js +0 -3
- package/dist/types/props.js +0 -3
- package/dist/types/sortablejs.d.js +0 -5
- package/dist/types/views.js +0 -5
- package/dist/types.js +0 -11
- package/dist/ui/color-themes.js +0 -268
- package/dist/ui/compose/ComposeDialog.js +0 -467
- package/dist/ui/compose/ComposePrompt.js +0 -55
- package/dist/ui/dash/ActionButtons.js +0 -46
- package/dist/ui/dash/CrudPageHeader.js +0 -22
- package/dist/ui/dash/DangerZone.js +0 -36
- package/dist/ui/dash/FormatBadge.js +0 -27
- package/dist/ui/dash/ListItemRow.js +0 -21
- package/dist/ui/dash/PageForm.js +0 -195
- package/dist/ui/dash/PostForm.js +0 -395
- package/dist/ui/dash/PostList.js +0 -83
- package/dist/ui/dash/StatusBadge.js +0 -46
- package/dist/ui/dash/collections/CollectionForm.js +0 -152
- package/dist/ui/dash/collections/CollectionsListContent.js +0 -68
- package/dist/ui/dash/collections/ViewCollectionContent.js +0 -96
- package/dist/ui/dash/index.js +0 -10
- package/dist/ui/dash/media/MediaListContent.js +0 -166
- package/dist/ui/dash/media/ViewMediaContent.js +0 -212
- package/dist/ui/dash/pages/LinkFormContent.js +0 -130
- package/dist/ui/dash/pages/UnifiedPagesContent.js +0 -193
- package/dist/ui/dash/settings/AccountContent.js +0 -209
- package/dist/ui/dash/settings/AppearanceContent.js +0 -259
- package/dist/ui/dash/settings/GeneralContent.js +0 -536
- package/dist/ui/dash/settings/SettingsNav.js +0 -41
- package/dist/ui/feed/LinkCard.js +0 -72
- package/dist/ui/feed/NoteCard.js +0 -58
- package/dist/ui/feed/QuoteCard.js +0 -63
- package/dist/ui/feed/ThreadPreview.js +0 -48
- package/dist/ui/feed/TimelineFeed.js +0 -41
- package/dist/ui/feed/TimelineItem.js +0 -27
- package/dist/ui/font-themes.js +0 -36
- package/dist/ui/layouts/BaseLayout.js +0 -153
- package/dist/ui/layouts/DashLayout.js +0 -141
- package/dist/ui/layouts/SiteLayout.js +0 -169
- package/dist/ui/pages/ArchivePage.js +0 -143
- package/dist/ui/pages/CollectionPage.js +0 -70
- package/dist/ui/pages/CollectionsPage.js +0 -76
- package/dist/ui/pages/FeaturedPage.js +0 -24
- package/dist/ui/pages/HomePage.js +0 -24
- package/dist/ui/pages/PostPage.js +0 -55
- package/dist/ui/pages/SearchPage.js +0 -122
- package/dist/ui/pages/SinglePage.js +0 -23
- package/dist/ui/shared/EmptyState.js +0 -27
- package/dist/ui/shared/MediaGallery.js +0 -35
- package/dist/ui/shared/Pagination.js +0 -195
- package/dist/ui/shared/ThreadView.js +0 -108
- package/dist/ui/shared/index.js +0 -5
- package/dist/vendor/datastar.js +0 -1606
- package/src/lib/__tests__/config.test.ts +0 -192
- package/src/lib/config.ts +0 -167
- package/src/routes/compose.ts +0 -63
- package/src/ui/dash/PostForm.tsx +0 -360
- package/src/ui/dash/settings/AppearanceContent.tsx +0 -254
|
@@ -1,98 +1,19 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* General settings form
|
|
3
|
+
*
|
|
4
|
+
* Server-side template that renders Lit Web Components for the
|
|
5
|
+
* settings page. Provides translated labels, initial data, and
|
|
6
|
+
* timezone/language options as JSON attributes.
|
|
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.
|
|
3
11
|
*/
|
|
4
12
|
|
|
5
13
|
import { useLingui } from "@lingui/react/macro";
|
|
6
14
|
import type { TimezoneEntry } from "../../../lib/timezones.js";
|
|
7
15
|
import { SettingsNav } from "./SettingsNav.js";
|
|
8
16
|
|
|
9
|
-
/**
|
|
10
|
-
* Build data-signals JSON with `_orig_<key>` duplicates for cancel/reset.
|
|
11
|
-
* Private `_orig_*` signals store original values so Cancel can revert.
|
|
12
|
-
* The `dirty` signal tracks whether the user has made any changes.
|
|
13
|
-
*/
|
|
14
|
-
function buildSignals(fields: Record<string, string>, dirty: string): string {
|
|
15
|
-
const signals: Record<string, string | boolean> = {};
|
|
16
|
-
for (const [key, value] of Object.entries(fields)) {
|
|
17
|
-
signals[key] = value;
|
|
18
|
-
signals[`_orig_${key}`] = value;
|
|
19
|
-
}
|
|
20
|
-
signals[dirty] = false;
|
|
21
|
-
return JSON.stringify(signals).replace(/</g, "\\u003c");
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
/** Spinner SVG shown inside buttons during loading */
|
|
25
|
-
function Spinner({ show }: { show: string }) {
|
|
26
|
-
return (
|
|
27
|
-
<svg
|
|
28
|
-
data-show={show}
|
|
29
|
-
style="display:none"
|
|
30
|
-
class="animate-spin size-4"
|
|
31
|
-
xmlns="http://www.w3.org/2000/svg"
|
|
32
|
-
viewBox="0 0 24 24"
|
|
33
|
-
fill="none"
|
|
34
|
-
stroke="currentColor"
|
|
35
|
-
stroke-width="2"
|
|
36
|
-
stroke-linecap="round"
|
|
37
|
-
stroke-linejoin="round"
|
|
38
|
-
role="status"
|
|
39
|
-
>
|
|
40
|
-
<path d="M21 12a9 9 0 1 1-6.219-8.56" />
|
|
41
|
-
</svg>
|
|
42
|
-
);
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* Save + Cancel button pair.
|
|
47
|
-
* Both are disabled when no changes (`!dirty`) or during loading.
|
|
48
|
-
* Cancel resets all signals to originals and clears dirty.
|
|
49
|
-
*/
|
|
50
|
-
function FormActions({
|
|
51
|
-
indicator,
|
|
52
|
-
dirty,
|
|
53
|
-
fields,
|
|
54
|
-
}: {
|
|
55
|
-
indicator: string;
|
|
56
|
-
dirty: string;
|
|
57
|
-
fields: string[];
|
|
58
|
-
}) {
|
|
59
|
-
const { t } = useLingui();
|
|
60
|
-
const resetExpr = [
|
|
61
|
-
...fields.map((f) => `$${f} = $_orig_${f}`),
|
|
62
|
-
`$${dirty} = false`,
|
|
63
|
-
].join("; ");
|
|
64
|
-
|
|
65
|
-
return (
|
|
66
|
-
<div class="flex gap-2 mt-4">
|
|
67
|
-
<button
|
|
68
|
-
type="submit"
|
|
69
|
-
class="btn"
|
|
70
|
-
disabled
|
|
71
|
-
data-attr:disabled={`$${indicator} || !$${dirty}`}
|
|
72
|
-
>
|
|
73
|
-
<Spinner show={`$${indicator}`} />
|
|
74
|
-
{t({
|
|
75
|
-
message: "Save",
|
|
76
|
-
comment: "@context: Button to save settings",
|
|
77
|
-
})}
|
|
78
|
-
</button>
|
|
79
|
-
<button
|
|
80
|
-
type="button"
|
|
81
|
-
class="btn-outline"
|
|
82
|
-
disabled
|
|
83
|
-
data-attr:disabled={`$${indicator} || !$${dirty}`}
|
|
84
|
-
data-on:click={resetExpr}
|
|
85
|
-
>
|
|
86
|
-
{t({
|
|
87
|
-
message: "Cancel",
|
|
88
|
-
comment:
|
|
89
|
-
"@context: Button to cancel unsaved changes and revert to original values",
|
|
90
|
-
})}
|
|
91
|
-
</button>
|
|
92
|
-
</div>
|
|
93
|
-
);
|
|
94
|
-
}
|
|
95
|
-
|
|
96
17
|
export function GeneralContent({
|
|
97
18
|
siteName,
|
|
98
19
|
siteDescription,
|
|
@@ -122,28 +43,129 @@ export function GeneralContent({
|
|
|
122
43
|
}) {
|
|
123
44
|
const { t } = useLingui();
|
|
124
45
|
|
|
125
|
-
const
|
|
126
|
-
{
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
46
|
+
const labels = JSON.stringify({
|
|
47
|
+
blogAvatar: t({
|
|
48
|
+
message: "Blog Avatar",
|
|
49
|
+
comment: "@context: Settings section heading for avatar",
|
|
50
|
+
}),
|
|
51
|
+
uploadAvatar: t({
|
|
52
|
+
message: "Upload Avatar",
|
|
53
|
+
comment: "@context: Button to upload avatar image",
|
|
54
|
+
}),
|
|
55
|
+
remove: t({
|
|
56
|
+
message: "Remove",
|
|
57
|
+
comment: "@context: Button to remove the blog avatar",
|
|
58
|
+
}),
|
|
59
|
+
avatarHelp: t({
|
|
60
|
+
message:
|
|
61
|
+
"This is used for your favicon and apple-touch-icon. For best results, upload a square image at least 180x180 pixels.",
|
|
62
|
+
comment: "@context: Help text for avatar upload",
|
|
63
|
+
}),
|
|
64
|
+
displayInHeader: t({
|
|
65
|
+
message: "Display avatar in my site header",
|
|
66
|
+
comment: "@context: Checkbox to show avatar in the site header",
|
|
67
|
+
}),
|
|
68
|
+
processing: t({
|
|
69
|
+
message: "Processing...",
|
|
70
|
+
comment:
|
|
71
|
+
"@context: Avatar upload button text while generating favicon variants",
|
|
72
|
+
}),
|
|
73
|
+
uploading: t({
|
|
74
|
+
message: "Uploading...",
|
|
75
|
+
comment: "@context: Avatar upload button text while uploading",
|
|
76
|
+
}),
|
|
77
|
+
uploadError: t({
|
|
78
|
+
message: "Upload failed. Please try again.",
|
|
79
|
+
comment: "@context: Error message when avatar upload fails",
|
|
80
|
+
}),
|
|
81
|
+
general: t({
|
|
82
|
+
message: "General",
|
|
83
|
+
comment: "@context: Settings section heading",
|
|
84
|
+
}),
|
|
85
|
+
siteName: t({
|
|
86
|
+
message: "Site Name",
|
|
87
|
+
comment: "@context: Settings form field",
|
|
88
|
+
}),
|
|
89
|
+
aboutBlog: t({
|
|
90
|
+
message: "About this blog",
|
|
91
|
+
comment: "@context: Settings form field for site description",
|
|
92
|
+
}),
|
|
93
|
+
aboutBlogHelp: t({
|
|
94
|
+
message:
|
|
95
|
+
"Displayed above your blog posts on the home page. Also used as the meta description. Markdown supported.",
|
|
96
|
+
comment: "@context: Help text for site description field",
|
|
97
|
+
}),
|
|
98
|
+
language: t({
|
|
99
|
+
message: "Language",
|
|
100
|
+
comment: "@context: Settings form field",
|
|
101
|
+
}),
|
|
102
|
+
defaultHomepageView: t({
|
|
103
|
+
message: "Default Homepage View",
|
|
104
|
+
comment: "@context: Settings form field",
|
|
105
|
+
}),
|
|
106
|
+
latest: t({
|
|
107
|
+
message: "Latest",
|
|
108
|
+
comment: "@context: Homepage view option - show latest posts",
|
|
109
|
+
}),
|
|
110
|
+
featured: t({
|
|
111
|
+
message: "Featured",
|
|
112
|
+
comment: "@context: Homepage view option - show featured posts",
|
|
113
|
+
}),
|
|
114
|
+
timeZone: t({
|
|
115
|
+
message: "Time Zone",
|
|
116
|
+
comment: "@context: Settings form field",
|
|
117
|
+
}),
|
|
118
|
+
siteFooter: t({
|
|
119
|
+
message: "Site Footer",
|
|
120
|
+
comment: "@context: Settings section heading for site footer",
|
|
121
|
+
}),
|
|
122
|
+
footerHelp: t({
|
|
123
|
+
message:
|
|
124
|
+
"Displayed at the bottom of all posts and pages. Markdown supported.",
|
|
125
|
+
comment: "@context: Help text for site footer field",
|
|
126
|
+
}),
|
|
127
|
+
markdownSupported: t({
|
|
128
|
+
message: "Markdown supported",
|
|
129
|
+
comment: "@context: Placeholder hint for markdown-enabled textareas",
|
|
130
|
+
}),
|
|
131
|
+
seo: t({
|
|
132
|
+
message: "SEO",
|
|
133
|
+
comment: "@context: Settings section heading for SEO",
|
|
134
|
+
}),
|
|
135
|
+
allowIndexing: t({
|
|
136
|
+
message: "It's OK for search engines to index my site",
|
|
137
|
+
comment: "@context: Checkbox for allowing search engine indexing",
|
|
138
|
+
}),
|
|
139
|
+
save: t({
|
|
140
|
+
message: "Save",
|
|
141
|
+
comment: "@context: Button to save settings",
|
|
142
|
+
}),
|
|
143
|
+
cancel: t({
|
|
144
|
+
message: "Cancel",
|
|
145
|
+
comment:
|
|
146
|
+
"@context: Button to cancel unsaved changes and revert to original values",
|
|
147
|
+
}),
|
|
148
|
+
}).replace(/</g, "\\u003c");
|
|
149
|
+
|
|
150
|
+
const timezonesJson = JSON.stringify(
|
|
151
|
+
timezones.map((tz) => ({ value: tz.value, label: tz.label })),
|
|
152
|
+
).replace(/</g, "\\u003c");
|
|
153
|
+
|
|
154
|
+
const languagesJson = JSON.stringify([
|
|
155
|
+
{ value: "en", label: "English" },
|
|
156
|
+
{ value: "zh-Hans", label: "\u7B80\u4F53\u4E2D\u6587" },
|
|
157
|
+
{ value: "zh-Hant", label: "\u7E41\u9AD4\u4E2D\u6587" },
|
|
158
|
+
]);
|
|
159
|
+
|
|
160
|
+
const initialData = JSON.stringify({
|
|
161
|
+
siteName,
|
|
162
|
+
siteDescription,
|
|
163
|
+
siteLanguage,
|
|
164
|
+
homeDefaultView,
|
|
165
|
+
timeZone,
|
|
166
|
+
siteFooter,
|
|
167
|
+
noindex,
|
|
168
|
+
}).replace(/</g, "\\u003c");
|
|
147
169
|
|
|
148
170
|
return (
|
|
149
171
|
<>
|
|
@@ -152,382 +174,41 @@ export function GeneralContent({
|
|
|
152
174
|
</h1>
|
|
153
175
|
<SettingsNav currentTab="general" />
|
|
154
176
|
|
|
155
|
-
<div class="flex flex-col
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
{t({
|
|
161
|
-
message: "Blog Avatar",
|
|
162
|
-
comment: "@context: Settings section heading for avatar",
|
|
163
|
-
})}
|
|
164
|
-
</h2>
|
|
165
|
-
</header>
|
|
166
|
-
<section class="flex flex-col gap-4">
|
|
167
|
-
<div class="flex items-center gap-4">
|
|
168
|
-
{siteAvatarUrl ? (
|
|
169
|
-
<img
|
|
170
|
-
src={siteAvatarUrl}
|
|
171
|
-
alt=""
|
|
172
|
-
class="rounded-full object-cover"
|
|
173
|
-
style="width:64px;height:64px"
|
|
174
|
-
/>
|
|
175
|
-
) : (
|
|
176
|
-
<div
|
|
177
|
-
class="rounded-full bg-muted flex items-center justify-center text-muted-foreground"
|
|
178
|
-
style="width:64px;height:64px"
|
|
179
|
-
>
|
|
180
|
-
<svg
|
|
181
|
-
xmlns="http://www.w3.org/2000/svg"
|
|
182
|
-
width="24"
|
|
183
|
-
height="24"
|
|
184
|
-
viewBox="0 0 24 24"
|
|
185
|
-
fill="none"
|
|
186
|
-
stroke="currentColor"
|
|
187
|
-
stroke-width="2"
|
|
188
|
-
stroke-linecap="round"
|
|
189
|
-
stroke-linejoin="round"
|
|
190
|
-
>
|
|
191
|
-
<rect width="18" height="18" x="3" y="3" rx="2" ry="2" />
|
|
192
|
-
<circle cx="9" cy="9" r="2" />
|
|
193
|
-
<path d="m21 15-3.086-3.086a2 2 0 0 0-2.828 0L6 21" />
|
|
194
|
-
</svg>
|
|
195
|
-
</div>
|
|
196
|
-
)}
|
|
197
|
-
<div class="flex flex-col gap-2">
|
|
198
|
-
<form
|
|
199
|
-
action="/dash/settings/avatar"
|
|
200
|
-
method="post"
|
|
201
|
-
enctype="multipart/form-data"
|
|
202
|
-
class="inline"
|
|
203
|
-
>
|
|
204
|
-
<label class="btn text-sm cursor-pointer">
|
|
205
|
-
{t({
|
|
206
|
-
message: "Upload Avatar",
|
|
207
|
-
comment: "@context: Button to upload avatar image",
|
|
208
|
-
})}
|
|
209
|
-
<input
|
|
210
|
-
type="file"
|
|
211
|
-
name="file"
|
|
212
|
-
accept="image/jpeg,image/png,image/gif,image/webp,image/svg+xml"
|
|
213
|
-
class="hidden"
|
|
214
|
-
data-avatar-upload
|
|
215
|
-
data-text-processing={t({
|
|
216
|
-
message: "Processing...",
|
|
217
|
-
comment:
|
|
218
|
-
"@context: Avatar upload button text while generating favicon variants",
|
|
219
|
-
})}
|
|
220
|
-
data-text-uploading={t({
|
|
221
|
-
message: "Uploading...",
|
|
222
|
-
comment:
|
|
223
|
-
"@context: Avatar upload button text while uploading",
|
|
224
|
-
})}
|
|
225
|
-
data-text-error={t({
|
|
226
|
-
message: "Upload failed. Please try again.",
|
|
227
|
-
comment:
|
|
228
|
-
"@context: Error message when avatar upload fails",
|
|
229
|
-
})}
|
|
230
|
-
/>
|
|
231
|
-
</label>
|
|
232
|
-
</form>
|
|
233
|
-
{siteAvatarUrl && (
|
|
234
|
-
<form
|
|
235
|
-
data-on:submit__prevent="@post('/dash/settings/avatar/remove')"
|
|
236
|
-
data-indicator="_removeAvatarLoading"
|
|
237
|
-
>
|
|
238
|
-
<button
|
|
239
|
-
type="submit"
|
|
240
|
-
class="btn-outline text-sm"
|
|
241
|
-
data-attr:disabled="$_removeAvatarLoading"
|
|
242
|
-
>
|
|
243
|
-
{t({
|
|
244
|
-
message: "Remove",
|
|
245
|
-
comment: "@context: Button to remove the blog avatar",
|
|
246
|
-
})}
|
|
247
|
-
</button>
|
|
248
|
-
</form>
|
|
249
|
-
)}
|
|
250
|
-
</div>
|
|
251
|
-
</div>
|
|
252
|
-
<p class="text-sm text-muted-foreground">
|
|
253
|
-
{t({
|
|
254
|
-
message:
|
|
255
|
-
"This is used for your favicon and apple-touch-icon. For best results, upload a square image at least 180x180 pixels.",
|
|
256
|
-
comment: "@context: Help text for avatar upload",
|
|
257
|
-
})}
|
|
258
|
-
</p>
|
|
259
|
-
<form
|
|
260
|
-
data-signals={avatarSignals}
|
|
261
|
-
data-on:submit__prevent="@post('/dash/settings/avatar/display')"
|
|
262
|
-
data-indicator="_avatarDisplayLoading"
|
|
263
|
-
>
|
|
264
|
-
<label class="flex items-center gap-2 cursor-pointer">
|
|
265
|
-
<input
|
|
266
|
-
type="checkbox"
|
|
267
|
-
class="checkbox"
|
|
268
|
-
data-bind="showHeaderAvatar"
|
|
269
|
-
data-on:change="$_avatarDisplayDirty = true"
|
|
270
|
-
checked={showHeaderAvatar || undefined}
|
|
271
|
-
value="true"
|
|
272
|
-
/>
|
|
273
|
-
<span>
|
|
274
|
-
{t({
|
|
275
|
-
message: "Display avatar in my site header",
|
|
276
|
-
comment:
|
|
277
|
-
"@context: Checkbox to show avatar in the site header",
|
|
278
|
-
})}
|
|
279
|
-
</span>
|
|
280
|
-
</label>
|
|
281
|
-
<FormActions
|
|
282
|
-
indicator="_avatarDisplayLoading"
|
|
283
|
-
dirty="_avatarDisplayDirty"
|
|
284
|
-
fields={["showHeaderAvatar"]}
|
|
285
|
-
/>
|
|
286
|
-
</form>
|
|
287
|
-
</section>
|
|
288
|
-
</div>
|
|
289
|
-
|
|
290
|
-
{/* General settings */}
|
|
291
|
-
<form
|
|
292
|
-
data-signals={generalSignals}
|
|
293
|
-
data-on:submit__prevent="@post('/dash/settings')"
|
|
294
|
-
data-indicator="_generalLoading"
|
|
177
|
+
<div class="flex flex-col max-w-lg">
|
|
178
|
+
<jant-settings-avatar
|
|
179
|
+
avatar-url={siteAvatarUrl}
|
|
180
|
+
show-in-header={showHeaderAvatar || undefined}
|
|
181
|
+
labels={labels}
|
|
295
182
|
>
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
message: "General",
|
|
301
|
-
comment: "@context: Settings section heading",
|
|
302
|
-
})}
|
|
303
|
-
</h2>
|
|
304
|
-
</header>
|
|
305
|
-
<section class="flex flex-col gap-4">
|
|
306
|
-
<div class="field">
|
|
307
|
-
<label class="label">
|
|
308
|
-
{t({
|
|
309
|
-
message: "Site Name",
|
|
310
|
-
comment: "@context: Settings form field",
|
|
311
|
-
})}
|
|
312
|
-
</label>
|
|
313
|
-
<input
|
|
314
|
-
type="text"
|
|
315
|
-
data-bind="siteName"
|
|
316
|
-
data-on:input="$_generalDirty = true"
|
|
317
|
-
class="input"
|
|
318
|
-
placeholder={siteNameFallback}
|
|
319
|
-
/>
|
|
320
|
-
</div>
|
|
321
|
-
|
|
322
|
-
<div class="field">
|
|
323
|
-
<label class="label">
|
|
324
|
-
{t({
|
|
325
|
-
message: "About this blog",
|
|
326
|
-
comment:
|
|
327
|
-
"@context: Settings form field for site description",
|
|
328
|
-
})}
|
|
329
|
-
</label>
|
|
330
|
-
<textarea
|
|
331
|
-
data-bind="siteDescription"
|
|
332
|
-
data-on:input="$_generalDirty = true"
|
|
333
|
-
class="textarea"
|
|
334
|
-
rows={3}
|
|
335
|
-
placeholder={siteDescriptionFallback}
|
|
336
|
-
>
|
|
337
|
-
{siteDescription}
|
|
338
|
-
</textarea>
|
|
339
|
-
<p class="text-sm text-muted-foreground mt-1">
|
|
340
|
-
{t({
|
|
341
|
-
message:
|
|
342
|
-
"This is displayed above your blog posts on your default home page. This is also used for the meta description on your home page.",
|
|
343
|
-
comment: "@context: Help text for site description field",
|
|
344
|
-
})}
|
|
345
|
-
</p>
|
|
346
|
-
</div>
|
|
347
|
-
|
|
348
|
-
<div class="field">
|
|
349
|
-
<label class="label">
|
|
350
|
-
{t({
|
|
351
|
-
message: "Language",
|
|
352
|
-
comment: "@context: Settings form field",
|
|
353
|
-
})}
|
|
354
|
-
</label>
|
|
355
|
-
<select
|
|
356
|
-
data-bind="siteLanguage"
|
|
357
|
-
data-on:change="$_generalDirty = true"
|
|
358
|
-
class="select"
|
|
359
|
-
>
|
|
360
|
-
<option value="en" selected={siteLanguage === "en"}>
|
|
361
|
-
English
|
|
362
|
-
</option>
|
|
363
|
-
<option value="zh-Hans" selected={siteLanguage === "zh-Hans"}>
|
|
364
|
-
简体中文
|
|
365
|
-
</option>
|
|
366
|
-
<option value="zh-Hant" selected={siteLanguage === "zh-Hant"}>
|
|
367
|
-
繁體中文
|
|
368
|
-
</option>
|
|
369
|
-
</select>
|
|
370
|
-
</div>
|
|
371
|
-
|
|
372
|
-
<div class="field">
|
|
373
|
-
<label class="label">
|
|
374
|
-
{t({
|
|
375
|
-
message: "Default Homepage View",
|
|
376
|
-
comment: "@context: Settings form field",
|
|
377
|
-
})}
|
|
378
|
-
</label>
|
|
379
|
-
<select
|
|
380
|
-
data-bind="homeDefaultView"
|
|
381
|
-
data-on:change="$_generalDirty = true"
|
|
382
|
-
class="select"
|
|
383
|
-
>
|
|
384
|
-
<option
|
|
385
|
-
value="latest"
|
|
386
|
-
selected={homeDefaultView === "latest"}
|
|
387
|
-
>
|
|
388
|
-
{t({
|
|
389
|
-
message: "Latest",
|
|
390
|
-
comment:
|
|
391
|
-
"@context: Homepage view option - show latest posts",
|
|
392
|
-
})}
|
|
393
|
-
</option>
|
|
394
|
-
<option
|
|
395
|
-
value="featured"
|
|
396
|
-
selected={homeDefaultView === "featured"}
|
|
397
|
-
>
|
|
398
|
-
{t({
|
|
399
|
-
message: "Featured",
|
|
400
|
-
comment:
|
|
401
|
-
"@context: Homepage view option - show featured posts",
|
|
402
|
-
})}
|
|
403
|
-
</option>
|
|
404
|
-
</select>
|
|
405
|
-
</div>
|
|
406
|
-
|
|
407
|
-
<div class="field">
|
|
408
|
-
<label class="label">
|
|
409
|
-
{t({
|
|
410
|
-
message: "Time Zone",
|
|
411
|
-
comment: "@context: Settings form field",
|
|
412
|
-
})}
|
|
413
|
-
</label>
|
|
414
|
-
<select
|
|
415
|
-
data-bind="timeZone"
|
|
416
|
-
data-on:change="$_generalDirty = true"
|
|
417
|
-
class="select"
|
|
418
|
-
>
|
|
419
|
-
{timezones.map((tz) => (
|
|
420
|
-
<option
|
|
421
|
-
key={tz.value}
|
|
422
|
-
value={tz.value}
|
|
423
|
-
selected={timeZone === tz.value}
|
|
424
|
-
>
|
|
425
|
-
{tz.label}
|
|
426
|
-
</option>
|
|
427
|
-
))}
|
|
428
|
-
</select>
|
|
429
|
-
</div>
|
|
430
|
-
<FormActions
|
|
431
|
-
indicator="_generalLoading"
|
|
432
|
-
dirty="_generalDirty"
|
|
433
|
-
fields={[
|
|
434
|
-
"siteName",
|
|
435
|
-
"siteDescription",
|
|
436
|
-
"siteLanguage",
|
|
437
|
-
"homeDefaultView",
|
|
438
|
-
"timeZone",
|
|
439
|
-
]}
|
|
440
|
-
/>
|
|
441
|
-
</section>
|
|
183
|
+
{/* SSR fallback skeleton */}
|
|
184
|
+
<div>
|
|
185
|
+
<h2 class="skel-label" />
|
|
186
|
+
<div class="skel-section-sm" />
|
|
442
187
|
</div>
|
|
443
|
-
</
|
|
188
|
+
</jant-settings-avatar>
|
|
444
189
|
|
|
445
|
-
|
|
446
|
-
<form
|
|
447
|
-
data-signals={footerSignals}
|
|
448
|
-
data-on:submit__prevent="@post('/dash/settings/footer')"
|
|
449
|
-
data-indicator="_footerLoading"
|
|
450
|
-
>
|
|
451
|
-
<div class="card">
|
|
452
|
-
<header>
|
|
453
|
-
<h2>
|
|
454
|
-
{t({
|
|
455
|
-
message: "Site Footer",
|
|
456
|
-
comment: "@context: Settings section heading for site footer",
|
|
457
|
-
})}
|
|
458
|
-
</h2>
|
|
459
|
-
</header>
|
|
460
|
-
<section class="flex flex-col gap-4">
|
|
461
|
-
<textarea
|
|
462
|
-
data-bind="siteFooter"
|
|
463
|
-
data-on:input="$_footerDirty = true"
|
|
464
|
-
class="textarea font-mono text-sm"
|
|
465
|
-
rows={4}
|
|
466
|
-
placeholder={t({
|
|
467
|
-
message: "Markdown supported",
|
|
468
|
-
comment: "@context: Placeholder for footer textarea",
|
|
469
|
-
})}
|
|
470
|
-
>
|
|
471
|
-
{siteFooter}
|
|
472
|
-
</textarea>
|
|
473
|
-
<p class="text-sm text-muted-foreground">
|
|
474
|
-
{t({
|
|
475
|
-
message:
|
|
476
|
-
"This is displayed at the bottom of all of your posts and pages. Markdown is supported.",
|
|
477
|
-
comment: "@context: Help text for site footer field",
|
|
478
|
-
})}
|
|
479
|
-
</p>
|
|
480
|
-
<FormActions
|
|
481
|
-
indicator="_footerLoading"
|
|
482
|
-
dirty="_footerDirty"
|
|
483
|
-
fields={["siteFooter"]}
|
|
484
|
-
/>
|
|
485
|
-
</section>
|
|
486
|
-
</div>
|
|
487
|
-
</form>
|
|
190
|
+
<hr class="my-8" />
|
|
488
191
|
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
192
|
+
<jant-settings-general
|
|
193
|
+
labels={labels}
|
|
194
|
+
timezones={timezonesJson}
|
|
195
|
+
languages={languagesJson}
|
|
196
|
+
sitename-fallback={siteNameFallback}
|
|
197
|
+
sitedescription-fallback={siteDescriptionFallback}
|
|
494
198
|
>
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
message: "SEO",
|
|
500
|
-
comment: "@context: Settings section heading for SEO",
|
|
501
|
-
})}
|
|
502
|
-
</h2>
|
|
503
|
-
</header>
|
|
504
|
-
<section>
|
|
505
|
-
<label class="flex items-center gap-2 cursor-pointer">
|
|
506
|
-
<input
|
|
507
|
-
type="checkbox"
|
|
508
|
-
class="checkbox"
|
|
509
|
-
data-bind="noindex"
|
|
510
|
-
data-on:change="$_seoDirty = true"
|
|
511
|
-
checked={!noindex || undefined}
|
|
512
|
-
value="true"
|
|
513
|
-
/>
|
|
514
|
-
<span>
|
|
515
|
-
{t({
|
|
516
|
-
message: "It's OK for search engines to index my site",
|
|
517
|
-
comment:
|
|
518
|
-
"@context: Checkbox for allowing search engine indexing",
|
|
519
|
-
})}
|
|
520
|
-
</span>
|
|
521
|
-
</label>
|
|
522
|
-
<FormActions
|
|
523
|
-
indicator="_seoLoading"
|
|
524
|
-
dirty="_seoDirty"
|
|
525
|
-
fields={["noindex"]}
|
|
526
|
-
/>
|
|
527
|
-
</section>
|
|
199
|
+
{/* SSR fallback skeleton */}
|
|
200
|
+
<div>
|
|
201
|
+
<h2 class="skel-label" />
|
|
202
|
+
<div class="skel-section-lg" />
|
|
528
203
|
</div>
|
|
529
|
-
</
|
|
204
|
+
</jant-settings-general>
|
|
530
205
|
</div>
|
|
206
|
+
|
|
207
|
+
<script
|
|
208
|
+
type="application/json"
|
|
209
|
+
id="settings-initial-data"
|
|
210
|
+
dangerouslySetInnerHTML={{ __html: initialData }}
|
|
211
|
+
/>
|
|
531
212
|
</>
|
|
532
213
|
);
|
|
533
214
|
}
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
import { useLingui } from "@lingui/react/macro";
|
|
6
6
|
|
|
7
|
-
export type SettingsTab = "general" | "
|
|
7
|
+
export type SettingsTab = "general" | "redirects" | "account";
|
|
8
8
|
|
|
9
9
|
export function SettingsNav({ currentTab }: { currentTab: SettingsTab }) {
|
|
10
10
|
const { t } = useLingui();
|
|
@@ -19,12 +19,12 @@ export function SettingsNav({ currentTab }: { currentTab: SettingsTab }) {
|
|
|
19
19
|
href: "/dash/settings",
|
|
20
20
|
},
|
|
21
21
|
{
|
|
22
|
-
id: "
|
|
22
|
+
id: "redirects",
|
|
23
23
|
label: t({
|
|
24
|
-
message: "
|
|
24
|
+
message: "Redirects",
|
|
25
25
|
comment: "@context: Settings sub-navigation tab",
|
|
26
26
|
}),
|
|
27
|
-
href: "/dash/settings/
|
|
27
|
+
href: "/dash/settings/redirects",
|
|
28
28
|
},
|
|
29
29
|
{
|
|
30
30
|
id: "account",
|