From a8a1800ae578be88027aa4ec7561ada4d055b888 Mon Sep 17 00:00:00 2001 From: Divyansh Singh <40380293+brc-dd@users.noreply.github.com> Date: Sat, 8 Mar 2025 21:35:06 +0530 Subject: [PATCH] fix: ignore non-text content in permalink generation and fix types of markdown.config --- src/node/markdown/markdown.ts | 46 +++++++++++++++++++++-------------- 1 file changed, 28 insertions(+), 18 deletions(-) diff --git a/src/node/markdown/markdown.ts b/src/node/markdown/markdown.ts index aca503e2..12de0d11 100644 --- a/src/node/markdown/markdown.ts +++ b/src/node/markdown/markdown.ts @@ -25,6 +25,7 @@ import attrsPlugin from 'markdown-it-attrs' import { full as emojiPlugin } from 'markdown-it-emoji' import type { BuiltinLanguage, BuiltinTheme, Highlighter } from 'shiki' import type { Logger } from 'vite' +import type { Awaitable } from '../shared' import { containerPlugin, type ContainerOptions } from './plugins/containers' import { gitHubAlertsPlugin } from './plugins/githubAlerts' import { highlight as createHighlighter } from './plugins/highlight' @@ -52,11 +53,11 @@ export interface MarkdownOptions extends Options { /** * Setup markdown-it instance before applying plugins */ - preConfig?: (md: MarkdownItAsync) => Awaited + preConfig?: (md: MarkdownItAsync) => Awaitable /** * Setup markdown-it instance */ - config?: (md: MarkdownItAsync) => Awaited + config?: (md: MarkdownItAsync) => Awaitable /** * Disable cache (experimental) */ @@ -268,22 +269,31 @@ export async function createMarkdownRenderer( .map((t) => t.content) .join('') }, - permalink: anchorPlugin.permalink.linkInsideHeader({ - symbol: '​', - renderAttrs: (slug, state) => { - // Find `heading_open` with the id identical to slug - const idx = state.tokens.findIndex((token) => { - const attrs = token.attrs - const id = attrs?.find((attr) => attr[0] === 'id') - return id && slug === id[1] - }) - // Get the actual heading content - const title = state.tokens[idx + 1].content - return { - 'aria-label': `Permalink to "${title}"` - } - } - }), + permalink: (slug, _, state, idx) => { + const title = + state.tokens[idx + 1]?.children + ?.filter((token) => ['text', 'code_inline'].includes(token.type)) + .reduce((acc, t) => acc + t.content, '') + .trim() || '' + + const linkTokens = [ + Object.assign(new state.Token('text', '', 0), { content: ' ' }), + Object.assign(new state.Token('link_open', 'a', 1), { + attrs: [ + ['class', 'header-anchor'], + ['href', `#${slug}`], + ['aria-label', `Permalink to “${title}”`] + ] + }), + Object.assign(new state.Token('html_inline', '', 0), { + content: '​', + meta: { isPermalinkSymbol: true } + }), + new state.Token('link_close', 'a', -1) + ] + + state.tokens[idx + 1].children?.push(...linkTokens) + }, ...options.anchor } as anchorPlugin.AnchorOptions).use(frontmatterPlugin, { ...options.frontmatter