@netlify/spark-ui 0.0.46-alpha.4 → 0.0.47-alpha

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 (40) hide show
  1. package/dist/analytics/analytics.d.ts +1 -1
  2. package/dist/assets/ChatApp.css +1 -0
  3. package/dist/chunks/compat.module.BjTzMHXJ.js +296 -0
  4. package/dist/chunks/hooks.module.DeYLzLnU.js +177 -0
  5. package/dist/chunks/{useTimeout.DOkYFG64.js → useTimeout.DYuvQwFN.js} +1 -1
  6. package/dist/components/index.d.ts +1 -0
  7. package/dist/components/preact/ChatApp/ChatApp.d.ts +2 -0
  8. package/dist/components/preact/ChatApp/ChatApp.js +5346 -0
  9. package/dist/components/preact/Modal/Modal.js +1 -1
  10. package/dist/components/preact/SearchModal/components/SearchInput.js +2 -2
  11. package/dist/components/preact/SearchModal/components/SearchLink/SearchLink.js +1 -1
  12. package/dist/components/preact/SearchModal/components/SearchModal/SearchModal.js +1 -1
  13. package/dist/components/preact/SearchModal/components/SearchToggle/SearchToggle.js +1 -1
  14. package/dist/components/preact/SearchModal/components/SearchWindow.js +1 -1
  15. package/dist/components/preact/SearchModal/hooks/useKeyboardNav.js +1 -1
  16. package/dist/components/preact/SearchModal/hooks/useSearch.js +2 -2
  17. package/dist/components/preact/SearchModal/hooks/useSearchHotkeys.js +1 -1
  18. package/dist/components/preact/SearchModal/hooks/useSearchLogger.d.ts +4 -4
  19. package/dist/components/preact/SearchModal/hooks/useSearchLogger.js +18 -18
  20. package/dist/components/preact/SearchModal/hooks/useSearchParams.js +1 -1
  21. package/dist/components/preact/SearchModal/hooks/useSearchToggles.js +1 -1
  22. package/dist/components/preact/SearchModal/types/search.types.d.ts +6 -0
  23. package/dist/components/preact/index.d.ts +1 -0
  24. package/dist/components/preact/index.js +7 -5
  25. package/package.json +4 -1
  26. package/packages/analytics/GoogleAnalytics.astro +16 -9
  27. package/packages/analytics/GoogleTagManager.astro +9 -7
  28. package/packages/analytics/GoogleTagManagerNoscript.astro +12 -9
  29. package/packages/analytics/analytics.ts +2 -2
  30. package/packages/components/CodeBlock.astro +208 -0
  31. package/packages/components/index.ts +1 -0
  32. package/packages/components/preact/ChatApp/ChatApp.css +142 -0
  33. package/packages/components/preact/ChatApp/ChatApp.tsx +237 -0
  34. package/packages/components/preact/SearchModal/hooks/useSearchLogger.ts +15 -18
  35. package/packages/components/preact/SearchModal/types/search.types.tsx +7 -0
  36. package/packages/components/preact/index.ts +1 -0
  37. package/packages/global.d.ts +5 -0
  38. package/packages/styles/code-block.css +249 -0
  39. package/dist/chunks/compat.module.DT16W4Wr.js +0 -175
  40. package/dist/chunks/hooks.module.CNV9mxng.js +0 -138
@@ -1,5 +1,5 @@
1
1
  import { u as l } from "../../../chunks/jsxRuntime.module.Bzuv3cXw.js";
2
- import { D as d } from "../../../chunks/compat.module.DT16W4Wr.js";
2
+ import { D as d } from "../../../chunks/compat.module.BjTzMHXJ.js";
3
3
  const u = d(function({ children: o, className: r, ...a }, t) {
4
4
  return /* @__PURE__ */ l("dialog", { ref: t, className: r, role: "dialog", "aria-modal": "true", ...a, children: o });
5
5
  });
@@ -1,6 +1,6 @@
1
1
  import { u as e } from "../../../../chunks/jsxRuntime.module.Bzuv3cXw.js";
2
- import { d as u, y as h } from "../../../../chunks/hooks.module.CNV9mxng.js";
3
- import { u as o } from "../../../../chunks/useTimeout.DOkYFG64.js";
2
+ import { d as u, y as h } from "../../../../chunks/hooks.module.DeYLzLnU.js";
3
+ import { u as o } from "../../../../chunks/useTimeout.DYuvQwFN.js";
4
4
  import d from "../../Icon/index.js";
5
5
  const I = (a) => {
6
6
  const [r, t] = u(""), { reset: c, run: i } = o((l) => {
@@ -1,5 +1,5 @@
1
1
  import { u as a } from "../../../../../chunks/jsxRuntime.module.Bzuv3cXw.js";
2
- import "../../../../../chunks/hooks.module.CNV9mxng.js";
2
+ import "../../../../../chunks/hooks.module.DeYLzLnU.js";
3
3
  import { useSearchLogger as h } from "../../hooks/useSearchLogger.js";
4
4
  import c from "../../../Icon/index.js";
5
5
  import '../../../../../assets/SearchLink.css';const k = ({ icon: n, label: s, url: r, excerpt: l, resource: e }) => {
@@ -1,5 +1,5 @@
1
1
  import { u as e } from "../../../../../chunks/jsxRuntime.module.Bzuv3cXw.js";
2
- import { A as v, y as u, d as S } from "../../../../../chunks/hooks.module.CNV9mxng.js";
2
+ import { A as v, y as u, d as S } from "../../../../../chunks/hooks.module.DeYLzLnU.js";
3
3
  import L from "../../../Modal/Modal.js";
4
4
  import d from "../../../KeyboardKey/KeyboardKey.js";
5
5
  import k from "../SearchWindow.js";
@@ -1,5 +1,5 @@
1
1
  import { u as e } from "../../../../../chunks/jsxRuntime.module.Bzuv3cXw.js";
2
- import { D as t } from "../../../../../chunks/compat.module.DT16W4Wr.js";
2
+ import { D as t } from "../../../../../chunks/compat.module.BjTzMHXJ.js";
3
3
  import o from "../../../Icon/index.js";
4
4
  import '../../../../../assets/SearchToggle.css';const s = t(function(r, a) {
5
5
  return /* @__PURE__ */ e("button", { id: r.id, ref: a, class: "n-search-toggle", disabled: !0, "data-ntl-search-toggle": !0, children: [
@@ -1,5 +1,5 @@
1
1
  import { u as e } from "../../../../chunks/jsxRuntime.module.Bzuv3cXw.js";
2
- import { T as o } from "../../../../chunks/hooks.module.CNV9mxng.js";
2
+ import { T as o } from "../../../../chunks/hooks.module.DeYLzLnU.js";
3
3
  import t from "./SearchHeading/SearchHeading.js";
4
4
  import a from "./SearchLink/SearchLink.js";
5
5
  const h = ({
@@ -1,4 +1,4 @@
1
- import { y as l } from "../../../../chunks/hooks.module.CNV9mxng.js";
1
+ import { y as l } from "../../../../chunks/hooks.module.DeYLzLnU.js";
2
2
  function s(r, t, o, c) {
3
3
  l(() => {
4
4
  if (!r) return;
@@ -1,5 +1,5 @@
1
- import { d as g, A as w } from "../../../../chunks/hooks.module.CNV9mxng.js";
2
- import { u as S } from "../../../../chunks/useTimeout.DOkYFG64.js";
1
+ import { d as g, A as w } from "../../../../chunks/hooks.module.DeYLzLnU.js";
2
+ import { u as S } from "../../../../chunks/useTimeout.DYuvQwFN.js";
3
3
  function x() {
4
4
  const [l, r] = g({
5
5
  searchTerm: "",
@@ -1,4 +1,4 @@
1
- import { y as u } from "../../../../chunks/hooks.module.CNV9mxng.js";
1
+ import { y as u } from "../../../../chunks/hooks.module.DeYLzLnU.js";
2
2
  function a(t, r, n, o) {
3
3
  u(() => {
4
4
  if (!t) return;
@@ -3,9 +3,9 @@ import { SearchState } from '../types/search.types';
3
3
  * Custom hook that logs search-related events for analytics.
4
4
  *
5
5
  * This hook tracks the following search events:
6
- * - `spark_search:query_changed`: Logs when the search query changes.
7
- * - `spark_search:no_results`: Logs when no results are found for a search.
8
- * - `spark_search:result_clicked`: Logs when a search result is clicked.
6
+ * - `search:new_query`: Logs when the search query changes.
7
+ * - `search:no_results`: Logs when no results are found for a search.
8
+ * - `search:result_selected`: Logs when a search result is clicked.
9
9
  *
10
10
  * It uses an instance of `Analytics` to track these events. The hook listens to changes in the `searchTerm` and `results` properties from the provided `SearchState`.
11
11
  *
@@ -15,7 +15,7 @@ import { SearchState } from '../types/search.types';
15
15
  * @param {boolean} [debug=false] - Flag to enable debug mode. If true, additional debug information will be sent with the event payloads.
16
16
  *
17
17
  * @returns {Object} - An object with the following function:
18
- * - `handleClick`: A callback function that should be attached to search result click events. It tracks `spark_search:result_clicked` events with the clicked result's title and URL.
18
+ * - `handleClick`: A callback function that should be attached to search result click events. It tracks `search:result_selected` events with the clicked result's title and URL.
19
19
  *
20
20
  * @example
21
21
  * const { handleClick } = useSearchLogger(searchState);
@@ -1,37 +1,37 @@
1
- import { A as g, y as f, q as k } from "../../../../chunks/hooks.module.CNV9mxng.js";
2
- class y {
1
+ import { A as y, y as h, q as k } from "../../../../chunks/hooks.module.DeYLzLnU.js";
2
+ class s {
3
3
  /**
4
4
  * Pushes an event payload into the dataLayer.
5
- * @param eventName e.g. 'www:search_query_changed'
5
+ * @param eventName e.g. 'www:search_new_query'
6
6
  * @param payload additional key/value data
7
7
  */
8
- track(e, o = {}) {
9
- typeof gtag == "function" ? gtag("event", e, { ...o }) : console.error("gtag is not defined. Tracking event failed.", e);
8
+ track(n, i = {}) {
9
+ typeof gtag == "function" ? gtag("event", n, { ...i }) : console.error(`gtag is not defined. Event [${n}] failed.`);
10
10
  }
11
11
  }
12
- function s(r, e) {
13
- const n = g(new y()).current;
14
- return f(() => {
12
+ function p(r, n) {
13
+ const l = y(new s()).current, e = (c) => c.debug_mode = !0;
14
+ return h(() => {
15
15
  if (!(r != null && r.searchTerm)) return;
16
16
  const c = { query: r == null ? void 0 : r.searchTerm };
17
- e && (c.debug_mode = !0), n.track("spark_search:query_changed", c);
18
- }, [r == null ? void 0 : r.searchTerm]), f(() => {
17
+ n && e(c), l.track("search:new_query", c);
18
+ }, [r == null ? void 0 : r.searchTerm]), h(() => {
19
19
  if (!(r != null && r.results)) return;
20
20
  const c = { query: r == null ? void 0 : r.searchTerm };
21
- e && (c.debug_mode = !0), r != null && r.searchTerm && r.results.length === 0 && n.track("spark_search:no_results", c);
21
+ n && e(c), r != null && r.searchTerm && r.results.length === 0 && l.track("search:no_results", c);
22
22
  }, [r == null ? void 0 : r.results]), {
23
23
  handleClick: k((c) => {
24
- var i;
25
- const l = c.target.closest("a");
26
- if (!l) return;
27
- const h = (i = l.querySelector(".n-search-link-label")) == null ? void 0 : i.innerHTML, d = l.getAttribute("href"), u = {
28
- resultTitle: h,
24
+ var f;
25
+ const o = c.target.closest("a");
26
+ if (!o) return;
27
+ const g = (f = o.querySelector(".n-search-link-label")) == null ? void 0 : f.innerHTML, d = o.getAttribute("href") || void 0, u = {
28
+ resultTitle: g,
29
29
  resultUrl: d
30
30
  };
31
- e && (u.debug_mode = !0), n.track("spark_search:result_clicked", u);
31
+ n && e(u), l.track("search:result_selected", u);
32
32
  }, [])
33
33
  };
34
34
  }
35
35
  export {
36
- s as useSearchLogger
36
+ p as useSearchLogger
37
37
  };
@@ -1,4 +1,4 @@
1
- import { y as c } from "../../../../chunks/hooks.module.CNV9mxng.js";
1
+ import { y as c } from "../../../../chunks/hooks.module.DeYLzLnU.js";
2
2
  function n(r, o, a) {
3
3
  c(() => {
4
4
  if (typeof location > "u" || r) return;
@@ -1,4 +1,4 @@
1
- import { y as l } from "../../../../chunks/hooks.module.CNV9mxng.js";
1
+ import { y as l } from "../../../../chunks/hooks.module.DeYLzLnU.js";
2
2
  function i(c, r) {
3
3
  l(() => {
4
4
  if (!c) return;
@@ -23,3 +23,9 @@ export interface SearchState {
23
23
  results: SearchLinkProps[];
24
24
  isLoading: boolean;
25
25
  }
26
+ export interface SearchGTMObjectProps {
27
+ resultTitle?: string;
28
+ resultUrl?: string;
29
+ query?: string;
30
+ debug_mode?: boolean;
31
+ }
@@ -4,5 +4,6 @@ export { default as Icon } from './Icon';
4
4
  export { default as SearchLink } from './SearchModal/components/SearchLink/SearchLink';
5
5
  export { default as SearchHeading } from './SearchModal/components/SearchHeading/SearchHeading';
6
6
  export { default as SearchToggle } from './SearchModal/components/SearchToggle/SearchToggle';
7
+ export { default as ChatApp } from './ChatApp/ChatApp';
7
8
  export { SearchModal } from './SearchModal';
8
9
  export { type IconName } from './Icon';
@@ -1,18 +1,20 @@
1
1
  import { default as t } from "./KeyboardKey/KeyboardKey.js";
2
2
  import { default as d } from "./Modal/Modal.js";
3
- import { default as m } from "./Icon/index.js";
3
+ import { default as l } from "./Icon/index.js";
4
4
  import { default as s } from "./SearchModal/components/SearchLink/SearchLink.js";
5
5
  import { default as x } from "./SearchModal/components/SearchHeading/SearchHeading.js";
6
6
  import { default as h } from "./SearchModal/components/SearchToggle/SearchToggle.js";
7
+ import { default as S } from "./ChatApp/ChatApp.js";
7
8
  import "preact";
8
- import "../../chunks/hooks.module.CNV9mxng.js";
9
- import { default as S } from "./SearchModal/components/SearchModal/SearchModal.js";
9
+ import "../../chunks/hooks.module.DeYLzLnU.js";
10
+ import { default as n } from "./SearchModal/components/SearchModal/SearchModal.js";
10
11
  export {
11
- m as Icon,
12
+ S as ChatApp,
13
+ l as Icon,
12
14
  t as KeyboardKey,
13
15
  d as Modal,
14
16
  x as SearchHeading,
15
17
  s as SearchLink,
16
- S as SearchModal,
18
+ n as SearchModal,
17
19
  h as SearchToggle
18
20
  };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@netlify/spark-ui",
3
3
  "description": "Assets, design tokens, components, and utilities",
4
- "version": "0.0.46-alpha.4",
4
+ "version": "0.0.47-alpha",
5
5
  "type": "module",
6
6
  "main": "dist/components/preact/index.js",
7
7
  "types": "dist/components/preact/index.d.ts",
@@ -32,8 +32,11 @@
32
32
  "./analytics": "./packages/analytics/index.ts"
33
33
  },
34
34
  "dependencies": {
35
+ "@kapaai/react-sdk": "^0.0.5",
35
36
  "@preact/preset-vite": "^2.10.1",
37
+ "@shikijs/transformers": "^3.3.0",
36
38
  "glob": "^11.0.1",
39
+ "marked": "^15.0.11",
37
40
  "preact": "^10.26.4",
38
41
  "vite": "^6.2.5",
39
42
  "vite-plugin-dts": "^4.5.3",
@@ -1,14 +1,21 @@
1
1
  ---
2
- const { gaId } = Astro.props;
2
+ interface Props {
3
+ gaId: string;
4
+ }
3
5
 
4
- if (!gaId) console.error ('[GoogleTagManager] no GTM id provided')
6
+ const { gaId } = Astro.props as Props;
5
7
  ---
6
8
  <!-- Google tag (gtag.js) -->
7
- <script async src=`https://www.googletagmanager.com/gtag/js?id=${gaId}`></script>
8
- <script define:vars={{id: gaId}}>
9
- window.dataLayer = window.dataLayer || [];
10
- function gtag(){dataLayer.push(arguments);}
11
- gtag('js', new Date());
12
9
 
13
- gtag('config', id);
14
- </script>
10
+ {gaId &&
11
+ <>
12
+ <script async src={`https://www.googletagmanager.com/gtag/js?id=${gaId}`}></script>
13
+ <script define:vars={{id: gaId}}>
14
+ window.dataLayer = window.dataLayer || [];
15
+ function gtag(){dataLayer.push(arguments);}
16
+ gtag('js', new Date());
17
+
18
+ gtag('config', id);
19
+ </script>
20
+ </>
21
+ }
@@ -1,11 +1,13 @@
1
1
  ---
2
- const { gtmId, gaId } = Astro.props;
2
+ interface Props {
3
+ gtmId: string;
4
+ gaId: string;
5
+ }
3
6
 
4
- if (!gtmId) console.error ('[GoogleTagManager] no GTM id provided')
5
- if (!gaId) console.error ('[GoogleTagManager] no GA id provided')
7
+ const { gtmId, gaId } = Astro.props as Props;
6
8
  ---
7
9
  <!-- Google Tag Manager -->
8
- <script define:vars={{id: gtmId}}>
10
+ {gtmId && <script define:vars={{id: gtmId}}>
9
11
  (function (w, d, s, l, i) {
10
12
  w[l] = w[l] || [];
11
13
  w[l].push({ 'gtm.start': new Date().getTime(), event: 'gtm.js' });
@@ -16,14 +18,14 @@ if (!gaId) console.error ('[GoogleTagManager] no GA id provided')
16
18
  j.src = 'https://www.googletagmanager.com/gtm.js?id=' + i + dl;
17
19
  f.parentNode.insertBefore(j, f);
18
20
  })(window, document, 'script', 'dataLayer', id);
19
- </script>
21
+ </script>}
20
22
  <!-- End Google Tag Manager -->
21
23
 
22
24
  <!-- Google Analytics (gtag.js) -->
23
- <script define:vars={{id: gaId}}>
25
+ {gaId && <script define:vars={{id: gaId}}>
24
26
  function gtag(){dataLayer.push(arguments);}
25
27
  gtag('js', new Date());
26
28
 
27
29
  gtag('config', id);
28
- </script>
30
+ </script>}
29
31
  <!-- End Google Analytics (gtag.js) -->
@@ -1,9 +1,12 @@
1
- <noscript>
2
- <iframe
3
- src=`https://www.googletagmanager.com/ns.html?id=${Astro.props.gtmId}`
4
- height="0"
5
- width="0"
6
- style="display:none;visibility:hidden"
7
- >
8
- </iframe>
9
- </noscript>
1
+ {
2
+ Astro.props.gtmId && (
3
+ <noscript>
4
+ <iframe
5
+ src={`https://www.googletagmanager.com/ns.html?id=${Astro.props.gtmId}`}
6
+ height="0"
7
+ width="0"
8
+ style="display:none;visibility:hidden"
9
+ ></iframe>
10
+ </noscript>
11
+ )
12
+ }
@@ -2,7 +2,7 @@ export default class Analytics {
2
2
 
3
3
  /**
4
4
  * Pushes an event payload into the dataLayer.
5
- * @param eventName e.g. 'www:search_query_changed'
5
+ * @param eventName e.g. 'www:search_new_query'
6
6
  * @param payload additional key/value data
7
7
  */
8
8
  track(eventName: string, payload: Record<string, any> = {}): void {
@@ -10,7 +10,7 @@ export default class Analytics {
10
10
  if (typeof gtag === 'function') {
11
11
  gtag('event', eventName, { ...payload });
12
12
  } else {
13
- console.error(`gtag is not defined. Tracking event failed.`, eventName);
13
+ console.error(`gtag is not defined. Event [${eventName}] failed.`);
14
14
  }
15
15
  }
16
16
  }
@@ -0,0 +1,208 @@
1
+ ---
2
+ import { Code } from 'astro:components';
3
+ import '../styles/code-block.css';
4
+ import poimandresTheme from '../../themes/poimandres.json';
5
+ import {
6
+ transformerMetaHighlight,
7
+ transformerNotationDiff,
8
+ transformerNotationFocus,
9
+ } from '@shikijs/transformers';
10
+
11
+ interface Props {
12
+ code: string;
13
+ lang?: string;
14
+ highlight?: string;
15
+ del?: string;
16
+ ins?: string;
17
+ frame?: 'code' | 'terminal' | 'none';
18
+ theme?: string;
19
+ wrap?: boolean;
20
+ title?: string;
21
+ class?: string;
22
+ showLineNumbers?: boolean;
23
+ }
24
+
25
+ const {
26
+ code,
27
+ lang = '',
28
+ highlight = '',
29
+ del = '',
30
+ ins = '',
31
+ frame = 'none',
32
+ theme = 'poimandres',
33
+ wrap = false,
34
+ title = '',
35
+ class: className,
36
+ showLineNumbers = false,
37
+ ...rest
38
+ } = Astro.props;
39
+
40
+ // Process highlighting ranges (e.g. {2-3,6})
41
+ const highlightRanges = highlight
42
+ ? highlight
43
+ .replace(/[{}]/g, '')
44
+ .split(',')
45
+ .map((range: string) => {
46
+ if (range.includes('-')) {
47
+ const [start, end] = range.split('-').map(Number);
48
+ return Array.from({ length: end - start + 1 }, (_, i) => start + i);
49
+ }
50
+ return [Number(range)];
51
+ })
52
+ .flat()
53
+ : [];
54
+
55
+ // Process deleted lines
56
+ const delRanges = del
57
+ ? del
58
+ .split(',')
59
+ .map((range: string) => {
60
+ if (range.includes('-')) {
61
+ const [start, end] = range.split('-').map(Number);
62
+ return Array.from({ length: end - start + 1 }, (_, i) => start + i);
63
+ }
64
+ return [Number(range)];
65
+ })
66
+ .flat()
67
+ : [];
68
+
69
+ // Process inserted lines
70
+ const insRanges = ins
71
+ ? ins
72
+ .split(',')
73
+ .map((range: string) => {
74
+ if (range.includes('-')) {
75
+ const [start, end] = range.split('-').map(Number);
76
+ return Array.from({ length: end - start + 1 }, (_, i) => start + i);
77
+ }
78
+ return [Number(range)];
79
+ })
80
+ .flat()
81
+ : [];
82
+
83
+ // Apply highlighting, deletions, and insertions using Shiki's transformers
84
+ const codeWithMeta = code
85
+ .split('\n')
86
+ .map((line: string, i: number) => {
87
+ const lineNumber = i + 1;
88
+ const meta = [];
89
+
90
+ if (highlightRanges.includes(lineNumber)) {
91
+ meta.push('focus');
92
+ }
93
+ if (delRanges.includes(lineNumber)) {
94
+ meta.push('--');
95
+ }
96
+ if (insRanges.includes(lineNumber)) {
97
+ meta.push('++');
98
+ }
99
+
100
+ return meta.length > 0 ? `${line} // [!code ${meta.join(' ')}]` : line;
101
+ })
102
+ .join('\n');
103
+
104
+ // Determine which theme to use
105
+ const selectedTheme = theme === 'poimandres' ? poimandresTheme : theme;
106
+
107
+ // Determine if we should show line numbers
108
+ const showNumbers = showLineNumbers && code.split('\n').length > 1;
109
+ ---
110
+
111
+ <div
112
+ class:list={[
113
+ 'code-block',
114
+ frame === 'terminal' && 'terminal-frame',
115
+ frame === 'code' && 'code-frame',
116
+ frame === 'none' && 'no-frame',
117
+ showNumbers && 'with-line-numbers',
118
+ className,
119
+ ]}
120
+ {...rest}
121
+ >
122
+ {
123
+ (title || lang) && frame !== 'none' && frame !== 'terminal' && (
124
+ <div class="code-block-title">
125
+ {lang && <span class="code-language">{lang}</span>}
126
+ {title && <span class="code-title">{title}</span>}
127
+ </div>
128
+ )
129
+ }
130
+ <div class="code-block-content">
131
+ <Code
132
+ code={codeWithMeta}
133
+ lang={lang}
134
+ theme={selectedTheme}
135
+ wrap={wrap}
136
+ transformers={[
137
+ transformerMetaHighlight(),
138
+ transformerNotationDiff(),
139
+ transformerNotationFocus(),
140
+ ]}
141
+ />
142
+ <button class="copy-button" aria-label="Copy code to clipboard" data-tooltip="Copy">
143
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" class="copy-icon">
144
+ <path
145
+ d="M18 3c0-1.657-1.343-3-3-3v0h-12c-1.657 0-3 1.343-3 3v0 12c0 1.657 1.343 3 3 3v0h12c1.657 0 3-1.343 3-3v0zM15 1.5c0.828 0 1.5 0.672 1.5 1.5v0 12c0 0.828-0.672 1.5-1.5 1.5v0h-12c-0.828 0-1.5-0.672-1.5-1.5v0-12c0-0.828 0.672-1.5 1.5-1.5v0zM21 7.5c0.828 0 1.5 0.672 1.5 1.5v0 12c0 0.828-0.672 1.5-1.5 1.5v0h-12c-0.828 0-1.5-0.672-1.5-1.5v0-1.5h-1.5v1.5c0 1.657 1.343 3 3 3v0h12c1.657 0 3-1.343 3-3v0-12c0-1.657-1.343-3-3-3v0h-1.5v1.5z"
146
+ ></path>
147
+ </svg>
148
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" class="check-icon">
149
+ <path
150
+ d="M22.942 6.582l-13.778 13.778c-0.156 0.156-0.371 0.253-0.609 0.253s-0.453-0.097-0.609-0.253v0l-6.028-6.028c-0.156-0.156-0.252-0.371-0.252-0.609 0-0.476 0.386-0.862 0.862-0.862 0.238 0 0.453 0.096 0.609 0.252v0l5.418 5.42 13.168-13.169c0.156-0.156 0.371-0.252 0.609-0.252 0.476 0 0.862 0.386 0.862 0.862 0 0.238-0.096 0.453-0.252 0.609v0z"
151
+ ></path>
152
+ </svg>
153
+ </button>
154
+ </div>
155
+ </div>
156
+
157
+ <script>
158
+ document.addEventListener('astro:page-load', () => {
159
+ setupCopyBtn();
160
+ });
161
+
162
+ // Also initialize immediately in case the page is not using transitions
163
+ if (document.readyState === 'complete' || document.readyState === 'interactive') {
164
+ setupCopyBtn();
165
+ } else {
166
+ document.addEventListener('DOMContentLoaded', setupCopyBtn);
167
+ }
168
+
169
+ function setupCopyBtn() {
170
+ const copyButtons = document.querySelectorAll(
171
+ '.code-block .copy-button'
172
+ ) as NodeListOf<HTMLButtonElement>;
173
+ copyButtons.forEach(button => {
174
+ button.addEventListener('click', async () => {
175
+ const codeBlock = button.closest('.code-block');
176
+ if (!codeBlock) return;
177
+
178
+ const pre = codeBlock.querySelector('pre');
179
+ if (!pre) return;
180
+
181
+ // Get the text content, remove the Shiki annotations if present
182
+ const rawText = pre.textContent || '';
183
+ const code = rawText.replace(/\/\/\s*\[!.*?\]/g, '').trim();
184
+
185
+ // Copy to clipboard
186
+ try {
187
+ await navigator.clipboard.writeText(code);
188
+ button.classList.add('copied');
189
+ button.setAttribute('data-tooltip', 'Copied!');
190
+ setTimeout(() => {
191
+ button.classList.remove('copied');
192
+ button.setAttribute('data-tooltip', 'Copy');
193
+ button.blur();
194
+ }, 2000);
195
+ } catch (error) {
196
+ console.error('Failed to copy code: ', error);
197
+ button.classList.add('error');
198
+ button.setAttribute('data-tooltip', 'Failed to copy!');
199
+ setTimeout(() => {
200
+ button.classList.remove('error');
201
+ button.setAttribute('data-tooltip', 'Copy');
202
+ button.blur();
203
+ }, 2000);
204
+ }
205
+ });
206
+ });
207
+ }
208
+ </script>
@@ -6,6 +6,7 @@ export { default as SearchLink } from './preact/SearchModal/components/SearchLin
6
6
  export { default as SearchHeading } from './preact/SearchModal/components/SearchHeading/SearchHeading';
7
7
  export { default as SearchToggle } from './preact/SearchModal/components/SearchToggle/SearchToggle';
8
8
  export { default as SearchModal } from './preact/SearchModal/components/SearchModal/SearchModal';
9
+ export { default as ChatApp } from './preact/ChatApp/ChatApp';
9
10
  export { type IconName } from './preact/Icon';
10
11
 
11
12
  // Astro components