@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
package/src/ui/dash/PostForm.tsx
DELETED
|
@@ -1,360 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Post Creation/Edit Form
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import type { FC } from "hono/jsx";
|
|
6
|
-
import type { Post, Media, Collection } from "../../types.js";
|
|
7
|
-
import { useLingui } from "@lingui/react/macro";
|
|
8
|
-
import {
|
|
9
|
-
getMediaUrl,
|
|
10
|
-
getImageUrl,
|
|
11
|
-
getPublicUrlForProvider,
|
|
12
|
-
} from "../../lib/image.js";
|
|
13
|
-
|
|
14
|
-
export interface PostFormProps {
|
|
15
|
-
post?: Post;
|
|
16
|
-
action: string;
|
|
17
|
-
mediaAttachments?: Media[];
|
|
18
|
-
r2PublicUrl?: string;
|
|
19
|
-
imageTransformUrl?: string;
|
|
20
|
-
s3PublicUrl?: string;
|
|
21
|
-
collections?: Collection[];
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export const PostForm: FC<PostFormProps> = ({
|
|
25
|
-
post,
|
|
26
|
-
action,
|
|
27
|
-
mediaAttachments,
|
|
28
|
-
r2PublicUrl,
|
|
29
|
-
imageTransformUrl,
|
|
30
|
-
s3PublicUrl,
|
|
31
|
-
collections,
|
|
32
|
-
}) => {
|
|
33
|
-
const { t } = useLingui();
|
|
34
|
-
const isEdit = !!post;
|
|
35
|
-
|
|
36
|
-
const existingMediaIds = (mediaAttachments ?? []).map((m) => m.id);
|
|
37
|
-
|
|
38
|
-
const signals = JSON.stringify({
|
|
39
|
-
format: post?.format ?? "note",
|
|
40
|
-
title: post?.title ?? "",
|
|
41
|
-
body: post?.body ?? "",
|
|
42
|
-
url: post?.url ?? "",
|
|
43
|
-
quoteText: post?.quoteText ?? "",
|
|
44
|
-
status: post?.status ?? "published",
|
|
45
|
-
featured: post?.featured === 1,
|
|
46
|
-
pinned: post?.pinned === 1,
|
|
47
|
-
rating: post?.rating ?? 0,
|
|
48
|
-
collectionId: post?.collectionId ?? 0,
|
|
49
|
-
mediaIds: existingMediaIds,
|
|
50
|
-
}).replace(/</g, "\\u003c");
|
|
51
|
-
|
|
52
|
-
return (
|
|
53
|
-
<form
|
|
54
|
-
data-signals={signals}
|
|
55
|
-
data-on:submit__prevent={`@post('${action}')`}
|
|
56
|
-
data-indicator="_loading"
|
|
57
|
-
class="flex flex-col gap-4"
|
|
58
|
-
>
|
|
59
|
-
<div id="post-form-message"></div>
|
|
60
|
-
|
|
61
|
-
{/* Format selector */}
|
|
62
|
-
<div class="field">
|
|
63
|
-
<label class="label">
|
|
64
|
-
{t({
|
|
65
|
-
message: "Format",
|
|
66
|
-
comment: "@context: Post form field - post format",
|
|
67
|
-
})}
|
|
68
|
-
</label>
|
|
69
|
-
<select data-bind="format" class="select" required>
|
|
70
|
-
<option value="note" selected={post?.format === "note" || !post}>
|
|
71
|
-
{t({ message: "Note", comment: "@context: Post format option" })}
|
|
72
|
-
</option>
|
|
73
|
-
<option value="link" selected={post?.format === "link"}>
|
|
74
|
-
{t({ message: "Link", comment: "@context: Post format option" })}
|
|
75
|
-
</option>
|
|
76
|
-
<option value="quote" selected={post?.format === "quote"}>
|
|
77
|
-
{t({ message: "Quote", comment: "@context: Post format option" })}
|
|
78
|
-
</option>
|
|
79
|
-
</select>
|
|
80
|
-
</div>
|
|
81
|
-
|
|
82
|
-
{/* Title (optional) */}
|
|
83
|
-
<div class="field">
|
|
84
|
-
<label class="label">
|
|
85
|
-
{t({
|
|
86
|
-
message: "Title (optional)",
|
|
87
|
-
comment: "@context: Post form field",
|
|
88
|
-
})}
|
|
89
|
-
</label>
|
|
90
|
-
<input
|
|
91
|
-
type="text"
|
|
92
|
-
data-bind="title"
|
|
93
|
-
class="input"
|
|
94
|
-
placeholder={t({
|
|
95
|
-
message: "Post title...",
|
|
96
|
-
comment: "@context: Post title placeholder",
|
|
97
|
-
})}
|
|
98
|
-
/>
|
|
99
|
-
</div>
|
|
100
|
-
|
|
101
|
-
{/* Body */}
|
|
102
|
-
<div class="field">
|
|
103
|
-
<label class="label">
|
|
104
|
-
{t({ message: "Content", comment: "@context: Post form field" })}
|
|
105
|
-
</label>
|
|
106
|
-
<textarea
|
|
107
|
-
data-bind="body"
|
|
108
|
-
class="textarea min-h-32"
|
|
109
|
-
placeholder={t({
|
|
110
|
-
message: "What's on your mind?",
|
|
111
|
-
comment: "@context: Post content placeholder",
|
|
112
|
-
})}
|
|
113
|
-
>
|
|
114
|
-
{post?.body ?? ""}
|
|
115
|
-
</textarea>
|
|
116
|
-
</div>
|
|
117
|
-
|
|
118
|
-
{/* URL (for link/quote formats) */}
|
|
119
|
-
<div class="field">
|
|
120
|
-
<label class="label">
|
|
121
|
-
{t({
|
|
122
|
-
message: "URL (optional)",
|
|
123
|
-
comment: "@context: Post form field - source URL",
|
|
124
|
-
})}
|
|
125
|
-
</label>
|
|
126
|
-
<input
|
|
127
|
-
type="url"
|
|
128
|
-
data-bind="url"
|
|
129
|
-
class="input"
|
|
130
|
-
placeholder="https://..."
|
|
131
|
-
/>
|
|
132
|
-
</div>
|
|
133
|
-
|
|
134
|
-
{/* Quote Text (for quote format) */}
|
|
135
|
-
<div class="field" data-show="$format === 'quote'">
|
|
136
|
-
<label class="label">
|
|
137
|
-
{t({
|
|
138
|
-
message: "Quote Text",
|
|
139
|
-
comment: "@context: Post form field - quoted text",
|
|
140
|
-
})}
|
|
141
|
-
</label>
|
|
142
|
-
<textarea
|
|
143
|
-
data-bind="quoteText"
|
|
144
|
-
class="textarea"
|
|
145
|
-
placeholder={t({
|
|
146
|
-
message: "The text being quoted...",
|
|
147
|
-
comment: "@context: Quote text placeholder",
|
|
148
|
-
})}
|
|
149
|
-
rows={3}
|
|
150
|
-
>
|
|
151
|
-
{post?.quoteText ?? ""}
|
|
152
|
-
</textarea>
|
|
153
|
-
</div>
|
|
154
|
-
|
|
155
|
-
{/* Media attachments */}
|
|
156
|
-
<div class="field">
|
|
157
|
-
<label class="label">
|
|
158
|
-
{t({
|
|
159
|
-
message: "Media",
|
|
160
|
-
comment: "@context: Post form field - media attachments",
|
|
161
|
-
})}
|
|
162
|
-
</label>
|
|
163
|
-
{mediaAttachments && mediaAttachments.length > 0 && (
|
|
164
|
-
<div class="grid grid-cols-4 sm:grid-cols-6 gap-2 mb-2">
|
|
165
|
-
{mediaAttachments.map((m) => {
|
|
166
|
-
const pUrl = getPublicUrlForProvider(
|
|
167
|
-
m.provider,
|
|
168
|
-
r2PublicUrl,
|
|
169
|
-
s3PublicUrl,
|
|
170
|
-
);
|
|
171
|
-
const mUrl = getMediaUrl(m.storageKey, pUrl);
|
|
172
|
-
const thumbUrl = getImageUrl(mUrl, imageTransformUrl, {
|
|
173
|
-
width: 150,
|
|
174
|
-
quality: 80,
|
|
175
|
-
format: "auto",
|
|
176
|
-
fit: "cover",
|
|
177
|
-
});
|
|
178
|
-
return (
|
|
179
|
-
<div
|
|
180
|
-
key={m.id}
|
|
181
|
-
class="relative group aspect-square"
|
|
182
|
-
data-show={`$mediaIds.includes('${m.id}')`}
|
|
183
|
-
>
|
|
184
|
-
<img
|
|
185
|
-
src={thumbUrl}
|
|
186
|
-
alt={m.alt || m.originalName}
|
|
187
|
-
class="w-full h-full object-cover rounded-lg border"
|
|
188
|
-
loading="lazy"
|
|
189
|
-
/>
|
|
190
|
-
<button
|
|
191
|
-
type="button"
|
|
192
|
-
class="absolute top-1 right-1 w-5 h-5 flex items-center justify-center bg-black/60 text-white rounded-full text-xs opacity-0 group-hover:opacity-100 transition-opacity cursor-pointer"
|
|
193
|
-
data-on:click={`$mediaIds = $mediaIds.filter(id => id !== '${m.id}')`}
|
|
194
|
-
title={t({
|
|
195
|
-
message: "Remove",
|
|
196
|
-
comment: "@context: Remove media attachment button",
|
|
197
|
-
})}
|
|
198
|
-
>
|
|
199
|
-
×
|
|
200
|
-
</button>
|
|
201
|
-
</div>
|
|
202
|
-
);
|
|
203
|
-
})}
|
|
204
|
-
</div>
|
|
205
|
-
)}
|
|
206
|
-
<button
|
|
207
|
-
type="button"
|
|
208
|
-
class="btn-outline text-sm"
|
|
209
|
-
data-on:click="document.getElementById('media-picker-dialog').showModal(); fetch('/dash/media/picker').then(r => r.text()).then(html => document.getElementById('media-picker-grid').innerHTML = html)"
|
|
210
|
-
>
|
|
211
|
-
{t({
|
|
212
|
-
message: "Add Media",
|
|
213
|
-
comment: "@context: Button to open media picker",
|
|
214
|
-
})}
|
|
215
|
-
</button>
|
|
216
|
-
</div>
|
|
217
|
-
|
|
218
|
-
{/* Status */}
|
|
219
|
-
<div class="field">
|
|
220
|
-
<label class="label">
|
|
221
|
-
{t({ message: "Status", comment: "@context: Post form field" })}
|
|
222
|
-
</label>
|
|
223
|
-
<select data-bind="status" class="select">
|
|
224
|
-
<option
|
|
225
|
-
value="published"
|
|
226
|
-
selected={post?.status === "published" || !post}
|
|
227
|
-
>
|
|
228
|
-
{t({
|
|
229
|
-
message: "Published",
|
|
230
|
-
comment: "@context: Post status option",
|
|
231
|
-
})}
|
|
232
|
-
</option>
|
|
233
|
-
<option value="draft" selected={post?.status === "draft"}>
|
|
234
|
-
{t({
|
|
235
|
-
message: "Draft",
|
|
236
|
-
comment: "@context: Post status option",
|
|
237
|
-
})}
|
|
238
|
-
</option>
|
|
239
|
-
</select>
|
|
240
|
-
</div>
|
|
241
|
-
|
|
242
|
-
{/* Featured & Pinned */}
|
|
243
|
-
<div class="flex gap-4">
|
|
244
|
-
<label class="flex items-center gap-2 text-sm">
|
|
245
|
-
<input type="checkbox" class="checkbox" data-bind="featured" />
|
|
246
|
-
{t({
|
|
247
|
-
message: "Featured",
|
|
248
|
-
comment: "@context: Post form checkbox - mark as featured",
|
|
249
|
-
})}
|
|
250
|
-
</label>
|
|
251
|
-
<label class="flex items-center gap-2 text-sm">
|
|
252
|
-
<input type="checkbox" class="checkbox" data-bind="pinned" />
|
|
253
|
-
{t({
|
|
254
|
-
message: "Pinned",
|
|
255
|
-
comment: "@context: Post form checkbox - pin to top",
|
|
256
|
-
})}
|
|
257
|
-
</label>
|
|
258
|
-
</div>
|
|
259
|
-
|
|
260
|
-
{/* Collection */}
|
|
261
|
-
{collections && collections.length > 0 && (
|
|
262
|
-
<div class="field">
|
|
263
|
-
<label class="label">
|
|
264
|
-
{t({
|
|
265
|
-
message: "Collection (optional)",
|
|
266
|
-
comment: "@context: Post form field - assign to collection",
|
|
267
|
-
})}
|
|
268
|
-
</label>
|
|
269
|
-
<select data-bind="collectionId" class="select">
|
|
270
|
-
<option value="0">
|
|
271
|
-
{t({
|
|
272
|
-
message: "None",
|
|
273
|
-
comment: "@context: No collection selected",
|
|
274
|
-
})}
|
|
275
|
-
</option>
|
|
276
|
-
{collections.map((col) => (
|
|
277
|
-
<option
|
|
278
|
-
key={col.id}
|
|
279
|
-
value={col.id}
|
|
280
|
-
selected={post?.collectionId === col.id}
|
|
281
|
-
>
|
|
282
|
-
{col.title}
|
|
283
|
-
</option>
|
|
284
|
-
))}
|
|
285
|
-
</select>
|
|
286
|
-
</div>
|
|
287
|
-
)}
|
|
288
|
-
|
|
289
|
-
{/* Submit */}
|
|
290
|
-
<div class="flex gap-2">
|
|
291
|
-
<button type="submit" class="btn" data-attr:disabled="$_loading">
|
|
292
|
-
<svg
|
|
293
|
-
data-show="$_loading"
|
|
294
|
-
style="display:none"
|
|
295
|
-
class="animate-spin size-4"
|
|
296
|
-
xmlns="http://www.w3.org/2000/svg"
|
|
297
|
-
viewBox="0 0 24 24"
|
|
298
|
-
fill="none"
|
|
299
|
-
stroke="currentColor"
|
|
300
|
-
stroke-width="2"
|
|
301
|
-
stroke-linecap="round"
|
|
302
|
-
stroke-linejoin="round"
|
|
303
|
-
role="status"
|
|
304
|
-
>
|
|
305
|
-
<path d="M21 12a9 9 0 1 1-6.219-8.56" />
|
|
306
|
-
</svg>
|
|
307
|
-
{isEdit
|
|
308
|
-
? t({
|
|
309
|
-
message: "Update",
|
|
310
|
-
comment: "@context: Button to update existing post",
|
|
311
|
-
})
|
|
312
|
-
: t({
|
|
313
|
-
message: "Publish",
|
|
314
|
-
comment: "@context: Button to publish new post",
|
|
315
|
-
})}
|
|
316
|
-
</button>
|
|
317
|
-
<a href="/dash/posts" class="btn-outline">
|
|
318
|
-
{t({ message: "Cancel", comment: "@context: Button to cancel form" })}
|
|
319
|
-
</a>
|
|
320
|
-
</div>
|
|
321
|
-
|
|
322
|
-
{/* Media picker dialog */}
|
|
323
|
-
<dialog
|
|
324
|
-
id="media-picker-dialog"
|
|
325
|
-
class="p-6 rounded-lg max-w-2xl w-full backdrop:bg-black/50"
|
|
326
|
-
onclick="event.target === this && this.close()"
|
|
327
|
-
>
|
|
328
|
-
<div class="flex items-center justify-between mb-4">
|
|
329
|
-
<h2 class="text-lg font-semibold">
|
|
330
|
-
{t({
|
|
331
|
-
message: "Select Media",
|
|
332
|
-
comment: "@context: Media picker dialog title",
|
|
333
|
-
})}
|
|
334
|
-
</h2>
|
|
335
|
-
<button
|
|
336
|
-
type="button"
|
|
337
|
-
class="btn-outline text-sm"
|
|
338
|
-
onclick="this.closest('dialog').close()"
|
|
339
|
-
>
|
|
340
|
-
{t({
|
|
341
|
-
message: "Done",
|
|
342
|
-
comment: "@context: Close media picker button",
|
|
343
|
-
})}
|
|
344
|
-
</button>
|
|
345
|
-
</div>
|
|
346
|
-
<div
|
|
347
|
-
id="media-picker-grid"
|
|
348
|
-
class="grid grid-cols-4 gap-2 max-h-96 overflow-y-auto"
|
|
349
|
-
>
|
|
350
|
-
<p class="text-muted-foreground text-sm col-span-4">
|
|
351
|
-
{t({
|
|
352
|
-
message: "Loading...",
|
|
353
|
-
comment: "@context: Loading state for media picker",
|
|
354
|
-
})}
|
|
355
|
-
</p>
|
|
356
|
-
</div>
|
|
357
|
-
</dialog>
|
|
358
|
-
</form>
|
|
359
|
-
);
|
|
360
|
-
};
|
|
@@ -1,254 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Appearance settings: color theme picker + custom CSS form
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import { useLingui } from "@lingui/react/macro";
|
|
6
|
-
import type { ColorTheme } from "../../color-themes.js";
|
|
7
|
-
import type { FontTheme } from "../../font-themes.js";
|
|
8
|
-
import { SettingsNav } from "./SettingsNav.js";
|
|
9
|
-
|
|
10
|
-
function ThemeCard({
|
|
11
|
-
theme,
|
|
12
|
-
selected,
|
|
13
|
-
}: {
|
|
14
|
-
theme: ColorTheme;
|
|
15
|
-
selected: boolean;
|
|
16
|
-
}) {
|
|
17
|
-
const expr = `$theme === '${theme.id}'`;
|
|
18
|
-
const { preview } = theme;
|
|
19
|
-
|
|
20
|
-
return (
|
|
21
|
-
<label
|
|
22
|
-
class={`block cursor-pointer rounded-lg border overflow-hidden transition-colors ${selected ? "border-primary" : "border-border"}`}
|
|
23
|
-
data-class:border-primary={expr}
|
|
24
|
-
data-class:border-border={`$theme !== '${theme.id}'`}
|
|
25
|
-
>
|
|
26
|
-
<div class="grid grid-cols-2">
|
|
27
|
-
<div
|
|
28
|
-
class="p-5"
|
|
29
|
-
style={`background-color:${preview.lightBg};color:${preview.lightText}`}
|
|
30
|
-
>
|
|
31
|
-
<input
|
|
32
|
-
type="radio"
|
|
33
|
-
name="theme"
|
|
34
|
-
value={theme.id}
|
|
35
|
-
data-bind="theme"
|
|
36
|
-
checked={selected || undefined}
|
|
37
|
-
class="mb-1"
|
|
38
|
-
/>
|
|
39
|
-
<h3 class="font-bold text-lg">{theme.name}</h3>
|
|
40
|
-
<p class="text-sm mt-2 leading-relaxed">
|
|
41
|
-
This is the {theme.name} theme in light mode. Links{" "}
|
|
42
|
-
<a
|
|
43
|
-
tabIndex={-1}
|
|
44
|
-
class="underline"
|
|
45
|
-
style={`color:${preview.lightLink}`}
|
|
46
|
-
>
|
|
47
|
-
look like this
|
|
48
|
-
</a>
|
|
49
|
-
. We'll show the correct light or dark mode based on your visitor's
|
|
50
|
-
settings.
|
|
51
|
-
</p>
|
|
52
|
-
</div>
|
|
53
|
-
<div
|
|
54
|
-
class="p-5"
|
|
55
|
-
style={`background-color:${preview.darkBg};color:${preview.darkText}`}
|
|
56
|
-
>
|
|
57
|
-
<h3 class="font-bold text-lg">{theme.name}</h3>
|
|
58
|
-
<p class="text-sm mt-2 leading-relaxed">
|
|
59
|
-
This is the {theme.name} theme in dark mode. Links{" "}
|
|
60
|
-
<a
|
|
61
|
-
tabIndex={-1}
|
|
62
|
-
class="underline"
|
|
63
|
-
style={`color:${preview.darkLink}`}
|
|
64
|
-
>
|
|
65
|
-
look like this
|
|
66
|
-
</a>
|
|
67
|
-
. We'll show the correct light or dark mode based on your visitor's
|
|
68
|
-
settings.
|
|
69
|
-
</p>
|
|
70
|
-
</div>
|
|
71
|
-
</div>
|
|
72
|
-
</label>
|
|
73
|
-
);
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
export function AppearanceContent({
|
|
77
|
-
themes,
|
|
78
|
-
currentThemeId,
|
|
79
|
-
fontThemes,
|
|
80
|
-
currentFontThemeId,
|
|
81
|
-
customCSS,
|
|
82
|
-
}: {
|
|
83
|
-
themes: ColorTheme[];
|
|
84
|
-
currentThemeId: string;
|
|
85
|
-
fontThemes: FontTheme[];
|
|
86
|
-
currentFontThemeId: string;
|
|
87
|
-
customCSS: string;
|
|
88
|
-
}) {
|
|
89
|
-
const { t } = useLingui();
|
|
90
|
-
|
|
91
|
-
const themeSignals = JSON.stringify({ theme: currentThemeId }).replace(
|
|
92
|
-
/</g,
|
|
93
|
-
"\\u003c",
|
|
94
|
-
);
|
|
95
|
-
|
|
96
|
-
const cssSignals = JSON.stringify({ customCSS }).replace(/</g, "\\u003c");
|
|
97
|
-
|
|
98
|
-
return (
|
|
99
|
-
<>
|
|
100
|
-
<h1 class="text-2xl font-semibold mb-2">
|
|
101
|
-
{t({ message: "Settings", comment: "@context: Dashboard heading" })}
|
|
102
|
-
</h1>
|
|
103
|
-
<SettingsNav currentTab="appearance" />
|
|
104
|
-
|
|
105
|
-
<div
|
|
106
|
-
data-signals={themeSignals}
|
|
107
|
-
data-on:change="@post('/dash/settings/appearance')"
|
|
108
|
-
class="max-w-3xl mb-8"
|
|
109
|
-
>
|
|
110
|
-
<fieldset>
|
|
111
|
-
<legend class="text-lg font-semibold">
|
|
112
|
-
{t({
|
|
113
|
-
message: "Color theme",
|
|
114
|
-
comment: "@context: Appearance settings heading",
|
|
115
|
-
})}
|
|
116
|
-
</legend>
|
|
117
|
-
<p class="text-sm text-muted-foreground mb-4">
|
|
118
|
-
{t({
|
|
119
|
-
message:
|
|
120
|
-
"This will theme both your site and your dashboard. All color themes support dark mode.",
|
|
121
|
-
comment: "@context: Appearance settings description",
|
|
122
|
-
})}
|
|
123
|
-
</p>
|
|
124
|
-
|
|
125
|
-
<div class="flex flex-col gap-4">
|
|
126
|
-
{themes.map((theme) => (
|
|
127
|
-
<ThemeCard
|
|
128
|
-
key={theme.id}
|
|
129
|
-
theme={theme}
|
|
130
|
-
selected={theme.id === currentThemeId}
|
|
131
|
-
/>
|
|
132
|
-
))}
|
|
133
|
-
</div>
|
|
134
|
-
</fieldset>
|
|
135
|
-
</div>
|
|
136
|
-
|
|
137
|
-
<div
|
|
138
|
-
data-signals={JSON.stringify({ fontTheme: currentFontThemeId }).replace(
|
|
139
|
-
/</g,
|
|
140
|
-
"\\u003c",
|
|
141
|
-
)}
|
|
142
|
-
data-on:change="@post('/dash/settings/font-theme')"
|
|
143
|
-
class="max-w-3xl"
|
|
144
|
-
>
|
|
145
|
-
<fieldset>
|
|
146
|
-
<legend class="text-lg font-semibold">
|
|
147
|
-
{t({
|
|
148
|
-
message: "Font theme",
|
|
149
|
-
comment: "@context: Appearance settings heading for font theme",
|
|
150
|
-
})}
|
|
151
|
-
</legend>
|
|
152
|
-
<p class="text-sm text-muted-foreground mb-4">
|
|
153
|
-
{t({
|
|
154
|
-
message:
|
|
155
|
-
"Choose a font for your site. All options use system fonts for fast loading.",
|
|
156
|
-
comment: "@context: Font theme settings description",
|
|
157
|
-
})}
|
|
158
|
-
</p>
|
|
159
|
-
<div class="flex flex-col gap-2">
|
|
160
|
-
{fontThemes.map((ft) => (
|
|
161
|
-
<label
|
|
162
|
-
key={ft.id}
|
|
163
|
-
class={`flex items-start gap-3 p-3 rounded-lg border cursor-pointer transition-colors ${ft.id === currentFontThemeId ? "border-primary" : "border-border"}`}
|
|
164
|
-
data-class:border-primary={`$fontTheme === '${ft.id}'`}
|
|
165
|
-
data-class:border-border={`$fontTheme !== '${ft.id}'`}
|
|
166
|
-
>
|
|
167
|
-
<input
|
|
168
|
-
type="radio"
|
|
169
|
-
name="fontTheme"
|
|
170
|
-
value={ft.id}
|
|
171
|
-
data-bind="fontTheme"
|
|
172
|
-
checked={ft.id === currentFontThemeId || undefined}
|
|
173
|
-
class="mt-1"
|
|
174
|
-
/>
|
|
175
|
-
<div>
|
|
176
|
-
<div class="font-medium">{ft.name}</div>
|
|
177
|
-
<div class="text-sm text-muted-foreground">
|
|
178
|
-
{ft.description}
|
|
179
|
-
</div>
|
|
180
|
-
<div
|
|
181
|
-
class="mt-1 text-sm"
|
|
182
|
-
style={`font-family:${ft.fontFamily}`}
|
|
183
|
-
>
|
|
184
|
-
The quick brown fox jumps over the lazy dog.{" "}
|
|
185
|
-
敏捷的棕色狐狸跳过了懒狗。
|
|
186
|
-
</div>
|
|
187
|
-
</div>
|
|
188
|
-
</label>
|
|
189
|
-
))}
|
|
190
|
-
</div>
|
|
191
|
-
</fieldset>
|
|
192
|
-
</div>
|
|
193
|
-
|
|
194
|
-
<form
|
|
195
|
-
data-signals={cssSignals}
|
|
196
|
-
data-on:submit__prevent="@post('/dash/settings/custom-css')"
|
|
197
|
-
data-indicator="_cssLoading"
|
|
198
|
-
class="max-w-3xl mt-8"
|
|
199
|
-
>
|
|
200
|
-
<fieldset>
|
|
201
|
-
<legend class="text-lg font-semibold">
|
|
202
|
-
{t({
|
|
203
|
-
message: "Custom CSS",
|
|
204
|
-
comment: "@context: Appearance settings heading for custom CSS",
|
|
205
|
-
})}
|
|
206
|
-
</legend>
|
|
207
|
-
<p class="text-sm text-muted-foreground mb-4">
|
|
208
|
-
{t({
|
|
209
|
-
message:
|
|
210
|
-
"Add custom CSS to override any styles. Use data attributes like [data-page], [data-post], [data-format] to target specific elements.",
|
|
211
|
-
comment: "@context: Custom CSS settings description",
|
|
212
|
-
})}
|
|
213
|
-
</p>
|
|
214
|
-
<textarea
|
|
215
|
-
data-bind="customCSS"
|
|
216
|
-
class="textarea font-mono text-sm min-h-32"
|
|
217
|
-
rows={8}
|
|
218
|
-
placeholder={t({
|
|
219
|
-
message: "/* Your custom CSS here */",
|
|
220
|
-
comment: "@context: Custom CSS textarea placeholder",
|
|
221
|
-
})}
|
|
222
|
-
>
|
|
223
|
-
{customCSS}
|
|
224
|
-
</textarea>
|
|
225
|
-
</fieldset>
|
|
226
|
-
<button
|
|
227
|
-
type="submit"
|
|
228
|
-
class="btn mt-4"
|
|
229
|
-
data-attr:disabled="$_cssLoading"
|
|
230
|
-
>
|
|
231
|
-
<svg
|
|
232
|
-
data-show="$_cssLoading"
|
|
233
|
-
style="display:none"
|
|
234
|
-
class="animate-spin size-4"
|
|
235
|
-
xmlns="http://www.w3.org/2000/svg"
|
|
236
|
-
viewBox="0 0 24 24"
|
|
237
|
-
fill="none"
|
|
238
|
-
stroke="currentColor"
|
|
239
|
-
stroke-width="2"
|
|
240
|
-
stroke-linecap="round"
|
|
241
|
-
stroke-linejoin="round"
|
|
242
|
-
role="status"
|
|
243
|
-
>
|
|
244
|
-
<path d="M21 12a9 9 0 1 1-6.219-8.56" />
|
|
245
|
-
</svg>
|
|
246
|
-
{t({
|
|
247
|
-
message: "Save CSS",
|
|
248
|
-
comment: "@context: Button to save custom CSS",
|
|
249
|
-
})}
|
|
250
|
-
</button>
|
|
251
|
-
</form>
|
|
252
|
-
</>
|
|
253
|
-
);
|
|
254
|
-
}
|