Skip to content

Commit db6d835

Browse files
feat: Improve handling of relative links in markdown (#563)
* WIP: MarkdownLink * Tweak MarkdownLink * Handle relative link without ./
1 parent c20e818 commit db6d835

File tree

1 file changed

+23
-71
lines changed

1 file changed

+23
-71
lines changed

src/components/MarkdownLink.tsx

Lines changed: 23 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -1,93 +1,45 @@
11
import { Link } from '@tanstack/react-router'
22
import type { HTMLProps } from 'react'
33

4+
function isRelativeLink(link: string) {
5+
return (
6+
!link.startsWith(`/`) &&
7+
!link.startsWith('http://') &&
8+
!link.startsWith('https://') &&
9+
!link.startsWith('//') &&
10+
!link.startsWith('#') &&
11+
!link.startsWith('mailto:')
12+
)
13+
}
14+
415
export function MarkdownLink({
516
href: hrefProp,
617
...rest
718
}: HTMLProps<HTMLAnchorElement>) {
8-
if (
9-
hrefProp?.startsWith('http') ||
10-
hrefProp?.startsWith('#') ||
11-
hrefProp?.startsWith('//')
12-
) {
13-
// eslint-disable-next-line jsx-a11y/anchor-has-content
19+
if (!isRelativeLink(hrefProp ?? '')) {
1420
return <a {...rest} href={hrefProp} />
1521
}
1622

1723
const [hrefWithoutHash, hash] = hrefProp?.split('#') ?? []
18-
let [to] = hrefWithoutHash?.split('.md') ?? []
24+
let hrefWithoutMd = hrefWithoutHash.replace('.md', '')
25+
26+
// Force relative links to resolve one level higher
27+
if (hrefWithoutMd.startsWith('../')) {
28+
hrefWithoutMd = hrefWithoutMd.replace(/^\.\.\//gm, '../../')
29+
} else if (hrefWithoutMd.startsWith('./')) {
30+
hrefWithoutMd = hrefWithoutMd.replace(/^\.\//gm, '../')
31+
} else {
32+
hrefWithoutMd = `../${hrefWithoutMd}`
33+
}
1934

2035
return (
2136
<Link
2237
{...rest}
2338
unsafeRelative="path"
24-
to={to}
39+
to={hrefWithoutMd}
2540
hash={hash}
2641
preload={undefined}
2742
ref={undefined}
2843
/>
2944
)
3045
}
31-
32-
// function resolveRelativePath(routerHref: string, markdownPath: string): string {
33-
// let hash = ''
34-
// let basePath = routerHref
35-
// let relativePath = markdownPath
36-
37-
// // Check if the relative path starts with a hash
38-
// if (relativePath.startsWith('#')) {
39-
// // If the basePath already has a hash, remove it
40-
// const hashIndex = basePath.indexOf('#')
41-
// if (hashIndex !== -1) {
42-
// basePath = basePath.substring(0, hashIndex)
43-
// }
44-
45-
// return basePath + relativePath
46-
// }
47-
48-
// // Remove hash from path if it exists, we'll add it back later
49-
// if (hashIndex !== -1) {
50-
// hash = relativePath.substring(hashIndex)
51-
// relativePath = relativePath.substring(0, hashIndex)
52-
// }
53-
54-
// // Remove .md extension if it exists
55-
// if (relativePath.endsWith('.md')) {
56-
// relativePath = relativePath.substring(0, relativePath.length - 3)
57-
// } else {
58-
// // If the path doesn't end with .md, return the path as is
59-
// return relativePath + hash
60-
// }
61-
62-
// const stack = basePath.split('/').filter(Boolean)
63-
// const parts = relativePath.split('/')
64-
65-
// let firstDoubleDotEncountered = false // Flag to track the first ".."
66-
67-
// for (let i = 0; i < parts.length; i++) {
68-
// const part = parts[i]
69-
// if (part === '.') {
70-
// continue
71-
// }
72-
// if (part === '..') {
73-
// if (!firstDoubleDotEncountered) {
74-
// // First time encountering ".."
75-
// stack.pop() // First pop
76-
// stack.pop() // Second pop
77-
// firstDoubleDotEncountered = true // Set the flag
78-
// } else {
79-
// // Subsequent ".."
80-
// stack.pop()
81-
// }
82-
// } else {
83-
// stack.push(part)
84-
// }
85-
// }
86-
87-
// let resolvedPath = '/' + stack.filter(Boolean).join('/')
88-
89-
// // Add the hash back
90-
// resolvedPath += hash
91-
92-
// return resolvedPath
93-
// }

0 commit comments

Comments
 (0)