···150150 @apply inline i-lucide:external-link rtl-flip ms-1 opacity-50;
151151}
152152153153+.readme :deep(a[href^='#']::after) {
154154+ /* 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. */
155155+ content: '__';
156156+ @apply inline i-carbon:link rtl-flip ms-1 opacity-0;
157157+}
158158+159159+.readme :deep(a[href^='#']:hover::after) {
160160+ @apply opacity-100;
161161+}
162162+153163.readme :deep(code) {
154164 @apply font-mono;
155165 font-size: 0.875em;
+3-19
server/utils/readme.ts
···221221 return true
222222}
223223224224-const replaceHtmlLink = (html: string) => {
225225- return html.replace(/href="([^"]+)"/g, (match, href) => {
226226- if (isNpmJsUrlThatCanBeRedirected(new URL(href, 'https://www.npmjs.com'))) {
227227- const newHref = href.replace(/^https?:\/\/(www\.)?npmjs\.com/, '')
228228- return `href="${newHref}"`
229229- }
230230- return match
231231- })
232232-}
233233-234224/**
235225 * Resolve a relative URL to an absolute URL.
236226 * If repository info is available, resolve to provider's raw file URLs.
···387377 toc.push({ text: plainText, id, depth })
388378 }
389379390390- return `<h${semanticLevel} id="${id}" data-level="${depth}">${text}</h${semanticLevel}>\n`
380380+ /** The link href uses the unique slug WITHOUT the 'user-content-' prefix, because that will later be added for all links. */
381381+ return `<h${semanticLevel} id="${id}" data-level="${depth}"><a href="#${uniqueSlug}">${plainText}</a></h${semanticLevel}>\n`
391382 }
392383393384 // Syntax highlighting for code blocks (uses shared highlighter)
···443434 return `<blockquote>${body}</blockquote>\n`
444435 }
445436446446- marked.setOptions({
447447- renderer,
448448- walkTokens: token => {
449449- if (token.type === 'html') {
450450- token.text = replaceHtmlLink(token.text)
451451- }
452452- },
453453- })
437437+ marked.setOptions({ renderer })
454438455439 const rawHtml = marked.parse(content) as string
456440
+2-1
test/unit/server/utils/readme.spec.ts
···388388 const markdown = `# Title\n\nSome **bold** text and a [link](https://example.com).`
389389 const result = await renderReadmeHtml(markdown, 'test-pkg')
390390391391- expect(result.html).toBe(`<h3 id="user-content-title" data-level="1">Title</h3>
391391+ expect(result.html)
392392+ .toBe(`<h3 id="user-content-title" data-level="1"><a href="#user-content-title">Title</a></h3>
392393<p>Some <strong>bold</strong> text and a <a href="https://example.com" rel="nofollow noreferrer noopener" target="_blank">link</a>.</p>
393394`)
394395 })