@jant/core 0.3.35 → 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 +3026 -2778
  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 +7 -7
  93. package/src/routes/feed/rss.ts +8 -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
@@ -0,0 +1,236 @@
1
+ /**
2
+ * Settings root page — iOS-style grouped list linking to sub-pages
3
+ */
4
+
5
+ import { useLingui } from "@lingui/react/macro";
6
+
7
+ /** Chevron right icon shared by all rows */
8
+ function ChevronRight() {
9
+ return (
10
+ <svg
11
+ class="settings-item-chevron"
12
+ xmlns="http://www.w3.org/2000/svg"
13
+ width="16"
14
+ height="16"
15
+ viewBox="0 0 24 24"
16
+ fill="none"
17
+ stroke="currentColor"
18
+ stroke-width="2"
19
+ stroke-linecap="round"
20
+ stroke-linejoin="round"
21
+ >
22
+ <path d="m9 18 6-6-6-6" />
23
+ </svg>
24
+ );
25
+ }
26
+
27
+ function SettingsItem({
28
+ href,
29
+ icon,
30
+ color,
31
+ name,
32
+ description,
33
+ }: {
34
+ href: string;
35
+ icon: string;
36
+ color: string;
37
+ name: string;
38
+ description: string;
39
+ }) {
40
+ return (
41
+ <a href={href} class="settings-item">
42
+ <span class="settings-item-icon" style={`background-color:${color}`}>
43
+ <span dangerouslySetInnerHTML={{ __html: icon }} />
44
+ </span>
45
+ <span class="settings-item-text">
46
+ <span class="settings-item-name">{name}</span>
47
+ <span class="settings-item-desc">{description}</span>
48
+ </span>
49
+ <ChevronRight />
50
+ </a>
51
+ );
52
+ }
53
+
54
+ // Lucide icon SVG paths (16x16, stroke-based)
55
+ const ICONS = {
56
+ settings: `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12.22 2h-.44a2 2 0 0 0-2 2v.18a2 2 0 0 1-1 1.73l-.43.25a2 2 0 0 1-2 0l-.15-.08a2 2 0 0 0-2.73.73l-.22.38a2 2 0 0 0 .73 2.73l.15.1a2 2 0 0 1 1 1.72v.51a2 2 0 0 1-1 1.74l-.15.09a2 2 0 0 0-.73 2.73l.22.38a2 2 0 0 0 2.73.73l.15-.08a2 2 0 0 1 2 0l.43.25a2 2 0 0 1 1 1.73V20a2 2 0 0 0 2 2h.44a2 2 0 0 0 2-2v-.18a2 2 0 0 1 1-1.73l.43-.25a2 2 0 0 1 2 0l.15.08a2 2 0 0 0 2.73-.73l.22-.39a2 2 0 0 0-.73-2.73l-.15-.08a2 2 0 0 1-1-1.74v-.5a2 2 0 0 1 1-1.74l.15-.09a2 2 0 0 0 .73-2.73l-.22-.38a2 2 0 0 0-2.73-.73l-.15.08a2 2 0 0 1-2 0l-.43-.25a2 2 0 0 1-1-1.73V4a2 2 0 0 0-2-2z"/><circle cx="12" cy="12" r="3"/></svg>`,
57
+ image: `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="18" height="18" x="3" y="3" rx="2" ry="2"/><circle cx="9" cy="9" r="2"/><path d="m21 15-3.086-3.086a2 2 0 0 0-2.828 0L6 21"/></svg>`,
58
+ menu: `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="4" x2="20" y1="12" y2="12"/><line x1="4" x2="20" y1="6" y2="6"/><line x1="4" x2="20" y1="18" y2="18"/></svg>`,
59
+ palette: `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="13.5" cy="6.5" r=".5" fill="currentColor"/><circle cx="17.5" cy="10.5" r=".5" fill="currentColor"/><circle cx="8.5" cy="7.5" r=".5" fill="currentColor"/><circle cx="6.5" cy="12.5" r=".5" fill="currentColor"/><path d="M12 2C6.5 2 2 6.5 2 12s4.5 10 10 10c.926 0 1.648-.746 1.648-1.688 0-.437-.18-.835-.437-1.125-.29-.289-.438-.652-.438-1.125a1.64 1.64 0 0 1 1.668-1.668h1.996c3.051 0 5.555-2.503 5.555-5.554C21.965 6.012 17.461 2 12 2z"/></svg>`,
60
+ type: `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="4 7 4 4 20 4 20 7"/><line x1="9" x2="15" y1="20" y2="20"/><line x1="12" x2="12" y1="4" y2="20"/></svg>`,
61
+ code: `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="16 18 22 12 16 6"/><polyline points="8 6 2 12 8 18"/></svg>`,
62
+ arrowRightLeft: `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m16 3 4 4-4 4"/><path d="M20 7H4"/><path d="m8 21-4-4 4-4"/><path d="M4 17h16"/></svg>`,
63
+ user: `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M19 21v-2a4 4 0 0 0-4-4H9a4 4 0 0 0-4 4v2"/><circle cx="12" cy="7" r="4"/></svg>`,
64
+ };
65
+
66
+ // oklch-based colors for icon backgrounds
67
+ const COLORS = {
68
+ blue: "oklch(0.55 0.2 250)",
69
+ purple: "oklch(0.55 0.2 300)",
70
+ green: "oklch(0.55 0.18 155)",
71
+ orange: "oklch(0.6 0.18 55)",
72
+ pink: "oklch(0.6 0.2 350)",
73
+ indigo: "oklch(0.5 0.2 275)",
74
+ amber: "oklch(0.6 0.16 75)",
75
+ gray: "oklch(0.55 0.01 250)",
76
+ };
77
+
78
+ export function SettingsRootContent() {
79
+ const { t } = useLingui();
80
+
81
+ return (
82
+ <div class="settings-root">
83
+ {/* Site */}
84
+ <div>
85
+ <div class="settings-group-label">
86
+ {t({
87
+ message: "Site",
88
+ comment: "@context: Settings group label for site settings",
89
+ })}
90
+ </div>
91
+ <div class="settings-group">
92
+ <SettingsItem
93
+ href="/dash/settings/general"
94
+ icon={ICONS.settings}
95
+ color={COLORS.blue}
96
+ name={t({
97
+ message: "General",
98
+ comment: "@context: Settings item — general settings",
99
+ })}
100
+ description={t({
101
+ message: "Name, description, language",
102
+ comment: "@context: Settings item description for general",
103
+ })}
104
+ />
105
+ </div>
106
+ </div>
107
+
108
+ {/* Design */}
109
+ <div>
110
+ <div class="settings-group-label">
111
+ {t({
112
+ message: "Design",
113
+ comment: "@context: Settings group label for design settings",
114
+ })}
115
+ </div>
116
+ <div class="settings-group">
117
+ <SettingsItem
118
+ href="/dash/settings/avatar"
119
+ icon={ICONS.image}
120
+ color={COLORS.purple}
121
+ name={t({
122
+ message: "Avatar",
123
+ comment: "@context: Settings item — avatar settings",
124
+ })}
125
+ description={t({
126
+ message: "Favicon and header icon",
127
+ comment: "@context: Settings item description for avatar",
128
+ })}
129
+ />
130
+ <SettingsItem
131
+ href="/dash/settings/navigation"
132
+ icon={ICONS.menu}
133
+ color={COLORS.green}
134
+ name={t({
135
+ message: "Navigation",
136
+ comment: "@context: Settings item — navigation settings",
137
+ })}
138
+ description={t({
139
+ message: "Header links, featured",
140
+ comment: "@context: Settings item description for navigation",
141
+ })}
142
+ />
143
+ <SettingsItem
144
+ href="/dash/settings/color-theme"
145
+ icon={ICONS.palette}
146
+ color={COLORS.orange}
147
+ name={t({
148
+ message: "Color Theme",
149
+ comment: "@context: Settings item — color theme settings",
150
+ })}
151
+ description={t({
152
+ message: "Color theme",
153
+ comment: "@context: Settings item description for color theme",
154
+ })}
155
+ />
156
+ <SettingsItem
157
+ href="/dash/settings/font-theme"
158
+ icon={ICONS.type}
159
+ color={COLORS.pink}
160
+ name={t({
161
+ message: "Font Theme",
162
+ comment: "@context: Settings item — font theme settings",
163
+ })}
164
+ description={t({
165
+ message: "Typography",
166
+ comment: "@context: Settings item description for font theme",
167
+ })}
168
+ />
169
+ <SettingsItem
170
+ href="/dash/settings/custom-css"
171
+ icon={ICONS.code}
172
+ color={COLORS.indigo}
173
+ name={t({
174
+ message: "Custom CSS",
175
+ comment: "@context: Settings item — custom CSS settings",
176
+ })}
177
+ description={t({
178
+ message: "Custom styling",
179
+ comment: "@context: Settings item description for custom CSS",
180
+ })}
181
+ />
182
+ </div>
183
+ </div>
184
+
185
+ {/* Advanced */}
186
+ <div>
187
+ <div class="settings-group-label">
188
+ {t({
189
+ message: "Advanced",
190
+ comment: "@context: Settings group label for advanced settings",
191
+ })}
192
+ </div>
193
+ <div class="settings-group">
194
+ <SettingsItem
195
+ href="/dash/settings/redirects"
196
+ icon={ICONS.arrowRightLeft}
197
+ color={COLORS.amber}
198
+ name={t({
199
+ message: "Redirects",
200
+ comment: "@context: Settings item — redirects settings",
201
+ })}
202
+ description={t({
203
+ message: "URL redirects",
204
+ comment: "@context: Settings item description for redirects",
205
+ })}
206
+ />
207
+ </div>
208
+ </div>
209
+
210
+ {/* Account */}
211
+ <div>
212
+ <div class="settings-group-label">
213
+ {t({
214
+ message: "Account",
215
+ comment: "@context: Settings group label for account settings",
216
+ })}
217
+ </div>
218
+ <div class="settings-group">
219
+ <SettingsItem
220
+ href="/dash/settings/account"
221
+ icon={ICONS.user}
222
+ color={COLORS.gray}
223
+ name={t({
224
+ message: "Account",
225
+ comment: "@context: Settings item — account settings",
226
+ })}
227
+ description={t({
228
+ message: "Profile, password",
229
+ comment: "@context: Settings item description for account",
230
+ })}
231
+ />
232
+ </div>
233
+ </div>
234
+ </div>
235
+ );
236
+ }
@@ -9,17 +9,46 @@ import type { Context } from "hono";
9
9
  import { useLingui } from "@lingui/react/macro";
10
10
  import { BaseLayout, type ToastProps } from "./BaseLayout.js";
11
11
 
12
+ export interface DashBreadcrumb {
13
+ parent: string;
14
+ parentHref: string;
15
+ current: string;
16
+ }
17
+
12
18
  export interface DashLayoutProps {
13
19
  c: Context;
14
20
  title: string;
15
21
  siteName: string;
22
+ siteAvatarUrl?: string;
16
23
  currentPath?: string;
24
+ breadcrumb?: DashBreadcrumb;
17
25
  toast?: ToastProps;
18
26
  }
19
27
 
28
+ const AVATAR_COLORS = [
29
+ "#737fab", // slate blue
30
+ "#8d7dab", // muted violet
31
+ "#ab7d8d", // dusty rose
32
+ "#ab917d", // warm taupe
33
+ "#7dab8d", // sage green
34
+ "#7d9bab", // steel blue
35
+ "#9a8d7d", // earth brown
36
+ "#7dabab", // teal grey
37
+ ];
38
+
39
+ function hashString(str: string): number {
40
+ let hash = 5381;
41
+ for (let i = 0; i < str.length; i++) {
42
+ hash = (hash * 33) ^ str.charCodeAt(i);
43
+ }
44
+ return Math.abs(hash);
45
+ }
46
+
20
47
  function DashLayoutContent({
21
48
  siteName,
49
+ siteAvatarUrl,
22
50
  currentPath,
51
+ breadcrumb,
23
52
  children,
24
53
  }: PropsWithChildren<Omit<DashLayoutProps, "c" | "title">>) {
25
54
  const { t } = useLingui();
@@ -32,52 +61,37 @@ function DashLayoutContent({
32
61
  {/* Header */}
33
62
  <header class="dash-header">
34
63
  <div class="container dash-header-inner">
35
- <div class="dash-header-left">
36
- <a id="site-name" href="/dash" class="dash-header-logo">
37
- {siteName}
38
- </a>
39
- <a
40
- href="/"
41
- target="_blank"
42
- rel="noopener noreferrer"
43
- class="dash-header-site-link"
44
- aria-label={t({
45
- message: "View Site",
46
- comment:
47
- "@context: Dashboard header link to view the public site",
48
- })}
49
- >
50
- <svg
51
- xmlns="http://www.w3.org/2000/svg"
52
- width="14"
53
- height="14"
54
- viewBox="0 0 24 24"
55
- fill="none"
56
- stroke="currentColor"
57
- stroke-width="2"
58
- stroke-linecap="round"
59
- stroke-linejoin="round"
64
+ <a href="/dash" class="dash-header-avatar-link">
65
+ {siteAvatarUrl ? (
66
+ <img src={siteAvatarUrl} alt="" class="dash-header-avatar" />
67
+ ) : (
68
+ <span
69
+ class="dash-header-avatar dash-header-avatar-fallback"
70
+ style={`background-color: ${AVATAR_COLORS[hashString(siteName) % AVATAR_COLORS.length]}`}
60
71
  >
61
- <path d="M15 3h6v6" />
62
- <path d="M10 14 21 3" />
63
- <path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6" />
64
- </svg>
65
- </a>
66
- </div>
67
-
72
+ {siteName.charAt(0).toUpperCase()}
73
+ </span>
74
+ )}
75
+ </a>
68
76
  <nav class="dash-header-nav">
69
- <a href="/dash/pages" class={navClass(/^\/dash\/pages/)}>
77
+ <a href="/dash" class={navClass(/^\/dash$/)}>
70
78
  {t({
71
- message: "Pages",
72
- comment: "@context: Dashboard navigation - pages management",
79
+ message: "Dashboard",
80
+ comment: "@context: Dashboard navigation - dashboard home",
73
81
  })}
74
82
  </a>
75
- <a href="/dash/appearance" class={navClass(/^\/dash\/appearance/)}>
83
+ <span class="dash-header-nav-sep" aria-hidden="true">
84
+ &middot;
85
+ </span>
86
+ <a href="/dash/pages" class={navClass(/^\/dash\/pages/)}>
76
87
  {t({
77
- message: "Appearance",
78
- comment: "@context: Dashboard navigation - appearance settings",
88
+ message: "Pages",
89
+ comment: "@context: Dashboard navigation - pages management",
79
90
  })}
80
91
  </a>
92
+ <span class="dash-header-nav-sep" aria-hidden="true">
93
+ &middot;
94
+ </span>
81
95
  <a href="/dash/settings" class={navClass(/^\/dash\/settings/)}>
82
96
  {t({
83
97
  message: "Settings",
@@ -86,54 +100,115 @@ function DashLayoutContent({
86
100
  </a>
87
101
  </nav>
88
102
 
89
- <div class="dropdown-menu">
90
- <button
91
- type="button"
92
- id="dash-menu-trigger"
93
- class="dash-header-menu-btn"
94
- aria-haspopup="menu"
95
- aria-controls="dash-menu"
96
- aria-expanded="false"
103
+ <div class="dash-header-right">
104
+ <a
105
+ href="/"
106
+ class="dash-header-visit"
107
+ target="_blank"
97
108
  aria-label={t({
98
- message: "Menu",
99
- comment: "@context: Dashboard header menu button",
109
+ message: "Visit Blog",
110
+ comment:
111
+ "@context: Dashboard header link to visit the public blog",
100
112
  })}
101
113
  >
102
- <svg
103
- xmlns="http://www.w3.org/2000/svg"
104
- width="16"
105
- height="16"
106
- viewBox="0 0 24 24"
107
- fill="currentColor"
114
+ <span
115
+ class="dash-header-visit-icon"
116
+ data-tooltip={t({
117
+ message: "Visit Blog",
118
+ comment:
119
+ "@context: Dashboard header tooltip for visit blog icon on mobile",
120
+ })}
121
+ data-side="bottom"
108
122
  >
109
- <circle cx="5" cy="12" r="2" />
110
- <circle cx="12" cy="12" r="2" />
111
- <circle cx="19" cy="12" r="2" />
112
- </svg>
113
- </button>
114
- <div
115
- id="dash-menu-popover"
116
- data-popover
117
- data-align="end"
118
- aria-hidden="true"
119
- >
123
+ <svg
124
+ xmlns="http://www.w3.org/2000/svg"
125
+ width="16"
126
+ height="16"
127
+ viewBox="0 0 24 24"
128
+ fill="none"
129
+ stroke="currentColor"
130
+ stroke-width="2"
131
+ stroke-linecap="round"
132
+ stroke-linejoin="round"
133
+ >
134
+ <path d="M21 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h6" />
135
+ <path d="m21 3-9 9" />
136
+ <path d="M15 3h6v6" />
137
+ </svg>
138
+ </span>
139
+ <span class="dash-header-visit-text">
140
+ {t({
141
+ message: "Visit Blog",
142
+ comment:
143
+ "@context: Dashboard header link text to visit the public blog",
144
+ })}
145
+ <span class="ml-1" aria-hidden="true">
146
+ {"\u2197"}
147
+ </span>
148
+ </span>
149
+ </a>
150
+
151
+ <div class="dropdown-menu">
152
+ <button
153
+ type="button"
154
+ id="dash-menu-trigger"
155
+ class="dash-header-menu-btn"
156
+ aria-haspopup="menu"
157
+ aria-controls="dash-menu"
158
+ aria-expanded="false"
159
+ aria-label={t({
160
+ message: "Menu",
161
+ comment: "@context: Dashboard header menu button",
162
+ })}
163
+ >
164
+ <svg
165
+ xmlns="http://www.w3.org/2000/svg"
166
+ width="16"
167
+ height="16"
168
+ viewBox="0 0 24 24"
169
+ fill="currentColor"
170
+ >
171
+ <circle cx="5" cy="12" r="2" />
172
+ <circle cx="12" cy="12" r="2" />
173
+ <circle cx="19" cy="12" r="2" />
174
+ </svg>
175
+ </button>
120
176
  <div
121
- role="menu"
122
- id="dash-menu"
123
- aria-labelledby="dash-menu-trigger"
177
+ id="dash-menu-popover"
178
+ data-popover
179
+ data-align="end"
180
+ aria-hidden="true"
124
181
  >
125
- <a href="/signout" role="menuitem">
126
- {t({
127
- message: "Sign Out",
128
- comment: "@context: Dashboard menu item to sign out",
129
- })}
130
- </a>
182
+ <div
183
+ role="menu"
184
+ id="dash-menu"
185
+ aria-labelledby="dash-menu-trigger"
186
+ >
187
+ <a href="/signout" role="menuitem">
188
+ {t({
189
+ message: "Sign Out",
190
+ comment: "@context: Dashboard menu item to sign out",
191
+ })}
192
+ </a>
193
+ </div>
131
194
  </div>
132
195
  </div>
133
196
  </div>
134
197
  </div>
135
198
  </header>
136
199
 
200
+ {breadcrumb && (
201
+ <div class="container">
202
+ <nav class="dash-breadcrumb">
203
+ <a href={breadcrumb.parentHref} class="dash-breadcrumb-parent">
204
+ {breadcrumb.parent}
205
+ </a>
206
+ <span class="dash-breadcrumb-sep">/</span>
207
+ <span class="dash-breadcrumb-current">{breadcrumb.current}</span>
208
+ </nav>
209
+ </div>
210
+ )}
211
+
137
212
  {/* Main */}
138
213
  <div class="container py-8">
139
214
  <main>{children}</main>
@@ -146,7 +221,9 @@ export const DashLayout: FC<PropsWithChildren<DashLayoutProps>> = ({
146
221
  c,
147
222
  title,
148
223
  siteName,
224
+ siteAvatarUrl,
149
225
  currentPath,
226
+ breadcrumb,
150
227
  toast,
151
228
  children,
152
229
  }) => {
@@ -157,7 +234,12 @@ export const DashLayout: FC<PropsWithChildren<DashLayoutProps>> = ({
157
234
  toast={toast}
158
235
  isAuthenticated={true}
159
236
  >
160
- <DashLayoutContent siteName={siteName} currentPath={currentPath}>
237
+ <DashLayoutContent
238
+ siteName={siteName}
239
+ siteAvatarUrl={siteAvatarUrl}
240
+ currentPath={currentPath}
241
+ breadcrumb={breadcrumb}
242
+ >
161
243
  {children}
162
244
  </DashLayoutContent>
163
245
  </BaseLayout>
@@ -37,6 +37,7 @@ export const SiteLayout: FC<PropsWithChildren<SiteLayoutProps>> = ({
37
37
  showHeaderAvatar,
38
38
  siteFooterHtml,
39
39
  sidebar,
40
+ uploadMaxFileSize,
40
41
  children,
41
42
  }) => {
42
43
  const { t } = useLingui();
@@ -78,7 +79,7 @@ export const SiteLayout: FC<PropsWithChildren<SiteLayoutProps>> = ({
78
79
 
79
80
  return (
80
81
  <div class="site-page">
81
- <header class={`site-header ${sidebar ? "site-header-sidebar" : ""}`}>
82
+ <header class="site-header">
82
83
  <div class="site-header-inner">
83
84
  <div class="site-header-top site-header-top-bordered">
84
85
  <a href="/" class="site-logo">
@@ -180,13 +181,9 @@ export const SiteLayout: FC<PropsWithChildren<SiteLayoutProps>> = ({
180
181
 
181
182
  <main class="site-main">
182
183
  {sidebar ? (
183
- <div class="container-sidebar">
184
- <div class="sidebar-layout">
185
- <aside class="sidebar-nav">{sidebar}</aside>
186
- <div class="sidebar-main">
187
- <div class="site-content">{children}</div>
188
- </div>
189
- </div>
184
+ <div class="site-container site-container-sidebar">
185
+ <aside class="sidebar-nav">{sidebar}</aside>
186
+ <div class="site-content">{children}</div>
190
187
  </div>
191
188
  ) : (
192
189
  <div class="site-container">
@@ -221,10 +218,7 @@ export const SiteLayout: FC<PropsWithChildren<SiteLayoutProps>> = ({
221
218
  </main>
222
219
 
223
220
  {siteFooterHtml && (
224
- <footer
225
- class={`site-footer ${sidebar ? "site-footer-sidebar" : ""}`}
226
- data-footer
227
- >
221
+ <footer class="site-footer" data-footer>
228
222
  <div class="site-container">
229
223
  <div
230
224
  class="prose"
@@ -234,7 +228,13 @@ export const SiteLayout: FC<PropsWithChildren<SiteLayoutProps>> = ({
234
228
  </footer>
235
229
  )}
236
230
 
237
- {isAuthenticated && <ComposeDialog collections={collections} />}
231
+ <jant-media-lightbox />
232
+ {isAuthenticated && (
233
+ <ComposeDialog
234
+ collections={collections}
235
+ uploadMaxFileSize={uploadMaxFileSize}
236
+ />
237
+ )}
238
238
  </div>
239
239
  );
240
240
  };
@@ -47,7 +47,7 @@ export const ArchivePage: FC<ArchivePageProps> = ({
47
47
  hasMore,
48
48
  nextCursor,
49
49
  format,
50
- featured,
50
+ visibility,
51
51
  }) => {
52
52
  const { t } = useLingui();
53
53
  const title = format
@@ -65,7 +65,7 @@ export const ArchivePage: FC<ArchivePageProps> = ({
65
65
  href="/archive"
66
66
  class={
67
67
  "badge " +
68
- (!format && !featured ? "badge-primary" : "badge-outline")
68
+ (!format && !visibility ? "badge-primary" : "badge-outline")
69
69
  }
70
70
  >
71
71
  {t({
@@ -86,8 +86,11 @@ export const ArchivePage: FC<ArchivePageProps> = ({
86
86
  </a>
87
87
  ))}
88
88
  <a
89
- href="/archive?featured=true"
90
- class={"badge " + (featured ? "badge-primary" : "badge-outline")}
89
+ href="/archive?visibility=featured"
90
+ class={
91
+ "badge " +
92
+ (visibility === "featured" ? "badge-primary" : "badge-outline")
93
+ }
91
94
  >
92
95
  {t({
93
96
  message: "Featured",
@@ -101,7 +104,7 @@ export const ArchivePage: FC<ArchivePageProps> = ({
101
104
  {groups.length === 0 ? (
102
105
  <p class="text-muted-foreground">
103
106
  {t({
104
- message: "No posts found.",
107
+ message: "No posts match this filter.",
105
108
  comment: "@context: Archive empty state",
106
109
  })}
107
110
  </p>
@@ -150,8 +153,8 @@ export const ArchivePage: FC<ArchivePageProps> = ({
150
153
  baseUrl={
151
154
  format
152
155
  ? "/archive?format=" + format
153
- : featured
154
- ? "/archive?featured=true"
156
+ : visibility
157
+ ? "/archive?visibility=" + visibility
155
158
  : "/archive"
156
159
  }
157
160
  hasMore={hasMore}