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
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:
46
lib/blog.ts
46
lib/blog.ts
@@ -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 };
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user