hirsun-vuepress-theme-hope-plus
Version:
A light vuepress theme with tons of features and typewriter effect
138 lines • 5.53 kB
JavaScript
import { usePageData } from "@vuepress/client";
import { defineComponent, h, onMounted, ref, shallowRef, watch } from "vue";
import { useRoute } from "vue-router";
import { VPLink, isActiveLink } from "vuepress-shared/client";
import PrintButton from "@theme-hope/modules/info/components/PrintButton";
import { useMetaLocale } from "@theme-hope/modules/info/composables/index";
import "../styles/toc.scss";
const renderHeader = ({ title, level, slug }) => h(VPLink, {
to: `#${slug}`,
class: ["toc-link", `level${level}`],
}, () => title);
const renderChildren = (headers, headerDepth) => {
const route = useRoute();
return headers.length && headerDepth > 0
? h("ul", { class: "toc-list" }, headers.map((header) => {
const children = renderChildren(header.children, headerDepth - 1);
return [
h("li", {
class: [
"toc-item",
{ active: isActiveLink(route, `#${header.slug}`) },
],
}, renderHeader(header)),
children ? h("li", children) : null,
];
}))
: null;
};
export default defineComponent({
name: "TOC",
props: {
/**
* TOC items config
*
* TOC 项目配置
*/
items: {
type: Array,
default: () => [],
},
/**
* Max header nesting depth
*
* 最大的标题嵌套深度
*/
headerDepth: {
type: Number,
default: 2,
},
},
slots: Object,
setup(props, { slots }) {
const route = useRoute();
const page = usePageData();
const metaLocale = useMetaLocale();
const toc = shallowRef();
const tocMarkerTop = ref("-1.7rem");
const scrollTo = (top) => {
toc.value?.scrollTo({ top, behavior: "smooth" });
};
const updateTocMarker = () => {
if (toc.value) {
const activeTocItem = document.querySelector(".toc-item.active");
if (activeTocItem)
tocMarkerTop.value = `${
// active toc item top
activeTocItem.getBoundingClientRect().top -
// toc top
toc.value.getBoundingClientRect().top +
// toc scroll top
toc.value.scrollTop}px`;
else
tocMarkerTop.value = "-1.7rem";
}
else {
tocMarkerTop.value = "-1.7rem";
}
};
onMounted(() => {
// scroll to active toc item
watch(() => route.hash, (hash) => {
if (toc.value) {
// get the active toc item DOM, whose href equals to the current route
const activeTocItem = document.querySelector(`#toc a.toc-link[href$="${hash}"]`);
if (!activeTocItem)
return;
// get the top and height of the toc
const { top: tocTop, height: tocHeight } = toc.value.getBoundingClientRect();
// get the top and height of the active toc item
const { top: activeTocItemTop, height: activeTocItemHeight } = activeTocItem.getBoundingClientRect();
// when the active toc item overflows the top edge of toc
if (activeTocItemTop < tocTop)
// scroll to the top edge of toc
scrollTo(toc.value.scrollTop + activeTocItemTop - tocTop);
// when the active toc item overflows the bottom edge of toc
else if (activeTocItemTop + activeTocItemHeight >
tocTop + tocHeight)
// scroll to the bottom edge of toc
scrollTo(toc.value.scrollTop +
activeTocItemTop +
activeTocItemHeight -
tocTop -
tocHeight);
}
});
watch(() => route.fullPath, () => updateTocMarker(), { flush: "post", immediate: true });
});
return () => {
const tocHeaders = props.items.length
? renderChildren(props.items, props.headerDepth)
: page.value.headers
? renderChildren(page.value.headers, props.headerDepth)
: null;
return tocHeaders
? h("div", { class: "toc-place-holder" }, [
h("aside", { id: "toc" }, [
slots.before?.(),
h("div", { class: "toc-header" }, [
metaLocale.value.toc,
h(PrintButton),
]),
h("div", { class: "toc-wrapper", ref: toc }, [
tocHeaders,
h("div", {
class: "toc-marker",
style: {
top: tocMarkerTop.value,
},
}),
]),
slots.after?.(),
]),
])
: null;
};
},
});
//# sourceMappingURL=TOC.js.map