Skip to content

Commit 36ef0a6

Browse files
brc-ddbtea
andcommitted
feat: support using titles instead of region names in markdown file inclusion
closes #4375 closes #4382 Co-authored-by: btea <[email protected]>
1 parent 50db6aa commit 36ef0a6

File tree

6 files changed

+99
-9
lines changed

6 files changed

+99
-9
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
# header 1
2+
3+
header 1 content
4+
5+
## header 1.1
6+
7+
header 1.1 content
8+
9+
### header 1.1.1
10+
11+
header 1.1.1 content
12+
13+
### header 1.1.2
14+
15+
header 1.1.2 content
16+
17+
## header 1.2
18+
19+
header 1.2 content
20+
21+
### header 1.2.1
22+
23+
header 1.2.1 content
24+
25+
### header 1.2.2
26+
27+
header 1.2.2 content

__tests__/e2e/markdown-extensions/index.md

+5-1
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,10 @@ export default config
213213

214214
<!--@include: ./region-include.md#range-region{5,}-->
215215

216+
## Markdown File Inclusion with Header
217+
218+
<!--@include: ./header-include.md#header-1-1-->
219+
216220
## Image Lazy Loading
217221

218-
![vitepress logo](/vitepress.png)
222+
![vitepress logo](/vitepress.png)

docs/en/guide/markdown.md

+35
Original file line numberDiff line numberDiff line change
@@ -897,6 +897,41 @@ You can also use a [VS Code region](https://code.visualstudio.com/docs/editor/co
897897
Note that this does not throw errors if your file is not present. Hence, when using this feature make sure that the contents are being rendered as expected.
898898
:::
899899

900+
Instead of VS Code regions, you can also use header anchors to include a specific section of the file. For example, if you have a header in your markdown file like this:
901+
902+
```md
903+
## My Base Section
904+
905+
Some content here.
906+
907+
### My Sub Section
908+
909+
Some more content here.
910+
911+
## Another Section
912+
913+
Content outside `My Base Section`.
914+
```
915+
916+
You can include the `My Base Section` section like this:
917+
918+
```md
919+
## My Extended Section
920+
<!--@include: ./parts/basics.md#my-base-section-->
921+
```
922+
923+
**Equivalent code**
924+
925+
```md
926+
## My Extended Section
927+
928+
Some content here.
929+
930+
### My Sub Section
931+
932+
Some more content here.
933+
```
934+
900935
## Math Equations
901936

902937
This is currently opt-in. To enable it, you need to install `markdown-it-mathjax3` and set `markdown.math` to `true` in your config file:

src/node/markdownToVue.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,7 @@ export async function createMarkdownToVueRenderFn(
142142

143143
// resolve includes
144144
let includes: string[] = []
145-
src = processIncludes(srcDir, src, fileOrig, includes)
145+
src = processIncludes(md, srcDir, src, fileOrig, includes)
146146

147147
const localeIndex = getLocaleForPath(siteConfig?.site, relativePath)
148148

src/node/plugins/localSearchPlugin.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ export async function localSearchPlugin(
5656
const relativePath = slash(path.relative(srcDir, file))
5757
const env: MarkdownEnv = { path: file, relativePath, cleanUrls }
5858
const md_raw = await fs.promises.readFile(file, 'utf-8')
59-
const md_src = processIncludes(srcDir, md_raw, file, [])
59+
const md_src = processIncludes(md, srcDir, md_raw, file, [])
6060
if (options._render) {
6161
return await options._render(md_src, env, md)
6262
} else {

src/node/utils/processIncludes.ts

+30-6
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,20 @@
11
import fs from 'fs-extra'
22
import matter from 'gray-matter'
3+
import type { MarkdownItAsync } from 'markdown-it-async'
34
import path from 'node:path'
45
import c from 'picocolors'
56
import { findRegion } from '../markdown/plugins/snippet'
67
import { slash } from '../shared'
78

89
export function processIncludes(
10+
md: MarkdownItAsync,
911
srcDir: string,
1012
src: string,
1113
file: string,
1214
includes: string[]
1315
): string {
1416
const includesRE = /<!--\s*@include:\s*(.*?)\s*-->/g
15-
const regionRE = /(#[\w-]+)/
17+
const regionRE = /(#\S+)/
1618
const rangeRE = /\{(\d*),(\d*)\}$/
1719

1820
return src.replace(includesRE, (m: string, m1: string) => {
@@ -39,17 +41,39 @@ export function processIncludes(
3941
if (region) {
4042
const [regionName] = region
4143
const lines = content.split(/\r?\n/)
42-
const regionLines = findRegion(lines, regionName.slice(1))
43-
content = lines.slice(regionLines?.start, regionLines?.end).join('\n')
44+
let { start, end } = findRegion(lines, regionName.slice(1)) ?? {}
45+
46+
if (start === undefined) {
47+
// region not found, it might be a header
48+
const tokens = md
49+
.parse(content, {})
50+
.filter((t) => t.type === 'heading_open' && t.map)
51+
const idx = tokens.findIndex(
52+
(t) => t.attrGet('id') === regionName.slice(1)
53+
)
54+
const token = tokens[idx]
55+
if (token) {
56+
start = token.map![1]
57+
const level = parseInt(token.tag.slice(1))
58+
for (let i = idx + 1; i < tokens.length; i++) {
59+
if (parseInt(tokens[i].tag.slice(1)) <= level) {
60+
end = tokens[i].map![0] - 1
61+
break
62+
}
63+
}
64+
}
65+
}
66+
67+
content = lines.slice(start, end).join('\n')
4468
}
4569

4670
if (range) {
4771
const [, startLine, endLine] = range
4872
const lines = content.split(/\r?\n/)
4973
content = lines
5074
.slice(
51-
startLine ? parseInt(startLine, 10) - 1 : undefined,
52-
endLine ? parseInt(endLine, 10) : undefined
75+
startLine ? parseInt(startLine) - 1 : undefined,
76+
endLine ? parseInt(endLine) : undefined
5377
)
5478
.join('\n')
5579
}
@@ -60,7 +84,7 @@ export function processIncludes(
6084

6185
includes.push(slash(includePath))
6286
// recursively process includes in the content
63-
return processIncludes(srcDir, content, includePath, includes)
87+
return processIncludes(md, srcDir, content, includePath, includes)
6488

6589
//
6690
} catch (error) {

0 commit comments

Comments
 (0)