feat(theme): add support for {data-outline="level"} attribute syntax for headings, close #757 (#759)
This commit is contained in:
parent
73f4935ca9
commit
fc3676d6dc
@ -139,6 +139,30 @@ permalink: /config/frontmatter/basic/
|
||||
|
||||
`'deep'` 与 `[2, 6]` 相同,将显示从 `<h2>` 到 `<h6>` 的所有标题。
|
||||
|
||||
::: 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 }`
|
||||
|
||||
@ -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 `<h2>` to `<h6>`.
|
||||
|
||||
::: 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"} <!-- Only affects descendant headings of the current heading -->
|
||||
|
||||
### Level 3 Heading
|
||||
#### Level 4 Heading
|
||||
##### Level 5 Heading <!-- Level 3, 4, and 5 headings will appear in the sidebar -->
|
||||
###### Level 6 Heading <!-- This heading will NOT appear in the sidebar -->
|
||||
|
||||
## Heading 2 <!-- Headings at the same level are not affected -->
|
||||
|
||||
### Level 3 Heading <!-- By default, only up to level 3 headings are shown -->
|
||||
#### Level 4 Heading <!-- Level 4 headings are not shown -->
|
||||
```
|
||||
|
||||
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 }`
|
||||
|
||||
@ -42,6 +42,7 @@ const resolvedHeaders: { element: HTMLHeadElement, link: string }[] = []
|
||||
export type MenuItem = Omit<Header, 'slug' | 'children'> & {
|
||||
element: HTMLHeadElement
|
||||
children?: MenuItem[]
|
||||
lowLevel?: number
|
||||
}
|
||||
|
||||
export const headersSymbol: InjectionKey<Ref<MenuItem[]>> = 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<ThemeOutline, boolean>): 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<HTMLElement | null>, marker: Ref<HTMLElement | null>): void {
|
||||
const { isAsideEnabled } = useAside()
|
||||
const router = useRouter()
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user