@jant/core 0.3.24 → 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 -25
- package/dist/db/schema.js +1 -1
- 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 +3 -9
- package/dist/lib/constants.js +1 -0
- package/dist/lib/nav-reorder.js +1 -1
- package/dist/lib/navigation.js +26 -1
- package/dist/lib/pagination.js +44 -0
- package/dist/lib/render.js +7 -11
- package/dist/lib/schemas.js +3 -3
- package/dist/lib/theme.js +4 -4
- package/dist/lib/timeline.js +24 -48
- package/dist/lib/view.js +2 -2
- 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 +2 -2
- package/dist/routes/api/search.js +2 -2
- package/dist/routes/api/settings.js +68 -0
- package/dist/routes/compose.js +48 -0
- package/dist/routes/dash/collections.js +2 -2
- package/dist/routes/dash/index.js +1 -1
- package/dist/routes/dash/media.js +2 -2
- package/dist/routes/dash/pages.js +411 -62
- package/dist/routes/dash/posts.js +3 -5
- package/dist/routes/dash/redirects.js +2 -2
- package/dist/routes/dash/settings.js +79 -5
- package/dist/routes/feed/rss.js +2 -2
- package/dist/routes/feed/sitemap.js +1 -1
- package/dist/routes/pages/archive.js +3 -6
- package/dist/routes/pages/collection.js +3 -6
- package/dist/routes/pages/collections.js +28 -0
- package/dist/routes/pages/featured.js +32 -0
- package/dist/routes/pages/home.js +9 -50
- package/dist/routes/pages/page.js +29 -32
- package/dist/routes/pages/post.js +3 -6
- package/dist/routes/pages/search.js +3 -6
- package/dist/services/page.js +5 -1
- package/dist/services/post.js +40 -6
- package/dist/services/search.js +1 -1
- 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} +1 -2
- package/dist/{theme/components → ui/dash}/PostForm.js +0 -27
- package/dist/{theme/components → ui/dash}/PostList.js +6 -6
- package/dist/{theme/components/VisibilityBadge.js → ui/dash/StatusBadge.js} +1 -2
- package/dist/{theme/components → ui/dash}/index.js +3 -6
- package/dist/{themes/threads/timeline → ui/feed}/LinkCard.js +6 -2
- package/dist/{themes/threads/timeline → ui/feed}/NoteCard.js +11 -6
- package/dist/{themes/threads/timeline → ui/feed}/QuoteCard.js +10 -6
- package/dist/{themes/threads/timeline → ui/feed}/ThreadPreview.js +7 -9
- 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/threads → ui}/pages/ArchivePage.js +16 -14
- package/dist/{themes/threads → ui}/pages/CollectionPage.js +6 -1
- 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/threads → ui}/pages/PostPage.js +13 -8
- package/dist/{themes/threads → ui}/pages/SearchPage.js +9 -7
- package/dist/{themes/threads → ui}/pages/SinglePage.js +3 -2
- package/dist/{theme/components → ui/shared}/MediaGallery.js +1 -1
- package/dist/{theme/components → ui/shared}/Pagination.js +41 -2
- package/dist/{theme/components → ui/shared}/ThreadView.js +2 -2
- package/dist/ui/shared/index.js +5 -0
- package/package.json +1 -9
- package/src/__tests__/helpers/db.ts +3 -0
- package/src/app.tsx +57 -27
- package/src/db/migrations/0006_rename_slug_to_path.sql +5 -0
- package/src/db/migrations/meta/_journal.json +7 -0
- package/src/db/schema.ts +1 -1
- package/src/i18n/locales/en.po +332 -181
- package/src/i18n/locales/en.ts +1 -1
- package/src/i18n/locales/zh-Hans.po +332 -181
- package/src/i18n/locales/zh-Hans.ts +1 -1
- package/src/i18n/locales/zh-Hant.po +332 -181
- package/src/i18n/locales/zh-Hant.ts +1 -1
- package/src/index.ts +7 -36
- package/src/lib/__tests__/schemas.test.ts +60 -19
- package/src/lib/__tests__/timeline.test.ts +45 -81
- package/src/lib/__tests__/view.test.ts +13 -7
- package/src/lib/constants.ts +1 -0
- package/src/lib/nav-reorder.ts +1 -1
- package/src/lib/navigation.ts +40 -2
- package/src/lib/pagination.ts +50 -0
- package/src/lib/render.tsx +7 -14
- package/src/lib/schemas.ts +8 -6
- package/src/lib/theme.ts +5 -5
- package/src/lib/timeline.ts +28 -57
- package/src/lib/view.ts +2 -2
- package/src/preset.css +2 -1
- 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__/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 +2 -2
- package/src/routes/api/search.ts +2 -2
- package/src/routes/api/settings.ts +91 -0
- package/src/routes/compose.ts +63 -0
- package/src/routes/dash/__tests__/pages.test.ts +225 -0
- package/src/routes/dash/collections.tsx +2 -2
- package/src/routes/dash/index.tsx +1 -1
- package/src/routes/dash/media.tsx +2 -2
- package/src/routes/dash/pages.tsx +443 -70
- package/src/routes/dash/posts.tsx +3 -7
- package/src/routes/dash/redirects.tsx +2 -2
- package/src/routes/dash/settings.tsx +83 -5
- package/src/routes/feed/rss.ts +2 -2
- package/src/routes/feed/sitemap.ts +1 -1
- 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 +2 -6
- package/src/routes/pages/collection.tsx +2 -6
- package/src/routes/pages/collections.tsx +36 -0
- package/src/routes/pages/featured.tsx +38 -0
- package/src/routes/pages/home.tsx +9 -55
- package/src/routes/pages/page.tsx +28 -30
- package/src/routes/pages/post.tsx +2 -5
- package/src/routes/pages/search.tsx +2 -6
- package/src/services/__tests__/page.test.ts +106 -0
- package/src/services/__tests__/post.test.ts +114 -15
- package/src/services/page.ts +13 -1
- package/src/services/post.ts +57 -7
- package/src/services/search.ts +2 -2
- package/src/styles/tokens.css +47 -0
- package/src/styles/ui.css +491 -0
- package/src/types.ts +29 -159
- package/src/ui/compose/ComposeDialog.tsx +395 -0
- package/src/ui/compose/ComposePrompt.tsx +55 -0
- package/src/{theme/components/TypeBadge.tsx → ui/dash/FormatBadge.tsx} +2 -3
- package/src/{theme/components → ui/dash}/PostForm.tsx +0 -25
- package/src/{theme/components → ui/dash}/PostList.tsx +7 -7
- package/src/{theme/components/VisibilityBadge.tsx → ui/dash/StatusBadge.tsx} +2 -3
- package/src/ui/dash/index.ts +10 -0
- package/src/{themes/threads/timeline → ui/feed}/LinkCard.tsx +9 -4
- package/src/{themes/threads/timeline → ui/feed}/NoteCard.tsx +13 -8
- package/src/{themes/threads/timeline → ui/feed}/QuoteCard.tsx +13 -8
- package/src/{themes/threads/timeline → ui/feed}/ThreadPreview.tsx +7 -8
- 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/{themes/threads → ui}/pages/ArchivePage.tsx +22 -17
- package/src/{themes/threads → ui}/pages/CollectionPage.tsx +14 -5
- package/src/ui/pages/CollectionsPage.tsx +73 -0
- package/src/ui/pages/FeaturedPage.tsx +31 -0
- package/src/{themes/threads → ui}/pages/HomePage.tsx +11 -15
- package/src/{themes/threads → ui}/pages/PostPage.tsx +23 -14
- package/src/{themes/threads → ui}/pages/SearchPage.tsx +13 -11
- package/src/{themes/threads → ui}/pages/SinglePage.tsx +4 -4
- package/src/{theme/components → ui/shared}/MediaGallery.tsx +1 -1
- package/src/{theme/components → ui/shared}/Pagination.tsx +67 -4
- package/src/{theme/components → ui/shared}/ThreadView.tsx +2 -2
- 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 -46
- package/dist/routes/dash/navigation.js +0 -289
- package/dist/theme/index.js +0 -18
- package/dist/theme/layouts/index.js +0 -2
- package/dist/themes/threads/ThreadsSiteLayout.js +0 -172
- package/dist/themes/threads/index.js +0 -81
- package/dist/themes/threads/pages/HomePage.js +0 -25
- package/dist/themes/threads/timeline/TimelineFeed.js +0 -58
- package/dist/themes/threads/timeline/TimelineItem.js +0 -36
- package/dist/themes/threads/timeline/TimelineLoadMore.js +0 -23
- package/dist/themes/threads/timeline/groupByDate.js +0 -22
- package/dist/themes/threads/timeline/timelineMore.js +0 -107
- package/src/lib/__tests__/theme-components.test.ts +0 -105
- package/src/lib/theme-components.ts +0 -65
- package/src/routes/dash/navigation.tsx +0 -317
- 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/threads/ThreadsSiteLayout.tsx +0 -194
- package/src/themes/threads/index.ts +0 -100
- package/src/themes/threads/style.css +0 -336
- package/src/themes/threads/timeline/TimelineFeed.tsx +0 -62
- package/src/themes/threads/timeline/TimelineItem.tsx +0 -67
- package/src/themes/threads/timeline/TimelineLoadMore.tsx +0 -35
- package/src/themes/threads/timeline/groupByDate.ts +0 -30
- package/src/themes/threads/timeline/timelineMore.tsx +0 -130
- /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/dash}/PageForm.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/dash}/PageForm.tsx +0 -0
- /package/src/{theme/components → ui/shared}/EmptyState.tsx +0 -0
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Theme Component Resolution
|
|
3
|
-
*
|
|
4
|
-
* Resolves theme-overridable components, falling back to defaults.
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import type { FC } from "hono/jsx";
|
|
8
|
-
import type { Format, ThemeComponents, TimelineCardProps } from "../types.js";
|
|
9
|
-
|
|
10
|
-
const THEME_KEY_MAP: Record<Format, keyof ThemeComponents> = {
|
|
11
|
-
note: "NoteCard",
|
|
12
|
-
link: "LinkCard",
|
|
13
|
-
quote: "QuoteCard",
|
|
14
|
-
};
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Generic component resolver.
|
|
18
|
-
*
|
|
19
|
-
* Looks up a component by key in `ThemeComponents` and falls back to the
|
|
20
|
-
* provided default component.
|
|
21
|
-
*
|
|
22
|
-
* @param key - ThemeComponents key to look up
|
|
23
|
-
* @param defaultComponent - Fallback component
|
|
24
|
-
* @param themeComponents - Optional theme component overrides
|
|
25
|
-
* @returns The resolved component
|
|
26
|
-
*
|
|
27
|
-
* @example
|
|
28
|
-
* ```ts
|
|
29
|
-
* const Gallery = resolveComponent("MediaGallery", DefaultMediaGallery, theme);
|
|
30
|
-
* ```
|
|
31
|
-
*/
|
|
32
|
-
export function resolveComponent<K extends keyof ThemeComponents>(
|
|
33
|
-
key: K,
|
|
34
|
-
defaultComponent: NonNullable<ThemeComponents[K]>,
|
|
35
|
-
themeComponents?: ThemeComponents,
|
|
36
|
-
): NonNullable<ThemeComponents[K]> {
|
|
37
|
-
return (themeComponents?.[key] ?? defaultComponent) as NonNullable<
|
|
38
|
-
ThemeComponents[K]
|
|
39
|
-
>;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
/**
|
|
43
|
-
* Resolves the card component for a given post format.
|
|
44
|
-
*
|
|
45
|
-
* Checks theme overrides first, then falls back to the provided default card component.
|
|
46
|
-
*
|
|
47
|
-
* @param format - The post format to resolve a card for
|
|
48
|
-
* @param defaults - Map of format to default card component
|
|
49
|
-
* @param themeComponents - Optional theme component overrides
|
|
50
|
-
* @returns The resolved card component
|
|
51
|
-
*
|
|
52
|
-
* @example
|
|
53
|
-
* ```ts
|
|
54
|
-
* const Card = resolveCardComponent("note", DEFAULT_CARD_MAP, c.var.config.theme?.components);
|
|
55
|
-
* ```
|
|
56
|
-
*/
|
|
57
|
-
export function resolveCardComponent(
|
|
58
|
-
format: Format,
|
|
59
|
-
defaults: Record<Format, FC<TimelineCardProps>>,
|
|
60
|
-
themeComponents?: ThemeComponents,
|
|
61
|
-
): FC<TimelineCardProps> {
|
|
62
|
-
const key = THEME_KEY_MAP[format];
|
|
63
|
-
const override = themeComponents?.[key] as FC<TimelineCardProps> | undefined;
|
|
64
|
-
return override ?? defaults[format];
|
|
65
|
-
}
|
|
@@ -1,317 +0,0 @@
|
|
|
1
|
-
import { getSiteName } from "../../lib/config.js";
|
|
2
|
-
/**
|
|
3
|
-
* Dashboard Navigation Items Routes
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { Hono } from "hono";
|
|
7
|
-
import { useLingui } from "@lingui/react/macro";
|
|
8
|
-
import type { Bindings, NavItem } 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({ items }: { items: NavItem[] }) {
|
|
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
|
-
{items.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
|
-
{items.map((item) => (
|
|
56
|
-
<ListItemRow
|
|
57
|
-
key={item.id}
|
|
58
|
-
actions={
|
|
59
|
-
<ActionButtons
|
|
60
|
-
editHref={`/dash/navigation/${item.id}/edit`}
|
|
61
|
-
editLabel={t({
|
|
62
|
-
message: "Edit",
|
|
63
|
-
comment: "@context: Button to edit navigation link",
|
|
64
|
-
})}
|
|
65
|
-
deleteAction={`/dash/navigation/${item.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={item.id}
|
|
76
|
-
>
|
|
77
|
-
<span class="text-muted-foreground select-none">⠿</span>
|
|
78
|
-
<div class="flex items-center gap-2">
|
|
79
|
-
<span class="font-medium">{item.label}</span>
|
|
80
|
-
<code class="text-sm text-muted-foreground bg-muted px-1 rounded">
|
|
81
|
-
{item.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
|
-
item,
|
|
98
|
-
isEdit,
|
|
99
|
-
}: {
|
|
100
|
-
item?: NavItem;
|
|
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: item?.label ?? "",
|
|
110
|
-
url: item?.url ?? "",
|
|
111
|
-
}).replace(/</g, "\\u003c");
|
|
112
|
-
|
|
113
|
-
const action = isEdit ? `/dash/navigation/${item?.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 items
|
|
204
|
-
navigationRoutes.get("/", async (c) => {
|
|
205
|
-
const siteName = await getSiteName(c);
|
|
206
|
-
const items = await c.var.services.navItems.list();
|
|
207
|
-
|
|
208
|
-
return c.html(
|
|
209
|
-
<DashLayout
|
|
210
|
-
c={c}
|
|
211
|
-
title="Navigation"
|
|
212
|
-
siteName={siteName}
|
|
213
|
-
currentPath="/dash/navigation"
|
|
214
|
-
>
|
|
215
|
-
<NavigationListContent items={items} />
|
|
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.navItems.create({
|
|
245
|
-
type: "link",
|
|
246
|
-
label: body.label,
|
|
247
|
-
url: body.url,
|
|
248
|
-
});
|
|
249
|
-
|
|
250
|
-
return dsRedirect("/dash/navigation");
|
|
251
|
-
});
|
|
252
|
-
|
|
253
|
-
// Reorder links (must be before /:id to avoid "reorder" matching as :id)
|
|
254
|
-
navigationRoutes.post("/reorder", async (c) => {
|
|
255
|
-
const body = await c.req.json<{ ids: number[] }>();
|
|
256
|
-
|
|
257
|
-
if (!Array.isArray(body.ids)) {
|
|
258
|
-
return dsToast("Invalid request", "error");
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
await c.var.services.navItems.reorder(body.ids);
|
|
262
|
-
|
|
263
|
-
return dsToast("Order saved");
|
|
264
|
-
});
|
|
265
|
-
|
|
266
|
-
// Edit link form
|
|
267
|
-
navigationRoutes.get("/:id/edit", async (c) => {
|
|
268
|
-
const id = parseInt(c.req.param("id"), 10);
|
|
269
|
-
if (isNaN(id)) return c.notFound();
|
|
270
|
-
|
|
271
|
-
const item = await c.var.services.navItems.getById(id);
|
|
272
|
-
if (!item) return c.notFound();
|
|
273
|
-
|
|
274
|
-
const siteName = await getSiteName(c);
|
|
275
|
-
|
|
276
|
-
return c.html(
|
|
277
|
-
<DashLayout
|
|
278
|
-
c={c}
|
|
279
|
-
title="Edit Link"
|
|
280
|
-
siteName={siteName}
|
|
281
|
-
currentPath="/dash/navigation"
|
|
282
|
-
>
|
|
283
|
-
<NavigationFormContent item={item} isEdit />
|
|
284
|
-
</DashLayout>,
|
|
285
|
-
);
|
|
286
|
-
});
|
|
287
|
-
|
|
288
|
-
// Update link
|
|
289
|
-
navigationRoutes.post("/:id", async (c) => {
|
|
290
|
-
const id = parseInt(c.req.param("id"), 10);
|
|
291
|
-
if (isNaN(id)) return c.notFound();
|
|
292
|
-
|
|
293
|
-
const body = await c.req.json<{ label: string; url: string }>();
|
|
294
|
-
|
|
295
|
-
if (!body.label || !body.url) {
|
|
296
|
-
return dsToast("Label and URL are required", "error");
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
const updated = await c.var.services.navItems.update(id, {
|
|
300
|
-
label: body.label,
|
|
301
|
-
url: body.url,
|
|
302
|
-
});
|
|
303
|
-
|
|
304
|
-
if (!updated) return c.notFound();
|
|
305
|
-
|
|
306
|
-
return dsRedirect("/dash/navigation");
|
|
307
|
-
});
|
|
308
|
-
|
|
309
|
-
// Delete link
|
|
310
|
-
navigationRoutes.post("/:id/delete", async (c) => {
|
|
311
|
-
const id = parseInt(c.req.param("id"), 10);
|
|
312
|
-
if (!isNaN(id)) {
|
|
313
|
-
await c.var.services.navItems.delete(id);
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
return dsRedirect("/dash/navigation");
|
|
317
|
-
});
|
|
@@ -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";
|
|
@@ -1,194 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Threads Theme - Site Layout
|
|
3
|
-
*
|
|
4
|
-
* Left icon sidebar (76px) on desktop, bottom tab bar (60px) on mobile.
|
|
5
|
-
* Gray page background (#fafafa) with white rounded content container.
|
|
6
|
-
* All dimensions match threads.com's --barcelona-* design tokens.
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
import type { FC, PropsWithChildren } from "hono/jsx";
|
|
10
|
-
import type { NavItemView, SiteLayoutProps } from "../../types.js";
|
|
11
|
-
|
|
12
|
-
/** Map known URL paths to SVG icons. Size 26x26 matching Threads' nav icons. */
|
|
13
|
-
function NavIcon({ url, isActive }: { url: string; isActive: boolean }) {
|
|
14
|
-
const stroke = "currentColor";
|
|
15
|
-
const sw = isActive ? "2.25" : "1.75";
|
|
16
|
-
const cls = "size-[26px]";
|
|
17
|
-
|
|
18
|
-
// Home
|
|
19
|
-
if (url === "/") {
|
|
20
|
-
return (
|
|
21
|
-
<svg
|
|
22
|
-
class={cls}
|
|
23
|
-
fill="none"
|
|
24
|
-
viewBox="0 0 24 24"
|
|
25
|
-
stroke-width={sw}
|
|
26
|
-
stroke={stroke}
|
|
27
|
-
>
|
|
28
|
-
<path
|
|
29
|
-
stroke-linecap="round"
|
|
30
|
-
stroke-linejoin="round"
|
|
31
|
-
d="m2.25 12 8.954-8.955a1.126 1.126 0 0 1 1.591 0L21.75 12M4.5 9.75v10.125c0 .621.504 1.125 1.125 1.125H9.75v-4.875c0-.621.504-1.125 1.125-1.125h2.25c.621 0 1.125.504 1.125 1.125V21h4.125c.621 0 1.125-.504 1.125-1.125V9.75M8.25 21h8.25"
|
|
32
|
-
/>
|
|
33
|
-
</svg>
|
|
34
|
-
);
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
// Search
|
|
38
|
-
if (url === "/search") {
|
|
39
|
-
return (
|
|
40
|
-
<svg
|
|
41
|
-
class={cls}
|
|
42
|
-
fill="none"
|
|
43
|
-
viewBox="0 0 24 24"
|
|
44
|
-
stroke-width={sw}
|
|
45
|
-
stroke={stroke}
|
|
46
|
-
>
|
|
47
|
-
<path
|
|
48
|
-
stroke-linecap="round"
|
|
49
|
-
stroke-linejoin="round"
|
|
50
|
-
d="m21 21-5.197-5.197m0 0A7.5 7.5 0 1 0 5.196 5.196a7.5 7.5 0 0 0 10.607 10.607Z"
|
|
51
|
-
/>
|
|
52
|
-
</svg>
|
|
53
|
-
);
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
// Archive
|
|
57
|
-
if (url === "/archive") {
|
|
58
|
-
return (
|
|
59
|
-
<svg
|
|
60
|
-
class={cls}
|
|
61
|
-
fill="none"
|
|
62
|
-
viewBox="0 0 24 24"
|
|
63
|
-
stroke-width={sw}
|
|
64
|
-
stroke={stroke}
|
|
65
|
-
>
|
|
66
|
-
<path
|
|
67
|
-
stroke-linecap="round"
|
|
68
|
-
stroke-linejoin="round"
|
|
69
|
-
d="M12 6.042A8.967 8.967 0 0 0 6 3.75c-1.052 0-2.062.18-3 .512v14.25A8.987 8.987 0 0 1 6 18c2.305 0 4.408.867 6 2.292m0-14.25a8.966 8.966 0 0 1 6-2.292c1.052 0 2.062.18 3 .512v14.25A8.987 8.987 0 0 0 18 18a8.967 8.967 0 0 0-6 2.292m0-14.25v14.25"
|
|
70
|
-
/>
|
|
71
|
-
</svg>
|
|
72
|
-
);
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
// RSS — common for /feed, /rss, /atom
|
|
76
|
-
if (url.match(/\/(feed|rss|atom)/)) {
|
|
77
|
-
return (
|
|
78
|
-
<svg
|
|
79
|
-
class={cls}
|
|
80
|
-
fill="none"
|
|
81
|
-
viewBox="0 0 24 24"
|
|
82
|
-
stroke-width={sw}
|
|
83
|
-
stroke={stroke}
|
|
84
|
-
>
|
|
85
|
-
<path
|
|
86
|
-
stroke-linecap="round"
|
|
87
|
-
stroke-linejoin="round"
|
|
88
|
-
d="M12.75 19.5v-.75a7.5 7.5 0 0 0-7.5-7.5H4.5m0-6.75h.75c7.87 0 14.25 6.38 14.25 14.25v.75M4.5 19.5a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5Z"
|
|
89
|
-
/>
|
|
90
|
-
</svg>
|
|
91
|
-
);
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
// External link
|
|
95
|
-
if (url.startsWith("http")) {
|
|
96
|
-
return (
|
|
97
|
-
<svg
|
|
98
|
-
class={cls}
|
|
99
|
-
fill="none"
|
|
100
|
-
viewBox="0 0 24 24"
|
|
101
|
-
stroke-width={sw}
|
|
102
|
-
stroke={stroke}
|
|
103
|
-
>
|
|
104
|
-
<path
|
|
105
|
-
stroke-linecap="round"
|
|
106
|
-
stroke-linejoin="round"
|
|
107
|
-
d="M13.5 6H5.25A2.25 2.25 0 0 0 3 8.25v10.5A2.25 2.25 0 0 0 5.25 21h10.5A2.25 2.25 0 0 0 18 18.75V10.5m-10.5 6L21 3m0 0h-5.25M21 3v5.25"
|
|
108
|
-
/>
|
|
109
|
-
</svg>
|
|
110
|
-
);
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
// Default: generic page icon
|
|
114
|
-
return (
|
|
115
|
-
<svg
|
|
116
|
-
class={cls}
|
|
117
|
-
fill="none"
|
|
118
|
-
viewBox="0 0 24 24"
|
|
119
|
-
stroke-width={sw}
|
|
120
|
-
stroke={stroke}
|
|
121
|
-
>
|
|
122
|
-
<path
|
|
123
|
-
stroke-linecap="round"
|
|
124
|
-
stroke-linejoin="round"
|
|
125
|
-
d="M19.5 14.25v-2.625a3.375 3.375 0 0 0-3.375-3.375h-1.5A1.125 1.125 0 0 1 13.5 7.125v-1.5a3.375 3.375 0 0 0-3.375-3.375H8.25m2.25 0H5.625c-.621 0-1.125.504-1.125 1.125v17.25c0 .621.504 1.125 1.125 1.125h12.75c.621 0 1.125-.504 1.125-1.125V11.25a9 9 0 0 0-9-9Z"
|
|
126
|
-
/>
|
|
127
|
-
</svg>
|
|
128
|
-
);
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
function SidebarLink({ link }: { link: NavItemView }) {
|
|
132
|
-
return (
|
|
133
|
-
<a
|
|
134
|
-
href={link.url}
|
|
135
|
-
class={`threads-sidebar-link ${link.isActive ? "threads-sidebar-link-active" : ""}`}
|
|
136
|
-
title={link.label}
|
|
137
|
-
{...(link.isExternal
|
|
138
|
-
? { target: "_blank", rel: "noopener noreferrer" }
|
|
139
|
-
: {})}
|
|
140
|
-
>
|
|
141
|
-
<NavIcon url={link.url} isActive={link.isActive} />
|
|
142
|
-
</a>
|
|
143
|
-
);
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
function MobileTabLink({ link }: { link: NavItemView }) {
|
|
147
|
-
return (
|
|
148
|
-
<a
|
|
149
|
-
href={link.url}
|
|
150
|
-
class={`threads-mobile-tab ${link.isActive ? "threads-mobile-tab-active" : ""}`}
|
|
151
|
-
{...(link.isExternal
|
|
152
|
-
? { target: "_blank", rel: "noopener noreferrer" }
|
|
153
|
-
: {})}
|
|
154
|
-
>
|
|
155
|
-
<NavIcon url={link.url} isActive={link.isActive} />
|
|
156
|
-
</a>
|
|
157
|
-
);
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
export const ThreadsSiteLayout: FC<PropsWithChildren<SiteLayoutProps>> = ({
|
|
161
|
-
siteName,
|
|
162
|
-
links,
|
|
163
|
-
children,
|
|
164
|
-
}) => {
|
|
165
|
-
return (
|
|
166
|
-
<div class="threads-page">
|
|
167
|
-
{/* Desktop: left icon sidebar — no border, on gray background */}
|
|
168
|
-
<aside class="threads-sidebar">
|
|
169
|
-
<a href="/" class="threads-logo" title={siteName}>
|
|
170
|
-
<span class="text-2xl font-black leading-none">@</span>
|
|
171
|
-
</a>
|
|
172
|
-
<nav class="flex flex-1 flex-col items-center gap-1">
|
|
173
|
-
{links.map((link) => (
|
|
174
|
-
<SidebarLink key={link.id} link={link} />
|
|
175
|
-
))}
|
|
176
|
-
</nav>
|
|
177
|
-
</aside>
|
|
178
|
-
|
|
179
|
-
{/* Main content — white rounded container on gray background */}
|
|
180
|
-
<main class="threads-main">
|
|
181
|
-
<div class="threads-container">
|
|
182
|
-
<div class="threads-content">{children}</div>
|
|
183
|
-
</div>
|
|
184
|
-
</main>
|
|
185
|
-
|
|
186
|
-
{/* Mobile: bottom tab bar */}
|
|
187
|
-
<nav class="threads-mobile-tabs">
|
|
188
|
-
{links.map((link) => (
|
|
189
|
-
<MobileTabLink key={link.id} link={link} />
|
|
190
|
-
))}
|
|
191
|
-
</nav>
|
|
192
|
-
</div>
|
|
193
|
-
);
|
|
194
|
-
};
|