fix(blog): table of contents scroll links
Some checks failed
Build & Deploy / 🔍 Prepare (push) Successful in 14s
Build & Deploy / 🧪 QA (push) Has started running
Build & Deploy / 🏗️ Build (push) Has been cancelled
Build & Deploy / 🚀 Deploy (push) Has been cancelled
Build & Deploy / 🧪 Smoke Test (push) Has been cancelled
Build & Deploy / ⚡ Lighthouse (push) Has been cancelled
Build & Deploy / 🔔 Notify (push) Has been cancelled

Fixes an issue where TOC links wouldn't scroll due to

ID generation mismatches on MDX headers containing

formatted text or German umlauts.
This commit is contained in:
2026-02-21 21:42:13 +01:00
parent a5384134e7
commit 36455ef479
2 changed files with 76 additions and 27 deletions

View File

@@ -109,7 +109,12 @@ export async function getAllPostsMetadata(locale: string): Promise<Partial<PostM
export async function getAdjacentPosts(
slug: string,
locale: string,
): Promise<{ prev: PostMdx | null; next: PostMdx | null; isPrevRandom?: boolean; isNextRandom?: boolean }> {
): Promise<{
prev: PostMdx | null;
next: PostMdx | null;
isPrevRandom?: boolean;
isNextRandom?: boolean;
}> {
const posts = await getAllPosts(locale);
const currentIndex = posts.findIndex((post) => post.slug === slug);
@@ -127,7 +132,7 @@ export async function getAdjacentPosts(
let isPrevRandom = false;
const getRandomPost = (excludeSlugs: string[]) => {
const available = posts.filter(p => !excludeSlugs.includes(p.slug));
const available = posts.filter((p) => !excludeSlugs.includes(p.slug));
if (available.length === 0) return null;
return available[Math.floor(Math.random() * available.length)];
};
@@ -154,17 +159,42 @@ export function getReadingTime(content: string): number {
return Math.ceil(minutes);
}
export function generateHeadingId(text: string): string {
let id = text.toLowerCase();
id = id.replace(/ä/g, 'ae');
id = id.replace(/ö/g, 'oe');
id = id.replace(/ü/g, 'ue');
id = id.replace(/ß/g, 'ss');
id = id.replace(/[*_`]/g, '');
id = id.replace(/[^\w\s-]/g, '');
id = id
.replace(/\s+/g, '-')
.replace(/-+/g, '-')
.replace(/^-+|-+$/g, '');
return id || 'heading';
}
export function getTextContent(node: any): string {
if (typeof node === 'string') return node;
if (typeof node === 'number') return node.toString();
if (Array.isArray(node)) return node.map(getTextContent).join('');
if (node && typeof node === 'object' && node.props && node.props.children) {
return getTextContent(node.props.children);
}
return '';
}
export function getHeadings(content: string): { id: string; text: string; level: number }[] {
const headingLines = content.split('\n').filter((line) => line.match(/^#{2,3}\s/));
return headingLines.map((line) => {
const level = line.match(/^#+/)?.[0].length || 0;
const text = line.replace(/^#+\s/, '').trim();
const id = text
.toLowerCase()
.replace(/[^\w\s-]/g, '')
.replace(/\s+/g, '-');
const rawText = line.replace(/^#+\s/, '').trim();
const cleanText = rawText.replace(/[*_`]/g, '');
const id = generateHeadingId(cleanText);
return { id, text, level };
return { id, text: cleanText, level };
});
}