@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.
- package/dist/client/assets/module-RjUF93sV.js +716 -0
- package/dist/client/assets/native-48B9X9Wg.js +1 -0
- package/dist/client/assets/url-8Dj-5CLW.js +1 -0
- package/dist/client/client.css +1 -1
- package/dist/client/client.js +3109 -2294
- package/dist/index.js +3026 -2778
- package/package.json +13 -4
- package/src/__tests__/helpers/app.ts +1 -1
- package/src/__tests__/helpers/db.ts +6 -0
- package/src/app.tsx +1 -5
- package/src/{lib → client}/avatar-upload.ts +1 -1
- 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/{ui → client}/components/__tests__/jant-compose-dialog.test.ts +46 -14
- package/src/{ui → client}/components/__tests__/jant-compose-editor.test.ts +64 -24
- package/src/{ui → client}/components/__tests__/jant-post-form.test.ts +24 -14
- package/src/{ui → client}/components/__tests__/jant-settings-general.test.ts +3 -3
- package/src/client/components/collection-sidebar-types.ts +45 -0
- package/src/{ui → client}/components/collection-types.ts +3 -4
- package/src/{ui → client}/components/compose-types.ts +3 -1
- package/src/{ui → client}/components/jant-collection-form.ts +301 -182
- package/src/client/components/jant-collection-sidebar.ts +801 -0
- package/src/{ui → client}/components/jant-compose-dialog.ts +231 -1
- package/src/client/components/jant-compose-editor.ts +1249 -0
- package/src/client/components/jant-compose-fullscreen.ts +338 -0
- package/src/client/components/jant-media-lightbox.ts +257 -0
- package/src/{ui → client}/components/jant-nav-manager.ts +143 -84
- package/src/{ui → client}/components/jant-post-form.ts +57 -8
- package/src/{ui → client}/components/jant-settings-general.ts +2 -2
- package/src/{ui → client}/components/nav-manager-types.ts +3 -0
- package/src/{ui → client}/components/post-form-template.ts +35 -31
- package/src/{ui → client}/components/post-form-types.ts +7 -3
- package/src/{lib → client}/compose-bridge.ts +9 -7
- package/src/client/lazy-slugify.ts +51 -0
- package/src/{lib → client}/media-upload.ts +16 -3
- package/src/{lib → client}/nav-manager-bridge.ts +1 -1
- package/src/client/page-slug-bridge.ts +42 -0
- package/src/{lib → client}/post-form-bridge.ts +2 -2
- package/src/{lib → client}/settings-bridge.ts +3 -3
- package/src/client/tiptap/bubble-menu.ts +205 -0
- package/src/client/tiptap/create-editor.ts +40 -0
- package/src/client/tiptap/exitable-marks.ts +73 -0
- package/src/client/tiptap/extensions.ts +60 -0
- package/src/client/tiptap/image-node.ts +488 -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 +140 -0
- package/src/client/tiptap/slash-commands.ts +328 -0
- package/src/{types → client/types}/sortablejs.d.ts +1 -1
- package/src/client.ts +24 -17
- package/src/db/migrations/0012_add_tiptap_columns.sql +2 -0
- package/src/db/migrations/0013_replace_featured_with_visibility.sql +8 -0
- package/src/db/schema.ts +6 -1
- package/src/i18n/locales/en.po +641 -215
- package/src/i18n/locales/en.ts +1 -1
- package/src/i18n/locales/zh-Hans.po +642 -204
- package/src/i18n/locales/zh-Hans.ts +1 -1
- package/src/i18n/locales/zh-Hant.po +642 -204
- package/src/i18n/locales/zh-Hant.ts +1 -1
- package/src/lib/__tests__/resolve-config.test.ts +2 -2
- package/src/lib/__tests__/schemas.test.ts +9 -6
- package/src/lib/__tests__/url.test.ts +2 -2
- package/src/lib/__tests__/view.test.ts +9 -9
- package/src/lib/emoji-catalog.ts +146 -0
- package/src/lib/feed.ts +1 -1
- package/src/lib/media-helpers.ts +10 -9
- package/src/lib/render.tsx +4 -3
- package/src/lib/resolve-config.ts +8 -1
- package/src/lib/schemas.ts +2 -3
- package/src/lib/summary.ts +92 -0
- package/src/lib/timeline.ts +2 -0
- package/src/lib/tiptap-render.ts +196 -0
- package/src/lib/upload.ts +97 -9
- package/src/lib/url.ts +7 -23
- package/src/lib/view.ts +33 -19
- package/src/middleware/error-handler.ts +3 -3
- package/src/preset.css +38 -0
- package/src/routes/api/collections.ts +20 -3
- package/src/routes/api/posts.ts +48 -33
- package/src/routes/api/upload.ts +7 -5
- package/src/routes/auth/reset.tsx +5 -4
- package/src/routes/auth/setup.tsx +26 -11
- package/src/routes/auth/signin.tsx +10 -7
- package/src/routes/compose.tsx +20 -11
- package/src/routes/dash/__tests__/settings-avatar.test.ts +43 -8
- package/src/routes/dash/index.tsx +7 -1
- package/src/routes/dash/media.tsx +3 -0
- package/src/routes/dash/pages.tsx +8 -2
- package/src/routes/dash/posts.tsx +6 -2
- package/src/routes/dash/redirects.tsx +15 -9
- package/src/routes/dash/settings.tsx +336 -32
- package/src/routes/feed/__tests__/rss.test.ts +7 -7
- package/src/routes/feed/rss.ts +8 -6
- package/src/routes/pages/__tests__/featured.test.ts +6 -7
- package/src/routes/pages/archive.tsx +11 -7
- package/src/routes/pages/collection.tsx +32 -15
- package/src/routes/pages/collections.tsx +11 -2
- package/src/routes/pages/featured.tsx +1 -1
- package/src/routes/pages/home.tsx +1 -1
- package/src/services/__tests__/post.test.ts +124 -33
- package/src/services/__tests__/settings.test.ts +3 -3
- package/src/services/page.ts +16 -3
- package/src/services/post.ts +96 -37
- package/src/services/search.ts +4 -2
- package/src/services/settings.ts +6 -2
- package/src/styles/components.css +240 -60
- package/src/styles/tokens.css +10 -0
- package/src/styles/ui.css +1157 -81
- package/src/types/bindings.ts +5 -0
- package/src/types/config.ts +23 -1
- package/src/types/constants.ts +3 -0
- package/src/types/entities.ts +9 -2
- package/src/types/operations.ts +9 -3
- package/src/types/props.ts +3 -3
- package/src/types/views.ts +3 -2
- package/src/ui/compose/ComposeDialog.tsx +24 -7
- package/src/ui/dash/PageForm.tsx +2 -0
- package/src/ui/dash/PostList.tsx +5 -5
- package/src/ui/dash/StatusBadge.tsx +13 -5
- package/src/ui/dash/appearance/AdvancedContent.tsx +52 -61
- package/src/ui/dash/appearance/ColorThemeContent.tsx +30 -35
- package/src/ui/dash/appearance/FontThemeContent.tsx +65 -73
- package/src/ui/dash/appearance/NavigationContent.tsx +107 -96
- package/src/ui/dash/media/MediaListContent.tsx +9 -4
- package/src/ui/dash/media/ViewMediaContent.tsx +2 -2
- package/src/ui/dash/pages/PagesContent.tsx +2 -1
- package/src/ui/dash/posts/PostForm.tsx +19 -7
- package/src/ui/dash/settings/AccountContent.tsx +133 -138
- package/src/ui/dash/settings/AvatarContent.tsx +70 -0
- package/src/ui/dash/settings/GeneralContent.tsx +3 -62
- package/src/ui/dash/settings/SettingsRootContent.tsx +236 -0
- package/src/ui/layouts/DashLayout.tsx +157 -75
- package/src/ui/layouts/SiteLayout.tsx +13 -13
- package/src/ui/pages/ArchivePage.tsx +10 -7
- package/src/ui/pages/CollectionPage.tsx +6 -35
- package/src/ui/pages/CollectionsPage.tsx +2 -1
- package/src/ui/pages/FeaturedPage.tsx +2 -1
- package/src/ui/pages/HomePage.tsx +1 -1
- package/src/ui/pages/SearchPage.tsx +1 -1
- package/src/ui/shared/CollectionsSidebar.tsx +228 -3
- package/src/ui/shared/MediaGallery.tsx +179 -41
- package/src/lib/collections-reorder.ts +0 -28
- package/src/routes/dash/appearance.tsx +0 -240
- package/src/routes/dash/collections.tsx +0 -211
- package/src/ui/components/jant-compose-editor.ts +0 -814
- 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/settings/SettingsNav.tsx +0 -52
- /package/src/{ui → client}/components/__tests__/jant-settings-avatar.test.ts +0 -0
- /package/src/{ui → client}/components/jant-settings-avatar.ts +0 -0
- /package/src/{ui → client}/components/settings-types.ts +0 -0
- /package/src/{lib → client}/image-processor.ts +0 -0
- /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
|
-
<
|
|
36
|
-
|
|
37
|
-
{
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
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
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
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
|
|
77
|
+
<a href="/dash" class={navClass(/^\/dash$/)}>
|
|
70
78
|
{t({
|
|
71
|
-
message: "
|
|
72
|
-
comment: "@context: Dashboard navigation -
|
|
79
|
+
message: "Dashboard",
|
|
80
|
+
comment: "@context: Dashboard navigation - dashboard home",
|
|
73
81
|
})}
|
|
74
82
|
</a>
|
|
75
|
-
<
|
|
83
|
+
<span class="dash-header-nav-sep" aria-hidden="true">
|
|
84
|
+
·
|
|
85
|
+
</span>
|
|
86
|
+
<a href="/dash/pages" class={navClass(/^\/dash\/pages/)}>
|
|
76
87
|
{t({
|
|
77
|
-
message: "
|
|
78
|
-
comment: "@context: Dashboard navigation -
|
|
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
|
+
·
|
|
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="
|
|
90
|
-
<
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
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: "
|
|
99
|
-
comment:
|
|
109
|
+
message: "Visit Blog",
|
|
110
|
+
comment:
|
|
111
|
+
"@context: Dashboard header link to visit the public blog",
|
|
100
112
|
})}
|
|
101
113
|
>
|
|
102
|
-
<
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
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
|
-
<
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
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
|
-
|
|
122
|
-
|
|
123
|
-
|
|
177
|
+
id="dash-menu-popover"
|
|
178
|
+
data-popover
|
|
179
|
+
data-align="end"
|
|
180
|
+
aria-hidden="true"
|
|
124
181
|
>
|
|
125
|
-
<
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
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
|
|
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=
|
|
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
|
-
<
|
|
185
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 && !
|
|
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
|
|
90
|
-
class={
|
|
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
|
|
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
|
-
:
|
|
154
|
-
? "/archive?
|
|
156
|
+
: visibility
|
|
157
|
+
? "/archive?visibility=" + visibility
|
|
155
158
|
: "/archive"
|
|
156
159
|
}
|
|
157
160
|
hasMore={hasMore}
|