[READ-ONLY] a fast, modern browser for the npm registry

feat: add clickable anchor links to readme headings (#1417)

authored by

Marcus Blättermann and committed by
GitHub
cad008c8 1e45cdca

+15 -20
+10
app/components/Readme.vue
··· 150 150 @apply inline i-lucide:external-link rtl-flip ms-1 opacity-50; 151 151 } 152 152 153 + .readme :deep(a[href^='#']::after) { 154 + /* I don't know what kind of sorcery this is, but it ensures this icon can't wrap to a new line on its own. */ 155 + content: '__'; 156 + @apply inline i-carbon:link rtl-flip ms-1 opacity-0; 157 + } 158 + 159 + .readme :deep(a[href^='#']:hover::after) { 160 + @apply opacity-100; 161 + } 162 + 153 163 .readme :deep(code) { 154 164 @apply font-mono; 155 165 font-size: 0.875em;
+3 -19
server/utils/readme.ts
··· 221 221 return true 222 222 } 223 223 224 - const replaceHtmlLink = (html: string) => { 225 - return html.replace(/href="([^"]+)"/g, (match, href) => { 226 - if (isNpmJsUrlThatCanBeRedirected(new URL(href, 'https://www.npmjs.com'))) { 227 - const newHref = href.replace(/^https?:\/\/(www\.)?npmjs\.com/, '') 228 - return `href="${newHref}"` 229 - } 230 - return match 231 - }) 232 - } 233 - 234 224 /** 235 225 * Resolve a relative URL to an absolute URL. 236 226 * If repository info is available, resolve to provider's raw file URLs. ··· 387 377 toc.push({ text: plainText, id, depth }) 388 378 } 389 379 390 - return `<h${semanticLevel} id="${id}" data-level="${depth}">${text}</h${semanticLevel}>\n` 380 + /** The link href uses the unique slug WITHOUT the 'user-content-' prefix, because that will later be added for all links. */ 381 + return `<h${semanticLevel} id="${id}" data-level="${depth}"><a href="#${uniqueSlug}">${plainText}</a></h${semanticLevel}>\n` 391 382 } 392 383 393 384 // Syntax highlighting for code blocks (uses shared highlighter) ··· 443 434 return `<blockquote>${body}</blockquote>\n` 444 435 } 445 436 446 - marked.setOptions({ 447 - renderer, 448 - walkTokens: token => { 449 - if (token.type === 'html') { 450 - token.text = replaceHtmlLink(token.text) 451 - } 452 - }, 453 - }) 437 + marked.setOptions({ renderer }) 454 438 455 439 const rawHtml = marked.parse(content) as string 456 440
+2 -1
test/unit/server/utils/readme.spec.ts
··· 388 388 const markdown = `# Title\n\nSome **bold** text and a [link](https://example.com).` 389 389 const result = await renderReadmeHtml(markdown, 'test-pkg') 390 390 391 - expect(result.html).toBe(`<h3 id="user-content-title" data-level="1">Title</h3> 391 + expect(result.html) 392 + .toBe(`<h3 id="user-content-title" data-level="1"><a href="#user-content-title">Title</a></h3> 392 393 <p>Some <strong>bold</strong> text and a <a href="https://example.com" rel="nofollow noreferrer noopener" target="_blank">link</a>.</p> 393 394 `) 394 395 })