Skip to content

Commit b414bfe

Browse files
committed
fix(search): load client.js module to enable search functionality
Search was non-functional because client.ts (containing openSearch, closeSearch, and keyboard shortcuts) was bundled to static/client.js but never loaded in HTML. Layouts were using mainScript inline code which lacked search functionality. Replaced duplicate mainScript with single client.js module import, reducing HTML size by ~6KB and centralizing all client-side logic.
1 parent 0b5c17c commit b414bfe

6 files changed

Lines changed: 7 additions & 195 deletions

File tree

app/templates/HomeLayout.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,7 @@ j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
107107
<SearchModal />
108108
<ScrollToTop />
109109
<script src={basePath("/pagefind/pagefind-ui.js")} />
110+
<script type="module" src={basePath("/static/client.js")} />
110111
</body>
111112
</html>
112113
);

app/templates/Layout.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
import type { Child } from "hono/jsx";
55
import { siteMetadata } from "../../data/docs.ts";
66
import { basePath } from "../lib/path.ts";
7-
import { mainScript, themeInitScript } from "./scripts.ts";
7+
import { themeInitScript } from "./scripts.ts";
88
import { Header } from "../components/Header.js";
99
import { SearchModal } from "../components/SearchModal.js";
1010
import { ScrollToTop } from "../components/ScrollToTop.js";
@@ -145,7 +145,7 @@ j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
145145
<SearchModal />
146146
<ScrollToTop />
147147
<script src={basePath("/pagefind/pagefind-ui.js")} />
148-
<script dangerouslySetInnerHTML={{ __html: mainScript }} />
148+
<script type="module" src={basePath("/static/client.js")} />
149149
</body>
150150
</html>
151151
);

app/templates/api/ApiPage.tsx

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ import {
1717
import { basePath } from "../../lib/path.ts";
1818
import { parseApiMarkdown } from "../../lib/markdown.ts";
1919
import { Layout } from "../Layout.tsx";
20-
import { mainScript } from "../scripts.ts";
2120
import { ApiToc, DocNodeRenderer, PackageSidebar } from "./components.tsx";
2221

2322
// ============================================================================
@@ -59,7 +58,6 @@ export async function ApiIndexPage() {
5958
</article>
6059
</main>
6160
</div>
62-
<script dangerouslySetInnerHTML={{ __html: mainScript }} />
6361
</Layout>
6462
);
6563
}
@@ -271,7 +269,6 @@ export async function PackagePage({ packageName }: PackagePageProps) {
271269
</div>
272270
</main>
273271
</div>
274-
<script dangerouslySetInnerHTML={{ __html: mainScript }} />
275272
</Layout>
276273
);
277274
}

app/templates/docs/MarkdownDoc.tsx

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ import {
1010
} from "../../lib/markdown.ts";
1111
import { DocLayout, TableOfContents } from "../components.tsx";
1212
import { Layout } from "../Layout.tsx";
13-
import { mainScript } from "../scripts.ts";
1413

1514
interface MarkdownDocProps {
1615
/** Raw markdown content */
@@ -67,7 +66,6 @@ export function MarkdownDoc(
6766
/>
6867
</div>
6968
</DocLayout>
70-
<script dangerouslySetInnerHTML={{ __html: mainScript }} />
7169
</Layout>
7270
);
7371
}

app/templates/home.tsx

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import { features } from "../../data/features.ts";
66
import { loadSampleCodes, type SampleCode } from "../../data/sample_codes.ts";
77
import { basePath } from "../lib/path.ts";
88
import { HomeLayout } from "./HomeLayout.tsx";
9-
import { mainScript } from "./scripts.ts";
109

1110
function CarouselTabs({ sampleCodes }: { sampleCodes: SampleCode[] }) {
1211
return (
@@ -221,7 +220,6 @@ export async function HomePage() {
221220
<FeaturesSection />
222221
<ClientsSection />
223222
<AiFriendlySection />
224-
<script dangerouslySetInnerHTML={{ __html: mainScript }} />
225223
</HomeLayout>
226224
);
227225
}

app/templates/scripts.ts

Lines changed: 4 additions & 186 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
/**
2-
* Client-side JavaScript for the page
2+
* Inline scripts for SSG pages
3+
*
4+
* These scripts are embedded inline in the HTML to run before external scripts load.
5+
* Main functionality is in client.ts which is bundled as a module.
36
*/
47

58
/** Theme initialization script (runs before render to prevent FOUC) */
@@ -9,188 +12,3 @@ export const themeInitScript = `(function() {
912
const theme = stored || (systemPrefersDark ? 'dark' : 'light');
1013
document.documentElement.setAttribute('data-theme', theme);
1114
})();`;
12-
13-
/** Main page scripts */
14-
export const mainScript = `
15-
const HLJS_CDN = 'https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.9.0/build';
16-
const HLJS_THEMES = {
17-
dark: 'https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.9.0/build/styles/github-dark.min.css',
18-
light: 'https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.9.0/build/styles/github.min.css'
19-
};
20-
21-
function loadScript(src) {
22-
return new Promise((resolve, reject) => {
23-
const script = document.createElement('script');
24-
script.src = src;
25-
script.async = true;
26-
script.onload = resolve;
27-
script.onerror = reject;
28-
document.head.appendChild(script);
29-
});
30-
}
31-
32-
async function ensureHljs() {
33-
if (window.hljs) return;
34-
await loadScript(\`\${HLJS_CDN}/highlight.min.js\`);
35-
await loadScript(\`\${HLJS_CDN}/languages/typescript.min.js\`);
36-
await loadScript(\`\${HLJS_CDN}/languages/json.min.js\`);
37-
}
38-
39-
function updateHljsTheme(theme) {
40-
const themeLink = document.getElementById('hljs-theme');
41-
if (!themeLink) return;
42-
themeLink.href = HLJS_THEMES[theme];
43-
}
44-
45-
function toggleTheme() {
46-
const current = document.documentElement.getAttribute('data-theme');
47-
const next = current === 'light' ? 'dark' : 'light';
48-
document.documentElement.setAttribute('data-theme', next);
49-
localStorage.setItem('theme', next);
50-
updateHljsTheme(next);
51-
}
52-
53-
function toggleMobileMenu() {
54-
const header = document.querySelector('.global-header');
55-
header.classList.toggle('menu-open');
56-
document.body.classList.toggle('menu-open');
57-
}
58-
59-
function closeMobileMenu() {
60-
const header = document.querySelector('.global-header');
61-
header.classList.remove('menu-open');
62-
document.body.classList.remove('menu-open');
63-
}
64-
65-
function initCarousel() {
66-
const tabs = document.querySelectorAll('.carousel-tab');
67-
const slides = document.querySelectorAll('.carousel-slide');
68-
const prevBtn = document.querySelector('.carousel-prev');
69-
const nextBtn = document.querySelector('.carousel-next');
70-
71-
// Skip if carousel elements not found
72-
if (!tabs.length || !slides.length || !prevBtn || !nextBtn) return;
73-
74-
const totalSlides = slides.length;
75-
76-
function goToSlide(index) {
77-
tabs.forEach(t => t.classList.remove('active'));
78-
tabs[index].classList.add('active');
79-
slides.forEach(s => s.classList.remove('active'));
80-
slides[index].classList.add('active');
81-
if (window.hljs) {
82-
slides[index].querySelectorAll('pre code:not([data-highlighted])').forEach(block => {
83-
hljs.highlightElement(block);
84-
});
85-
}
86-
}
87-
88-
function getCurrentIndex() {
89-
return [...tabs].findIndex(t => t.classList.contains('active'));
90-
}
91-
92-
tabs.forEach(tab => {
93-
tab.addEventListener('click', () => {
94-
goToSlide(parseInt(tab.dataset.index));
95-
});
96-
});
97-
98-
prevBtn.addEventListener('click', () => {
99-
const current = getCurrentIndex();
100-
goToSlide((current - 1 + totalSlides) % totalSlides);
101-
});
102-
103-
nextBtn.addEventListener('click', () => {
104-
const current = getCurrentIndex();
105-
goToSlide((current + 1) % totalSlides);
106-
});
107-
}
108-
109-
function initScrollableNavFade() {
110-
document.querySelectorAll('.scrollable-nav').forEach(el => {
111-
function updateFade() {
112-
const atTop = el.scrollTop <= 10;
113-
const atBottom = el.scrollTop + el.clientHeight >= el.scrollHeight - 10;
114-
115-
el.classList.toggle('scroll-top', atTop);
116-
el.classList.toggle('scroll-bottom', atBottom);
117-
}
118-
119-
updateFade();
120-
el.addEventListener('scroll', updateFade, { passive: true });
121-
});
122-
}
123-
124-
function initCodeCopyButtons() {
125-
document.querySelectorAll('pre').forEach(pre => {
126-
const code = pre.querySelector('code');
127-
if (!code) return;
128-
129-
const wrapper = document.createElement('div');
130-
wrapper.className = 'code-block-wrapper';
131-
pre.parentNode.insertBefore(wrapper, pre);
132-
wrapper.appendChild(pre);
133-
134-
const btn = document.createElement('button');
135-
btn.type = 'button';
136-
btn.className = 'code-copy-btn';
137-
btn.title = 'Copy code';
138-
btn.innerHTML = '<i class="ti ti-copy"></i>';
139-
wrapper.appendChild(btn);
140-
141-
btn.addEventListener('click', async () => {
142-
try {
143-
await navigator.clipboard.writeText(code.textContent || '');
144-
btn.innerHTML = '<i class="ti ti-check"></i>';
145-
btn.classList.add('copied');
146-
setTimeout(() => {
147-
btn.innerHTML = '<i class="ti ti-copy"></i>';
148-
btn.classList.remove('copied');
149-
}, 2000);
150-
} catch (err) {
151-
console.error('Failed to copy:', err);
152-
}
153-
});
154-
});
155-
}
156-
157-
function initSignatureScrollFade() {
158-
document.querySelectorAll('.api-signature pre code').forEach(code => {
159-
function updateFade() {
160-
const canScrollLeft = code.scrollLeft > 5;
161-
const canScrollRight = code.scrollLeft + code.clientWidth < code.scrollWidth - 5;
162-
163-
code.classList.toggle('scroll-left', canScrollLeft);
164-
code.classList.toggle('scroll-right', canScrollRight);
165-
}
166-
167-
// Initial check
168-
updateFade();
169-
170-
// Update on scroll
171-
code.addEventListener('scroll', updateFade, { passive: true });
172-
173-
// Also check on window resize (content width may change)
174-
window.addEventListener('resize', updateFade, { passive: true });
175-
});
176-
}
177-
178-
document.addEventListener('DOMContentLoaded', async () => {
179-
updateHljsTheme(document.documentElement.getAttribute('data-theme') || 'dark');
180-
initCarousel();
181-
initScrollableNavFade();
182-
initCodeCopyButtons();
183-
initSignatureScrollFade();
184-
try {
185-
await ensureHljs();
186-
if (window.hljs) {
187-
// Highlight all code blocks, including those in hidden carousel slides
188-
document.querySelectorAll('pre code').forEach(block => {
189-
hljs.highlightElement(block);
190-
});
191-
}
192-
} catch (err) {
193-
console.warn('Failed to load highlight.js', err);
194-
}
195-
});
196-
`;

0 commit comments

Comments
 (0)