@jant/core 0.6.12 → 0.6.14
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/import-site.js +2 -10
- package/bin/commands/migrate-text-attachments.js +1 -3
- package/bin/commands/site/export.js +1 -3
- package/bin/commands/telegram/register-webhooks.js +9 -3
- package/bin/lib/site-snapshot.js +1 -5
- package/dist/{app-D-zAhayc.js → app-2i7-WQHg.js} +426 -450
- package/dist/app-DZDlmh7C.js +6 -0
- package/dist/client/.vite/manifest.json +3 -3
- package/dist/client/_assets/{client-S1cdvulk.js → client-BvID5ucz.js} +1 -1
- package/dist/client/_assets/client-Cevfpm8H.css +2 -0
- package/dist/client/_assets/{client-auth-YW92ZH7u.js → client-auth-v3yvhbgZ.js} +596 -590
- package/dist/{export-Ckwh4BiE.js → export-DWn149b7.js} +24 -19
- package/dist/{github-sync-BMmK3JZ0.js → github-sync-C0zyF6XS.js} +2 -2
- package/dist/{github-sync-Dl9wQMar.js → github-sync-Ckk0sJxK.js} +1 -1
- package/dist/index.js +3 -3
- package/dist/node.js +4 -4
- package/package.json +1 -1
- package/src/__tests__/export-service.test.ts +1 -2
- package/src/__tests__/helpers/app.ts +3 -0
- package/src/__tests__/helpers/export-fixtures.ts +0 -1
- package/src/__tests__/import-site-command.test.ts +1 -2
- package/src/client/__tests__/site-header-nav.test.ts +36 -2
- package/src/client/components/__tests__/compose-title-from-heading.test.ts +57 -0
- package/src/client/components/__tests__/jant-command-palette.test.ts +137 -10
- package/src/client/components/__tests__/jant-compose-dialog.test.ts +3 -0
- package/src/client/components/__tests__/jant-compose-editor.test.ts +94 -2
- package/src/client/components/__tests__/jant-nav-manager.test.ts +107 -22
- package/src/client/components/__tests__/jant-settings-general.test.ts +1 -0
- package/src/client/components/compose-title-from-heading.ts +66 -0
- package/src/client/components/compose-types.ts +8 -0
- package/src/client/components/inline-image-issues.ts +17 -0
- package/src/client/components/jant-command-palette.ts +100 -70
- package/src/client/components/jant-compose-dialog.ts +3 -1
- package/src/client/components/jant-compose-editor.ts +78 -1
- package/src/client/components/jant-compose-fullscreen.ts +2 -0
- package/src/client/components/jant-nav-manager.ts +245 -155
- package/src/client/components/nav-manager-types.ts +0 -10
- package/src/client/site-header-nav.d.ts +1 -0
- package/src/client/site-header-nav.js +105 -47
- package/src/client/tiptap/__tests__/block-insertion.test.ts +35 -0
- package/src/client/tiptap/__tests__/footnotes.test.ts +284 -0
- package/src/client/tiptap/__tests__/image-input-rules.test.ts +117 -0
- package/src/client/tiptap/__tests__/markdown-clipboard.test.ts +148 -1
- package/src/client/tiptap/bubble-menu.ts +3 -3
- package/src/client/tiptap/create-editor.ts +7 -0
- package/src/client/tiptap/extensions.ts +7 -2
- package/src/client/tiptap/footnotes.ts +156 -0
- package/src/client/tiptap/image-input-rules.ts +48 -0
- package/src/client/tiptap/image-node.ts +189 -7
- package/src/client/tiptap/markdown-clipboard.ts +7 -2
- package/src/client-auth.ts +0 -1
- package/src/i18n/locales/public/en.po +20 -4
- package/src/i18n/locales/public/en.ts +1 -1
- package/src/i18n/locales/public/zh-Hans.po +20 -4
- package/src/i18n/locales/public/zh-Hans.ts +1 -1
- package/src/i18n/locales/public/zh-Hant.po +20 -4
- package/src/i18n/locales/public/zh-Hant.ts +1 -1
- package/src/i18n/locales/settings/en.po +23 -20
- package/src/i18n/locales/settings/en.ts +1 -1
- package/src/i18n/locales/settings/zh-Hans.po +23 -20
- package/src/i18n/locales/settings/zh-Hans.ts +1 -1
- package/src/i18n/locales/settings/zh-Hant.po +23 -20
- package/src/i18n/locales/settings/zh-Hant.ts +1 -1
- package/src/lib/__tests__/markdown-to-tiptap.test.ts +4 -0
- package/src/lib/__tests__/navigation.test.ts +4 -37
- package/src/lib/__tests__/resolve-config.test.ts +6 -1
- package/src/lib/__tests__/view.test.ts +6 -30
- package/src/lib/github-sync-site-config.ts +0 -2
- package/src/lib/hugo-markdown.ts +1 -1
- package/src/lib/markdown-manager.ts +14 -5
- package/src/lib/navigation.ts +5 -22
- package/src/lib/resolve-config.ts +9 -2
- package/src/lib/site-header-fragment.tsx +37 -0
- package/src/lib/view.ts +2 -7
- package/src/node/__tests__/cli-site-snapshot.test.ts +45 -45
- package/src/routes/api/__tests__/nav-items.test.ts +26 -0
- package/src/routes/api/export.ts +0 -2
- package/src/routes/api/internal/__tests__/uploads.test.ts +9 -13
- package/src/routes/api/internal/sites.ts +5 -5
- package/src/routes/api/nav-items.ts +25 -4
- package/src/routes/dash/settings.tsx +8 -2
- package/src/routes/feed/__tests__/feed.test.ts +42 -0
- package/src/routes/feed/__tests__/sitemap.test.ts +2 -13
- package/src/routes/feed/feed.ts +1 -0
- package/src/routes/feed/sitemap.ts +2 -6
- package/src/routes/pages/archive.tsx +1 -0
- package/src/routes/pages/collection.tsx +1 -0
- package/src/routes/pages/featured.tsx +0 -4
- package/src/routes/pages/home.tsx +4 -51
- package/src/routes/pages/latest.tsx +2 -51
- package/src/services/__tests__/github-app-installations.test.ts +10 -10
- package/src/services/__tests__/navigation.test.ts +41 -0
- package/src/services/__tests__/post-timeline.test.ts +56 -0
- package/src/services/__tests__/post.test.ts +33 -0
- package/src/services/__tests__/settings.test.ts +5 -20
- package/src/services/export-theme/assets/client-site.js +10 -10
- package/src/services/export-theme/layouts/_default/rss.xml +5 -15
- package/src/services/export-theme/layouts/index.html +26 -39
- package/src/services/export.ts +5 -10
- package/src/services/github-app-installations.ts +1 -3
- package/src/services/navigation.ts +44 -13
- package/src/services/post.ts +80 -57
- package/src/services/settings.ts +0 -10
- package/src/services/site-admin.ts +0 -2
- package/src/services/upload-session.ts +4 -18
- package/src/styles/tokens.css +2 -2
- package/src/styles/ui.css +316 -34
- package/src/types/bindings.ts +0 -1
- package/src/types/config.ts +3 -7
- package/src/ui/compose/ComposeDialog.tsx +28 -0
- package/src/ui/dash/appearance/NavigationContent.tsx +1 -1
- package/src/ui/dash/settings/AccountMenuContent.tsx +2 -2
- package/src/ui/dash/settings/SettingsRootContent.tsx +3 -3
- package/src/ui/feed/__tests__/timeline-cards.test.ts +8 -3
- package/src/ui/layouts/BaseLayout.tsx +1 -1
- package/src/ui/layouts/SiteLayout.tsx +266 -227
- package/src/ui/layouts/__tests__/SiteLayout.test.tsx +48 -0
- package/dist/app-B5P1b4TF.js +0 -6
- package/dist/client/_assets/client-BUxR-E8O.css +0 -2
- package/src/client/nav-manager-bridge.ts +0 -54
|
@@ -112,26 +112,44 @@ function HeaderLink({
|
|
|
112
112
|
);
|
|
113
113
|
}
|
|
114
114
|
|
|
115
|
-
export
|
|
115
|
+
export interface SiteHeaderProps {
|
|
116
|
+
siteName: string;
|
|
117
|
+
links: NavItemView[];
|
|
118
|
+
currentPath: string;
|
|
119
|
+
sitePathPrefix?: string;
|
|
120
|
+
siteAvatarUrl?: string;
|
|
121
|
+
showHeaderAvatar?: boolean;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function linkCollapseTier(idx: number): string {
|
|
125
|
+
if (idx < 2) return "";
|
|
126
|
+
if (idx === 2) return "site-header-link-collapse-sm";
|
|
127
|
+
if (idx === 3) return "site-header-link-collapse-md";
|
|
128
|
+
return "site-header-link-collapse-lg";
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function moreLinkRevealTier(idx: number): string {
|
|
132
|
+
if (idx === 2) return "site-header-more-link-show-sm";
|
|
133
|
+
if (idx === 3) return "site-header-more-link-show-md";
|
|
134
|
+
return "site-header-more-link-show-lg";
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function getResponsiveOverflowTier(
|
|
138
|
+
headerLinkCount: number,
|
|
139
|
+
): "sm" | "md" | "lg" | null {
|
|
140
|
+
if (headerLinkCount >= 5) return "lg";
|
|
141
|
+
if (headerLinkCount === 4) return "md";
|
|
142
|
+
if (headerLinkCount === 3) return "sm";
|
|
143
|
+
return null;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export const SiteHeader: FC<SiteHeaderProps> = ({
|
|
116
147
|
siteName,
|
|
117
148
|
links,
|
|
118
149
|
currentPath,
|
|
119
150
|
sitePathPrefix = "",
|
|
120
|
-
isAuthenticated,
|
|
121
|
-
collections,
|
|
122
151
|
siteAvatarUrl,
|
|
123
152
|
showHeaderAvatar,
|
|
124
|
-
siteDescriptionHtml,
|
|
125
|
-
siteFooterHtml,
|
|
126
|
-
showHomeBranding = false,
|
|
127
|
-
sidebar,
|
|
128
|
-
uploadMaxFileSize,
|
|
129
|
-
showComposeDialog = true,
|
|
130
|
-
showHeader = true,
|
|
131
|
-
composeOpenShortcutDiscovered = false,
|
|
132
|
-
slashCommandDiscovered = false,
|
|
133
|
-
composeCollectionId,
|
|
134
|
-
children,
|
|
135
153
|
}) => {
|
|
136
154
|
const { i18n } = useLingui();
|
|
137
155
|
const linksWithLabels = links.map((link) => ({
|
|
@@ -154,56 +172,30 @@ export const SiteLayout: FC<PropsWithChildren<SiteLayoutProps>> = ({
|
|
|
154
172
|
comment: "@context: Hamburger menu button label",
|
|
155
173
|
}),
|
|
156
174
|
);
|
|
157
|
-
const
|
|
158
|
-
const newPostLabel = i18n._(
|
|
175
|
+
const closeMenuLabel = i18n._(
|
|
159
176
|
msg({
|
|
160
|
-
message: "
|
|
161
|
-
comment:
|
|
162
|
-
"@context: Mobile floating action button label to open the compose dialog",
|
|
177
|
+
message: "Close menu",
|
|
178
|
+
comment: "@context: Close drawer button label",
|
|
163
179
|
}),
|
|
164
180
|
);
|
|
181
|
+
const moreLabel = i18n._(NAV_MORE_LABEL);
|
|
165
182
|
|
|
166
|
-
// Split custom links by placement
|
|
183
|
+
// Split custom links by placement.
|
|
167
184
|
const headerLinks = linksWithLabels.filter(
|
|
168
185
|
(l) => l.placement === "header" || !l.placement,
|
|
169
186
|
);
|
|
170
187
|
const moreLinks = linksWithLabels.filter((l) => l.placement === "more");
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
// windows (tablets, narrow laptops) can show 3 or 4 links before falling
|
|
174
|
-
// back to the minimum of 2.
|
|
175
|
-
//
|
|
176
|
-
// idx 2 → collapse-sm (hidden ≤580px, shown in popover)
|
|
177
|
-
// idx 3 → collapse-md (hidden ≤780px)
|
|
178
|
-
// idx ≥ 4 → collapse-lg (hidden ≤960px)
|
|
179
|
-
function linkCollapseTier(idx: number): string {
|
|
180
|
-
if (idx < 2) return "";
|
|
181
|
-
if (idx === 2) return "site-header-link-collapse-sm";
|
|
182
|
-
if (idx === 3) return "site-header-link-collapse-md";
|
|
183
|
-
return "site-header-link-collapse-lg";
|
|
184
|
-
}
|
|
185
|
-
function moreLinkRevealTier(idx: number): string {
|
|
186
|
-
if (idx === 2) return "site-header-more-link-show-sm";
|
|
187
|
-
if (idx === 3) return "site-header-more-link-show-md";
|
|
188
|
-
return "site-header-more-link-show-lg";
|
|
189
|
-
}
|
|
190
|
-
const hasResponsiveOverflow = headerLinks.length > 2;
|
|
188
|
+
const responsiveOverflowTier = getResponsiveOverflowTier(headerLinks.length);
|
|
189
|
+
const hasResponsiveOverflow = responsiveOverflowTier !== null;
|
|
191
190
|
const hasSupplementalMoreLinks = moreLinks.length > 0;
|
|
192
191
|
const showMoreMenu = hasResponsiveOverflow || hasSupplementalMoreLinks;
|
|
192
|
+
|
|
193
193
|
// Decide the widest breakpoint at which the "More" button must appear
|
|
194
|
-
// when there are no supplemental links.
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
if (headerLinks.length >= 5) {
|
|
200
|
-
responsiveTierClass = "site-header-more-tier-lg";
|
|
201
|
-
} else if (headerLinks.length === 4) {
|
|
202
|
-
responsiveTierClass = "site-header-more-tier-md";
|
|
203
|
-
} else {
|
|
204
|
-
responsiveTierClass = "site-header-more-tier-sm";
|
|
205
|
-
}
|
|
206
|
-
}
|
|
194
|
+
// when there are no supplemental links.
|
|
195
|
+
const responsiveTierClass =
|
|
196
|
+
responsiveOverflowTier && !hasSupplementalMoreLinks
|
|
197
|
+
? `site-header-more-tier-${responsiveOverflowTier}`
|
|
198
|
+
: "";
|
|
207
199
|
const moreMenuClass = [
|
|
208
200
|
"site-header-more",
|
|
209
201
|
hasResponsiveOverflow && !hasSupplementalMoreLinks
|
|
@@ -213,127 +205,87 @@ export const SiteLayout: FC<PropsWithChildren<SiteLayoutProps>> = ({
|
|
|
213
205
|
]
|
|
214
206
|
.filter(Boolean)
|
|
215
207
|
.join(" ");
|
|
216
|
-
|
|
217
208
|
const isHomePage =
|
|
218
209
|
currentPath === toPublicPath("/", sitePathPrefix) ||
|
|
219
210
|
currentPath === toPublicPath("/featured", sitePathPrefix) ||
|
|
220
211
|
currentPath === toPublicPath("/latest", sitePathPrefix);
|
|
221
|
-
const showMobileComposeFab = Boolean(
|
|
222
|
-
(isHomePage || composeCollectionId) && isAuthenticated && showComposeDialog,
|
|
223
|
-
);
|
|
224
|
-
const mobileComposeFabClick = composeCollectionId
|
|
225
|
-
? `document.getElementById('compose-dialog')?.querySelector('jant-compose-dialog')?.openNew(${JSON.stringify(
|
|
226
|
-
{ collectionId: composeCollectionId },
|
|
227
|
-
)})`
|
|
228
|
-
: "document.getElementById('compose-dialog')?.querySelector('jant-compose-dialog')?.openNew()";
|
|
229
|
-
const contentClass = [
|
|
230
|
-
"site-content",
|
|
231
|
-
isHomePage ? "site-content-home" : "",
|
|
232
|
-
showMobileComposeFab ? "site-content-mobile-compose-enabled" : "",
|
|
233
|
-
]
|
|
234
|
-
.filter(Boolean)
|
|
235
|
-
.join(" ");
|
|
236
212
|
|
|
237
213
|
return (
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
<
|
|
241
|
-
<div
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
>
|
|
245
|
-
|
|
246
|
-
{
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
{
|
|
268
|
-
<
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
stroke-linejoin="round"
|
|
287
|
-
aria-hidden="true"
|
|
288
|
-
>
|
|
289
|
-
<path d="m6 9 6 6 6-6" />
|
|
290
|
-
</svg>
|
|
291
|
-
</button>
|
|
292
|
-
<div
|
|
293
|
-
id="site-nav-more-popover"
|
|
294
|
-
class="site-header-more-popover"
|
|
295
|
-
data-popover
|
|
296
|
-
data-align="start"
|
|
214
|
+
<>
|
|
215
|
+
<header class="site-header" data-site-header-fragment="header">
|
|
216
|
+
<div class="site-header-inner">
|
|
217
|
+
<div
|
|
218
|
+
class={`site-header-top site-header-top-bordered${isHomePage ? " site-header-top-home" : ""}`}
|
|
219
|
+
>
|
|
220
|
+
<a href={toPublicPath("/", sitePathPrefix)} class="site-logo">
|
|
221
|
+
{showHeaderAvatar && siteAvatarUrl && (
|
|
222
|
+
<img src={siteAvatarUrl} class="site-logo-avatar" alt="" />
|
|
223
|
+
)}
|
|
224
|
+
{siteName}
|
|
225
|
+
</a>
|
|
226
|
+
<nav class="site-header-nav" aria-label="Primary">
|
|
227
|
+
{headerLinks.map((link, idx) => (
|
|
228
|
+
<HeaderLink
|
|
229
|
+
key={link.id}
|
|
230
|
+
link={link}
|
|
231
|
+
label={link.displayLabel}
|
|
232
|
+
className={[
|
|
233
|
+
idx < 2
|
|
234
|
+
? "site-header-link-primary"
|
|
235
|
+
: "site-header-link-overflow",
|
|
236
|
+
linkCollapseTier(idx),
|
|
237
|
+
]
|
|
238
|
+
.filter(Boolean)
|
|
239
|
+
.join(" ")}
|
|
240
|
+
/>
|
|
241
|
+
))}
|
|
242
|
+
{showMoreMenu && (
|
|
243
|
+
<div class={moreMenuClass}>
|
|
244
|
+
<button
|
|
245
|
+
type="button"
|
|
246
|
+
class="site-header-more-btn"
|
|
247
|
+
id="site-nav-more-trigger"
|
|
248
|
+
aria-haspopup="menu"
|
|
249
|
+
aria-expanded="false"
|
|
250
|
+
>
|
|
251
|
+
{moreLabel}
|
|
252
|
+
<svg
|
|
253
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
254
|
+
width="12"
|
|
255
|
+
height="12"
|
|
256
|
+
viewBox="0 0 24 24"
|
|
257
|
+
fill="none"
|
|
258
|
+
stroke="currentColor"
|
|
259
|
+
stroke-width="2.5"
|
|
260
|
+
stroke-linecap="round"
|
|
261
|
+
stroke-linejoin="round"
|
|
297
262
|
aria-hidden="true"
|
|
298
263
|
>
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
{...(link.freshAt
|
|
321
|
-
? { "data-fresh-at": link.freshAt }
|
|
322
|
-
: {})}
|
|
323
|
-
>
|
|
324
|
-
{link.displayLabel}
|
|
325
|
-
{link.isExternal && <ExternalLinkIcon />}
|
|
326
|
-
</a>
|
|
327
|
-
);
|
|
328
|
-
})}
|
|
329
|
-
{hasResponsiveOverflow && hasSupplementalMoreLinks && (
|
|
330
|
-
<div class="site-header-more-divider site-header-more-divider-responsive" />
|
|
331
|
-
)}
|
|
332
|
-
{moreLinks.map((link) => (
|
|
264
|
+
<path d="m6 9 6 6 6-6" />
|
|
265
|
+
</svg>
|
|
266
|
+
</button>
|
|
267
|
+
<div
|
|
268
|
+
id="site-nav-more-popover"
|
|
269
|
+
class="site-header-more-popover"
|
|
270
|
+
data-popover
|
|
271
|
+
data-align="start"
|
|
272
|
+
aria-hidden="true"
|
|
273
|
+
>
|
|
274
|
+
{headerLinks.slice(2).map((link, i) => {
|
|
275
|
+
const idx = i + 2;
|
|
276
|
+
const classes = [
|
|
277
|
+
"site-header-more-link",
|
|
278
|
+
"site-header-more-link-responsive",
|
|
279
|
+
moreLinkRevealTier(idx),
|
|
280
|
+
link.isActive ? "site-header-more-link-active" : "",
|
|
281
|
+
]
|
|
282
|
+
.filter(Boolean)
|
|
283
|
+
.join(" ");
|
|
284
|
+
return (
|
|
333
285
|
<a
|
|
334
286
|
key={link.id}
|
|
335
287
|
href={link.url}
|
|
336
|
-
class={
|
|
288
|
+
class={classes}
|
|
337
289
|
{...(link.isExternal
|
|
338
290
|
? {
|
|
339
291
|
target: "_blank",
|
|
@@ -347,75 +299,101 @@ export const SiteLayout: FC<PropsWithChildren<SiteLayoutProps>> = ({
|
|
|
347
299
|
{link.displayLabel}
|
|
348
300
|
{link.isExternal && <ExternalLinkIcon />}
|
|
349
301
|
</a>
|
|
350
|
-
)
|
|
351
|
-
|
|
302
|
+
);
|
|
303
|
+
})}
|
|
304
|
+
{responsiveOverflowTier && hasSupplementalMoreLinks && (
|
|
305
|
+
<div
|
|
306
|
+
class={`site-header-more-divider site-header-more-divider-responsive site-header-more-divider-show-${responsiveOverflowTier}`}
|
|
307
|
+
/>
|
|
308
|
+
)}
|
|
309
|
+
{moreLinks.map((link) => (
|
|
310
|
+
<a
|
|
311
|
+
key={link.id}
|
|
312
|
+
href={link.url}
|
|
313
|
+
class={`site-header-more-link site-header-more-link-supplemental ${link.isActive ? "site-header-more-link-active" : ""}`}
|
|
314
|
+
{...(link.isExternal
|
|
315
|
+
? {
|
|
316
|
+
target: "_blank",
|
|
317
|
+
rel: "noopener noreferrer",
|
|
318
|
+
}
|
|
319
|
+
: {})}
|
|
320
|
+
{...(link.freshAt
|
|
321
|
+
? { "data-fresh-at": link.freshAt }
|
|
322
|
+
: {})}
|
|
323
|
+
>
|
|
324
|
+
{link.displayLabel}
|
|
325
|
+
{link.isExternal && <ExternalLinkIcon />}
|
|
326
|
+
</a>
|
|
327
|
+
))}
|
|
352
328
|
</div>
|
|
353
|
-
|
|
354
|
-
|
|
329
|
+
</div>
|
|
330
|
+
)}
|
|
331
|
+
</nav>
|
|
355
332
|
|
|
356
|
-
|
|
357
|
-
<
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
placeholder={searchLabel}
|
|
369
|
-
aria-label={searchLabel}
|
|
370
|
-
enterkeyhint="search"
|
|
371
|
-
/>
|
|
372
|
-
</form>
|
|
373
|
-
<a
|
|
374
|
-
href={searchHref}
|
|
375
|
-
class="site-header-search-link"
|
|
333
|
+
<div class="site-header-search-slot">
|
|
334
|
+
<form
|
|
335
|
+
class="site-header-search-form"
|
|
336
|
+
action={searchHref}
|
|
337
|
+
method="get"
|
|
338
|
+
>
|
|
339
|
+
<SearchIcon class="site-header-search-icon" />
|
|
340
|
+
<input
|
|
341
|
+
type="search"
|
|
342
|
+
name="q"
|
|
343
|
+
class="site-header-search-input"
|
|
344
|
+
placeholder={searchLabel}
|
|
376
345
|
aria-label={searchLabel}
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
346
|
+
enterkeyhint="search"
|
|
347
|
+
/>
|
|
348
|
+
</form>
|
|
349
|
+
<a
|
|
350
|
+
href={searchHref}
|
|
351
|
+
class="site-header-search-link"
|
|
352
|
+
aria-label={searchLabel}
|
|
353
|
+
title={searchLabel}
|
|
354
|
+
>
|
|
355
|
+
<SearchIcon class="site-header-search-link-icon" />
|
|
356
|
+
</a>
|
|
357
|
+
</div>
|
|
382
358
|
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
359
|
+
<div class="site-header-right">
|
|
360
|
+
<button
|
|
361
|
+
type="button"
|
|
362
|
+
class="site-header-hamburger"
|
|
363
|
+
aria-controls="site-nav-drawer"
|
|
364
|
+
aria-expanded="false"
|
|
365
|
+
aria-label={menuLabel}
|
|
366
|
+
>
|
|
367
|
+
<svg
|
|
368
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
369
|
+
width="20"
|
|
370
|
+
height="20"
|
|
371
|
+
viewBox="0 0 24 24"
|
|
372
|
+
fill="none"
|
|
373
|
+
stroke="currentColor"
|
|
374
|
+
stroke-width="2"
|
|
375
|
+
stroke-linecap="round"
|
|
376
|
+
stroke-linejoin="round"
|
|
391
377
|
>
|
|
392
|
-
<
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
fill="none"
|
|
398
|
-
stroke="currentColor"
|
|
399
|
-
stroke-width="2"
|
|
400
|
-
stroke-linecap="round"
|
|
401
|
-
stroke-linejoin="round"
|
|
402
|
-
>
|
|
403
|
-
<line x1="4" x2="20" y1="12" y2="12" />
|
|
404
|
-
<line x1="4" x2="20" y1="6" y2="6" />
|
|
405
|
-
<line x1="4" x2="20" y1="18" y2="18" />
|
|
406
|
-
</svg>
|
|
407
|
-
</button>
|
|
408
|
-
</div>
|
|
378
|
+
<line x1="4" x2="20" y1="12" y2="12" />
|
|
379
|
+
<line x1="4" x2="20" y1="6" y2="6" />
|
|
380
|
+
<line x1="4" x2="20" y1="18" y2="18" />
|
|
381
|
+
</svg>
|
|
382
|
+
</button>
|
|
409
383
|
</div>
|
|
410
384
|
</div>
|
|
411
|
-
</
|
|
412
|
-
|
|
385
|
+
</div>
|
|
386
|
+
</header>
|
|
413
387
|
|
|
414
|
-
|
|
415
|
-
|
|
388
|
+
<div
|
|
389
|
+
class="site-nav-drawer-backdrop"
|
|
390
|
+
data-site-header-fragment="drawer-backdrop"
|
|
391
|
+
aria-hidden="true"
|
|
392
|
+
/>
|
|
416
393
|
<div
|
|
417
394
|
id="site-nav-drawer"
|
|
418
395
|
class="site-nav-drawer"
|
|
396
|
+
data-site-header-fragment="drawer"
|
|
419
397
|
role="dialog"
|
|
420
398
|
aria-modal="true"
|
|
421
399
|
aria-label={menuLabel}
|
|
@@ -439,12 +417,7 @@ export const SiteLayout: FC<PropsWithChildren<SiteLayoutProps>> = ({
|
|
|
439
417
|
<button
|
|
440
418
|
type="button"
|
|
441
419
|
class="site-nav-drawer-close"
|
|
442
|
-
aria-label={
|
|
443
|
-
msg({
|
|
444
|
-
message: "Close menu",
|
|
445
|
-
comment: "@context: Close drawer button label",
|
|
446
|
-
}),
|
|
447
|
-
)}
|
|
420
|
+
aria-label={closeMenuLabel}
|
|
448
421
|
>
|
|
449
422
|
<svg
|
|
450
423
|
xmlns="http://www.w3.org/2000/svg"
|
|
@@ -499,6 +472,72 @@ export const SiteLayout: FC<PropsWithChildren<SiteLayoutProps>> = ({
|
|
|
499
472
|
)}
|
|
500
473
|
</nav>
|
|
501
474
|
</div>
|
|
475
|
+
</>
|
|
476
|
+
);
|
|
477
|
+
};
|
|
478
|
+
|
|
479
|
+
export const SiteLayout: FC<PropsWithChildren<SiteLayoutProps>> = ({
|
|
480
|
+
siteName,
|
|
481
|
+
links,
|
|
482
|
+
currentPath,
|
|
483
|
+
sitePathPrefix = "",
|
|
484
|
+
isAuthenticated,
|
|
485
|
+
collections,
|
|
486
|
+
siteAvatarUrl,
|
|
487
|
+
showHeaderAvatar,
|
|
488
|
+
siteDescriptionHtml,
|
|
489
|
+
siteFooterHtml,
|
|
490
|
+
showHomeBranding = false,
|
|
491
|
+
sidebar,
|
|
492
|
+
uploadMaxFileSize,
|
|
493
|
+
showComposeDialog = true,
|
|
494
|
+
showHeader = true,
|
|
495
|
+
composeOpenShortcutDiscovered = false,
|
|
496
|
+
slashCommandDiscovered = false,
|
|
497
|
+
composeCollectionId,
|
|
498
|
+
children,
|
|
499
|
+
}) => {
|
|
500
|
+
const { i18n } = useLingui();
|
|
501
|
+
const newPostLabel = i18n._(
|
|
502
|
+
msg({
|
|
503
|
+
message: "New post",
|
|
504
|
+
comment:
|
|
505
|
+
"@context: Mobile floating action button label to open the compose dialog",
|
|
506
|
+
}),
|
|
507
|
+
);
|
|
508
|
+
|
|
509
|
+
const isHomePage =
|
|
510
|
+
currentPath === toPublicPath("/", sitePathPrefix) ||
|
|
511
|
+
currentPath === toPublicPath("/featured", sitePathPrefix) ||
|
|
512
|
+
currentPath === toPublicPath("/latest", sitePathPrefix);
|
|
513
|
+
const showMobileComposeFab = Boolean(
|
|
514
|
+
(isHomePage || composeCollectionId) && isAuthenticated && showComposeDialog,
|
|
515
|
+
);
|
|
516
|
+
const mobileComposeFabClick = composeCollectionId
|
|
517
|
+
? `document.getElementById('compose-dialog')?.querySelector('jant-compose-dialog')?.openNew(${JSON.stringify(
|
|
518
|
+
{ collectionId: composeCollectionId },
|
|
519
|
+
)})`
|
|
520
|
+
: "document.getElementById('compose-dialog')?.querySelector('jant-compose-dialog')?.openNew()";
|
|
521
|
+
const contentClass = [
|
|
522
|
+
"site-content",
|
|
523
|
+
isHomePage ? "site-content-home" : "",
|
|
524
|
+
showMobileComposeFab ? "site-content-mobile-compose-enabled" : "",
|
|
525
|
+
]
|
|
526
|
+
.filter(Boolean)
|
|
527
|
+
.join(" ");
|
|
528
|
+
|
|
529
|
+
return (
|
|
530
|
+
<div class="site-page">
|
|
531
|
+
{showHeader && (
|
|
532
|
+
<SiteHeader
|
|
533
|
+
siteName={siteName}
|
|
534
|
+
links={links}
|
|
535
|
+
currentPath={currentPath}
|
|
536
|
+
sitePathPrefix={sitePathPrefix}
|
|
537
|
+
siteAvatarUrl={siteAvatarUrl}
|
|
538
|
+
showHeaderAvatar={showHeaderAvatar}
|
|
539
|
+
/>
|
|
540
|
+
)}
|
|
502
541
|
|
|
503
542
|
<main class="site-main">
|
|
504
543
|
{sidebar ? (
|
|
@@ -3,8 +3,24 @@ import { renderToString } from "hono/jsx/dom/server";
|
|
|
3
3
|
import { describe, expect, it } from "vitest";
|
|
4
4
|
import { I18nProvider } from "../../../i18n/context.js";
|
|
5
5
|
import { createI18n } from "../../../i18n/i18n.js";
|
|
6
|
+
import type { NavItemView } from "../../../types.js";
|
|
6
7
|
import { SiteLayout } from "../SiteLayout.js";
|
|
7
8
|
|
|
9
|
+
function createNavItem(
|
|
10
|
+
index: number,
|
|
11
|
+
placement: NavItemView["placement"],
|
|
12
|
+
): NavItemView {
|
|
13
|
+
return {
|
|
14
|
+
id: `nav_${index}`,
|
|
15
|
+
type: "link",
|
|
16
|
+
label: `Link ${index}`,
|
|
17
|
+
url: `/link-${index}`,
|
|
18
|
+
placement,
|
|
19
|
+
isActive: false,
|
|
20
|
+
isExternal: false,
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
|
|
8
24
|
function renderSiteLayout(
|
|
9
25
|
props: Partial<Parameters<typeof SiteLayout>[0]> = {},
|
|
10
26
|
): string {
|
|
@@ -30,6 +46,38 @@ function renderSiteLayout(
|
|
|
30
46
|
}
|
|
31
47
|
|
|
32
48
|
describe("SiteLayout", () => {
|
|
49
|
+
it.each([
|
|
50
|
+
[3, "sm"],
|
|
51
|
+
[4, "md"],
|
|
52
|
+
[5, "lg"],
|
|
53
|
+
] as const)(
|
|
54
|
+
"reveals the More divider at the first breakpoint that overflows %i header links",
|
|
55
|
+
(headerLinkCount, tier) => {
|
|
56
|
+
const headerLinks = Array.from({ length: headerLinkCount }, (_, index) =>
|
|
57
|
+
createNavItem(index, "header"),
|
|
58
|
+
);
|
|
59
|
+
const html = renderSiteLayout({
|
|
60
|
+
links: [...headerLinks, createNavItem(headerLinkCount, "more")],
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
expect(html).toContain(
|
|
64
|
+
`class="site-header-more-divider site-header-more-divider-responsive site-header-more-divider-show-${tier}"`,
|
|
65
|
+
);
|
|
66
|
+
},
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
it("omits the More divider when no header link can overflow", () => {
|
|
70
|
+
const html = renderSiteLayout({
|
|
71
|
+
links: [
|
|
72
|
+
createNavItem(0, "header"),
|
|
73
|
+
createNavItem(1, "header"),
|
|
74
|
+
createNavItem(2, "more"),
|
|
75
|
+
],
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
expect(html).not.toContain("site-header-more-divider");
|
|
79
|
+
});
|
|
80
|
+
|
|
33
81
|
it("renders the mobile compose FAB for authenticated timeline pages", () => {
|
|
34
82
|
const html = renderSiteLayout({
|
|
35
83
|
currentPath: "/latest",
|