Projekt-Tagebuch Blog, Tag 36


coden
Rustikaler, tropischer Arbeitsplatz mit einem Laptop auf einem Holztisch, daneben eine Hängematte.
Daniel Kaser|24. Juni 2024
3 min.
Montag, 24. Juni 2024

Wochen-Ziele:

  • Blog-Projekt-Tagebuch (Mai) in den Blog uploaden

Tages-Ziele:


Nach meiner Plasmaspende und einem 6er Kata hab ich mich daran gemacht, meine erste Code-Challenge-Lösung hochzuladen. Wie immer viel mir dabei eine Möglichkeit auf etwas zu optimieren und so habe ich dafür gesorgt, dass Anchor-Links von nun an automatisch generiert werden können.

Zur Erinnerung, einen Anchor Link in MDX erstellt man so:

[Hurra!](#hurra)

Man gibt also die id des Zieles, z.B. eine Überschrift, in den runden Klammern an und bekommt:

<a href="”#hurra”">Hurra!</a>

Wie du hier nachlesen kannst, hab ich ja bereits dafür gesorgt, das für Headings automatisch eine id aus dem Heading-Text generiert wird.

Da in vielen Fällen (z.B. bei Überschriften/Inhaltsverzeichnissen) der Link-Text nicht vom Ziel abweicht, wird der Anchor-Link nun automatisch generiert, wenn in den runden Klammern nur ein # steht.

[Hurra!](#)

Trotzdem kommt ein vollständiger Anchor-Link dabei raus:

<a href="”#hurra”">Hurra!</a>

Cool oder?!

Wie hab ich das gemacht?

In der CustomStyledMDX-Komponente kann man ja beeinflussen, was beim Rendern des MDX zu HTML am Ende raus kommt. Genau hier hab ich meine Logik platziert.

Bereits vorher hatte ich eine Bedingung für a-Tags, nach der, je nach href-Art eine andere Komponente rendert:

// src/components/CustomStyledMDX.tsx; vorher
  // ...
  a: (props) => {
    const { href, title, children, ...restProps } = props;
         if (href?.startsWith("#")) return <a {...props}>{children}</a>;
    if (href?.startsWith("/")) return <Link {...props}>{children}</Link>;
    if (href?.startsWith("$"))
      return (
        <AffiliateLink partner={href.slice(1)} tooltip={title} {...restProps}>
          {children}
        </AffiliateLink>
      );
    return <ExternalLink {...props}>{children}</ExternalLink>;
  },
  // ...

Jetzt haben die Links, die mit # beginnen eine zusätzliche Bedingung bekommen:

// src/components/CustomStyledMDX.tsx; nachher
  // ...
  a: (props) => {
    const { href, title, children, ...restProps } = props;
    // internal link; same page (i.e. anchor link)
    if (href?.startsWith("#")) {
      if (href === "#") {
        const anchor = getAnchorFromLinkText(children);
        return (
          <a href={anchor} title={title} {...restProps}>
            {children}
          </a>
        );
      }
      return <a {...props}>{children}</a>;
    }
    // internal link; other page
    if (href?.startsWith("/")) return <Link {...props}>{children}</Link>;
    if (href?.startsWith("$"))
      return (
        <AffiliateLink partner={href.slice(1)} tooltip={title} {...restProps}>
          {children}
        </AffiliateLink>
      );
    return <ExternalLink {...props}>{children}</ExternalLink>;
  },
  // ...

Wenn das href also nur aus einem einzelnen # besteht, wird der Anchor-href aus dem Link-Text generiert. Dafür hab ich folgende getAnchorFromLinkText()-Funktion erstellt:

// lib/utils/getAnchorFromLinkText.ts
import React from "react";
import { slugify } from "@lib/utils";

export function getAnchorFromLinkText(
  children: React.ReactNode,
  maxDepth: number = 3,
): string {
  let currNode = children;
  let currDepth = 0;

  while (currDepth < maxDepth) {
    const childrenArr = React.Children.toArray(currNode);
    if (!childrenArr.length) break;

    currNode = childrenArr[0];

    if (typeof currNode === "string") return "#" + slugify(currNode);
    else if (!React.isValidElement(currNode)) break;

    currNode = (currNode as React.ReactElement).props.children;
    currDepth++;
  }

  throw new Error("No string content found within the specified depth.");
}

Der Trick ist hier, sich erstmal durch die ganzen Children-Nodes durchzuwühlen, bis man einen String findet. Hier suche ich bis zu einer maximalen Tiefe von 3 Ebenen.

ÜBRIGENS
Die JSDoc-Kommentare für Funktionen kann man sich wunderbar von ChatGPT erstellen lassen! Nicht vergessen nochmal rüberzukucken 🧐

Feedback

Schreib mir!