@jant/core 0.3.26 → 0.3.28
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/client/client.css +1 -0
- package/dist/client/client.js +31561 -0
- package/dist/index.js +15209 -15
- package/package.json +21 -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 +112 -173
- package/src/auth.ts +4 -1
- 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 -265
- package/dist/auth.js +0 -36
- 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
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* App Context Types
|
|
3
|
+
*
|
|
4
|
+
* Shared app-level types used across routes, middleware, and lib.
|
|
5
|
+
* Lives here (not in app.tsx) to avoid forbidden upward imports
|
|
6
|
+
* from feature modules to composition roots.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type { Hono } from "hono";
|
|
10
|
+
import type { Services } from "../services/index.js";
|
|
11
|
+
import type { Auth } from "../auth.js";
|
|
12
|
+
import type { AppConfig } from "./config.js";
|
|
13
|
+
import type { StorageDriver } from "../lib/storage.js";
|
|
14
|
+
import type { Bindings } from "./bindings.js";
|
|
15
|
+
|
|
16
|
+
export interface AppVariables {
|
|
17
|
+
services: Services;
|
|
18
|
+
auth: Auth;
|
|
19
|
+
appConfig: AppConfig;
|
|
20
|
+
allSettings: Record<string, string>;
|
|
21
|
+
themeStyle: string;
|
|
22
|
+
storage: StorageDriver | null;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export type App = Hono<{ Bindings: Bindings; Variables: AppVariables }>;
|
package/src/types/bindings.ts
CHANGED
package/src/types/config.ts
CHANGED
|
@@ -4,9 +4,6 @@
|
|
|
4
4
|
* Single Source of Truth for all configuration fields.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import type { ColorTheme } from "../ui/color-themes.js";
|
|
8
|
-
import type { FeedData, SitemapData } from "./views.js";
|
|
9
|
-
|
|
10
7
|
/**
|
|
11
8
|
* Configuration Registry - Single Source of Truth
|
|
12
9
|
*
|
|
@@ -37,6 +34,10 @@ export const CONFIG_FIELDS = {
|
|
|
37
34
|
},
|
|
38
35
|
|
|
39
36
|
// Environment-only (deployment/infrastructure config)
|
|
37
|
+
DEFAULT_THEME: {
|
|
38
|
+
defaultValue: "halloween",
|
|
39
|
+
envOnly: true,
|
|
40
|
+
},
|
|
40
41
|
SITE_URL: {
|
|
41
42
|
defaultValue: "",
|
|
42
43
|
envOnly: true,
|
|
@@ -125,6 +126,11 @@ export const CONFIG_FIELDS = {
|
|
|
125
126
|
envOnly: false,
|
|
126
127
|
internal: true,
|
|
127
128
|
},
|
|
129
|
+
SITE_FAVICON_VERSION: {
|
|
130
|
+
defaultValue: "",
|
|
131
|
+
envOnly: false,
|
|
132
|
+
internal: true,
|
|
133
|
+
},
|
|
128
134
|
FONT_THEME: {
|
|
129
135
|
defaultValue: "",
|
|
130
136
|
envOnly: false,
|
|
@@ -157,27 +163,58 @@ export const CONFIG_FIELDS = {
|
|
|
157
163
|
export type ConfigKey = keyof typeof CONFIG_FIELDS;
|
|
158
164
|
|
|
159
165
|
/**
|
|
160
|
-
*
|
|
161
|
-
*
|
|
162
|
-
* Configuration Philosophy:
|
|
163
|
-
* - Use environment variables for runtime config (API keys, feature flags, site settings)
|
|
164
|
-
* - Use code config (this object) for CSS customization and feed overrides
|
|
166
|
+
* Unified application configuration
|
|
165
167
|
*
|
|
166
|
-
*
|
|
167
|
-
*
|
|
168
|
+
* Resolved once per request from DB settings + env + defaults.
|
|
169
|
+
* Access via `c.var.appConfig` in routes and lib functions.
|
|
168
170
|
*/
|
|
169
|
-
export interface
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
171
|
+
export interface AppConfig {
|
|
172
|
+
// Site identity (DB > ENV > Default)
|
|
173
|
+
siteName: string;
|
|
174
|
+
siteDescription: string;
|
|
175
|
+
/** true only when description is set in DB or ENV (not just the default) */
|
|
176
|
+
siteDescriptionExplicit: boolean;
|
|
177
|
+
siteLanguage: string;
|
|
178
|
+
homeDefaultView: string;
|
|
179
|
+
timeZone: string;
|
|
180
|
+
siteFooter: string;
|
|
181
|
+
noindex: boolean;
|
|
182
|
+
|
|
183
|
+
// Infrastructure (ENV only)
|
|
184
|
+
siteUrl: string;
|
|
185
|
+
authConfigured: boolean;
|
|
186
|
+
|
|
187
|
+
// Media (ENV only)
|
|
188
|
+
storageDriver: string;
|
|
189
|
+
r2PublicUrl: string;
|
|
190
|
+
s3PublicUrl: string;
|
|
191
|
+
imageTransformUrl: string;
|
|
192
|
+
|
|
193
|
+
// Pagination/Feed (ENV only, parsed to number)
|
|
194
|
+
pageSize: number;
|
|
195
|
+
rssFeedLimit: number;
|
|
196
|
+
|
|
197
|
+
// Demo (ENV only)
|
|
198
|
+
demoEmail: string;
|
|
199
|
+
demoPassword: string;
|
|
200
|
+
|
|
201
|
+
// Theme (DB internal)
|
|
202
|
+
themeId: string;
|
|
203
|
+
defaultThemeId: string;
|
|
204
|
+
fontThemeId: string;
|
|
205
|
+
customCSS: string;
|
|
206
|
+
|
|
207
|
+
// Site appearance (DB internal)
|
|
208
|
+
siteAvatar: string;
|
|
209
|
+
showHeaderAvatar: boolean;
|
|
210
|
+
/** Derived: getMediaUrl(siteAvatar, publicUrl) */
|
|
211
|
+
siteAvatarUrl: string;
|
|
212
|
+
faviconVersion: string;
|
|
213
|
+
|
|
214
|
+
// Dashboard form placeholders (ENV > Default, without DB)
|
|
215
|
+
fallbacks: {
|
|
216
|
+
siteName: string;
|
|
217
|
+
siteDescription: string;
|
|
218
|
+
defaultTheme: string;
|
|
182
219
|
};
|
|
183
220
|
}
|
package/src/types/entities.ts
CHANGED
|
@@ -17,7 +17,6 @@ export interface Post {
|
|
|
17
17
|
bodyHtml: string | null;
|
|
18
18
|
quoteText: string | null;
|
|
19
19
|
rating: number | null;
|
|
20
|
-
collectionId: number | null;
|
|
21
20
|
replyToId: number | null;
|
|
22
21
|
threadId: number | null;
|
|
23
22
|
deletedAt: number | null;
|
|
@@ -78,11 +77,22 @@ export interface Collection {
|
|
|
78
77
|
icon: string | null;
|
|
79
78
|
sortOrder: SortOrder;
|
|
80
79
|
position: number;
|
|
81
|
-
showDivider: number; // 0 | 1
|
|
82
80
|
createdAt: number;
|
|
83
81
|
updatedAt: number;
|
|
84
82
|
}
|
|
85
83
|
|
|
84
|
+
export interface CollectionDivider {
|
|
85
|
+
id: number;
|
|
86
|
+
position: number;
|
|
87
|
+
createdAt: number;
|
|
88
|
+
updatedAt: number;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export interface PostCollection {
|
|
92
|
+
postId: number;
|
|
93
|
+
collectionId: number;
|
|
94
|
+
}
|
|
95
|
+
|
|
86
96
|
export interface NavItem {
|
|
87
97
|
id: number;
|
|
88
98
|
type: NavItemType;
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
declare module "@lingui/react/macro" {
|
|
12
|
-
import type { I18n } from "@lingui/core";
|
|
12
|
+
import type { I18n, MessageDescriptor } from "@lingui/core";
|
|
13
13
|
import type { FC, PropsWithChildren } from "hono/jsx";
|
|
14
14
|
|
|
15
15
|
interface TranslationDescriptor {
|
|
@@ -20,8 +20,8 @@ declare module "@lingui/react/macro" {
|
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
export function useLingui(): {
|
|
23
|
-
t: (descriptor: TranslationDescriptor) => string;
|
|
24
|
-
_: (descriptor: TranslationDescriptor) => string;
|
|
23
|
+
t: (descriptor: TranslationDescriptor | MessageDescriptor) => string;
|
|
24
|
+
_: (descriptor: TranslationDescriptor | MessageDescriptor) => string;
|
|
25
25
|
i18n: I18n;
|
|
26
26
|
};
|
|
27
27
|
|
package/src/types/operations.ts
CHANGED
|
@@ -15,7 +15,7 @@ export interface CreatePost {
|
|
|
15
15
|
body?: string;
|
|
16
16
|
quoteText?: string;
|
|
17
17
|
rating?: number;
|
|
18
|
-
|
|
18
|
+
collectionIds?: number[];
|
|
19
19
|
replyToId?: number;
|
|
20
20
|
publishedAt?: number;
|
|
21
21
|
mediaIds?: string[];
|
|
@@ -32,7 +32,7 @@ export interface UpdatePost {
|
|
|
32
32
|
body?: string | null;
|
|
33
33
|
quoteText?: string | null;
|
|
34
34
|
rating?: number | null;
|
|
35
|
-
|
|
35
|
+
collectionIds?: number[];
|
|
36
36
|
publishedAt?: number;
|
|
37
37
|
mediaIds?: string[];
|
|
38
38
|
}
|
|
@@ -74,7 +74,6 @@ export interface CreateCollection {
|
|
|
74
74
|
icon?: string;
|
|
75
75
|
sortOrder?: SortOrder;
|
|
76
76
|
position?: number;
|
|
77
|
-
showDivider?: boolean;
|
|
78
77
|
}
|
|
79
78
|
|
|
80
79
|
export interface UpdateCollection {
|
|
@@ -84,5 +83,4 @@ export interface UpdateCollection {
|
|
|
84
83
|
icon?: string | null;
|
|
85
84
|
sortOrder?: SortOrder;
|
|
86
85
|
position?: number;
|
|
87
|
-
showDivider?: boolean;
|
|
88
86
|
}
|
package/src/types/views.ts
CHANGED
|
@@ -39,9 +39,6 @@ export interface PostView {
|
|
|
39
39
|
pinned: boolean;
|
|
40
40
|
rating?: number;
|
|
41
41
|
|
|
42
|
-
// Collection
|
|
43
|
-
collectionId?: number;
|
|
44
|
-
|
|
45
42
|
// Time -- pre-formatted
|
|
46
43
|
/** ISO 8601 string */
|
|
47
44
|
publishedAt: string;
|
|
@@ -169,4 +166,5 @@ export interface SiteLayoutProps {
|
|
|
169
166
|
siteAvatarUrl?: string;
|
|
170
167
|
showHeaderAvatar?: boolean;
|
|
171
168
|
siteFooterHtml?: string;
|
|
169
|
+
sidebar?: import("hono/jsx").Child;
|
|
172
170
|
}
|
|
@@ -2,8 +2,8 @@ import { describe, it, expect } from "vitest";
|
|
|
2
2
|
import { BUILTIN_FONT_THEMES } from "../font-themes.js";
|
|
3
3
|
|
|
4
4
|
describe("BUILTIN_FONT_THEMES", () => {
|
|
5
|
-
it("contains
|
|
6
|
-
expect(BUILTIN_FONT_THEMES).toHaveLength(
|
|
5
|
+
it("contains 5 themes", () => {
|
|
6
|
+
expect(BUILTIN_FONT_THEMES).toHaveLength(5);
|
|
7
7
|
});
|
|
8
8
|
|
|
9
9
|
it("has 'default' as the first theme", () => {
|
|
@@ -13,9 +13,10 @@ describe("BUILTIN_FONT_THEMES", () => {
|
|
|
13
13
|
it("each theme has required fields", () => {
|
|
14
14
|
for (const theme of BUILTIN_FONT_THEMES) {
|
|
15
15
|
expect(theme.id).toBeTruthy();
|
|
16
|
-
expect(theme.name).toBeTruthy();
|
|
17
|
-
expect(theme.
|
|
18
|
-
expect(theme.
|
|
16
|
+
expect(theme.name.message).toBeTruthy();
|
|
17
|
+
expect(theme.headingFontFamily).toBeTruthy();
|
|
18
|
+
expect(theme.bodyFontFamily).toBeTruthy();
|
|
19
|
+
expect(theme.description.message).toBeTruthy();
|
|
19
20
|
}
|
|
20
21
|
});
|
|
21
22
|
|
|
@@ -27,8 +28,26 @@ describe("BUILTIN_FONT_THEMES", () => {
|
|
|
27
28
|
it("includes expected theme IDs", () => {
|
|
28
29
|
const ids = BUILTIN_FONT_THEMES.map((t) => t.id);
|
|
29
30
|
expect(ids).toContain("default");
|
|
30
|
-
expect(ids).toContain("
|
|
31
|
-
expect(ids).toContain("
|
|
32
|
-
expect(ids).toContain("
|
|
31
|
+
expect(ids).toContain("classic-editorial");
|
|
32
|
+
expect(ids).toContain("modern-editorial");
|
|
33
|
+
expect(ids).toContain("literary");
|
|
34
|
+
expect(ids).toContain("geometric");
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it("default theme uses the same font for heading and body", () => {
|
|
38
|
+
const defaultTheme = BUILTIN_FONT_THEMES.find(
|
|
39
|
+
(t) => t.id === "default",
|
|
40
|
+
) as (typeof BUILTIN_FONT_THEMES)[number];
|
|
41
|
+
expect(defaultTheme.headingFontFamily).toBe(defaultTheme.bodyFontFamily);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it("pairing themes have distinct heading and body fonts", () => {
|
|
45
|
+
const pairingIds = ["classic-editorial", "modern-editorial", "literary"];
|
|
46
|
+
for (const id of pairingIds) {
|
|
47
|
+
const theme = BUILTIN_FONT_THEMES.find(
|
|
48
|
+
(t) => t.id === id,
|
|
49
|
+
) as (typeof BUILTIN_FONT_THEMES)[number];
|
|
50
|
+
expect(theme.headingFontFamily).not.toBe(theme.bodyFontFamily);
|
|
51
|
+
}
|
|
33
52
|
});
|
|
34
53
|
});
|
package/src/ui/color-themes.ts
CHANGED
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
// @vitest-environment happy-dom
|
|
2
|
+
|
|
3
|
+
import { describe, it, expect, beforeEach } from "vitest";
|
|
4
|
+
import type {
|
|
5
|
+
CollectionFormInitial,
|
|
6
|
+
CollectionFormLabels,
|
|
7
|
+
CollectionSubmitDetail,
|
|
8
|
+
} from "../collection-types.js";
|
|
9
|
+
import "../jant-collection-form.js";
|
|
10
|
+
import type { JantCollectionForm } from "../jant-collection-form.js";
|
|
11
|
+
|
|
12
|
+
const labels: CollectionFormLabels = {
|
|
13
|
+
titleLabel: "Title",
|
|
14
|
+
titlePlaceholder: "Placeholder Title",
|
|
15
|
+
slugLabel: "Slug",
|
|
16
|
+
slugHelp: "Help text",
|
|
17
|
+
descriptionLabel: "Description",
|
|
18
|
+
descriptionPlaceholder: "Placeholder Description",
|
|
19
|
+
iconLabel: "Icon",
|
|
20
|
+
chooseIcon: "Choose Icon",
|
|
21
|
+
removeIcon: "Remove",
|
|
22
|
+
dialogTitle: "Choose Icon",
|
|
23
|
+
dialogClose: "Close",
|
|
24
|
+
searchIconsPlaceholder: "Search icons...",
|
|
25
|
+
sortOrderLabel: "Sort Order",
|
|
26
|
+
sortNewest: "Newest first",
|
|
27
|
+
sortOldest: "Oldest first",
|
|
28
|
+
sortRatingDesc: "Highest rated",
|
|
29
|
+
sortRatingAsc: "Lowest rated",
|
|
30
|
+
submitLabel: "Create Collection",
|
|
31
|
+
cancelLabel: "Cancel",
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const initial: CollectionFormInitial = {
|
|
35
|
+
title: "",
|
|
36
|
+
slug: "",
|
|
37
|
+
description: "",
|
|
38
|
+
sortOrder: "newest",
|
|
39
|
+
icon: "",
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
async function createElement(
|
|
43
|
+
overrides: Partial<JantCollectionForm> = {},
|
|
44
|
+
): Promise<JantCollectionForm> {
|
|
45
|
+
const el = document.createElement(
|
|
46
|
+
"jant-collection-form",
|
|
47
|
+
) as JantCollectionForm;
|
|
48
|
+
el.labels = labels;
|
|
49
|
+
el.initial = initial;
|
|
50
|
+
el.action = "/dash/collections";
|
|
51
|
+
el.cancelHref = "/dash/collections";
|
|
52
|
+
el.isEdit = false;
|
|
53
|
+
Object.assign(el, overrides);
|
|
54
|
+
document.body.appendChild(el);
|
|
55
|
+
await el.updateComplete;
|
|
56
|
+
return el;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
describe("JantCollectionForm", () => {
|
|
60
|
+
beforeEach(() => {
|
|
61
|
+
document.body.innerHTML = "";
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it("renders form fields with labels", async () => {
|
|
65
|
+
const el = await createElement();
|
|
66
|
+
const inputs = el.querySelectorAll("input");
|
|
67
|
+
expect(inputs.length).toBeGreaterThan(0);
|
|
68
|
+
|
|
69
|
+
const titleLabel = el.querySelector(".field .label");
|
|
70
|
+
expect(titleLabel?.textContent?.trim()).toBe("Title");
|
|
71
|
+
|
|
72
|
+
const submitButton = el.querySelector<HTMLButtonElement>(
|
|
73
|
+
"button[type=submit]",
|
|
74
|
+
);
|
|
75
|
+
expect(submitButton?.textContent?.trim()).toBe("Create Collection");
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it("auto-generates slug when not editing", async () => {
|
|
79
|
+
const el = await createElement();
|
|
80
|
+
const titleInput = el.querySelectorAll<HTMLInputElement>("input")[0];
|
|
81
|
+
const slugInput = el.querySelectorAll<HTMLInputElement>("input")[1];
|
|
82
|
+
|
|
83
|
+
titleInput.value = "My Great Collection!";
|
|
84
|
+
titleInput.dispatchEvent(new Event("input", { bubbles: true }));
|
|
85
|
+
await el.updateComplete;
|
|
86
|
+
|
|
87
|
+
expect(slugInput.value).toBe("my-great-collection");
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it("does not overwrite slug when editing", async () => {
|
|
91
|
+
const el = await createElement({
|
|
92
|
+
isEdit: true,
|
|
93
|
+
initial: {
|
|
94
|
+
...initial,
|
|
95
|
+
title: "Existing",
|
|
96
|
+
slug: "existing-slug",
|
|
97
|
+
},
|
|
98
|
+
});
|
|
99
|
+
await el.updateComplete;
|
|
100
|
+
|
|
101
|
+
const titleInput = el.querySelectorAll<HTMLInputElement>("input")[0];
|
|
102
|
+
const slugInput = el.querySelectorAll<HTMLInputElement>("input")[1];
|
|
103
|
+
|
|
104
|
+
titleInput.value = "Updated Title";
|
|
105
|
+
titleInput.dispatchEvent(new Event("input", { bubbles: true }));
|
|
106
|
+
await el.updateComplete;
|
|
107
|
+
|
|
108
|
+
expect(slugInput.value).toBe("existing-slug");
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it("dispatches jant:collection-submit on submit", async () => {
|
|
112
|
+
const el = await createElement();
|
|
113
|
+
|
|
114
|
+
const titleInput = el.querySelectorAll<HTMLInputElement>("input")[0];
|
|
115
|
+
const slugInput = el.querySelectorAll<HTMLInputElement>("input")[1];
|
|
116
|
+
const descriptionTextarea =
|
|
117
|
+
el.querySelector<HTMLTextAreaElement>("textarea");
|
|
118
|
+
const select = el.querySelector<HTMLSelectElement>("select");
|
|
119
|
+
|
|
120
|
+
titleInput.value = "Books";
|
|
121
|
+
titleInput.dispatchEvent(new Event("input", { bubbles: true }));
|
|
122
|
+
slugInput.value = "books";
|
|
123
|
+
slugInput.dispatchEvent(new Event("input", { bubbles: true }));
|
|
124
|
+
if (descriptionTextarea) {
|
|
125
|
+
descriptionTextarea.value = "All about books";
|
|
126
|
+
descriptionTextarea.dispatchEvent(new Event("input", { bubbles: true }));
|
|
127
|
+
}
|
|
128
|
+
if (select) {
|
|
129
|
+
select.value = "rating_desc";
|
|
130
|
+
select.dispatchEvent(new Event("change", { bubbles: true }));
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
let detail: CollectionSubmitDetail | null = null;
|
|
134
|
+
el.addEventListener("jant:collection-submit", (event) => {
|
|
135
|
+
const customEvent = event as CustomEvent<CollectionSubmitDetail>;
|
|
136
|
+
detail = customEvent.detail;
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
const form = el.querySelector("form");
|
|
140
|
+
form?.dispatchEvent(
|
|
141
|
+
new Event("submit", { bubbles: true, cancelable: true }),
|
|
142
|
+
);
|
|
143
|
+
|
|
144
|
+
expect(detail).not.toBeNull();
|
|
145
|
+
const d = detail as unknown as CollectionSubmitDetail;
|
|
146
|
+
expect(d.endpoint).toBe("/dash/collections");
|
|
147
|
+
expect(d.data.title).toBe("Books");
|
|
148
|
+
expect(d.data.slug).toBe("books");
|
|
149
|
+
expect(d.data.description).toBe("All about books");
|
|
150
|
+
expect(d.data.sortOrder).toBe("rating_desc");
|
|
151
|
+
expect(d.data.icon).toBeUndefined();
|
|
152
|
+
});
|
|
153
|
+
});
|