diff --git a/docs/config/frontmatter/basic.md b/docs/config/frontmatter/basic.md
index a8f86723..e194a54c 100644
--- a/docs/config/frontmatter/basic.md
+++ b/docs/config/frontmatter/basic.md
@@ -139,6 +139,30 @@ permalink: /config/frontmatter/basic/
`'deep'` 与 `[2, 6]` 相同,将显示从 `
` 到 `` 的所有标题。
+::: tip 小技巧
+在 markdown 内容的 标题后面,使用属性语法 `{data-outline="level"}` / `{outline="level"}`,
+可以重新设置当前标题的后代标题的显示的最大级别。
+
+**例如**:
+
+```md /{data-outline="5"}/
+## 标题 1 {data-outline="5"}
+
+### 三级标题
+#### 四级标题
+##### 五级标题
+###### 六级标题
+
+## 标题 2
+
+### 三级标题
+#### 四级标题
+```
+
+需要注意的是, `level` 的值应该大于当前标题的级别,否则不会生效。
+
+:::
+
### prev
- 类型: `string | { text: string, link: string, icon?: string }`
diff --git a/docs/en/config/frontmatter/basic.md b/docs/en/config/frontmatter/basic.md
index b683b4a9..d2f0cef1 100644
--- a/docs/en/config/frontmatter/basic.md
+++ b/docs/en/config/frontmatter/basic.md
@@ -138,6 +138,30 @@ Display a badge on the right side of the article title.
`'deep'` is the same as `[2, 6]`, which displays all headings from `` to ``.
+::: tip Tips
+In markdown content, using the attribute syntax `{data-outline="level"}` / `{outline="level"}`
+after a heading allows you to reset the maximum display level for descendant headings under the current heading.
+
+**For example**:
+
+```md /{data-outline="5"}/
+## Heading 1 {data-outline="5"}
+
+### Level 3 Heading
+#### Level 4 Heading
+##### Level 5 Heading
+###### Level 6 Heading
+
+## Heading 2
+
+### Level 3 Heading
+#### Level 4 Heading
+```
+
+Note that the value of `level` should be greater than the level of the current heading; otherwise, it will not take effect.
+
+:::
+
### prev
- Type: `string | { text: string, link: string, icon?: string }`
diff --git a/theme/src/client/composables/outline.ts b/theme/src/client/composables/outline.ts
index b74637a5..a9219a0d 100644
--- a/theme/src/client/composables/outline.ts
+++ b/theme/src/client/composables/outline.ts
@@ -42,6 +42,7 @@ const resolvedHeaders: { element: HTMLHeadElement, link: string }[] = []
export type MenuItem = Omit & {
element: HTMLHeadElement
children?: MenuItem[]
+ lowLevel?: number
}
export const headersSymbol: InjectionKey[> = Symbol(
@@ -85,9 +86,41 @@ export function getHeaders(range?: ThemeOutline): MenuItem[] {
title: serializeHeader(el),
link: `#${el.id}`,
level,
+ lowLevel: getLowLevel(el as HTMLHeadElement, level),
}
})
- return resolveHeaders(headers, range)
+ if (range === false)
+ return []
+
+ const [high, low] = getRange(range)
+ return resolveSubRangeHeader(resolveHeaders(headers, high), low)
+}
+
+function getRange(range?: Exclude): readonly [number, number] {
+ const levelsRange = range || 2
+ // [high, low]
+ return typeof levelsRange === 'number'
+ ? [levelsRange, levelsRange]
+ : levelsRange === 'deep'
+ ? [2, 6]
+ : levelsRange
+}
+
+function getLowLevel(el: HTMLHeadElement, level: number): number | undefined {
+ if (!el.hasAttribute('data-outline') && !el.hasAttribute('outline'))
+ return
+
+ // only support
+ // data-outline="3" -> star, end -> [level, 3]
+ const str = (el.getAttribute('data-outline') || el.getAttribute('outline'))?.trim()
+ if (!str)
+ return
+
+ const num = Number(str)
+ if (!Number.isNaN(num) && num >= level)
+ return num
+
+ return undefined
}
function serializeHeader(h: Element): string {
@@ -137,20 +170,8 @@ function clearHeaderNodeList(list?: ChildNode[]) {
}
}
-export function resolveHeaders(headers: MenuItem[], range?: ThemeOutline): MenuItem[] {
- if (range === false)
- return []
-
- const levelsRange = range || 2
-
- const [high, low]: [number, number]
- = typeof levelsRange === 'number'
- ? [levelsRange, levelsRange]
- : levelsRange === 'deep'
- ? [2, 6]
- : levelsRange
-
- headers = headers.filter(h => h.level >= high && h.level <= low)
+export function resolveHeaders(headers: MenuItem[], high: number): MenuItem[] {
+ headers = headers.filter(h => h.level >= high)
// clear previous caches
resolvedHeaders.length = 0
// update global header list for active link rendering
@@ -180,6 +201,17 @@ export function resolveHeaders(headers: MenuItem[], range?: ThemeOutline): MenuI
return ret
}
+function resolveSubRangeHeader(headers: MenuItem[], low: number): MenuItem[] {
+ return headers.map((header) => {
+ if (header.children?.length) {
+ const current = header.lowLevel ? Math.max(header.lowLevel, low) : low
+ const children = header.children.filter(({ level }) => level <= current)
+ header.children = resolveSubRangeHeader(children, header.lowLevel || low)
+ }
+ return header
+ })
+}
+
export function useActiveAnchor(container: Ref, marker: Ref): void {
const { isAsideEnabled } = useAside()
const router = useRouter()
]