Skip to content

Commit 507e35d

Browse files
committed
fix: handle anchor inner links + update doc example
1 parent 32ffb80 commit 507e35d

3 files changed

Lines changed: 80 additions & 18 deletions

File tree

docs/usecase/README.md

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,16 @@
22

33
This link is very [cool](example.md).
44

5-
This one doesn't have an extension, but it works : [example](example).
5+
This one doesn't have an extension, but it works : [example](example).
66
Another way : [example](usecase/example).
77

8+
A specific link in the page [here](example#specific-section)
9+
Another specific link in the page [here with .md](example.md#specific-section)
10+
Another specific link in the page [here with query param](example?id=specific-section)
11+
Another specific link in the page [here with .md and query param](example.md?id=specific-section)
12+
813
| Syntax | Description |
9-
|-----------|-------------|
14+
| --------- | ----------- |
1015
| Header | Title |
1116
| Paragraph | Text |
1217

docs/usecase/example.md

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,27 @@
11
# 1.1 - Never Gonna Give You Up
22

3+
[Back to README](../usecase/README.md)
4+
35
## Description
46

57
It's an **example**
68

7-
![Rick.png](img.png ':size=400') ![Rick.png](img.png ':size=400') <img src="img.png" style="display: none" /> img.png :)
9+
![Rick.png](img.png ":size=400") ![Rick.png](img.png ":size=400") <img src="img.png" style="display: none" /> img.png :)
810

911
uhhhh up.svg (up.svg not replaced!) img.png 🐱
1012

11-
<img
12-
alt="ahah"
13-
src="up.svg"
13+
<img
14+
alt="ahah"
15+
src="up.svg"
1416
height="80px"
1517
/>
1618

1719
<img alt="cool" src="../usecase/../usecase/img.png" height="20px" style="border: 3px solid black; border-radius: 5px;" />
20+
21+
# Specific Section
22+
23+
Hello
24+
25+
# Another Section
26+
27+
World

src/process-inner-links.js

Lines changed: 59 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
const path = require('path')
2+
const url = require('url')
23
const markdownLinkExtractor = require('markdown-link-extractor')
34
const unified = require('unified')
45
const parser = require('remark-parse')
@@ -29,28 +30,73 @@ const cleanText = (string) => {
2930
module.exports = ({ content, name }, _, arr) => {
3031
let newContent = content
3132
const b = markdownLinkExtractor(content, true)
32-
.filter(({ href: link, title }) => {
33-
if (title && title.startsWith(':include')) return false
34-
const ext = path.parse(link).ext
33+
.filter(({ title }) => !title || !title.startsWith(':include'))
34+
.map(({ href }) => ({ href, parsed: url.parse(href) }))
35+
.filter(({ parsed }) => {
36+
const ext = path.parse(parsed.pathname).ext
3537
return ext === '' || ext === '.md'
3638
})
37-
.map(v => v.href)
38-
.map(link => ({ file: arr.find(({ name }) => name.includes(link)), link }))
39+
.map(({ href, parsed }) => {
40+
const linkPath = parsed.pathname
41+
let anchor = parsed.hash ? parsed.hash.substring(1) : null
42+
if (!anchor && parsed.query) {
43+
const params = new URLSearchParams(parsed.query)
44+
if (params.has('id')) {
45+
anchor = params.get('id')
46+
}
47+
}
48+
const resolvedPath = path.resolve(path.dirname(name), linkPath)
49+
const docsPath = arr.find(({ name }) => name.endsWith('docs/README.md'))
50+
const docsDir = docsPath ? path.dirname(docsPath.name) : ''
51+
52+
return {
53+
file: arr.find(({ name: fileName }) => {
54+
if (fileName === resolvedPath) return true
55+
if (fileName === `${resolvedPath}.md`) return true
56+
57+
if (docsDir) {
58+
const resolvedFromDocs = path.resolve(docsDir, linkPath)
59+
if (fileName === resolvedFromDocs) return true
60+
if (fileName === `${resolvedFromDocs}.md`) return true
61+
}
62+
return false
63+
}),
64+
link: href,
65+
anchor
66+
}
67+
})
3968
.filter(({ file }) => file)
40-
.map(({ file: { content }, link }) => ({
69+
.map(({ file: { content }, link, anchor }) => ({
4170
ast: unified().use(parser)
4271
.parse(content),
43-
link
72+
link,
73+
anchor
4474
}))
45-
.map(({ ast, link }) => {
46-
const [a] = ast.children.filter(({ type }) => type === 'heading')
47-
if (!a) {
75+
.map(({ ast, link, anchor }) => {
76+
let headingNode
77+
const headings = ast.children.filter(({ type }) => type === 'heading')
78+
79+
if (anchor) {
80+
for (const heading of headings) {
81+
const array = []
82+
recursiveGetValueInChildren(heading.children, array)
83+
const value = cleanText(array.join(' ')).trim()
84+
if (slug(value) === anchor) {
85+
headingNode = heading
86+
break
87+
}
88+
}
89+
} else {
90+
[headingNode] = headings
91+
}
92+
93+
if (!headingNode) {
4894
console.error('no heading (non blocking error)', link)
4995
return { link, unsafeTag: '' }
5096
}
5197

5298
const array = []
53-
recursiveGetValueInChildren(a.children, array)
99+
recursiveGetValueInChildren(headingNode.children, array)
54100
const value = cleanText(array.join(' ')).trim()
55101

56102
return { link, unsafeTag: value }
@@ -65,7 +111,8 @@ module.exports = ({ content, name }, _, arr) => {
65111
}))
66112

67113
b.forEach(({ tag, link }) => {
68-
newContent = newContent.replace(new RegExp('\\[(.*)\\]\\(' + link + '\\)'), `[$1](${tag})`)
114+
const escapedLink = link.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
115+
newContent = newContent.replace(new RegExp('\\[(.*)\\]\\(' + escapedLink + '\\)'), `[$1](${tag})`)
69116
})
70117

71118
return { content: newContent, name }

0 commit comments

Comments
 (0)