fix: normalize url fragments in internal links to correctly resolve to anchors (#4628)

closes #4605

- Normalizations aren't applied to raw html inside markdown or vue code.
- It is assumed `slugify(slugify(something)) === slugify(something)`
pull/4633/head
Divyansh Singh 6 months ago committed by GitHub
parent e06b83ec09
commit e25d080550
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -13,7 +13,7 @@ import {
import { sfcPlugin, type SfcPluginOptions } from '@mdit-vue/plugin-sfc'
import { titlePlugin } from '@mdit-vue/plugin-title'
import { tocPlugin, type TocPluginOptions } from '@mdit-vue/plugin-toc'
import { slugify } from '@mdit-vue/shared'
import { slugify as defaultSlugify } from '@mdit-vue/shared'
import type {
LanguageInput,
ShikiTransformer,
@ -232,6 +232,8 @@ export async function createMarkdownRenderer(
await options.preConfig(md)
}
const slugify = options.anchor?.slugify ?? defaultSlugify
// custom plugins
md.use(componentPlugin, { ...options.component })
.use(highlightLinePlugin)
@ -242,7 +244,8 @@ export async function createMarkdownRenderer(
.use(
linkPlugin,
{ target: '_blank', rel: 'noreferrer', ...options.externalLinks },
base
base,
slugify
)
.use(lineNumberPlugin, options.lineNumbers)
@ -317,6 +320,7 @@ export async function createMarkdownRenderer(
} as SfcPluginOptions)
.use(titlePlugin)
.use(tocPlugin, {
slugify,
...options.toc
} as TocPluginOptions)

@ -16,7 +16,8 @@ const indexRE = /(^|.*\/)index.md(#?.*)$/i
export const linkPlugin = (
md: MarkdownItAsync,
externalAttrs: Record<string, string>,
base: string
base: string,
slugify: (str: string) => string
) => {
md.renderer.rules.link_open = (
tokens,
@ -27,9 +28,12 @@ export const linkPlugin = (
) => {
const token = tokens[idx]
const hrefIndex = token.attrIndex('href')
const targetIndex = token.attrIndex('target')
const downloadIndex = token.attrIndex('download')
if (hrefIndex >= 0 && targetIndex < 0 && downloadIndex < 0) {
if (
hrefIndex >= 0 &&
token.attrIndex('target') < 0 &&
token.attrIndex('download') < 0 &&
token.attrGet('class') !== 'header-anchor' // header anchors are already normalized
) {
const hrefAttr = token.attrs![hrefIndex]
const url = hrefAttr[1]
if (isExternal(url)) {
@ -54,7 +58,7 @@ export const linkPlugin = (
) {
normalizeHref(hrefAttr, env)
} else if (url.startsWith('#')) {
hrefAttr[1] = decodeURI(hrefAttr[1])
hrefAttr[1] = decodeURI(normalizeHash(hrefAttr[1]))
}
// append base to internal (non-relative) urls
@ -72,7 +76,7 @@ export const linkPlugin = (
const indexMatch = url.match(indexRE)
if (indexMatch) {
const [, path, hash] = indexMatch
url = path + hash
url = path + normalizeHash(hash)
} else {
let cleanUrl = url.replace(/[?#].*$/, '')
// transform foo.md -> foo[.html]
@ -88,7 +92,7 @@ export const linkPlugin = (
cleanUrl += '.html'
}
const parsed = new URL(url, 'http://a.com')
url = cleanUrl + parsed.search + parsed.hash
url = cleanUrl + parsed.search + normalizeHash(parsed.hash)
}
// ensure leading . for relative paths
@ -103,6 +107,10 @@ export const linkPlugin = (
hrefAttr[1] = decodeURI(url)
}
function normalizeHash(str: string) {
return str ? encodeURI('#' + slugify(decodeURI(str).slice(1))) : ''
}
function pushLink(link: string, env: MarkdownEnv) {
const links = env.links || (env.links = [])
links.push(link)

Loading…
Cancel
Save