@jant/core 0.3.34 → 0.3.36

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.
Files changed (156) hide show
  1. package/dist/client/assets/module-RjUF93sV.js +716 -0
  2. package/dist/client/assets/native-48B9X9Wg.js +1 -0
  3. package/dist/client/assets/url-8Dj-5CLW.js +1 -0
  4. package/dist/client/client.css +1 -1
  5. package/dist/client/client.js +3109 -2294
  6. package/dist/index.js +3327 -3031
  7. package/package.json +13 -4
  8. package/src/__tests__/helpers/app.ts +1 -1
  9. package/src/__tests__/helpers/db.ts +6 -0
  10. package/src/app.tsx +1 -5
  11. package/src/{lib → client}/avatar-upload.ts +1 -1
  12. package/src/{lib → client}/collection-form-bridge.ts +2 -2
  13. package/src/{ui → client}/components/__tests__/jant-collection-form.test.ts +26 -9
  14. package/src/{ui → client}/components/__tests__/jant-compose-dialog.test.ts +46 -14
  15. package/src/{ui → client}/components/__tests__/jant-compose-editor.test.ts +64 -24
  16. package/src/{ui → client}/components/__tests__/jant-post-form.test.ts +24 -14
  17. package/src/{ui → client}/components/__tests__/jant-settings-general.test.ts +3 -3
  18. package/src/client/components/collection-sidebar-types.ts +45 -0
  19. package/src/{ui → client}/components/collection-types.ts +3 -4
  20. package/src/{ui → client}/components/compose-types.ts +3 -1
  21. package/src/{ui → client}/components/jant-collection-form.ts +301 -182
  22. package/src/client/components/jant-collection-sidebar.ts +801 -0
  23. package/src/{ui → client}/components/jant-compose-dialog.ts +231 -1
  24. package/src/client/components/jant-compose-editor.ts +1249 -0
  25. package/src/client/components/jant-compose-fullscreen.ts +338 -0
  26. package/src/client/components/jant-media-lightbox.ts +257 -0
  27. package/src/{ui → client}/components/jant-nav-manager.ts +143 -84
  28. package/src/{ui → client}/components/jant-post-form.ts +57 -8
  29. package/src/{ui → client}/components/jant-settings-general.ts +2 -2
  30. package/src/{ui → client}/components/nav-manager-types.ts +3 -0
  31. package/src/{ui → client}/components/post-form-template.ts +35 -31
  32. package/src/{ui → client}/components/post-form-types.ts +7 -3
  33. package/src/{lib → client}/compose-bridge.ts +9 -7
  34. package/src/client/lazy-slugify.ts +51 -0
  35. package/src/{lib → client}/media-upload.ts +16 -3
  36. package/src/{lib → client}/nav-manager-bridge.ts +1 -1
  37. package/src/client/page-slug-bridge.ts +42 -0
  38. package/src/{lib → client}/post-form-bridge.ts +2 -2
  39. package/src/{lib → client}/settings-bridge.ts +3 -3
  40. package/src/client/tiptap/bubble-menu.ts +205 -0
  41. package/src/client/tiptap/create-editor.ts +40 -0
  42. package/src/client/tiptap/exitable-marks.ts +73 -0
  43. package/src/client/tiptap/extensions.ts +60 -0
  44. package/src/client/tiptap/image-node.ts +488 -0
  45. package/src/client/tiptap/link-toolbar.ts +371 -0
  46. package/src/client/tiptap/more-break.ts +50 -0
  47. package/src/client/tiptap/paste-image.ts +140 -0
  48. package/src/client/tiptap/slash-commands.ts +328 -0
  49. package/src/{types → client/types}/sortablejs.d.ts +1 -1
  50. package/src/client.ts +24 -17
  51. package/src/db/migrations/0012_add_tiptap_columns.sql +2 -0
  52. package/src/db/migrations/0013_replace_featured_with_visibility.sql +8 -0
  53. package/src/db/schema.ts +6 -1
  54. package/src/i18n/locales/en.po +641 -215
  55. package/src/i18n/locales/en.ts +1 -1
  56. package/src/i18n/locales/zh-Hans.po +642 -204
  57. package/src/i18n/locales/zh-Hans.ts +1 -1
  58. package/src/i18n/locales/zh-Hant.po +642 -204
  59. package/src/i18n/locales/zh-Hant.ts +1 -1
  60. package/src/lib/__tests__/resolve-config.test.ts +2 -2
  61. package/src/lib/__tests__/schemas.test.ts +9 -6
  62. package/src/lib/__tests__/url.test.ts +2 -2
  63. package/src/lib/__tests__/view.test.ts +9 -9
  64. package/src/lib/emoji-catalog.ts +146 -0
  65. package/src/lib/feed.ts +1 -1
  66. package/src/lib/media-helpers.ts +10 -9
  67. package/src/lib/render.tsx +4 -3
  68. package/src/lib/resolve-config.ts +8 -1
  69. package/src/lib/schemas.ts +2 -3
  70. package/src/lib/summary.ts +92 -0
  71. package/src/lib/timeline.ts +2 -0
  72. package/src/lib/tiptap-render.ts +196 -0
  73. package/src/lib/upload.ts +97 -9
  74. package/src/lib/url.ts +7 -23
  75. package/src/lib/view.ts +33 -19
  76. package/src/middleware/error-handler.ts +3 -3
  77. package/src/preset.css +38 -0
  78. package/src/routes/api/collections.ts +20 -3
  79. package/src/routes/api/posts.ts +48 -33
  80. package/src/routes/api/upload.ts +7 -5
  81. package/src/routes/auth/reset.tsx +5 -4
  82. package/src/routes/auth/setup.tsx +26 -11
  83. package/src/routes/auth/signin.tsx +10 -7
  84. package/src/routes/compose.tsx +20 -11
  85. package/src/routes/dash/__tests__/settings-avatar.test.ts +43 -8
  86. package/src/routes/dash/index.tsx +7 -1
  87. package/src/routes/dash/media.tsx +3 -0
  88. package/src/routes/dash/pages.tsx +8 -2
  89. package/src/routes/dash/posts.tsx +6 -2
  90. package/src/routes/dash/redirects.tsx +15 -9
  91. package/src/routes/dash/settings.tsx +336 -32
  92. package/src/routes/feed/__tests__/rss.test.ts +245 -6
  93. package/src/routes/feed/rss.ts +70 -6
  94. package/src/routes/pages/__tests__/featured.test.ts +6 -7
  95. package/src/routes/pages/archive.tsx +11 -7
  96. package/src/routes/pages/collection.tsx +32 -15
  97. package/src/routes/pages/collections.tsx +11 -2
  98. package/src/routes/pages/featured.tsx +1 -1
  99. package/src/routes/pages/home.tsx +1 -1
  100. package/src/services/__tests__/post.test.ts +124 -33
  101. package/src/services/__tests__/settings.test.ts +3 -3
  102. package/src/services/page.ts +16 -3
  103. package/src/services/post.ts +96 -37
  104. package/src/services/search.ts +4 -2
  105. package/src/services/settings.ts +6 -2
  106. package/src/styles/components.css +240 -60
  107. package/src/styles/tokens.css +10 -0
  108. package/src/styles/ui.css +1157 -81
  109. package/src/types/bindings.ts +5 -0
  110. package/src/types/config.ts +23 -1
  111. package/src/types/constants.ts +3 -0
  112. package/src/types/entities.ts +9 -2
  113. package/src/types/operations.ts +9 -3
  114. package/src/types/props.ts +3 -3
  115. package/src/types/views.ts +3 -2
  116. package/src/ui/compose/ComposeDialog.tsx +24 -7
  117. package/src/ui/dash/PageForm.tsx +2 -0
  118. package/src/ui/dash/PostList.tsx +5 -5
  119. package/src/ui/dash/StatusBadge.tsx +13 -5
  120. package/src/ui/dash/appearance/AdvancedContent.tsx +52 -61
  121. package/src/ui/dash/appearance/ColorThemeContent.tsx +30 -35
  122. package/src/ui/dash/appearance/FontThemeContent.tsx +65 -73
  123. package/src/ui/dash/appearance/NavigationContent.tsx +107 -96
  124. package/src/ui/dash/media/MediaListContent.tsx +9 -4
  125. package/src/ui/dash/media/ViewMediaContent.tsx +2 -2
  126. package/src/ui/dash/pages/PagesContent.tsx +2 -1
  127. package/src/ui/dash/posts/PostForm.tsx +19 -7
  128. package/src/ui/dash/settings/AccountContent.tsx +133 -138
  129. package/src/ui/dash/settings/AvatarContent.tsx +70 -0
  130. package/src/ui/dash/settings/GeneralContent.tsx +3 -62
  131. package/src/ui/dash/settings/SettingsRootContent.tsx +236 -0
  132. package/src/ui/layouts/DashLayout.tsx +157 -75
  133. package/src/ui/layouts/SiteLayout.tsx +13 -13
  134. package/src/ui/pages/ArchivePage.tsx +10 -7
  135. package/src/ui/pages/CollectionPage.tsx +6 -35
  136. package/src/ui/pages/CollectionsPage.tsx +2 -1
  137. package/src/ui/pages/FeaturedPage.tsx +2 -1
  138. package/src/ui/pages/HomePage.tsx +1 -1
  139. package/src/ui/pages/SearchPage.tsx +1 -1
  140. package/src/ui/shared/CollectionsSidebar.tsx +228 -3
  141. package/src/ui/shared/MediaGallery.tsx +179 -41
  142. package/src/lib/collections-reorder.ts +0 -28
  143. package/src/routes/dash/appearance.tsx +0 -240
  144. package/src/routes/dash/collections.tsx +0 -211
  145. package/src/ui/components/jant-compose-editor.ts +0 -814
  146. package/src/ui/dash/appearance/AppearanceNav.tsx +0 -60
  147. package/src/ui/dash/collections/CollectionForm.tsx +0 -166
  148. package/src/ui/dash/collections/CollectionsListContent.tsx +0 -146
  149. package/src/ui/dash/collections/IconPickerGrid.tsx +0 -50
  150. package/src/ui/dash/collections/ViewCollectionContent.tsx +0 -103
  151. package/src/ui/dash/settings/SettingsNav.tsx +0 -52
  152. /package/src/{ui → client}/components/__tests__/jant-settings-avatar.test.ts +0 -0
  153. /package/src/{ui → client}/components/jant-settings-avatar.ts +0 -0
  154. /package/src/{ui → client}/components/settings-types.ts +0 -0
  155. /package/src/{lib → client}/image-processor.ts +0 -0
  156. /package/src/{lib → client}/toast.ts +0 -0
@@ -4,7 +4,6 @@
4
4
 
5
5
  import { useLingui } from "@lingui/react/macro";
6
6
  import type { FontTheme } from "../../font-themes.js";
7
- import { AppearanceNav } from "./AppearanceNav.js";
8
7
 
9
8
  export function FontThemeContent({
10
9
  fontThemes,
@@ -16,80 +15,73 @@ export function FontThemeContent({
16
15
  const { t } = useLingui();
17
16
 
18
17
  return (
19
- <>
20
- <AppearanceNav currentTab="fonts" />
21
-
22
- <div
23
- data-signals={JSON.stringify({ fontTheme: currentFontThemeId }).replace(
24
- /</g,
25
- "\\u003c",
26
- )}
27
- data-on:change="@post('/dash/appearance/font-theme')"
28
- class="max-w-3xl"
29
- >
30
- <fieldset>
31
- <legend class="text-lg font-semibold">
32
- {t({
33
- message: "Font theme",
34
- comment: "@context: Appearance settings heading for font theme",
35
- })}
36
- </legend>
37
- <p class="text-sm text-muted-foreground mb-4">
38
- {t({
39
- message:
40
- "Choose a font pairing for your site. All options use system fonts for fast loading.",
41
- comment: "@context: Font theme settings description",
42
- })}
43
- </p>
44
- <div class="flex flex-col gap-2">
45
- {fontThemes.map((ft) => (
46
- <label
47
- key={ft.id}
48
- class={`flex items-start gap-3 p-3 rounded-lg border cursor-pointer transition-colors ${ft.id === currentFontThemeId ? "border-primary" : "border-border"}`}
49
- data-class:border-primary={`$fontTheme === '${ft.id}'`}
50
- data-class:border-border={`$fontTheme !== '${ft.id}'`}
51
- >
52
- <input
53
- type="radio"
54
- name="fontTheme"
55
- value={ft.id}
56
- data-bind="fontTheme"
57
- checked={ft.id === currentFontThemeId || undefined}
58
- class="mt-1"
59
- />
60
- <div>
61
- <div class="font-medium">{t(ft.name)}</div>
62
- <div class="text-sm text-muted-foreground">
63
- {t(ft.description)}
18
+ <div
19
+ data-signals={JSON.stringify({ fontTheme: currentFontThemeId }).replace(
20
+ /</g,
21
+ "\\u003c",
22
+ )}
23
+ data-on:change="@post('/dash/settings/font-theme')"
24
+ class="max-w-3xl"
25
+ >
26
+ <fieldset>
27
+ <legend class="text-lg font-semibold">
28
+ {t({
29
+ message: "Font theme",
30
+ comment: "@context: Appearance settings heading for font theme",
31
+ })}
32
+ </legend>
33
+ <p class="text-sm text-muted-foreground mb-4">
34
+ {t({
35
+ message:
36
+ "Choose a font pairing for your site. All options use system fonts for fast loading.",
37
+ comment: "@context: Font theme settings description",
38
+ })}
39
+ </p>
40
+ <div class="flex flex-col gap-2">
41
+ {fontThemes.map((ft) => (
42
+ <label
43
+ key={ft.id}
44
+ class={`flex items-start gap-3 p-3 rounded-lg border cursor-pointer transition-colors ${ft.id === currentFontThemeId ? "border-primary" : "border-border"}`}
45
+ data-class:border-primary={`$fontTheme === '${ft.id}'`}
46
+ data-class:border-border={`$fontTheme !== '${ft.id}'`}
47
+ >
48
+ <input
49
+ type="radio"
50
+ name="fontTheme"
51
+ value={ft.id}
52
+ data-bind="fontTheme"
53
+ checked={ft.id === currentFontThemeId || undefined}
54
+ class="mt-1"
55
+ />
56
+ <div>
57
+ <div class="font-medium">{t(ft.name)}</div>
58
+ <div class="text-sm text-muted-foreground">
59
+ {t(ft.description)}
60
+ </div>
61
+ <div class="mt-1 text-sm leading-relaxed">
62
+ <div
63
+ class="font-semibold"
64
+ style={`font-family:${ft.headingFontFamily}`}
65
+ >
66
+ {t({
67
+ message: "The quick brown fox jumps over the lazy dog.",
68
+ comment:
69
+ "@context: Font theme preview sentence for headings",
70
+ })}
64
71
  </div>
65
- <div class="mt-1 text-sm leading-relaxed">
66
- <div
67
- class="font-semibold"
68
- style={`font-family:${ft.headingFontFamily}`}
69
- >
70
- {t({
71
- message: "The quick brown fox jumps over the lazy dog.",
72
- comment:
73
- "@context: Font theme preview sentence for headings",
74
- })}
75
- </div>
76
- <div
77
- class="mt-2"
78
- style={`font-family:${ft.bodyFontFamily}`}
79
- >
80
- {t({
81
- message: "The quick brown fox jumps over the lazy dog.",
82
- comment:
83
- "@context: Font theme preview sentence for body text",
84
- })}
85
- </div>
72
+ <div class="mt-2" style={`font-family:${ft.bodyFontFamily}`}>
73
+ {t({
74
+ message: "The quick brown fox jumps over the lazy dog.",
75
+ comment:
76
+ "@context: Font theme preview sentence for body text",
77
+ })}
86
78
  </div>
87
79
  </div>
88
- </label>
89
- ))}
90
- </div>
91
- </fieldset>
92
- </div>
93
- </>
80
+ </div>
81
+ </label>
82
+ ))}
83
+ </div>
84
+ </fieldset>
85
+ </div>
94
86
  );
95
87
  }
@@ -8,9 +8,7 @@ import { SYSTEM_NAV_KEYS } from "../../../types.js";
8
8
  import type {
9
9
  NavManagerLabels,
10
10
  SystemNavConfig,
11
- } from "../../components/nav-manager-types.js";
12
- import { AppearanceNav } from "./AppearanceNav.js";
13
-
11
+ } from "../../../client/components/nav-manager-types.js";
14
12
  // =============================================================================
15
13
  // System descriptions (used to build the config passed to the Lit component)
16
14
  // =============================================================================
@@ -69,7 +67,7 @@ export function NavigationContent({
69
67
 
70
68
  const labels: NavManagerLabels = {
71
69
  preview: t({
72
- message: "Preview",
70
+ message: "Navigation Preview",
73
71
  comment: "@context: Label for nav preview section",
74
72
  }),
75
73
  navigationItems: t({
@@ -121,11 +119,11 @@ export function NavigationContent({
121
119
  comment: "@context: Error toast when nav label is empty",
122
120
  }),
123
121
  saveFailed: t({
124
- message: "Failed to save. Please try again.",
122
+ message: "Couldn't save. Try again in a moment.",
125
123
  comment: "@context: Error toast when nav save fails",
126
124
  }),
127
125
  deleteFailed: t({
128
- message: "Failed to delete. Please try again.",
126
+ message: "Couldn't delete. Try again in a moment.",
129
127
  comment: "@context: Error toast when nav delete fails",
130
128
  }),
131
129
  systemLinks: t({
@@ -154,7 +152,7 @@ export function NavigationContent({
154
152
  comment: "@context: Placeholder for page search input in combobox",
155
153
  }),
156
154
  noPagesFound: t({
157
- message: "No pages found.",
155
+ message: "No matching pages.",
158
156
  comment: "@context: Empty state when page search has no results",
159
157
  }),
160
158
  addLink: t({
@@ -169,20 +167,34 @@ export function NavigationContent({
169
167
  message: "All pages are already in navigation.",
170
168
  comment: "@context: Message when no pages available to add",
171
169
  }),
170
+ createPage: t({
171
+ message: "Create page",
172
+ comment: "@context: Link at bottom of page combobox to create a new page",
173
+ }),
172
174
  urlPlaceholder: "/archive or https://...",
173
175
  maxVisibleLinks: t({
174
- message: "Max visible links",
176
+ message: "Links shown in header",
175
177
  comment: "@context: Label for max visible nav links number input",
176
178
  }),
179
+ maxVisibleLinksDescription: t({
180
+ message: "The rest will be tucked into a ··· menu",
181
+ comment:
182
+ "@context: Description for max visible nav links, explains overflow behavior",
183
+ }),
177
184
  maxVisibleSaved: t({
178
185
  message: "Max visible links saved",
179
186
  comment: "@context: Toast after saving max visible nav links setting",
180
187
  }),
181
188
  useFeaturedAsDefault: t({
182
- message: "Use Featured as default home view",
189
+ message: "Open with Featured posts",
183
190
  comment:
184
191
  "@context: Switch label for setting featured posts as default homepage",
185
192
  }),
193
+ useFeaturedAsDefaultDescription: t({
194
+ message: "When off, visitors see your latest posts first",
195
+ comment:
196
+ "@context: Description for featured default toggle, explains what happens when off",
197
+ }),
186
198
  homeViewSaved: t({
187
199
  message: "Home view saved",
188
200
  comment: "@context: Toast after saving home default view setting",
@@ -205,98 +217,97 @@ export function NavigationContent({
205
217
  JSON.stringify(data).replace(/</g, "\\u003c");
206
218
 
207
219
  return (
208
- <>
209
- <AppearanceNav currentTab="navigation" />
210
-
211
- <div class="max-w-3xl flex flex-col gap-8">
212
- <jant-nav-manager
213
- items={escapeJson(itemsData)}
214
- labels={escapeJson(labels)}
215
- system-nav-items={escapeJson(systemNavData)}
216
- available-pages={escapeJson(pagesData)}
217
- site-name={siteName}
218
- max-visible={headerNavMaxVisible}
219
- home-default-view={homeDefaultView}
220
- >
221
- {/* SSR fallback: static preview until JS hydrates */}
222
- <div class="border rounded-lg">
223
- <p class="text-xs text-muted-foreground px-4 pt-3">
220
+ <div class="max-w-3xl flex flex-col gap-8">
221
+ <jant-nav-manager
222
+ items={escapeJson(itemsData)}
223
+ labels={escapeJson(labels)}
224
+ system-nav-items={escapeJson(systemNavData)}
225
+ available-pages={escapeJson(pagesData)}
226
+ site-name={siteName}
227
+ max-visible={headerNavMaxVisible}
228
+ home-default-view={homeDefaultView}
229
+ >
230
+ {/* SSR fallback: static preview until JS hydrates */}
231
+ <div class="nav-preview">
232
+ <div class="nav-preview-chrome">
233
+ <div class="nav-preview-dots">
234
+ <span />
235
+ <span />
236
+ <span />
237
+ </div>
238
+ <span class="nav-preview-label">
224
239
  {t({
225
- message: "Preview",
240
+ message: "Navigation Preview",
226
241
  comment: "@context: Label for nav preview section",
227
242
  })}
228
- </p>
229
- <div class="px-5 py-3">
230
- <div class="site-header-top">
231
- <a href="/" class="site-logo">
232
- {siteName}
233
- </a>
234
- <div class="site-header-right">
235
- {navItems.length > 0 && (
236
- <nav class="site-header-nav">
237
- {navItems.slice(0, headerNavMaxVisible).map((item) => (
238
- <a
239
- key={item.id}
240
- href={item.url}
241
- class="site-header-link"
242
- >
243
- {item.label}
244
- </a>
245
- ))}
246
- {navItems.length > headerNavMaxVisible && (
247
- <span class="text-muted-foreground">…</span>
248
- )}
249
- </nav>
250
- )}
251
- <span class="site-header-search" aria-hidden="true">
252
- <svg
253
- xmlns="http://www.w3.org/2000/svg"
254
- width="16"
255
- height="16"
256
- viewBox="0 0 24 24"
257
- fill="none"
258
- stroke="currentColor"
259
- stroke-width="2"
260
- stroke-linecap="round"
261
- stroke-linejoin="round"
262
- >
263
- <circle cx="11" cy="11" r="8" />
264
- <path d="m21 21-4.35-4.35" />
265
- </svg>
266
- </span>
267
- </div>
268
- </div>
269
- <nav class="site-browse-nav">
270
- <span class="site-browse-link site-browse-link-active">
271
- {homeDefaultView === "featured"
272
- ? t({
273
- message: "Featured",
274
- comment: "@context: Browse filter label",
275
- })
276
- : t({
277
- message: "Latest",
278
- comment: "@context: Browse filter label",
279
- })}
280
- </span>
281
- <span class="site-browse-sep" aria-hidden="true">
282
- /
283
- </span>
284
- <span class="site-browse-link">
285
- {homeDefaultView === "featured"
286
- ? t({
287
- message: "Latest",
288
- comment: "@context: Browse filter label",
289
- })
290
- : t({
291
- message: "Featured",
292
- comment: "@context: Browse filter label",
293
- })}
243
+ </span>
244
+ </div>
245
+ <div class="nav-preview-content">
246
+ <div class="site-header-top">
247
+ <a href="/" class="site-logo">
248
+ {siteName}
249
+ </a>
250
+ <div class="site-header-right">
251
+ {navItems.length > 0 && (
252
+ <nav class="site-header-nav">
253
+ {navItems.slice(0, headerNavMaxVisible).map((item) => (
254
+ <a key={item.id} href={item.url} class="site-header-link">
255
+ {item.label}
256
+ </a>
257
+ ))}
258
+ {navItems.length > headerNavMaxVisible && (
259
+ <span class="text-muted-foreground">…</span>
260
+ )}
261
+ </nav>
262
+ )}
263
+ <span class="site-header-search" aria-hidden="true">
264
+ <svg
265
+ xmlns="http://www.w3.org/2000/svg"
266
+ width="16"
267
+ height="16"
268
+ viewBox="0 0 24 24"
269
+ fill="none"
270
+ stroke="currentColor"
271
+ stroke-width="2"
272
+ stroke-linecap="round"
273
+ stroke-linejoin="round"
274
+ >
275
+ <circle cx="11" cy="11" r="8" />
276
+ <path d="m21 21-4.35-4.35" />
277
+ </svg>
294
278
  </span>
295
- </nav>
279
+ </div>
296
280
  </div>
281
+ <nav class="site-browse-nav">
282
+ <span class="site-browse-link site-browse-link-active">
283
+ {homeDefaultView === "featured"
284
+ ? t({
285
+ message: "Featured",
286
+ comment: "@context: Browse filter label",
287
+ })
288
+ : t({
289
+ message: "Latest",
290
+ comment: "@context: Browse filter label",
291
+ })}
292
+ </span>
293
+ <span class="site-browse-sep" aria-hidden="true">
294
+ /
295
+ </span>
296
+ <span class="site-browse-link">
297
+ {homeDefaultView === "featured"
298
+ ? t({
299
+ message: "Latest",
300
+ comment: "@context: Browse filter label",
301
+ })
302
+ : t({
303
+ message: "Featured",
304
+ comment: "@context: Browse filter label",
305
+ })}
306
+ </span>
307
+ </nav>
297
308
  </div>
298
- </jant-nav-manager>
299
- </div>
300
- </>
309
+ </div>
310
+ </jant-nav-manager>
311
+ </div>
301
312
  );
302
313
  }
@@ -10,6 +10,7 @@ import {
10
10
  getImageUrl,
11
11
  getPublicUrlForProvider,
12
12
  } from "../../../lib/image.js";
13
+ import { UPLOAD_ACCEPT } from "../../../lib/upload.js";
13
14
 
14
15
  function formatSize(bytes: number): string {
15
16
  if (bytes < 1024) return `${bytes} B`;
@@ -84,11 +85,13 @@ export function MediaListContent({
84
85
  r2PublicUrl,
85
86
  imageTransformUrl,
86
87
  s3PublicUrl,
88
+ uploadMaxFileSize,
87
89
  }: {
88
90
  mediaList: Media[];
89
91
  r2PublicUrl?: string;
90
92
  imageTransformUrl?: string;
91
93
  s3PublicUrl?: string;
94
+ uploadMaxFileSize?: number;
92
95
  }) {
93
96
  const { t } = useLingui();
94
97
 
@@ -105,7 +108,7 @@ export function MediaListContent({
105
108
  comment: "@context: Button to upload media file",
106
109
  });
107
110
  const errorText = t({
108
- message: "Upload failed. Please try again.",
111
+ message: "Upload didn't go through. Try again in a moment.",
109
112
  comment: "@context: Upload error message",
110
113
  });
111
114
 
@@ -131,8 +134,9 @@ export function MediaListContent({
131
134
  <input
132
135
  type="file"
133
136
  class="hidden"
134
- accept="image/*"
137
+ accept={UPLOAD_ACCEPT}
135
138
  data-media-upload
139
+ data-max-file-size={uploadMaxFileSize ?? 500}
136
140
  data-text-processing={processingText}
137
141
  data-text-uploading={uploadingText}
138
142
  data-text-error={errorText}
@@ -146,7 +150,7 @@ export function MediaListContent({
146
150
  <p>
147
151
  {t({
148
152
  message:
149
- "Images are automatically optimized: resized to max 1920px, converted to WebP, and metadata stripped.",
153
+ "Images are automatically optimized (resized, converted to WebP). Video, audio, and PDF files are uploaded as-is (max 200MB).",
150
154
  comment:
151
155
  "@context: Media upload instructions - auto optimization",
152
156
  })}
@@ -160,7 +164,8 @@ export function MediaListContent({
160
164
  <div id="empty-state">
161
165
  <EmptyState
162
166
  message={t({
163
- message: "No media uploaded yet.",
167
+ message:
168
+ "Your media library is empty. Upload your first file to get started.",
164
169
  comment: "@context: Empty state message when no media exists",
165
170
  })}
166
171
  />
@@ -178,10 +178,10 @@ export function ViewMediaContent({
178
178
  comment: "@context: Button to delete media",
179
179
  })}
180
180
  formAction={`/dash/media/${media.id}/delete`}
181
- confirmMessage="Are you sure you want to delete this media?"
181
+ confirmMessage="Delete this file permanently?"
182
182
  description={t({
183
183
  message:
184
- "Deleting this media will remove it permanently from storage.",
184
+ "This file will be permanently removed from storage. Posts using it will show a broken link.",
185
185
  comment: "@context: Warning message before deleting media",
186
186
  })}
187
187
  />
@@ -28,7 +28,8 @@ export function PagesContent({ pages }: { pages: Page[] }) {
28
28
  {pages.length === 0 ? (
29
29
  <p class="text-sm text-muted-foreground py-4">
30
30
  {t({
31
- message: "No pages yet. Create your first page to get started.",
31
+ message:
32
+ "No pages yet. Create one to add static content to your site.",
32
33
  comment: "@context: Empty state for pages list",
33
34
  })}
34
35
  </p>
@@ -100,7 +100,7 @@ export const PostForm: FC<PostFormProps> = ({
100
100
  comment: "@context: Remove media attachment button",
101
101
  }),
102
102
  mediaEmptyLabel: t({
103
- message: "No media selected yet.",
103
+ message: "No media attached.",
104
104
  comment: "@context: Post form media empty state",
105
105
  }),
106
106
  statusLabel: t({
@@ -115,9 +115,21 @@ export const PostForm: FC<PostFormProps> = ({
115
115
  message: "Draft",
116
116
  comment: "@context: Post status option",
117
117
  }),
118
- featuredLabel: t({
118
+ visibilityLabel: t({
119
+ message: "Visibility",
120
+ comment: "@context: Post form field - post visibility",
121
+ }),
122
+ visibilityListed: t({
123
+ message: "Listed",
124
+ comment: "@context: Visibility option - appears everywhere",
125
+ }),
126
+ visibilityFeatured: t({
119
127
  message: "Featured",
120
- comment: "@context: Post form checkbox - mark as featured",
128
+ comment: "@context: Visibility option - highlighted on featured page",
129
+ }),
130
+ visibilityUnlisted: t({
131
+ message: "Unlisted",
132
+ comment: "@context: Visibility option - hidden from feeds",
121
133
  }),
122
134
  pinnedLabel: t({
123
135
  message: "Pinned",
@@ -154,15 +166,15 @@ export const PostForm: FC<PostFormProps> = ({
154
166
  }),
155
167
  submitSuccessMessage: isEdit
156
168
  ? t({
157
- message: "Post updated successfully.",
169
+ message: "Post updated.",
158
170
  comment: "@context: Toast after editing post",
159
171
  })
160
172
  : t({
161
- message: "Post published successfully.",
173
+ message: "Post published.",
162
174
  comment: "@context: Toast after creating post",
163
175
  }),
164
176
  submitErrorMessage: t({
165
- message: "Failed to save post. Please try again.",
177
+ message: "Couldn't save your post. Try again in a moment.",
166
178
  comment: "@context: Toast when post save fails",
167
179
  }),
168
180
  }).replace(/</g, "\\u003c");
@@ -174,7 +186,7 @@ export const PostForm: FC<PostFormProps> = ({
174
186
  url: post?.url ?? "",
175
187
  quoteText: post?.quoteText ?? "",
176
188
  status: post?.status ?? "published",
177
- featured: post?.featured === 1,
189
+ visibility: post?.visibility ?? "listed",
178
190
  pinned: post?.pinned === 1,
179
191
  rating: post?.rating ?? 0,
180
192
  collectionIds: postCollectionIds,