@jant/core 0.3.27 → 0.3.29
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/bin/reset-password.js +22 -0
- package/dist/client/client.css +1 -0
- package/dist/client/client.js +31561 -0
- package/dist/index.js +15209 -15
- package/package.json +25 -15
- package/src/__tests__/helpers/app.ts +19 -3
- package/src/__tests__/helpers/db.ts +44 -0
- package/src/__tests__/helpers/lingui-core-macro-mock.ts +33 -0
- package/src/app.tsx +111 -174
- package/src/client.ts +13 -0
- package/src/db/migrations/0007_post_collections_m2m.sql +94 -0
- package/src/db/migrations/0008_add_collection_dividers.sql +8 -0
- package/src/db/migrations/0009_drop_collection_show_divider.sql +2 -0
- package/src/db/migrations/0010_add_performance_indexes.sql +16 -0
- package/src/db/schema.ts +24 -4
- package/src/i18n/locales/en.po +810 -385
- package/src/i18n/locales/en.ts +1 -1
- package/src/i18n/locales/zh-Hans.po +733 -522
- package/src/i18n/locales/zh-Hans.ts +1 -1
- package/src/i18n/locales/zh-Hant.po +733 -522
- package/src/i18n/locales/zh-Hant.ts +1 -1
- package/src/i18n/middleware.ts +7 -11
- package/src/index.ts +1 -1
- package/src/lib/__tests__/icons.test.ts +178 -0
- package/src/lib/__tests__/resolve-config.test.ts +184 -0
- package/src/lib/__tests__/schemas.test.ts +12 -6
- package/src/lib/__tests__/theme.test.ts +62 -0
- package/src/lib/__tests__/timezones.test.ts +1 -1
- package/src/lib/__tests__/url.test.ts +12 -0
- package/src/lib/__tests__/view.test.ts +1 -5
- package/src/lib/avatar-upload.ts +18 -10
- package/src/lib/collection-form-bridge.ts +52 -0
- package/src/lib/collections-reorder.ts +28 -0
- package/src/lib/compose-bridge.ts +251 -0
- package/src/lib/errors.ts +116 -0
- package/src/lib/excerpt.ts +1 -1
- package/src/lib/favicon.ts +3 -5
- package/src/lib/html.ts +22 -0
- package/src/lib/icon-catalog.ts +181 -0
- package/src/lib/icons.ts +202 -0
- package/src/lib/navigation.ts +18 -33
- package/src/lib/pagination.ts +3 -2
- package/src/lib/post-form-bridge.ts +136 -0
- package/src/lib/render.tsx +11 -4
- package/src/lib/resolve-config.ts +157 -0
- package/src/lib/schemas.ts +76 -12
- package/src/lib/settings-bridge.ts +139 -0
- package/src/lib/storage.ts +37 -16
- package/src/lib/theme.ts +5 -7
- package/src/lib/timeline.ts +4 -8
- package/src/lib/toast.ts +134 -0
- package/src/lib/upload.ts +71 -0
- package/src/lib/url.ts +9 -1
- package/src/lib/version.ts +16 -0
- package/src/lib/view.ts +9 -10
- package/src/middleware/__tests__/auth.test.ts +6 -28
- package/src/middleware/__tests__/onboarding.test.ts +1 -1
- package/src/middleware/auth.ts +6 -12
- package/src/middleware/config.ts +51 -0
- package/src/middleware/error-handler.ts +56 -0
- package/src/middleware/onboarding.ts +1 -1
- package/src/preset.css +6 -0
- package/src/routes/__tests__/compose.test.ts +104 -17
- package/src/routes/api/__tests__/collections.test.ts +93 -2
- package/src/routes/api/__tests__/posts.test.ts +2 -1
- package/src/routes/api/__tests__/settings.test.ts +1 -1
- package/src/routes/api/collections.ts +64 -68
- package/src/routes/api/nav-items.ts +21 -59
- package/src/routes/api/pages.ts +18 -46
- package/src/routes/api/posts.ts +64 -86
- package/src/routes/api/search.ts +6 -4
- package/src/routes/api/settings.ts +8 -24
- package/src/routes/api/upload.ts +55 -53
- package/src/routes/auth/__tests__/setup.test.ts +118 -0
- package/src/routes/auth/reset.tsx +17 -66
- package/src/routes/auth/setup.tsx +67 -11
- package/src/routes/auth/signin.tsx +44 -8
- package/src/routes/compose.tsx +194 -0
- package/src/routes/dash/__tests__/font-theme.test.ts +110 -0
- package/src/routes/dash/__tests__/pages.test.ts +2 -2
- package/src/routes/dash/__tests__/settings-avatar.test.ts +23 -12
- package/src/routes/dash/appearance.tsx +173 -0
- package/src/routes/dash/collections.tsx +80 -14
- package/src/routes/dash/index.tsx +12 -14
- package/src/routes/dash/media.tsx +46 -49
- package/src/routes/dash/pages.tsx +85 -37
- package/src/routes/dash/posts.tsx +60 -23
- package/src/routes/dash/redirects.tsx +43 -33
- package/src/routes/dash/settings.tsx +234 -214
- package/src/routes/feed/__tests__/rss.test.ts +7 -3
- package/src/routes/feed/rss.ts +11 -16
- package/src/routes/feed/sitemap.ts +15 -9
- package/src/routes/pages/__tests__/collections.test.ts +9 -8
- package/src/routes/pages/archive.tsx +2 -2
- package/src/routes/pages/collection.tsx +76 -9
- package/src/routes/pages/collections.tsx +3 -1
- package/src/routes/pages/featured.tsx +2 -2
- package/src/routes/pages/home.tsx +3 -3
- package/src/routes/pages/latest.tsx +2 -2
- package/src/routes/pages/page.tsx +2 -2
- package/src/routes/pages/post.tsx +2 -2
- package/src/routes/pages/search.tsx +2 -2
- package/src/services/__tests__/collection.test.ts +324 -34
- package/src/services/__tests__/media.test.ts +1 -1
- package/src/services/__tests__/page.test.ts +116 -1
- package/src/services/auth.ts +88 -0
- package/src/services/collection.ts +169 -30
- package/src/services/index.ts +8 -3
- package/src/services/media.ts +39 -12
- package/src/services/navigation.ts +17 -5
- package/src/services/page.ts +24 -4
- package/src/services/post.ts +87 -19
- package/src/services/search.ts +0 -1
- package/src/services/settings.ts +21 -13
- package/src/style.css +3 -0
- package/src/styles/components.css +42 -1
- package/src/styles/tokens.css +4 -0
- package/src/styles/ui.css +902 -73
- package/src/types/app-context.ts +25 -0
- package/src/types/bindings.ts +1 -0
- package/src/types/config.ts +60 -23
- package/src/types/entities.ts +12 -2
- package/src/types/lingui-react-macro.d.ts +3 -3
- package/src/types/operations.ts +2 -4
- package/src/types/views.ts +1 -3
- package/src/ui/__tests__/font-themes.test.ts +27 -8
- package/src/ui/color-themes.ts +1 -1
- package/src/ui/components/__tests__/jant-collection-form.test.ts +153 -0
- package/src/ui/components/__tests__/jant-compose-dialog.test.ts +512 -0
- package/src/ui/components/__tests__/jant-compose-editor.test.ts +272 -0
- package/src/ui/components/__tests__/jant-post-form.test.ts +172 -0
- package/src/ui/components/__tests__/jant-settings-avatar.test.ts +235 -0
- package/src/ui/components/__tests__/jant-settings-general.test.ts +319 -0
- package/src/ui/components/collection-types.ts +45 -0
- package/src/ui/components/compose-types.ts +75 -0
- package/src/ui/components/jant-collection-form.ts +512 -0
- package/src/ui/components/jant-compose-dialog.ts +494 -0
- package/src/ui/components/jant-compose-editor.ts +799 -0
- package/src/ui/components/jant-post-form.ts +290 -0
- package/src/ui/components/jant-settings-avatar.ts +231 -0
- package/src/ui/components/jant-settings-general.ts +436 -0
- package/src/ui/components/post-form-template.ts +260 -0
- package/src/ui/components/post-form-types.ts +87 -0
- package/src/ui/components/settings-types.ts +62 -0
- package/src/ui/compose/ComposeDialog.tsx +141 -385
- package/src/ui/compose/ComposePrompt.tsx +3 -3
- package/src/ui/dash/PostList.tsx +55 -61
- package/src/ui/dash/appearance/AdvancedContent.tsx +80 -0
- package/src/ui/dash/appearance/AppearanceNav.tsx +56 -0
- package/src/ui/dash/appearance/ColorThemeContent.tsx +129 -0
- package/src/ui/dash/appearance/FontThemeContent.tsx +98 -0
- package/src/ui/dash/collections/CollectionForm.tsx +130 -117
- package/src/ui/dash/collections/CollectionsListContent.tsx +102 -41
- package/src/ui/dash/collections/IconPickerGrid.tsx +50 -0
- package/src/ui/dash/collections/ViewCollectionContent.tsx +14 -3
- package/src/ui/dash/index.ts +1 -1
- package/src/ui/dash/posts/PostForm.tsx +248 -0
- package/src/ui/dash/settings/AccountContent.tsx +69 -80
- package/src/ui/dash/settings/GeneralContent.tsx +159 -478
- package/src/ui/dash/settings/SettingsNav.tsx +4 -4
- package/src/ui/font-themes.ts +115 -32
- package/src/ui/layouts/BaseLayout.tsx +49 -19
- package/src/ui/layouts/DashLayout.tsx +14 -9
- package/src/ui/layouts/SiteLayout.tsx +38 -23
- package/src/ui/pages/CollectionPage.tsx +12 -2
- package/src/ui/pages/CollectionsPage.tsx +27 -27
- package/src/ui/pages/HomePage.tsx +15 -6
- package/src/ui/pages/SearchPage.tsx +1 -2
- package/src/ui/shared/CollectionsSidebar.tsx +59 -0
- package/src/ui/shared/Pagination.tsx +2 -2
- package/dist/app.js +0 -267
- package/dist/auth.js +0 -39
- package/dist/client.js +0 -13
- package/dist/db/index.js +0 -10
- package/dist/db/schema.js +0 -224
- package/dist/i18n/Trans.js +0 -24
- package/dist/i18n/context.js +0 -58
- package/dist/i18n/detect.js +0 -26
- package/dist/i18n/i18n.js +0 -49
- package/dist/i18n/index.js +0 -44
- package/dist/i18n/locales/en.js +0 -1
- package/dist/i18n/locales/zh-Hans.js +0 -1
- package/dist/i18n/locales/zh-Hant.js +0 -1
- package/dist/i18n/locales.js +0 -13
- package/dist/i18n/middleware.js +0 -30
- package/dist/lib/avatar-upload.js +0 -134
- package/dist/lib/config.js +0 -143
- package/dist/lib/constants.js +0 -50
- package/dist/lib/excerpt.js +0 -76
- package/dist/lib/favicon.js +0 -102
- package/dist/lib/feed.js +0 -123
- package/dist/lib/image-processor.js +0 -187
- package/dist/lib/image.js +0 -97
- package/dist/lib/index.js +0 -7
- package/dist/lib/markdown.js +0 -83
- package/dist/lib/media-helpers.js +0 -49
- package/dist/lib/media-upload.js +0 -104
- package/dist/lib/nav-reorder.js +0 -27
- package/dist/lib/navigation.js +0 -79
- package/dist/lib/pagination.js +0 -44
- package/dist/lib/render.js +0 -53
- package/dist/lib/schemas.js +0 -174
- package/dist/lib/sqid.js +0 -72
- package/dist/lib/sse.js +0 -218
- package/dist/lib/storage.js +0 -164
- package/dist/lib/theme.js +0 -65
- package/dist/lib/time.js +0 -159
- package/dist/lib/timeline.js +0 -95
- package/dist/lib/timezones.js +0 -388
- package/dist/lib/url.js +0 -89
- package/dist/lib/view.js +0 -217
- package/dist/middleware/auth.js +0 -52
- package/dist/middleware/onboarding.js +0 -41
- package/dist/routes/api/collections.js +0 -124
- package/dist/routes/api/nav-items.js +0 -104
- package/dist/routes/api/pages.js +0 -91
- package/dist/routes/api/posts.js +0 -218
- package/dist/routes/api/search.js +0 -48
- package/dist/routes/api/settings.js +0 -68
- package/dist/routes/api/upload.js +0 -246
- package/dist/routes/auth/reset.js +0 -221
- package/dist/routes/auth/setup.js +0 -194
- package/dist/routes/auth/signin.js +0 -176
- package/dist/routes/compose.js +0 -48
- package/dist/routes/dash/collections.js +0 -115
- package/dist/routes/dash/index.js +0 -118
- package/dist/routes/dash/media.js +0 -106
- package/dist/routes/dash/pages.js +0 -294
- package/dist/routes/dash/posts.js +0 -244
- package/dist/routes/dash/redirects.js +0 -257
- package/dist/routes/dash/settings.js +0 -379
- package/dist/routes/feed/rss.js +0 -62
- package/dist/routes/feed/sitemap.js +0 -49
- package/dist/routes/pages/archive.js +0 -62
- package/dist/routes/pages/collection.js +0 -34
- package/dist/routes/pages/collections.js +0 -28
- package/dist/routes/pages/featured.js +0 -36
- package/dist/routes/pages/home.js +0 -64
- package/dist/routes/pages/latest.js +0 -45
- package/dist/routes/pages/page.js +0 -68
- package/dist/routes/pages/post.js +0 -44
- package/dist/routes/pages/search.js +0 -54
- package/dist/services/collection.js +0 -109
- package/dist/services/index.js +0 -24
- package/dist/services/media.js +0 -117
- package/dist/services/navigation.js +0 -91
- package/dist/services/page.js +0 -84
- package/dist/services/post.js +0 -229
- package/dist/services/redirect.js +0 -48
- package/dist/services/search.js +0 -67
- package/dist/services/settings.js +0 -68
- package/dist/types/bindings.js +0 -3
- package/dist/types/config.js +0 -147
- package/dist/types/constants.js +0 -27
- package/dist/types/entities.js +0 -3
- package/dist/types/lingui-react-macro.d.js +0 -9
- package/dist/types/operations.js +0 -3
- package/dist/types/props.js +0 -3
- package/dist/types/sortablejs.d.js +0 -5
- package/dist/types/views.js +0 -5
- package/dist/types.js +0 -11
- package/dist/ui/color-themes.js +0 -268
- package/dist/ui/compose/ComposeDialog.js +0 -467
- package/dist/ui/compose/ComposePrompt.js +0 -55
- package/dist/ui/dash/ActionButtons.js +0 -46
- package/dist/ui/dash/CrudPageHeader.js +0 -22
- package/dist/ui/dash/DangerZone.js +0 -36
- package/dist/ui/dash/FormatBadge.js +0 -27
- package/dist/ui/dash/ListItemRow.js +0 -21
- package/dist/ui/dash/PageForm.js +0 -195
- package/dist/ui/dash/PostForm.js +0 -395
- package/dist/ui/dash/PostList.js +0 -83
- package/dist/ui/dash/StatusBadge.js +0 -46
- package/dist/ui/dash/collections/CollectionForm.js +0 -152
- package/dist/ui/dash/collections/CollectionsListContent.js +0 -68
- package/dist/ui/dash/collections/ViewCollectionContent.js +0 -96
- package/dist/ui/dash/index.js +0 -10
- package/dist/ui/dash/media/MediaListContent.js +0 -166
- package/dist/ui/dash/media/ViewMediaContent.js +0 -212
- package/dist/ui/dash/pages/LinkFormContent.js +0 -130
- package/dist/ui/dash/pages/UnifiedPagesContent.js +0 -193
- package/dist/ui/dash/settings/AccountContent.js +0 -209
- package/dist/ui/dash/settings/AppearanceContent.js +0 -259
- package/dist/ui/dash/settings/GeneralContent.js +0 -536
- package/dist/ui/dash/settings/SettingsNav.js +0 -41
- package/dist/ui/feed/LinkCard.js +0 -72
- package/dist/ui/feed/NoteCard.js +0 -58
- package/dist/ui/feed/QuoteCard.js +0 -63
- package/dist/ui/feed/ThreadPreview.js +0 -48
- package/dist/ui/feed/TimelineFeed.js +0 -41
- package/dist/ui/feed/TimelineItem.js +0 -27
- package/dist/ui/font-themes.js +0 -36
- package/dist/ui/layouts/BaseLayout.js +0 -153
- package/dist/ui/layouts/DashLayout.js +0 -141
- package/dist/ui/layouts/SiteLayout.js +0 -169
- package/dist/ui/pages/ArchivePage.js +0 -143
- package/dist/ui/pages/CollectionPage.js +0 -70
- package/dist/ui/pages/CollectionsPage.js +0 -76
- package/dist/ui/pages/FeaturedPage.js +0 -24
- package/dist/ui/pages/HomePage.js +0 -24
- package/dist/ui/pages/PostPage.js +0 -55
- package/dist/ui/pages/SearchPage.js +0 -122
- package/dist/ui/pages/SinglePage.js +0 -23
- package/dist/ui/shared/EmptyState.js +0 -27
- package/dist/ui/shared/MediaGallery.js +0 -35
- package/dist/ui/shared/Pagination.js +0 -195
- package/dist/ui/shared/ThreadView.js +0 -108
- package/dist/ui/shared/index.js +0 -5
- package/dist/vendor/datastar.js +0 -1606
- package/src/lib/__tests__/config.test.ts +0 -192
- package/src/lib/config.ts +0 -167
- package/src/routes/compose.ts +0 -63
- package/src/ui/dash/PostForm.tsx +0 -360
- package/src/ui/dash/settings/AppearanceContent.tsx +0 -254
|
@@ -1,28 +1,24 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
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.
|
|
3
6
|
*/
|
|
4
7
|
|
|
5
8
|
import { useLingui } from "@lingui/react/macro";
|
|
9
|
+
import type { FC } from "hono/jsx";
|
|
6
10
|
import type { Collection } from "../../../types.js";
|
|
7
11
|
|
|
8
|
-
|
|
9
|
-
collection,
|
|
10
|
-
isEdit,
|
|
11
|
-
}: {
|
|
12
|
+
interface CollectionFormProps {
|
|
12
13
|
collection?: Collection;
|
|
13
14
|
isEdit?: boolean;
|
|
14
|
-
}
|
|
15
|
-
const { t } = useLingui();
|
|
16
|
-
|
|
17
|
-
const signals = JSON.stringify({
|
|
18
|
-
title: collection?.title ?? "",
|
|
19
|
-
slug: collection?.slug ?? "",
|
|
20
|
-
description: collection?.description ?? "",
|
|
21
|
-
}).replace(/</g, "\\u003c");
|
|
15
|
+
}
|
|
22
16
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
17
|
+
export const CollectionForm: FC<CollectionFormProps> = ({
|
|
18
|
+
collection,
|
|
19
|
+
isEdit,
|
|
20
|
+
}) => {
|
|
21
|
+
const { t } = useLingui();
|
|
26
22
|
|
|
27
23
|
const heading = isEdit
|
|
28
24
|
? t({ message: "Edit Collection", comment: "@context: Page heading" })
|
|
@@ -38,6 +34,95 @@ export function CollectionForm({
|
|
|
38
34
|
comment: "@context: Button to save new collection",
|
|
39
35
|
});
|
|
40
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
|
+
|
|
41
126
|
const cancelHref = isEdit
|
|
42
127
|
? `/dash/collections/${collection?.id}`
|
|
43
128
|
: "/dash/collections";
|
|
@@ -46,108 +131,36 @@ export function CollectionForm({
|
|
|
46
131
|
<>
|
|
47
132
|
<h1 class="text-2xl font-semibold mb-6">{heading}</h1>
|
|
48
133
|
|
|
49
|
-
<form
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
134
|
+
<jant-collection-form
|
|
135
|
+
labels={labels}
|
|
136
|
+
initial={initial}
|
|
137
|
+
action={action}
|
|
138
|
+
cancel-href={cancelHref}
|
|
139
|
+
is-edit={isEdit ? "true" : undefined}
|
|
54
140
|
>
|
|
55
|
-
<div class="
|
|
56
|
-
<
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
class="
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
</div>
|
|
77
|
-
|
|
78
|
-
<div class="field">
|
|
79
|
-
<label class="label">
|
|
80
|
-
{t({ message: "Slug", comment: "@context: Collection form field" })}
|
|
81
|
-
</label>
|
|
82
|
-
<input
|
|
83
|
-
type="text"
|
|
84
|
-
data-bind="slug"
|
|
85
|
-
class="input"
|
|
86
|
-
required
|
|
87
|
-
pattern="[a-z0-9-]+"
|
|
88
|
-
placeholder={isEdit ? undefined : "my-collection"}
|
|
89
|
-
/>
|
|
90
|
-
{!isEdit && (
|
|
91
|
-
<p class="text-xs text-muted-foreground mt-1">
|
|
92
|
-
{t({
|
|
93
|
-
message: "URL-safe identifier (lowercase, numbers, hyphens)",
|
|
94
|
-
comment: "@context: Collection path help text",
|
|
95
|
-
})}
|
|
96
|
-
</p>
|
|
97
|
-
)}
|
|
98
|
-
</div>
|
|
99
|
-
|
|
100
|
-
<div class="field">
|
|
101
|
-
<label class="label">
|
|
102
|
-
{t({
|
|
103
|
-
message: "Description (optional)",
|
|
104
|
-
comment: "@context: Collection form field",
|
|
105
|
-
})}
|
|
106
|
-
</label>
|
|
107
|
-
<textarea
|
|
108
|
-
data-bind="description"
|
|
109
|
-
class="textarea"
|
|
110
|
-
rows={3}
|
|
111
|
-
placeholder={
|
|
112
|
-
isEdit
|
|
113
|
-
? undefined
|
|
114
|
-
: t({
|
|
115
|
-
message: "What's this collection about?",
|
|
116
|
-
comment: "@context: Collection description placeholder",
|
|
117
|
-
})
|
|
118
|
-
}
|
|
119
|
-
>
|
|
120
|
-
{collection?.description ?? ""}
|
|
121
|
-
</textarea>
|
|
122
|
-
</div>
|
|
123
|
-
|
|
124
|
-
<div class="flex gap-2">
|
|
125
|
-
<button type="submit" class="btn" data-attr:disabled="$_loading">
|
|
126
|
-
<svg
|
|
127
|
-
data-show="$_loading"
|
|
128
|
-
style="display:none"
|
|
129
|
-
class="animate-spin size-4"
|
|
130
|
-
xmlns="http://www.w3.org/2000/svg"
|
|
131
|
-
viewBox="0 0 24 24"
|
|
132
|
-
fill="none"
|
|
133
|
-
stroke="currentColor"
|
|
134
|
-
stroke-width="2"
|
|
135
|
-
stroke-linecap="round"
|
|
136
|
-
stroke-linejoin="round"
|
|
137
|
-
role="status"
|
|
138
|
-
>
|
|
139
|
-
<path d="M21 12a9 9 0 1 1-6.219-8.56" />
|
|
140
|
-
</svg>
|
|
141
|
-
{submitLabel}
|
|
142
|
-
</button>
|
|
143
|
-
<a href={cancelHref} class="btn-outline">
|
|
144
|
-
{t({
|
|
145
|
-
message: "Cancel",
|
|
146
|
-
comment: "@context: Button to cancel form",
|
|
147
|
-
})}
|
|
148
|
-
</a>
|
|
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>
|
|
149
162
|
</div>
|
|
150
|
-
</form>
|
|
163
|
+
</jant-collection-form>
|
|
151
164
|
</>
|
|
152
165
|
);
|
|
153
|
-
}
|
|
166
|
+
};
|
|
@@ -1,23 +1,34 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Collections list view
|
|
2
|
+
* Collections list view with drag-and-drop reordering
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import { useLingui } from "@lingui/react/macro";
|
|
6
|
-
import type { Collection } from "../../../types.js";
|
|
7
|
-
import {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
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
13
|
|
|
14
14
|
export function CollectionsListContent({
|
|
15
15
|
collections,
|
|
16
|
+
dividers,
|
|
17
|
+
postCounts,
|
|
16
18
|
}: {
|
|
17
19
|
collections: Collection[];
|
|
20
|
+
dividers: CollectionDivider[];
|
|
21
|
+
postCounts: Map<number, number>;
|
|
18
22
|
}) {
|
|
19
23
|
const { t } = useLingui();
|
|
20
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
|
+
|
|
21
32
|
return (
|
|
22
33
|
<>
|
|
23
34
|
<CrudPageHeader
|
|
@@ -25,14 +36,26 @@ export function CollectionsListContent({
|
|
|
25
36
|
message: "Collections",
|
|
26
37
|
comment: "@context: Dashboard heading",
|
|
27
38
|
})}
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
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>
|
|
34
57
|
|
|
35
|
-
{
|
|
58
|
+
{!hasItems ? (
|
|
36
59
|
<EmptyState
|
|
37
60
|
message={t({
|
|
38
61
|
message: "No collections yet.",
|
|
@@ -45,39 +68,77 @@ export function CollectionsListContent({
|
|
|
45
68
|
ctaHref="/dash/collections/new"
|
|
46
69
|
/>
|
|
47
70
|
) : (
|
|
48
|
-
<div class="flex flex-col
|
|
49
|
-
{
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
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>
|
|
53
132
|
<ActionButtons
|
|
54
133
|
editHref={`/dash/collections/${col.id}/edit`}
|
|
55
134
|
editLabel={t({
|
|
56
135
|
message: "Edit",
|
|
57
136
|
comment: "@context: Button to edit collection",
|
|
58
137
|
})}
|
|
59
|
-
viewHref={`/c/${col.slug}`}
|
|
60
|
-
viewLabel={t({
|
|
61
|
-
message: "View",
|
|
62
|
-
comment: "@context: Button to view collection",
|
|
63
|
-
})}
|
|
64
138
|
/>
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
href={`/dash/collections/${col.id}`}
|
|
69
|
-
class="font-medium hover:underline"
|
|
70
|
-
>
|
|
71
|
-
{col.title}
|
|
72
|
-
</a>
|
|
73
|
-
<p class="text-sm text-muted-foreground">/{col.slug}</p>
|
|
74
|
-
{col.description && (
|
|
75
|
-
<p class="text-sm text-muted-foreground mt-1">
|
|
76
|
-
{col.description}
|
|
77
|
-
</p>
|
|
78
|
-
)}
|
|
79
|
-
</ListItemRow>
|
|
80
|
-
))}
|
|
139
|
+
</div>
|
|
140
|
+
);
|
|
141
|
+
})}
|
|
81
142
|
</div>
|
|
82
143
|
)}
|
|
83
144
|
</>
|
|
@@ -0,0 +1,50 @@
|
|
|
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
|
+
};
|
|
@@ -6,6 +6,7 @@ import { useLingui } from "@lingui/react/macro";
|
|
|
6
6
|
import type { Collection, PostView } from "../../../types.js";
|
|
7
7
|
import { ActionButtons } from "../index.js";
|
|
8
8
|
import { encode } from "../../../lib/sqid.js";
|
|
9
|
+
import { renderCollectionIcon } from "../../../lib/icons.js";
|
|
9
10
|
|
|
10
11
|
export function ViewCollectionContent({
|
|
11
12
|
collection,
|
|
@@ -15,17 +16,27 @@ export function ViewCollectionContent({
|
|
|
15
16
|
posts: PostView[];
|
|
16
17
|
}) {
|
|
17
18
|
const { t } = useLingui();
|
|
19
|
+
const count = String(posts.length);
|
|
18
20
|
const postsHeader = t({
|
|
19
|
-
message:
|
|
21
|
+
message: `Posts in Collection (${count})`,
|
|
20
22
|
comment: "@context: Collection posts section heading",
|
|
21
|
-
values: { count: String(posts.length) },
|
|
22
23
|
});
|
|
23
24
|
|
|
24
25
|
return (
|
|
25
26
|
<>
|
|
26
27
|
<div class="flex items-center justify-between mb-6">
|
|
27
28
|
<div>
|
|
28
|
-
<h1 class="text-2xl font-semibold">
|
|
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>
|
|
29
40
|
<p class="text-sm text-muted-foreground">/{collection.slug}</p>
|
|
30
41
|
</div>
|
|
31
42
|
<ActionButtons
|
package/src/ui/dash/index.ts
CHANGED
|
@@ -5,6 +5,6 @@ export { EmptyState, type EmptyStateProps } from "../shared/EmptyState.js";
|
|
|
5
5
|
export { FormatBadge, type FormatBadgeProps } from "./FormatBadge.js";
|
|
6
6
|
export { ListItemRow, type ListItemRowProps } from "./ListItemRow.js";
|
|
7
7
|
export { PageForm, type PageFormProps } from "./PageForm.js";
|
|
8
|
-
export { PostForm, type PostFormProps } from "./PostForm.js";
|
|
8
|
+
export { PostForm, type PostFormProps } from "./posts/PostForm.js";
|
|
9
9
|
export { PostList, type PostListProps } from "./PostList.js";
|
|
10
10
|
export { StatusBadge, type StatusBadgeProps } from "./StatusBadge.js";
|