@jant/core 0.6.12 → 0.6.13

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 (106) hide show
  1. package/bin/commands/import-site.js +2 -10
  2. package/bin/commands/migrate-text-attachments.js +1 -3
  3. package/bin/commands/site/export.js +1 -3
  4. package/bin/commands/telegram/register-webhooks.js +9 -3
  5. package/bin/lib/site-snapshot.js +1 -5
  6. package/dist/app-CWJFPjuR.js +6 -0
  7. package/dist/{app-D-zAhayc.js → app-HnzmsaqU.js} +416 -444
  8. package/dist/client/.vite/manifest.json +3 -3
  9. package/dist/client/_assets/{client-S1cdvulk.js → client-3_OaxhTs.js} +1 -1
  10. package/dist/client/_assets/{client-BUxR-E8O.css → client-DCrcHVJl.css} +1 -1
  11. package/dist/client/_assets/{client-auth-YW92ZH7u.js → client-auth-TsYq90xm.js} +570 -567
  12. package/dist/{export-Ckwh4BiE.js → export-Dfbq9aot.js} +10 -13
  13. package/dist/{github-sync-Dl9wQMar.js → github-sync--PSoE-Ne.js} +1 -1
  14. package/dist/{github-sync-BMmK3JZ0.js → github-sync-BGTLx8Mf.js} +2 -2
  15. package/dist/index.js +3 -3
  16. package/dist/node.js +4 -4
  17. package/package.json +1 -1
  18. package/src/__tests__/export-service.test.ts +1 -2
  19. package/src/__tests__/helpers/app.ts +3 -0
  20. package/src/__tests__/helpers/export-fixtures.ts +0 -1
  21. package/src/__tests__/import-site-command.test.ts +1 -2
  22. package/src/client/__tests__/site-header-nav.test.ts +36 -2
  23. package/src/client/components/__tests__/compose-title-from-heading.test.ts +57 -0
  24. package/src/client/components/__tests__/jant-compose-editor.test.ts +94 -2
  25. package/src/client/components/__tests__/jant-nav-manager.test.ts +107 -22
  26. package/src/client/components/__tests__/jant-settings-general.test.ts +1 -0
  27. package/src/client/components/compose-title-from-heading.ts +66 -0
  28. package/src/client/components/compose-types.ts +8 -0
  29. package/src/client/components/inline-image-issues.ts +17 -0
  30. package/src/client/components/jant-compose-dialog.ts +2 -0
  31. package/src/client/components/jant-compose-editor.ts +78 -1
  32. package/src/client/components/jant-compose-fullscreen.ts +2 -0
  33. package/src/client/components/jant-nav-manager.ts +245 -155
  34. package/src/client/components/nav-manager-types.ts +0 -10
  35. package/src/client/site-header-nav.d.ts +1 -0
  36. package/src/client/site-header-nav.js +105 -47
  37. package/src/client/tiptap/__tests__/block-insertion.test.ts +35 -0
  38. package/src/client/tiptap/__tests__/image-input-rules.test.ts +117 -0
  39. package/src/client/tiptap/create-editor.ts +7 -0
  40. package/src/client/tiptap/extensions.ts +7 -2
  41. package/src/client/tiptap/image-input-rules.ts +48 -0
  42. package/src/client/tiptap/image-node.ts +189 -7
  43. package/src/client-auth.ts +0 -1
  44. package/src/i18n/locales/public/en.po +20 -4
  45. package/src/i18n/locales/public/en.ts +1 -1
  46. package/src/i18n/locales/public/zh-Hans.po +20 -4
  47. package/src/i18n/locales/public/zh-Hans.ts +1 -1
  48. package/src/i18n/locales/public/zh-Hant.po +20 -4
  49. package/src/i18n/locales/public/zh-Hant.ts +1 -1
  50. package/src/i18n/locales/settings/en.po +23 -20
  51. package/src/i18n/locales/settings/en.ts +1 -1
  52. package/src/i18n/locales/settings/zh-Hans.po +23 -20
  53. package/src/i18n/locales/settings/zh-Hans.ts +1 -1
  54. package/src/i18n/locales/settings/zh-Hant.po +23 -20
  55. package/src/i18n/locales/settings/zh-Hant.ts +1 -1
  56. package/src/lib/__tests__/navigation.test.ts +4 -37
  57. package/src/lib/__tests__/resolve-config.test.ts +6 -1
  58. package/src/lib/__tests__/view.test.ts +6 -30
  59. package/src/lib/github-sync-site-config.ts +0 -2
  60. package/src/lib/hugo-markdown.ts +1 -1
  61. package/src/lib/navigation.ts +5 -22
  62. package/src/lib/resolve-config.ts +9 -2
  63. package/src/lib/site-header-fragment.tsx +37 -0
  64. package/src/lib/view.ts +2 -7
  65. package/src/node/__tests__/cli-site-snapshot.test.ts +45 -45
  66. package/src/routes/api/__tests__/nav-items.test.ts +26 -0
  67. package/src/routes/api/export.ts +0 -2
  68. package/src/routes/api/internal/__tests__/uploads.test.ts +9 -13
  69. package/src/routes/api/internal/sites.ts +5 -5
  70. package/src/routes/api/nav-items.ts +25 -4
  71. package/src/routes/dash/settings.tsx +8 -2
  72. package/src/routes/feed/__tests__/feed.test.ts +42 -0
  73. package/src/routes/feed/__tests__/sitemap.test.ts +2 -13
  74. package/src/routes/feed/feed.ts +1 -0
  75. package/src/routes/feed/sitemap.ts +2 -6
  76. package/src/routes/pages/archive.tsx +1 -0
  77. package/src/routes/pages/collection.tsx +1 -0
  78. package/src/routes/pages/featured.tsx +0 -4
  79. package/src/routes/pages/home.tsx +4 -51
  80. package/src/routes/pages/latest.tsx +2 -51
  81. package/src/services/__tests__/github-app-installations.test.ts +10 -10
  82. package/src/services/__tests__/navigation.test.ts +41 -0
  83. package/src/services/__tests__/post-timeline.test.ts +56 -0
  84. package/src/services/__tests__/post.test.ts +33 -0
  85. package/src/services/__tests__/settings.test.ts +5 -20
  86. package/src/services/export-theme/assets/client-site.js +10 -10
  87. package/src/services/export-theme/layouts/_default/rss.xml +5 -15
  88. package/src/services/export-theme/layouts/index.html +26 -39
  89. package/src/services/export.ts +5 -10
  90. package/src/services/github-app-installations.ts +1 -3
  91. package/src/services/navigation.ts +44 -13
  92. package/src/services/post.ts +80 -57
  93. package/src/services/settings.ts +0 -10
  94. package/src/services/site-admin.ts +0 -2
  95. package/src/services/upload-session.ts +4 -18
  96. package/src/styles/tokens.css +2 -2
  97. package/src/styles/ui.css +200 -6
  98. package/src/types/bindings.ts +0 -1
  99. package/src/types/config.ts +3 -7
  100. package/src/ui/compose/ComposeDialog.tsx +28 -0
  101. package/src/ui/dash/appearance/NavigationContent.tsx +1 -1
  102. package/src/ui/dash/settings/AccountMenuContent.tsx +2 -2
  103. package/src/ui/dash/settings/SettingsRootContent.tsx +3 -3
  104. package/src/ui/layouts/SiteLayout.tsx +249 -216
  105. package/dist/app-B5P1b4TF.js +0 -6
  106. package/src/client/nav-manager-bridge.ts +0 -54
@@ -66,7 +66,7 @@ export function AccountMenuContent({
66
66
  : i18n._(
67
67
  msg({
68
68
  message:
69
- "Manage sign-in security, exports, and irreversible actions.",
69
+ "Manage sign-in security, site exports, and irreversible actions.",
70
70
  comment:
71
71
  "@context: Intro text on the account settings menu page below the title",
72
72
  }),
@@ -105,7 +105,7 @@ export function AccountMenuContent({
105
105
  <h1 class="page-intro-title page-intro-title-compact">
106
106
  {i18n._(
107
107
  msg({
108
- message: "Account",
108
+ message: "Account & Data",
109
109
  comment: "@context: Page title for the account settings menu",
110
110
  }),
111
111
  )}
@@ -51,7 +51,7 @@ export function SettingsRootContent({
51
51
  )
52
52
  : i18n._(
53
53
  msg({
54
- message: "Sessions and password",
54
+ message: "Sign-in, export, and deletion",
55
55
  comment:
56
56
  "@context: Settings item description for account settings on the settings home page",
57
57
  }),
@@ -171,7 +171,7 @@ export function SettingsRootContent({
171
171
  )}
172
172
  description={i18n._(
173
173
  msg({
174
- message: "Header links, home feed, and overflow menu",
174
+ message: "Header links, built-in destinations, and overflow menu",
175
175
  comment: "@context: Settings item description for navigation",
176
176
  }),
177
177
  )}
@@ -333,7 +333,7 @@ export function SettingsRootContent({
333
333
  tone="subtle"
334
334
  name={i18n._(
335
335
  msg({
336
- message: "Account",
336
+ message: "Account & Data",
337
337
  comment: "@context: Settings item — account settings",
338
338
  }),
339
339
  )}
@@ -112,26 +112,35 @@ function HeaderLink({
112
112
  );
113
113
  }
114
114
 
115
- export const SiteLayout: FC<PropsWithChildren<SiteLayoutProps>> = ({
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
+ export const SiteHeader: FC<SiteHeaderProps> = ({
116
138
  siteName,
117
139
  links,
118
140
  currentPath,
119
141
  sitePathPrefix = "",
120
- isAuthenticated,
121
- collections,
122
142
  siteAvatarUrl,
123
143
  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
144
  }) => {
136
145
  const { i18n } = useLingui();
137
146
  const linksWithLabels = links.map((link) => ({
@@ -154,46 +163,25 @@ export const SiteLayout: FC<PropsWithChildren<SiteLayoutProps>> = ({
154
163
  comment: "@context: Hamburger menu button label",
155
164
  }),
156
165
  );
157
- const moreLabel = i18n._(NAV_MORE_LABEL);
158
- const newPostLabel = i18n._(
166
+ const closeMenuLabel = i18n._(
159
167
  msg({
160
- message: "New post",
161
- comment:
162
- "@context: Mobile floating action button label to open the compose dialog",
168
+ message: "Close menu",
169
+ comment: "@context: Close drawer button label",
163
170
  }),
164
171
  );
172
+ const moreLabel = i18n._(NAV_MORE_LABEL);
165
173
 
166
- // Split custom links by placement
174
+ // Split custom links by placement.
167
175
  const headerLinks = linksWithLabels.filter(
168
176
  (l) => l.placement === "header" || !l.placement,
169
177
  );
170
178
  const moreLinks = linksWithLabels.filter((l) => l.placement === "more");
171
- // Responsive collapse tiers — first two links always stay inline; the
172
- // remaining links collapse into the "More" popover in stages so wider
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
179
  const hasResponsiveOverflow = headerLinks.length > 2;
191
180
  const hasSupplementalMoreLinks = moreLinks.length > 0;
192
181
  const showMoreMenu = hasResponsiveOverflow || hasSupplementalMoreLinks;
182
+
193
183
  // Decide the widest breakpoint at which the "More" button must appear
194
- // when there are no supplemental links. With 3 total links only idx 2 ever
195
- // collapses (≤780); with 4 links idx 3 starts collapsing at ≤920; with 5+
196
- // idx 4 starts collapsing at ≤1100.
184
+ // when there are no supplemental links.
197
185
  let responsiveTierClass = "";
198
186
  if (hasResponsiveOverflow && !hasSupplementalMoreLinks) {
199
187
  if (headerLinks.length >= 5) {
@@ -213,127 +201,87 @@ export const SiteLayout: FC<PropsWithChildren<SiteLayoutProps>> = ({
213
201
  ]
214
202
  .filter(Boolean)
215
203
  .join(" ");
216
-
217
204
  const isHomePage =
218
205
  currentPath === toPublicPath("/", sitePathPrefix) ||
219
206
  currentPath === toPublicPath("/featured", sitePathPrefix) ||
220
207
  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
208
 
237
209
  return (
238
- <div class="site-page">
239
- {showHeader && (
240
- <header class="site-header">
241
- <div class="site-header-inner">
242
- <div
243
- class={`site-header-top site-header-top-bordered${isHomePage ? " site-header-top-home" : ""}`}
244
- >
245
- <a href={toPublicPath("/", sitePathPrefix)} class="site-logo">
246
- {showHeaderAvatar && siteAvatarUrl && (
247
- <img src={siteAvatarUrl} class="site-logo-avatar" alt="" />
248
- )}
249
- {siteName}
250
- </a>
251
- <nav class="site-header-nav" aria-label="Primary">
252
- {headerLinks.map((link, idx) => (
253
- <HeaderLink
254
- key={link.id}
255
- link={link}
256
- label={link.displayLabel}
257
- className={[
258
- idx < 2
259
- ? "site-header-link-primary"
260
- : "site-header-link-overflow",
261
- linkCollapseTier(idx),
262
- ]
263
- .filter(Boolean)
264
- .join(" ")}
265
- />
266
- ))}
267
- {showMoreMenu && (
268
- <div class={moreMenuClass}>
269
- <button
270
- type="button"
271
- class="site-header-more-btn"
272
- id="site-nav-more-trigger"
273
- aria-haspopup="menu"
274
- aria-expanded="false"
275
- >
276
- {moreLabel}
277
- <svg
278
- xmlns="http://www.w3.org/2000/svg"
279
- width="12"
280
- height="12"
281
- viewBox="0 0 24 24"
282
- fill="none"
283
- stroke="currentColor"
284
- stroke-width="2.5"
285
- stroke-linecap="round"
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"
210
+ <>
211
+ <header class="site-header" data-site-header-fragment="header">
212
+ <div class="site-header-inner">
213
+ <div
214
+ class={`site-header-top site-header-top-bordered${isHomePage ? " site-header-top-home" : ""}`}
215
+ >
216
+ <a href={toPublicPath("/", sitePathPrefix)} class="site-logo">
217
+ {showHeaderAvatar && siteAvatarUrl && (
218
+ <img src={siteAvatarUrl} class="site-logo-avatar" alt="" />
219
+ )}
220
+ {siteName}
221
+ </a>
222
+ <nav class="site-header-nav" aria-label="Primary">
223
+ {headerLinks.map((link, idx) => (
224
+ <HeaderLink
225
+ key={link.id}
226
+ link={link}
227
+ label={link.displayLabel}
228
+ className={[
229
+ idx < 2
230
+ ? "site-header-link-primary"
231
+ : "site-header-link-overflow",
232
+ linkCollapseTier(idx),
233
+ ]
234
+ .filter(Boolean)
235
+ .join(" ")}
236
+ />
237
+ ))}
238
+ {showMoreMenu && (
239
+ <div class={moreMenuClass}>
240
+ <button
241
+ type="button"
242
+ class="site-header-more-btn"
243
+ id="site-nav-more-trigger"
244
+ aria-haspopup="menu"
245
+ aria-expanded="false"
246
+ >
247
+ {moreLabel}
248
+ <svg
249
+ xmlns="http://www.w3.org/2000/svg"
250
+ width="12"
251
+ height="12"
252
+ viewBox="0 0 24 24"
253
+ fill="none"
254
+ stroke="currentColor"
255
+ stroke-width="2.5"
256
+ stroke-linecap="round"
257
+ stroke-linejoin="round"
297
258
  aria-hidden="true"
298
259
  >
299
- {headerLinks.slice(2).map((link, i) => {
300
- const idx = i + 2;
301
- const classes = [
302
- "site-header-more-link",
303
- "site-header-more-link-responsive",
304
- moreLinkRevealTier(idx),
305
- link.isActive ? "site-header-more-link-active" : "",
306
- ]
307
- .filter(Boolean)
308
- .join(" ");
309
- return (
310
- <a
311
- key={link.id}
312
- href={link.url}
313
- class={classes}
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
- );
328
- })}
329
- {hasResponsiveOverflow && hasSupplementalMoreLinks && (
330
- <div class="site-header-more-divider site-header-more-divider-responsive" />
331
- )}
332
- {moreLinks.map((link) => (
260
+ <path d="m6 9 6 6 6-6" />
261
+ </svg>
262
+ </button>
263
+ <div
264
+ id="site-nav-more-popover"
265
+ class="site-header-more-popover"
266
+ data-popover
267
+ data-align="start"
268
+ aria-hidden="true"
269
+ >
270
+ {headerLinks.slice(2).map((link, i) => {
271
+ const idx = i + 2;
272
+ const classes = [
273
+ "site-header-more-link",
274
+ "site-header-more-link-responsive",
275
+ moreLinkRevealTier(idx),
276
+ link.isActive ? "site-header-more-link-active" : "",
277
+ ]
278
+ .filter(Boolean)
279
+ .join(" ");
280
+ return (
333
281
  <a
334
282
  key={link.id}
335
283
  href={link.url}
336
- class={`site-header-more-link site-header-more-link-supplemental ${link.isActive ? "site-header-more-link-active" : ""}`}
284
+ class={classes}
337
285
  {...(link.isExternal
338
286
  ? {
339
287
  target: "_blank",
@@ -347,75 +295,99 @@ export const SiteLayout: FC<PropsWithChildren<SiteLayoutProps>> = ({
347
295
  {link.displayLabel}
348
296
  {link.isExternal && <ExternalLinkIcon />}
349
297
  </a>
350
- ))}
351
- </div>
298
+ );
299
+ })}
300
+ {hasResponsiveOverflow && hasSupplementalMoreLinks && (
301
+ <div class="site-header-more-divider site-header-more-divider-responsive" />
302
+ )}
303
+ {moreLinks.map((link) => (
304
+ <a
305
+ key={link.id}
306
+ href={link.url}
307
+ class={`site-header-more-link site-header-more-link-supplemental ${link.isActive ? "site-header-more-link-active" : ""}`}
308
+ {...(link.isExternal
309
+ ? {
310
+ target: "_blank",
311
+ rel: "noopener noreferrer",
312
+ }
313
+ : {})}
314
+ {...(link.freshAt
315
+ ? { "data-fresh-at": link.freshAt }
316
+ : {})}
317
+ >
318
+ {link.displayLabel}
319
+ {link.isExternal && <ExternalLinkIcon />}
320
+ </a>
321
+ ))}
352
322
  </div>
353
- )}
354
- </nav>
323
+ </div>
324
+ )}
325
+ </nav>
355
326
 
356
- {/* Search */}
357
- <div class="site-header-search-slot">
358
- <form
359
- class="site-header-search-form"
360
- action={searchHref}
361
- method="get"
362
- >
363
- <SearchIcon class="site-header-search-icon" />
364
- <input
365
- type="search"
366
- name="q"
367
- class="site-header-search-input"
368
- placeholder={searchLabel}
369
- aria-label={searchLabel}
370
- enterkeyhint="search"
371
- />
372
- </form>
373
- <a
374
- href={searchHref}
375
- class="site-header-search-link"
327
+ <div class="site-header-search-slot">
328
+ <form
329
+ class="site-header-search-form"
330
+ action={searchHref}
331
+ method="get"
332
+ >
333
+ <SearchIcon class="site-header-search-icon" />
334
+ <input
335
+ type="search"
336
+ name="q"
337
+ class="site-header-search-input"
338
+ placeholder={searchLabel}
376
339
  aria-label={searchLabel}
377
- title={searchLabel}
378
- >
379
- <SearchIcon class="site-header-search-link-icon" />
380
- </a>
381
- </div>
340
+ enterkeyhint="search"
341
+ />
342
+ </form>
343
+ <a
344
+ href={searchHref}
345
+ class="site-header-search-link"
346
+ aria-label={searchLabel}
347
+ title={searchLabel}
348
+ >
349
+ <SearchIcon class="site-header-search-link-icon" />
350
+ </a>
351
+ </div>
382
352
 
383
- <div class="site-header-right">
384
- {/* Mobile hamburger */}
385
- <button
386
- type="button"
387
- class="site-header-hamburger"
388
- aria-controls="site-nav-drawer"
389
- aria-expanded="false"
390
- aria-label={menuLabel}
353
+ <div class="site-header-right">
354
+ <button
355
+ type="button"
356
+ class="site-header-hamburger"
357
+ aria-controls="site-nav-drawer"
358
+ aria-expanded="false"
359
+ aria-label={menuLabel}
360
+ >
361
+ <svg
362
+ xmlns="http://www.w3.org/2000/svg"
363
+ width="20"
364
+ height="20"
365
+ viewBox="0 0 24 24"
366
+ fill="none"
367
+ stroke="currentColor"
368
+ stroke-width="2"
369
+ stroke-linecap="round"
370
+ stroke-linejoin="round"
391
371
  >
392
- <svg
393
- xmlns="http://www.w3.org/2000/svg"
394
- width="20"
395
- height="20"
396
- viewBox="0 0 24 24"
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>
372
+ <line x1="4" x2="20" y1="12" y2="12" />
373
+ <line x1="4" x2="20" y1="6" y2="6" />
374
+ <line x1="4" x2="20" y1="18" y2="18" />
375
+ </svg>
376
+ </button>
409
377
  </div>
410
378
  </div>
411
- </header>
412
- )}
379
+ </div>
380
+ </header>
413
381
 
414
- {/* Mobile navigation drawer */}
415
- <div class="site-nav-drawer-backdrop" aria-hidden="true" />
382
+ <div
383
+ class="site-nav-drawer-backdrop"
384
+ data-site-header-fragment="drawer-backdrop"
385
+ aria-hidden="true"
386
+ />
416
387
  <div
417
388
  id="site-nav-drawer"
418
389
  class="site-nav-drawer"
390
+ data-site-header-fragment="drawer"
419
391
  role="dialog"
420
392
  aria-modal="true"
421
393
  aria-label={menuLabel}
@@ -439,12 +411,7 @@ export const SiteLayout: FC<PropsWithChildren<SiteLayoutProps>> = ({
439
411
  <button
440
412
  type="button"
441
413
  class="site-nav-drawer-close"
442
- aria-label={i18n._(
443
- msg({
444
- message: "Close menu",
445
- comment: "@context: Close drawer button label",
446
- }),
447
- )}
414
+ aria-label={closeMenuLabel}
448
415
  >
449
416
  <svg
450
417
  xmlns="http://www.w3.org/2000/svg"
@@ -499,6 +466,72 @@ export const SiteLayout: FC<PropsWithChildren<SiteLayoutProps>> = ({
499
466
  )}
500
467
  </nav>
501
468
  </div>
469
+ </>
470
+ );
471
+ };
472
+
473
+ export const SiteLayout: FC<PropsWithChildren<SiteLayoutProps>> = ({
474
+ siteName,
475
+ links,
476
+ currentPath,
477
+ sitePathPrefix = "",
478
+ isAuthenticated,
479
+ collections,
480
+ siteAvatarUrl,
481
+ showHeaderAvatar,
482
+ siteDescriptionHtml,
483
+ siteFooterHtml,
484
+ showHomeBranding = false,
485
+ sidebar,
486
+ uploadMaxFileSize,
487
+ showComposeDialog = true,
488
+ showHeader = true,
489
+ composeOpenShortcutDiscovered = false,
490
+ slashCommandDiscovered = false,
491
+ composeCollectionId,
492
+ children,
493
+ }) => {
494
+ const { i18n } = useLingui();
495
+ const newPostLabel = i18n._(
496
+ msg({
497
+ message: "New post",
498
+ comment:
499
+ "@context: Mobile floating action button label to open the compose dialog",
500
+ }),
501
+ );
502
+
503
+ const isHomePage =
504
+ currentPath === toPublicPath("/", sitePathPrefix) ||
505
+ currentPath === toPublicPath("/featured", sitePathPrefix) ||
506
+ currentPath === toPublicPath("/latest", sitePathPrefix);
507
+ const showMobileComposeFab = Boolean(
508
+ (isHomePage || composeCollectionId) && isAuthenticated && showComposeDialog,
509
+ );
510
+ const mobileComposeFabClick = composeCollectionId
511
+ ? `document.getElementById('compose-dialog')?.querySelector('jant-compose-dialog')?.openNew(${JSON.stringify(
512
+ { collectionId: composeCollectionId },
513
+ )})`
514
+ : "document.getElementById('compose-dialog')?.querySelector('jant-compose-dialog')?.openNew()";
515
+ const contentClass = [
516
+ "site-content",
517
+ isHomePage ? "site-content-home" : "",
518
+ showMobileComposeFab ? "site-content-mobile-compose-enabled" : "",
519
+ ]
520
+ .filter(Boolean)
521
+ .join(" ");
522
+
523
+ return (
524
+ <div class="site-page">
525
+ {showHeader && (
526
+ <SiteHeader
527
+ siteName={siteName}
528
+ links={links}
529
+ currentPath={currentPath}
530
+ sitePathPrefix={sitePathPrefix}
531
+ siteAvatarUrl={siteAvatarUrl}
532
+ showHeaderAvatar={showHeaderAvatar}
533
+ />
534
+ )}
502
535
 
503
536
  <main class="site-main">
504
537
  {sidebar ? (
@@ -1,6 +0,0 @@
1
- import "./url-BMYO-Zlt.js";
2
- import { t as createApp } from "./app-D-zAhayc.js";
3
- import "./export-Ckwh4BiE.js";
4
- import "./env-OHRKGcMj.js";
5
- import "./github-sync-Dl9wQMar.js";
6
- export { createApp };
@@ -1,54 +0,0 @@
1
- /**
2
- * Nav Manager Bridge
3
- *
4
- * Handles communication between <jant-nav-manager> and the server.
5
- * Listens for `jant:nav-update` and `jant:nav-delete`, calls API endpoints,
6
- * and reloads the page on success.
7
- */
8
-
9
- import type {
10
- NavManagerUpdateDetail,
11
- NavManagerDeleteDetail,
12
- } from "./components/nav-manager-types.js";
13
- import { showToast } from "./toast.js";
14
-
15
- document.addEventListener("jant:nav-update", async (event: Event) => {
16
- const { detail } = event as CustomEvent<NavManagerUpdateDetail>;
17
- if (!detail?.id) return;
18
-
19
- try {
20
- const res = await fetch(`/api/nav-items/${detail.id}`, {
21
- method: "PUT",
22
- headers: {
23
- "Content-Type": "application/json",
24
- Accept: "application/json",
25
- },
26
- body: JSON.stringify({
27
- label: detail.label,
28
- ...(detail.url !== undefined && { url: detail.url }),
29
- }),
30
- });
31
-
32
- if (!res.ok) throw new Error(`HTTP ${res.status}`);
33
- window.location.reload();
34
- } catch {
35
- showToast("Failed to save. Please try again.", "error");
36
- }
37
- });
38
-
39
- document.addEventListener("jant:nav-delete", async (event: Event) => {
40
- const { detail } = event as CustomEvent<NavManagerDeleteDetail>;
41
- if (!detail?.id) return;
42
-
43
- try {
44
- const res = await fetch(`/api/nav-items/${detail.id}`, {
45
- method: "DELETE",
46
- headers: { Accept: "application/json" },
47
- });
48
-
49
- if (!res.ok) throw new Error(`HTTP ${res.status}`);
50
- window.location.reload();
51
- } catch {
52
- showToast("Failed to delete. Please try again.", "error");
53
- }
54
- });