-
+
diff --git a/src/client/theme-default/components/VPDocAside.vue b/src/client/theme-default/components/VPDocAside.vue
index 4232d1563e82..f434ce55fd7b 100644
--- a/src/client/theme-default/components/VPDocAside.vue
+++ b/src/client/theme-default/components/VPDocAside.vue
@@ -3,7 +3,7 @@ import { useData } from 'vitepress'
import VPDocAsideOutline from './VPDocAsideOutline.vue'
import VPDocAsideCarbonAds from './VPDocAsideCarbonAds.vue'
-const { page, theme } = useData()
+const { theme } = useData()
@@ -11,7 +11,7 @@ const { page, theme } = useData()
-
+
diff --git a/src/client/theme-default/components/VPDocAsideOutline.vue b/src/client/theme-default/components/VPDocAsideOutline.vue
index ee0d0f9a68cf..c8160cca43d7 100644
--- a/src/client/theme-default/components/VPDocAsideOutline.vue
+++ b/src/client/theme-default/components/VPDocAsideOutline.vue
@@ -1,14 +1,27 @@
+
+
+
+
+
+
diff --git a/src/client/theme-default/composables/outline.ts b/src/client/theme-default/composables/outline.ts
index 914dd55327e0..9467d0f1cbae 100644
--- a/src/client/theme-default/composables/outline.ts
+++ b/src/client/theme-default/composables/outline.ts
@@ -1,21 +1,87 @@
-import { Ref, computed, onMounted, onUpdated, onUnmounted } from 'vue'
-import { useData } from 'vitepress'
+import type { DefaultTheme } from 'vitepress/theme'
+import { onMounted, onUnmounted, onUpdated, type Ref } from 'vue'
+import type { Header } from '../../shared.js'
import { useAside } from '../composables/aside.js'
import { throttleAndDebounce } from '../support/utils.js'
// magic number to avoid repeated retrieval
-const PAGE_OFFSET = 56
+const PAGE_OFFSET = 71
-export function useOutline() {
- const { page } = useData()
+export type MenuItem = Omit & {
+ children?: MenuItem[]
+}
+
+export function getHeaders(pageOutline: DefaultTheme.Config['outline']) {
+ if (pageOutline === false) return []
+ let updatedHeaders: MenuItem[] = []
+ document
+ .querySelectorAll('h2, h3, h4, h5, h6')
+ .forEach((el) => {
+ if (el.textContent && el.id) {
+ updatedHeaders.push({
+ level: Number(el.tagName[1]),
+ title: el.innerText.split('\n')[0],
+ link: `#${el.id}`
+ })
+ }
+ })
+ return resolveHeaders(updatedHeaders, pageOutline)
+}
+
+export function resolveHeaders(
+ headers: MenuItem[],
+ levelsRange: Exclude = 2
+) {
+ const levels: [number, number] =
+ typeof levelsRange === 'number'
+ ? [levelsRange, levelsRange]
+ : levelsRange === 'deep'
+ ? [2, 6]
+ : levelsRange
+
+ return groupHeaders(headers, levels)
+}
+
+function groupHeaders(headers: MenuItem[], levelsRange: [number, number]) {
+ const result: MenuItem[] = []
- const hasOutline = computed(() => {
- return page.value.headers.length > 0
+ headers = headers.map((h) => ({ ...h }))
+ headers.forEach((h, index) => {
+ if (h.level >= levelsRange[0] && h.level <= levelsRange[1]) {
+ if (addToParent(index, headers, levelsRange)) {
+ result.push(h)
+ }
+ }
})
- return {
- hasOutline
+ return result
+}
+
+function addToParent(
+ currIndex: number,
+ headers: MenuItem[],
+ levelsRange: [number, number]
+) {
+ if (currIndex === 0) {
+ return true
}
+
+ const currentHeader = headers[currIndex]
+ for (let index = currIndex - 1; index >= 0; index--) {
+ const header = headers[index]
+
+ if (
+ header.level < currentHeader.level &&
+ header.level >= levelsRange[0] &&
+ header.level <= levelsRange[1]
+ ) {
+ if (header.children == null) header.children = []
+ header.children.push(currentHeader)
+ return false
+ }
+ }
+
+ return true
}
export function useActiveAnchor(
@@ -108,7 +174,7 @@ export function useActiveAnchor(
}
function getAnchorTop(anchor: HTMLAnchorElement): number {
- return anchor.parentElement!.offsetTop - PAGE_OFFSET - 15
+ return anchor.parentElement!.offsetTop - PAGE_OFFSET
}
function isAnchorActive(
diff --git a/types/default-theme.d.ts b/types/default-theme.d.ts
index 6bdcd6c0096a..36ba6f0d68f4 100644
--- a/types/default-theme.d.ts
+++ b/types/default-theme.d.ts
@@ -13,6 +13,13 @@ export namespace DefaultTheme {
*/
siteTitle?: string | false
+ /**
+ * Custom header levels of outline in the aside component.
+ *
+ * @default 2
+ */
+ outline?: number | [number, number] | 'deep' | false
+
/**
* Custom outline title in the aside component.
*