Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions docs/.vitepress/theme/Layout.vue
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
<script setup>
import DefaultTheme from 'vitepress/theme'
import CustomFooter from './components/CustomFooter.vue'
import { useTabAnchors } from './useTabAnchors'

const { Layout } = DefaultTheme

useTabAnchors()
</script>

<template>
Expand Down
75 changes: 75 additions & 0 deletions docs/.vitepress/theme/useTabAnchors.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import { onMounted, onUnmounted, watch } from "vue";
import { useRoute } from "vitepress";

function slugify(text: string): string {
return text
.toLowerCase()
.replace(/[^a-z0-9]+/g, "-")
.replace(/(^-|-$)/g, "");
}

function findTabButton(labelSlug: string): HTMLButtonElement | null {
const buttons = document.querySelectorAll<HTMLButtonElement>(
".plugin-tabs--tab"
);
for (let i = 0; i < buttons.length; i++) {
const btn = buttons[i];
if (slugify(btn.textContent?.trim() ?? "") === labelSlug) {
return btn;
}
}
return null;
}

function activateTabFromHash(): boolean {
const hash = decodeURIComponent(location.hash.slice(1));
if (!hash) return false;

const btn = findTabButton(hash);
if (!btn) return false;
if (btn.getAttribute("aria-selected") !== "true") {
btn.click();
}
// Defer scroll to after Vue re-renders the tab content
setTimeout(() => {
btn.closest(".plugin-tabs")?.scrollIntoView({ behavior: "smooth", block: "nearest" });
}, 0);
return true;
}

function activateTabFromHashWhenReady() {
if (!location.hash || activateTabFromHash()) return;

const observer = new MutationObserver(() => {
if (activateTabFromHash()) observer.disconnect();
});
observer.observe(document.body, { childList: true, subtree: true });
setTimeout(() => observer.disconnect(), 5000);
}

export function useTabAnchors() {
const route = useRoute();

function onTabClick(e: Event) {
const target = e.target as HTMLElement;
const btn = target.closest(".plugin-tabs--tab") as HTMLButtonElement | null;
if (!btn) return;
const label = btn.textContent?.trim();
if (!label) return;
history.replaceState(null, "", `#${slugify(label)}`);
}

onMounted(() => {
document.addEventListener("click", onTabClick);
activateTabFromHashWhenReady();
});

watch(
() => route.path,
() => activateTabFromHashWhenReady()
);

onUnmounted(() => {
document.removeEventListener("click", onTabClick);
});
}
Loading