-
-
Notifications
You must be signed in to change notification settings - Fork 270
fix: readme rendering result lacks anchor point jump #994
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -139,23 +139,24 @@ const ALLOWED_TAGS = [ | |
| ] | ||
|
|
||
| const ALLOWED_ATTR: Record<string, string[]> = { | ||
| a: ['href', 'title', 'target', 'rel'], | ||
| img: ['src', 'alt', 'title', 'width', 'height', 'align'], | ||
| source: ['src', 'srcset', 'type', 'media'], | ||
| button: ['class', 'title', 'type', 'aria-label', 'data-copy'], | ||
| th: ['colspan', 'rowspan', 'align'], | ||
| td: ['colspan', 'rowspan', 'align'], | ||
| h3: ['id', 'data-level', 'align'], | ||
| h4: ['id', 'data-level', 'align'], | ||
| h5: ['id', 'data-level', 'align'], | ||
| h6: ['id', 'data-level', 'align'], | ||
| blockquote: ['data-callout'], | ||
| details: ['open'], | ||
| code: ['class'], | ||
| pre: ['class', 'style'], | ||
| span: ['class', 'style'], | ||
| div: ['class', 'style', 'align'], | ||
| p: ['align'], | ||
| '*': ['id'], // Allow id on all tags | ||
| 'a': ['href', 'title', 'target', 'rel'], | ||
| 'img': ['src', 'alt', 'title', 'width', 'height', 'align'], | ||
| 'source': ['src', 'srcset', 'type', 'media'], | ||
| 'button': ['class', 'title', 'type', 'aria-label', 'data-copy'], | ||
| 'th': ['colspan', 'rowspan', 'align'], | ||
| 'td': ['colspan', 'rowspan', 'align'], | ||
| 'h3': ['data-level', 'align'], | ||
| 'h4': ['data-level', 'align'], | ||
| 'h5': ['data-level', 'align'], | ||
| 'h6': ['data-level', 'align'], | ||
| 'blockquote': ['data-callout'], | ||
| 'details': ['open'], | ||
| 'code': ['class'], | ||
| 'pre': ['class', 'style'], | ||
| 'span': ['class', 'style'], | ||
| 'div': ['class', 'style', 'align'], | ||
| 'p': ['align'], | ||
| } | ||
|
|
||
| // GitHub-style callout types | ||
|
|
@@ -397,6 +398,14 @@ ${html} | |
|
|
||
| const rawHtml = marked.parse(content) as string | ||
|
|
||
| // Helper to prefix id attributes with 'user-content-' | ||
| const prefixId = (tagName: string, attribs: sanitizeHtml.Attributes) => { | ||
| if (attribs.id && !attribs.id.startsWith('user-content-')) { | ||
| attribs.id = `user-content-${attribs.id}` | ||
| } | ||
| return { tagName, attribs } | ||
| } | ||
|
|
||
| const sanitized = sanitizeHtml(rawHtml, { | ||
| allowedTags: ALLOWED_TAGS, | ||
| allowedAttributes: ALLOWED_ATTR, | ||
|
|
@@ -436,6 +445,11 @@ ${html} | |
| } | ||
| return { tagName, attribs } | ||
| }, | ||
| div: prefixId, | ||
| p: prefixId, | ||
| span: prefixId, | ||
| section: prefixId, | ||
| article: prefixId, | ||
|
Comment on lines
+448
to
+452
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: # First, let's locate the file and check its existence
find . -name "readme.ts" -path "*/server/utils/*" 2>/dev/null | head -20Repository: npmx-dev/npmx.dev Length of output: 85 🏁 Script executed: # Check the file size and read the specific lines mentioned
if [ -f "server/utils/readme.ts" ]; then
wc -l server/utils/readme.ts
echo "=== Lines 440-460 ==="
sed -n '440,460p' server/utils/readme.ts
fiRepository: npmx-dev/npmx.dev Length of output: 648 🏁 Script executed: # Search for the transformTags configuration to understand the full context
rg "transformTags" server/utils/readme.ts -A 30 -B 5Repository: npmx-dev/npmx.dev Length of output: 1470 🏁 Script executed: # Look for the prefixId function definition
rg "prefixId\s*[:=]|function prefixId" server/utils/readme.ts -B 2 -A 10Repository: npmx-dev/npmx.dev Length of output: 511 🏁 Script executed: # Check package.json for sanitize-html version
cat package.json | grep -A 2 -B 2 "sanitize-html"Repository: npmx-dev/npmx.dev Length of output: 377 🌐 Web query:
💡 Result: In sanitize-html v2.17.0, import sanitizeHtml from "sanitize-html";
const clean = sanitizeHtml(dirtyHtml, {
transformTags: {
"*": (tagName, attribs) => ({
tagName,
attribs: { ...attribs, "data-sanitized": "1" }
})
}
});You can also use the wildcard with 🏁 Script executed: # Also search for any usage of wildcard transforms in the codebase
rg "transformTags" . -A 50 | grep -E "(\*|wildcard)" || echo "No wildcard patterns found in transformTags"Repository: npmx-dev/npmx.dev Length of output: 104 🏁 Script executed: # Let's check if there are any fragment link rewrites happening elsewhere
rg "user-content" server/utils/readme.ts -B 3 -A 3Repository: npmx-dev/npmx.dev Length of output: 1054 🏁 Script executed: # Check the ALLOWED_TAGS to see what tags are allowed through
rg "ALLOWED_TAGS\s*=" server/utils/readme.ts -A 30Repository: npmx-dev/npmx.dev Length of output: 366 Prefix IDs for anchor elements to match rewritten fragment links. Fragment links are rewritten to ♻️ Proposed fix (apply to `a`) a: (tagName, attribs) => {
// Add security attributes for external links
if (attribs.href && hasProtocol(attribs.href, { acceptRelative: true })) {
attribs.rel = 'nofollow noreferrer noopener'
attribs.target = '_blank'
}
- return { tagName, attribs }
+ return prefixId(tagName, attribs)
},Alternatively, |
||
| }, | ||
| }) | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Make fragment normalisation idempotent to avoid double‑prefixing.
Links already using
#user-content-…(common when copied from GitHub) will become#user-content-user-content-…becauseresolveUrlalways prefixes hash fragments. Guard against that so those anchors still resolve.🔧 Proposed fix
function resolveUrl(url: string, packageName: string, repoInfo?: RepositoryInfo): string { if (!url) return url if (url.startsWith('#')) { // Prefix anchor links to match heading IDs (avoids collision with page IDs) - return `#user-content-${url.slice(1)}` + if (url.startsWith('#user-content-')) return url + return `#user-content-${url.slice(1)}` }