@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
|
@@ -1,248 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Post Form
|
|
3
|
-
*
|
|
4
|
-
* Server-rendered wrapper that feeds data/labels to `<jant-post-form>`.
|
|
5
|
-
* Provides SSR fallback skeleton while Lit hydrates.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import { useLingui } from "@lingui/react/macro";
|
|
9
|
-
import type { FC } from "hono/jsx";
|
|
10
|
-
import type { Post, Media, Collection } from "../../../types.js";
|
|
11
|
-
import {
|
|
12
|
-
getMediaUrl,
|
|
13
|
-
getImageUrl,
|
|
14
|
-
getPublicUrlForProvider,
|
|
15
|
-
} from "../../../lib/image.js";
|
|
16
|
-
import { renderCollectionIcon } from "../../../lib/icons.js";
|
|
17
|
-
|
|
18
|
-
export interface PostFormProps {
|
|
19
|
-
post?: Post;
|
|
20
|
-
action: string;
|
|
21
|
-
mediaAttachments?: Media[];
|
|
22
|
-
r2PublicUrl?: string;
|
|
23
|
-
imageTransformUrl?: string;
|
|
24
|
-
s3PublicUrl?: string;
|
|
25
|
-
collections?: Collection[];
|
|
26
|
-
postCollectionIds?: number[];
|
|
27
|
-
cancelHref?: string;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
export const PostForm: FC<PostFormProps> = ({
|
|
31
|
-
post,
|
|
32
|
-
action,
|
|
33
|
-
mediaAttachments = [],
|
|
34
|
-
r2PublicUrl,
|
|
35
|
-
imageTransformUrl,
|
|
36
|
-
s3PublicUrl,
|
|
37
|
-
collections = [],
|
|
38
|
-
postCollectionIds = [],
|
|
39
|
-
cancelHref,
|
|
40
|
-
}) => {
|
|
41
|
-
const { t } = useLingui();
|
|
42
|
-
const isEdit = Boolean(post);
|
|
43
|
-
|
|
44
|
-
const labels = JSON.stringify({
|
|
45
|
-
formatLabel: t({
|
|
46
|
-
message: "Format",
|
|
47
|
-
comment: "@context: Post form field - post format",
|
|
48
|
-
}),
|
|
49
|
-
noteOption: t({
|
|
50
|
-
message: "Note",
|
|
51
|
-
comment: "@context: Post format option",
|
|
52
|
-
}),
|
|
53
|
-
linkOption: t({
|
|
54
|
-
message: "Link",
|
|
55
|
-
comment: "@context: Post format option",
|
|
56
|
-
}),
|
|
57
|
-
quoteOption: t({
|
|
58
|
-
message: "Quote",
|
|
59
|
-
comment: "@context: Post format option",
|
|
60
|
-
}),
|
|
61
|
-
titleLabel: t({
|
|
62
|
-
message: "Title (optional)",
|
|
63
|
-
comment: "@context: Post form field",
|
|
64
|
-
}),
|
|
65
|
-
titlePlaceholder: t({
|
|
66
|
-
message: "Post title...",
|
|
67
|
-
comment: "@context: Post title placeholder",
|
|
68
|
-
}),
|
|
69
|
-
bodyLabel: t({
|
|
70
|
-
message: "Content",
|
|
71
|
-
comment: "@context: Post form field",
|
|
72
|
-
}),
|
|
73
|
-
bodyPlaceholder: t({
|
|
74
|
-
message: "What's on your mind?",
|
|
75
|
-
comment: "@context: Post content placeholder",
|
|
76
|
-
}),
|
|
77
|
-
urlLabel: t({
|
|
78
|
-
message: "URL (optional)",
|
|
79
|
-
comment: "@context: Post form field - source URL",
|
|
80
|
-
}),
|
|
81
|
-
urlPlaceholder: "https://...",
|
|
82
|
-
quoteTextLabel: t({
|
|
83
|
-
message: "Quote Text",
|
|
84
|
-
comment: "@context: Post form field - quoted text",
|
|
85
|
-
}),
|
|
86
|
-
quoteTextPlaceholder: t({
|
|
87
|
-
message: "The text being quoted...",
|
|
88
|
-
comment: "@context: Quote text placeholder",
|
|
89
|
-
}),
|
|
90
|
-
mediaLabel: t({
|
|
91
|
-
message: "Media",
|
|
92
|
-
comment: "@context: Post form field - media attachments",
|
|
93
|
-
}),
|
|
94
|
-
mediaAddButton: t({
|
|
95
|
-
message: "Add Media",
|
|
96
|
-
comment: "@context: Button to open media picker",
|
|
97
|
-
}),
|
|
98
|
-
mediaRemoveButton: t({
|
|
99
|
-
message: "Remove",
|
|
100
|
-
comment: "@context: Remove media attachment button",
|
|
101
|
-
}),
|
|
102
|
-
mediaEmptyLabel: t({
|
|
103
|
-
message: "No media selected yet.",
|
|
104
|
-
comment: "@context: Post form media empty state",
|
|
105
|
-
}),
|
|
106
|
-
statusLabel: t({
|
|
107
|
-
message: "Status",
|
|
108
|
-
comment: "@context: Post form field",
|
|
109
|
-
}),
|
|
110
|
-
statusPublished: t({
|
|
111
|
-
message: "Published",
|
|
112
|
-
comment: "@context: Post status option",
|
|
113
|
-
}),
|
|
114
|
-
statusDraft: t({
|
|
115
|
-
message: "Draft",
|
|
116
|
-
comment: "@context: Post status option",
|
|
117
|
-
}),
|
|
118
|
-
featuredLabel: t({
|
|
119
|
-
message: "Featured",
|
|
120
|
-
comment: "@context: Post form checkbox - mark as featured",
|
|
121
|
-
}),
|
|
122
|
-
pinnedLabel: t({
|
|
123
|
-
message: "Pinned",
|
|
124
|
-
comment: "@context: Post form checkbox - pin to top",
|
|
125
|
-
}),
|
|
126
|
-
collectionsLabel: t({
|
|
127
|
-
message: "Collections (optional)",
|
|
128
|
-
comment: "@context: Post form field - assign to collections",
|
|
129
|
-
}),
|
|
130
|
-
submitLabel: isEdit
|
|
131
|
-
? t({
|
|
132
|
-
message: "Update",
|
|
133
|
-
comment: "@context: Button to update existing post",
|
|
134
|
-
})
|
|
135
|
-
: t({
|
|
136
|
-
message: "Publish",
|
|
137
|
-
comment: "@context: Button to publish new post",
|
|
138
|
-
}),
|
|
139
|
-
cancelLabel: t({
|
|
140
|
-
message: "Cancel",
|
|
141
|
-
comment: "@context: Button to cancel form",
|
|
142
|
-
}),
|
|
143
|
-
mediaDialogTitle: t({
|
|
144
|
-
message: "Select Media",
|
|
145
|
-
comment: "@context: Media picker dialog title",
|
|
146
|
-
}),
|
|
147
|
-
mediaDialogDone: t({
|
|
148
|
-
message: "Done",
|
|
149
|
-
comment: "@context: Close media picker button",
|
|
150
|
-
}),
|
|
151
|
-
mediaDialogLoading: t({
|
|
152
|
-
message: "Loading...",
|
|
153
|
-
comment: "@context: Loading state for media picker",
|
|
154
|
-
}),
|
|
155
|
-
submitSuccessMessage: isEdit
|
|
156
|
-
? t({
|
|
157
|
-
message: "Post updated successfully.",
|
|
158
|
-
comment: "@context: Toast after editing post",
|
|
159
|
-
})
|
|
160
|
-
: t({
|
|
161
|
-
message: "Post published successfully.",
|
|
162
|
-
comment: "@context: Toast after creating post",
|
|
163
|
-
}),
|
|
164
|
-
submitErrorMessage: t({
|
|
165
|
-
message: "Failed to save post. Please try again.",
|
|
166
|
-
comment: "@context: Toast when post save fails",
|
|
167
|
-
}),
|
|
168
|
-
}).replace(/</g, "\\u003c");
|
|
169
|
-
|
|
170
|
-
const initial = JSON.stringify({
|
|
171
|
-
format: post?.format ?? "note",
|
|
172
|
-
title: post?.title ?? "",
|
|
173
|
-
body: post?.body ?? "",
|
|
174
|
-
url: post?.url ?? "",
|
|
175
|
-
quoteText: post?.quoteText ?? "",
|
|
176
|
-
status: post?.status ?? "published",
|
|
177
|
-
featured: post?.featured === 1,
|
|
178
|
-
pinned: post?.pinned === 1,
|
|
179
|
-
rating: post?.rating ?? 0,
|
|
180
|
-
collectionIds: postCollectionIds,
|
|
181
|
-
mediaIds: mediaAttachments.map((m) => m.id),
|
|
182
|
-
}).replace(/</g, "\\u003c");
|
|
183
|
-
|
|
184
|
-
const media = JSON.stringify(
|
|
185
|
-
mediaAttachments.map((m) => {
|
|
186
|
-
const pUrl = getPublicUrlForProvider(
|
|
187
|
-
m.provider,
|
|
188
|
-
r2PublicUrl,
|
|
189
|
-
s3PublicUrl,
|
|
190
|
-
);
|
|
191
|
-
const mediaUrl = getMediaUrl(m.storageKey, pUrl);
|
|
192
|
-
const thumbUrl = getImageUrl(mediaUrl, imageTransformUrl, {
|
|
193
|
-
width: 150,
|
|
194
|
-
quality: 80,
|
|
195
|
-
format: "auto",
|
|
196
|
-
fit: "cover",
|
|
197
|
-
});
|
|
198
|
-
return {
|
|
199
|
-
id: m.id,
|
|
200
|
-
thumbUrl,
|
|
201
|
-
alt: m.alt || m.originalName,
|
|
202
|
-
};
|
|
203
|
-
}),
|
|
204
|
-
).replace(/</g, "\\u003c");
|
|
205
|
-
|
|
206
|
-
const collectionOptions = JSON.stringify(
|
|
207
|
-
collections.map((col) => ({
|
|
208
|
-
id: col.id,
|
|
209
|
-
title: col.title,
|
|
210
|
-
icon: col.icon,
|
|
211
|
-
iconHtml: renderCollectionIcon(col.icon, { size: 18 }),
|
|
212
|
-
})),
|
|
213
|
-
).replace(/</g, "\\u003c");
|
|
214
|
-
|
|
215
|
-
const cancel = cancelHref ?? "/dash/posts";
|
|
216
|
-
|
|
217
|
-
return (
|
|
218
|
-
<jant-post-form
|
|
219
|
-
labels={labels}
|
|
220
|
-
initial={initial}
|
|
221
|
-
action={action}
|
|
222
|
-
cancel-href={cancel}
|
|
223
|
-
media={media}
|
|
224
|
-
collections={collectionOptions}
|
|
225
|
-
media-picker-url="/dash/media/picker"
|
|
226
|
-
is-edit={isEdit ? "true" : undefined}
|
|
227
|
-
>
|
|
228
|
-
<div class="flex flex-col gap-4 max-w-2xl">
|
|
229
|
-
<div class="field">
|
|
230
|
-
<div class="label skel-label"></div>
|
|
231
|
-
<div class="input skel-input"></div>
|
|
232
|
-
</div>
|
|
233
|
-
<div class="field">
|
|
234
|
-
<div class="label skel-label"></div>
|
|
235
|
-
<div class="textarea skel-textarea"></div>
|
|
236
|
-
</div>
|
|
237
|
-
<div class="field">
|
|
238
|
-
<div class="label skel-label"></div>
|
|
239
|
-
<div class="input skel-input"></div>
|
|
240
|
-
</div>
|
|
241
|
-
<div class="flex gap-2">
|
|
242
|
-
<div class="btn skel-input min-w-24"></div>
|
|
243
|
-
<div class="btn-outline skel-input min-w-20"></div>
|
|
244
|
-
</div>
|
|
245
|
-
</div>
|
|
246
|
-
</jant-post-form>
|
|
247
|
-
);
|
|
248
|
-
};
|
|
@@ -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
|
-
}
|
|
@@ -1,165 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Dashboard Layout
|
|
3
|
-
*
|
|
4
|
-
* Layout for admin dashboard pages
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import type { FC, PropsWithChildren } from "hono/jsx";
|
|
8
|
-
import type { Context } from "hono";
|
|
9
|
-
import { useLingui } from "@lingui/react/macro";
|
|
10
|
-
import { BaseLayout, type ToastProps } from "./BaseLayout.js";
|
|
11
|
-
|
|
12
|
-
export interface DashLayoutProps {
|
|
13
|
-
c: Context;
|
|
14
|
-
title: string;
|
|
15
|
-
siteName: string;
|
|
16
|
-
currentPath?: string;
|
|
17
|
-
toast?: ToastProps;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
function DashLayoutContent({
|
|
21
|
-
siteName,
|
|
22
|
-
currentPath,
|
|
23
|
-
children,
|
|
24
|
-
}: PropsWithChildren<Omit<DashLayoutProps, "c" | "title">>) {
|
|
25
|
-
const { t } = useLingui();
|
|
26
|
-
|
|
27
|
-
const navClass = (match: RegExp) =>
|
|
28
|
-
`dash-header-link ${currentPath && match.test(currentPath) ? "dash-header-link-active" : ""}`;
|
|
29
|
-
|
|
30
|
-
return (
|
|
31
|
-
<div class="min-h-screen">
|
|
32
|
-
{/* Header */}
|
|
33
|
-
<header class="dash-header">
|
|
34
|
-
<div class="container dash-header-inner">
|
|
35
|
-
<div class="dash-header-left">
|
|
36
|
-
<a id="site-name" href="/dash" class="dash-header-logo">
|
|
37
|
-
{siteName}
|
|
38
|
-
</a>
|
|
39
|
-
<a
|
|
40
|
-
href="/"
|
|
41
|
-
target="_blank"
|
|
42
|
-
rel="noopener noreferrer"
|
|
43
|
-
class="dash-header-site-link"
|
|
44
|
-
aria-label={t({
|
|
45
|
-
message: "View Site",
|
|
46
|
-
comment:
|
|
47
|
-
"@context: Dashboard header link to view the public site",
|
|
48
|
-
})}
|
|
49
|
-
>
|
|
50
|
-
<svg
|
|
51
|
-
xmlns="http://www.w3.org/2000/svg"
|
|
52
|
-
width="14"
|
|
53
|
-
height="14"
|
|
54
|
-
viewBox="0 0 24 24"
|
|
55
|
-
fill="none"
|
|
56
|
-
stroke="currentColor"
|
|
57
|
-
stroke-width="2"
|
|
58
|
-
stroke-linecap="round"
|
|
59
|
-
stroke-linejoin="round"
|
|
60
|
-
>
|
|
61
|
-
<path d="M15 3h6v6" />
|
|
62
|
-
<path d="M10 14 21 3" />
|
|
63
|
-
<path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6" />
|
|
64
|
-
</svg>
|
|
65
|
-
</a>
|
|
66
|
-
</div>
|
|
67
|
-
|
|
68
|
-
<nav class="dash-header-nav">
|
|
69
|
-
<a href="/dash/pages" class={navClass(/^\/dash\/pages/)}>
|
|
70
|
-
{t({
|
|
71
|
-
message: "Pages",
|
|
72
|
-
comment: "@context: Dashboard navigation - pages management",
|
|
73
|
-
})}
|
|
74
|
-
</a>
|
|
75
|
-
<a href="/dash/appearance" class={navClass(/^\/dash\/appearance/)}>
|
|
76
|
-
{t({
|
|
77
|
-
message: "Appearance",
|
|
78
|
-
comment: "@context: Dashboard navigation - appearance settings",
|
|
79
|
-
})}
|
|
80
|
-
</a>
|
|
81
|
-
<a href="/dash/settings" class={navClass(/^\/dash\/settings/)}>
|
|
82
|
-
{t({
|
|
83
|
-
message: "Settings",
|
|
84
|
-
comment: "@context: Dashboard navigation - site settings",
|
|
85
|
-
})}
|
|
86
|
-
</a>
|
|
87
|
-
</nav>
|
|
88
|
-
|
|
89
|
-
<div class="dropdown-menu">
|
|
90
|
-
<button
|
|
91
|
-
type="button"
|
|
92
|
-
id="dash-menu-trigger"
|
|
93
|
-
class="dash-header-menu-btn"
|
|
94
|
-
aria-haspopup="menu"
|
|
95
|
-
aria-controls="dash-menu"
|
|
96
|
-
aria-expanded="false"
|
|
97
|
-
aria-label={t({
|
|
98
|
-
message: "Menu",
|
|
99
|
-
comment: "@context: Dashboard header menu button",
|
|
100
|
-
})}
|
|
101
|
-
>
|
|
102
|
-
<svg
|
|
103
|
-
xmlns="http://www.w3.org/2000/svg"
|
|
104
|
-
width="16"
|
|
105
|
-
height="16"
|
|
106
|
-
viewBox="0 0 24 24"
|
|
107
|
-
fill="currentColor"
|
|
108
|
-
>
|
|
109
|
-
<circle cx="5" cy="12" r="2" />
|
|
110
|
-
<circle cx="12" cy="12" r="2" />
|
|
111
|
-
<circle cx="19" cy="12" r="2" />
|
|
112
|
-
</svg>
|
|
113
|
-
</button>
|
|
114
|
-
<div
|
|
115
|
-
id="dash-menu-popover"
|
|
116
|
-
data-popover
|
|
117
|
-
data-align="end"
|
|
118
|
-
aria-hidden="true"
|
|
119
|
-
>
|
|
120
|
-
<div
|
|
121
|
-
role="menu"
|
|
122
|
-
id="dash-menu"
|
|
123
|
-
aria-labelledby="dash-menu-trigger"
|
|
124
|
-
>
|
|
125
|
-
<a href="/signout" role="menuitem">
|
|
126
|
-
{t({
|
|
127
|
-
message: "Sign Out",
|
|
128
|
-
comment: "@context: Dashboard menu item to sign out",
|
|
129
|
-
})}
|
|
130
|
-
</a>
|
|
131
|
-
</div>
|
|
132
|
-
</div>
|
|
133
|
-
</div>
|
|
134
|
-
</div>
|
|
135
|
-
</header>
|
|
136
|
-
|
|
137
|
-
{/* Main */}
|
|
138
|
-
<div class="container py-8">
|
|
139
|
-
<main>{children}</main>
|
|
140
|
-
</div>
|
|
141
|
-
</div>
|
|
142
|
-
);
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
export const DashLayout: FC<PropsWithChildren<DashLayoutProps>> = ({
|
|
146
|
-
c,
|
|
147
|
-
title,
|
|
148
|
-
siteName,
|
|
149
|
-
currentPath,
|
|
150
|
-
toast,
|
|
151
|
-
children,
|
|
152
|
-
}) => {
|
|
153
|
-
return (
|
|
154
|
-
<BaseLayout
|
|
155
|
-
title={`${title} - ${siteName}`}
|
|
156
|
-
c={c}
|
|
157
|
-
toast={toast}
|
|
158
|
-
isAuthenticated={true}
|
|
159
|
-
>
|
|
160
|
-
<DashLayoutContent siteName={siteName} currentPath={currentPath}>
|
|
161
|
-
{children}
|
|
162
|
-
</DashLayoutContent>
|
|
163
|
-
</BaseLayout>
|
|
164
|
-
);
|
|
165
|
-
};
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Single Page (Custom Page)
|
|
3
|
-
*
|
|
4
|
-
* Custom page view — clean centered content.
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import type { FC } from "hono/jsx";
|
|
8
|
-
import type { SinglePageProps } from "../../types.js";
|
|
9
|
-
|
|
10
|
-
export const SinglePage: FC<SinglePageProps> = ({ page }) => {
|
|
11
|
-
return (
|
|
12
|
-
<article class="h-entry py-6" data-page="single-page">
|
|
13
|
-
{page.title && (
|
|
14
|
-
<h1 class="p-name text-2xl font-semibold mb-6">{page.title}</h1>
|
|
15
|
-
)}
|
|
16
|
-
|
|
17
|
-
<div
|
|
18
|
-
class="e-content prose"
|
|
19
|
-
dangerouslySetInnerHTML={{ __html: page.bodyHtml || "" }}
|
|
20
|
-
/>
|
|
21
|
-
</article>
|
|
22
|
-
);
|
|
23
|
-
};
|
|
@@ -1,136 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Thread View Component
|
|
3
|
-
*
|
|
4
|
-
* Displays a thread of posts with reply chain visualization
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import type { FC } from "hono/jsx";
|
|
8
|
-
import { useLingui } from "@lingui/react/macro";
|
|
9
|
-
import type { Post } from "../../types.js";
|
|
10
|
-
import * as sqid from "../../lib/sqid.js";
|
|
11
|
-
import * as time from "../../lib/time.js";
|
|
12
|
-
|
|
13
|
-
export interface ThreadViewProps {
|
|
14
|
-
/** All posts in the thread, ordered by createdAt */
|
|
15
|
-
posts: Post[];
|
|
16
|
-
/** ID of the currently viewed post (to highlight) */
|
|
17
|
-
currentPostId: number;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
const ThreadPost: FC<{
|
|
21
|
-
post: Post;
|
|
22
|
-
isCurrent: boolean;
|
|
23
|
-
isRoot: boolean;
|
|
24
|
-
}> = ({ post, isCurrent, isRoot }) => {
|
|
25
|
-
const { t } = useLingui();
|
|
26
|
-
return (
|
|
27
|
-
<article
|
|
28
|
-
id={`post-${post.id}`}
|
|
29
|
-
class={`h-entry p-4 rounded-lg border ${
|
|
30
|
-
isCurrent
|
|
31
|
-
? "border-primary bg-primary/5 ring-2 ring-primary/20"
|
|
32
|
-
: "border-border hover:border-muted-foreground/30"
|
|
33
|
-
}`}
|
|
34
|
-
>
|
|
35
|
-
{post.title && (
|
|
36
|
-
<h2 class="p-name text-lg font-medium mb-2">
|
|
37
|
-
<a
|
|
38
|
-
href={`${post.path ? `/${post.path}` : `/p/${sqid.encode(post.id)}`}`}
|
|
39
|
-
class="u-url hover:underline"
|
|
40
|
-
>
|
|
41
|
-
{post.title}
|
|
42
|
-
</a>
|
|
43
|
-
</h2>
|
|
44
|
-
)}
|
|
45
|
-
|
|
46
|
-
<div
|
|
47
|
-
class="e-content prose prose-sm"
|
|
48
|
-
dangerouslySetInnerHTML={{ __html: post.bodyHtml || "" }}
|
|
49
|
-
/>
|
|
50
|
-
|
|
51
|
-
<footer class="mt-3 flex items-center gap-3 text-sm text-muted-foreground">
|
|
52
|
-
<time
|
|
53
|
-
class="dt-published"
|
|
54
|
-
datetime={time.toISOString(post.publishedAt)}
|
|
55
|
-
>
|
|
56
|
-
{time.formatDate(post.publishedAt)}
|
|
57
|
-
</time>
|
|
58
|
-
{isRoot && (
|
|
59
|
-
<span class="text-xs">
|
|
60
|
-
{t({
|
|
61
|
-
message: "Thread start",
|
|
62
|
-
comment: "@context: Thread view indicator - first post in thread",
|
|
63
|
-
})}
|
|
64
|
-
</span>
|
|
65
|
-
)}
|
|
66
|
-
{!isCurrent && (
|
|
67
|
-
<a
|
|
68
|
-
href={`${post.path ? `/${post.path}` : `/p/${sqid.encode(post.id)}`}`}
|
|
69
|
-
class="text-xs hover:underline"
|
|
70
|
-
>
|
|
71
|
-
{t({
|
|
72
|
-
message: "Permalink",
|
|
73
|
-
comment: "@context: Link to individual post in thread",
|
|
74
|
-
})}
|
|
75
|
-
</a>
|
|
76
|
-
)}
|
|
77
|
-
</footer>
|
|
78
|
-
</article>
|
|
79
|
-
);
|
|
80
|
-
};
|
|
81
|
-
|
|
82
|
-
export const ThreadView: FC<ThreadViewProps> = ({ posts, currentPostId }) => {
|
|
83
|
-
const { t } = useLingui();
|
|
84
|
-
if (posts.length === 0) {
|
|
85
|
-
return null;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
const rootPost = posts[0];
|
|
89
|
-
const isThread = posts.length > 1;
|
|
90
|
-
|
|
91
|
-
// Single post, no thread
|
|
92
|
-
if (!isThread) {
|
|
93
|
-
return (
|
|
94
|
-
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- Early return for empty array at line 73 guarantees posts[0] exists
|
|
95
|
-
<ThreadPost post={rootPost!} isCurrent={true} isRoot={false} />
|
|
96
|
-
);
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
const threadLabel =
|
|
100
|
-
posts.length === 1
|
|
101
|
-
? t({
|
|
102
|
-
message: "Thread with 1 post",
|
|
103
|
-
comment: "@context: Thread view header - single post",
|
|
104
|
-
})
|
|
105
|
-
: t({
|
|
106
|
-
message: "Thread with {count} posts",
|
|
107
|
-
comment: "@context: Thread view header - multiple posts",
|
|
108
|
-
values: { count: String(posts.length) },
|
|
109
|
-
});
|
|
110
|
-
|
|
111
|
-
return (
|
|
112
|
-
<div class="thread-view">
|
|
113
|
-
<div class="mb-4 text-sm text-muted-foreground">{threadLabel}</div>
|
|
114
|
-
|
|
115
|
-
<div class="flex flex-col gap-3">
|
|
116
|
-
{posts.map((post, index) => (
|
|
117
|
-
<div key={post.id} class="relative">
|
|
118
|
-
{/* Connection line */}
|
|
119
|
-
{index > 0 && (
|
|
120
|
-
<div class="absolute left-6 -top-3 w-0.5 h-3 bg-border" />
|
|
121
|
-
)}
|
|
122
|
-
{index < posts.length - 1 && (
|
|
123
|
-
<div class="absolute left-6 -bottom-3 w-0.5 h-3 bg-border" />
|
|
124
|
-
)}
|
|
125
|
-
|
|
126
|
-
<ThreadPost
|
|
127
|
-
post={post}
|
|
128
|
-
isCurrent={post.id === currentPostId}
|
|
129
|
-
isRoot={index === 0}
|
|
130
|
-
/>
|
|
131
|
-
</div>
|
|
132
|
-
))}
|
|
133
|
-
</div>
|
|
134
|
-
</div>
|
|
135
|
-
);
|
|
136
|
-
};
|
|
File without changes
|