@jant/core 0.6.10 → 0.6.12

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 (92) hide show
  1. package/bin/commands/import-site.js +45 -20
  2. package/dist/app-B5P1b4TF.js +6 -0
  3. package/dist/{app-CGHkOdme.js → app-D-zAhayc.js} +847 -244
  4. package/dist/client/.vite/manifest.json +3 -3
  5. package/dist/client/_assets/client-BUxR-E8O.css +2 -0
  6. package/dist/client/_assets/{client-DYrWuaIk.js → client-S1cdvulk.js} +1 -1
  7. package/dist/client/_assets/{client-auth-B5Re0uCd.js → client-auth-YW92ZH7u.js} +158 -97
  8. package/dist/{export-DY1v5Iqu.js → export-Ckwh4BiE.js} +96 -96
  9. package/dist/{github-sync-LefaslGJ.js → github-sync-BMmK3JZ0.js} +2 -2
  10. package/dist/{github-sync-2_T7nbOv.js → github-sync-Dl9wQMar.js} +1 -1
  11. package/dist/index.js +3 -3
  12. package/dist/node.js +4 -4
  13. package/package.json +1 -1
  14. package/src/__tests__/import-site-command.test.ts +75 -0
  15. package/src/__tests__/vite-dev-plugins.test.ts +34 -0
  16. package/src/client/__tests__/compose-launch.test.ts +50 -1
  17. package/src/client/__tests__/compose-shortcuts.test.ts +74 -18
  18. package/src/client/components/__tests__/jant-command-palette.test.ts +63 -0
  19. package/src/client/components/__tests__/jant-compose-dialog.test.ts +33 -0
  20. package/src/client/components/__tests__/jant-compose-editor-rehost-notice.test.ts +81 -0
  21. package/src/client/components/__tests__/jant-nav-manager.test.ts +77 -1
  22. package/src/client/components/__tests__/jant-settings-avatar.test.ts +6 -0
  23. package/src/client/components/__tests__/jant-settings-general.test.ts +71 -0
  24. package/src/client/components/compose-types.ts +4 -0
  25. package/src/client/components/jant-command-palette.ts +67 -2
  26. package/src/client/components/jant-compose-dialog.ts +8 -2
  27. package/src/client/components/jant-compose-editor.ts +111 -0
  28. package/src/client/components/jant-nav-manager.ts +142 -2
  29. package/src/client/components/jant-settings-general.ts +56 -0
  30. package/src/client/components/nav-manager-types.ts +14 -0
  31. package/src/client/components/settings-types.ts +30 -0
  32. package/src/client/compose-bridge.ts +25 -8
  33. package/src/client/compose-launch.ts +30 -0
  34. package/src/client/compose-shortcuts.ts +46 -3
  35. package/src/client/tiptap/__tests__/inline-image-upload.test.ts +143 -0
  36. package/src/client/tiptap/__tests__/paste-rehost-e2e.test.ts +65 -0
  37. package/src/client/tiptap/__tests__/rehost-images.test.ts +192 -0
  38. package/src/client/tiptap/create-editor.ts +3 -0
  39. package/src/client/tiptap/extensions.ts +4 -0
  40. package/src/client/tiptap/inline-image-upload.ts +174 -50
  41. package/src/client/tiptap/rehost-images.ts +108 -0
  42. package/src/i18n/__tests__/fallback.test.ts +24 -0
  43. package/src/i18n/locales/public/en.po +10 -0
  44. package/src/i18n/locales/public/en.ts +1 -1
  45. package/src/i18n/locales/public/zh-Hans.po +10 -0
  46. package/src/i18n/locales/public/zh-Hans.ts +1 -1
  47. package/src/i18n/locales/public/zh-Hant.po +10 -0
  48. package/src/i18n/locales/public/zh-Hant.ts +1 -1
  49. package/src/i18n/locales/settings/en.po +54 -0
  50. package/src/i18n/locales/settings/en.ts +1 -1
  51. package/src/i18n/locales/settings/zh-Hans.po +64 -10
  52. package/src/i18n/locales/settings/zh-Hans.ts +1 -1
  53. package/src/i18n/locales/settings/zh-Hant.po +56 -2
  54. package/src/i18n/locales/settings/zh-Hant.ts +1 -1
  55. package/src/lib/__tests__/feed.test.ts +96 -10
  56. package/src/lib/__tests__/upload-sideload.test.ts +78 -0
  57. package/src/lib/__tests__/url-fetch.test.ts +181 -0
  58. package/src/lib/feed.ts +63 -3
  59. package/src/lib/post-display.ts +14 -8
  60. package/src/lib/upload.ts +111 -0
  61. package/src/lib/url-fetch.ts +263 -0
  62. package/src/preset.css +60 -18
  63. package/src/routes/api/__tests__/uploads.test.ts +63 -1
  64. package/src/routes/api/uploads.ts +52 -0
  65. package/src/routes/dash/settings.tsx +41 -2
  66. package/src/routes/pages/__tests__/draft-about-edit.test.ts +65 -0
  67. package/src/routes/pages/page.tsx +44 -12
  68. package/src/services/__tests__/about-page.test.ts +116 -0
  69. package/src/services/__tests__/media.test.ts +168 -1
  70. package/src/services/__tests__/navigation.test.ts +190 -1
  71. package/src/services/__tests__/post-timeline.test.ts +119 -2
  72. package/src/services/about-page.ts +143 -0
  73. package/src/services/export-theme/layouts/_default/rss.xml +2 -1
  74. package/src/services/export-theme/layouts/partials/feed-post-content.xml +25 -1
  75. package/src/services/export-theme/styles/main.css +46 -14
  76. package/src/services/index.ts +27 -17
  77. package/src/services/media.ts +111 -0
  78. package/src/services/navigation.ts +155 -1
  79. package/src/services/post.ts +24 -7
  80. package/src/styles/components.css +20 -0
  81. package/src/styles/tokens.css +26 -8
  82. package/src/styles/ui.css +149 -7
  83. package/src/types/views.ts +13 -0
  84. package/src/ui/compose/ComposeDialog.tsx +16 -0
  85. package/src/ui/dash/appearance/NavigationContent.tsx +57 -0
  86. package/src/ui/dash/appearance/__tests__/NavigationContent.test.tsx +19 -0
  87. package/src/ui/dash/settings/GeneralContent.tsx +44 -0
  88. package/src/ui/dash/settings/__tests__/GeneralContent.test.tsx +8 -0
  89. package/src/ui/feed/__tests__/timeline-cards.test.ts +162 -1
  90. package/src/ui/layouts/BaseLayout.tsx +12 -0
  91. package/dist/app-D24n0DoH.js +0 -6
  92. package/dist/client/_assets/client-xWDl78yi.css +0 -2
@@ -7,12 +7,14 @@ import { useLingui } from "../../../i18n/context.js";
7
7
  import type {
8
8
  CollectionsDirectoryData,
9
9
  NavItem,
10
+ SuggestedNavLink,
10
11
  SystemNavKey,
11
12
  } from "../../../types.js";
12
13
  import { SYSTEM_NAV_KEYS } from "../../../types.js";
13
14
  import type {
14
15
  NavManagerCollection,
15
16
  NavManagerLabels,
17
+ NavManagerSuggestedLink,
16
18
  SystemNavConfig,
17
19
  } from "../../../client/components/nav-manager-types.js";
18
20
  import { toPublicHref, toPublicPath } from "../../../lib/url.js";
@@ -30,12 +32,14 @@ import {
30
32
  export function NavigationContent({
31
33
  navItems,
32
34
  directoryData,
35
+ suggestedLinks,
33
36
  mainRssFeed,
34
37
  siteName,
35
38
  sitePathPrefix = "",
36
39
  }: {
37
40
  navItems: NavItem[];
38
41
  directoryData: CollectionsDirectoryData;
42
+ suggestedLinks: SuggestedNavLink[];
39
43
  mainRssFeed: string;
40
44
  siteName: string;
41
45
  sitePathPrefix?: string;
@@ -61,6 +65,27 @@ export function NavigationContent({
61
65
  );
62
66
  const moreLabel = i18n._(NAV_MORE_LABEL);
63
67
 
68
+ const suggestedTargetLabels = {
69
+ page: i18n._(
70
+ msg({
71
+ message: "Page",
72
+ comment: "@context: Suggested navigation link target type",
73
+ }),
74
+ ),
75
+ collection: i18n._(
76
+ msg({
77
+ message: "Collection",
78
+ comment: "@context: Suggested navigation link target type",
79
+ }),
80
+ ),
81
+ archive: i18n._(
82
+ msg({
83
+ message: "Archive",
84
+ comment: "@context: Suggested navigation link target type",
85
+ }),
86
+ ),
87
+ } satisfies Record<SuggestedNavLink["targetType"], string>;
88
+
64
89
  // Serialize nav items for the Lit component
65
90
  const itemsData = navItems.map((item) => {
66
91
  // System link URLs are always computed from constants, never from DB
@@ -110,6 +135,13 @@ export function NavigationContent({
110
135
  return result;
111
136
  })();
112
137
 
138
+ const suggestedLinksData: NavManagerSuggestedLink[] = suggestedLinks.map(
139
+ (link) => ({
140
+ ...link,
141
+ targetLabel: suggestedTargetLabels[link.targetType],
142
+ }),
143
+ );
144
+
113
145
  // Build system nav config array for the Lit component
114
146
  const systemNavData: SystemNavConfig[] = (
115
147
  Object.keys(SYSTEM_NAV_KEYS) as SystemNavKey[]
@@ -297,6 +329,30 @@ export function NavigationContent({
297
329
  comment: "@context: Error toast when nav link fields are empty",
298
330
  }),
299
331
  ),
332
+ suggestedLinks: i18n._(
333
+ msg({
334
+ message: "Suggested links",
335
+ comment: "@context: Section heading for suggested nav links",
336
+ }),
337
+ ),
338
+ suggestedLinksDescription: i18n._(
339
+ msg({
340
+ message: "Add common destinations that already exist on your site.",
341
+ comment: "@context: Description for suggested nav links",
342
+ }),
343
+ ),
344
+ addSuggestedLink: i18n._(
345
+ msg({
346
+ message: "Add",
347
+ comment: "@context: Button to add a suggested nav link",
348
+ }),
349
+ ),
350
+ suggestedLinkAdded: i18n._(
351
+ msg({
352
+ message: "Link added to navigation.",
353
+ comment: "@context: Toast after adding a suggested nav link",
354
+ }),
355
+ ),
300
356
  collection: i18n._(
301
357
  msg({
302
358
  message: "collection",
@@ -356,6 +412,7 @@ export function NavigationContent({
356
412
  labels={escapeJson(labels)}
357
413
  system-nav-items={escapeJson(systemNavData)}
358
414
  collections={escapeJson(collectionsData)}
415
+ suggested-links={escapeJson(suggestedLinksData)}
359
416
  site-name={siteName}
360
417
  >
361
418
  {/* SSR fallback: static preview until JS hydrates */}
@@ -22,6 +22,7 @@ function renderNavigationContent(
22
22
  NavigationContent({
23
23
  navItems: [],
24
24
  directoryData: { collections: [], items: [], directoryItems: [] },
25
+ suggestedLinks: [],
25
26
  mainRssFeed: "latest",
26
27
  siteName: "Test Site",
27
28
  ...props,
@@ -51,4 +52,22 @@ describe("NavigationContent", () => {
51
52
 
52
53
  expect(html).toContain("Built-in links");
53
54
  });
55
+
56
+ it("serializes suggested links with translated target labels", () => {
57
+ const html = renderNavigationContent({
58
+ suggestedLinks: [
59
+ {
60
+ key: "now",
61
+ label: "Now",
62
+ url: "/now",
63
+ targetType: "collection",
64
+ navItemType: "collection",
65
+ collectionId: "col_now",
66
+ },
67
+ ],
68
+ });
69
+
70
+ expect(html).toContain("suggested-links=");
71
+ expect(html).toContain("&quot;targetLabel&quot;:&quot;Collection&quot;");
72
+ });
54
73
  });
@@ -10,6 +10,7 @@
10
10
  import { msg } from "@lingui/core/macro";
11
11
  import { useLingui } from "../../../i18n/context.js";
12
12
  import type { TimezoneEntry } from "../../../lib/timezones.js";
13
+ import type { AboutPageStatus } from "../../../services/about-page.js";
13
14
 
14
15
  export function GeneralContent({
15
16
  siteName,
@@ -30,6 +31,9 @@ export function GeneralContent({
30
31
  noindex,
31
32
  demoMode,
32
33
  timezones,
34
+ aboutPage,
35
+ aboutEditUrl,
36
+ aboutCreateUrl,
33
37
  }: {
34
38
  siteName: string;
35
39
  siteDescription: string;
@@ -49,6 +53,9 @@ export function GeneralContent({
49
53
  noindex: boolean;
50
54
  demoMode: boolean;
51
55
  timezones: TimezoneEntry[];
56
+ aboutPage: AboutPageStatus;
57
+ aboutEditUrl: string;
58
+ aboutCreateUrl: string;
52
59
  }) {
53
60
  const { i18n } = useLingui();
54
61
 
@@ -65,6 +72,38 @@ export function GeneralContent({
65
72
  comment: "@context: Settings subsection heading for basic site fields",
66
73
  }),
67
74
  ),
75
+ aboutPage: i18n._(
76
+ msg({
77
+ message: "About page",
78
+ comment: "@context: Link label for editing or creating the About page",
79
+ }),
80
+ ),
81
+ aboutPagePrompt: i18n._(
82
+ msg({
83
+ message: "Want to write a fuller introduction?",
84
+ comment:
85
+ "@context: Prompt shown below the short site description, before the About page action",
86
+ }),
87
+ ),
88
+ createAboutPage: i18n._(
89
+ msg({
90
+ message: "Create About page",
91
+ comment: "@context: Button to create the standard About page",
92
+ }),
93
+ ),
94
+ editAboutPage: i18n._(
95
+ msg({
96
+ message: "Edit About page",
97
+ comment: "@context: Link to edit the standard About page",
98
+ }),
99
+ ),
100
+ aboutPageConflict: i18n._(
101
+ msg({
102
+ message:
103
+ "/about is already used. Rename that item before creating an About page.",
104
+ comment: "@context: Compact conflict message when /about is occupied",
105
+ }),
106
+ ),
68
107
  languageAndTime: i18n._(
69
108
  msg({
70
109
  message: "Language & Time",
@@ -369,6 +408,8 @@ export function GeneralContent({
369
408
  { value: "ko", label: "\uD55C\uAD6D\uC5B4 (Korean)" },
370
409
  ]).replace(/</g, "\\u003c");
371
410
 
411
+ const aboutPageJson = JSON.stringify(aboutPage).replace(/</g, "\\u003c");
412
+
372
413
  const initialData = JSON.stringify({
373
414
  siteName,
374
415
  siteDescription,
@@ -397,6 +438,9 @@ export function GeneralContent({
397
438
  featured-feed-url={featuredFeedUrl}
398
439
  archive-feed-url={archiveFeedUrl}
399
440
  demo-mode={demoMode || undefined}
441
+ about-page={aboutPageJson}
442
+ about-edit-url={aboutEditUrl}
443
+ about-create-url={aboutCreateUrl}
400
444
  >
401
445
  {/* SSR fallback skeleton */}
402
446
  <div>
@@ -37,6 +37,8 @@ function createProps(
37
37
  siteName: "My Blog",
38
38
  siteDescription: "A test blog",
39
39
  siteLanguage: "en",
40
+ dashboardLanguage: "en",
41
+ cjkSerifFont: "off",
40
42
  siteNameFallback: "Fallback Name",
41
43
  siteDescriptionFallback: "Fallback Description",
42
44
  mainRssFeed: "featured" as const,
@@ -49,6 +51,12 @@ function createProps(
49
51
  showJantBrandingOnHome: false,
50
52
  noindex: false,
51
53
  demoMode,
54
+ aboutPage: {
55
+ state: "missing" as const,
56
+ path: "/about" as const,
57
+ },
58
+ aboutEditUrl: "/about?edit=1",
59
+ aboutCreateUrl: "/settings/general/about-page",
52
60
  timezones: [
53
61
  {
54
62
  value: "UTC",
@@ -104,13 +104,43 @@ describe("timeline cards", () => {
104
104
  });
105
105
 
106
106
  it("preserves authored line breaks in quote cards", () => {
107
+ const tokens = readFileSync(
108
+ new URL("../../../styles/tokens.css", import.meta.url),
109
+ "utf8",
110
+ );
107
111
  const css = readFileSync(
108
112
  new URL("../../../styles/ui.css", import.meta.url),
109
113
  "utf8",
110
114
  );
115
+ const exportCss = readFileSync(
116
+ new URL(
117
+ "../../../services/export-theme/styles/main.css",
118
+ import.meta.url,
119
+ ),
120
+ "utf8",
121
+ );
111
122
 
123
+ expect(tokens).toMatch(
124
+ /--feed-note-title-size:\s*calc\(var\(--type-content-body\) \* 1\.36\);/,
125
+ );
126
+ expect(tokens).toMatch(
127
+ /--type-content-quote:\s*calc\(var\(--type-content-body\) \* 1\.16\);/,
128
+ );
129
+ expect(tokens).toMatch(/--type-content-quote-leading:\s*1\.4;/);
130
+ expect(css).toMatch(
131
+ /\.feed-note-title\s*\{[\s\S]*font-size:\s*var\(--feed-note-title-size\);/,
132
+ );
133
+ expect(css).toMatch(
134
+ /\.feed-link-title\s*\{[\s\S]*font-size:\s*var\(--feed-note-title-size\);/,
135
+ );
136
+ expect(exportCss).toMatch(
137
+ /\.post-card-title\s*\{[\s\S]*font-size:\s*var\(--feed-note-title-size\);/,
138
+ );
112
139
  expect(css).toMatch(
113
- /\.feed-quote-content\s*\{[\s\S]*white-space:\s*pre-line;/,
140
+ /\.feed-quote-content\s*\{[\s\S]*font-size:\s*var\(--type-content-quote\);[\s\S]*line-height:\s*var\(--type-content-quote-leading\);[\s\S]*white-space:\s*pre-line;/,
141
+ );
142
+ expect(exportCss).toMatch(
143
+ /\.post-card-quote-content\s*\{[\s\S]*font-size:\s*var\(--type-content-quote\);[\s\S]*line-height:\s*var\(--type-content-quote-leading\);[\s\S]*white-space:\s*pre-line;/,
114
144
  );
115
145
  });
116
146
 
@@ -136,6 +166,123 @@ describe("timeline cards", () => {
136
166
  expect(css).toContain("margin-bottom: 0;");
137
167
  });
138
168
 
169
+ it("keeps prose list rhythm compact in live and exported reading", () => {
170
+ const presetCss = readFileSync(
171
+ new URL("../../../preset.css", import.meta.url),
172
+ "utf8",
173
+ );
174
+ const exportCss = readFileSync(
175
+ new URL(
176
+ "../../../services/export-theme/styles/main.css",
177
+ import.meta.url,
178
+ ),
179
+ "utf8",
180
+ );
181
+
182
+ expect(presetCss).toMatch(
183
+ /:where\(ul,\s*ol\)\s*\{[\s\S]*padding-left:\s*1\.4em;/,
184
+ );
185
+ expect(exportCss).toMatch(
186
+ /ul,\s*[\s\S]*ol\s*\{[\s\S]*padding-left:\s*1\.4em;/,
187
+ );
188
+ expect(presetCss).toMatch(
189
+ /:where\(li,\s*dt\)\s*\{[\s\S]*margin-top:\s*0\.65em;[\s\S]*margin-bottom:\s*0\.65em;/,
190
+ );
191
+ expect(exportCss).toMatch(
192
+ /li\s*\{[\s\S]*margin-top:\s*0\.65em;[\s\S]*margin-bottom:\s*0\.65em;/,
193
+ );
194
+ });
195
+
196
+ it("uses dedicated markdown code surfaces in live and exported prose", () => {
197
+ const presetCss = readFileSync(
198
+ new URL("../../../preset.css", import.meta.url),
199
+ "utf8",
200
+ );
201
+ const tokenCss = readFileSync(
202
+ new URL("../../../styles/tokens.css", import.meta.url),
203
+ "utf8",
204
+ );
205
+ const exportCss = readFileSync(
206
+ new URL(
207
+ "../../../services/export-theme/styles/main.css",
208
+ import.meta.url,
209
+ ),
210
+ "utf8",
211
+ );
212
+
213
+ expect(tokenCss).toContain("--site-code-bg:");
214
+ expect(tokenCss).toContain("--site-code-block-bg:");
215
+ expect(tokenCss).toContain(
216
+ "--type-code: calc(var(--type-body-size) * 0.94);",
217
+ );
218
+ expect(tokenCss).toContain(
219
+ "--type-code-block: calc(var(--type-body-size) * 0.9);",
220
+ );
221
+ expect(presetCss).toContain("background-color: var(--site-code-bg);");
222
+ expect(presetCss).toContain("background-color: var(--site-code-block-bg);");
223
+ expect(exportCss).toContain("background-color: var(--site-code-bg);");
224
+ expect(exportCss).toContain("background-color: var(--site-code-block-bg);");
225
+ });
226
+
227
+ it("keeps markdown h2 and h3 headings upright across surfaces", () => {
228
+ const presetCss = readFileSync(
229
+ new URL("../../../preset.css", import.meta.url),
230
+ "utf8",
231
+ );
232
+ const uiCss = readFileSync(
233
+ new URL("../../../styles/ui.css", import.meta.url),
234
+ "utf8",
235
+ );
236
+ const exportCss = readFileSync(
237
+ new URL(
238
+ "../../../services/export-theme/styles/main.css",
239
+ import.meta.url,
240
+ ),
241
+ "utf8",
242
+ );
243
+
244
+ expect(presetCss).not.toMatch(
245
+ /:where\(h[23]\)\s*\{[^}]*font-style:\s*italic;/,
246
+ );
247
+ expect(uiCss).not.toMatch(
248
+ /\.compose-tiptap-body\s+\.tiptap\s+h[23]\s*\{[^}]*font-style:\s*italic;/,
249
+ );
250
+ expect(exportCss).not.toMatch(/h[23]\s*\{[^}]*font-style:\s*italic;/);
251
+ });
252
+
253
+ it("compacts authored headings inside feed body prose", () => {
254
+ const presetCss = readFileSync(
255
+ new URL("../../../preset.css", import.meta.url),
256
+ "utf8",
257
+ );
258
+ const exportCss = readFileSync(
259
+ new URL(
260
+ "../../../services/export-theme/styles/main.css",
261
+ import.meta.url,
262
+ ),
263
+ "utf8",
264
+ );
265
+
266
+ expect(presetCss).toContain(
267
+ '[data-post]:not([data-page="post"]) [data-post-body].prose',
268
+ );
269
+ expect(presetCss).toMatch(
270
+ /:where\(h1,\s*h2\)\s*\{[\s\S]*font-size:\s*calc\(var\(--type-content-body\) \* 1\.12\);/,
271
+ );
272
+ expect(presetCss).toMatch(
273
+ /:where\(h3,\s*h4\)\s*\{[\s\S]*font-size:\s*var\(--type-content-body\);/,
274
+ );
275
+ expect(presetCss).toMatch(
276
+ /:where\(h5,\s*h6\)\s*\{[\s\S]*font-size:\s*var\(--type-secondary\);/,
277
+ );
278
+ expect(exportCss).toMatch(
279
+ /\.post-card > \.post-card-summary :is\(h1,\s*h2\),/,
280
+ );
281
+ expect(exportCss).toMatch(
282
+ /\.post-card > \.post-card-summary :is\(h5,\s*h6\),/,
283
+ );
284
+ });
285
+
139
286
  it("keeps quote footers on the shared card spacing baseline", () => {
140
287
  const css = readFileSync(
141
288
  new URL("../../../styles/ui.css", import.meta.url),
@@ -266,6 +413,20 @@ describe("timeline cards", () => {
266
413
  );
267
414
  });
268
415
 
416
+ it("keeps titled detail headers spaced as one reading group", () => {
417
+ const css = readFileSync(
418
+ new URL("../../../styles/ui.css", import.meta.url),
419
+ "utf8",
420
+ );
421
+
422
+ expect(css).toMatch(
423
+ /\.post-header-block\s*\{[\s\S]*margin-bottom:\s*1\.7rem;/,
424
+ );
425
+ expect(css).toMatch(
426
+ /\.post-header-block-detail\s*\{[\s\S]*gap:\s*0\.7rem;/,
427
+ );
428
+ });
429
+
269
430
  it("renders titled note feed summaries as secondary prose without shrinking detail reading", () => {
270
431
  const post = createPostView({
271
432
  format: "note",
@@ -18,6 +18,7 @@ import {
18
18
  toPublicAssetPath,
19
19
  } from "../../lib/asset-path.js";
20
20
  import { getJantIconHref } from "../../lib/jant-branding.js";
21
+ import { getPublicUrlForProvider } from "../../lib/image.js";
21
22
  import { getThemeBrowserColors, resolveBuiltinTheme } from "../../lib/theme.js";
22
23
  import { toAbsoluteAssetUrl, toPublicPath } from "../../lib/url.js";
23
24
  import {
@@ -133,6 +134,16 @@ export const BaseLayout: FC<PropsWithChildren<BaseLayoutProps>> = ({
133
134
  const assetBasePath = IS_VITE_DEV
134
135
  ? "/"
135
136
  : appConfig?.assetBasePath || getPublicAssetBasePath(sitePathPrefix);
137
+ // Public base URL for the active media provider, exposed so the client can
138
+ // tell which pasted images are already ours and skip rehosting them. Empty
139
+ // means media is served same-origin (the client's same-origin check covers it).
140
+ const mediaBase =
141
+ getPublicUrlForProvider(
142
+ appConfig?.storageDriver ?? "",
143
+ appConfig?.r2PublicUrl,
144
+ appConfig?.s3PublicUrl,
145
+ appConfig?.localPublicUrl,
146
+ ) ?? "";
136
147
  const currentUrl = c ? c.get("publicRequestUrl") : undefined;
137
148
  const rawPath = c?.req?.path ?? "/";
138
149
  const manifestStartPath = sitePathPrefix
@@ -281,6 +292,7 @@ export const BaseLayout: FC<PropsWithChildren<BaseLayoutProps>> = ({
281
292
  data-theme-mode={themeMode}
282
293
  data-site-path-prefix={sitePathPrefix}
283
294
  data-asset-base-path={assetBasePath}
295
+ data-media-base={mediaBase}
284
296
  >
285
297
  <head>
286
298
  <meta charset="UTF-8" />
@@ -1,6 +0,0 @@
1
- import "./url-BMYO-Zlt.js";
2
- import { t as createApp } from "./app-CGHkOdme.js";
3
- import "./export-DY1v5Iqu.js";
4
- import "./env-OHRKGcMj.js";
5
- import "./github-sync-2_T7nbOv.js";
6
- export { createApp };