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:
+4
-423
@@ -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>
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
+60
-5
@@ -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 };
|
||||||
|
|||||||
Reference in New Issue
Block a user