@jant/core 0.3.35 → 0.3.37
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/commands/export.js +1 -1
- package/bin/commands/import-site.js +529 -0
- package/bin/commands/reset-password.js +3 -2
- package/dist/client/assets/heic-to-DIRPI3VF.js +1 -0
- package/dist/client/assets/module-RjUF93sV.js +716 -0
- package/dist/client/assets/native-48B9X9Wg.js +1 -0
- package/dist/client/assets/url-FWFqPJPb.js +1 -0
- package/dist/client/client.css +1 -1
- package/dist/client/client.js +4564 -3013
- package/dist/index.js +12885 -8161
- package/package.json +23 -6
- package/src/__tests__/helpers/app.ts +10 -10
- package/src/__tests__/helpers/db.ts +91 -87
- package/src/app.tsx +157 -31
- package/src/auth.ts +20 -2
- package/src/client/archive-nav.js +187 -0
- package/src/client/audio-player.ts +478 -0
- package/src/client/audio-processor.ts +84 -0
- package/src/{lib → client}/avatar-upload.ts +4 -3
- 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/client/components/__tests__/jant-compose-dialog.test.ts +1140 -0
- package/src/client/components/__tests__/jant-compose-editor.test.ts +504 -0
- package/src/{ui → client}/components/__tests__/jant-post-form.test.ts +37 -17
- package/src/{ui → client}/components/__tests__/jant-settings-avatar.test.ts +2 -2
- package/src/{ui → client}/components/__tests__/jant-settings-general.test.ts +3 -3
- package/src/client/components/collection-sidebar-types.ts +43 -0
- package/src/{ui → client}/components/collection-types.ts +3 -4
- package/src/client/components/compose-types.ts +174 -0
- package/src/client/components/jant-collection-form.ts +667 -0
- package/src/client/components/jant-collection-sidebar.ts +805 -0
- package/src/client/components/jant-compose-dialog.ts +2161 -0
- package/src/client/components/jant-compose-editor.ts +1813 -0
- package/src/client/components/jant-compose-fullscreen.ts +283 -0
- package/src/client/components/jant-media-lightbox.ts +259 -0
- package/src/{ui → client}/components/jant-nav-manager.ts +97 -298
- package/src/{ui → client}/components/jant-post-form.ts +141 -12
- package/src/client/components/jant-post-menu.ts +1019 -0
- package/src/{ui → client}/components/jant-settings-avatar.ts +3 -3
- package/src/{ui → client}/components/jant-settings-general.ts +38 -4
- package/src/client/components/jant-text-preview.ts +232 -0
- package/src/{ui → client}/components/nav-manager-types.ts +6 -18
- package/src/{ui → client}/components/post-form-template.ts +137 -38
- package/src/{ui → client}/components/post-form-types.ts +15 -4
- package/src/client/compose-bridge.ts +583 -0
- package/src/{lib → client}/image-processor.ts +26 -8
- package/src/client/lazy-slugify.ts +51 -0
- package/src/client/media-metadata.ts +247 -0
- package/src/client/multipart-upload.ts +160 -0
- package/src/{lib → client}/nav-manager-bridge.ts +1 -1
- package/src/{lib → client}/post-form-bridge.ts +53 -2
- package/src/{lib → client}/settings-bridge.ts +3 -15
- package/src/client/thread-context.ts +140 -0
- package/src/client/tiptap/bubble-menu.ts +205 -0
- package/src/client/tiptap/create-editor.ts +86 -0
- package/src/client/tiptap/exitable-marks.ts +73 -0
- package/src/client/tiptap/extensions.ts +65 -0
- package/src/client/tiptap/image-node.ts +482 -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 +129 -0
- package/src/client/tiptap/slash-commands.ts +438 -0
- package/src/{lib → client}/toast.ts +101 -3
- package/src/client/types/sortablejs.d.ts +44 -0
- package/src/client/upload-with-metadata.ts +54 -0
- package/src/client/video-processor.ts +207 -0
- package/src/client.ts +27 -17
- package/src/db/__tests__/migrations.test.ts +118 -0
- package/src/db/index.ts +52 -0
- package/src/db/migrations/0000_baseline.sql +269 -0
- package/src/db/migrations/0001_fts_setup.sql +31 -0
- package/src/db/migrations/meta/0000_snapshot.json +703 -119
- package/src/db/migrations/meta/0001_snapshot.json +1337 -0
- package/src/db/migrations/meta/_journal.json +4 -39
- package/src/db/schema.ts +409 -140
- package/src/i18n/__tests__/detect.test.ts +115 -0
- package/src/i18n/context.tsx +2 -2
- package/src/i18n/detect.ts +85 -1
- package/src/i18n/i18n.ts +1 -1
- package/src/i18n/index.ts +2 -1
- package/src/i18n/locales/en.po +783 -1087
- package/src/i18n/locales/en.ts +1 -1
- package/src/i18n/locales/zh-Hans.po +867 -812
- package/src/i18n/locales/zh-Hans.ts +1 -1
- package/src/i18n/locales/zh-Hant.po +878 -823
- package/src/i18n/locales/zh-Hant.ts +1 -1
- package/src/i18n/middleware.ts +6 -0
- package/src/index.ts +5 -7
- package/src/lib/__tests__/blurhash-placeholder.test.ts +75 -0
- package/src/lib/__tests__/constants.test.ts +0 -1
- package/src/lib/__tests__/markdown-to-tiptap.test.ts +358 -0
- package/src/lib/__tests__/nanoid.test.ts +26 -0
- package/src/lib/__tests__/resolve-config.test.ts +2 -2
- package/src/lib/__tests__/schemas.test.ts +186 -65
- package/src/lib/__tests__/slug.test.ts +126 -0
- package/src/lib/__tests__/sse.test.ts +6 -6
- package/src/lib/__tests__/summary.test.ts +264 -0
- package/src/lib/__tests__/theme.test.ts +1 -1
- package/src/lib/__tests__/timeline.test.ts +33 -30
- package/src/lib/__tests__/tiptap-to-markdown.test.ts +346 -0
- package/src/lib/__tests__/url.test.ts +2 -2
- package/src/lib/__tests__/view.test.ts +140 -65
- package/src/lib/blurhash-placeholder.ts +102 -0
- package/src/lib/constants.ts +3 -1
- package/src/lib/emoji-catalog.ts +963 -0
- package/src/lib/errors.ts +11 -8
- package/src/lib/feed.ts +77 -31
- package/src/lib/html.ts +2 -1
- package/src/lib/icon-catalog.ts +5033 -1
- package/src/lib/icons.ts +3 -2
- package/src/lib/index.ts +0 -1
- package/src/lib/markdown-to-tiptap.ts +286 -0
- package/src/lib/media-helpers.ts +22 -12
- package/src/lib/nanoid.ts +29 -0
- package/src/lib/navigation.ts +1 -1
- package/src/lib/render.tsx +24 -5
- package/src/lib/resolve-config.ts +13 -2
- package/src/lib/schemas.ts +226 -58
- package/src/lib/search-snippet.ts +34 -0
- package/src/lib/slug.ts +96 -0
- package/src/lib/sse.ts +6 -6
- package/src/lib/storage.ts +115 -7
- package/src/lib/summary.ts +158 -0
- package/src/lib/theme.ts +11 -8
- package/src/lib/timeline.ts +76 -34
- package/src/lib/tiptap-render.ts +191 -0
- package/src/lib/tiptap-to-markdown.ts +305 -0
- package/src/lib/upload.ts +263 -14
- package/src/lib/url.ts +37 -22
- package/src/lib/view.ts +236 -55
- package/src/middleware/__tests__/auth.test.ts +191 -11
- package/src/middleware/__tests__/onboarding.test.ts +12 -10
- package/src/middleware/auth.ts +63 -9
- package/src/middleware/error-handler.ts +3 -3
- package/src/middleware/onboarding.ts +1 -1
- package/src/middleware/secure-headers.ts +40 -0
- package/src/preset.css +83 -2
- package/src/routes/__tests__/compose.test.ts +17 -24
- package/src/routes/api/__tests__/collections.test.ts +109 -61
- package/src/routes/api/__tests__/nav-items.test.ts +46 -29
- package/src/routes/api/__tests__/posts.test.ts +132 -68
- package/src/routes/api/__tests__/search.test.ts +15 -2
- package/src/routes/api/__tests__/upload-multipart.test.ts +534 -0
- package/src/routes/api/collections.ts +57 -31
- package/src/routes/api/custom-urls.ts +80 -0
- package/src/routes/api/export.ts +31 -0
- package/src/routes/api/nav-items.ts +23 -19
- package/src/routes/api/posts.ts +81 -62
- package/src/routes/api/search.ts +3 -4
- package/src/routes/api/upload-multipart.ts +245 -0
- package/src/routes/api/upload.ts +92 -24
- package/src/routes/auth/__tests__/setup.test.ts +20 -60
- package/src/routes/auth/reset.tsx +5 -4
- package/src/routes/auth/setup.tsx +39 -31
- package/src/routes/auth/signin.tsx +13 -14
- package/src/routes/compose.tsx +27 -63
- package/src/routes/dash/__tests__/settings-avatar.test.ts +44 -9
- package/src/routes/dash/custom-urls.tsx +414 -0
- package/src/routes/dash/settings.tsx +475 -99
- package/src/routes/feed/__tests__/rss.test.ts +22 -23
- package/src/routes/feed/rss.ts +6 -2
- package/src/routes/feed/sitemap.ts +2 -12
- package/src/routes/pages/__tests__/collections.test.ts +5 -6
- package/src/routes/pages/__tests__/featured.test.ts +36 -18
- package/src/routes/pages/archive.tsx +177 -37
- package/src/routes/pages/collection.tsx +43 -14
- package/src/routes/pages/collections.tsx +11 -2
- package/src/routes/pages/featured.tsx +27 -3
- package/src/routes/pages/home.tsx +15 -14
- package/src/routes/pages/latest.tsx +1 -11
- package/src/routes/pages/new.tsx +39 -0
- package/src/routes/pages/page.tsx +95 -49
- package/src/routes/pages/search.tsx +1 -1
- package/src/services/__tests__/api-token.test.ts +135 -0
- package/src/services/__tests__/collection.test.ts +275 -227
- package/src/services/__tests__/custom-url.test.ts +213 -0
- package/src/services/__tests__/media.test.ts +162 -22
- package/src/services/__tests__/navigation.test.ts +109 -68
- package/src/services/__tests__/post-timeline.test.ts +205 -32
- package/src/services/__tests__/post.test.ts +800 -230
- package/src/services/__tests__/search.test.ts +67 -10
- package/src/services/__tests__/settings.test.ts +3 -3
- package/src/services/api-token.ts +166 -0
- package/src/services/auth.ts +17 -2
- package/src/services/collection.ts +397 -131
- package/src/services/custom-url.ts +188 -0
- package/src/services/export.ts +802 -0
- package/src/services/index.ts +26 -19
- package/src/services/media.ts +100 -22
- package/src/services/navigation.ts +158 -47
- package/src/services/path.ts +339 -0
- package/src/services/post.ts +764 -172
- package/src/services/search.ts +161 -74
- package/src/services/settings.ts +6 -2
- package/src/styles/components.css +293 -62
- package/src/styles/tokens.css +93 -5
- package/src/styles/ui.css +4349 -766
- package/src/types/bindings.ts +8 -0
- package/src/types/config.ts +34 -4
- package/src/types/constants.ts +17 -2
- package/src/types/entities.ts +83 -37
- package/src/types/operations.ts +20 -27
- package/src/types/props.ts +52 -17
- package/src/types/views.ts +48 -24
- package/src/ui/color-themes.ts +133 -23
- package/src/ui/compose/ComposeDialog.tsx +255 -16
- package/src/ui/compose/ComposePrompt.tsx +1 -1
- package/src/ui/dash/CrudPageHeader.tsx +1 -1
- package/src/ui/dash/ListItemRow.tsx +1 -1
- package/src/ui/dash/StatusBadge.tsx +12 -2
- package/src/ui/dash/appearance/AdvancedContent.tsx +71 -59
- package/src/ui/dash/appearance/ColorThemeContent.tsx +48 -33
- package/src/ui/dash/appearance/FontThemeContent.tsx +65 -73
- package/src/ui/dash/appearance/NavigationContent.tsx +106 -135
- package/src/ui/dash/index.ts +0 -3
- package/src/ui/dash/settings/AccountContent.tsx +87 -146
- package/src/ui/dash/settings/AccountMenuContent.tsx +147 -0
- package/src/ui/dash/settings/ApiTokensContent.tsx +232 -0
- package/src/ui/dash/settings/AvatarContent.tsx +78 -0
- package/src/ui/dash/settings/GeneralContent.tsx +3 -62
- package/src/ui/dash/settings/SessionsContent.tsx +159 -0
- package/src/ui/dash/settings/SettingsRootContent.tsx +266 -0
- package/src/ui/feed/LinkCard.tsx +89 -40
- package/src/ui/feed/NoteCard.tsx +39 -25
- package/src/ui/feed/PostStatusBadges.tsx +67 -0
- package/src/ui/feed/QuoteCard.tsx +38 -23
- package/src/ui/feed/ThreadPreview.tsx +90 -26
- package/src/ui/feed/TimelineFeed.tsx +3 -2
- package/src/ui/feed/TimelineItem.tsx +15 -6
- package/src/ui/feed/__tests__/thread-preview.test.ts +107 -0
- package/src/ui/feed/thread-preview-state.ts +61 -0
- package/src/ui/font-themes.ts +2 -2
- package/src/ui/layouts/BaseLayout.tsx +2 -2
- package/src/ui/layouts/SiteLayout.tsx +116 -103
- package/src/ui/pages/ArchivePage.tsx +923 -95
- package/src/ui/pages/CollectionPage.tsx +6 -35
- package/src/ui/pages/CollectionsPage.tsx +2 -1
- package/src/ui/pages/ComposePage.tsx +54 -0
- package/src/ui/pages/FeaturedPage.tsx +2 -1
- package/src/ui/pages/HomePage.tsx +1 -1
- package/src/ui/pages/PostPage.tsx +30 -45
- package/src/ui/pages/SearchPage.tsx +182 -38
- package/src/ui/shared/AdminBreadcrumb.tsx +29 -0
- package/src/ui/shared/CollectionsSidebar.tsx +239 -4
- package/src/ui/shared/MediaGallery.tsx +475 -41
- package/src/ui/shared/PostFooter.tsx +204 -0
- package/src/ui/shared/StarRating.tsx +27 -0
- package/src/ui/shared/__tests__/format-chars.test.ts +35 -0
- package/src/ui/shared/index.ts +0 -1
- package/src/db/migrations/0000_square_wallflower.sql +0 -118
- package/src/db/migrations/0001_add_search_fts.sql +0 -34
- package/src/db/migrations/0002_add_media_attachments.sql +0 -3
- package/src/db/migrations/0003_add_navigation_links.sql +0 -8
- package/src/db/migrations/0004_add_storage_provider.sql +0 -3
- package/src/db/migrations/0005_v2_schema_migration.sql +0 -268
- package/src/db/migrations/0006_rename_slug_to_path.sql +0 -5
- package/src/db/migrations/0007_post_collections_m2m.sql +0 -94
- package/src/db/migrations/0008_add_collection_dividers.sql +0 -8
- package/src/db/migrations/0009_drop_collection_show_divider.sql +0 -2
- package/src/db/migrations/0010_add_performance_indexes.sql +0 -16
- package/src/db/migrations/0011_add_path_registry.sql +0 -23
- package/src/db/migrations/meta/0003_snapshot.json +0 -821
- package/src/lib/__tests__/sqid.test.ts +0 -65
- package/src/lib/collections-reorder.ts +0 -28
- package/src/lib/compose-bridge.ts +0 -280
- package/src/lib/media-upload.ts +0 -148
- package/src/lib/sqid.ts +0 -79
- package/src/routes/api/__tests__/pages.test.ts +0 -218
- package/src/routes/api/pages.ts +0 -73
- package/src/routes/dash/__tests__/pages.test.ts +0 -226
- package/src/routes/dash/appearance.tsx +0 -240
- package/src/routes/dash/collections.tsx +0 -211
- package/src/routes/dash/index.tsx +0 -103
- package/src/routes/dash/media.tsx +0 -132
- package/src/routes/dash/pages.tsx +0 -239
- package/src/routes/dash/posts.tsx +0 -334
- package/src/routes/dash/redirects.tsx +0 -257
- package/src/routes/pages/post.tsx +0 -59
- package/src/services/__tests__/page.test.ts +0 -298
- package/src/services/__tests__/path-registry.test.ts +0 -165
- package/src/services/__tests__/redirect.test.ts +0 -159
- package/src/services/page.ts +0 -203
- package/src/services/path-registry.ts +0 -160
- package/src/services/redirect.ts +0 -97
- package/src/types/sortablejs.d.ts +0 -29
- package/src/ui/components/__tests__/jant-compose-dialog.test.ts +0 -512
- package/src/ui/components/__tests__/jant-compose-editor.test.ts +0 -272
- package/src/ui/components/compose-types.ts +0 -75
- package/src/ui/components/jant-collection-form.ts +0 -512
- package/src/ui/components/jant-compose-dialog.ts +0 -495
- package/src/ui/components/jant-compose-editor.ts +0 -814
- package/src/ui/dash/PageForm.tsx +0 -185
- package/src/ui/dash/PostList.tsx +0 -95
- 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/media/MediaListContent.tsx +0 -201
- package/src/ui/dash/media/ViewMediaContent.tsx +0 -208
- package/src/ui/dash/pages/PagesContent.tsx +0 -74
- package/src/ui/dash/posts/PostForm.tsx +0 -248
- package/src/ui/dash/settings/SettingsNav.tsx +0 -52
- package/src/ui/layouts/DashLayout.tsx +0 -165
- package/src/ui/pages/SinglePage.tsx +0 -23
- package/src/ui/shared/ThreadView.tsx +0 -136
- /package/src/{ui → client}/components/settings-types.ts +0 -0
package/src/ui/dash/PageForm.tsx
DELETED
|
@@ -1,185 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Page Creation/Edit Form
|
|
3
|
-
*
|
|
4
|
-
* For managing standalone pages (about, now, etc.)
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import type { FC } from "hono/jsx";
|
|
8
|
-
import type { Page } from "../../types.js";
|
|
9
|
-
import { useLingui } from "@lingui/react/macro";
|
|
10
|
-
|
|
11
|
-
export interface PageFormProps {
|
|
12
|
-
page?: Page;
|
|
13
|
-
action: string;
|
|
14
|
-
cancelUrl?: string;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export const PageForm: FC<PageFormProps> = ({
|
|
18
|
-
page,
|
|
19
|
-
action,
|
|
20
|
-
cancelUrl = "/dash/pages",
|
|
21
|
-
}) => {
|
|
22
|
-
const { t } = useLingui();
|
|
23
|
-
const isEdit = !!page;
|
|
24
|
-
|
|
25
|
-
const signals = JSON.stringify({
|
|
26
|
-
title: page?.title ?? "",
|
|
27
|
-
slug: page?.slug ?? "",
|
|
28
|
-
body: page?.body ?? "",
|
|
29
|
-
status: page?.status ?? "published",
|
|
30
|
-
}).replace(/</g, "\\u003c");
|
|
31
|
-
|
|
32
|
-
return (
|
|
33
|
-
<form
|
|
34
|
-
data-signals={signals}
|
|
35
|
-
data-on:submit__prevent={`@post('${action}')`}
|
|
36
|
-
data-indicator="_loading"
|
|
37
|
-
class="flex flex-col gap-4"
|
|
38
|
-
>
|
|
39
|
-
<div id="page-form-message"></div>
|
|
40
|
-
|
|
41
|
-
{/* Title */}
|
|
42
|
-
<div class="field">
|
|
43
|
-
<label class="label">
|
|
44
|
-
{t({
|
|
45
|
-
message: "Title",
|
|
46
|
-
comment: "@context: Page form field label - title",
|
|
47
|
-
})}
|
|
48
|
-
</label>
|
|
49
|
-
<input
|
|
50
|
-
type="text"
|
|
51
|
-
data-bind="title"
|
|
52
|
-
class="input"
|
|
53
|
-
placeholder={t({
|
|
54
|
-
message: "Page title...",
|
|
55
|
-
comment: "@context: Page title placeholder",
|
|
56
|
-
})}
|
|
57
|
-
required
|
|
58
|
-
/>
|
|
59
|
-
</div>
|
|
60
|
-
|
|
61
|
-
{/* Slug */}
|
|
62
|
-
<div class="field">
|
|
63
|
-
<label class="label">
|
|
64
|
-
{t({
|
|
65
|
-
message: "Slug",
|
|
66
|
-
comment: "@context: Page form field label - URL slug",
|
|
67
|
-
})}
|
|
68
|
-
</label>
|
|
69
|
-
<div class="flex items-center gap-2">
|
|
70
|
-
<span class="text-muted-foreground">/</span>
|
|
71
|
-
<input
|
|
72
|
-
type="text"
|
|
73
|
-
data-bind="slug"
|
|
74
|
-
class="input flex-1"
|
|
75
|
-
placeholder="about"
|
|
76
|
-
pattern="[a-z0-9\-]+"
|
|
77
|
-
title={t({
|
|
78
|
-
message: "Lowercase letters, numbers, and hyphens only",
|
|
79
|
-
comment: "@context: Page slug validation message",
|
|
80
|
-
})}
|
|
81
|
-
required
|
|
82
|
-
/>
|
|
83
|
-
</div>
|
|
84
|
-
<p class="text-xs text-muted-foreground mt-1">
|
|
85
|
-
{t({
|
|
86
|
-
message:
|
|
87
|
-
"The URL path for this page. Use lowercase letters, numbers, and hyphens.",
|
|
88
|
-
comment: "@context: Page slug helper text",
|
|
89
|
-
})}
|
|
90
|
-
</p>
|
|
91
|
-
</div>
|
|
92
|
-
|
|
93
|
-
{/* Body */}
|
|
94
|
-
<div class="field">
|
|
95
|
-
<label class="label">
|
|
96
|
-
{t({
|
|
97
|
-
message: "Content",
|
|
98
|
-
comment: "@context: Page form field label - content",
|
|
99
|
-
})}
|
|
100
|
-
</label>
|
|
101
|
-
<textarea
|
|
102
|
-
data-bind="body"
|
|
103
|
-
class="textarea min-h-48"
|
|
104
|
-
placeholder={t({
|
|
105
|
-
message: "Page content (Markdown supported)...",
|
|
106
|
-
comment: "@context: Page content placeholder",
|
|
107
|
-
})}
|
|
108
|
-
required
|
|
109
|
-
>
|
|
110
|
-
{page?.body ?? ""}
|
|
111
|
-
</textarea>
|
|
112
|
-
</div>
|
|
113
|
-
|
|
114
|
-
{/* Status */}
|
|
115
|
-
<div class="field">
|
|
116
|
-
<label class="label">
|
|
117
|
-
{t({
|
|
118
|
-
message: "Status",
|
|
119
|
-
comment: "@context: Page form field label - publish status",
|
|
120
|
-
})}
|
|
121
|
-
</label>
|
|
122
|
-
<select data-bind="status" class="select">
|
|
123
|
-
<option
|
|
124
|
-
value="published"
|
|
125
|
-
selected={page?.status === "published" || !page}
|
|
126
|
-
>
|
|
127
|
-
{t({
|
|
128
|
-
message: "Published",
|
|
129
|
-
comment: "@context: Page status option - published",
|
|
130
|
-
})}
|
|
131
|
-
</option>
|
|
132
|
-
<option value="draft" selected={page?.status === "draft"}>
|
|
133
|
-
{t({
|
|
134
|
-
message: "Draft",
|
|
135
|
-
comment: "@context: Page status option - draft",
|
|
136
|
-
})}
|
|
137
|
-
</option>
|
|
138
|
-
</select>
|
|
139
|
-
<p class="text-xs text-muted-foreground mt-1">
|
|
140
|
-
{t({
|
|
141
|
-
message:
|
|
142
|
-
"Published pages are accessible via their slug. Drafts are not visible.",
|
|
143
|
-
comment: "@context: Page status helper text",
|
|
144
|
-
})}
|
|
145
|
-
</p>
|
|
146
|
-
</div>
|
|
147
|
-
|
|
148
|
-
{/* Submit */}
|
|
149
|
-
<div class="flex gap-2">
|
|
150
|
-
<button type="submit" class="btn" data-attr:disabled="$_loading">
|
|
151
|
-
<svg
|
|
152
|
-
data-show="$_loading"
|
|
153
|
-
style="display:none"
|
|
154
|
-
class="animate-spin size-4"
|
|
155
|
-
xmlns="http://www.w3.org/2000/svg"
|
|
156
|
-
viewBox="0 0 24 24"
|
|
157
|
-
fill="none"
|
|
158
|
-
stroke="currentColor"
|
|
159
|
-
stroke-width="2"
|
|
160
|
-
stroke-linecap="round"
|
|
161
|
-
stroke-linejoin="round"
|
|
162
|
-
role="status"
|
|
163
|
-
>
|
|
164
|
-
<path d="M21 12a9 9 0 1 1-6.219-8.56" />
|
|
165
|
-
</svg>
|
|
166
|
-
{isEdit
|
|
167
|
-
? t({
|
|
168
|
-
message: "Update Page",
|
|
169
|
-
comment: "@context: Button to update existing page",
|
|
170
|
-
})
|
|
171
|
-
: t({
|
|
172
|
-
message: "Create Page",
|
|
173
|
-
comment: "@context: Button to create new page",
|
|
174
|
-
})}
|
|
175
|
-
</button>
|
|
176
|
-
<a href={cancelUrl} class="btn-outline">
|
|
177
|
-
{t({
|
|
178
|
-
message: "Cancel",
|
|
179
|
-
comment: "@context: Button to cancel and go back",
|
|
180
|
-
})}
|
|
181
|
-
</a>
|
|
182
|
-
</div>
|
|
183
|
-
</form>
|
|
184
|
-
);
|
|
185
|
-
};
|
package/src/ui/dash/PostList.tsx
DELETED
|
@@ -1,95 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Post List Component
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import type { FC } from "hono/jsx";
|
|
6
|
-
import { useLingui } from "@lingui/react/macro";
|
|
7
|
-
import type { PostView } from "../../types.js";
|
|
8
|
-
import * as sqid from "../../lib/sqid.js";
|
|
9
|
-
import { StatusBadge } from "./StatusBadge.js";
|
|
10
|
-
import { FormatBadge } from "./FormatBadge.js";
|
|
11
|
-
import { EmptyState } from "../shared/EmptyState.js";
|
|
12
|
-
import { ListItemRow } from "./ListItemRow.js";
|
|
13
|
-
import { ActionButtons } from "./ActionButtons.js";
|
|
14
|
-
|
|
15
|
-
export interface PostListProps {
|
|
16
|
-
posts: PostView[];
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export const PostList: FC<PostListProps> = ({ posts }) => {
|
|
20
|
-
const { t } = useLingui();
|
|
21
|
-
if (posts.length === 0) {
|
|
22
|
-
return (
|
|
23
|
-
<EmptyState
|
|
24
|
-
message={t({
|
|
25
|
-
message: "No posts yet.",
|
|
26
|
-
comment: "@context: Empty state message when no posts exist",
|
|
27
|
-
})}
|
|
28
|
-
ctaText={t({
|
|
29
|
-
message: "Create your first post",
|
|
30
|
-
comment: "@context: Button in empty state to create first post",
|
|
31
|
-
})}
|
|
32
|
-
ctaHref="/dash/posts/new"
|
|
33
|
-
/>
|
|
34
|
-
);
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
return (
|
|
38
|
-
<div class="flex flex-col divide-y">
|
|
39
|
-
{posts.map((post) => (
|
|
40
|
-
<ListItemRow
|
|
41
|
-
key={post.id}
|
|
42
|
-
actions={
|
|
43
|
-
<ActionButtons
|
|
44
|
-
editHref={`/dash/posts/${sqid.encode(post.id)}/edit`}
|
|
45
|
-
editLabel={t({
|
|
46
|
-
message: "Edit",
|
|
47
|
-
comment: "@context: Button to edit post",
|
|
48
|
-
})}
|
|
49
|
-
viewHref={post.permalink}
|
|
50
|
-
viewLabel={t({
|
|
51
|
-
message: "View",
|
|
52
|
-
comment: "@context: Button to view post on public site",
|
|
53
|
-
})}
|
|
54
|
-
deleteAction={`/dash/posts/${sqid.encode(post.id)}/delete`}
|
|
55
|
-
deleteConfirm={t({
|
|
56
|
-
message:
|
|
57
|
-
"Are you sure you want to delete this post? This cannot be undone.",
|
|
58
|
-
comment:
|
|
59
|
-
"@context: Confirmation dialog when deleting a post from the list",
|
|
60
|
-
})}
|
|
61
|
-
/>
|
|
62
|
-
}
|
|
63
|
-
>
|
|
64
|
-
<div class="flex items-center gap-2 mb-1">
|
|
65
|
-
<FormatBadge type={post.format} />
|
|
66
|
-
<StatusBadge
|
|
67
|
-
status={post.status}
|
|
68
|
-
featured={post.featured}
|
|
69
|
-
pinned={post.pinned}
|
|
70
|
-
/>
|
|
71
|
-
<span class="text-xs text-muted-foreground">
|
|
72
|
-
{post.publishedAtFormatted}
|
|
73
|
-
</span>
|
|
74
|
-
</div>
|
|
75
|
-
<a
|
|
76
|
-
href={`/dash/posts/${sqid.encode(post.id)}`}
|
|
77
|
-
class="font-medium hover:underline"
|
|
78
|
-
>
|
|
79
|
-
{post.title ||
|
|
80
|
-
post.body?.slice(0, 60) ||
|
|
81
|
-
t({
|
|
82
|
-
message: "Untitled",
|
|
83
|
-
comment: "@context: Default title for untitled post",
|
|
84
|
-
})}
|
|
85
|
-
</a>
|
|
86
|
-
{post.body && !post.title && (
|
|
87
|
-
<p class="text-sm text-muted-foreground mt-1 line-clamp-2">
|
|
88
|
-
{post.body.slice(0, 120)}
|
|
89
|
-
</p>
|
|
90
|
-
)}
|
|
91
|
-
</ListItemRow>
|
|
92
|
-
))}
|
|
93
|
-
</div>
|
|
94
|
-
);
|
|
95
|
-
};
|
|
@@ -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
|
-
}
|