radiant-docs 0.1.61 → 0.1.62

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 (30) hide show
  1. package/package.json +1 -1
  2. package/template/package-lock.json +10 -4
  3. package/template/package.json +11 -2
  4. package/template/scripts/generate-proxy-allowed-origins.mjs +14 -6
  5. package/template/scripts/publish-shiki-platform-assets.mjs +1151 -0
  6. package/template/src/components/Header.astro +6 -1
  7. package/template/src/components/NavigationTabList.astro +65 -0
  8. package/template/src/components/NavigationTabs.astro +109 -0
  9. package/template/src/components/OpenApiPage.astro +17 -1
  10. package/template/src/components/Sidebar.astro +2 -2
  11. package/template/src/components/SidebarDropdown.astro +105 -44
  12. package/template/src/components/SidebarMenu.astro +3 -0
  13. package/template/src/components/SidebarSegmented.astro +87 -52
  14. package/template/src/components/SidebarTabs.astro +86 -0
  15. package/template/src/components/chat/AssistantDocsWidget.tsx +127 -2
  16. package/template/src/components/chat/AssistantEmbedPanel.tsx +269 -283
  17. package/template/src/components/user/Accordion.astro +1 -1
  18. package/template/src/components/user/Callout.astro +2 -2
  19. package/template/src/components/user/CodeBlock.astro +58 -7
  20. package/template/src/components/user/CodeGroup.astro +52 -1
  21. package/template/src/components/user/Column.astro +1 -1
  22. package/template/src/components/user/Step.astro +1 -1
  23. package/template/src/components/user/Tabs.astro +1 -1
  24. package/template/src/generated/shiki-platform-assets.json +24 -0
  25. package/template/src/layouts/Layout.astro +111 -8
  26. package/template/src/lib/assistant-panel-config.ts +59 -0
  27. package/template/src/lib/assistant-shiki-client.ts +506 -0
  28. package/template/src/lib/mdx/remark-resolve-internal-links.ts +334 -17
  29. package/template/src/lib/routes.ts +66 -24
  30. package/template/src/styles/global.css +12 -0
@@ -70,7 +70,7 @@ const TITLE_SIZE_CLASS = {
70
70
  <div class="min-h-0 overflow-hidden">
71
71
  <div
72
72
  data-rd-accordion-content-inner
73
- class="prose-rules max-w-none! *:max-w-none! pb-4!"
73
+ class="prose-rules max-w-none! pb-4!"
74
74
  >
75
75
  <slot />
76
76
  </div>
@@ -108,7 +108,7 @@ const hasTitle = typeof resolvedTitle === "string" && resolvedTitle.length > 0;
108
108
  {resolvedTitle}
109
109
  </div>
110
110
  </div>
111
- <div class="prose-rules prose-sm! max-w-none! *:max-w-none! text-neutral-700 dark:text-neutral-300">
111
+ <div class="prose-rules max-w-none! text-sm! leading-6! text-neutral-700 dark:text-neutral-300">
112
112
  <slot />
113
113
  </div>
114
114
  </>
@@ -129,7 +129,7 @@ const hasTitle = typeof resolvedTitle === "string" && resolvedTitle.length > 0;
129
129
  class="mt-0.5 shrink-0 border border-white"
130
130
  />
131
131
  ) : null}
132
- <div class="prose-rules prose-sm! max-w-none! min-w-0 flex-1 *:max-w-none! text-neutral-700 dark:text-neutral-300">
132
+ <div class="prose-rules max-w-none! min-w-0 flex-1 text-sm! leading-6! text-neutral-700 dark:text-neutral-300">
133
133
  <slot />
134
134
  </div>
135
135
  </div>
@@ -258,7 +258,7 @@ const renderedCodeLinesHtml = normalizedTokenLines
258
258
  ? ' data-rd-code-line-highlighted="true"'
259
259
  : "";
260
260
 
261
- return `<span class="flex min-w-full"${lineHighlightAttribute}>${lineNumberHtml}<span class="${lineContentClass}">${tokenHtml}</span></span>`;
261
+ return `<span class="flex w-max min-w-full"${lineHighlightAttribute}>${lineNumberHtml}<span class="${lineContentClass}">${tokenHtml}</span></span>`;
262
262
  })
263
263
  .join("");
264
264
  ---
@@ -339,7 +339,7 @@ const renderedCodeLinesHtml = normalizedTokenLines
339
339
 
340
340
  <div
341
341
  class:list={[
342
- "relative min-w-0 border-[0.5px] overflow-hidden border-(--rd-code-tab-edge-border)",
342
+ "relative min-w-0 border-[0.5px] overflow-hidden border-(--rd-code-tab-edge-border) bg-(--rd-code-surface)",
343
343
  parsedFlushTop ? "rounded-b-xl" : "rounded-xl",
344
344
  parsedFlushTop && "border-t-0",
345
345
  parsedShowFilename && "border-t-0 rounded-t-none",
@@ -349,10 +349,10 @@ const renderedCodeLinesHtml = normalizedTokenLines
349
349
  >
350
350
  {
351
351
  !parsedInCodeGroup && !parsedShowFilename ? (
352
- <div class="pointer-events-none absolute right-2 top-2 z-20">
352
+ <div class="pointer-events-none absolute right-2 top-2 z-20 [@media(pointer:coarse)]:pointer-events-auto">
353
353
  <button
354
354
  type="button"
355
- class="pointer-events-none inline-flex size-7 appearance-none items-center justify-center rounded-md bg-(--rd-code-surface)/40 backdrop-blur-sm text-neutral-500/80 outline-none ring-0 transition-colors duration-150 hover:text-neutral-700 focus:outline-none focus-visible:outline-none focus:ring-0 focus-visible:ring-0 cursor-pointer group-hover/prose-code:pointer-events-auto group-focus-within/prose-code:pointer-events-auto dark:text-neutral-400 dark:hover:text-neutral-200 relative before:absolute before:inset-1 hover:before:inset-0 before:rounded-md hover:before:bg-neutral-900/4 dark:hover:before:bg-white/4 before:duration-150"
355
+ class="pointer-events-none inline-flex size-7 appearance-none items-center justify-center rounded-md bg-(--rd-code-surface)/40 backdrop-blur-sm text-neutral-500/80 outline-none ring-0 transition-colors duration-150 hover:text-neutral-700 focus:outline-none focus-visible:outline-none focus:ring-0 focus-visible:ring-0 cursor-pointer group-hover/prose-code:pointer-events-auto group-focus-within/prose-code:pointer-events-auto [@media(pointer:coarse)]:pointer-events-auto dark:text-neutral-400 dark:hover:text-neutral-200 relative before:absolute before:inset-1 hover:before:inset-0 before:rounded-md hover:before:bg-neutral-900/4 dark:hover:before:bg-white/4 before:duration-150"
356
356
  data-rd-copy-trigger="true"
357
357
  data-rd-copy-content={encodedRaw}
358
358
  aria-label="Copy code"
@@ -390,10 +390,10 @@ const renderedCodeLinesHtml = normalizedTokenLines
390
390
 
391
391
  <div
392
392
  data-rd-code-scroll-area
393
- class="relative overflow-x-auto [scrollbar-width:thin] [scrollbar-color:var(--color-neutral-300)_transparent] [&::-webkit-scrollbar]:h-1.5 [&::-webkit-scrollbar]:w-1.5 [&::-webkit-scrollbar-track]:bg-transparent [&::-webkit-scrollbar-thumb]:rounded-full [&::-webkit-scrollbar-thumb]:bg-neutral-300/70 hover:[&::-webkit-scrollbar-thumb]:bg-neutral-300/90 dark:[scrollbar-color:var(--color-neutral-700)_transparent] dark:[&::-webkit-scrollbar-thumb]:bg-neutral-700/70 dark:hover:[&::-webkit-scrollbar-thumb]:bg-neutral-700/90"
393
+ class="relative overflow-x-auto overscroll-x-contain bg-(--rd-code-surface) [scrollbar-width:thin] [scrollbar-color:var(--color-neutral-300)_transparent] [&::-webkit-scrollbar]:h-1.5 [&::-webkit-scrollbar]:w-1.5 [&::-webkit-scrollbar-track]:bg-transparent [&::-webkit-scrollbar-thumb]:rounded-full [&::-webkit-scrollbar-thumb]:bg-neutral-300/70 hover:[&::-webkit-scrollbar-thumb]:bg-neutral-300/90 dark:[scrollbar-color:var(--color-neutral-700)_transparent] dark:[&::-webkit-scrollbar-thumb]:bg-neutral-700/70 dark:hover:[&::-webkit-scrollbar-thumb]:bg-neutral-700/90"
394
394
  >
395
395
  <pre
396
- class="relative m-0 min-w-full bg-(--rd-code-surface) p-0 text-[13px] leading-6"><code data-rd-code-theme class="block min-w-full py-2.5 font-mono text-neutral-800 dark:text-neutral-200"><Fragment set:html={renderedCodeLinesHtml} /></code></pre>
396
+ class="relative m-0 w-max min-w-full bg-(--rd-code-surface) p-0 text-[13px] leading-6"><code data-rd-code-theme class="block w-max min-w-full py-2.5 font-mono text-neutral-800 dark:text-neutral-200"><Fragment set:html={renderedCodeLinesHtml} /></code></pre>
397
397
  </div>
398
398
  </div>
399
399
  </div>
@@ -432,6 +432,52 @@ const renderedCodeLinesHtml = normalizedTokenLines
432
432
  checkIcon.classList.add("scale-50", "opacity-0", "rotate-6");
433
433
  };
434
434
 
435
+ const fallbackCopy = (text) => {
436
+ try {
437
+ const textarea = document.createElement("textarea");
438
+ textarea.value = text;
439
+ textarea.setAttribute("readonly", "");
440
+ textarea.style.position = "fixed";
441
+ textarea.style.top = "0";
442
+ textarea.style.left = "0";
443
+ textarea.style.width = "1px";
444
+ textarea.style.height = "1px";
445
+ textarea.style.padding = "0";
446
+ textarea.style.border = "0";
447
+ textarea.style.opacity = "0";
448
+ textarea.style.fontSize = "16px";
449
+ textarea.style.pointerEvents = "none";
450
+ document.body.appendChild(textarea);
451
+
452
+ try {
453
+ textarea.focus({ preventScroll: true });
454
+ } catch {
455
+ textarea.focus();
456
+ }
457
+ textarea.select();
458
+ textarea.setSelectionRange(0, textarea.value.length);
459
+
460
+ const copied = document.execCommand("copy");
461
+ document.body.removeChild(textarea);
462
+ return copied;
463
+ } catch {
464
+ return false;
465
+ }
466
+ };
467
+
468
+ const copyToClipboard = async (text) => {
469
+ try {
470
+ if (navigator.clipboard?.writeText) {
471
+ await navigator.clipboard.writeText(text);
472
+ return true;
473
+ }
474
+ } catch {
475
+ // Fallback below when the async Clipboard API is unavailable or blocked.
476
+ }
477
+
478
+ return fallbackCopy(text);
479
+ };
480
+
435
481
  copyButtons.forEach((button) => {
436
482
  let timeoutId = null;
437
483
  button.addEventListener("click", async () => {
@@ -440,7 +486,12 @@ const renderedCodeLinesHtml = normalizedTokenLines
440
486
  const copyValue = decodeURIComponent(encodedCopyValue);
441
487
 
442
488
  try {
443
- await navigator.clipboard.writeText(copyValue);
489
+ const copied = await copyToClipboard(copyValue);
490
+ if (!copied) {
491
+ setCopiedState(button, false);
492
+ return;
493
+ }
494
+
444
495
  setCopiedState(button, true);
445
496
 
446
497
  if (timeoutId) window.clearTimeout(timeoutId);
@@ -130,6 +130,52 @@ import CodeTabEdge from "../ui/CodeTabEdge.astro";
130
130
  checkIcon.classList.add("scale-50", "opacity-0", "rotate-6");
131
131
  };
132
132
 
133
+ const fallbackCopy = (text) => {
134
+ try {
135
+ const textarea = document.createElement("textarea");
136
+ textarea.value = text;
137
+ textarea.setAttribute("readonly", "");
138
+ textarea.style.position = "fixed";
139
+ textarea.style.top = "0";
140
+ textarea.style.left = "0";
141
+ textarea.style.width = "1px";
142
+ textarea.style.height = "1px";
143
+ textarea.style.padding = "0";
144
+ textarea.style.border = "0";
145
+ textarea.style.opacity = "0";
146
+ textarea.style.fontSize = "16px";
147
+ textarea.style.pointerEvents = "none";
148
+ document.body.appendChild(textarea);
149
+
150
+ try {
151
+ textarea.focus({ preventScroll: true });
152
+ } catch {
153
+ textarea.focus();
154
+ }
155
+ textarea.select();
156
+ textarea.setSelectionRange(0, textarea.value.length);
157
+
158
+ const copied = document.execCommand("copy");
159
+ document.body.removeChild(textarea);
160
+ return copied;
161
+ } catch {
162
+ return false;
163
+ }
164
+ };
165
+
166
+ const copyToClipboard = async (text) => {
167
+ try {
168
+ if (navigator.clipboard?.writeText) {
169
+ await navigator.clipboard.writeText(text);
170
+ return true;
171
+ }
172
+ } catch {
173
+ // Fallback below when the async Clipboard API is unavailable or blocked.
174
+ }
175
+
176
+ return fallbackCopy(text);
177
+ };
178
+
133
179
  const tabs = codeItems.map((itemElement, index) => {
134
180
  const filename =
135
181
  itemElement.getAttribute("data-rd-tab-filename") ??
@@ -411,7 +457,12 @@ import CodeTabEdge from "../ui/CodeTabEdge.astro";
411
457
  const copyValue = decodeURIComponent(encodedCopyValue);
412
458
 
413
459
  try {
414
- await navigator.clipboard.writeText(copyValue);
460
+ const copied = await copyToClipboard(copyValue);
461
+ if (!copied) {
462
+ setCopiedState(copyButton, false);
463
+ return;
464
+ }
465
+
415
466
  setCopiedState(copyButton, true);
416
467
 
417
468
  if (timeoutId) window.clearTimeout(timeoutId);
@@ -2,7 +2,7 @@
2
2
  ---
3
3
 
4
4
  <div data-rd-column class="min-w-0">
5
- <div class="prose-rules max-w-none! *:max-w-none!">
5
+ <div class="prose-rules max-w-none!">
6
6
  <slot />
7
7
  </div>
8
8
  </div>
@@ -27,7 +27,7 @@ const { title } = Astro.props;
27
27
  {title}
28
28
  </h3>
29
29
  </div>
30
- <div class="prose-rules max-w-none! *:max-w-none!">
30
+ <div class="prose-rules max-w-none!">
31
31
  <slot />
32
32
  </div>
33
33
  </div>
@@ -190,7 +190,7 @@ class="rd-prose-block">
190
190
  >
191
191
  <div
192
192
  data-rd-tabs-panel-content
193
- class="prose-rules w-full max-w-none! *:max-w-none!"
193
+ class="prose-rules w-full max-w-none!"
194
194
  set:html={content}
195
195
  />
196
196
  </div>
@@ -0,0 +1,24 @@
1
+ {
2
+ "assetVersion": "shiki-3.23.0-c5695a7656dd",
3
+ "counts": {
4
+ "languages": 347,
5
+ "themes": 66
6
+ },
7
+ "manifest": {
8
+ "bytes": 172640,
9
+ "module": "manifest.json",
10
+ "sha256": "d8ce64a7c4247eac90030e4dd92a9ac00f1de67114e0ac6c5e003e810c8e9cfb"
11
+ },
12
+ "packageVersions": {
13
+ "@shikijs/core": "3.23.0",
14
+ "@shikijs/engine-javascript": "3.23.0",
15
+ "@shikijs/langs": "3.23.0",
16
+ "@shikijs/themes": "3.23.0",
17
+ "shiki": "3.23.0"
18
+ },
19
+ "prefix": "_platform/shiki",
20
+ "runtime": {
21
+ "engine": "javascript-regexp",
22
+ "module": "runtime.mjs"
23
+ }
24
+ }
@@ -4,6 +4,7 @@ import Sidebar from "../components/Sidebar.astro";
4
4
  import { getConfig } from "../lib/validation";
5
5
  import Header from "../components/Header.astro";
6
6
  import Footer from "../components/Footer.astro";
7
+ import NavigationTabs from "../components/NavigationTabs.astro";
7
8
  import AssistantDocsWidget from "../components/chat/AssistantDocsWidget.astro";
8
9
  import { ClientRouter } from "astro:transitions";
9
10
  import { prependBasePath, stripBasePath } from "../lib/base-path";
@@ -18,6 +19,8 @@ interface Props {
18
19
  pageDescription?: string;
19
20
  }
20
21
 
22
+ type TabsPresentation = "topbar" | "sidebar";
23
+
21
24
  function normalizeRoutePath(routePath: string): string {
22
25
  if (!routePath || routePath === "/") return "/";
23
26
  if (!routePath.startsWith("/")) routePath = `/${routePath}`;
@@ -66,6 +69,12 @@ const orgTier =
66
69
  Number.isFinite(parsedOrgTier) && parsedOrgTier > 0 ? parsedOrgTier : 1;
67
70
  const isDev = import.meta.env.DEV;
68
71
  const askAiEnabled = isDev || orgTier >= 3;
72
+ const navigationTabs = config.navigation.tabs as
73
+ | (typeof config.navigation.tabs & { presentation?: TabsPresentation })
74
+ | undefined;
75
+ const hasNavigationTabs =
76
+ Array.isArray(navigationTabs?.items) && navigationTabs.items.length > 0;
77
+ const tabsPresentation = navigationTabs?.presentation ?? "topbar";
69
78
  ---
70
79
 
71
80
  <!doctype html>
@@ -73,7 +82,7 @@ const askAiEnabled = isDev || orgTier >= 3;
73
82
  <head>
74
83
  <style is:inline is:global set:html={neutralPaletteCss}></style>
75
84
  <style is:inline is:global set:html={fontCss}></style>
76
- <ClientRouter />
85
+ <ClientRouter fallback="swap" />
77
86
  <script is:inline>
78
87
  const applyTheme = () => {
79
88
  const modeParam = new URLSearchParams(window.location.search).get(
@@ -192,6 +201,71 @@ const askAiEnabled = isDev || orgTier >= 3;
192
201
  }
193
202
  })();
194
203
  </script>
204
+ <script is:inline>
205
+ (() => {
206
+ const root = document.documentElement;
207
+ let viewportFrameId = null;
208
+
209
+ const syncVisualViewport = () => {
210
+ viewportFrameId = null;
211
+
212
+ const viewport = window.visualViewport;
213
+ const width =
214
+ viewport && Number.isFinite(viewport.width)
215
+ ? viewport.width
216
+ : window.innerWidth;
217
+ const height =
218
+ viewport && Number.isFinite(viewport.height)
219
+ ? viewport.height
220
+ : window.innerHeight;
221
+ const offsetLeft =
222
+ viewport && Number.isFinite(viewport.offsetLeft)
223
+ ? viewport.offsetLeft
224
+ : 0;
225
+ const offsetTop =
226
+ viewport && Number.isFinite(viewport.offsetTop)
227
+ ? viewport.offsetTop
228
+ : 0;
229
+
230
+ root.style.setProperty(
231
+ "--rd-visual-viewport-left",
232
+ `${Math.max(0, offsetLeft)}px`,
233
+ );
234
+ root.style.setProperty(
235
+ "--rd-visual-viewport-top",
236
+ `${Math.max(0, offsetTop)}px`,
237
+ );
238
+ root.style.setProperty(
239
+ "--rd-visual-viewport-width",
240
+ `${Math.max(1, width)}px`,
241
+ );
242
+ root.style.setProperty(
243
+ "--rd-visual-viewport-height",
244
+ `${Math.max(1, height)}px`,
245
+ );
246
+ };
247
+
248
+ const scheduleVisualViewportSync = () => {
249
+ if (viewportFrameId !== null) return;
250
+ viewportFrameId = window.requestAnimationFrame(syncVisualViewport);
251
+ };
252
+
253
+ scheduleVisualViewportSync();
254
+ window.visualViewport?.addEventListener(
255
+ "resize",
256
+ scheduleVisualViewportSync,
257
+ );
258
+ window.visualViewport?.addEventListener(
259
+ "scroll",
260
+ scheduleVisualViewportSync,
261
+ );
262
+ window.addEventListener("resize", scheduleVisualViewportSync);
263
+ window.addEventListener(
264
+ "orientationchange",
265
+ scheduleVisualViewportSync,
266
+ );
267
+ })();
268
+ </script>
195
269
  {
196
270
  fontPreloads.map((font) => (
197
271
  <link
@@ -233,12 +307,10 @@ const askAiEnabled = isDev || orgTier >= 3;
233
307
  <body
234
308
  class="bg-background text-neutral-900 dark:text-white"
235
309
  x-data="{ open: false }"
236
- x-bind:class="open ? 'overflow-hidden touch-none' : ''"
310
+ x-bind:class="open ? 'overflow-hidden' : ''"
237
311
  >
238
312
  <!-- Edges -->
239
- <div
240
- class="fixed top-1 inset-x-1 h-16 -z-10 bg-background-dark"
241
- >
313
+ <div class="fixed top-1 inset-x-1 h-16 -z-10 bg-background-dark">
242
314
  <div class="bg-background w-full h-full rounded-t-2xl"></div>
243
315
  </div>
244
316
  <div
@@ -252,10 +324,21 @@ const askAiEnabled = isDev || orgTier >= 3;
252
324
 
253
325
  <!-- Header -->
254
326
  <Header showAskAiTrigger={askAiEnabled} />
327
+ {
328
+ hasNavigationTabs ? (
329
+ <NavigationTabs
330
+ tabs={navigationTabs!}
331
+ presentation={tabsPresentation}
332
+ />
333
+ ) : null
334
+ }
255
335
 
256
336
  <!-- Desktop Sidebar -->
257
337
  <div
258
- class="bg-background w-[283px] ml-[5px] fixed inset-y-0 mt-17 hidden lg:block border-r border-r-border-light"
338
+ class:list={[
339
+ "bg-background w-[283px] ml-[5px] fixed inset-y-0 hidden lg:block border-r border-r-border-light",
340
+ hasNavigationTabs ? "mt-[112px]" : "mt-17",
341
+ ]}
259
342
  data-pagefind-ignore
260
343
  >
261
344
  <Sidebar />
@@ -265,7 +348,7 @@ const askAiEnabled = isDev || orgTier >= 3;
265
348
  <div
266
349
  x-show="open"
267
350
  x-cloak
268
- class="bg-background mx-[5px] min-h-[calc(100vh-68px)] mt-17 fixed inset-0 lg:hidden z-50 overflow-y-auto [scrollbar-width:none] [&::-webkit-scrollbar]:hidden"
351
+ class="rd-mobile-menu bg-background fixed lg:hidden z-50 overflow-y-auto overscroll-contain [scrollbar-width:none] [&::-webkit-scrollbar]:hidden"
269
352
  x-transition.opacity
270
353
  >
271
354
  <Sidebar askAiEnabled={askAiEnabled} />
@@ -275,11 +358,31 @@ const askAiEnabled = isDev || orgTier >= 3;
275
358
  <div
276
359
  class="mx-1 mt-1 px-4 sm:px-6 lg:pl-[calc(288px+32px)] pt-16 lg:pr-8 bg-background"
277
360
  >
278
- <main class="mx-auto pt-16 pb-16 min-h-[calc(100vh-64px)]">
361
+ <main class="mx-auto pt-16 pb-20 min-h-[calc(100vh-64px)]">
279
362
  <slot />
280
363
  </main>
281
364
  <Footer askAiEnabled={askAiEnabled} />
282
365
  </div>
283
366
  {askAiEnabled ? <AssistantDocsWidget /> : null}
367
+ <style is:global>
368
+ :root {
369
+ --rd-visual-viewport-left: 0px;
370
+ --rd-visual-viewport-top: 0px;
371
+ --rd-visual-viewport-width: 100vw;
372
+ --rd-visual-viewport-height: 100dvh;
373
+ }
374
+
375
+ .rd-mobile-menu {
376
+ --rd-mobile-menu-top-offset: 68px;
377
+ left: calc(var(--rd-visual-viewport-left) + 5px);
378
+ top: calc(
379
+ var(--rd-visual-viewport-top) + var(--rd-mobile-menu-top-offset)
380
+ );
381
+ width: calc(var(--rd-visual-viewport-width) - 10px);
382
+ height: calc(
383
+ var(--rd-visual-viewport-height) - var(--rd-mobile-menu-top-offset)
384
+ );
385
+ }
386
+ </style>
284
387
  </body>
285
388
  </html>
@@ -4,7 +4,13 @@ import {
4
4
  } from "./assistant-chrome";
5
5
  import { withBasePath } from "./base-path";
6
6
  import { getAssistantLauncherIconConfig } from "./assistant-embed-script";
7
+ import type { AssistantShikiRuntimeConfig } from "./assistant-shiki-client";
7
8
  import type { DocsConfig } from "./validation";
9
+ import shikiPlatformAssets from "../generated/shiki-platform-assets.json";
10
+ import {
11
+ DEFAULT_SHIKI_DARK_THEME,
12
+ DEFAULT_SHIKI_LIGHT_THEME,
13
+ } from "radiant-docs-validator/shiki-theme-config";
8
14
 
9
15
  export type AssistantPanelRuntimeConfig = {
10
16
  apiPath: string;
@@ -26,8 +32,60 @@ export type AssistantPanelRuntimeConfig = {
26
32
  emptyStateQuestions?: string[];
27
33
  devProxyToken?: string;
28
34
  chrome: AssistantChromeConfig;
35
+ shiki?: AssistantShikiRuntimeConfig;
29
36
  };
30
37
 
38
+ function normalizeStaticAssetHost(value: unknown): string {
39
+ const rawValue = typeof value === "string" ? value.trim() : "";
40
+ if (!rawValue) return "";
41
+
42
+ const normalizedHost = rawValue.replace(/\/+$/, "");
43
+ return /^https?:\/\//i.test(normalizedHost)
44
+ ? normalizedHost
45
+ : `https://${normalizedHost}`;
46
+ }
47
+
48
+ function joinUrl(baseUrl: string, path: string): string {
49
+ return `${baseUrl.replace(/\/+$/, "")}/${path.replace(/^\/+/, "")}`;
50
+ }
51
+
52
+ function getConfiguredCodeSyntaxThemes(
53
+ config: DocsConfig,
54
+ ): AssistantShikiRuntimeConfig["syntaxThemes"] {
55
+ const configuredSyntaxTheme = config.theme?.code?.syntaxTheme;
56
+
57
+ if (typeof configuredSyntaxTheme === "string") {
58
+ return {
59
+ light: configuredSyntaxTheme,
60
+ dark: configuredSyntaxTheme,
61
+ };
62
+ }
63
+
64
+ return {
65
+ light: configuredSyntaxTheme?.light ?? DEFAULT_SHIKI_LIGHT_THEME,
66
+ dark: configuredSyntaxTheme?.dark ?? DEFAULT_SHIKI_DARK_THEME,
67
+ };
68
+ }
69
+
70
+ function getAssistantShikiRuntimeConfig(
71
+ config: DocsConfig,
72
+ ): AssistantShikiRuntimeConfig | undefined {
73
+ const staticAssetHost = normalizeStaticAssetHost(
74
+ import.meta.env.STATIC_ASSET_HOST ?? process.env.STATIC_ASSET_HOST,
75
+ );
76
+ if (!staticAssetHost) {
77
+ return undefined;
78
+ }
79
+
80
+ return {
81
+ assetBaseUrl: joinUrl(
82
+ staticAssetHost,
83
+ `${shikiPlatformAssets.prefix}/${shikiPlatformAssets.assetVersion}`,
84
+ ),
85
+ syntaxThemes: getConfiguredCodeSyntaxThemes(config),
86
+ };
87
+ }
88
+
31
89
  export function getAssistantPanelRuntimeConfig(
32
90
  config: DocsConfig,
33
91
  ): AssistantPanelRuntimeConfig {
@@ -76,5 +134,6 @@ export function getAssistantPanelRuntimeConfig(
76
134
  emptyStateQuestions: assistantConfig?.questions,
77
135
  devProxyToken: isDev ? assistantDevProxySecret : undefined,
78
136
  chrome: getAssistantChromeConfig(config),
137
+ shiki: getAssistantShikiRuntimeConfig(config),
79
138
  };
80
139
  }