@jant/core 0.3.23 → 0.3.25
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/app.js +50 -26
- package/dist/db/schema.js +72 -47
- package/dist/i18n/locales/en.js +1 -1
- package/dist/i18n/locales/zh-Hans.js +1 -1
- package/dist/i18n/locales/zh-Hant.js +1 -1
- package/dist/index.js +5 -11
- package/dist/lib/constants.js +2 -4
- package/dist/lib/excerpt.js +76 -0
- package/dist/lib/feed.js +18 -7
- package/dist/lib/nav-reorder.js +1 -1
- package/dist/lib/navigation.js +30 -6
- package/dist/lib/pagination.js +44 -0
- package/dist/lib/render.js +7 -11
- package/dist/lib/schemas.js +80 -38
- package/dist/lib/theme.js +4 -4
- package/dist/lib/time.js +56 -1
- package/dist/lib/timeline.js +95 -0
- package/dist/lib/view.js +61 -72
- package/dist/routes/api/collections.js +124 -0
- package/dist/routes/api/nav-items.js +104 -0
- package/dist/routes/api/pages.js +91 -0
- package/dist/routes/api/posts.js +27 -33
- package/dist/routes/api/search.js +4 -5
- package/dist/routes/api/settings.js +68 -0
- package/dist/routes/api/upload.js +13 -13
- package/dist/routes/compose.js +48 -0
- package/dist/routes/dash/collections.js +24 -42
- package/dist/routes/dash/index.js +3 -3
- package/dist/routes/dash/media.js +2 -2
- package/dist/routes/dash/pages.js +440 -106
- package/dist/routes/dash/posts.js +27 -37
- package/dist/routes/dash/redirects.js +2 -2
- package/dist/routes/dash/settings.js +79 -5
- package/dist/routes/feed/rss.js +4 -6
- package/dist/routes/feed/sitemap.js +11 -8
- package/dist/routes/pages/archive.js +13 -15
- package/dist/routes/pages/collection.js +12 -9
- package/dist/routes/pages/collections.js +28 -0
- package/dist/routes/pages/featured.js +32 -0
- package/dist/routes/pages/home.js +19 -68
- package/dist/routes/pages/page.js +57 -29
- package/dist/routes/pages/post.js +7 -17
- package/dist/routes/pages/search.js +5 -9
- package/dist/services/collection.js +52 -64
- package/dist/services/index.js +5 -3
- package/dist/services/navigation.js +29 -53
- package/dist/services/page.js +84 -0
- package/dist/services/post.js +102 -69
- package/dist/services/search.js +24 -18
- package/dist/types.js +24 -40
- package/dist/ui/compose/ComposeDialog.js +452 -0
- package/dist/ui/compose/ComposePrompt.js +55 -0
- package/dist/{theme/components/TypeBadge.js → ui/dash/FormatBadge.js} +3 -15
- package/dist/{theme/components → ui/dash}/PageForm.js +15 -15
- package/dist/{theme/components → ui/dash}/PostForm.js +117 -137
- package/dist/{theme/components → ui/dash}/PostList.js +18 -13
- package/dist/ui/dash/StatusBadge.js +46 -0
- package/dist/{theme/components → ui/dash}/index.js +3 -6
- package/dist/ui/feed/LinkCard.js +72 -0
- package/dist/ui/feed/NoteCard.js +58 -0
- package/dist/{themes/minimal/timeline → ui/feed}/QuoteCard.js +29 -14
- package/dist/{themes/minimal/timeline → ui/feed}/ThreadPreview.js +20 -18
- package/dist/ui/feed/TimelineFeed.js +41 -0
- package/dist/ui/feed/TimelineItem.js +27 -0
- package/dist/{theme → ui}/layouts/BaseLayout.js +10 -0
- package/dist/{theme → ui}/layouts/DashLayout.js +0 -8
- package/dist/ui/layouts/SiteLayout.js +141 -0
- package/dist/{themes/minimal → ui}/pages/ArchivePage.js +37 -50
- package/dist/ui/pages/CollectionPage.js +70 -0
- package/dist/ui/pages/CollectionsPage.js +76 -0
- package/dist/ui/pages/FeaturedPage.js +24 -0
- package/dist/ui/pages/HomePage.js +24 -0
- package/dist/{themes/minimal → ui}/pages/PostPage.js +20 -12
- package/dist/{themes/minimal → ui}/pages/SearchPage.js +19 -18
- package/dist/{themes/minimal → ui}/pages/SinglePage.js +5 -4
- package/dist/ui/shared/MediaGallery.js +35 -0
- package/dist/{theme/components → ui/shared}/Pagination.js +41 -2
- package/dist/{theme/components → ui/shared}/ThreadView.js +3 -3
- package/dist/ui/shared/index.js +5 -0
- package/package.json +2 -9
- package/src/__tests__/helpers/app.ts +4 -0
- package/src/__tests__/helpers/db.ts +53 -73
- package/src/app.tsx +56 -28
- package/src/db/migrations/0005_v2_schema_migration.sql +268 -0
- package/src/db/migrations/0006_rename_slug_to_path.sql +5 -0
- package/src/db/migrations/meta/_journal.json +14 -0
- package/src/db/schema.ts +63 -46
- package/src/i18n/locales/en.po +443 -240
- package/src/i18n/locales/en.ts +1 -1
- package/src/i18n/locales/zh-Hans.po +443 -240
- package/src/i18n/locales/zh-Hans.ts +1 -1
- package/src/i18n/locales/zh-Hant.po +443 -240
- package/src/i18n/locales/zh-Hant.ts +1 -1
- package/src/index.ts +29 -42
- package/src/lib/__tests__/excerpt.test.ts +125 -0
- package/src/lib/__tests__/schemas.test.ts +201 -99
- package/src/lib/__tests__/time.test.ts +62 -0
- package/src/{routes/api → lib}/__tests__/timeline.test.ts +81 -75
- package/src/lib/__tests__/view.test.ts +204 -50
- package/src/lib/constants.ts +2 -4
- package/src/lib/excerpt.ts +87 -0
- package/src/lib/feed.ts +22 -7
- package/src/lib/nav-reorder.ts +1 -1
- package/src/lib/navigation.ts +45 -8
- package/src/lib/pagination.ts +50 -0
- package/src/lib/render.tsx +7 -14
- package/src/lib/schemas.ts +119 -51
- package/src/lib/theme.ts +5 -5
- package/src/lib/time.ts +64 -0
- package/src/lib/timeline.ts +141 -0
- package/src/lib/view.ts +80 -82
- package/src/preset.css +46 -0
- package/src/routes/__tests__/compose.test.ts +199 -0
- package/src/routes/api/__tests__/collections.test.ts +249 -0
- package/src/routes/api/__tests__/nav-items.test.ts +222 -0
- package/src/routes/api/__tests__/pages.test.ts +218 -0
- package/src/routes/api/__tests__/posts.test.ts +50 -108
- package/src/routes/api/__tests__/search.test.ts +2 -3
- package/src/routes/api/__tests__/settings.test.ts +132 -0
- package/src/routes/api/collections.ts +143 -0
- package/src/routes/api/nav-items.ts +115 -0
- package/src/routes/api/pages.ts +101 -0
- package/src/routes/api/posts.ts +28 -28
- package/src/routes/api/search.ts +3 -3
- package/src/routes/api/settings.ts +91 -0
- package/src/routes/api/upload.ts +16 -6
- package/src/routes/compose.ts +63 -0
- package/src/routes/dash/__tests__/pages.test.ts +225 -0
- package/src/routes/dash/collections.tsx +20 -42
- package/src/routes/dash/index.tsx +3 -3
- package/src/routes/dash/media.tsx +2 -2
- package/src/routes/dash/pages.tsx +480 -122
- package/src/routes/dash/posts.tsx +42 -54
- package/src/routes/dash/redirects.tsx +2 -2
- package/src/routes/dash/settings.tsx +83 -5
- package/src/routes/feed/rss.ts +4 -3
- package/src/routes/feed/sitemap.ts +15 -5
- package/src/routes/pages/__tests__/collections.test.ts +94 -0
- package/src/routes/pages/__tests__/featured.test.ts +94 -0
- package/src/routes/pages/archive.tsx +15 -15
- package/src/routes/pages/collection.tsx +16 -9
- package/src/routes/pages/collections.tsx +36 -0
- package/src/routes/pages/featured.tsx +38 -0
- package/src/routes/pages/home.tsx +21 -92
- package/src/routes/pages/page.tsx +62 -27
- package/src/routes/pages/post.tsx +6 -18
- package/src/routes/pages/search.tsx +3 -7
- package/src/services/__tests__/collection.test.ts +257 -158
- package/src/services/__tests__/media.test.ts +18 -18
- package/src/services/__tests__/navigation.test.ts +161 -87
- package/src/services/__tests__/page.test.ts +106 -0
- package/src/services/__tests__/post-timeline.test.ts +92 -88
- package/src/services/__tests__/post.test.ts +432 -197
- package/src/services/__tests__/search.test.ts +19 -25
- package/src/services/collection.ts +71 -113
- package/src/services/index.ts +9 -8
- package/src/services/navigation.ts +38 -71
- package/src/services/page.ts +136 -0
- package/src/services/post.ts +141 -101
- package/src/services/search.ts +38 -27
- package/src/styles/tokens.css +47 -0
- package/src/styles/ui.css +491 -0
- package/src/types.ts +212 -198
- package/src/ui/compose/ComposeDialog.tsx +395 -0
- package/src/ui/compose/ComposePrompt.tsx +55 -0
- package/src/ui/dash/FormatBadge.tsx +28 -0
- package/src/{theme/components → ui/dash}/PageForm.tsx +21 -21
- package/src/{theme/components → ui/dash}/PostForm.tsx +110 -131
- package/src/ui/dash/PostList.tsx +101 -0
- package/src/ui/dash/StatusBadge.tsx +61 -0
- package/src/ui/dash/index.ts +10 -0
- package/src/ui/feed/LinkCard.tsx +72 -0
- package/src/ui/feed/NoteCard.tsx +63 -0
- package/src/ui/feed/QuoteCard.tsx +68 -0
- package/src/ui/feed/ThreadPreview.tsx +48 -0
- package/src/ui/feed/TimelineFeed.tsx +49 -0
- package/src/ui/feed/TimelineItem.tsx +45 -0
- package/src/{theme → ui}/layouts/BaseLayout.tsx +11 -1
- package/src/{theme → ui}/layouts/DashLayout.tsx +0 -10
- package/src/ui/layouts/SiteLayout.tsx +150 -0
- package/src/ui/pages/ArchivePage.tsx +162 -0
- package/src/ui/pages/CollectionPage.tsx +70 -0
- package/src/ui/pages/CollectionsPage.tsx +73 -0
- package/src/ui/pages/FeaturedPage.tsx +31 -0
- package/src/ui/pages/HomePage.tsx +37 -0
- package/src/ui/pages/PostPage.tsx +56 -0
- package/src/{themes/minimal → ui}/pages/SearchPage.tsx +24 -20
- package/src/{themes/minimal → ui}/pages/SinglePage.tsx +5 -5
- package/src/ui/shared/MediaGallery.tsx +59 -0
- package/src/{theme/components → ui/shared}/Pagination.tsx +67 -4
- package/src/{theme/components → ui/shared}/ThreadView.tsx +6 -3
- package/src/ui/shared/__tests__/pagination.test.ts +46 -0
- package/src/ui/shared/index.ts +12 -0
- package/bin/jant.js +0 -185
- package/dist/lib/theme-components.js +0 -49
- package/dist/routes/api/timeline.js +0 -120
- package/dist/routes/dash/navigation.js +0 -288
- package/dist/theme/components/MediaGallery.js +0 -107
- package/dist/theme/components/VisibilityBadge.js +0 -37
- package/dist/theme/index.js +0 -18
- package/dist/theme/layouts/index.js +0 -2
- package/dist/themes/minimal/MinimalSiteLayout.js +0 -83
- package/dist/themes/minimal/index.js +0 -65
- package/dist/themes/minimal/pages/CollectionPage.js +0 -65
- package/dist/themes/minimal/pages/HomePage.js +0 -25
- package/dist/themes/minimal/timeline/ArticleCard.js +0 -36
- package/dist/themes/minimal/timeline/ImageCard.js +0 -67
- package/dist/themes/minimal/timeline/LinkCard.js +0 -47
- package/dist/themes/minimal/timeline/NoteCard.js +0 -34
- package/dist/themes/minimal/timeline/TimelineFeed.js +0 -48
- package/dist/themes/minimal/timeline/TimelineItem.js +0 -44
- package/src/lib/__tests__/theme-components.test.ts +0 -126
- package/src/lib/theme-components.ts +0 -68
- package/src/routes/api/timeline.tsx +0 -159
- package/src/routes/dash/navigation.tsx +0 -316
- package/src/theme/components/MediaGallery.tsx +0 -128
- package/src/theme/components/PostList.tsx +0 -92
- package/src/theme/components/TypeBadge.tsx +0 -37
- package/src/theme/components/VisibilityBadge.tsx +0 -45
- package/src/theme/components/index.ts +0 -23
- package/src/theme/index.ts +0 -22
- package/src/theme/layouts/index.ts +0 -7
- package/src/themes/minimal/MinimalSiteLayout.tsx +0 -100
- package/src/themes/minimal/index.ts +0 -83
- package/src/themes/minimal/pages/ArchivePage.tsx +0 -157
- package/src/themes/minimal/pages/CollectionPage.tsx +0 -60
- package/src/themes/minimal/pages/HomePage.tsx +0 -41
- package/src/themes/minimal/pages/PostPage.tsx +0 -43
- package/src/themes/minimal/timeline/ArticleCard.tsx +0 -37
- package/src/themes/minimal/timeline/ImageCard.tsx +0 -63
- package/src/themes/minimal/timeline/LinkCard.tsx +0 -48
- package/src/themes/minimal/timeline/NoteCard.tsx +0 -35
- package/src/themes/minimal/timeline/QuoteCard.tsx +0 -49
- package/src/themes/minimal/timeline/ThreadPreview.tsx +0 -47
- package/src/themes/minimal/timeline/TimelineFeed.tsx +0 -57
- package/src/themes/minimal/timeline/TimelineItem.tsx +0 -75
- /package/dist/{theme → ui}/color-themes.js +0 -0
- /package/dist/{theme/components → ui/dash}/ActionButtons.js +0 -0
- /package/dist/{theme/components → ui/dash}/CrudPageHeader.js +0 -0
- /package/dist/{theme/components → ui/dash}/DangerZone.js +0 -0
- /package/dist/{theme/components → ui/dash}/ListItemRow.js +0 -0
- /package/dist/{theme/components → ui/shared}/EmptyState.js +0 -0
- /package/src/{theme → ui}/color-themes.ts +0 -0
- /package/src/{theme/components → ui/dash}/ActionButtons.tsx +0 -0
- /package/src/{theme/components → ui/dash}/CrudPageHeader.tsx +0 -0
- /package/src/{theme/components → ui/dash}/DangerZone.tsx +0 -0
- /package/src/{theme/components → ui/dash}/ListItemRow.tsx +0 -0
- /package/src/{theme/components → ui/shared}/EmptyState.tsx +0 -0
|
@@ -1,316 +0,0 @@
|
|
|
1
|
-
import { getSiteName } from "../../lib/config.js";
|
|
2
|
-
/**
|
|
3
|
-
* Dashboard Navigation Links Routes
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { Hono } from "hono";
|
|
7
|
-
import { useLingui } from "@lingui/react/macro";
|
|
8
|
-
import type { Bindings, NavigationLink } from "../../types.js";
|
|
9
|
-
import type { AppVariables } from "../../app.js";
|
|
10
|
-
import { DashLayout } from "../../theme/layouts/index.js";
|
|
11
|
-
import {
|
|
12
|
-
EmptyState,
|
|
13
|
-
ListItemRow,
|
|
14
|
-
ActionButtons,
|
|
15
|
-
CrudPageHeader,
|
|
16
|
-
} from "../../theme/components/index.js";
|
|
17
|
-
import { dsRedirect, dsToast } from "../../lib/sse.js";
|
|
18
|
-
|
|
19
|
-
type Env = { Bindings: Bindings; Variables: AppVariables };
|
|
20
|
-
|
|
21
|
-
export const navigationRoutes = new Hono<Env>();
|
|
22
|
-
|
|
23
|
-
function NavigationListContent({ links }: { links: NavigationLink[] }) {
|
|
24
|
-
const { t } = useLingui();
|
|
25
|
-
|
|
26
|
-
return (
|
|
27
|
-
<>
|
|
28
|
-
<CrudPageHeader
|
|
29
|
-
title={t({
|
|
30
|
-
message: "Navigation",
|
|
31
|
-
comment: "@context: Dashboard heading",
|
|
32
|
-
})}
|
|
33
|
-
ctaLabel={t({
|
|
34
|
-
message: "New Link",
|
|
35
|
-
comment: "@context: Button to create new navigation link",
|
|
36
|
-
})}
|
|
37
|
-
ctaHref="/dash/navigation/new"
|
|
38
|
-
/>
|
|
39
|
-
|
|
40
|
-
{links.length === 0 ? (
|
|
41
|
-
<EmptyState
|
|
42
|
-
message={t({
|
|
43
|
-
message: "No navigation links configured.",
|
|
44
|
-
comment: "@context: Empty state message",
|
|
45
|
-
})}
|
|
46
|
-
ctaText={t({
|
|
47
|
-
message: "New Link",
|
|
48
|
-
comment: "@context: Button to create new navigation link",
|
|
49
|
-
})}
|
|
50
|
-
ctaHref="/dash/navigation/new"
|
|
51
|
-
/>
|
|
52
|
-
) : (
|
|
53
|
-
<>
|
|
54
|
-
<div id="nav-links-list" class="flex flex-col divide-y">
|
|
55
|
-
{links.map((link) => (
|
|
56
|
-
<ListItemRow
|
|
57
|
-
key={link.id}
|
|
58
|
-
actions={
|
|
59
|
-
<ActionButtons
|
|
60
|
-
editHref={`/dash/navigation/${link.id}/edit`}
|
|
61
|
-
editLabel={t({
|
|
62
|
-
message: "Edit",
|
|
63
|
-
comment: "@context: Button to edit navigation link",
|
|
64
|
-
})}
|
|
65
|
-
deleteAction={`/dash/navigation/${link.id}/delete`}
|
|
66
|
-
deleteLabel={t({
|
|
67
|
-
message: "Delete",
|
|
68
|
-
comment: "@context: Button to delete navigation link",
|
|
69
|
-
})}
|
|
70
|
-
/>
|
|
71
|
-
}
|
|
72
|
-
>
|
|
73
|
-
<div
|
|
74
|
-
class="flex items-center gap-3 cursor-grab"
|
|
75
|
-
data-id={link.id}
|
|
76
|
-
>
|
|
77
|
-
<span class="text-muted-foreground select-none">⠿</span>
|
|
78
|
-
<div class="flex items-center gap-2">
|
|
79
|
-
<span class="font-medium">{link.label}</span>
|
|
80
|
-
<code class="text-sm text-muted-foreground bg-muted px-1 rounded">
|
|
81
|
-
{link.url}
|
|
82
|
-
</code>
|
|
83
|
-
</div>
|
|
84
|
-
</div>
|
|
85
|
-
</ListItemRow>
|
|
86
|
-
))}
|
|
87
|
-
</div>
|
|
88
|
-
|
|
89
|
-
{/* SortableJS is initialized by client.ts via lib/nav-reorder.ts */}
|
|
90
|
-
</>
|
|
91
|
-
)}
|
|
92
|
-
</>
|
|
93
|
-
);
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
function NavigationFormContent({
|
|
97
|
-
link,
|
|
98
|
-
isEdit,
|
|
99
|
-
}: {
|
|
100
|
-
link?: NavigationLink;
|
|
101
|
-
isEdit?: boolean;
|
|
102
|
-
}) {
|
|
103
|
-
const { t } = useLingui();
|
|
104
|
-
const title = isEdit
|
|
105
|
-
? t({ message: "Edit Link", comment: "@context: Page heading" })
|
|
106
|
-
: t({ message: "New Link", comment: "@context: Page heading" });
|
|
107
|
-
|
|
108
|
-
const signals = JSON.stringify({
|
|
109
|
-
label: link?.label ?? "",
|
|
110
|
-
url: link?.url ?? "",
|
|
111
|
-
}).replace(/</g, "\\u003c");
|
|
112
|
-
|
|
113
|
-
const action = isEdit ? `/dash/navigation/${link?.id}` : "/dash/navigation";
|
|
114
|
-
|
|
115
|
-
return (
|
|
116
|
-
<>
|
|
117
|
-
<h1 class="text-2xl font-semibold mb-6">{title}</h1>
|
|
118
|
-
|
|
119
|
-
<form
|
|
120
|
-
data-signals={signals}
|
|
121
|
-
data-on:submit__prevent={`@post('${action}')`}
|
|
122
|
-
data-indicator="_loading"
|
|
123
|
-
class="flex flex-col gap-4 max-w-lg"
|
|
124
|
-
>
|
|
125
|
-
<div class="field">
|
|
126
|
-
<label class="label">
|
|
127
|
-
{t({
|
|
128
|
-
message: "Label",
|
|
129
|
-
comment: "@context: Navigation link form field",
|
|
130
|
-
})}
|
|
131
|
-
</label>
|
|
132
|
-
<input
|
|
133
|
-
type="text"
|
|
134
|
-
data-bind="label"
|
|
135
|
-
class="input"
|
|
136
|
-
placeholder="Home"
|
|
137
|
-
required
|
|
138
|
-
/>
|
|
139
|
-
<p class="text-xs text-muted-foreground mt-1">
|
|
140
|
-
{t({
|
|
141
|
-
message: "Display text for the link",
|
|
142
|
-
comment: "@context: Navigation label help text",
|
|
143
|
-
})}
|
|
144
|
-
</p>
|
|
145
|
-
</div>
|
|
146
|
-
|
|
147
|
-
<div class="field">
|
|
148
|
-
<label class="label">
|
|
149
|
-
{t({
|
|
150
|
-
message: "URL",
|
|
151
|
-
comment: "@context: Navigation link form field",
|
|
152
|
-
})}
|
|
153
|
-
</label>
|
|
154
|
-
<input
|
|
155
|
-
type="text"
|
|
156
|
-
data-bind="url"
|
|
157
|
-
class="input"
|
|
158
|
-
placeholder="/archive or https://..."
|
|
159
|
-
required
|
|
160
|
-
/>
|
|
161
|
-
<p class="text-xs text-muted-foreground mt-1">
|
|
162
|
-
{t({
|
|
163
|
-
message:
|
|
164
|
-
"Path (e.g. /archive) or full URL (e.g. https://example.com)",
|
|
165
|
-
comment: "@context: Navigation URL help text",
|
|
166
|
-
})}
|
|
167
|
-
</p>
|
|
168
|
-
</div>
|
|
169
|
-
|
|
170
|
-
<div class="flex gap-2">
|
|
171
|
-
<button type="submit" class="btn" data-attr-disabled="$_loading">
|
|
172
|
-
<span data-show="!$_loading">
|
|
173
|
-
{isEdit
|
|
174
|
-
? t({
|
|
175
|
-
message: "Save Changes",
|
|
176
|
-
comment: "@context: Button to save edited navigation link",
|
|
177
|
-
})
|
|
178
|
-
: t({
|
|
179
|
-
message: "Create Link",
|
|
180
|
-
comment: "@context: Button to save new navigation link",
|
|
181
|
-
})}
|
|
182
|
-
</span>
|
|
183
|
-
<span data-show="$_loading">
|
|
184
|
-
{t({
|
|
185
|
-
message: "Processing...",
|
|
186
|
-
comment:
|
|
187
|
-
"@context: Loading text shown on submit button while request is in progress",
|
|
188
|
-
})}
|
|
189
|
-
</span>
|
|
190
|
-
</button>
|
|
191
|
-
<a href="/dash/navigation" class="btn-outline">
|
|
192
|
-
{t({
|
|
193
|
-
message: "Cancel",
|
|
194
|
-
comment: "@context: Button to cancel form",
|
|
195
|
-
})}
|
|
196
|
-
</a>
|
|
197
|
-
</div>
|
|
198
|
-
</form>
|
|
199
|
-
</>
|
|
200
|
-
);
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
// List navigation links
|
|
204
|
-
navigationRoutes.get("/", async (c) => {
|
|
205
|
-
const siteName = await getSiteName(c);
|
|
206
|
-
const links = await c.var.services.navigationLinks.list();
|
|
207
|
-
|
|
208
|
-
return c.html(
|
|
209
|
-
<DashLayout
|
|
210
|
-
c={c}
|
|
211
|
-
title="Navigation"
|
|
212
|
-
siteName={siteName}
|
|
213
|
-
currentPath="/dash/navigation"
|
|
214
|
-
>
|
|
215
|
-
<NavigationListContent links={links} />
|
|
216
|
-
</DashLayout>,
|
|
217
|
-
);
|
|
218
|
-
});
|
|
219
|
-
|
|
220
|
-
// New link form
|
|
221
|
-
navigationRoutes.get("/new", async (c) => {
|
|
222
|
-
const siteName = await getSiteName(c);
|
|
223
|
-
|
|
224
|
-
return c.html(
|
|
225
|
-
<DashLayout
|
|
226
|
-
c={c}
|
|
227
|
-
title="New Link"
|
|
228
|
-
siteName={siteName}
|
|
229
|
-
currentPath="/dash/navigation"
|
|
230
|
-
>
|
|
231
|
-
<NavigationFormContent />
|
|
232
|
-
</DashLayout>,
|
|
233
|
-
);
|
|
234
|
-
});
|
|
235
|
-
|
|
236
|
-
// Create link
|
|
237
|
-
navigationRoutes.post("/", async (c) => {
|
|
238
|
-
const body = await c.req.json<{ label: string; url: string }>();
|
|
239
|
-
|
|
240
|
-
if (!body.label || !body.url) {
|
|
241
|
-
return dsToast("Label and URL are required", "error");
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
await c.var.services.navigationLinks.create({
|
|
245
|
-
label: body.label,
|
|
246
|
-
url: body.url,
|
|
247
|
-
});
|
|
248
|
-
|
|
249
|
-
return dsRedirect("/dash/navigation");
|
|
250
|
-
});
|
|
251
|
-
|
|
252
|
-
// Reorder links (must be before /:id to avoid "reorder" matching as :id)
|
|
253
|
-
navigationRoutes.post("/reorder", async (c) => {
|
|
254
|
-
const body = await c.req.json<{ ids: number[] }>();
|
|
255
|
-
|
|
256
|
-
if (!Array.isArray(body.ids)) {
|
|
257
|
-
return dsToast("Invalid request", "error");
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
await c.var.services.navigationLinks.reorder(body.ids);
|
|
261
|
-
|
|
262
|
-
return dsToast("Order saved");
|
|
263
|
-
});
|
|
264
|
-
|
|
265
|
-
// Edit link form
|
|
266
|
-
navigationRoutes.get("/:id/edit", async (c) => {
|
|
267
|
-
const id = parseInt(c.req.param("id"), 10);
|
|
268
|
-
if (isNaN(id)) return c.notFound();
|
|
269
|
-
|
|
270
|
-
const link = await c.var.services.navigationLinks.getById(id);
|
|
271
|
-
if (!link) return c.notFound();
|
|
272
|
-
|
|
273
|
-
const siteName = await getSiteName(c);
|
|
274
|
-
|
|
275
|
-
return c.html(
|
|
276
|
-
<DashLayout
|
|
277
|
-
c={c}
|
|
278
|
-
title="Edit Link"
|
|
279
|
-
siteName={siteName}
|
|
280
|
-
currentPath="/dash/navigation"
|
|
281
|
-
>
|
|
282
|
-
<NavigationFormContent link={link} isEdit />
|
|
283
|
-
</DashLayout>,
|
|
284
|
-
);
|
|
285
|
-
});
|
|
286
|
-
|
|
287
|
-
// Update link
|
|
288
|
-
navigationRoutes.post("/:id", async (c) => {
|
|
289
|
-
const id = parseInt(c.req.param("id"), 10);
|
|
290
|
-
if (isNaN(id)) return c.notFound();
|
|
291
|
-
|
|
292
|
-
const body = await c.req.json<{ label: string; url: string }>();
|
|
293
|
-
|
|
294
|
-
if (!body.label || !body.url) {
|
|
295
|
-
return dsToast("Label and URL are required", "error");
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
const updated = await c.var.services.navigationLinks.update(id, {
|
|
299
|
-
label: body.label,
|
|
300
|
-
url: body.url,
|
|
301
|
-
});
|
|
302
|
-
|
|
303
|
-
if (!updated) return c.notFound();
|
|
304
|
-
|
|
305
|
-
return dsRedirect("/dash/navigation");
|
|
306
|
-
});
|
|
307
|
-
|
|
308
|
-
// Delete link
|
|
309
|
-
navigationRoutes.post("/:id/delete", async (c) => {
|
|
310
|
-
const id = parseInt(c.req.param("id"), 10);
|
|
311
|
-
if (!isNaN(id)) {
|
|
312
|
-
await c.var.services.navigationLinks.delete(id);
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
return dsRedirect("/dash/navigation");
|
|
316
|
-
});
|
|
@@ -1,128 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Media Gallery Component
|
|
3
|
-
*
|
|
4
|
-
* Renders media attachments on public post pages.
|
|
5
|
-
* Layout adapts based on the number of images.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import type { FC } from "hono/jsx";
|
|
9
|
-
import type { MediaView } from "../../types.js";
|
|
10
|
-
|
|
11
|
-
export interface MediaGalleryProps {
|
|
12
|
-
attachments: MediaView[];
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export const MediaGallery: FC<MediaGalleryProps> = ({ attachments }) => {
|
|
16
|
-
const images = attachments.filter((a) => a.mimeType.startsWith("image/"));
|
|
17
|
-
if (images.length === 0) return null;
|
|
18
|
-
|
|
19
|
-
if (images.length === 1) {
|
|
20
|
-
const [img] = images;
|
|
21
|
-
if (!img) return null;
|
|
22
|
-
return (
|
|
23
|
-
<div class="mt-3">
|
|
24
|
-
<a href={img.url} target="_blank" rel="noopener noreferrer">
|
|
25
|
-
<img
|
|
26
|
-
src={img.thumbnailUrl}
|
|
27
|
-
alt={img.altText || ""}
|
|
28
|
-
width={img.width ?? undefined}
|
|
29
|
-
height={img.height ?? undefined}
|
|
30
|
-
class="rounded-lg max-w-full h-auto"
|
|
31
|
-
loading="lazy"
|
|
32
|
-
/>
|
|
33
|
-
</a>
|
|
34
|
-
</div>
|
|
35
|
-
);
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
if (images.length === 2) {
|
|
39
|
-
return (
|
|
40
|
-
<div class="mt-3 grid grid-cols-2 gap-1 rounded-lg overflow-hidden">
|
|
41
|
-
{images.map((img) => (
|
|
42
|
-
<a
|
|
43
|
-
key={img.id}
|
|
44
|
-
href={img.url}
|
|
45
|
-
target="_blank"
|
|
46
|
-
rel="noopener noreferrer"
|
|
47
|
-
class="aspect-square"
|
|
48
|
-
>
|
|
49
|
-
<img
|
|
50
|
-
src={img.thumbnailUrl}
|
|
51
|
-
alt={img.altText || ""}
|
|
52
|
-
class="w-full h-full object-cover"
|
|
53
|
-
loading="lazy"
|
|
54
|
-
/>
|
|
55
|
-
</a>
|
|
56
|
-
))}
|
|
57
|
-
</div>
|
|
58
|
-
);
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
if (images.length === 3) {
|
|
62
|
-
const [first, ...rest] = images;
|
|
63
|
-
if (!first) return null;
|
|
64
|
-
return (
|
|
65
|
-
<div class="mt-3 grid grid-cols-2 gap-1 rounded-lg overflow-hidden">
|
|
66
|
-
<a
|
|
67
|
-
href={first.url}
|
|
68
|
-
target="_blank"
|
|
69
|
-
rel="noopener noreferrer"
|
|
70
|
-
class="row-span-2"
|
|
71
|
-
>
|
|
72
|
-
<img
|
|
73
|
-
src={first.thumbnailUrl}
|
|
74
|
-
alt={first.altText || ""}
|
|
75
|
-
class="w-full h-full object-cover"
|
|
76
|
-
loading="lazy"
|
|
77
|
-
/>
|
|
78
|
-
</a>
|
|
79
|
-
{rest.map((img) => (
|
|
80
|
-
<a
|
|
81
|
-
key={img.id}
|
|
82
|
-
href={img.url}
|
|
83
|
-
target="_blank"
|
|
84
|
-
rel="noopener noreferrer"
|
|
85
|
-
class="aspect-square"
|
|
86
|
-
>
|
|
87
|
-
<img
|
|
88
|
-
src={img.thumbnailUrl}
|
|
89
|
-
alt={img.altText || ""}
|
|
90
|
-
class="w-full h-full object-cover"
|
|
91
|
-
loading="lazy"
|
|
92
|
-
/>
|
|
93
|
-
</a>
|
|
94
|
-
))}
|
|
95
|
-
</div>
|
|
96
|
-
);
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
// 4+ images: 2-column grid, show first 4 with remaining count
|
|
100
|
-
const shown = images.slice(0, 4);
|
|
101
|
-
const remaining = images.length - 4;
|
|
102
|
-
|
|
103
|
-
return (
|
|
104
|
-
<div class="mt-3 grid grid-cols-2 gap-1 rounded-lg overflow-hidden">
|
|
105
|
-
{shown.map((img, i) => (
|
|
106
|
-
<a
|
|
107
|
-
key={img.id}
|
|
108
|
-
href={img.url}
|
|
109
|
-
target="_blank"
|
|
110
|
-
rel="noopener noreferrer"
|
|
111
|
-
class="relative aspect-square"
|
|
112
|
-
>
|
|
113
|
-
<img
|
|
114
|
-
src={img.thumbnailUrl}
|
|
115
|
-
alt={img.altText || ""}
|
|
116
|
-
class="w-full h-full object-cover"
|
|
117
|
-
loading="lazy"
|
|
118
|
-
/>
|
|
119
|
-
{i === 3 && remaining > 0 && (
|
|
120
|
-
<div class="absolute inset-0 bg-black/50 flex items-center justify-center text-white text-xl font-semibold">
|
|
121
|
-
+{remaining}
|
|
122
|
-
</div>
|
|
123
|
-
)}
|
|
124
|
-
</a>
|
|
125
|
-
))}
|
|
126
|
-
</div>
|
|
127
|
-
);
|
|
128
|
-
};
|
|
@@ -1,92 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Post List Component
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import type { FC } from "hono/jsx";
|
|
6
|
-
import { useLingui } from "@lingui/react/macro";
|
|
7
|
-
import type { Post } from "../../types.js";
|
|
8
|
-
import * as sqid from "../../lib/sqid.js";
|
|
9
|
-
import * as time from "../../lib/time.js";
|
|
10
|
-
import { VisibilityBadge } from "./VisibilityBadge.js";
|
|
11
|
-
import { TypeBadge } from "./TypeBadge.js";
|
|
12
|
-
import { EmptyState } from "./EmptyState.js";
|
|
13
|
-
import { ListItemRow } from "./ListItemRow.js";
|
|
14
|
-
import { ActionButtons } from "./ActionButtons.js";
|
|
15
|
-
|
|
16
|
-
export interface PostListProps {
|
|
17
|
-
posts: Post[];
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
export const PostList: FC<PostListProps> = ({ posts }) => {
|
|
21
|
-
const { t } = useLingui();
|
|
22
|
-
if (posts.length === 0) {
|
|
23
|
-
return (
|
|
24
|
-
<EmptyState
|
|
25
|
-
message={t({
|
|
26
|
-
message: "No posts yet.",
|
|
27
|
-
comment: "@context: Empty state message when no posts exist",
|
|
28
|
-
})}
|
|
29
|
-
ctaText={t({
|
|
30
|
-
message: "Create your first post",
|
|
31
|
-
comment: "@context: Button in empty state to create first post",
|
|
32
|
-
})}
|
|
33
|
-
ctaHref="/dash/posts/new"
|
|
34
|
-
/>
|
|
35
|
-
);
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
return (
|
|
39
|
-
<div class="flex flex-col divide-y">
|
|
40
|
-
{posts.map((post) => (
|
|
41
|
-
<ListItemRow
|
|
42
|
-
key={post.id}
|
|
43
|
-
actions={
|
|
44
|
-
<ActionButtons
|
|
45
|
-
editHref={`/dash/posts/${sqid.encode(post.id)}/edit`}
|
|
46
|
-
editLabel={t({
|
|
47
|
-
message: "Edit",
|
|
48
|
-
comment: "@context: Button to edit post",
|
|
49
|
-
})}
|
|
50
|
-
viewHref={`/p/${sqid.encode(post.id)}`}
|
|
51
|
-
viewLabel={t({
|
|
52
|
-
message: "View",
|
|
53
|
-
comment: "@context: Button to view post on public site",
|
|
54
|
-
})}
|
|
55
|
-
deleteAction={`/dash/posts/${sqid.encode(post.id)}/delete`}
|
|
56
|
-
deleteConfirm={t({
|
|
57
|
-
message:
|
|
58
|
-
"Are you sure you want to delete this post? This cannot be undone.",
|
|
59
|
-
comment:
|
|
60
|
-
"@context: Confirmation dialog when deleting a post from the list",
|
|
61
|
-
})}
|
|
62
|
-
/>
|
|
63
|
-
}
|
|
64
|
-
>
|
|
65
|
-
<div class="flex items-center gap-2 mb-1">
|
|
66
|
-
<TypeBadge type={post.type} />
|
|
67
|
-
<VisibilityBadge visibility={post.visibility} />
|
|
68
|
-
<span class="text-xs text-muted-foreground">
|
|
69
|
-
{time.formatDate(post.publishedAt)}
|
|
70
|
-
</span>
|
|
71
|
-
</div>
|
|
72
|
-
<a
|
|
73
|
-
href={`/dash/posts/${sqid.encode(post.id)}`}
|
|
74
|
-
class="font-medium hover:underline"
|
|
75
|
-
>
|
|
76
|
-
{post.title ||
|
|
77
|
-
post.content?.slice(0, 60) ||
|
|
78
|
-
t({
|
|
79
|
-
message: "Untitled",
|
|
80
|
-
comment: "@context: Default title for untitled post",
|
|
81
|
-
})}
|
|
82
|
-
</a>
|
|
83
|
-
{post.content && !post.title && (
|
|
84
|
-
<p class="text-sm text-muted-foreground mt-1 line-clamp-2">
|
|
85
|
-
{post.content.slice(0, 120)}
|
|
86
|
-
</p>
|
|
87
|
-
)}
|
|
88
|
-
</ListItemRow>
|
|
89
|
-
))}
|
|
90
|
-
</div>
|
|
91
|
-
);
|
|
92
|
-
};
|
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Type Badge Component
|
|
3
|
-
*
|
|
4
|
-
* Displays a badge indicating the type of a post (note, article, link, etc.)
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import type { FC } from "hono/jsx";
|
|
8
|
-
import { useLingui } from "@lingui/react/macro";
|
|
9
|
-
import type { PostType } from "../../types.js";
|
|
10
|
-
|
|
11
|
-
export interface TypeBadgeProps {
|
|
12
|
-
type: PostType;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export const TypeBadge: FC<TypeBadgeProps> = ({ type }) => {
|
|
16
|
-
const { t } = useLingui();
|
|
17
|
-
|
|
18
|
-
const labels: Record<PostType, string> = {
|
|
19
|
-
note: t({ message: "Note", comment: "@context: Post type badge - note" }),
|
|
20
|
-
article: t({
|
|
21
|
-
message: "Article",
|
|
22
|
-
comment: "@context: Post type badge - article",
|
|
23
|
-
}),
|
|
24
|
-
link: t({ message: "Link", comment: "@context: Post type badge - link" }),
|
|
25
|
-
quote: t({
|
|
26
|
-
message: "Quote",
|
|
27
|
-
comment: "@context: Post type badge - quote",
|
|
28
|
-
}),
|
|
29
|
-
image: t({
|
|
30
|
-
message: "Image",
|
|
31
|
-
comment: "@context: Post type badge - image",
|
|
32
|
-
}),
|
|
33
|
-
page: t({ message: "Page", comment: "@context: Post type badge - page" }),
|
|
34
|
-
};
|
|
35
|
-
|
|
36
|
-
return <span class="badge-outline">{labels[type]}</span>;
|
|
37
|
-
};
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Visibility Badge Component
|
|
3
|
-
*
|
|
4
|
-
* Displays a badge indicating the visibility level of a post
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import type { FC } from "hono/jsx";
|
|
8
|
-
import { useLingui } from "@lingui/react/macro";
|
|
9
|
-
import type { Visibility } from "../../types.js";
|
|
10
|
-
|
|
11
|
-
export interface VisibilityBadgeProps {
|
|
12
|
-
visibility: Visibility;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export const VisibilityBadge: FC<VisibilityBadgeProps> = ({ visibility }) => {
|
|
16
|
-
const { t } = useLingui();
|
|
17
|
-
|
|
18
|
-
const variants: Record<Visibility, string> = {
|
|
19
|
-
featured: "badge-primary",
|
|
20
|
-
quiet: "badge-secondary",
|
|
21
|
-
unlisted: "badge-outline",
|
|
22
|
-
draft: "badge-outline",
|
|
23
|
-
};
|
|
24
|
-
|
|
25
|
-
const labels: Record<Visibility, string> = {
|
|
26
|
-
featured: t({
|
|
27
|
-
message: "Featured",
|
|
28
|
-
comment: "@context: Post visibility badge - featured",
|
|
29
|
-
}),
|
|
30
|
-
quiet: t({
|
|
31
|
-
message: "Quiet",
|
|
32
|
-
comment: "@context: Post visibility badge - normal",
|
|
33
|
-
}),
|
|
34
|
-
unlisted: t({
|
|
35
|
-
message: "Unlisted",
|
|
36
|
-
comment: "@context: Post visibility badge - unlisted",
|
|
37
|
-
}),
|
|
38
|
-
draft: t({
|
|
39
|
-
message: "Draft",
|
|
40
|
-
comment: "@context: Post visibility badge - draft",
|
|
41
|
-
}),
|
|
42
|
-
};
|
|
43
|
-
|
|
44
|
-
return <span class={variants[visibility]}>{labels[visibility]}</span>;
|
|
45
|
-
};
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
export { ActionButtons, type ActionButtonsProps } from "./ActionButtons.js";
|
|
2
|
-
export { CrudPageHeader, type CrudPageHeaderProps } from "./CrudPageHeader.js";
|
|
3
|
-
export { DangerZone, type DangerZoneProps } from "./DangerZone.js";
|
|
4
|
-
export { EmptyState, type EmptyStateProps } from "./EmptyState.js";
|
|
5
|
-
export { ListItemRow, type ListItemRowProps } from "./ListItemRow.js";
|
|
6
|
-
export { MediaGallery, type MediaGalleryProps } from "./MediaGallery.js";
|
|
7
|
-
export { PageForm, type PageFormProps } from "./PageForm.js";
|
|
8
|
-
export {
|
|
9
|
-
Pagination,
|
|
10
|
-
LoadMore,
|
|
11
|
-
PagePagination,
|
|
12
|
-
type PaginationProps,
|
|
13
|
-
type LoadMoreProps,
|
|
14
|
-
type PagePaginationProps,
|
|
15
|
-
} from "./Pagination.js";
|
|
16
|
-
export { PostForm, type PostFormProps } from "./PostForm.js";
|
|
17
|
-
export { PostList, type PostListProps } from "./PostList.js";
|
|
18
|
-
export { ThreadView, type ThreadViewProps } from "./ThreadView.js";
|
|
19
|
-
export { TypeBadge, type TypeBadgeProps } from "./TypeBadge.js";
|
|
20
|
-
export {
|
|
21
|
-
VisibilityBadge,
|
|
22
|
-
type VisibilityBadgeProps,
|
|
23
|
-
} from "./VisibilityBadge.js";
|
package/src/theme/index.ts
DELETED
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Jant Theme - Shared Infrastructure
|
|
3
|
-
*
|
|
4
|
-
* Exports shared layouts, components, and color themes used by all themes.
|
|
5
|
-
* Individual theme packages (minimal, card, etc.) import from here.
|
|
6
|
-
*
|
|
7
|
-
* @example
|
|
8
|
-
* ```typescript
|
|
9
|
-
* // In a theme package:
|
|
10
|
-
* import { MediaGallery, Pagination } from "@jant/core/theme";
|
|
11
|
-
* import type { ColorTheme } from "@jant/core/theme";
|
|
12
|
-
* ```
|
|
13
|
-
*/
|
|
14
|
-
|
|
15
|
-
// Layout components (BaseLayout, DashLayout)
|
|
16
|
-
export * from "./layouts/index.js";
|
|
17
|
-
|
|
18
|
-
// Shared UI components (MediaGallery, Pagination, EmptyState, etc.)
|
|
19
|
-
export * from "./components/index.js";
|
|
20
|
-
|
|
21
|
-
// Color themes
|
|
22
|
-
export * from "./color-themes.js";
|