@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
|
@@ -14,7 +14,8 @@ import { BaseLayout } from "../../ui/layouts/BaseLayout.js";
|
|
|
14
14
|
import { dsRedirect, dsToast } from "../../lib/sse.js";
|
|
15
15
|
import { SetupSchema } from "../../lib/schemas.js";
|
|
16
16
|
import { mapIanaToTimezone } from "../../lib/timezones.js";
|
|
17
|
-
import { getI18n } from "../../i18n/index.js";
|
|
17
|
+
import { getI18n, baseLocale } from "../../i18n/index.js";
|
|
18
|
+
import { detectLocaleFromHeader } from "../../i18n/detect.js";
|
|
18
19
|
|
|
19
20
|
type Env = { Bindings: Bindings; Variables: AppVariables };
|
|
20
21
|
|
|
@@ -40,8 +41,8 @@ const SetupContent: FC = () => {
|
|
|
40
41
|
</header>
|
|
41
42
|
<section>
|
|
42
43
|
<form
|
|
43
|
-
data-signals="{
|
|
44
|
-
data-init="$
|
|
44
|
+
data-signals="{siteName: '', email: '', password: '', timezone: '', language: ''}"
|
|
45
|
+
data-init="$timezone = Intl.DateTimeFormat().resolvedOptions().timeZone || ''; $language = navigator.language || ''"
|
|
45
46
|
data-on:submit__prevent="@post('/setup')"
|
|
46
47
|
data-indicator="_loading"
|
|
47
48
|
class="flex flex-col gap-4"
|
|
@@ -49,16 +50,16 @@ const SetupContent: FC = () => {
|
|
|
49
50
|
<div class="field">
|
|
50
51
|
<label class="label">
|
|
51
52
|
{t({
|
|
52
|
-
message: "
|
|
53
|
-
comment: "@context: Setup form field -
|
|
53
|
+
message: "Site Name",
|
|
54
|
+
comment: "@context: Setup form field - site name",
|
|
54
55
|
})}
|
|
55
56
|
</label>
|
|
56
57
|
<input
|
|
57
58
|
type="text"
|
|
58
|
-
data-bind="
|
|
59
|
+
data-bind="siteName"
|
|
59
60
|
class="input"
|
|
60
61
|
required
|
|
61
|
-
placeholder="
|
|
62
|
+
placeholder="My Blog"
|
|
62
63
|
/>
|
|
63
64
|
</div>
|
|
64
65
|
<div class="field">
|
|
@@ -139,27 +140,29 @@ setupRoutes.post("/setup", async (c) => {
|
|
|
139
140
|
|
|
140
141
|
const body = await c.req.json<Record<string, string>>();
|
|
141
142
|
const parsed = SetupSchema.safeParse(body);
|
|
142
|
-
const browserTimezone = body.
|
|
143
|
+
const browserTimezone = body.timezone;
|
|
144
|
+
const browserLanguage = body.language;
|
|
143
145
|
|
|
144
146
|
if (!parsed.success) {
|
|
145
147
|
const errorMsg =
|
|
146
148
|
parsed.error.issues[0]?.message ??
|
|
147
149
|
i18n._(
|
|
148
150
|
msg({
|
|
149
|
-
message:
|
|
151
|
+
message:
|
|
152
|
+
"Something doesn't look right. Check the form and try again.",
|
|
150
153
|
comment: "@context: Fallback validation error for setup form",
|
|
151
154
|
}),
|
|
152
155
|
);
|
|
153
156
|
return dsToast(errorMsg, "error");
|
|
154
157
|
}
|
|
155
158
|
|
|
156
|
-
const {
|
|
159
|
+
const { siteName, email, password } = parsed.data;
|
|
157
160
|
|
|
158
161
|
if (!c.var.auth) {
|
|
159
162
|
return dsToast(
|
|
160
163
|
i18n._(
|
|
161
164
|
msg({
|
|
162
|
-
message: "
|
|
165
|
+
message: "Auth secret is missing. Check your environment variables.",
|
|
163
166
|
comment:
|
|
164
167
|
"@context: Error toast when authentication secret is missing from server config",
|
|
165
168
|
}),
|
|
@@ -170,14 +173,15 @@ setupRoutes.post("/setup", async (c) => {
|
|
|
170
173
|
|
|
171
174
|
try {
|
|
172
175
|
const signUpResponse = await c.var.auth.api.signUpEmail({
|
|
173
|
-
body: { name, email, password },
|
|
176
|
+
body: { name: siteName.trim(), email, password },
|
|
174
177
|
});
|
|
175
178
|
|
|
176
179
|
if (!signUpResponse || "error" in signUpResponse) {
|
|
177
180
|
return dsToast(
|
|
178
181
|
i18n._(
|
|
179
182
|
msg({
|
|
180
|
-
message:
|
|
183
|
+
message:
|
|
184
|
+
"Couldn't create your account. Check the details and try again.",
|
|
181
185
|
comment: "@context: Error toast when account creation fails",
|
|
182
186
|
}),
|
|
183
187
|
),
|
|
@@ -187,6 +191,9 @@ setupRoutes.post("/setup", async (c) => {
|
|
|
187
191
|
|
|
188
192
|
await c.var.services.settings.completeOnboarding();
|
|
189
193
|
|
|
194
|
+
// Save site name
|
|
195
|
+
await c.var.services.settings.set("SITE_NAME", siteName.trim());
|
|
196
|
+
|
|
190
197
|
// Save auto-detected timezone
|
|
191
198
|
if (browserTimezone) {
|
|
192
199
|
const tz = mapIanaToTimezone(browserTimezone);
|
|
@@ -195,37 +202,37 @@ setupRoutes.post("/setup", async (c) => {
|
|
|
195
202
|
}
|
|
196
203
|
}
|
|
197
204
|
|
|
205
|
+
// Save auto-detected language from browser's navigator.language
|
|
206
|
+
if (browserLanguage) {
|
|
207
|
+
const detectedLang = detectLocaleFromHeader(browserLanguage);
|
|
208
|
+
if (detectedLang !== baseLocale) {
|
|
209
|
+
await c.var.services.settings.set("SITE_LANGUAGE", detectedLang);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// Seed default navigation items (order: Collections, Archive, RSS, Settings)
|
|
198
214
|
await c.var.services.navItems.create({
|
|
199
215
|
type: "link",
|
|
200
216
|
label: "Collections",
|
|
201
217
|
url: "/c",
|
|
202
218
|
});
|
|
203
|
-
|
|
219
|
+
|
|
204
220
|
await c.var.services.navItems.create({
|
|
205
221
|
type: "link",
|
|
206
222
|
label: "Archive",
|
|
207
223
|
url: "/archive",
|
|
208
224
|
});
|
|
209
225
|
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
body: [
|
|
215
|
-
"Welcome to my corner of the internet.",
|
|
216
|
-
"",
|
|
217
|
-
"This is a place where I share my thoughts, ideas, and things I find interesting. Feel free to look around and get to know what this site is all about.",
|
|
218
|
-
"",
|
|
219
|
-
"If you'd like to get in touch, don't hesitate to reach out.",
|
|
220
|
-
].join("\n"),
|
|
221
|
-
status: "published",
|
|
226
|
+
await c.var.services.navItems.create({
|
|
227
|
+
type: "system",
|
|
228
|
+
label: "RSS",
|
|
229
|
+
url: "/feed",
|
|
222
230
|
});
|
|
223
231
|
|
|
224
232
|
await c.var.services.navItems.create({
|
|
225
|
-
type: "
|
|
226
|
-
label: "
|
|
227
|
-
url: "/
|
|
228
|
-
pageId: aboutPage.id,
|
|
233
|
+
type: "system",
|
|
234
|
+
label: "Settings",
|
|
235
|
+
url: "/settings",
|
|
229
236
|
});
|
|
230
237
|
|
|
231
238
|
return dsRedirect("/signin?setup");
|
|
@@ -235,7 +242,8 @@ setupRoutes.post("/setup", async (c) => {
|
|
|
235
242
|
return dsToast(
|
|
236
243
|
i18n._(
|
|
237
244
|
msg({
|
|
238
|
-
message:
|
|
245
|
+
message:
|
|
246
|
+
"Couldn't create your account. Check the details and try again.",
|
|
239
247
|
comment: "@context: Error toast when account creation fails",
|
|
240
248
|
}),
|
|
241
249
|
),
|
|
@@ -40,7 +40,8 @@ const SigninContent: FC<{
|
|
|
40
40
|
{demoEmail && demoPassword && (
|
|
41
41
|
<p class="text-muted-foreground text-sm mb-4">
|
|
42
42
|
{t({
|
|
43
|
-
message:
|
|
43
|
+
message:
|
|
44
|
+
"Demo credentials are pre-filled — hit Sign In to continue.",
|
|
44
45
|
comment:
|
|
45
46
|
"@context: Hint shown on signin page when demo credentials are pre-filled",
|
|
46
47
|
})}
|
|
@@ -110,9 +111,9 @@ signinRoutes.get("/signin", async (c) => {
|
|
|
110
111
|
const isReset = c.req.query("reset") !== undefined;
|
|
111
112
|
let toast: { message: string } | undefined;
|
|
112
113
|
if (isSetup) {
|
|
113
|
-
toast = { message: "Account created
|
|
114
|
+
toast = { message: "Account created. Sign in to get started." };
|
|
114
115
|
} else if (isReset) {
|
|
115
|
-
toast = { message: "Password reset
|
|
116
|
+
toast = { message: "Password reset. Sign in with your new password." };
|
|
116
117
|
}
|
|
117
118
|
|
|
118
119
|
return c.html(
|
|
@@ -132,7 +133,7 @@ signinRoutes.post("/signin", async (c) => {
|
|
|
132
133
|
return dsToast(
|
|
133
134
|
i18n._(
|
|
134
135
|
msg({
|
|
135
|
-
message: "
|
|
136
|
+
message: "Authentication isn't set up. Check your server config.",
|
|
136
137
|
comment:
|
|
137
138
|
"@context: Error toast when authentication system is unavailable",
|
|
138
139
|
}),
|
|
@@ -149,7 +150,8 @@ signinRoutes.post("/signin", async (c) => {
|
|
|
149
150
|
parsed.error.issues[0]?.message ??
|
|
150
151
|
i18n._(
|
|
151
152
|
msg({
|
|
152
|
-
message:
|
|
153
|
+
message:
|
|
154
|
+
"Something doesn't look right. Check the form and try again.",
|
|
153
155
|
comment: "@context: Fallback validation error for sign-in form",
|
|
154
156
|
}),
|
|
155
157
|
);
|
|
@@ -165,12 +167,13 @@ signinRoutes.post("/signin", async (c) => {
|
|
|
165
167
|
headers: c.req.raw.headers,
|
|
166
168
|
});
|
|
167
169
|
|
|
168
|
-
return dsRedirect("/
|
|
170
|
+
return dsRedirect("/", { headers });
|
|
169
171
|
} catch {
|
|
170
172
|
return dsToast(
|
|
171
173
|
i18n._(
|
|
172
174
|
msg({
|
|
173
|
-
message:
|
|
175
|
+
message:
|
|
176
|
+
"Wrong email or password. Check your credentials and try again.",
|
|
174
177
|
comment: "@context: Error toast when sign-in credentials are wrong",
|
|
175
178
|
}),
|
|
176
179
|
),
|
|
@@ -179,21 +182,17 @@ signinRoutes.post("/signin", async (c) => {
|
|
|
179
182
|
}
|
|
180
183
|
});
|
|
181
184
|
|
|
182
|
-
signinRoutes.
|
|
185
|
+
signinRoutes.post("/signout", async (c) => {
|
|
183
186
|
if (c.var.auth) {
|
|
184
187
|
try {
|
|
185
188
|
const res = await c.var.auth.api.signOut({
|
|
186
189
|
headers: c.req.raw.headers,
|
|
187
190
|
asResponse: true,
|
|
188
191
|
});
|
|
189
|
-
|
|
190
|
-
for (const cookie of res.headers.getSetCookie()) {
|
|
191
|
-
redirect.headers.append("Set-Cookie", cookie);
|
|
192
|
-
}
|
|
193
|
-
return redirect;
|
|
192
|
+
return dsRedirect("/", { headers: res.headers });
|
|
194
193
|
} catch {
|
|
195
194
|
// Ignore signout errors
|
|
196
195
|
}
|
|
197
196
|
}
|
|
198
|
-
return
|
|
197
|
+
return dsRedirect("/");
|
|
199
198
|
});
|
package/src/routes/compose.tsx
CHANGED
|
@@ -2,26 +2,19 @@
|
|
|
2
2
|
* Compose Route
|
|
3
3
|
*
|
|
4
4
|
* Handles post creation from the public-site compose dialog.
|
|
5
|
-
*
|
|
5
|
+
* On publish the client reloads the page to pick up the new post.
|
|
6
6
|
* Drafts close the dialog and show a confirmation toast.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
import { Hono
|
|
9
|
+
import { Hono } from "hono";
|
|
10
10
|
import { msg } from "@lingui/core/macro";
|
|
11
|
-
import type { Bindings
|
|
11
|
+
import type { Bindings } from "../types.js";
|
|
12
12
|
import type { AppVariables } from "../types/app-context.js";
|
|
13
13
|
import { requireAuth } from "../middleware/auth.js";
|
|
14
14
|
import { CreatePostSchema } from "../lib/schemas.js";
|
|
15
15
|
import { ValidationError } from "../lib/errors.js";
|
|
16
16
|
import { sse, dsToast } from "../lib/sse.js";
|
|
17
17
|
import { getI18n } from "../i18n/index.js";
|
|
18
|
-
import {
|
|
19
|
-
toPostView,
|
|
20
|
-
toPostViewFromPost,
|
|
21
|
-
createMediaContext,
|
|
22
|
-
} from "../lib/view.js";
|
|
23
|
-
import { buildMediaMap } from "../lib/media-helpers.js";
|
|
24
|
-
import { TimelineItemFromPost } from "../ui/feed/TimelineItem.js";
|
|
25
18
|
|
|
26
19
|
type Env = { Bindings: Bindings; Variables: AppVariables };
|
|
27
20
|
|
|
@@ -48,40 +41,7 @@ const INITIAL_SIGNALS = {
|
|
|
48
41
|
|
|
49
42
|
/** Script fragment that closes the compose dialog and self-removes */
|
|
50
43
|
const CLOSE_DIALOG_SCRIPT =
|
|
51
|
-
"<
|
|
52
|
-
|
|
53
|
-
/** Build a timeline card HTML string for a newly created post */
|
|
54
|
-
async function buildTimelineCard(
|
|
55
|
-
c: Context<Env>,
|
|
56
|
-
post: Post,
|
|
57
|
-
mediaIds: string[] | undefined,
|
|
58
|
-
): Promise<string> {
|
|
59
|
-
const mediaCtx = createMediaContext(c.var.appConfig);
|
|
60
|
-
let postView;
|
|
61
|
-
|
|
62
|
-
if (mediaIds && mediaIds.length > 0) {
|
|
63
|
-
const rawMediaMap = await c.var.services.media.getByPostIds([post.id]);
|
|
64
|
-
const mediaMap = buildMediaMap(
|
|
65
|
-
rawMediaMap,
|
|
66
|
-
mediaCtx.r2PublicUrl,
|
|
67
|
-
mediaCtx.imageTransformUrl,
|
|
68
|
-
mediaCtx.s3PublicUrl,
|
|
69
|
-
);
|
|
70
|
-
postView = toPostView(
|
|
71
|
-
{ ...post, mediaAttachments: mediaMap.get(post.id) ?? [] },
|
|
72
|
-
mediaCtx,
|
|
73
|
-
);
|
|
74
|
-
} else {
|
|
75
|
-
postView = toPostViewFromPost(post, mediaCtx);
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
return (
|
|
79
|
-
<div>
|
|
80
|
-
<TimelineItemFromPost post={postView} />
|
|
81
|
-
<hr class="feed-divider" />
|
|
82
|
-
</div>
|
|
83
|
-
).toString();
|
|
84
|
-
}
|
|
44
|
+
"<div data-init=\"document.getElementById('compose-dialog').close(); el.remove()\"></div>";
|
|
85
45
|
|
|
86
46
|
composeRoutes.post("/", async (c) => {
|
|
87
47
|
const i18n = getI18n(c);
|
|
@@ -94,7 +54,8 @@ composeRoutes.post("/", async (c) => {
|
|
|
94
54
|
result.error.issues[0]?.message ??
|
|
95
55
|
i18n._(
|
|
96
56
|
msg({
|
|
97
|
-
message:
|
|
57
|
+
message:
|
|
58
|
+
"Something doesn't look right. Check the form and try again.",
|
|
98
59
|
comment: "@context: Fallback validation error for compose form",
|
|
99
60
|
}),
|
|
100
61
|
);
|
|
@@ -121,16 +82,26 @@ composeRoutes.post("/", async (c) => {
|
|
|
121
82
|
}
|
|
122
83
|
}
|
|
123
84
|
|
|
124
|
-
const post = await c.var.services.posts.create(
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
85
|
+
const post = await c.var.services.posts.create(
|
|
86
|
+
{
|
|
87
|
+
format: data.format,
|
|
88
|
+
title: data.title || undefined,
|
|
89
|
+
body: data.body || undefined,
|
|
90
|
+
bodyMarkdown: data.bodyMarkdown || undefined,
|
|
91
|
+
status: data.status ?? "published",
|
|
92
|
+
visibility: data.visibility || undefined,
|
|
93
|
+
featured: data.featured,
|
|
94
|
+
url: data.url || undefined,
|
|
95
|
+
quoteText: data.quoteText || undefined,
|
|
96
|
+
rating: data.rating || undefined,
|
|
97
|
+
collectionIds: data.collectionIds,
|
|
98
|
+
replyToId: data.replyToId,
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
maxParagraphs: c.var.appConfig.summaryMaxParagraphs,
|
|
102
|
+
maxChars: c.var.appConfig.summaryMaxChars,
|
|
103
|
+
},
|
|
104
|
+
);
|
|
134
105
|
|
|
135
106
|
// Attach media if provided
|
|
136
107
|
if (data.mediaIds && data.mediaIds.length > 0) {
|
|
@@ -163,8 +134,7 @@ composeRoutes.post("/", async (c) => {
|
|
|
163
134
|
});
|
|
164
135
|
}
|
|
165
136
|
|
|
166
|
-
|
|
167
|
-
return c.json({ status: "published" as const, cardHtml });
|
|
137
|
+
return c.json({ status: "published" as const, permalink: `/${post.slug}` });
|
|
168
138
|
}
|
|
169
139
|
|
|
170
140
|
// ── SSE response mode (used by Datastar) ─────────────────────────
|
|
@@ -186,13 +156,7 @@ composeRoutes.post("/", async (c) => {
|
|
|
186
156
|
});
|
|
187
157
|
}
|
|
188
158
|
|
|
189
|
-
const cardHtml = await buildTimelineCard(c, post, data.mediaIds);
|
|
190
|
-
|
|
191
159
|
return sse(c, async (stream) => {
|
|
192
|
-
await stream.patchElements(cardHtml, {
|
|
193
|
-
mode: "prepend",
|
|
194
|
-
selector: "#timeline-items",
|
|
195
|
-
});
|
|
196
160
|
await stream.patchElements(CLOSE_DIALOG_SCRIPT, {
|
|
197
161
|
mode: "append",
|
|
198
162
|
selector: "body",
|
|
@@ -38,7 +38,7 @@ function createMockFile(
|
|
|
38
38
|
};
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
-
describe("
|
|
41
|
+
describe("Settings - Avatar Upload Logic", () => {
|
|
42
42
|
let db: Database;
|
|
43
43
|
let settingsService: ReturnType<typeof createSettingsService>;
|
|
44
44
|
let mediaService: ReturnType<typeof createMediaService>;
|
|
@@ -57,7 +57,12 @@ describe("Dashboard Settings - Avatar Upload Logic", () => {
|
|
|
57
57
|
|
|
58
58
|
await settingsService.uploadAvatar(
|
|
59
59
|
{ file },
|
|
60
|
-
{
|
|
60
|
+
{
|
|
61
|
+
media: mediaService,
|
|
62
|
+
storage,
|
|
63
|
+
storageProvider: "r2",
|
|
64
|
+
maxFileSizeMB: 500,
|
|
65
|
+
},
|
|
61
66
|
);
|
|
62
67
|
|
|
63
68
|
const avatarKey = await settingsService.get("SITE_AVATAR");
|
|
@@ -72,7 +77,12 @@ describe("Dashboard Settings - Avatar Upload Logic", () => {
|
|
|
72
77
|
|
|
73
78
|
await settingsService.uploadAvatar(
|
|
74
79
|
{ file },
|
|
75
|
-
{
|
|
80
|
+
{
|
|
81
|
+
media: mediaService,
|
|
82
|
+
storage,
|
|
83
|
+
storageProvider: "r2",
|
|
84
|
+
maxFileSizeMB: 500,
|
|
85
|
+
},
|
|
76
86
|
);
|
|
77
87
|
|
|
78
88
|
const mediaList = await mediaService.list();
|
|
@@ -89,7 +99,12 @@ describe("Dashboard Settings - Avatar Upload Logic", () => {
|
|
|
89
99
|
|
|
90
100
|
await settingsService.uploadAvatar(
|
|
91
101
|
{ file, faviconIco: fakeIcoData.buffer },
|
|
92
|
-
{
|
|
102
|
+
{
|
|
103
|
+
media: mediaService,
|
|
104
|
+
storage,
|
|
105
|
+
storageProvider: "r2",
|
|
106
|
+
maxFileSizeMB: 500,
|
|
107
|
+
},
|
|
93
108
|
);
|
|
94
109
|
|
|
95
110
|
const stored = await settingsService.get("SITE_FAVICON_ICO");
|
|
@@ -105,7 +120,12 @@ describe("Dashboard Settings - Avatar Upload Logic", () => {
|
|
|
105
120
|
|
|
106
121
|
await settingsService.uploadAvatar(
|
|
107
122
|
{ file, appleTouchIcon: appleTouchData },
|
|
108
|
-
{
|
|
123
|
+
{
|
|
124
|
+
media: mediaService,
|
|
125
|
+
storage,
|
|
126
|
+
storageProvider: "r2",
|
|
127
|
+
maxFileSizeMB: 500,
|
|
128
|
+
},
|
|
109
129
|
);
|
|
110
130
|
|
|
111
131
|
const stored = await settingsService.get("SITE_FAVICON_APPLE_TOUCH");
|
|
@@ -120,7 +140,12 @@ describe("Dashboard Settings - Avatar Upload Logic", () => {
|
|
|
120
140
|
|
|
121
141
|
await settingsService.uploadAvatar(
|
|
122
142
|
{ file },
|
|
123
|
-
{
|
|
143
|
+
{
|
|
144
|
+
media: mediaService,
|
|
145
|
+
storage,
|
|
146
|
+
storageProvider: "r2",
|
|
147
|
+
maxFileSizeMB: 500,
|
|
148
|
+
},
|
|
124
149
|
);
|
|
125
150
|
|
|
126
151
|
const stored = await settingsService.get("SITE_FAVICON_VERSION");
|
|
@@ -135,19 +160,29 @@ describe("Dashboard Settings - Avatar Upload Logic", () => {
|
|
|
135
160
|
await expect(
|
|
136
161
|
settingsService.uploadAvatar(
|
|
137
162
|
{ file },
|
|
138
|
-
{
|
|
163
|
+
{
|
|
164
|
+
media: mediaService,
|
|
165
|
+
storage,
|
|
166
|
+
storageProvider: "r2",
|
|
167
|
+
maxFileSizeMB: 500,
|
|
168
|
+
},
|
|
139
169
|
),
|
|
140
170
|
).rejects.toThrow("File type not allowed");
|
|
141
171
|
});
|
|
142
172
|
|
|
143
173
|
it("throws ValidationError for oversized file", async () => {
|
|
144
174
|
const storage = createMockStorage();
|
|
145
|
-
const file = createMockFile("big.png", "image/png",
|
|
175
|
+
const file = createMockFile("big.png", "image/png", 501 * 1024 * 1024);
|
|
146
176
|
|
|
147
177
|
await expect(
|
|
148
178
|
settingsService.uploadAvatar(
|
|
149
179
|
{ file },
|
|
150
|
-
{
|
|
180
|
+
{
|
|
181
|
+
media: mediaService,
|
|
182
|
+
storage,
|
|
183
|
+
storageProvider: "r2",
|
|
184
|
+
maxFileSizeMB: 500,
|
|
185
|
+
},
|
|
151
186
|
),
|
|
152
187
|
).rejects.toThrow("File too large");
|
|
153
188
|
});
|