@jant/core 0.3.31 → 0.3.33
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 +1429 -1055
- package/package.json +2 -2
- package/src/__tests__/helpers/app.ts +6 -3
- package/src/__tests__/helpers/db.ts +3 -0
- 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 +2 -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
|
@@ -12,9 +12,6 @@ export function AccountContent({ userName }: { userName: string }) {
|
|
|
12
12
|
|
|
13
13
|
return (
|
|
14
14
|
<>
|
|
15
|
-
<h1 class="text-2xl font-semibold mb-2">
|
|
16
|
-
{t({ message: "Settings", comment: "@context: Dashboard heading" })}
|
|
17
|
-
</h1>
|
|
18
15
|
<SettingsNav currentTab="account" />
|
|
19
16
|
|
|
20
17
|
<div class="flex flex-col max-w-lg">
|
|
@@ -18,7 +18,6 @@ export function GeneralContent({
|
|
|
18
18
|
siteName,
|
|
19
19
|
siteDescription,
|
|
20
20
|
siteLanguage,
|
|
21
|
-
homeDefaultView,
|
|
22
21
|
siteNameFallback,
|
|
23
22
|
siteDescriptionFallback,
|
|
24
23
|
siteAvatarUrl,
|
|
@@ -31,7 +30,6 @@ export function GeneralContent({
|
|
|
31
30
|
siteName: string;
|
|
32
31
|
siteDescription: string;
|
|
33
32
|
siteLanguage: string;
|
|
34
|
-
homeDefaultView: string;
|
|
35
33
|
siteNameFallback: string;
|
|
36
34
|
siteDescriptionFallback: string;
|
|
37
35
|
siteAvatarUrl: string;
|
|
@@ -92,25 +90,13 @@ export function GeneralContent({
|
|
|
92
90
|
}),
|
|
93
91
|
aboutBlogHelp: t({
|
|
94
92
|
message:
|
|
95
|
-
"
|
|
93
|
+
"A short intro for search engines and feed readers. Plain text only.",
|
|
96
94
|
comment: "@context: Help text for site description field",
|
|
97
95
|
}),
|
|
98
96
|
language: t({
|
|
99
97
|
message: "Language",
|
|
100
98
|
comment: "@context: Settings form field",
|
|
101
99
|
}),
|
|
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
100
|
timeZone: t({
|
|
115
101
|
message: "Time Zone",
|
|
116
102
|
comment: "@context: Settings form field",
|
|
@@ -161,7 +147,6 @@ export function GeneralContent({
|
|
|
161
147
|
siteName,
|
|
162
148
|
siteDescription,
|
|
163
149
|
siteLanguage,
|
|
164
|
-
homeDefaultView,
|
|
165
150
|
timeZone,
|
|
166
151
|
siteFooter,
|
|
167
152
|
noindex,
|
|
@@ -169,9 +154,6 @@ export function GeneralContent({
|
|
|
169
154
|
|
|
170
155
|
return (
|
|
171
156
|
<>
|
|
172
|
-
<h1 class="text-2xl font-semibold mb-2">
|
|
173
|
-
{t({ message: "Settings", comment: "@context: Dashboard heading" })}
|
|
174
|
-
</h1>
|
|
175
157
|
<SettingsNav currentTab="general" />
|
|
176
158
|
|
|
177
159
|
<div class="flex flex-col max-w-lg">
|
|
@@ -37,16 +37,12 @@ export function SettingsNav({ currentTab }: { currentTab: SettingsTab }) {
|
|
|
37
37
|
];
|
|
38
38
|
|
|
39
39
|
return (
|
|
40
|
-
<nav class="
|
|
40
|
+
<nav class="dash-subnav">
|
|
41
41
|
{tabs.map((tab) => (
|
|
42
42
|
<a
|
|
43
43
|
key={tab.id}
|
|
44
44
|
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
|
-
}`}
|
|
45
|
+
class={tab.id === currentTab ? "active" : ""}
|
|
50
46
|
>
|
|
51
47
|
{tab.label}
|
|
52
48
|
</a>
|
package/src/ui/feed/NoteCard.tsx
CHANGED
|
@@ -42,10 +42,10 @@ export const NoteCard: FC<TimelineCardProps> = ({ post, compact }) => {
|
|
|
42
42
|
)}
|
|
43
43
|
{!compact && isArticle && post.summaryHasMore && (
|
|
44
44
|
<a
|
|
45
|
-
href={post.permalink}
|
|
45
|
+
href={`${post.permalink}#continue`}
|
|
46
46
|
class="text-sm text-muted-foreground hover:underline mt-1 inline-block"
|
|
47
47
|
>
|
|
48
|
-
|
|
48
|
+
Continue →
|
|
49
49
|
</a>
|
|
50
50
|
)}
|
|
51
51
|
<footer class="mt-2" data-post-meta>
|
|
@@ -24,122 +24,119 @@ function DashLayoutContent({
|
|
|
24
24
|
}: PropsWithChildren<Omit<DashLayoutProps, "c" | "title">>) {
|
|
25
25
|
const { t } = useLingui();
|
|
26
26
|
|
|
27
|
-
const
|
|
28
|
-
|
|
29
|
-
if (match) return match.test(currentPath);
|
|
30
|
-
return currentPath === path;
|
|
31
|
-
};
|
|
32
|
-
|
|
33
|
-
const navClass = (path: string, match?: RegExp) =>
|
|
34
|
-
`justify-start px-3 py-2 text-sm rounded-md ${
|
|
35
|
-
isActive(path, match)
|
|
36
|
-
? "bg-accent text-accent-foreground font-medium"
|
|
37
|
-
: "text-muted-foreground hover:bg-accent hover:text-accent-foreground"
|
|
38
|
-
}`;
|
|
27
|
+
const navClass = (match: RegExp) =>
|
|
28
|
+
`dash-header-link ${currentPath && match.test(currentPath) ? "dash-header-link-active" : ""}`;
|
|
39
29
|
|
|
40
30
|
return (
|
|
41
31
|
<div class="min-h-screen">
|
|
42
32
|
{/* Header */}
|
|
43
|
-
<header class="
|
|
44
|
-
<div class="container
|
|
45
|
-
<
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
33
|
+
<header class="dash-header">
|
|
34
|
+
<div class="container dash-header-inner">
|
|
35
|
+
<div class="dash-header-left">
|
|
36
|
+
<a id="site-name" href="/dash" class="dash-header-logo">
|
|
37
|
+
{siteName}
|
|
38
|
+
</a>
|
|
49
39
|
<a
|
|
50
40
|
href="/"
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
41
|
+
target="_blank"
|
|
42
|
+
rel="noopener noreferrer"
|
|
43
|
+
class="dash-header-site-link"
|
|
44
|
+
aria-label={t({
|
|
54
45
|
message: "View Site",
|
|
55
46
|
comment:
|
|
56
47
|
"@context: Dashboard header link to view the public site",
|
|
57
48
|
})}
|
|
58
|
-
</a>
|
|
59
|
-
<a
|
|
60
|
-
href="/signout"
|
|
61
|
-
class="text-sm text-muted-foreground hover:text-foreground"
|
|
62
49
|
>
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
50
|
+
<svg
|
|
51
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
52
|
+
width="14"
|
|
53
|
+
height="14"
|
|
54
|
+
viewBox="0 0 24 24"
|
|
55
|
+
fill="none"
|
|
56
|
+
stroke="currentColor"
|
|
57
|
+
stroke-width="2"
|
|
58
|
+
stroke-linecap="round"
|
|
59
|
+
stroke-linejoin="round"
|
|
60
|
+
>
|
|
61
|
+
<path d="M15 3h6v6" />
|
|
62
|
+
<path d="M10 14 21 3" />
|
|
63
|
+
<path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6" />
|
|
64
|
+
</svg>
|
|
67
65
|
</a>
|
|
68
|
-
</
|
|
69
|
-
</div>
|
|
70
|
-
</header>
|
|
66
|
+
</div>
|
|
71
67
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
{/* Sidebar */}
|
|
75
|
-
<aside class="sidebar-nav">
|
|
76
|
-
<nav class="flex flex-col gap-1">
|
|
77
|
-
<a href="/dash" class={navClass("/dash", /^\/dash$/)}>
|
|
78
|
-
{t({
|
|
79
|
-
message: "Dashboard",
|
|
80
|
-
comment: "@context: Dashboard navigation - main dashboard page",
|
|
81
|
-
})}
|
|
82
|
-
</a>
|
|
83
|
-
<a
|
|
84
|
-
href="/dash/posts"
|
|
85
|
-
class={navClass("/dash/posts", /^\/dash\/posts/)}
|
|
86
|
-
>
|
|
87
|
-
{t({
|
|
88
|
-
message: "Posts",
|
|
89
|
-
comment: "@context: Dashboard navigation - posts management",
|
|
90
|
-
})}
|
|
91
|
-
</a>
|
|
92
|
-
<a
|
|
93
|
-
href="/dash/pages"
|
|
94
|
-
class={navClass("/dash/pages", /^\/dash\/pages/)}
|
|
95
|
-
>
|
|
68
|
+
<nav class="dash-header-nav">
|
|
69
|
+
<a href="/dash/pages" class={navClass(/^\/dash\/pages/)}>
|
|
96
70
|
{t({
|
|
97
71
|
message: "Pages",
|
|
98
72
|
comment: "@context: Dashboard navigation - pages management",
|
|
99
73
|
})}
|
|
100
74
|
</a>
|
|
101
|
-
<a
|
|
102
|
-
href="/dash/media"
|
|
103
|
-
class={navClass("/dash/media", /^\/dash\/media/)}
|
|
104
|
-
>
|
|
105
|
-
{t({
|
|
106
|
-
message: "Media",
|
|
107
|
-
comment: "@context: Dashboard navigation - media library",
|
|
108
|
-
})}
|
|
109
|
-
</a>
|
|
110
|
-
<a
|
|
111
|
-
href="/dash/collections"
|
|
112
|
-
class={navClass("/dash/collections", /^\/dash\/collections/)}
|
|
113
|
-
>
|
|
114
|
-
{t({
|
|
115
|
-
message: "Collections",
|
|
116
|
-
comment:
|
|
117
|
-
"@context: Dashboard navigation - collections management",
|
|
118
|
-
})}
|
|
119
|
-
</a>
|
|
120
|
-
<a
|
|
121
|
-
href="/dash/appearance"
|
|
122
|
-
class={navClass("/dash/appearance", /^\/dash\/appearance/)}
|
|
123
|
-
>
|
|
75
|
+
<a href="/dash/appearance" class={navClass(/^\/dash\/appearance/)}>
|
|
124
76
|
{t({
|
|
125
77
|
message: "Appearance",
|
|
126
78
|
comment: "@context: Dashboard navigation - appearance settings",
|
|
127
79
|
})}
|
|
128
80
|
</a>
|
|
129
|
-
<a
|
|
130
|
-
href="/dash/settings"
|
|
131
|
-
class={navClass("/dash/settings", /^\/dash\/settings/)}
|
|
132
|
-
>
|
|
81
|
+
<a href="/dash/settings" class={navClass(/^\/dash\/settings/)}>
|
|
133
82
|
{t({
|
|
134
83
|
message: "Settings",
|
|
135
84
|
comment: "@context: Dashboard navigation - site settings",
|
|
136
85
|
})}
|
|
137
86
|
</a>
|
|
138
87
|
</nav>
|
|
139
|
-
</aside>
|
|
140
88
|
|
|
141
|
-
|
|
142
|
-
|
|
89
|
+
<div class="dropdown-menu">
|
|
90
|
+
<button
|
|
91
|
+
type="button"
|
|
92
|
+
id="dash-menu-trigger"
|
|
93
|
+
class="dash-header-menu-btn"
|
|
94
|
+
aria-haspopup="menu"
|
|
95
|
+
aria-controls="dash-menu"
|
|
96
|
+
aria-expanded="false"
|
|
97
|
+
aria-label={t({
|
|
98
|
+
message: "Menu",
|
|
99
|
+
comment: "@context: Dashboard header menu button",
|
|
100
|
+
})}
|
|
101
|
+
>
|
|
102
|
+
<svg
|
|
103
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
104
|
+
width="16"
|
|
105
|
+
height="16"
|
|
106
|
+
viewBox="0 0 24 24"
|
|
107
|
+
fill="currentColor"
|
|
108
|
+
>
|
|
109
|
+
<circle cx="5" cy="12" r="2" />
|
|
110
|
+
<circle cx="12" cy="12" r="2" />
|
|
111
|
+
<circle cx="19" cy="12" r="2" />
|
|
112
|
+
</svg>
|
|
113
|
+
</button>
|
|
114
|
+
<div
|
|
115
|
+
id="dash-menu-popover"
|
|
116
|
+
data-popover
|
|
117
|
+
data-align="end"
|
|
118
|
+
aria-hidden="true"
|
|
119
|
+
>
|
|
120
|
+
<div
|
|
121
|
+
role="menu"
|
|
122
|
+
id="dash-menu"
|
|
123
|
+
aria-labelledby="dash-menu-trigger"
|
|
124
|
+
>
|
|
125
|
+
<a href="/signout" role="menuitem">
|
|
126
|
+
{t({
|
|
127
|
+
message: "Sign Out",
|
|
128
|
+
comment: "@context: Dashboard menu item to sign out",
|
|
129
|
+
})}
|
|
130
|
+
</a>
|
|
131
|
+
</div>
|
|
132
|
+
</div>
|
|
133
|
+
</div>
|
|
134
|
+
</div>
|
|
135
|
+
</header>
|
|
136
|
+
|
|
137
|
+
{/* Main */}
|
|
138
|
+
<div class="container py-8">
|
|
139
|
+
<main>{children}</main>
|
|
143
140
|
</div>
|
|
144
141
|
</div>
|
|
145
142
|
);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Site Layout
|
|
3
3
|
*
|
|
4
|
-
* Vertical header: site name on top, custom nav links below
|
|
4
|
+
* Vertical header: site name on top, custom nav links below.
|
|
5
5
|
* Content area with browse filter tabs and compose prompt/dialog for authenticated users.
|
|
6
6
|
*/
|
|
7
7
|
|
|
@@ -27,12 +27,12 @@ function HeaderLink({ link }: { link: NavItemView }) {
|
|
|
27
27
|
|
|
28
28
|
export const SiteLayout: FC<PropsWithChildren<SiteLayoutProps>> = ({
|
|
29
29
|
siteName,
|
|
30
|
-
siteDescription,
|
|
31
30
|
links,
|
|
32
31
|
currentPath,
|
|
33
32
|
isAuthenticated,
|
|
34
33
|
collections,
|
|
35
34
|
homeDefaultView,
|
|
35
|
+
headerNavMaxVisible,
|
|
36
36
|
siteAvatarUrl,
|
|
37
37
|
showHeaderAvatar,
|
|
38
38
|
siteFooterHtml,
|
|
@@ -40,6 +40,7 @@ export const SiteLayout: FC<PropsWithChildren<SiteLayoutProps>> = ({
|
|
|
40
40
|
children,
|
|
41
41
|
}) => {
|
|
42
42
|
const { t } = useLingui();
|
|
43
|
+
const maxVisible = headerNavMaxVisible ?? 3;
|
|
43
44
|
|
|
44
45
|
const latestHref = homeDefaultView === "featured" ? "/latest" : "/";
|
|
45
46
|
const featuredHref = homeDefaultView === "featured" ? "/" : "/featured";
|
|
@@ -89,9 +90,66 @@ export const SiteLayout: FC<PropsWithChildren<SiteLayoutProps>> = ({
|
|
|
89
90
|
<div class="site-header-right">
|
|
90
91
|
{links.length > 0 && (
|
|
91
92
|
<nav class="site-header-nav">
|
|
92
|
-
{links.map((link) => (
|
|
93
|
+
{links.slice(0, maxVisible).map((link) => (
|
|
93
94
|
<HeaderLink key={link.id} link={link} />
|
|
94
95
|
))}
|
|
96
|
+
{links.length > maxVisible && (
|
|
97
|
+
<div class="dropdown-menu site-header-more">
|
|
98
|
+
<button
|
|
99
|
+
type="button"
|
|
100
|
+
id="site-nav-more-trigger"
|
|
101
|
+
class="site-header-more-btn"
|
|
102
|
+
aria-haspopup="menu"
|
|
103
|
+
aria-controls="site-nav-more-menu"
|
|
104
|
+
aria-expanded="false"
|
|
105
|
+
aria-label={t({
|
|
106
|
+
message: "More links",
|
|
107
|
+
comment:
|
|
108
|
+
"@context: Button to show overflow nav links",
|
|
109
|
+
})}
|
|
110
|
+
>
|
|
111
|
+
<svg
|
|
112
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
113
|
+
width="16"
|
|
114
|
+
height="16"
|
|
115
|
+
viewBox="0 0 24 24"
|
|
116
|
+
fill="currentColor"
|
|
117
|
+
>
|
|
118
|
+
<circle cx="5" cy="12" r="2" />
|
|
119
|
+
<circle cx="12" cy="12" r="2" />
|
|
120
|
+
<circle cx="19" cy="12" r="2" />
|
|
121
|
+
</svg>
|
|
122
|
+
</button>
|
|
123
|
+
<div
|
|
124
|
+
id="site-nav-more-popover"
|
|
125
|
+
data-popover
|
|
126
|
+
data-align="end"
|
|
127
|
+
aria-hidden="true"
|
|
128
|
+
>
|
|
129
|
+
<div
|
|
130
|
+
role="menu"
|
|
131
|
+
id="site-nav-more-menu"
|
|
132
|
+
aria-labelledby="site-nav-more-trigger"
|
|
133
|
+
>
|
|
134
|
+
{links.slice(maxVisible).map((link) => (
|
|
135
|
+
<a
|
|
136
|
+
key={link.id}
|
|
137
|
+
href={link.url}
|
|
138
|
+
role="menuitem"
|
|
139
|
+
{...(link.isExternal
|
|
140
|
+
? {
|
|
141
|
+
target: "_blank",
|
|
142
|
+
rel: "noopener noreferrer",
|
|
143
|
+
}
|
|
144
|
+
: {})}
|
|
145
|
+
>
|
|
146
|
+
{link.label}
|
|
147
|
+
</a>
|
|
148
|
+
))}
|
|
149
|
+
</div>
|
|
150
|
+
</div>
|
|
151
|
+
</div>
|
|
152
|
+
)}
|
|
95
153
|
</nav>
|
|
96
154
|
)}
|
|
97
155
|
<a
|
|
@@ -117,9 +175,6 @@ export const SiteLayout: FC<PropsWithChildren<SiteLayoutProps>> = ({
|
|
|
117
175
|
</a>
|
|
118
176
|
</div>
|
|
119
177
|
</div>
|
|
120
|
-
{isHomePage && siteDescription && (
|
|
121
|
-
<p class="site-description">{siteDescription}</p>
|
|
122
|
-
)}
|
|
123
178
|
</div>
|
|
124
179
|
</header>
|
|
125
180
|
|
|
@@ -137,22 +192,28 @@ export const SiteLayout: FC<PropsWithChildren<SiteLayoutProps>> = ({
|
|
|
137
192
|
<div class="site-container">
|
|
138
193
|
<div class="site-content">
|
|
139
194
|
{isHomePage && (
|
|
140
|
-
<
|
|
141
|
-
{
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
195
|
+
<div class="site-home-header">
|
|
196
|
+
{isAuthenticated && <ComposePrompt />}
|
|
197
|
+
<nav class="site-browse-nav">
|
|
198
|
+
{browseLinks.map((link, i) => (
|
|
199
|
+
<>
|
|
200
|
+
{i > 0 && (
|
|
201
|
+
<span class="site-browse-sep" aria-hidden="true">
|
|
202
|
+
/
|
|
203
|
+
</span>
|
|
204
|
+
)}
|
|
205
|
+
<a
|
|
206
|
+
key={link.href}
|
|
207
|
+
href={link.href}
|
|
208
|
+
class={`site-browse-link ${currentPath === link.href ? "site-browse-link-active" : ""}`}
|
|
209
|
+
>
|
|
210
|
+
{link.label}
|
|
211
|
+
</a>
|
|
212
|
+
</>
|
|
213
|
+
))}
|
|
214
|
+
</nav>
|
|
215
|
+
</div>
|
|
154
216
|
)}
|
|
155
|
-
{isHomePage && isAuthenticated && <ComposePrompt />}
|
|
156
217
|
{children}
|
|
157
218
|
</div>
|
|
158
219
|
</div>
|
package/src/lib/nav-reorder.ts
DELETED
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Navigation Link Reorder
|
|
3
|
-
*
|
|
4
|
-
* Initializes SortableJS on the navigation links list in the dashboard.
|
|
5
|
-
* Auto-detects the list element and only activates when present.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import Sortable from "sortablejs";
|
|
9
|
-
|
|
10
|
-
const list = document.getElementById("nav-links-list");
|
|
11
|
-
if (list) {
|
|
12
|
-
Sortable.create(list, {
|
|
13
|
-
animation: 150,
|
|
14
|
-
handle: "[data-id]",
|
|
15
|
-
onEnd() {
|
|
16
|
-
const ids = [...list.querySelectorAll<HTMLElement>("[data-id]")].map(
|
|
17
|
-
(el) => Number(el.dataset.id),
|
|
18
|
-
);
|
|
19
|
-
fetch("/dash/pages/reorder", {
|
|
20
|
-
method: "POST",
|
|
21
|
-
headers: { "Content-Type": "application/json" },
|
|
22
|
-
body: JSON.stringify({ ids }),
|
|
23
|
-
});
|
|
24
|
-
},
|
|
25
|
-
});
|
|
26
|
-
}
|
|
@@ -1,119 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Link creation/editing form
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import { useLingui } from "@lingui/react/macro";
|
|
6
|
-
import type { NavItem } from "../../../types.js";
|
|
7
|
-
|
|
8
|
-
export function LinkFormContent({
|
|
9
|
-
item,
|
|
10
|
-
isEdit,
|
|
11
|
-
}: {
|
|
12
|
-
item?: NavItem;
|
|
13
|
-
isEdit?: boolean;
|
|
14
|
-
}) {
|
|
15
|
-
const { t } = useLingui();
|
|
16
|
-
const title = isEdit
|
|
17
|
-
? t({ message: "Edit Link", comment: "@context: Page heading" })
|
|
18
|
-
: t({ message: "New Link", comment: "@context: Page heading" });
|
|
19
|
-
|
|
20
|
-
const signals = JSON.stringify({
|
|
21
|
-
label: item?.label ?? "",
|
|
22
|
-
url: item?.url ?? "",
|
|
23
|
-
}).replace(/</g, "\\u003c");
|
|
24
|
-
|
|
25
|
-
const action = isEdit ? `/dash/pages/links/${item?.id}` : "/dash/pages/links";
|
|
26
|
-
|
|
27
|
-
return (
|
|
28
|
-
<>
|
|
29
|
-
<h1 class="text-2xl font-semibold mb-6">{title}</h1>
|
|
30
|
-
|
|
31
|
-
<form
|
|
32
|
-
data-signals={signals}
|
|
33
|
-
data-on:submit__prevent={`@post('${action}')`}
|
|
34
|
-
data-indicator="_loading"
|
|
35
|
-
class="flex flex-col gap-4 max-w-lg"
|
|
36
|
-
>
|
|
37
|
-
<div class="field">
|
|
38
|
-
<label class="label">
|
|
39
|
-
{t({
|
|
40
|
-
message: "Label",
|
|
41
|
-
comment: "@context: Navigation link form field",
|
|
42
|
-
})}
|
|
43
|
-
</label>
|
|
44
|
-
<input
|
|
45
|
-
type="text"
|
|
46
|
-
data-bind="label"
|
|
47
|
-
class="input"
|
|
48
|
-
placeholder="Home"
|
|
49
|
-
required
|
|
50
|
-
/>
|
|
51
|
-
<p class="text-xs text-muted-foreground mt-1">
|
|
52
|
-
{t({
|
|
53
|
-
message: "Display text for the link",
|
|
54
|
-
comment: "@context: Navigation label help text",
|
|
55
|
-
})}
|
|
56
|
-
</p>
|
|
57
|
-
</div>
|
|
58
|
-
|
|
59
|
-
<div class="field">
|
|
60
|
-
<label class="label">
|
|
61
|
-
{t({
|
|
62
|
-
message: "URL",
|
|
63
|
-
comment: "@context: Navigation link form field",
|
|
64
|
-
})}
|
|
65
|
-
</label>
|
|
66
|
-
<input
|
|
67
|
-
type="text"
|
|
68
|
-
data-bind="url"
|
|
69
|
-
class="input"
|
|
70
|
-
placeholder="/archive or https://..."
|
|
71
|
-
required
|
|
72
|
-
/>
|
|
73
|
-
<p class="text-xs text-muted-foreground mt-1">
|
|
74
|
-
{t({
|
|
75
|
-
message:
|
|
76
|
-
"Path (e.g. /archive) or full URL (e.g. https://example.com)",
|
|
77
|
-
comment: "@context: Navigation URL help text",
|
|
78
|
-
})}
|
|
79
|
-
</p>
|
|
80
|
-
</div>
|
|
81
|
-
|
|
82
|
-
<div class="flex gap-2">
|
|
83
|
-
<button type="submit" class="btn" data-attr:disabled="$_loading">
|
|
84
|
-
<svg
|
|
85
|
-
data-show="$_loading"
|
|
86
|
-
style="display:none"
|
|
87
|
-
class="animate-spin size-4"
|
|
88
|
-
xmlns="http://www.w3.org/2000/svg"
|
|
89
|
-
viewBox="0 0 24 24"
|
|
90
|
-
fill="none"
|
|
91
|
-
stroke="currentColor"
|
|
92
|
-
stroke-width="2"
|
|
93
|
-
stroke-linecap="round"
|
|
94
|
-
stroke-linejoin="round"
|
|
95
|
-
role="status"
|
|
96
|
-
>
|
|
97
|
-
<path d="M21 12a9 9 0 1 1-6.219-8.56" />
|
|
98
|
-
</svg>
|
|
99
|
-
{isEdit
|
|
100
|
-
? t({
|
|
101
|
-
message: "Save Changes",
|
|
102
|
-
comment: "@context: Button to save edited navigation link",
|
|
103
|
-
})
|
|
104
|
-
: t({
|
|
105
|
-
message: "Create Link",
|
|
106
|
-
comment: "@context: Button to save new navigation link",
|
|
107
|
-
})}
|
|
108
|
-
</button>
|
|
109
|
-
<a href="/dash/pages" class="btn-outline">
|
|
110
|
-
{t({
|
|
111
|
-
message: "Cancel",
|
|
112
|
-
comment: "@context: Button to cancel form",
|
|
113
|
-
})}
|
|
114
|
-
</a>
|
|
115
|
-
</div>
|
|
116
|
-
</form>
|
|
117
|
-
</>
|
|
118
|
-
);
|
|
119
|
-
}
|