@jant/core 0.3.35 → 0.3.36
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/assets/module-RjUF93sV.js +716 -0
- package/dist/client/assets/native-48B9X9Wg.js +1 -0
- package/dist/client/assets/url-8Dj-5CLW.js +1 -0
- package/dist/client/client.css +1 -1
- package/dist/client/client.js +3109 -2294
- package/dist/index.js +3026 -2778
- package/package.json +13 -4
- package/src/__tests__/helpers/app.ts +1 -1
- package/src/__tests__/helpers/db.ts +6 -0
- package/src/app.tsx +1 -5
- package/src/{lib → client}/avatar-upload.ts +1 -1
- package/src/{lib → client}/collection-form-bridge.ts +2 -2
- package/src/{ui → client}/components/__tests__/jant-collection-form.test.ts +26 -9
- package/src/{ui → client}/components/__tests__/jant-compose-dialog.test.ts +46 -14
- package/src/{ui → client}/components/__tests__/jant-compose-editor.test.ts +64 -24
- package/src/{ui → client}/components/__tests__/jant-post-form.test.ts +24 -14
- package/src/{ui → client}/components/__tests__/jant-settings-general.test.ts +3 -3
- package/src/client/components/collection-sidebar-types.ts +45 -0
- package/src/{ui → client}/components/collection-types.ts +3 -4
- package/src/{ui → client}/components/compose-types.ts +3 -1
- package/src/{ui → client}/components/jant-collection-form.ts +301 -182
- package/src/client/components/jant-collection-sidebar.ts +801 -0
- package/src/{ui → client}/components/jant-compose-dialog.ts +231 -1
- package/src/client/components/jant-compose-editor.ts +1249 -0
- package/src/client/components/jant-compose-fullscreen.ts +338 -0
- package/src/client/components/jant-media-lightbox.ts +257 -0
- package/src/{ui → client}/components/jant-nav-manager.ts +143 -84
- package/src/{ui → client}/components/jant-post-form.ts +57 -8
- package/src/{ui → client}/components/jant-settings-general.ts +2 -2
- package/src/{ui → client}/components/nav-manager-types.ts +3 -0
- package/src/{ui → client}/components/post-form-template.ts +35 -31
- package/src/{ui → client}/components/post-form-types.ts +7 -3
- package/src/{lib → client}/compose-bridge.ts +9 -7
- package/src/client/lazy-slugify.ts +51 -0
- package/src/{lib → client}/media-upload.ts +16 -3
- package/src/{lib → client}/nav-manager-bridge.ts +1 -1
- package/src/client/page-slug-bridge.ts +42 -0
- package/src/{lib → client}/post-form-bridge.ts +2 -2
- package/src/{lib → client}/settings-bridge.ts +3 -3
- package/src/client/tiptap/bubble-menu.ts +205 -0
- package/src/client/tiptap/create-editor.ts +40 -0
- package/src/client/tiptap/exitable-marks.ts +73 -0
- package/src/client/tiptap/extensions.ts +60 -0
- package/src/client/tiptap/image-node.ts +488 -0
- package/src/client/tiptap/link-toolbar.ts +371 -0
- package/src/client/tiptap/more-break.ts +50 -0
- package/src/client/tiptap/paste-image.ts +140 -0
- package/src/client/tiptap/slash-commands.ts +328 -0
- package/src/{types → client/types}/sortablejs.d.ts +1 -1
- package/src/client.ts +24 -17
- package/src/db/migrations/0012_add_tiptap_columns.sql +2 -0
- package/src/db/migrations/0013_replace_featured_with_visibility.sql +8 -0
- package/src/db/schema.ts +6 -1
- package/src/i18n/locales/en.po +641 -215
- package/src/i18n/locales/en.ts +1 -1
- package/src/i18n/locales/zh-Hans.po +642 -204
- package/src/i18n/locales/zh-Hans.ts +1 -1
- package/src/i18n/locales/zh-Hant.po +642 -204
- package/src/i18n/locales/zh-Hant.ts +1 -1
- package/src/lib/__tests__/resolve-config.test.ts +2 -2
- package/src/lib/__tests__/schemas.test.ts +9 -6
- package/src/lib/__tests__/url.test.ts +2 -2
- package/src/lib/__tests__/view.test.ts +9 -9
- package/src/lib/emoji-catalog.ts +146 -0
- package/src/lib/feed.ts +1 -1
- package/src/lib/media-helpers.ts +10 -9
- package/src/lib/render.tsx +4 -3
- package/src/lib/resolve-config.ts +8 -1
- package/src/lib/schemas.ts +2 -3
- package/src/lib/summary.ts +92 -0
- package/src/lib/timeline.ts +2 -0
- package/src/lib/tiptap-render.ts +196 -0
- package/src/lib/upload.ts +97 -9
- package/src/lib/url.ts +7 -23
- package/src/lib/view.ts +33 -19
- package/src/middleware/error-handler.ts +3 -3
- package/src/preset.css +38 -0
- package/src/routes/api/collections.ts +20 -3
- package/src/routes/api/posts.ts +48 -33
- package/src/routes/api/upload.ts +7 -5
- package/src/routes/auth/reset.tsx +5 -4
- package/src/routes/auth/setup.tsx +26 -11
- package/src/routes/auth/signin.tsx +10 -7
- package/src/routes/compose.tsx +20 -11
- package/src/routes/dash/__tests__/settings-avatar.test.ts +43 -8
- package/src/routes/dash/index.tsx +7 -1
- package/src/routes/dash/media.tsx +3 -0
- package/src/routes/dash/pages.tsx +8 -2
- package/src/routes/dash/posts.tsx +6 -2
- package/src/routes/dash/redirects.tsx +15 -9
- package/src/routes/dash/settings.tsx +336 -32
- package/src/routes/feed/__tests__/rss.test.ts +7 -7
- package/src/routes/feed/rss.ts +8 -6
- package/src/routes/pages/__tests__/featured.test.ts +6 -7
- package/src/routes/pages/archive.tsx +11 -7
- package/src/routes/pages/collection.tsx +32 -15
- package/src/routes/pages/collections.tsx +11 -2
- package/src/routes/pages/featured.tsx +1 -1
- package/src/routes/pages/home.tsx +1 -1
- package/src/services/__tests__/post.test.ts +124 -33
- package/src/services/__tests__/settings.test.ts +3 -3
- package/src/services/page.ts +16 -3
- package/src/services/post.ts +96 -37
- package/src/services/search.ts +4 -2
- package/src/services/settings.ts +6 -2
- package/src/styles/components.css +240 -60
- package/src/styles/tokens.css +10 -0
- package/src/styles/ui.css +1157 -81
- package/src/types/bindings.ts +5 -0
- package/src/types/config.ts +23 -1
- package/src/types/constants.ts +3 -0
- package/src/types/entities.ts +9 -2
- package/src/types/operations.ts +9 -3
- package/src/types/props.ts +3 -3
- package/src/types/views.ts +3 -2
- package/src/ui/compose/ComposeDialog.tsx +24 -7
- package/src/ui/dash/PageForm.tsx +2 -0
- package/src/ui/dash/PostList.tsx +5 -5
- package/src/ui/dash/StatusBadge.tsx +13 -5
- package/src/ui/dash/appearance/AdvancedContent.tsx +52 -61
- package/src/ui/dash/appearance/ColorThemeContent.tsx +30 -35
- package/src/ui/dash/appearance/FontThemeContent.tsx +65 -73
- package/src/ui/dash/appearance/NavigationContent.tsx +107 -96
- package/src/ui/dash/media/MediaListContent.tsx +9 -4
- package/src/ui/dash/media/ViewMediaContent.tsx +2 -2
- package/src/ui/dash/pages/PagesContent.tsx +2 -1
- package/src/ui/dash/posts/PostForm.tsx +19 -7
- package/src/ui/dash/settings/AccountContent.tsx +133 -138
- package/src/ui/dash/settings/AvatarContent.tsx +70 -0
- package/src/ui/dash/settings/GeneralContent.tsx +3 -62
- package/src/ui/dash/settings/SettingsRootContent.tsx +236 -0
- package/src/ui/layouts/DashLayout.tsx +157 -75
- package/src/ui/layouts/SiteLayout.tsx +13 -13
- package/src/ui/pages/ArchivePage.tsx +10 -7
- package/src/ui/pages/CollectionPage.tsx +6 -35
- package/src/ui/pages/CollectionsPage.tsx +2 -1
- package/src/ui/pages/FeaturedPage.tsx +2 -1
- package/src/ui/pages/HomePage.tsx +1 -1
- package/src/ui/pages/SearchPage.tsx +1 -1
- package/src/ui/shared/CollectionsSidebar.tsx +228 -3
- package/src/ui/shared/MediaGallery.tsx +179 -41
- package/src/lib/collections-reorder.ts +0 -28
- package/src/routes/dash/appearance.tsx +0 -240
- package/src/routes/dash/collections.tsx +0 -211
- package/src/ui/components/jant-compose-editor.ts +0 -814
- package/src/ui/dash/appearance/AppearanceNav.tsx +0 -60
- package/src/ui/dash/collections/CollectionForm.tsx +0 -166
- package/src/ui/dash/collections/CollectionsListContent.tsx +0 -146
- package/src/ui/dash/collections/IconPickerGrid.tsx +0 -50
- package/src/ui/dash/collections/ViewCollectionContent.tsx +0 -103
- package/src/ui/dash/settings/SettingsNav.tsx +0 -52
- /package/src/{ui → client}/components/__tests__/jant-settings-avatar.test.ts +0 -0
- /package/src/{ui → client}/components/jant-settings-avatar.ts +0 -0
- /package/src/{ui → client}/components/settings-types.ts +0 -0
- /package/src/{lib → client}/image-processor.ts +0 -0
- /package/src/{lib → client}/toast.ts +0 -0
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Appearance sub-navigation tabs
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import { useLingui } from "@lingui/react/macro";
|
|
6
|
-
|
|
7
|
-
export type AppearanceTab = "navigation" | "color" | "fonts" | "advanced";
|
|
8
|
-
|
|
9
|
-
export function AppearanceNav({ currentTab }: { currentTab: AppearanceTab }) {
|
|
10
|
-
const { t } = useLingui();
|
|
11
|
-
|
|
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
|
-
},
|
|
21
|
-
{
|
|
22
|
-
id: "color",
|
|
23
|
-
label: t({
|
|
24
|
-
message: "Color Theme",
|
|
25
|
-
comment: "@context: Appearance sub-navigation tab",
|
|
26
|
-
}),
|
|
27
|
-
href: "/dash/appearance/color",
|
|
28
|
-
},
|
|
29
|
-
{
|
|
30
|
-
id: "fonts",
|
|
31
|
-
label: t({
|
|
32
|
-
message: "Font Theme",
|
|
33
|
-
comment: "@context: Appearance sub-navigation tab",
|
|
34
|
-
}),
|
|
35
|
-
href: "/dash/appearance/fonts",
|
|
36
|
-
},
|
|
37
|
-
{
|
|
38
|
-
id: "advanced",
|
|
39
|
-
label: t({
|
|
40
|
-
message: "Advanced",
|
|
41
|
-
comment: "@context: Appearance sub-navigation tab",
|
|
42
|
-
}),
|
|
43
|
-
href: "/dash/appearance/advanced",
|
|
44
|
-
},
|
|
45
|
-
];
|
|
46
|
-
|
|
47
|
-
return (
|
|
48
|
-
<nav class="dash-subnav">
|
|
49
|
-
{tabs.map((tab) => (
|
|
50
|
-
<a
|
|
51
|
-
key={tab.id}
|
|
52
|
-
href={tab.href}
|
|
53
|
-
class={tab.id === currentTab ? "active" : ""}
|
|
54
|
-
>
|
|
55
|
-
{tab.label}
|
|
56
|
-
</a>
|
|
57
|
-
))}
|
|
58
|
-
</nav>
|
|
59
|
-
);
|
|
60
|
-
}
|
|
@@ -1,166 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Collection Form
|
|
3
|
-
*
|
|
4
|
-
* Server-rendered shell that provides data/labels to the Lit component
|
|
5
|
-
* `<jant-collection-form>`. Includes heading and SSR fallback skeleton.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import { useLingui } from "@lingui/react/macro";
|
|
9
|
-
import type { FC } from "hono/jsx";
|
|
10
|
-
import type { Collection } from "../../../types.js";
|
|
11
|
-
|
|
12
|
-
interface CollectionFormProps {
|
|
13
|
-
collection?: Collection;
|
|
14
|
-
isEdit?: boolean;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export const CollectionForm: FC<CollectionFormProps> = ({
|
|
18
|
-
collection,
|
|
19
|
-
isEdit,
|
|
20
|
-
}) => {
|
|
21
|
-
const { t } = useLingui();
|
|
22
|
-
|
|
23
|
-
const heading = isEdit
|
|
24
|
-
? t({ message: "Edit Collection", comment: "@context: Page heading" })
|
|
25
|
-
: t({ message: "New Collection", comment: "@context: Page heading" });
|
|
26
|
-
|
|
27
|
-
const submitLabel = isEdit
|
|
28
|
-
? t({
|
|
29
|
-
message: "Update Collection",
|
|
30
|
-
comment: "@context: Button to save collection changes",
|
|
31
|
-
})
|
|
32
|
-
: t({
|
|
33
|
-
message: "Create Collection",
|
|
34
|
-
comment: "@context: Button to save new collection",
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
const labels = JSON.stringify({
|
|
38
|
-
titleLabel: t({
|
|
39
|
-
message: "Title",
|
|
40
|
-
comment: "@context: Collection form field",
|
|
41
|
-
}),
|
|
42
|
-
titlePlaceholder: t({
|
|
43
|
-
message: "My Collection",
|
|
44
|
-
comment: "@context: Collection title placeholder",
|
|
45
|
-
}),
|
|
46
|
-
slugLabel: t({
|
|
47
|
-
message: "Slug",
|
|
48
|
-
comment: "@context: Collection form field",
|
|
49
|
-
}),
|
|
50
|
-
slugHelp: t({
|
|
51
|
-
message:
|
|
52
|
-
"URL-safe identifier (lowercase, numbers, hyphens). For CJK titles, slug will be auto-generated on the server.",
|
|
53
|
-
comment: "@context: Collection path help text",
|
|
54
|
-
}),
|
|
55
|
-
descriptionLabel: t({
|
|
56
|
-
message: "Description (optional)",
|
|
57
|
-
comment: "@context: Collection form field",
|
|
58
|
-
}),
|
|
59
|
-
descriptionPlaceholder: t({
|
|
60
|
-
message: "What's this collection about?",
|
|
61
|
-
comment: "@context: Collection description placeholder",
|
|
62
|
-
}),
|
|
63
|
-
iconLabel: t({
|
|
64
|
-
message: "Icon (optional)",
|
|
65
|
-
comment: "@context: Collection form field",
|
|
66
|
-
}),
|
|
67
|
-
chooseIcon: t({
|
|
68
|
-
message: "Choose Icon",
|
|
69
|
-
comment: "@context: Button to open icon picker",
|
|
70
|
-
}),
|
|
71
|
-
removeIcon: t({
|
|
72
|
-
message: "Remove",
|
|
73
|
-
comment: "@context: Button to remove icon",
|
|
74
|
-
}),
|
|
75
|
-
dialogTitle: t({
|
|
76
|
-
message: "Choose Icon",
|
|
77
|
-
comment: "@context: Icon picker dialog title",
|
|
78
|
-
}),
|
|
79
|
-
dialogClose: t({
|
|
80
|
-
message: "Close",
|
|
81
|
-
comment: "@context: Button to close icon picker",
|
|
82
|
-
}),
|
|
83
|
-
searchIconsPlaceholder: t({
|
|
84
|
-
message: "Search icons...",
|
|
85
|
-
comment: "@context: Icon picker search placeholder",
|
|
86
|
-
}),
|
|
87
|
-
sortOrderLabel: t({
|
|
88
|
-
message: "Sort Order",
|
|
89
|
-
comment: "@context: Collection form field",
|
|
90
|
-
}),
|
|
91
|
-
sortNewest: t({
|
|
92
|
-
message: "Newest first",
|
|
93
|
-
comment: "@context: Collection sort order option",
|
|
94
|
-
}),
|
|
95
|
-
sortOldest: t({
|
|
96
|
-
message: "Oldest first",
|
|
97
|
-
comment: "@context: Collection sort order option",
|
|
98
|
-
}),
|
|
99
|
-
sortRatingDesc: t({
|
|
100
|
-
message: "Highest rated",
|
|
101
|
-
comment: "@context: Collection sort order option",
|
|
102
|
-
}),
|
|
103
|
-
sortRatingAsc: t({
|
|
104
|
-
message: "Lowest rated",
|
|
105
|
-
comment: "@context: Collection sort order option",
|
|
106
|
-
}),
|
|
107
|
-
submitLabel,
|
|
108
|
-
cancelLabel: t({
|
|
109
|
-
message: "Cancel",
|
|
110
|
-
comment: "@context: Button to cancel form",
|
|
111
|
-
}),
|
|
112
|
-
}).replace(/</g, "\\u003c");
|
|
113
|
-
|
|
114
|
-
const initial = JSON.stringify({
|
|
115
|
-
title: collection?.title ?? "",
|
|
116
|
-
slug: collection?.slug ?? "",
|
|
117
|
-
description: collection?.description ?? "",
|
|
118
|
-
sortOrder: collection?.sortOrder ?? "newest",
|
|
119
|
-
icon: collection?.icon ?? "",
|
|
120
|
-
}).replace(/</g, "\\u003c");
|
|
121
|
-
|
|
122
|
-
const action = isEdit
|
|
123
|
-
? `/dash/collections/${collection?.id}`
|
|
124
|
-
: "/dash/collections";
|
|
125
|
-
|
|
126
|
-
const cancelHref = isEdit
|
|
127
|
-
? `/dash/collections/${collection?.id}`
|
|
128
|
-
: "/dash/collections";
|
|
129
|
-
|
|
130
|
-
return (
|
|
131
|
-
<>
|
|
132
|
-
<h1 class="text-2xl font-semibold mb-6">{heading}</h1>
|
|
133
|
-
|
|
134
|
-
<jant-collection-form
|
|
135
|
-
labels={labels}
|
|
136
|
-
initial={initial}
|
|
137
|
-
action={action}
|
|
138
|
-
cancel-href={cancelHref}
|
|
139
|
-
is-edit={isEdit ? "true" : undefined}
|
|
140
|
-
>
|
|
141
|
-
<div class="flex flex-col gap-4 max-w-lg">
|
|
142
|
-
<div class="field">
|
|
143
|
-
<div class="label skel-label"></div>
|
|
144
|
-
<div class="input skel-input"></div>
|
|
145
|
-
</div>
|
|
146
|
-
<div class="field">
|
|
147
|
-
<div class="label skel-label"></div>
|
|
148
|
-
<div class="input skel-input"></div>
|
|
149
|
-
</div>
|
|
150
|
-
<div class="field">
|
|
151
|
-
<div class="label skel-label"></div>
|
|
152
|
-
<div class="textarea skel-textarea"></div>
|
|
153
|
-
</div>
|
|
154
|
-
<div class="field">
|
|
155
|
-
<div class="label skel-label"></div>
|
|
156
|
-
<div class="input skel-input"></div>
|
|
157
|
-
</div>
|
|
158
|
-
<div class="flex gap-2">
|
|
159
|
-
<div class="btn skel-input min-w-28"></div>
|
|
160
|
-
<div class="btn-outline skel-input min-w-20"></div>
|
|
161
|
-
</div>
|
|
162
|
-
</div>
|
|
163
|
-
</jant-collection-form>
|
|
164
|
-
</>
|
|
165
|
-
);
|
|
166
|
-
};
|
|
@@ -1,146 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Collections list view with drag-and-drop reordering
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import { useLingui } from "@lingui/react/macro";
|
|
6
|
-
import type { Collection, CollectionDivider } from "../../../types.js";
|
|
7
|
-
import { EmptyState, ActionButtons, CrudPageHeader } from "../index.js";
|
|
8
|
-
import { renderCollectionIcon } from "../../../lib/icons.js";
|
|
9
|
-
|
|
10
|
-
type ListItem =
|
|
11
|
-
| { type: "collection"; data: Collection }
|
|
12
|
-
| { type: "divider"; data: CollectionDivider };
|
|
13
|
-
|
|
14
|
-
export function CollectionsListContent({
|
|
15
|
-
collections,
|
|
16
|
-
dividers,
|
|
17
|
-
postCounts,
|
|
18
|
-
}: {
|
|
19
|
-
collections: Collection[];
|
|
20
|
-
dividers: CollectionDivider[];
|
|
21
|
-
postCounts: Map<number, number>;
|
|
22
|
-
}) {
|
|
23
|
-
const { t } = useLingui();
|
|
24
|
-
|
|
25
|
-
const items: ListItem[] = [
|
|
26
|
-
...collections.map((c) => ({ type: "collection", data: c }) as ListItem),
|
|
27
|
-
...dividers.map((d) => ({ type: "divider", data: d }) as ListItem),
|
|
28
|
-
].sort((a, b) => a.data.position - b.data.position);
|
|
29
|
-
|
|
30
|
-
const hasItems = collections.length > 0 || dividers.length > 0;
|
|
31
|
-
|
|
32
|
-
return (
|
|
33
|
-
<>
|
|
34
|
-
<CrudPageHeader
|
|
35
|
-
title={t({
|
|
36
|
-
message: "Collections",
|
|
37
|
-
comment: "@context: Dashboard heading",
|
|
38
|
-
})}
|
|
39
|
-
>
|
|
40
|
-
<div class="flex items-center gap-2">
|
|
41
|
-
<form method="post" action="/dash/collections/dividers">
|
|
42
|
-
<button type="submit" class="btn-sm-outline">
|
|
43
|
-
{t({
|
|
44
|
-
message: "New Divider",
|
|
45
|
-
comment: "@context: Button to add divider between collections",
|
|
46
|
-
})}
|
|
47
|
-
</button>
|
|
48
|
-
</form>
|
|
49
|
-
<a href="/dash/collections/new" class="btn-sm">
|
|
50
|
-
{t({
|
|
51
|
-
message: "New Collection",
|
|
52
|
-
comment: "@context: Button to create new collection",
|
|
53
|
-
})}
|
|
54
|
-
</a>
|
|
55
|
-
</div>
|
|
56
|
-
</CrudPageHeader>
|
|
57
|
-
|
|
58
|
-
{!hasItems ? (
|
|
59
|
-
<EmptyState
|
|
60
|
-
message={t({
|
|
61
|
-
message: "No collections yet.",
|
|
62
|
-
comment: "@context: Empty state message",
|
|
63
|
-
})}
|
|
64
|
-
ctaText={t({
|
|
65
|
-
message: "New Collection",
|
|
66
|
-
comment: "@context: Button to create new collection",
|
|
67
|
-
})}
|
|
68
|
-
ctaHref="/dash/collections/new"
|
|
69
|
-
/>
|
|
70
|
-
) : (
|
|
71
|
-
<div id="collections-list" class="flex flex-col">
|
|
72
|
-
{items.map((item) => {
|
|
73
|
-
if (item.type === "divider") {
|
|
74
|
-
return (
|
|
75
|
-
<div
|
|
76
|
-
key={`d-${item.data.id}`}
|
|
77
|
-
class="py-2 flex items-center gap-4"
|
|
78
|
-
>
|
|
79
|
-
<div
|
|
80
|
-
class="flex-1 min-w-0 flex items-center gap-3 cursor-grab"
|
|
81
|
-
data-id={`d-${item.data.id}`}
|
|
82
|
-
>
|
|
83
|
-
<span class="text-muted-foreground select-none">⠿</span>
|
|
84
|
-
<hr class="flex-1 border-border" />
|
|
85
|
-
</div>
|
|
86
|
-
<form
|
|
87
|
-
method="post"
|
|
88
|
-
action={`/dash/collections/dividers/${item.data.id}/delete`}
|
|
89
|
-
>
|
|
90
|
-
<button
|
|
91
|
-
type="submit"
|
|
92
|
-
class="btn-sm-ghost text-muted-foreground hover:text-destructive"
|
|
93
|
-
title={t({
|
|
94
|
-
message: "Remove divider",
|
|
95
|
-
comment: "@context: Button to delete a divider",
|
|
96
|
-
})}
|
|
97
|
-
>
|
|
98
|
-
✕
|
|
99
|
-
</button>
|
|
100
|
-
</form>
|
|
101
|
-
</div>
|
|
102
|
-
);
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
const col = item.data;
|
|
106
|
-
const count = postCounts.get(col.id) ?? 0;
|
|
107
|
-
return (
|
|
108
|
-
<div key={`c-${col.id}`} class="py-2 flex items-center gap-4">
|
|
109
|
-
<div
|
|
110
|
-
class="flex-1 min-w-0 flex items-center gap-3 cursor-grab"
|
|
111
|
-
data-id={`c-${col.id}`}
|
|
112
|
-
>
|
|
113
|
-
<span class="text-muted-foreground select-none">⠿</span>
|
|
114
|
-
{col.icon && (
|
|
115
|
-
<span
|
|
116
|
-
class="flex items-center justify-center w-5 h-5 shrink-0"
|
|
117
|
-
dangerouslySetInnerHTML={{
|
|
118
|
-
__html: renderCollectionIcon(col.icon, {
|
|
119
|
-
size: 18,
|
|
120
|
-
}),
|
|
121
|
-
}}
|
|
122
|
-
/>
|
|
123
|
-
)}
|
|
124
|
-
<a
|
|
125
|
-
href={`/dash/collections/${col.id}`}
|
|
126
|
-
class="font-medium hover:underline"
|
|
127
|
-
>
|
|
128
|
-
{col.title}
|
|
129
|
-
</a>
|
|
130
|
-
<span class="badge-secondary">{count}</span>
|
|
131
|
-
</div>
|
|
132
|
-
<ActionButtons
|
|
133
|
-
editHref={`/dash/collections/${col.id}/edit`}
|
|
134
|
-
editLabel={t({
|
|
135
|
-
message: "Edit",
|
|
136
|
-
comment: "@context: Button to edit collection",
|
|
137
|
-
})}
|
|
138
|
-
/>
|
|
139
|
-
</div>
|
|
140
|
-
);
|
|
141
|
-
})}
|
|
142
|
-
</div>
|
|
143
|
-
)}
|
|
144
|
-
</>
|
|
145
|
-
);
|
|
146
|
-
}
|
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Icon Picker Grid
|
|
3
|
-
*
|
|
4
|
-
* HTML fragment returned by GET /dash/collections/icons.
|
|
5
|
-
* Renders a grid of icon buttons organized by category.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import type { FC } from "hono/jsx";
|
|
9
|
-
import { ICON_CATALOG } from "../../../lib/icon-catalog.js";
|
|
10
|
-
import { getIconSvg } from "../../../lib/icons.js";
|
|
11
|
-
|
|
12
|
-
export const IconPickerGrid: FC = () => {
|
|
13
|
-
return (
|
|
14
|
-
<div class="flex flex-col gap-4">
|
|
15
|
-
{Object.entries(ICON_CATALOG).map(([category, names]) => (
|
|
16
|
-
<div key={category} data-category={category}>
|
|
17
|
-
<h3 class="text-xs font-medium text-muted-foreground uppercase tracking-wider mb-2">
|
|
18
|
-
{category}
|
|
19
|
-
</h3>
|
|
20
|
-
<div class="grid grid-cols-8 gap-1">
|
|
21
|
-
{names.map((name) => {
|
|
22
|
-
const svg = getIconSvg(name);
|
|
23
|
-
if (!svg) return null;
|
|
24
|
-
return (
|
|
25
|
-
<button
|
|
26
|
-
key={name}
|
|
27
|
-
type="button"
|
|
28
|
-
class="flex items-center justify-center w-9 h-9 rounded-md hover:bg-accent transition-colors"
|
|
29
|
-
data-icon-name={name}
|
|
30
|
-
data-icon-svg={svg}
|
|
31
|
-
title={name}
|
|
32
|
-
data-on:click={`$iconName = el.dataset.iconName; $iconSvg = el.dataset.iconSvg; $icon = JSON.stringify({ name: $iconName, svg: $iconSvg, color: $iconColor }); const p = document.getElementById('icon-preview'); if (p) p.innerHTML = el.dataset.iconSvg; document.getElementById('icon-picker-dialog')?.close()`}
|
|
33
|
-
>
|
|
34
|
-
<span
|
|
35
|
-
class="w-5 h-5 flex items-center justify-center"
|
|
36
|
-
dangerouslySetInnerHTML={{
|
|
37
|
-
__html: svg
|
|
38
|
-
.replace(/width="24"/, 'width="20"')
|
|
39
|
-
.replace(/height="24"/, 'height="20"'),
|
|
40
|
-
}}
|
|
41
|
-
/>
|
|
42
|
-
</button>
|
|
43
|
-
);
|
|
44
|
-
})}
|
|
45
|
-
</div>
|
|
46
|
-
</div>
|
|
47
|
-
))}
|
|
48
|
-
</div>
|
|
49
|
-
);
|
|
50
|
-
};
|
|
@@ -1,103 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Single collection detail view
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import { useLingui } from "@lingui/react/macro";
|
|
6
|
-
import type { Collection, PostView } from "../../../types.js";
|
|
7
|
-
import { ActionButtons } from "../index.js";
|
|
8
|
-
import { encode } from "../../../lib/sqid.js";
|
|
9
|
-
import { renderCollectionIcon } from "../../../lib/icons.js";
|
|
10
|
-
|
|
11
|
-
export function ViewCollectionContent({
|
|
12
|
-
collection,
|
|
13
|
-
posts,
|
|
14
|
-
}: {
|
|
15
|
-
collection: Collection;
|
|
16
|
-
posts: PostView[];
|
|
17
|
-
}) {
|
|
18
|
-
const { t } = useLingui();
|
|
19
|
-
const count = String(posts.length);
|
|
20
|
-
const postsHeader = t({
|
|
21
|
-
message: `Posts in Collection (${count})`,
|
|
22
|
-
comment: "@context: Collection posts section heading",
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
return (
|
|
26
|
-
<>
|
|
27
|
-
<div class="flex items-center justify-between mb-6">
|
|
28
|
-
<div>
|
|
29
|
-
<h1 class="text-2xl font-semibold flex items-center gap-2">
|
|
30
|
-
{collection.icon && (
|
|
31
|
-
<span
|
|
32
|
-
class="shrink-0"
|
|
33
|
-
dangerouslySetInnerHTML={{
|
|
34
|
-
__html: renderCollectionIcon(collection.icon, { size: 24 }),
|
|
35
|
-
}}
|
|
36
|
-
/>
|
|
37
|
-
)}
|
|
38
|
-
{collection.title}
|
|
39
|
-
</h1>
|
|
40
|
-
<p class="text-sm text-muted-foreground">/{collection.slug}</p>
|
|
41
|
-
</div>
|
|
42
|
-
<ActionButtons
|
|
43
|
-
editHref={`/dash/collections/${collection.id}/edit`}
|
|
44
|
-
editLabel={t({
|
|
45
|
-
message: "Edit",
|
|
46
|
-
comment: "@context: Button to edit collection",
|
|
47
|
-
})}
|
|
48
|
-
viewHref={`/c/${collection.slug}`}
|
|
49
|
-
viewLabel={t({
|
|
50
|
-
message: "View",
|
|
51
|
-
comment: "@context: Button to view collection",
|
|
52
|
-
})}
|
|
53
|
-
/>
|
|
54
|
-
</div>
|
|
55
|
-
|
|
56
|
-
{collection.description && (
|
|
57
|
-
<p class="text-muted-foreground mb-6">{collection.description}</p>
|
|
58
|
-
)}
|
|
59
|
-
|
|
60
|
-
<div class="card">
|
|
61
|
-
<header>
|
|
62
|
-
<h2>{postsHeader}</h2>
|
|
63
|
-
</header>
|
|
64
|
-
<section>
|
|
65
|
-
{posts.length === 0 ? (
|
|
66
|
-
<p class="text-muted-foreground">
|
|
67
|
-
{t({
|
|
68
|
-
message: "No posts in this collection.",
|
|
69
|
-
comment: "@context: Empty state message",
|
|
70
|
-
})}
|
|
71
|
-
</p>
|
|
72
|
-
) : (
|
|
73
|
-
<div class="flex flex-col divide-y">
|
|
74
|
-
{posts.map((post) => (
|
|
75
|
-
<div key={post.id} class="py-3 flex items-center gap-4">
|
|
76
|
-
<div class="flex-1 min-w-0">
|
|
77
|
-
<a
|
|
78
|
-
href={`/dash/posts/${encode(post.id)}`}
|
|
79
|
-
class="font-medium hover:underline"
|
|
80
|
-
>
|
|
81
|
-
{post.title ||
|
|
82
|
-
post.excerpt?.slice(0, 50) ||
|
|
83
|
-
`Post #${post.id}`}
|
|
84
|
-
</a>
|
|
85
|
-
</div>
|
|
86
|
-
</div>
|
|
87
|
-
))}
|
|
88
|
-
</div>
|
|
89
|
-
)}
|
|
90
|
-
</section>
|
|
91
|
-
</div>
|
|
92
|
-
|
|
93
|
-
<div class="mt-6">
|
|
94
|
-
<a href="/dash/collections" class="text-sm hover:underline">
|
|
95
|
-
{t({
|
|
96
|
-
message: "\u2190 Back to Collections",
|
|
97
|
-
comment: "@context: Navigation link",
|
|
98
|
-
})}
|
|
99
|
-
</a>
|
|
100
|
-
</div>
|
|
101
|
-
</>
|
|
102
|
-
);
|
|
103
|
-
}
|
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Settings sub-navigation tabs
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import { useLingui } from "@lingui/react/macro";
|
|
6
|
-
|
|
7
|
-
export type SettingsTab = "general" | "redirects" | "account";
|
|
8
|
-
|
|
9
|
-
export function SettingsNav({ currentTab }: { currentTab: SettingsTab }) {
|
|
10
|
-
const { t } = useLingui();
|
|
11
|
-
|
|
12
|
-
const tabs: { id: SettingsTab; label: string; href: string }[] = [
|
|
13
|
-
{
|
|
14
|
-
id: "general",
|
|
15
|
-
label: t({
|
|
16
|
-
message: "General",
|
|
17
|
-
comment: "@context: Settings sub-navigation tab",
|
|
18
|
-
}),
|
|
19
|
-
href: "/dash/settings",
|
|
20
|
-
},
|
|
21
|
-
{
|
|
22
|
-
id: "redirects",
|
|
23
|
-
label: t({
|
|
24
|
-
message: "Redirects",
|
|
25
|
-
comment: "@context: Settings sub-navigation tab",
|
|
26
|
-
}),
|
|
27
|
-
href: "/dash/settings/redirects",
|
|
28
|
-
},
|
|
29
|
-
{
|
|
30
|
-
id: "account",
|
|
31
|
-
label: t({
|
|
32
|
-
message: "Account",
|
|
33
|
-
comment: "@context: Settings sub-navigation tab",
|
|
34
|
-
}),
|
|
35
|
-
href: "/dash/settings/account",
|
|
36
|
-
},
|
|
37
|
-
];
|
|
38
|
-
|
|
39
|
-
return (
|
|
40
|
-
<nav class="dash-subnav">
|
|
41
|
-
{tabs.map((tab) => (
|
|
42
|
-
<a
|
|
43
|
-
key={tab.id}
|
|
44
|
-
href={tab.href}
|
|
45
|
-
class={tab.id === currentTab ? "active" : ""}
|
|
46
|
-
>
|
|
47
|
-
{tab.label}
|
|
48
|
-
</a>
|
|
49
|
-
))}
|
|
50
|
-
</nav>
|
|
51
|
-
);
|
|
52
|
-
}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|