@jant/core 0.3.32 → 0.3.34
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 -1
- package/dist/client/client.js +1442 -989
- package/dist/index.js +1431 -1057
- package/package.json +1 -1
- package/src/__tests__/helpers/app.ts +6 -3
- package/src/__tests__/helpers/db.ts +3 -0
- package/src/app.tsx +1 -1
- package/src/client.ts +2 -1
- package/src/db/migrations/0011_add_path_registry.sql +23 -0
- package/src/db/schema.ts +12 -1
- package/src/i18n/locales/en.po +225 -91
- package/src/i18n/locales/en.ts +1 -1
- package/src/i18n/locales/zh-Hans.po +201 -152
- package/src/i18n/locales/zh-Hans.ts +1 -1
- package/src/i18n/locales/zh-Hant.po +201 -152
- package/src/i18n/locales/zh-Hant.ts +1 -1
- package/src/lib/__tests__/excerpt.test.ts +25 -0
- package/src/lib/__tests__/resolve-config.test.ts +26 -2
- package/src/lib/__tests__/timeline.test.ts +2 -1
- package/src/lib/compose-bridge.ts +30 -1
- package/src/lib/excerpt.ts +16 -7
- package/src/lib/nav-manager-bridge.ts +54 -0
- package/src/lib/navigation.ts +7 -4
- package/src/lib/render.tsx +5 -2
- package/src/lib/resolve-config.ts +7 -0
- package/src/lib/view.ts +42 -10
- package/src/middleware/error-handler.ts +16 -0
- package/src/routes/api/__tests__/posts.test.ts +80 -0
- package/src/routes/api/__tests__/settings.test.ts +1 -1
- package/src/routes/api/posts.ts +6 -29
- package/src/routes/api/upload.ts +2 -14
- package/src/routes/auth/__tests__/setup.test.ts +3 -2
- package/src/routes/auth/setup.tsx +1 -1
- package/src/routes/compose.tsx +13 -5
- package/src/routes/dash/__tests__/pages.test.ts +2 -1
- package/src/routes/dash/__tests__/settings-avatar.test.ts +151 -33
- package/src/routes/dash/appearance.tsx +71 -4
- package/src/routes/dash/collections.tsx +15 -21
- package/src/routes/dash/media.tsx +1 -13
- package/src/routes/dash/pages.tsx +5 -150
- package/src/routes/dash/posts.tsx +25 -32
- package/src/routes/dash/redirects.tsx +9 -11
- package/src/routes/dash/settings.tsx +29 -111
- package/src/routes/feed/__tests__/rss.test.ts +5 -1
- package/src/routes/pages/__tests__/collections.test.ts +2 -1
- package/src/routes/pages/__tests__/featured.test.ts +2 -1
- package/src/routes/pages/page.tsx +20 -25
- package/src/services/__tests__/collection.test.ts +2 -1
- package/src/services/__tests__/media.test.ts +78 -1
- package/src/services/__tests__/navigation.test.ts +2 -1
- package/src/services/__tests__/page.test.ts +78 -1
- package/src/services/__tests__/path-registry.test.ts +165 -0
- package/src/services/__tests__/post-timeline.test.ts +2 -1
- package/src/services/__tests__/post.test.ts +103 -1
- package/src/services/__tests__/redirect.test.ts +53 -4
- package/src/services/__tests__/search.test.ts +2 -1
- package/src/services/__tests__/settings.test.ts +153 -0
- package/src/services/index.ts +12 -4
- package/src/services/media.ts +72 -4
- package/src/services/page.ts +64 -17
- package/src/services/path-registry.ts +160 -0
- package/src/services/post.ts +119 -24
- package/src/services/redirect.ts +23 -3
- package/src/services/settings.ts +181 -0
- package/src/styles/components.css +135 -0
- package/src/styles/tokens.css +6 -1
- package/src/styles/ui.css +70 -26
- package/src/types/bindings.ts +1 -0
- package/src/types/config.ts +7 -2
- package/src/types/constants.ts +9 -1
- package/src/types/sortablejs.d.ts +8 -2
- package/src/types/views.ts +1 -1
- package/src/ui/color-themes.ts +31 -31
- package/src/ui/components/__tests__/jant-settings-avatar.test.ts +0 -3
- package/src/ui/components/__tests__/jant-settings-general.test.ts +2 -6
- package/src/ui/components/jant-compose-dialog.ts +3 -2
- package/src/ui/components/jant-compose-editor.ts +17 -2
- package/src/ui/components/jant-nav-manager.ts +1067 -0
- package/src/ui/components/jant-settings-general.ts +2 -35
- package/src/ui/components/nav-manager-types.ts +72 -0
- package/src/ui/components/settings-types.ts +0 -3
- package/src/ui/compose/ComposePrompt.tsx +3 -11
- package/src/ui/dash/appearance/AdvancedContent.tsx +0 -3
- package/src/ui/dash/appearance/AppearanceNav.tsx +12 -8
- package/src/ui/dash/appearance/ColorThemeContent.tsx +1 -4
- package/src/ui/dash/appearance/FontThemeContent.tsx +0 -3
- package/src/ui/dash/appearance/NavigationContent.tsx +302 -0
- package/src/ui/dash/pages/PagesContent.tsx +74 -0
- package/src/ui/dash/settings/AccountContent.tsx +0 -3
- package/src/ui/dash/settings/GeneralContent.tsx +1 -19
- package/src/ui/dash/settings/SettingsNav.tsx +2 -6
- package/src/ui/feed/NoteCard.tsx +2 -2
- package/src/ui/layouts/DashLayout.tsx +83 -86
- package/src/ui/layouts/SiteLayout.tsx +82 -21
- package/src/lib/nav-reorder.ts +0 -26
- package/src/ui/dash/pages/LinkFormContent.tsx +0 -119
- package/src/ui/dash/pages/UnifiedPagesContent.tsx +0 -203
|
@@ -35,7 +35,6 @@ export class JantSettingsGeneral extends LitElement {
|
|
|
35
35
|
_siteDescription: { state: true },
|
|
36
36
|
_siteFooter: { state: true },
|
|
37
37
|
_siteLanguage: { state: true },
|
|
38
|
-
_homeDefaultView: { state: true },
|
|
39
38
|
_timeZone: { state: true },
|
|
40
39
|
_origGeneral: { state: true },
|
|
41
40
|
_generalDirty: { state: true },
|
|
@@ -59,7 +58,6 @@ export class JantSettingsGeneral extends LitElement {
|
|
|
59
58
|
declare _siteDescription: string;
|
|
60
59
|
declare _siteFooter: string;
|
|
61
60
|
declare _siteLanguage: string;
|
|
62
|
-
declare _homeDefaultView: string;
|
|
63
61
|
declare _timeZone: string;
|
|
64
62
|
declare _origGeneral: Record<string, string>;
|
|
65
63
|
declare _generalDirty: boolean;
|
|
@@ -88,7 +86,6 @@ export class JantSettingsGeneral extends LitElement {
|
|
|
88
86
|
this._siteDescription = "";
|
|
89
87
|
this._siteFooter = "";
|
|
90
88
|
this._siteLanguage = "en";
|
|
91
|
-
this._homeDefaultView = "latest";
|
|
92
89
|
this._timeZone = "UTC";
|
|
93
90
|
this._origGeneral = {};
|
|
94
91
|
this._generalDirty = false;
|
|
@@ -105,7 +102,6 @@ export class JantSettingsGeneral extends LitElement {
|
|
|
105
102
|
siteName: string;
|
|
106
103
|
siteDescription: string;
|
|
107
104
|
siteLanguage: string;
|
|
108
|
-
homeDefaultView: string;
|
|
109
105
|
timeZone: string;
|
|
110
106
|
siteFooter: string;
|
|
111
107
|
noindex: boolean;
|
|
@@ -114,14 +110,12 @@ export class JantSettingsGeneral extends LitElement {
|
|
|
114
110
|
this._siteDescription = data.siteDescription;
|
|
115
111
|
this._siteFooter = data.siteFooter;
|
|
116
112
|
this._siteLanguage = data.siteLanguage;
|
|
117
|
-
this._homeDefaultView = data.homeDefaultView;
|
|
118
113
|
this._timeZone = data.timeZone;
|
|
119
114
|
this._origGeneral = {
|
|
120
115
|
siteName: data.siteName,
|
|
121
116
|
siteDescription: data.siteDescription,
|
|
122
117
|
siteFooter: data.siteFooter,
|
|
123
118
|
siteLanguage: data.siteLanguage,
|
|
124
|
-
homeDefaultView: data.homeDefaultView,
|
|
125
119
|
timeZone: data.timeZone,
|
|
126
120
|
};
|
|
127
121
|
|
|
@@ -137,7 +131,6 @@ export class JantSettingsGeneral extends LitElement {
|
|
|
137
131
|
siteDescription: this._siteDescription,
|
|
138
132
|
siteFooter: this._siteFooter,
|
|
139
133
|
siteLanguage: this._siteLanguage,
|
|
140
|
-
homeDefaultView: this._homeDefaultView,
|
|
141
134
|
timeZone: this._timeZone,
|
|
142
135
|
};
|
|
143
136
|
this._generalDirty = false;
|
|
@@ -166,7 +159,6 @@ export class JantSettingsGeneral extends LitElement {
|
|
|
166
159
|
this._siteDescription = this._origGeneral.siteDescription ?? "";
|
|
167
160
|
this._siteFooter = this._origGeneral.siteFooter ?? "";
|
|
168
161
|
this._siteLanguage = this._origGeneral.siteLanguage ?? "en";
|
|
169
|
-
this._homeDefaultView = this._origGeneral.homeDefaultView ?? "latest";
|
|
170
162
|
this._timeZone = this._origGeneral.timeZone ?? "UTC";
|
|
171
163
|
this._generalDirty = false;
|
|
172
164
|
}
|
|
@@ -184,7 +176,6 @@ export class JantSettingsGeneral extends LitElement {
|
|
|
184
176
|
siteDescription: this._siteDescription,
|
|
185
177
|
siteFooter: this._siteFooter,
|
|
186
178
|
siteLanguage: this._siteLanguage,
|
|
187
|
-
homeDefaultView: this._homeDefaultView,
|
|
188
179
|
timeZone: this._timeZone,
|
|
189
180
|
},
|
|
190
181
|
section: "general",
|
|
@@ -288,9 +279,9 @@ export class JantSettingsGeneral extends LitElement {
|
|
|
288
279
|
<label class="label">${this.labels.aboutBlog}</label>
|
|
289
280
|
<textarea
|
|
290
281
|
class="textarea"
|
|
291
|
-
rows="
|
|
282
|
+
rows="2"
|
|
292
283
|
.value=${this._siteDescription}
|
|
293
|
-
placeholder=${this.
|
|
284
|
+
placeholder=${this.siteDescriptionFallback}
|
|
294
285
|
@input=${(e: Event) => {
|
|
295
286
|
this._siteDescription = (e.target as HTMLTextAreaElement).value;
|
|
296
287
|
this._markGeneralDirty();
|
|
@@ -340,30 +331,6 @@ export class JantSettingsGeneral extends LitElement {
|
|
|
340
331
|
</select>
|
|
341
332
|
</div>
|
|
342
333
|
|
|
343
|
-
<div class="field">
|
|
344
|
-
<label class="label">${this.labels.defaultHomepageView}</label>
|
|
345
|
-
<select
|
|
346
|
-
class="select"
|
|
347
|
-
@change=${(e: Event) => {
|
|
348
|
-
this._homeDefaultView = (e.target as HTMLSelectElement).value;
|
|
349
|
-
this._markGeneralDirty();
|
|
350
|
-
}}
|
|
351
|
-
>
|
|
352
|
-
<option
|
|
353
|
-
value="latest"
|
|
354
|
-
?selected=${this._homeDefaultView === "latest"}
|
|
355
|
-
>
|
|
356
|
-
${this.labels.latest}
|
|
357
|
-
</option>
|
|
358
|
-
<option
|
|
359
|
-
value="featured"
|
|
360
|
-
?selected=${this._homeDefaultView === "featured"}
|
|
361
|
-
>
|
|
362
|
-
${this.labels.featured}
|
|
363
|
-
</option>
|
|
364
|
-
</select>
|
|
365
|
-
</div>
|
|
366
|
-
|
|
367
334
|
<div class="field">
|
|
368
335
|
<label class="label">${this.labels.timeZone}</label>
|
|
369
336
|
<select
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared type definitions for the nav manager Lit component.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export interface NavManagerItem {
|
|
6
|
+
id: number;
|
|
7
|
+
type: "page" | "link" | "system";
|
|
8
|
+
label: string;
|
|
9
|
+
url: string;
|
|
10
|
+
pageId: number | null;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface SystemNavConfig {
|
|
14
|
+
key: string;
|
|
15
|
+
defaultLabel: string;
|
|
16
|
+
url: string;
|
|
17
|
+
description: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface AvailablePage {
|
|
21
|
+
id: number;
|
|
22
|
+
title: string;
|
|
23
|
+
slug: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface NavManagerLabels {
|
|
27
|
+
preview: string;
|
|
28
|
+
navigationItems: string;
|
|
29
|
+
emptyState: string;
|
|
30
|
+
page: string;
|
|
31
|
+
link: string;
|
|
32
|
+
system: string;
|
|
33
|
+
toggleEdit: string;
|
|
34
|
+
label: string;
|
|
35
|
+
url: string;
|
|
36
|
+
save: string;
|
|
37
|
+
delete: string;
|
|
38
|
+
editPage: string;
|
|
39
|
+
remove: string;
|
|
40
|
+
orderSaved: string;
|
|
41
|
+
labelRequired: string;
|
|
42
|
+
saveFailed: string;
|
|
43
|
+
deleteFailed: string;
|
|
44
|
+
systemLinks: string;
|
|
45
|
+
systemLinksDescription: string;
|
|
46
|
+
addPageToNavigation: string;
|
|
47
|
+
addCustomLinkToNavigation: string;
|
|
48
|
+
choosePage: string;
|
|
49
|
+
searchPages: string;
|
|
50
|
+
noPagesFound: string;
|
|
51
|
+
addLink: string;
|
|
52
|
+
addLinkDescription: string;
|
|
53
|
+
allPagesInNav: string;
|
|
54
|
+
urlPlaceholder: string;
|
|
55
|
+
labelAndUrlRequired: string;
|
|
56
|
+
maxVisibleLinks: string;
|
|
57
|
+
maxVisibleSaved: string;
|
|
58
|
+
useFeaturedAsDefault: string;
|
|
59
|
+
homeViewSaved: string;
|
|
60
|
+
latest: string;
|
|
61
|
+
featured: string;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export interface NavManagerUpdateDetail {
|
|
65
|
+
id: number;
|
|
66
|
+
label: string;
|
|
67
|
+
url?: string;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export interface NavManagerDeleteDetail {
|
|
71
|
+
id: number;
|
|
72
|
+
}
|
|
@@ -30,7 +30,9 @@ export const ComposePrompt: FC = () => {
|
|
|
30
30
|
stroke-linecap="round"
|
|
31
31
|
stroke-linejoin="round"
|
|
32
32
|
>
|
|
33
|
-
<path d="
|
|
33
|
+
<path d="M20.24 12.24a6 6 0 0 0-8.49-8.49L5 10.5V19h8.5z" />
|
|
34
|
+
<line x1="16" y1="8" x2="2" y2="22" />
|
|
35
|
+
<line x1="17.5" y1="15" x2="9" y2="15" />
|
|
34
36
|
</svg>
|
|
35
37
|
</span>
|
|
36
38
|
<span class="compose-prompt-text">
|
|
@@ -40,16 +42,6 @@ export const ComposePrompt: FC = () => {
|
|
|
40
42
|
})}
|
|
41
43
|
</span>
|
|
42
44
|
</button>
|
|
43
|
-
<button
|
|
44
|
-
type="button"
|
|
45
|
-
class="compose-prompt-post-btn"
|
|
46
|
-
onclick="const d=document.getElementById('compose-dialog');d.showModal();d.querySelector('jant-compose-editor')?.focusInput()"
|
|
47
|
-
>
|
|
48
|
-
{t({
|
|
49
|
-
message: "Post",
|
|
50
|
-
comment: "@context: Compose prompt post button",
|
|
51
|
-
})}
|
|
52
|
-
</button>
|
|
53
45
|
</div>
|
|
54
46
|
);
|
|
55
47
|
};
|
|
@@ -12,9 +12,6 @@ export function AdvancedContent({ customCSS }: { customCSS: string }) {
|
|
|
12
12
|
|
|
13
13
|
return (
|
|
14
14
|
<>
|
|
15
|
-
<h1 class="text-2xl font-semibold mb-2">
|
|
16
|
-
{t({ message: "Appearance", comment: "@context: Dashboard heading" })}
|
|
17
|
-
</h1>
|
|
18
15
|
<AppearanceNav currentTab="advanced" />
|
|
19
16
|
|
|
20
17
|
<form
|
|
@@ -4,19 +4,27 @@
|
|
|
4
4
|
|
|
5
5
|
import { useLingui } from "@lingui/react/macro";
|
|
6
6
|
|
|
7
|
-
export type AppearanceTab = "color" | "fonts" | "advanced";
|
|
7
|
+
export type AppearanceTab = "navigation" | "color" | "fonts" | "advanced";
|
|
8
8
|
|
|
9
9
|
export function AppearanceNav({ currentTab }: { currentTab: AppearanceTab }) {
|
|
10
10
|
const { t } = useLingui();
|
|
11
11
|
|
|
12
12
|
const tabs: { id: AppearanceTab; label: string; href: string }[] = [
|
|
13
|
+
{
|
|
14
|
+
id: "navigation",
|
|
15
|
+
label: t({
|
|
16
|
+
message: "Navigation",
|
|
17
|
+
comment: "@context: Appearance sub-navigation tab",
|
|
18
|
+
}),
|
|
19
|
+
href: "/dash/appearance",
|
|
20
|
+
},
|
|
13
21
|
{
|
|
14
22
|
id: "color",
|
|
15
23
|
label: t({
|
|
16
24
|
message: "Color Theme",
|
|
17
25
|
comment: "@context: Appearance sub-navigation tab",
|
|
18
26
|
}),
|
|
19
|
-
href: "/dash/appearance",
|
|
27
|
+
href: "/dash/appearance/color",
|
|
20
28
|
},
|
|
21
29
|
{
|
|
22
30
|
id: "fonts",
|
|
@@ -37,16 +45,12 @@ export function AppearanceNav({ currentTab }: { currentTab: AppearanceTab }) {
|
|
|
37
45
|
];
|
|
38
46
|
|
|
39
47
|
return (
|
|
40
|
-
<nav class="
|
|
48
|
+
<nav class="dash-subnav">
|
|
41
49
|
{tabs.map((tab) => (
|
|
42
50
|
<a
|
|
43
51
|
key={tab.id}
|
|
44
52
|
href={tab.href}
|
|
45
|
-
class={
|
|
46
|
-
tab.id === currentTab
|
|
47
|
-
? "bg-accent text-accent-foreground font-medium"
|
|
48
|
-
: "text-muted-foreground hover:bg-accent hover:text-accent-foreground"
|
|
49
|
-
}`}
|
|
53
|
+
class={tab.id === currentTab ? "active" : ""}
|
|
50
54
|
>
|
|
51
55
|
{tab.label}
|
|
52
56
|
</a>
|
|
@@ -88,14 +88,11 @@ export function ColorThemeContent({
|
|
|
88
88
|
|
|
89
89
|
return (
|
|
90
90
|
<>
|
|
91
|
-
<h1 class="text-2xl font-semibold mb-2">
|
|
92
|
-
{t({ message: "Appearance", comment: "@context: Dashboard heading" })}
|
|
93
|
-
</h1>
|
|
94
91
|
<AppearanceNav currentTab="color" />
|
|
95
92
|
|
|
96
93
|
<div
|
|
97
94
|
data-signals={themeSignals}
|
|
98
|
-
data-on:change="@post('/dash/appearance')"
|
|
95
|
+
data-on:change="@post('/dash/appearance/color')"
|
|
99
96
|
class="max-w-3xl"
|
|
100
97
|
>
|
|
101
98
|
<fieldset>
|
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Navigation management: Lit-powered reorderable nav items, add area, system toggles
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { useLingui } from "@lingui/react/macro";
|
|
6
|
+
import type { NavItem, Page, SystemNavKey } from "../../../types.js";
|
|
7
|
+
import { SYSTEM_NAV_KEYS } from "../../../types.js";
|
|
8
|
+
import type {
|
|
9
|
+
NavManagerLabels,
|
|
10
|
+
SystemNavConfig,
|
|
11
|
+
} from "../../components/nav-manager-types.js";
|
|
12
|
+
import { AppearanceNav } from "./AppearanceNav.js";
|
|
13
|
+
|
|
14
|
+
// =============================================================================
|
|
15
|
+
// System descriptions (used to build the config passed to the Lit component)
|
|
16
|
+
// =============================================================================
|
|
17
|
+
|
|
18
|
+
const SYSTEM_DESCRIPTIONS: Record<SystemNavKey, string> = {
|
|
19
|
+
rss: "Add a link to your RSS feed",
|
|
20
|
+
dashboard: "Shows 'Dashboard' when logged in, 'Sign in' when logged out",
|
|
21
|
+
collections: "Link to your collections page",
|
|
22
|
+
archive: "Link to the post archive",
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
// =============================================================================
|
|
26
|
+
// Main component
|
|
27
|
+
// =============================================================================
|
|
28
|
+
|
|
29
|
+
export function NavigationContent({
|
|
30
|
+
navItems,
|
|
31
|
+
availablePages,
|
|
32
|
+
headerNavMaxVisible,
|
|
33
|
+
homeDefaultView,
|
|
34
|
+
siteName,
|
|
35
|
+
}: {
|
|
36
|
+
navItems: NavItem[];
|
|
37
|
+
availablePages: Page[];
|
|
38
|
+
headerNavMaxVisible: number;
|
|
39
|
+
homeDefaultView: string;
|
|
40
|
+
siteName: string;
|
|
41
|
+
}) {
|
|
42
|
+
const { t } = useLingui();
|
|
43
|
+
|
|
44
|
+
// Serialize nav items for the Lit component
|
|
45
|
+
const itemsData = navItems.map((item) => ({
|
|
46
|
+
id: item.id,
|
|
47
|
+
type: item.type,
|
|
48
|
+
label: item.label,
|
|
49
|
+
url: item.url,
|
|
50
|
+
pageId: item.pageId,
|
|
51
|
+
}));
|
|
52
|
+
|
|
53
|
+
// Build system nav config array for the Lit component
|
|
54
|
+
const systemNavData: SystemNavConfig[] = (
|
|
55
|
+
Object.keys(SYSTEM_NAV_KEYS) as SystemNavKey[]
|
|
56
|
+
).map((key) => ({
|
|
57
|
+
key,
|
|
58
|
+
defaultLabel: SYSTEM_NAV_KEYS[key].defaultLabel,
|
|
59
|
+
url: SYSTEM_NAV_KEYS[key].url,
|
|
60
|
+
description: SYSTEM_DESCRIPTIONS[key],
|
|
61
|
+
}));
|
|
62
|
+
|
|
63
|
+
// Serialize available pages for the Lit component
|
|
64
|
+
const pagesData = availablePages.map((page) => ({
|
|
65
|
+
id: page.id,
|
|
66
|
+
title: page.title,
|
|
67
|
+
slug: page.slug,
|
|
68
|
+
}));
|
|
69
|
+
|
|
70
|
+
const labels: NavManagerLabels = {
|
|
71
|
+
preview: t({
|
|
72
|
+
message: "Preview",
|
|
73
|
+
comment: "@context: Label for nav preview section",
|
|
74
|
+
}),
|
|
75
|
+
navigationItems: t({
|
|
76
|
+
message: "Navigation items",
|
|
77
|
+
comment: "@context: Section heading for nav items",
|
|
78
|
+
}),
|
|
79
|
+
emptyState: t({
|
|
80
|
+
message:
|
|
81
|
+
"No navigation items yet. Add pages, links, or enable system items below.",
|
|
82
|
+
comment: "@context: Empty state for navigation items",
|
|
83
|
+
}),
|
|
84
|
+
page: t({ message: "page", comment: "@context: Nav item type badge" }),
|
|
85
|
+
link: t({ message: "link", comment: "@context: Nav item type badge" }),
|
|
86
|
+
system: t({
|
|
87
|
+
message: "system",
|
|
88
|
+
comment: "@context: Nav item type badge",
|
|
89
|
+
}),
|
|
90
|
+
toggleEdit: t({
|
|
91
|
+
message: "Toggle edit panel",
|
|
92
|
+
comment: "@context: Button to expand/collapse nav item edit",
|
|
93
|
+
}),
|
|
94
|
+
label: t({
|
|
95
|
+
message: "Label",
|
|
96
|
+
comment: "@context: Nav item label field",
|
|
97
|
+
}),
|
|
98
|
+
url: t({ message: "URL", comment: "@context: Nav item URL field" }),
|
|
99
|
+
save: t({
|
|
100
|
+
message: "Save",
|
|
101
|
+
comment: "@context: Save nav item changes",
|
|
102
|
+
}),
|
|
103
|
+
delete: t({
|
|
104
|
+
message: "Delete",
|
|
105
|
+
comment: "@context: Delete nav item",
|
|
106
|
+
}),
|
|
107
|
+
editPage: t({
|
|
108
|
+
message: "Edit Page",
|
|
109
|
+
comment: "@context: Link to edit the page",
|
|
110
|
+
}),
|
|
111
|
+
remove: t({
|
|
112
|
+
message: "Remove",
|
|
113
|
+
comment: "@context: Remove page from navigation",
|
|
114
|
+
}),
|
|
115
|
+
orderSaved: t({
|
|
116
|
+
message: "Order saved",
|
|
117
|
+
comment: "@context: Toast after saving navigation item order",
|
|
118
|
+
}),
|
|
119
|
+
labelRequired: t({
|
|
120
|
+
message: "Label is required",
|
|
121
|
+
comment: "@context: Error toast when nav label is empty",
|
|
122
|
+
}),
|
|
123
|
+
saveFailed: t({
|
|
124
|
+
message: "Failed to save. Please try again.",
|
|
125
|
+
comment: "@context: Error toast when nav save fails",
|
|
126
|
+
}),
|
|
127
|
+
deleteFailed: t({
|
|
128
|
+
message: "Failed to delete. Please try again.",
|
|
129
|
+
comment: "@context: Error toast when nav delete fails",
|
|
130
|
+
}),
|
|
131
|
+
systemLinks: t({
|
|
132
|
+
message: "System links",
|
|
133
|
+
comment: "@context: Section heading for system nav items",
|
|
134
|
+
}),
|
|
135
|
+
systemLinksDescription: t({
|
|
136
|
+
message:
|
|
137
|
+
"Toggle built-in navigation items. Enabled items appear in your navigation alongside pages and links.",
|
|
138
|
+
comment: "@context: Description for system nav toggles",
|
|
139
|
+
}),
|
|
140
|
+
addPageToNavigation: t({
|
|
141
|
+
message: "Add page to navigation",
|
|
142
|
+
comment: "@context: Section heading for adding page to nav",
|
|
143
|
+
}),
|
|
144
|
+
addCustomLinkToNavigation: t({
|
|
145
|
+
message: "Add custom link to navigation",
|
|
146
|
+
comment: "@context: Section heading for adding custom link to nav",
|
|
147
|
+
}),
|
|
148
|
+
choosePage: t({
|
|
149
|
+
message: "Choose a page…",
|
|
150
|
+
comment: "@context: Placeholder for page select combobox trigger",
|
|
151
|
+
}),
|
|
152
|
+
searchPages: t({
|
|
153
|
+
message: "Search pages…",
|
|
154
|
+
comment: "@context: Placeholder for page search input in combobox",
|
|
155
|
+
}),
|
|
156
|
+
noPagesFound: t({
|
|
157
|
+
message: "No pages found.",
|
|
158
|
+
comment: "@context: Empty state when page search has no results",
|
|
159
|
+
}),
|
|
160
|
+
addLink: t({
|
|
161
|
+
message: "Add Link",
|
|
162
|
+
comment: "@context: Button and heading for adding custom link",
|
|
163
|
+
}),
|
|
164
|
+
addLinkDescription: t({
|
|
165
|
+
message: "Add a custom link to any URL",
|
|
166
|
+
comment: "@context: Description in link popover form",
|
|
167
|
+
}),
|
|
168
|
+
allPagesInNav: t({
|
|
169
|
+
message: "All pages are already in navigation.",
|
|
170
|
+
comment: "@context: Message when no pages available to add",
|
|
171
|
+
}),
|
|
172
|
+
urlPlaceholder: "/archive or https://...",
|
|
173
|
+
maxVisibleLinks: t({
|
|
174
|
+
message: "Max visible links",
|
|
175
|
+
comment: "@context: Label for max visible nav links number input",
|
|
176
|
+
}),
|
|
177
|
+
maxVisibleSaved: t({
|
|
178
|
+
message: "Max visible links saved",
|
|
179
|
+
comment: "@context: Toast after saving max visible nav links setting",
|
|
180
|
+
}),
|
|
181
|
+
useFeaturedAsDefault: t({
|
|
182
|
+
message: "Use Featured as default home view",
|
|
183
|
+
comment:
|
|
184
|
+
"@context: Switch label for setting featured posts as default homepage",
|
|
185
|
+
}),
|
|
186
|
+
homeViewSaved: t({
|
|
187
|
+
message: "Home view saved",
|
|
188
|
+
comment: "@context: Toast after saving home default view setting",
|
|
189
|
+
}),
|
|
190
|
+
latest: t({
|
|
191
|
+
message: "Latest",
|
|
192
|
+
comment: "@context: Browse filter label for latest posts",
|
|
193
|
+
}),
|
|
194
|
+
featured: t({
|
|
195
|
+
message: "Featured",
|
|
196
|
+
comment: "@context: Browse filter label for featured posts",
|
|
197
|
+
}),
|
|
198
|
+
labelAndUrlRequired: t({
|
|
199
|
+
message: "Label and URL are required",
|
|
200
|
+
comment: "@context: Error toast when nav link fields are empty",
|
|
201
|
+
}),
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
const escapeJson = (data: unknown) =>
|
|
205
|
+
JSON.stringify(data).replace(/</g, "\\u003c");
|
|
206
|
+
|
|
207
|
+
return (
|
|
208
|
+
<>
|
|
209
|
+
<AppearanceNav currentTab="navigation" />
|
|
210
|
+
|
|
211
|
+
<div class="max-w-3xl flex flex-col gap-8">
|
|
212
|
+
<jant-nav-manager
|
|
213
|
+
items={escapeJson(itemsData)}
|
|
214
|
+
labels={escapeJson(labels)}
|
|
215
|
+
system-nav-items={escapeJson(systemNavData)}
|
|
216
|
+
available-pages={escapeJson(pagesData)}
|
|
217
|
+
site-name={siteName}
|
|
218
|
+
max-visible={headerNavMaxVisible}
|
|
219
|
+
home-default-view={homeDefaultView}
|
|
220
|
+
>
|
|
221
|
+
{/* SSR fallback: static preview until JS hydrates */}
|
|
222
|
+
<div class="border rounded-lg">
|
|
223
|
+
<p class="text-xs text-muted-foreground px-4 pt-3">
|
|
224
|
+
{t({
|
|
225
|
+
message: "Preview",
|
|
226
|
+
comment: "@context: Label for nav preview section",
|
|
227
|
+
})}
|
|
228
|
+
</p>
|
|
229
|
+
<div class="px-5 py-3">
|
|
230
|
+
<div class="site-header-top">
|
|
231
|
+
<a href="/" class="site-logo">
|
|
232
|
+
{siteName}
|
|
233
|
+
</a>
|
|
234
|
+
<div class="site-header-right">
|
|
235
|
+
{navItems.length > 0 && (
|
|
236
|
+
<nav class="site-header-nav">
|
|
237
|
+
{navItems.slice(0, headerNavMaxVisible).map((item) => (
|
|
238
|
+
<a
|
|
239
|
+
key={item.id}
|
|
240
|
+
href={item.url}
|
|
241
|
+
class="site-header-link"
|
|
242
|
+
>
|
|
243
|
+
{item.label}
|
|
244
|
+
</a>
|
|
245
|
+
))}
|
|
246
|
+
{navItems.length > headerNavMaxVisible && (
|
|
247
|
+
<span class="text-muted-foreground">…</span>
|
|
248
|
+
)}
|
|
249
|
+
</nav>
|
|
250
|
+
)}
|
|
251
|
+
<span class="site-header-search" aria-hidden="true">
|
|
252
|
+
<svg
|
|
253
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
254
|
+
width="16"
|
|
255
|
+
height="16"
|
|
256
|
+
viewBox="0 0 24 24"
|
|
257
|
+
fill="none"
|
|
258
|
+
stroke="currentColor"
|
|
259
|
+
stroke-width="2"
|
|
260
|
+
stroke-linecap="round"
|
|
261
|
+
stroke-linejoin="round"
|
|
262
|
+
>
|
|
263
|
+
<circle cx="11" cy="11" r="8" />
|
|
264
|
+
<path d="m21 21-4.35-4.35" />
|
|
265
|
+
</svg>
|
|
266
|
+
</span>
|
|
267
|
+
</div>
|
|
268
|
+
</div>
|
|
269
|
+
<nav class="site-browse-nav">
|
|
270
|
+
<span class="site-browse-link site-browse-link-active">
|
|
271
|
+
{homeDefaultView === "featured"
|
|
272
|
+
? t({
|
|
273
|
+
message: "Featured",
|
|
274
|
+
comment: "@context: Browse filter label",
|
|
275
|
+
})
|
|
276
|
+
: t({
|
|
277
|
+
message: "Latest",
|
|
278
|
+
comment: "@context: Browse filter label",
|
|
279
|
+
})}
|
|
280
|
+
</span>
|
|
281
|
+
<span class="site-browse-sep" aria-hidden="true">
|
|
282
|
+
/
|
|
283
|
+
</span>
|
|
284
|
+
<span class="site-browse-link">
|
|
285
|
+
{homeDefaultView === "featured"
|
|
286
|
+
? t({
|
|
287
|
+
message: "Latest",
|
|
288
|
+
comment: "@context: Browse filter label",
|
|
289
|
+
})
|
|
290
|
+
: t({
|
|
291
|
+
message: "Featured",
|
|
292
|
+
comment: "@context: Browse filter label",
|
|
293
|
+
})}
|
|
294
|
+
</span>
|
|
295
|
+
</nav>
|
|
296
|
+
</div>
|
|
297
|
+
</div>
|
|
298
|
+
</jant-nav-manager>
|
|
299
|
+
</div>
|
|
300
|
+
</>
|
|
301
|
+
);
|
|
302
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pages list — page CRUD only
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { useLingui } from "@lingui/react/macro";
|
|
6
|
+
import type { Page } from "../../../types.js";
|
|
7
|
+
import { ListItemRow, ActionButtons, CrudPageHeader } from "../index.js";
|
|
8
|
+
|
|
9
|
+
export function PagesContent({ pages }: { pages: Page[] }) {
|
|
10
|
+
const { t } = useLingui();
|
|
11
|
+
|
|
12
|
+
return (
|
|
13
|
+
<>
|
|
14
|
+
<CrudPageHeader
|
|
15
|
+
title={t({
|
|
16
|
+
message: "Pages",
|
|
17
|
+
comment: "@context: Pages main heading",
|
|
18
|
+
})}
|
|
19
|
+
>
|
|
20
|
+
<a href="/dash/pages/new" class="btn">
|
|
21
|
+
{t({
|
|
22
|
+
message: "New Page",
|
|
23
|
+
comment: "@context: Button to create new page",
|
|
24
|
+
})}
|
|
25
|
+
</a>
|
|
26
|
+
</CrudPageHeader>
|
|
27
|
+
|
|
28
|
+
{pages.length === 0 ? (
|
|
29
|
+
<p class="text-sm text-muted-foreground py-4">
|
|
30
|
+
{t({
|
|
31
|
+
message: "No pages yet. Create your first page to get started.",
|
|
32
|
+
comment: "@context: Empty state for pages list",
|
|
33
|
+
})}
|
|
34
|
+
</p>
|
|
35
|
+
) : (
|
|
36
|
+
<div class="flex flex-col divide-y">
|
|
37
|
+
{pages.map((page) => (
|
|
38
|
+
<ListItemRow
|
|
39
|
+
key={page.id}
|
|
40
|
+
actions={
|
|
41
|
+
<ActionButtons
|
|
42
|
+
editHref={`/dash/pages/${page.id}/edit`}
|
|
43
|
+
editLabel={t({
|
|
44
|
+
message: "Edit",
|
|
45
|
+
comment: "@context: Button to edit page",
|
|
46
|
+
})}
|
|
47
|
+
viewHref={
|
|
48
|
+
page.status !== "draft" ? `/${page.slug}` : undefined
|
|
49
|
+
}
|
|
50
|
+
viewLabel={t({
|
|
51
|
+
message: "View",
|
|
52
|
+
comment: "@context: Button to view page on public site",
|
|
53
|
+
})}
|
|
54
|
+
/>
|
|
55
|
+
}
|
|
56
|
+
>
|
|
57
|
+
<a
|
|
58
|
+
href={`/dash/pages/${page.id}`}
|
|
59
|
+
class="font-medium hover:underline"
|
|
60
|
+
>
|
|
61
|
+
{page.title ||
|
|
62
|
+
t({
|
|
63
|
+
message: "Untitled",
|
|
64
|
+
comment: "@context: Default title for untitled page",
|
|
65
|
+
})}
|
|
66
|
+
</a>
|
|
67
|
+
<p class="text-sm text-muted-foreground mt-1">/{page.slug}</p>
|
|
68
|
+
</ListItemRow>
|
|
69
|
+
))}
|
|
70
|
+
</div>
|
|
71
|
+
)}
|
|
72
|
+
</>
|
|
73
|
+
);
|
|
74
|
+
}
|