refactor(docs): remove inline JS from docs template

- Removed ~400 lines of inline JavaScript from docs.templ
- Moved toggleSection function to docs.ts (now uses Alpine )
- Added highlightCurrentPage function to docs.ts
- Added initializeCodeCopyButtons function to docs.ts
- Updated template to use x-init for initialization
- Functions exported for use in Alpine.data
This commit is contained in:
john-okeefe committed 2026-03-13 12:50:51 -04:00
1 parent 5b7540be99
commit 7068fabbee
3 files changed
+67 -431

No files matched your search

+4 -423
View File
@@ -13,7 +13,7 @@ templ DocsLayout(nav Navigation, doc Document, user User, currentPath string) {
<link rel="stylesheet" href="/static/highlight-dark.min.css"/> <link rel="stylesheet" href="/static/highlight-dark.min.css"/>
<script src="/static/main.js" defer></script> <script src="/static/main.js" defer></script>
</head> </head>
<body x-data="docs" x-init="initializeSearch" class={ "theme-" + user.Theme + " page-docs bg-background-primary text-text-primary font-sans antialiased" }> <body x-data="docs" x-init="initializeSearch(); highlightCurrentPage(); initializeCodeCopyButtons()" class={ "theme-" + user.Theme + " page-docs bg-background-primary text-text-primary font-sans antialiased" }>
@Header(user, currentPath) @Header(user, currentPath)
<!-- Mobile Menu Button --> <!-- Mobile Menu Button -->
<button <button
@@ -43,7 +43,7 @@ templ DocsLayout(nav Navigation, doc Document, user User, currentPath string) {
<div class="nav-section mb-6"> <div class="nav-section mb-6">
<div <div
class="nav-section-title font-semibold text-xs uppercase tracking-wider text-text-secondary px-4 py-3 cursor-pointer select-none flex items-center justify-between" class="nav-section-title font-semibold text-xs uppercase tracking-wider text-text-secondary px-4 py-3 cursor-pointer select-none flex items-center justify-between"
onclick="toggleSection(this)" @click="toggleSection($el)"
> >
<span>{ section.Title }</span> <span>{ section.Title }</span>
<svg class="w-4 h-4 transform transition-transform section-arrow" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <svg class="w-4 h-4 transform transition-transform section-arrow" fill="none" stroke="currentColor" viewBox="0 0 24 24">
@@ -107,426 +107,7 @@ templ DocsLayout(nav Navigation, doc Document, user User, currentPath string) {
<div class="prose prose-invert max-w-none"> <div class="prose prose-invert max-w-none">
@UnsafeHTML(doc.Content).ToComponent() @UnsafeHTML(doc.Content).ToComponent()
</div> </div>
</main> </main>
<script> </body>
// Toggle sidebar section
function toggleSection(el) {
const items = el.nextElementSibling;
const arrow = el.querySelector(".section-arrow");
const isCollapsed = items.getAttribute("data-collapsed") === "true";
if (isCollapsed) {
items.classList.remove("hidden");
items.setAttribute("data-collapsed", "false");
arrow.style.transform = "rotate(0deg)";
} else {
items.classList.add("hidden");
items.setAttribute("data-collapsed", "true");
arrow.style.transform = "rotate(-90deg)";
}
}
// Toggle sidebar on mobile
function toggleSidebar() {
const sidebar = document.querySelector(".sidebar");
sidebar.classList.toggle("open");
}
// Highlight current page in nav
document.addEventListener("DOMContentLoaded", function () {
const currentPath = window.location.pathname;
document.querySelectorAll(".nav-item").forEach((item) => {
if (item.getAttribute("href") === currentPath) {
item.classList.add(
"text-accent",
"bg-accent/10",
"border-r-2",
"border-accent",
);
}
});
// Initialize syntax highlighting
if (typeof hljs !== "undefined") {
hljs.highlightAll();
// Add copy buttons to all code blocks
document.querySelectorAll("pre code").forEach((block) => {
const pre = block.parentElement;
const wrapper = document.createElement("div");
wrapper.className = "relative group";
pre.parentNode.insertBefore(wrapper, pre);
wrapper.appendChild(pre);
const button = document.createElement("button");
button.className =
"absolute top-2 right-2 bg-background-secondary text-text-secondary px-2 py-1 rounded text-xs opacity-0 group-hover:opacity-100 transition-opacity border border-border hover:text-accent";
button.textContent = "Copy";
button.onclick = async () => {
await navigator.clipboard.writeText(block.textContent);
button.textContent = "Copied!";
setTimeout(() => {
button.textContent = "Copy";
}, 2000);
};
wrapper.appendChild(button);
});
}
// Add copy buttons to all code blocks
document.querySelectorAll("pre code").forEach((block) => {
const pre = block.parentElement;
const wrapper = document.createElement("div");
wrapper.className = "relative group";
pre.parentNode.insertBefore(wrapper, pre);
wrapper.appendChild(pre);
const button = document.createElement("button");
button.className =
"absolute top-2 right-2 bg-background-secondary text-text-secondary px-2 py-1 rounded text-xs opacity-0 group-hover:opacity-100 transition-opacity border border-border hover:text-accent";
button.textContent = "Copy";
button.onclick = async () => {
await navigator.clipboard.writeText(block.textContent);
button.textContent = "Copied!";
setTimeout(() => {
button.textContent = "Copy";
}, 2000);
};
wrapper.appendChild(button);
});
});
// Add copy buttons to all code blocks
document.querySelectorAll("pre code").forEach((block) => {
const pre = block.parentElement;
const wrapper = document.createElement("div");
wrapper.className = "relative group";
pre.parentNode.insertBefore(wrapper, pre);
wrapper.appendChild(pre);
const button = document.createElement("button");
button.className =
"absolute top-2 right-2 bg-background-secondary text-text-secondary px-2 py-1 rounded text-xs opacity-0 group-hover:opacity-100 transition-opacity border border-border hover:text-accent";
button.textContent = "Copy";
button.onclick = async () => {
await navigator.clipboard.writeText(block.textContent);
button.textContent = "Copied!";
setTimeout(() => {
button.textContent = "Copy";
}, 2000);
};
wrapper.appendChild(button);
});
// Search state
let idx;
let searchDocs = [];
// Load index on page load
fetch("/docs/search-index.json")
.then((r) => r.json())
.then((data) => {
idx = lunr(function () {
this.use(lunr.flex);
this.ref("id");
this.field("title", { boost: 10 });
this.field("content", { boost: 1 });
data.forEach((doc) => this.add(doc));
});
searchDocs = data;
console.log("Search index loaded:", data.length, "documents");
})
.catch((err) => console.error("Failed to load search index:", err));
// Search input with debounce
const searchInput = document.getElementById("docs-search");
const searchResultsDiv = document.getElementById("search-results");
let debounceTimer;
searchInput.addEventListener("input", (e) => {
clearTimeout(debounceTimer);
debounceTimer = setTimeout(() => {
const query = e.target.value.trim();
if (query.length < 2) {
searchResultsDiv.classList.add("hidden");
return;
}
// Search with fuzzy matching
const results = idx.search(query, {
fields: { title: { boost: 10 }, content: 1 },
expand: true,
});
// Render results
renderSearchResults(results, query);
}, 150);
});
// Render search results
function renderSearchResults(results, query) {
if (results.length === 0) {
searchResultsDiv.innerHTML =
'<div class="text-center py-8 text-text-secondary">No results found</div>';
searchResultsDiv.classList.remove("hidden");
return;
}
searchResultsDiv.innerHTML = results
.map((r) => {
const doc = searchDocs.find((d) => d.id === r.ref);
if (!doc) return "";
const snippet = getSnippet(doc.content, query);
return `
<a href="${doc.url}" class="block p-4 border-b border-border hover:bg-background-secondary text-text-primary no-underline">
<div class="font-semibold mb-2">${doc.title}</div>
<div class="text-sm text-text-secondary">${snippet}...</div>
</a>
`;
})
.join("");
searchResultsDiv.classList.remove("hidden");
}
function getSnippet(content, query) {
const words = query.toLowerCase().split(/\s+/);
const contentLower = content.toLowerCase();
for (const word of words) {
const idx = contentLower.indexOf(word);
if (idx !== -1) {
const start = Math.max(0, idx - 50);
const end = Math.min(content.length, idx + 100);
return "..." + content.substring(start, end) + "...";
}
}
return content.substring(0, 150) + "...";
}
// Close search results when clicking outside
document.addEventListener("click", (e) => {
if (
!searchResultsDiv.contains(e.target) &&
e.target !== searchInput
) {
searchResultsDiv.classList.add("hidden");
}
});
// Close search on Escape key
document.addEventListener("keydown", (e) => {
if (e.key === "Escape") {
searchResultsDiv.classList.add("hidden");
searchInput.blur();
}
});
</script>
<script>
// Toggle sidebar on mobile
function toggleSidebar() {
const sidebar = document.querySelector(".sidebar");
sidebar.classList.toggle("open");
}
// Highlight current page in nav
document.addEventListener("DOMContentLoaded", function () {
const currentPath = window.location.pathname;
document.querySelectorAll(".nav-item").forEach((item) => {
if (item.getAttribute("href") === currentPath) {
item.classList.add(
"text-accent",
"bg-accent/10",
"border-r-2",
"border-accent",
);
}
});
// Initialize syntax highlighting
if (typeof hljs !== "undefined") {
hljs.highlightAll();
// Add copy buttons to all code blocks
document.querySelectorAll("pre code").forEach((block) => {
const pre = block.parentElement;
const wrapper = document.createElement("div");
wrapper.className = "relative group";
pre.parentNode.insertBefore(wrapper, pre);
wrapper.appendChild(pre);
const button = document.createElement("button");
button.className =
"absolute top-2 right-2 bg-background-secondary text-text-secondary px-2 py-1 rounded text-xs opacity-0 group-hover:opacity-100 transition-opacity border border-border hover:text-accent";
button.textContent = "Copy";
button.onclick = async () => {
await navigator.clipboard.writeText(block.textContent);
button.textContent = "Copied!";
setTimeout(() => {
button.textContent = "Copy";
}, 2000);
};
wrapper.appendChild(button);
});
}
// Add copy buttons to all code blocks
document.querySelectorAll("pre code").forEach((block) => {
const pre = block.parentElement;
const wrapper = document.createElement("div");
wrapper.className = "relative group";
pre.parentNode.insertBefore(wrapper, pre);
wrapper.appendChild(pre);
const button = document.createElement("button");
button.className =
"absolute top-2 right-2 bg-background-secondary text-text-secondary px-2 py-1 rounded text-xs opacity-0 group-hover:opacity-100 transition-opacity border border-border hover:text-accent";
button.textContent = "Copy";
button.onclick = async () => {
await navigator.clipboard.writeText(block.textContent);
button.textContent = "Copied!";
setTimeout(() => {
button.textContent = "Copy";
}, 2000);
};
wrapper.appendChild(button);
});
});
// Add copy buttons to all code blocks
document.querySelectorAll("pre code").forEach((block) => {
const pre = block.parentElement;
const wrapper = document.createElement("div");
wrapper.className = "relative group";
pre.parentNode.insertBefore(wrapper, pre);
wrapper.appendChild(pre);
const button = document.createElement("button");
button.className =
"absolute top-2 right-2 bg-background-secondary text-text-secondary px-2 py-1 rounded text-xs opacity-0 group-hover:opacity-100 transition-opacity border border-border hover:text-accent";
button.textContent = "Copy";
button.onclick = async () => {
await navigator.clipboard.writeText(block.textContent);
button.textContent = "Copied!";
setTimeout(() => {
button.textContent = "Copy";
}, 2000);
};
wrapper.appendChild(button);
});
// Search state
let idx;
let searchDocs = [];
// Load index on page load
fetch("/docs/search-index.json")
.then((r) => r.json())
.then((data) => {
idx = lunr(function () {
this.use(lunr.flex);
this.ref("id");
this.field("title", { boost: 10 });
this.field("content", { boost: 1 });
data.forEach((doc) => this.add(doc));
});
searchDocs = data;
console.log("Search index loaded:", data.length, "documents");
})
.catch((err) => console.error("Failed to load search index:", err));
// Search input with debounce
const searchInput = document.getElementById("docs-search");
const searchResultsDiv = document.getElementById("search-results");
let debounceTimer;
searchInput.addEventListener("input", (e) => {
clearTimeout(debounceTimer);
debounceTimer = setTimeout(() => {
const query = e.target.value.trim();
if (query.length < 2) {
searchResultsDiv.classList.add("hidden");
return;
}
// Search with fuzzy matching
const results = idx.search(query, {
fields: { title: { boost: 10 }, content: 1 },
expand: true,
});
// Render results
renderSearchResults(results, query);
}, 150);
});
// Render search results
function renderSearchResults(results, query) {
if (results.length === 0) {
searchResultsDiv.innerHTML =
'<div class="text-center py-8 text-text-secondary">No results found</div>';
searchResultsDiv.classList.remove("hidden");
return;
}
searchResultsDiv.innerHTML = results
.map((r) => {
const doc = searchDocs.find((d) => d.id === r.ref);
if (!doc) return "";
const snippet = getSnippet(doc.content, query);
return `
<a href="${doc.url}" class="block p-4 border-b border-border hover:bg-background-secondary text-text-primary no-underline">
<div class="font-semibold mb-2">${doc.title}</div>
<div class="text-sm text-text-secondary">${snippet}...</div>
</a>
`;
})
.join("");
searchResultsDiv.classList.remove("hidden");
}
function getSnippet(content, query) {
const words = query.toLowerCase().split(/\s+/);
const contentLower = content.toLowerCase();
for (const word of words) {
const idx = contentLower.indexOf(word);
if (idx !== -1) {
const start = Math.max(0, idx - 50);
const end = Math.min(content.length, idx + 100);
return "..." + content.substring(start, end) + "...";
}
}
return content.substring(0, 150) + "...";
}
// Close search results when clicking outside
document.addEventListener("click", (e) => {
if (
!searchResultsDiv.contains(e.target) &&
e.target !== searchInput
) {
searchResultsDiv.classList.add("hidden");
}
});
// Close search on Escape key
document.addEventListener("keydown", (e) => {
if (e.key === "Escape") {
searchResultsDiv.classList.add("hidden");
searchInput.blur();
}
});
</script>
</body>
</html> </html>
} }
+3 -3
View File
@@ -53,7 +53,7 @@ func DocsLayout(nav Navigation, doc Document, user User, currentPath string) tem
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "<body x-data=\"docs\" x-init=\"initializeSearch\" class=\"") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "<body x-data=\"docs\" x-init=\"initializeSearch(); highlightCurrentPage(); initializeCodeCopyButtons()\" class=\"")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
@@ -79,7 +79,7 @@ func DocsLayout(nav Navigation, doc Document, user User, currentPath string) tem
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
for _, section := range nav.Sections { for _, section := range nav.Sections {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "<div class=\"nav-section mb-6\"><div class=\"nav-section-title font-semibold text-xs uppercase tracking-wider text-text-secondary px-4 py-3 cursor-pointer select-none flex items-center justify-between\" onclick=\"toggleSection(this)\"><span>") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "<div class=\"nav-section mb-6\"><div class=\"nav-section-title font-semibold text-xs uppercase tracking-wider text-text-secondary px-4 py-3 cursor-pointer select-none flex items-center justify-between\" @click=\"toggleSection($el)\"><span>")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
@@ -342,7 +342,7 @@ func DocsLayout(nav Navigation, doc Document, user User, currentPath string) tem
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 37, "</div></main><script>\n\t\t\t\t// Toggle sidebar section\n\t\t\t\tfunction toggleSection(el) {\n\t\t\t\t\tconst items = el.nextElementSibling;\n\t\t\t\t\tconst arrow = el.querySelector(\".section-arrow\");\n\t\t\t\t\tconst isCollapsed = items.getAttribute(\"data-collapsed\") === \"true\";\n\n\t\t\t\t\tif (isCollapsed) {\n\t\t\t\t\t\titems.classList.remove(\"hidden\");\n\t\t\t\t\t\titems.setAttribute(\"data-collapsed\", \"false\");\n\t\t\t\t\t\tarrow.style.transform = \"rotate(0deg)\";\n\t\t\t\t\t} else {\n\t\t\t\t\t\titems.classList.add(\"hidden\");\n\t\t\t\t\t\titems.setAttribute(\"data-collapsed\", \"true\");\n\t\t\t\t\t\tarrow.style.transform = \"rotate(-90deg)\";\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// Toggle sidebar on mobile\n\t\t\t\tfunction toggleSidebar() {\n\t\t\t\t\tconst sidebar = document.querySelector(\".sidebar\");\n\t\t\t\t\tsidebar.classList.toggle(\"open\");\n\t\t\t\t}\n\n\t\t\t\t// Highlight current page in nav\n\t\t\t\tdocument.addEventListener(\"DOMContentLoaded\", function () {\n\t\t\t\t\tconst currentPath = window.location.pathname;\n\t\t\t\t\tdocument.querySelectorAll(\".nav-item\").forEach((item) => {\n\t\t\t\t\t\tif (item.getAttribute(\"href\") === currentPath) {\n\t\t\t\t\t\t\titem.classList.add(\n\t\t\t\t\t\t\t\t\"text-accent\",\n\t\t\t\t\t\t\t\t\"bg-accent/10\",\n\t\t\t\t\t\t\t\t\"border-r-2\",\n\t\t\t\t\t\t\t\t\"border-accent\",\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t}\n\t\t\t\t\t});\n\n\t\t\t\t\t// Initialize syntax highlighting\n\t\t\t\t\tif (typeof hljs !== \"undefined\") {\n\t\t\t\t\t\thljs.highlightAll();\n\n\t\t\t\t\t\t// Add copy buttons to all code blocks\n\t\t\t\t\t\tdocument.querySelectorAll(\"pre code\").forEach((block) => {\n\t\t\t\t\t\t\tconst pre = block.parentElement;\n\t\t\t\t\t\t\tconst wrapper = document.createElement(\"div\");\n\t\t\t\t\t\t\twrapper.className = \"relative group\";\n\t\t\t\t\t\t\tpre.parentNode.insertBefore(wrapper, pre);\n\t\t\t\t\t\t\twrapper.appendChild(pre);\n\n\t\t\t\t\t\t\tconst button = document.createElement(\"button\");\n\t\t\t\t\t\t\tbutton.className =\n\t\t\t\t\t\t\t\t\"absolute top-2 right-2 bg-background-secondary text-text-secondary px-2 py-1 rounded text-xs opacity-0 group-hover:opacity-100 transition-opacity border border-border hover:text-accent\";\n\t\t\t\t\t\t\tbutton.textContent = \"Copy\";\n\t\t\t\t\t\t\tbutton.onclick = async () => {\n\t\t\t\t\t\t\t\tawait navigator.clipboard.writeText(block.textContent);\n\t\t\t\t\t\t\t\tbutton.textContent = \"Copied!\";\n\t\t\t\t\t\t\t\tsetTimeout(() => {\n\t\t\t\t\t\t\t\t\tbutton.textContent = \"Copy\";\n\t\t\t\t\t\t\t\t}, 2000);\n\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\twrapper.appendChild(button);\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\n\t\t\t\t\t// Add copy buttons to all code blocks\n\t\t\t\t\tdocument.querySelectorAll(\"pre code\").forEach((block) => {\n\t\t\t\t\t\tconst pre = block.parentElement;\n\t\t\t\t\t\tconst wrapper = document.createElement(\"div\");\n\t\t\t\t\t\twrapper.className = \"relative group\";\n\t\t\t\t\t\tpre.parentNode.insertBefore(wrapper, pre);\n\t\t\t\t\t\twrapper.appendChild(pre);\n\n\t\t\t\t\t\tconst button = document.createElement(\"button\");\n\t\t\t\t\t\tbutton.className =\n\t\t\t\t\t\t\t\"absolute top-2 right-2 bg-background-secondary text-text-secondary px-2 py-1 rounded text-xs opacity-0 group-hover:opacity-100 transition-opacity border border-border hover:text-accent\";\n\t\t\t\t\t\tbutton.textContent = \"Copy\";\n\t\t\t\t\t\tbutton.onclick = async () => {\n\t\t\t\t\t\t\tawait navigator.clipboard.writeText(block.textContent);\n\t\t\t\t\t\t\tbutton.textContent = \"Copied!\";\n\t\t\t\t\t\t\tsetTimeout(() => {\n\t\t\t\t\t\t\t\tbutton.textContent = \"Copy\";\n\t\t\t\t\t\t\t}, 2000);\n\t\t\t\t\t\t};\n\t\t\t\t\t\twrapper.appendChild(button);\n\t\t\t\t\t});\n\t\t\t\t});\n\n\t\t\t\t// Add copy buttons to all code blocks\n\t\t\t\tdocument.querySelectorAll(\"pre code\").forEach((block) => {\n\t\t\t\t\tconst pre = block.parentElement;\n\t\t\t\t\tconst wrapper = document.createElement(\"div\");\n\t\t\t\t\twrapper.className = \"relative group\";\n\t\t\t\t\tpre.parentNode.insertBefore(wrapper, pre);\n\t\t\t\t\twrapper.appendChild(pre);\n\n\t\t\t\t\tconst button = document.createElement(\"button\");\n\t\t\t\t\tbutton.className =\n\t\t\t\t\t\t\"absolute top-2 right-2 bg-background-secondary text-text-secondary px-2 py-1 rounded text-xs opacity-0 group-hover:opacity-100 transition-opacity border border-border hover:text-accent\";\n\t\t\t\t\tbutton.textContent = \"Copy\";\n\t\t\t\t\tbutton.onclick = async () => {\n\t\t\t\t\t\tawait navigator.clipboard.writeText(block.textContent);\n\t\t\t\t\t\tbutton.textContent = \"Copied!\";\n\t\t\t\t\t\tsetTimeout(() => {\n\t\t\t\t\t\t\tbutton.textContent = \"Copy\";\n\t\t\t\t\t\t}, 2000);\n\t\t\t\t\t};\n\t\t\t\t\twrapper.appendChild(button);\n\t\t\t\t});\n\n\t\t\t\t// Search state\n\t\t\t\tlet idx;\n\t\t\t\tlet searchDocs = [];\n\n\t\t\t\t// Load index on page load\n\t\t\t\tfetch(Line truncated templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 37, "</div></main></body></html>")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
+60 -5
View File
@@ -11,6 +11,24 @@ function toggleSidebar(): void {
} }
} }
function toggleSection(el: HTMLElement): void {
const items = el.nextElementSibling as HTMLElement;
const arrow = el.querySelector(".section-arrow") as HTMLElement;
if (!items || !arrow) return;
const isCollapsed = items.getAttribute("data-collapsed") === "true";
if (isCollapsed) {
items.classList.remove("hidden");
items.setAttribute("data-collapsed", "false");
arrow.style.transform = "rotate(0deg)";
} else {
items.classList.add("hidden");
items.setAttribute("data-collapsed", "true");
arrow.style.transform = "rotate(-90deg)";
}
}
function initializeDocsSearch(): void { function initializeDocsSearch(): void {
const searchInput = document.getElementById( const searchInput = document.getElementById(
"docs-search", "docs-search",
@@ -89,14 +107,51 @@ function performDocsSearch(query: string): void {
} }
} }
Alpine.data("docs", () => ({ function highlightCurrentPage(): void {
toggleSidebar, const currentPath = window.location.pathname;
initializeSearch: initializeDocsSearch, document.querySelectorAll(".nav-item").forEach((item) => {
})); if (item.getAttribute("href") === currentPath) {
item.classList.add("text-accent", "bg-accent/10", "border-r-2", "border-accent");
}
});
}
export { toggleSidebar, initializeDocsSearch }; function initializeCodeCopyButtons(): void {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const hljs = (window as any).hljs;
if (hljs) {
hljs.highlightAll();
document.querySelectorAll("pre code").forEach((block) => {
const pre = block.parentElement;
if (!pre) return;
const wrapper = document.createElement("div");
wrapper.className = "relative group";
pre.parentNode.insertBefore(wrapper, pre);
wrapper.appendChild(pre);
const button = document.createElement("button");
button.className = "absolute top-2 right-2 bg-background-secondary text-text-secondary px-2 py-1 rounded text-xs opacity-0 group-hover:opacity-100 transition-opacity border border-border hover:text-accent";
button.textContent = "Copy";
button.onclick = async () => {
await navigator.clipboard.writeText(block.textContent || "");
button.textContent = "Copied!";
setTimeout(() => {
button.textContent = "Copy";
}, 2000);
};
wrapper.appendChild(button);
});
}
}
Alpine.data("docs", () => ({ Alpine.data("docs", () => ({
toggleSidebar, toggleSidebar,
toggleSection,
initializeSearch: initializeDocsSearch, initializeSearch: initializeDocsSearch,
highlightCurrentPage,
initializeCodeCopyButtons,
})); }));
export { toggleSidebar, toggleSection, initializeDocsSearch, highlightCurrentPage, initializeCodeCopyButtons };