diff --git a/blog/raect-markdown/react-markdown.md b/blog/raect-markdown/react-markdown.md
new file mode 100644
index 000000000..026118ca8
--- /dev/null
+++ b/blog/raect-markdown/react-markdown.md
@@ -0,0 +1,235 @@
+---
+slug: react-markdown
+title: Using Remark to Create an Interactive Table of Contents in a Next.js Blog
+description: A table of contents has numerous benefits, and is a valuable addition for websites, especially blogs. An organized and easily navigable table of contents significantly improves the user experience, simplifying the process for readers to find information they require. By adding a table of contents, not only do you provide readers with streamlined navigation, but you also increase the overall accessibility and usability of content.
+authors: [ owen ]
+image: https://cdn.illacloud.com/illa-website/blog/react-markdown/cover.webp
+tags: [ postgresql, select ]
+date: 2024-02-26T10:00
+---
+
+A table of contents has numerous benefits, and is a valuable addition for websites, especially blogs. An organized and easily navigable table of contents significantly improves the user experience, simplifying the process for readers to find information they require. By adding a table of contents, not only do you provide readers with streamlined navigation, but you also increase the overall accessibility and usability of content.
+
+In this article, we'll cover the necessary steps for creating an interactive table of contents for a `Next.js` blog using Remark (a powerful Markdown processor). While some Remark plugins (like `Remark-toc`) offer this functionality, the generated table of contents resides within the content itself, limiting its potential use cases. For example, in this blog the table of contents is rendered outside the blog content, keeping it visible whilst navigating. This is the type of table of contents we will build in this tutorial. We'll start by briefly discussing the basics of Remark, its plugins, and integration with Next.js. We'll then dive into the actual steps to implement the custom table of contents, and finally, we'll make it interactive so clicking the table of contents items will scroll the page to the corresponding section.
+
+## Remark and its Plugins
+
+Remark is an extensible Markdown processor that simplifies the process of converting Markdown files into HTML or other formats. A key aspect of Remark is its plugin-based architecture which enables developers to extend and customize its functionality. These plugins can handle tasks like syntax highlighting, adding a table of contents, or parsing custom Markdown syntax. Integrating Remark with Next.js is very straightforward – usually used along with the `getStaticProps` function to process Markdown files during build. It can also handle MDX files making it a viable choice for Next.js sites using the new `app` directory. Remark’s powerful processing capabilities and seamless integration with Next.js make it an ideal choice for enhancing Next.js blogs and websites content as well as user experience.
+
+## Getting Started
+
+Although we're building a custom table of contents, we don't need to write everything from scratch. To separate frontmatter content from Markdown/MDX content itself, we'll utilize the `Gray-matter` package. This is optional if there is no frontmatter in the Markdown files. To process the Markdown itself, we'll use the Remark package. We'll also need the `unist-util-visit` package to traverse the node tree and the `mdast-util-to-string` package to get node text content.
+
+Let's install all these packages:
+
+```shell
+npm i remark mdast-util-to-string gray-matter unist-util-visit
+```
+
+## Custom Remark Plugin to Extract Headings from Content
+
+Before rendering the table of contents, we need to extract all headings from the Markdown file and organize them into an array of nodes. This process can be broken down into a few steps:
+
+1. Parse file contents to separate frontmatter from content
+2. Generate IDs for each heading element. This is necessary later for implementing scroll to section functionality.
+3. Parse content and extract headings with their properties
+
+For step 2, we could manually add IDs as custom markdown attributes e.g. `## Heading 1 {#heading-id}` and then use a library like `Remark-heading-id` to render them into the HTML. However, this approach requires manually adding and maintaining these headings across titles, less efficient. A more efficient way is to automatically generate IDs based on heading text e.g. the heading `Heading 1` will automatically get ID `heading-1` when converted to HTML.
+
+Additionally, we can combine steps 2 and 3 by creating a custom Remark plugin.
+
+```javascript
+export function headingTree() {
+ return (node, file) => {
+ file.data.headings = getHeadings(node);
+ };
+}
+
+function getHeadings(root) {
+ // implementation details
+}
+```
+
+Here we have our custom Remark plugin `headingTree` which extracts headings from the document and adds them as a `headings` property to the processed content.
+
+The main component is the `getHeadings` function which is an accessor function that traverses the node tree and manipulates nodes. For improved readability, the function is split into two parts.
+
+The `addID` function traverses heading nodes in the document, replaces all special characters in them, and outputs them as lowercase strings with spaces replaced by hyphens. These IDs will be stored in the `hProperties` attribute of the headings.
+
+```javascript
+function addID(node, nodes) {
+ // implementation details
+}
+```
+
+Note that we use a `nodes` variable to keep track of occurrences of each heading. This is to prefix them with a number in case there are duplicate headings in the document (e.g. some sections may have subheadings with the same text). The `transformNode` function takes nodes obtained from the parsed Markdown abstract syntax tree (AST) and transforms them into a format more suitable for building the table of contents.
+
+```javascript
+import { toString } from "mdast-util-to-string";
+
+function transformNode(node, output, indexMap) {
+ // implementation details
+}
+```
+
+This function checks if a node has depth 2 (## elements in Markdown). If yes, the transformed node is added to the output array and stored at the corresponding depth position in `indexMap`. This indicates the transformed node resides at the top level of the table of contents. Here, we designate depth 2 as the top level depth since this will produce `
` tags in the HTML output. We don't use depth 1 since having multiple `` elements on a page isn't good for accessibility and SEO.
+
+If a node has depth greater than 2 (e.g. ### or #### elements), the function identifies the parent node by looking up the position of the node's previous level depth (i.e. `node.depth - 1`) in `indexMap`. If a parent is found, the transformed node is appended to the parent's `children` array and `indexMap` is updated accordingly. This helps build the nested structure of the table of contents where deeper level nodes become children of higher level nodes.
+
+It's worth noting that for this function to work properly, the table of contents should have a valid structure e.g. there shouldn't be jumps from node depth 2 directly to depth 4.
+
+Now we have everything needed to implement the `getHeadings` function.
+
+```javascript
+import matter from "gray-matter";
+import { remark } from "remark";
+
+import { headingTree } from "./headings";
+
+const postsDirectory = path.join(process.cwd(), "posts");
+
+export async function getHeadings(id) {
+ // Gets Markdown file
+ // Parses frontmatter with gray-matter
+
+ // Use remark to process Markdown
+ const processedContent = await remark()
+ .use(headingTree)
+ .process(matterResult.content);
+
+ return processedContent.data.headings;
+}
+```
+
+With this, we have the array of headings from the document along with their data properties. The structure of the array is:
+
+```javascript
+[
+ {
+ value: "Heading 1",
+ depth: 2,
+ data: { hProperties: { id: "heading-1"} },
+ children: [
+ // nested headings
+ ]
+ }
+]
+```
+
+## Rendering the Table of Contents
+
+Now that we have the heading data, we can use it to render the table of contents. First, we'll create a `TableOfContents` component which will be the wrapper for the rendering logic of the table of contents.
+
+```jsx
+"use client";
+
+export const TableOfContents = ({ nodes }) => {
+ if (!nodes?.length) {
+ return null;
+ }
+
+ return (
+
+
Table of contents
+ {renderNodes(nodes)}
+
+ );
+};
+```
+
+Note you need the `"use client"` directive to mark this component as a client component if using Next.js `app` directory.
+
+The actual rendering will be handled by the `renderNodes` function since the rendering logic is recursive, we define it in a separate function instead of inside the component.
+
+```jsx
+function renderNodes(nodes) {
+ return (
+
+ {nodes.map((node) => (
+ -
+ {node.value}
+ {node.children?.length > 0 &&
+ renderNodes(node.children)}
+
+ ))}
+
+ );
+}
+```
+
+Each element in the table of contents is a link which points to the corresponding heading's ID via its `href` attribute.
+
+## Adding Smooth Scroll Effect on Table of Contents Link Click
+
+The basic table of contents is now complete. On the page where we render the article, we can get the headings by calling `await getHeadings(postId)` (or executing this in `getStaticProps` when using the "pages" directory) and pass the data to the TableOfContents component. When we click on table of contents links on the article page, navigation should happen to the corresponding part of the page. Instead of abruptly jumping however, we can enable smooth scrolling. As an additional enhancement, we can gradually decrease font size of child links based on their depth.
+
+To achieve this, we'll introduce a `TOCLink` component that is responsible for smooth scrolling and styling individual links, then we'll use it in `renderNodes`.
+
+```jsx
+function renderNodes(nodes) {
+ return (
+
+ {nodes.map((node) => (
+ -
+
+ {node.children?.length > 0 &&
+ renderNodes(node.children)}
+
+ ))}
+
+ );
+}
+
+const TOCLink = ({ node }) => {
+
+ // Smooth scroll implementation
+
+ // Depth based font size classes
+};
+```
+
+For smooth scrolling to a specific element on the page, we first locate the element using its ID, then use the `scrollIntoView` method with `behavior: "smooth"` option. See MDN for more on this method. It has wide browser support but `smooth` option may not be compatible with some older browsers. With this approach, clicking on table of contents links now produces a nice scrolling animation rather than the abrupt transition previously.
+
+If you need to add an offset when scrolling to heading elements (e.g. when page has a fixed navbar), you can apply the `scroll-margin-top` CSS property to heading elements.
+
+Additionally, we can leverage `TailwindCSS` and its `text` utility classes to gradually decrease font size of table of contents links based on depth.
+
+## Highlighting Active Links
+
+For enhanced navigation with the table of contents, a final touch is to highlight table of contents links as their corresponding title comes into view on the page.
+
+To detect visibility of elements on page, we'll utilize the `Intersection Observer API` which has good browser support but some caveats. Additionally, we'll move this functionality into a custom hook that returns a boolean indicating if link is highlighted and provides callback to manually set highlight state. This hook will be consumed in the `TOCLink` component.
+
+```jsx
+import { useEffect, useRef, useState } from "react";
+
+function useHighlighted(id) {
+
+ // Implementation details
+
+ return [highlighted, setHighlighted];
+}
+
+const TOCLink = ({ node }) => {
+
+ const [highlighted, setHighlighted] = useHighlighted(id);
+
+ // Other implementation details
+};
+```
+
+In this hook, the `handleObserver` function serves as callback for `Intersection Observer`, handling visibility changes of observed elements, taking an entries array as its parameter.
+
+The `handleObserver` loops through entries, including h2, h3, h4 elements, checks if `isIntersecting` is `true` – indicating element is visible in viewport – and if so, updates active section in table of contents via `setActiveId`. When link is clicked we set it to be highlighted via the `setHighlighted` callback.
+
+Additionally, we store a new `Intersection Observer` instance in a `ref` to persist its identity across component renders.
+
+By scrolling the page, you can see the effect of this in action on this page, observing how the active section in table of contents updates as page reaches corresponding sections.
+
+## Conclusion
+
+Overall, using Remark and custom plugins to create a table of contents for a Next.js blog can bring many benefits for your site's user experience and accessibility. Through Remark, this powerful Markdown processor, and its rich range of plugins, it's easy to extract headings from Markdown files and transform them into an interactive, easy to navigate table of contents.
+
+By introducing a table of contents, you can enhance user experience on your Next.js blogs, making it easier for readers to find information they need. Additionally, using Remark to create custom table of contents plugins enables you to integrate the table outside of content itself, further increasing content availability and accessibility. By leveraging plugins like `mdast-util-to-string` and `unist-util-visit`, headings can be extracted from content, unique IDs can be generated, and they can be parsed into a format suitable for building the table of contents.
+
+This tutorial has walked through that process by creating a custom table of contents with nested structure, smooth scrolling, and active link highlighting. Readers can now quickly find and navigate to content they are interested in, enhancing overall usability and value of the blog.
\ No newline at end of file
diff --git a/i18n/de/docusaurus-plugin-content-blog/raect-markdown/react-markdown.md b/i18n/de/docusaurus-plugin-content-blog/raect-markdown/react-markdown.md
new file mode 100644
index 000000000..5b11575fb
--- /dev/null
+++ b/i18n/de/docusaurus-plugin-content-blog/raect-markdown/react-markdown.md
@@ -0,0 +1,235 @@
+---
+slug: react-markdown
+title: Verwendung von Remark zur Erstellung eines interaktiven Inhaltsverzeichnisses in einem Next.js-Blog
+description: Ein Inhaltsverzeichnis hat zahlreiche Vorteile und ist eine wertvolle Ergänzung für Websites, insbesondere Blogs. Ein organisiertes und leicht navigierbares Inhaltsverzeichnis verbessert die Benutzererfahrung erheblich und vereinfacht den Prozess für Leser, die benötigten Informationen zu finden. Durch Hinzufügen eines Inhaltsverzeichnisses bieten Sie den Lesern nicht nur eine übersichtliche Navigation, sondern erhöhen auch die Gesamtzugänglichkeit und Benutzerfreundlichkeit des Inhalts.
+authors: [ owen ]
+image: https://cdn.illacloud.com/illa-website/blog/react-markdown/cover.webp
+tags: [ postgresql, select ]
+date: 2024-02-26T10:00
+---
+
+Ein Inhaltsverzeichnis hat zahlreiche Vorteile und ist eine wertvolle Ergänzung für Websites, insbesondere Blogs. Ein organisiertes und leicht navigierbares Inhaltsverzeichnis verbessert die Benutzererfahrung erheblich und vereinfacht den Prozess für Leser, die benötigten Informationen zu finden. Durch Hinzufügen eines Inhaltsverzeichnisses bieten Sie den Lesern nicht nur eine übersichtliche Navigation, sondern erhöhen auch die Gesamtzugänglichkeit und Benutzerfreundlichkeit des Inhalts.
+
+In diesem Artikel werden wir die notwendigen Schritte zur Erstellung eines interaktiven Inhaltsverzeichnisses für einen `Next.js`-Blog unter Verwendung von Remark (einem leistungsstarken Markdown-Prozessor) behandeln. Obwohl einige Remark-Plugins (wie `Remark-toc`) diese Funktionalität bieten, befindet sich das generierte Inhaltsverzeichnis innerhalb des Inhalts selbst, was seine potenziellen Anwendungsfälle einschränkt. Zum Beispiel wird im vorliegenden Blog das Inhaltsverzeichnis außerhalb des Bloginhalts gerendert und bleibt beim Navigieren sichtbar. Dies ist die Art von Inhaltsverzeichnis, das wir in diesem Tutorial erstellen werden. Wir werden zunächst kurz die Grundlagen von Remark, seine Plugins und die Integration mit Next.js diskutieren. Anschließend werden wir uns mit den tatsächlichen Schritten zur Implementierung des benutzerdefinierten Inhaltsverzeichnisses befassen und es schließlich interaktiv gestalten, sodass das Klicken auf die Einträge im Inhaltsverzeichnis die Seite zum entsprechenden Abschnitt scrollt.
+
+## Remark und seine Plugins
+
+Remark ist ein erweiterbarer Markdown-Prozessor, der den Prozess der Umwandlung von Markdown-Dateien in HTML oder andere Formate vereinfacht. Ein wichtiger Aspekt von Remark ist seine Plugin-basierte Architektur, die es Entwicklern ermöglicht, seine Funktionalität zu erweitern und anzupassen. Diese Plugins können Aufgaben wie Syntaxhervorhebung, Hinzufügen eines Inhaltsverzeichnisses oder Parsen benutzerdefinierter Markdown-Syntax übernehmen. Die Integration von Remark in Next.js ist sehr einfach - in der Regel wird es zusammen mit der `getStaticProps`-Funktion verwendet, um Markdown-Dateien während des Builds zu verarbeiten. Es kann auch MDX-Dateien verarbeiten, was es zu einer geeigneten Wahl für Next.js-Websites mit dem neuen `app`-Verzeichnis macht. Die leistungsstarken Verarbeitungsfähigkeiten von Remark und die nahtlose Integration mit Next.js machen es zu einer idealen Wahl zur Verbesserung von Inhalten sowie Benutzererfahrungen von Next.js-Blogs und Websites.
+
+## Erste Schritte
+
+Obwohl wir ein benutzerdefiniertes Inhaltsverzeichnis erstellen, müssen wir nicht alles von Grund auf schreiben. Um den Frontmatter-Inhalt von Markdown/MDX-Inhalt selbst zu trennen, werden wir das Paket `Gray-matter` verwenden. Dies ist optional, wenn in den Markdown-Dateien kein Frontmatter vorhanden ist. Zur Verarbeitung des Markdown selbst verwenden wir das Paket Remark. Außerdem benötigen wir das Paket `unist-util-visit`, um den Knotenbaum zu durchlaufen, und das Paket `mdast-util-to-string`, um den Textinhalt des Knotens zu erhalten.
+
+Lassen Sie uns alle diese Pakete installieren:
+
+```shell
+npm i remark mdast-util-to-string gray-matter unist-util-visit
+```
+
+## Benutzerdefiniertes Remark-Plugin zum Extrahieren von Überschriften aus dem Inhalt
+
+Bevor das Inhaltsverzeichnis gerendert wird, müssen wir alle Überschriften aus der Markdown-Datei extrahieren und sie in ein Array von Knoten organisieren. Dieser Prozess kann in einige Schritte unterteilt werden:
+
+1. Parsen des Dateiinhalts, um Frontmatter vom Inhalt zu trennen
+2. Generieren von IDs für jedes Überschriften-Element. Dies ist später für die Implementierung der Scrollfunktionalität zu Abschnitten erforderlich.
+3. Parsen des Inhalts und Extrahieren von Überschriften mit ihren Eigenschaften
+
+Für Schritt 2 könnten wir manuell IDs als benutzerdefinierte Markdown-Attribute hinzufügen, z.B. `## Überschrift 1 {#heading-id}`, und dann eine Bibliothek wie `Remark-heading-id` verwenden, um sie in HTML zu rendern. Dieser Ansatz erfordert jedoch das manuelle Hinzufügen und Pflegen dieser Überschriften über Titeln hinweg und ist weniger effizient. Ein effizienterer Weg ist das automatische Generieren von IDs basierend auf dem Überschriftentext, z.B. die Überschrift `Überschrift 1` erhält automatisch die ID `heading-1`, wenn sie in HTML konvertiert wird.
+
+Zusätzlich können wir Schritte 2 und 3 kombinieren, indem wir ein benutzerdefiniertes Remark-Plugin erstellen.
+
+```javascript
+export function headingTree() {
+ return (node, file) => {
+ file.data.headings = getHeadings(node);
+ };
+}
+
+function getHeadings(root) {
+ // Implementierungsdetails
+}
+```
+
+Hier haben wir unser benutzerdefiniertes Remark-Plugin `headingTree`, das Überschriften aus dem Dokument extrahiert und sie als `headings`-Eigenschaft zum verarbeiteten Inhalt hinzufügt.
+
+Die Hauptkomponente ist die `getHeadings`-Funktion, die eine Zugriffsfunktion ist, die den Knotenbaum durchläuft und Knoten manipuliert. Zur verbesserten Lesbarkeit ist die Funktion in zwei Teile aufgeteilt.
+
+Die `addID`-Funktion durchläuft Überschriftenknoten im Dokument, ersetzt alle Sonderzeichen darin und gibt sie als Kleinbuchstabenzeichenfolgen aus, bei denen Leerzeichen durch Bindestriche ersetzt werden. Diese IDs werden im `hProperties`-Attribut der Überschriften gespeichert.
+
+```javascript
+function addID(node, nodes) {
+ // Implementierungsdetails
+}
+```
+
+Beachten Sie, dass wir eine Variable `nodes` verwenden, um Vorkommen jeder Überschrift zu verfolgen. Dies geschieht, um sie mit einer Nummer zu versehen, falls es doppelte Überschriften im Dokument gibt (z.B. einige Abschnitte können Untertitel mit dem gleichen Text haben). Die `transformNode`-Funktion nimmt Knoten, die aus dem geparsten Markdown-Abstraktsyntaxbaum (AST) erhalten wurden, und wandelt sie in ein Format um, das besser für den Aufbau des Inhaltsverzeichnisses geeignet ist.
+
+```javascript
+import { toString } from "mdast-util-to-string";
+
+function transformNode(node, output, indexMap) {
+ // Implementierungsdetails
+}
+```
+
+Diese Funktion überprüft, ob ein Knoten die Tiefe 2 hat (##-Elemente in Markdown). Wenn ja, wird der transformierte Knoten dem Ausgabearray hinzugefügt und an der entsprechenden Tiefenposition in `indexMap` gespeichert. Dies zeigt an, dass der transformierte Knoten auf oberster Ebene des Inhaltsverzeichnisses liegt. Hier bezeichnen wir Tiefe 2 als die oberste Tiefe, da dies ``-Tags in der HTML-Ausgabe erzeugt. Wir verwenden nicht Tiefe 1, da mehrere ``-Elemente auf einer Seite nicht gut für die Zugänglichkeit und SEO sind.
+
+Wenn ein Knoten eine Tiefe größer als 2 hat (z.B. ### oder ####-Elemente), identifiziert die Funktion den Elternknoten, indem sie die Position der vorherigen Tiefenebene des Knotens (d.h. `node.depth - 1`) in `indexMap` nachschlägt. Wenn ein Elternteil gefunden wird, wird der transformierte Knoten an das `children`-Array des Elternteils angehängt und `indexMap` entsprechend aktualisiert. Dies hilft beim Aufbau der verschachtelten Struktur des Inhaltsverzeichnisses, bei der Knoten mit tieferer Ebene zu Kindern von Knoten mit höherer Ebene werden.
+
+Es ist erwähnenswert, dass für das ordnungsgemäße Funktionieren dieser Funktion das Inhaltsverzeichnis eine gültige Struktur haben sollte, z.B. sollten keine Sprünge von Knotentiefe 2 direkt auf Tiefe 4 vorhanden sein.
+
+Nun haben wir alles, was benötigt wird, um die `getHeadings`-Funktion zu implementieren.
+
+```javascript
+import matter from "gray-matter";
+import { remark } from "remark";
+
+import { headingTree } from "./headings";
+
+const postsDirectory = path.join(process.cwd(), "posts");
+
+export async function getHeadings(id) {
+ // Holt Markdown-Datei
+ // Parsen des Frontmatters mit gray-matter
+
+ // Verwenden von remark zur Verarbeitung von Markdown
+ const processedContent = await remark()
+ .use(headingTree)
+ .process(matterResult.content);
+
+ return processedContent.data.headings;
+}
+```
+
+Damit haben wir das Array der Überschriften aus dem Dokument zusammen mit ihren Datenattributen. Die Struktur des Arrays ist:
+
+```javascript
+[
+ {
+ value: "Überschrift 1",
+ depth: 2,
+ data: { hProperties: { id: "heading-1"} },
+ children: [
+ // verschachtelte Überschriften
+ ]
+ }
+]
+```
+
+## Rendering des Inhaltsverzeichnisses
+
+Nun, da wir die Überschriftendaten haben, können wir sie verwenden, um das Inhaltsverzeichnis zu rendern. Zuerst werden wir eine `TableOfContents`-Komponente erstellen, die den Wrapper für die Rendering-Logik des Inhaltsverzeichnisses darstellt.
+
+```jsx
+"use client";
+
+export const TableOfContents = ({ nodes }) => {
+ if (!nodes?.length) {
+ return null;
+ }
+
+ return (
+
+
Inhaltsverzeichnis
+ {renderNodes(nodes)}
+
+ );
+};
+```
+
+Beachten Sie, dass Sie die `"use client"`-Direktive benötigen, um diese Komponente als Client-Komponente zu kennzeichnen, wenn Sie das `app`-Verzeichnis von Next.js verwenden.
+
+Das eigentliche Rendering wird von der `renderNodes`-Funktion behandelt. Da die Rendering-Logik rekursiv ist, definieren wir sie in einer separaten Funktion anstatt innerhalb der Komponente.
+
+```jsx
+function renderNodes(nodes) {
+ return (
+
+ {nodes.map((node) => (
+ -
+ {node.value}
+ {node.children?.length > 0 &&
+ renderNodes(node.children)}
+
+ ))}
+
+ );
+}
+```
+
+Jedes Element im Inhaltsverzeichnis ist ein Link, der über sein `href`-Attribut auf die entsprechende Überschriften-ID verweist.
+
+## Hinzufügen des Smooth-Scroll-Effekts beim Klicken auf den Inhaltsverzeichnis-Link
+
+Das grundlegende Inhaltsverzeichnis ist nun fertig. Auf der Seite, auf der wir den Artikel rendern, können wir die Überschriften erhalten, indem wir `await getHeadings(postId)` aufrufen (oder dies in `getStaticProps` ausführen, wenn das "pages"-Verzeichnis verwendet wird) und die Daten an das TableOfContents-Komponente übergeben. Wenn wir auf Inhaltsverzeichnis-Links auf der Artikelseite klicken, sollte die Navigation zum entsprechenden Teil der Seite erfolgen. Anstatt jedoch abrupt zu springen, können wir sanftes Scrollen aktivieren. Als zusätzliche Verbesserung können wir die Schriftgröße der Unterlinks basierend auf ihrer Tiefe allmählich verringern.
+
+Um dies zu erreichen, werden wir eine `TOCLink`-Komponente einführen, die für sanftes Scrollen und das Stylen einzelner Links verantwortlich ist, und sie dann in `renderNodes` verwenden.
+
+```jsx
+function renderNodes(nodes) {
+ return (
+
+ {nodes.map((node) => (
+ -
+
+ {node.children?.length > 0 &&
+ renderNodes(node.children)}
+
+ ))}
+
+ );
+}
+
+const TOCLink = ({ node }) => {
+
+ // Implementierung des sanften Scrollens
+
+ // Klassen für die Schriftgröße basierend auf der Tiefe
+};
+```
+
+Um sanft zu einem bestimmten Element auf der Seite zu scrollen, lokalisieren wir zuerst das Element anhand seiner ID und verwenden dann die Methode `scrollIntoView` mit der Option `behavior: "smooth"`. Siehe MDN für weitere Informationen zu dieser Methode. Es hat eine breite Browserunterstützung, aber die `smooth`-Option ist möglicherweise nicht mit einigen älteren Browsern kompatibel. Mit diesem Ansatz erzeugt das Klicken auf Inhaltsverzeichnis-Links nun eine schöne Bildlaufanimation anstelle des früheren abrupten Übergangs.
+
+Wenn Sie einen Offset beim Scrollen zu Überschriftenelementen hinzufügen müssen (z. B. wenn die Seite eine feste Navbar hat), können Sie die CSS-Eigenschaft `scroll-margin-top` auf Überschriftenelemente anwenden.
+
+Zusätzlich können wir `TailwindCSS` und seine `text`-Hilfsklassen nutzen, um die Schriftgröße der Inhaltsverzeichnis-Links basierend auf der Tiefe allmählich zu verringern.
+
+## Hervorheben aktiver Links
+
+Für eine verbesserte Navigation mit dem Inhaltsverzeichnis ist eine abschließende Note, Inhaltsverzeichnis-Links hervorzuheben, wenn der entsprechende Titel auf der Seite angezeigt wird.
+
+Um die Sichtbarkeit von Elementen auf der Seite zu erkennen, nutzen wir die `Intersection Observer API`, die eine gute Browserunterstützung bietet, aber einige Einschränkungen hat. Zusätzlich werden wir diese Funktionalität in einen benutzerdefinierten Hook verschieben, der einen Booleschen Wert zurückgibt, der angibt, ob der Link hervorgehoben ist, und einen Rückruf bereitstellt, um den Hervorhebungszustand manuell festzulegen. Dieser Hook wird in der `TOCLink`-Komponente verwendet.
+
+```jsx
+import { useEffect, useRef, useState } from "react";
+
+function useHighlighted(id) {
+
+ // Implementierungsdetails
+
+ return [highlighted, setHighlighted];
+}
+
+const TOCLink = ({ node }) => {
+
+ const [highlighted, setHighlighted] = useHighlighted(id);
+
+ // Andere Implementierungsdetails
+};
+```
+
+In diesem Hook dient die Funktion `handleObserver` als Rückruf für den `Intersection Observer`, der Sichtbarkeitsänderungen der beobachteten Elemente behandelt und ein Array von Einträgen als Parameter verwendet.
+
+Die `handleObserver`-Funktion durchläuft Einträge, einschließlich h2-, h3- und h4-Elemente, überprüft, ob `isIntersecting` `true` ist - was anzeigt, dass das Element im Viewport sichtbar ist - und aktualisiert dann den aktiven Abschnitt im Inhaltsverzeichnis über `setActiveId`. Wenn ein Link geklickt wird, wird er über den `setHighlighted`-Rückruf hervorgehoben.
+
+Zusätzlich speichern wir eine neue Instanz des `Intersection Observer` in einem `ref`, um ihre Identität über die Rendern von Komponenten hinweg zu erhalten.
+
+Durch Scrollen der Seite können Sie den Effekt dieses Vorgangs auf dieser Seite sehen und beobachten, wie sich der aktive Abschnitt im Inhaltsverzeichnis aktualisiert, wenn die Seite die entsprechenden Abschnitte erreicht.
+
+## Fazit
+
+Insgesamt kann die Verwendung von Remark und benutzerdefinierten Plugins zur Erstellung eines Inhaltsverzeichnisses für einen Next.js-Blog viele Vorteile für die Benutzererfahrung und die Barrierefreiheit Ihrer Website bringen. Durch Remark, diesen leistungsfähigen Markdown-Prozessor, und seine reiche Auswahl an Plugins ist es einfach, Überschriften aus Markdown-Dateien zu extrahieren und sie in ein interaktives, leicht zu navigierendes Inhaltsverzeichnis umzuwandeln.
+
+Durch die Einführung eines Inhaltsverzeichnisses können Sie die Benutzererfahrung auf Ihren Next.js-Blogs verbessern und es den Lesern erleichtern, die benötigten Informationen zu finden. Durch die Verwendung von Remark zur Erstellung benutzerdefinierter Inhaltsverzeichnis-Plugins können Sie das Inhaltsverzeichnis außerhalb des Inhalts selbst integrieren und so die Verfügbarkeit und Zugänglichkeit des Inhalts weiter erhöhen. Durch die Nutzung von Plugins wie `mdast-util-to-string` und `unist-util-visit` können Überschriften aus Inhalten extrahiert, eindeutige IDs generiert und sie in ein für den Aufbau des Inhaltsverzeichnisses geeignetes Format analysiert werden.
+
+Dieses Tutorial hat diesen Prozess durch die Erstellung eines benutzerdefinierten Inhaltsverzeichnisses mit einer verschachtelten Struktur, sanftem Scrollen und Hervorheben aktiver Links durchlaufen. Die Leser können jetzt schnell zu den Inhalten navigieren, die sie interessieren, was die Gesamtbenutzerfreundlichkeit und den Wert des Blogs erhöht.
\ No newline at end of file
diff --git a/i18n/ja/docusaurus-plugin-content-blog/raect-markdown/react-markdown.md b/i18n/ja/docusaurus-plugin-content-blog/raect-markdown/react-markdown.md
new file mode 100644
index 000000000..aca89d9d4
--- /dev/null
+++ b/i18n/ja/docusaurus-plugin-content-blog/raect-markdown/react-markdown.md
@@ -0,0 +1,340 @@
+---
+slug: react-markdown
+title: Remarkを使用してNext.jsブログにインタラクティブな目次を作成する
+description: 目次には多くの利点があり、特にブログなどのWebサイトにとって価値のある追加機能です。整理されたナビゲーションしやすい目次は、必要な情報を見つけるプロセスを読者にとって容易にすることでユーザーエクスペリエンスを大幅に向上させます。目次を追加することで、読者にシンプルなナビゲーションを提供するだけでなく、コンテンツの全体的なアクセシビリティと使いやすさを向上させることができます。
+authors: [ owen ]
+image: https://cdn.illacloud.com/illa-website/blog/react-markdown/cover.webp
+tags: [ postgresql, select ]
+date: 2024-02-26T10:00
+---
+
+目次には多くの利点があり、特にブログなどのWebサイトにとって価値のある追加機能です。整理されたナビゲーションしやすい目次は、必要な情報を見つけるプロセスを読者にとって容易にすることでユーザーエクスペリエンスを大幅に向上させます。目次を追加することで、読者にシンプルなナビゲーションを提供するだけでなく、コンテンツの全体的なアクセシビリティと使いやすさを向上させることができます。
+
+
+この記事では、強力なMarkdownプロセッサであるRemarkを使用して、Next.jsブログにインタラクティブな目次を作成するために必要な手順を説明します。 一部のRemarkプラグイン(remark-tocなど)はこの機能を提供していますが、生成された目次はコンテンツ自体の内部にあり、その可能な用途が制限されます。 たとえば、このブログの目次はブログコンテンツの外側にレンダリングされているため、ナビゲーション中に表示されたままです。 これは、このチュートリアルで構築する目次のタイプです。 Remarkの基本、そのプラグイン、Next.jsとの統合についての簡単な説明から始めます。 次に、カスタム目次を実装する実際の手順を詳しく調べ、最後に目次のアイテムをクリックするとページが対応するセクションにスクロールするインタラクティブなものにします。
+
+
+## Remarkとそのプラグイン
+
+Remarkは、MarkdownファイルをHTMLやその他の形式に変換するプロセスを簡略化する拡張可能なMarkdownプロセッサです。 Remarkの重要な点は、開発者が機能を拡張およびカスタマイズできるようにするプラグインベースのアーキテクチャです。 これらのプラグインは、構文のハイライト表示、目次の追加、カスタムMarkdown構文の解析などのタスクを処理できます。 RemarkをNext.jsと統合することは非常に簡単です。通常、ビルドプロセス中にMarkdownファイルを処理するためにgetStaticProps関数とともに使用されます。 MDXファイルも処理できるため、新しい「アプリ」ディレクトリを使用するNext.js Webサイトの実行可能な選択肢です。 Remarkの強力な処理機能とNext.jsとのシームレスな統合により、Next.jsブログとWebサイトのコンテンツとユーザーエクスペリエンスを向上させるのに理想的な選択肢です。
+
+
+## はじめに
+
+カスタム目次を構築していますが、すべてをゼロから記述する必要はありません。 Markdown/MDXコンテンツをフロントマターから分離するために、Gray-matterパッケージを使用します。 Markdownファイルにフロントマターがない場合はオプションです。 Markdown自体を処理するために、Remarkパッケージを使用します。 ノードツリーを走査するunist-util-visitパッケージと、ノードのテキストコンテンツを取得するmdast-util-to-stringパッケージも必要です。
+
+これらのパッケージをすべてインストールしましょう。
+
+```shell
+npm i remark mdast-util-to-string gray-matter unist-util-visit
+```
+
+
+## コンテンツから見出しを抽出するためのカスタムRemarkプラグイン
+
+目次をレンダリングする前に、すべての見出しをMarkdownファイルから抽出し、ノード配列として編成する必要があります。 このプロセスは次の手順に分けることができます。
+
+1. ファイルコンテンツを解析して、フロントマターとコンテンツを区別する。
+2. 各見出し要素にIDを生成します。 これは、後で「部分にスクロール」機能を実装するために必要です。
+3. コンテンツを解析し、見出しとその属性を抽出する。
+
+第2ステップの場合、 `##見出し1 {#heading-id}` のようなカスタムMarkdown属性としてIDを手動で追加し、`Remark-heading-id` のようなライブラリを使用してHTMLとしてレンダリングできます。 ただし、この方法では見出しの手動追加とメンテナンスが必要で、効率が悪くなります。 より効率的な方法は、HTMLにレンダリングされたときに、見出し `Heading 1` が自動的に `heading-1` というIDを取得するように、見出しテキストに基づいてIDを自動生成することです。
+
+さらに、カスタムRemarkプラグインを作成することにより、ステップ2と3を組み合わせることができます。
+
+```javascript
+export function headingTree() {
+ return (node, file) => {
+ file.data.headings = getHeadings(node);
+ };
+}
+
+function getHeadings(root) {
+ const nodes = {};
+ const output = [];
+ const indexMap = {};
+ visit(root, "heading", (node) => {
+ addID(node, nodes);
+ transformNode(node, output, indexMap);
+ });
+
+ return output;
+}
+```
+
+ここでは、ドキュメントから見出しを抽出して処理済みコンテンツに `headings` プロパティとして追加するカスタム Remark プラグイン `headingTree` があります。
+
+プラグインの主なコンポーネントは、ノードツリーを走査し操作するアクセサー関数である `getHeadings` 関数です。 読みやすさのために、この関数は2つの部分に分割されています。
+
+`addID`関数は、ドキュメント内の見出しノードを巡回し、すべての特殊文字を置き換え、スペースをダッシュに置き換えた小文字の文字列として出力します。 これらのIDは見出しの `hProperties` 属性に保存されます。
+
+```javascript
+function addID(node, nodes) {
+ const id = node.children.map((c) => c.value).join("");
+ nodes[id] = (nodes[id] || 0) + 1;
+ node.data = node.data || {
+ hProperties: {
+ id: `${id}${nodes[id] > 1 ? ` ${nodes[id] - 1}` : ""}`
+ .replace(/[^a-zA-Z\d\s-]/g, "")
+ .split(" ")
+ .join("-")
+ .toLowerCase(),
+ },
+ };
+}
+```
+
+各見出しがドキュメントで発生する回数を追跡するために `nodes` 変数を使用していることに注意してください。 これは、ドキュメントで複数回発生する見出し(たとえば、一部のセクションには同じテキストのサブ見出しがある場合があります)に数値のプレフィックスを付けるためです。 `transformNode` 関数は、解析された Markdown 抽象構文木(AST)から取得したノードを受け取り、目次の構築に適した形式に変換します。
+
+```javascript
+import { toString } from "mdast-util-to-string";
+
+function transformNode(node, output, indexMap) {
+ const transformedNode = {
+ value: toString(node),
+ depth: node.depth,
+ data: node.data,
+ children: [],
+ };
+
+ if (node.depth === 2) {
+ output.push(transformedNode);
+ indexMap[node.depth] = transformedNode;
+ } else {
+ const parent = indexMap[node.depth - 1];
+ if (parent) {
+ parent.children.push(transformedNode);
+ indexMap[node.depth] = transformedNode;
+ }
+ }
+}
+```
+
+この関数は、ノードが深さ2(Markdownの##要素)を持っているかどうかをチェックします。 はいの場合、変換されたノードが出力配列に追加され、`indexMap` の対応する深さの位置に保存されます。 これは、変換されたノードがディレクトリのトップレベルにあることを意味します。 ここで、深さ2をトップレベルの深さとして指定しています。これにより、HTML出力で `` タグが生成されます。 深さ1は使用しません。ページ上に複数の `` 要素があると、そのページのアクセシビリティとSEOの点で望ましくありません。
+
+ノードの深さが大きい場合(たとえば、###または####要素)、この関数は `indexMap` で現在のノードの1つ上の深さの位置(つまり、 `node.depth - 1`)を調べて親ノードを特定します。 親ノードが見つかった場合、変換されたノードが親ノードの `children` 配列に追加され、 `indexMap` が適切に更新されます。 これは、下位レベルのノードが上位レベルノードの子ノードになる目次の入れ子構造の構築に役立ちます。
+
+
+この関数が適切に機能するには、ディレクトリに有効な構造が必要です。つまり、ノードの深さがレベル2から直接レベル4に飛ぶべきではありません。
+
+これで、`getHeadings`関数を実装するために必要なすべてが揃いました。
+
+```javascript
+import matter from "gray-matter";
+import { remark } from "remark";
+
+import { headingTree } from "./headings";
+
+const postsDirectory = path.join(process.cwd(), "posts");
+
+export async function getHeadings(id) {
+ const fullPath = path.join(postsDirectory, `${id}.mdx`);
+ const fileContents = fs.readFileSync(fullPath, "utf8");
+
+ // Use gray-matter to parse the post metadata section
+ const matterResult = matter(fileContents);
+
+ // Use remark to convert Markdown into HTML string
+ const processedContent = await remark()
+ .use(headingTree)
+ .process(matterResult.content);
+
+ return processedContent.data.headings;
+}
+```
+
+これで、ドキュメントからの見出し配列とそのデータ属性が得られました。 配列の構造は次のとおりです。
+
+
+```javascript
+[
+ {
+ value: "Heading 1",
+ depth: 2,
+ data: { hProperties: { id: "heading-1" } },
+ children: [
+ {
+ value: "Heading 2",
+ depth: 3,
+ data: { hProperties: { id: "heading-2" } },
+ children: [
+ {
+ value: "Heading 3",
+ depth: 4,
+ data: { hProperties: { id: "heading-3" } },
+ children: [],
+ },
+ ],
+ },
+ ],
+ },
+ {
+ value: "Heading 4",
+ depth: 2,
+ data: { hProperties: { id: "heading-4" } },
+ children: [],
+ },
+];
+```
+
+## 目次のレンダリング
+
+これでタイトルデータが得られたので、目次をレンダリングするために使用できます。 まず、目次のレンダリングロジックのラッパーとなる `TableOfContents` コンポーネントを作成します。
+
+
+```jsx
+"use client";
+
+export const TableOfContents = ({ nodes }) => {
+ if (!nodes?.length) {
+ return null;
+ }
+
+ return (
+
+
目次
+ {renderNodes(nodes)}
+
+ );
+};
+```
+
+Next.jsの「アプリ」ディレクトリを使用している場合は、 `"use client"`ディレクティブを使用してこのコンポーネントをクライアントコンポーネントとしてマークする必要があることに注意してください。
+
+
+目次自体のレンダリングは、`renderNodes`関数によって管理されます。 レンダリングロジックは再帰的であるため、コンポーネント内で定義するのではなく、個別の関数を使用します。
+
+```jsx
+function renderNodes(nodes) {
+ return (
+
+ {nodes.map((node) => (
+ -
+ {node.value}
+ {node.children?.length > 0 && renderNodes(node.children)}
+
+ ))}
+
+ );
+}
+```
+
+目次の各要素は、`href`属性を介して対応する見出しIDを指すリンクです。
+
+## 目次リンクをクリックしたときにスムーススクロールエフェクトを追加する
+
+基本的な目次が完成しました。記事をレンダリングするページで、`await getHeadings(postId)` を呼び出すか、「ページ」ディレクトリを使用している場合は `getStaticProps` 内でこの操作を実行することによって見出しを取得し、そのデータを TableOfContents コンポーネントに渡すことができます。記事ページ上で目次リンクをクリックしたとき、ページの対応する部分に移動する必要があります。ただし、見出しに突然ジャンプする代わりに、スムーススクロールを有効にできます。追加の強化として、深さに基づいて子リンクのフォントサイズを徐々に縮小できます。
+
+これを実現するために、スムーススクロールと個々のリンクスタイルを担当する `TOCLink` コンポーネントを導入し、 `renderNodes` 内でそれを使用します。
+
+```jsx
+function renderNodes(nodes) {
+ return (
+
+ {nodes.map((node) => (
+ -
+
+ {node.children?.length > 0 && renderNodes(node.children)}
+
+ ))}
+
+ );
+}
+
+const TOCLink = ({ node }) => {
+ const fontSizes = { 2: "base", 3: "sm", 4: "xs" };
+ const id = node.data.hProperties.id;
+ return (
+ {
+ e.preventDefault();
+ document
+ .getElementById(id)
+ .scrollIntoView({ behavior: "smooth", block: "start" });
+ }}
+ >
+ {node.value}
+
+ );
+};
+```
+
+Web ページ上の特定の要素にスムーススクロールするために、最初に ID を使用して要素を特定し、 `behavior: "smooth"` オプションを使用した `scrollIntoView` メソッドを使用します。このメソッドの詳細については、 `MDN` Webサイトをご覧ください。このメソッドはブラウザのサポートが広範囲に渡っていますが、 `smooth` オプションは一部の古いブラウザと互換性がない場合があります。この方法を採用することで、今では目次リンクをクリックすると以前の突然の遷移ではなくきれいなスクロールアニメーションが発生します。
+
+見出し要素にスクロールするときにオフセットを追加する必要がある場合(ページに固定ナビゲーションバーがある場合など)は、見出し要素に `scroll-margin-top` CSSプロパティを適用できます。
+
+加えて、`TailwindCSS` とその `text` ユーティリティクラスを使用して、深さに基づいて目次リンクのフォントサイズを徐々に縮小できます。
+
+## アクティブリンクの強調表示
+
+目次ナビゲーションを強化するための最後のポイントは、対応する見出しがページ上に表示されているときに目次リンクを強調表示することです。
+
+ページ上の要素の可視性を検出するために、 `Intersection Observer API` を使用します。この API はブラウザのサポートが広範囲に渡っていますが、いくつかの小さな問題があります。 加えて、この機能をカスタムフックに移し替えます。このフックは、リンクが強調表示されているかどうかを示すブール値を返し、強調表示状態を手動で設定するコールバックを提供します。 このフックは、 `TOCLink` コンポーネントで使用されます。
+
+```jsx
+import { useEffect, useRef, useState } from "react";
+
+function useHighlighted(id) {
+ const observer = useRef();
+ const [activeId, setActiveId] = useState("");
+
+ useEffect(() => {
+ const handleObserver = (entries) => {
+ entries.forEach((entry) => {
+ if (entry?.isIntersecting) {
+ setActiveId(entry.target.id);
+ }
+ });
+ };
+
+ observer.current = new IntersectionObserver(handleObserver, {
+ rootMargin: "0% 0% -35% 0px",
+ });
+
+ const elements = document.querySelectorAll("h2, h3, h4");
+ elements.forEach((elem) => observer.current.observe(elem));
+ return () => observer.current?.disconnect();
+ }, []);
+
+ return [activeId === id, setActiveId];
+}
+
+const TOCLink = ({ node }) => {
+ const fontSizes = { 2: "base", 3: "sm", 4: "xs" };
+ const id = node.data.hProperties.id;
+ const [highlighted, setHighlighted] = useHighlighted(id);
+ return (
+ {
+ e.preventDefault();
+ setHighlighted(id);
+ document
+ .getElementById(id)
+ .scrollIntoView({ behavior: "smooth", block: "start" });
+ }}
+ >
+ {node.value}
+
+ );
+};
+```
+
+このフックの `handleObserver` 関数は、`Intersection Observer` のコールバック関数として機能し、観察対象の要素の可視性の変更を処理し、エントリー配列をパラメータとして受け取ります。 `handleObserver` 関数は、h2、h3、h4要素を含むエントリーを反復処理し、 `isIntersecting` プロパティが `true` であるかどうかをチェックします。つまり、要素がビューポート内で可視であるかどうかを示します。可視の場合は、 `setActiveId` を使用して目次内のアクティブなセクションを更新します。リンクがクリックされると、 `setHighlighted` コールバックを使用してそれを強調表示に設定します。
+
+加えて、新しい `Intersection Observer` インスタンスを `ref` に格納して、コンポーネントのレンダリング中にそのアイデンティティを保持します。
+
+ページをスクロールすると、ページが対応するセクションに到達したときに目次内のアクティブなセクションがどのように更新されるかを、このページでこの目次の実際の効果を確認できます。
+
+## 結論
+
+全体として、Remarkとカスタムプラグインを使用してNext.jsブログ用の目次を作成することで、Webサイトのユーザーエクスペリエンスとアクセシビリティが大幅に向上します。 Remarkという強力なマークダウン・プロセッサと、その豊富なプラグインの範囲により、マークダウンファイルから見出しを簡単に抽出し、インタラクティブでナビゲーションが容易な目次に変換することができます。
+
+目次を導入することで、Next.jsブログ上のユーザーエクスペリエンスを向上させ、読者が必要な情報をより簡単に見つけられるようになります。 加えて、Remarkを使用してカスタム目次プラグインを作成することで、コンテンツから目次を分離してコンテンツの可用性とアクセシビリティを向上させることができます。 `mdast-util-to-string` や `unist-util-visit` などのプラグインを使用することで、コンテンツから見出しを抽出し、一意のIDを生成し、目次の構築に適した形式に解析できます。
+
+このチュートリアルでは、入れ子構造、スムーススクロール、アクティブリンクの強調表示を備えたカスタム目次を作成することで、このプロセスを案内しました。 したがって、読者は今や関心のあるコンテンツをすばやく見つけてナビゲートできるようになり、ブログの全体的な可用性と価値が向上しました。
\ No newline at end of file
diff --git a/i18n/zh/docusaurus-plugin-content-blog/raect-markdown/react-markdown.md b/i18n/zh/docusaurus-plugin-content-blog/raect-markdown/react-markdown.md
new file mode 100644
index 000000000..7416b1d5f
--- /dev/null
+++ b/i18n/zh/docusaurus-plugin-content-blog/raect-markdown/react-markdown.md
@@ -0,0 +1,333 @@
+---
+slug: react-markdown
+title: 在 Next.js 博客中使用 Remark 创建交互式目录
+description: 目录具有许多好处,对于网站,特别是博客,是一项有价值的增加。一个组织良好且易于导航的目录显著提升用户体验,为读者简化查找所需信息的过程。通过添加目录,您不仅可以为读者提供简化的导航,还可以提高内容的整体可访问性和可用性。
+authors: [ owen ]
+image: https://cdn.illacloud.com/illa-website/blog/react-markdown/cover.webp
+tags: [ postgresql, select ]
+date: 2024-02-26T10:00
+---
+
+目录具有许多好处,对于网站,特别是博客,是一项有价值的增加。一个组织良好且易于导航的目录显著提升用户体验,为读者简化查找所需信息的过程。通过添加目录,您不仅可以为读者提供简化的导航,还可以提高内容的整体可访问性和可用性。
+
+在这篇文章中,我们将介绍使用Remark(一种强大的Markdown处理器)为`Next.js`博客创建交互式目录的必要步骤。虽然一些Remark插件(如`Remark-toc`)提供了这种功能,但生成的目录位于内容本身内部,限制了其潜在用途。例如,在此博客上,目录被呈现在博客内容之外,使其在导航时可见。这是我们将在本教程中构建的目录类型。我们将从简要讨论Remark的基本知识、其插件以及与Next.js的集成开始。然后,我们将深入研究实现自定义目录的实际步骤,最后,我们将使其具有交互性,以便单击目录项将页面滚动到相应的部分。
+
+## Remark及其插件
+
+Remark是一款可扩展的Markdown处理器,简化了将Markdown文件转换为HTML或其他格式的过程。Remark的关键方面是其基于插件的架构,使开发人员能够扩展和定制其功能。这些插件可以处理诸如语法高亮显示、添加目录或解析自定义Markdown语法等任务。将Remark与Next.js集成非常简单——通常与`getStaticProps`函数一起使用,以在构建过程中处理Markdown文件。它还可以处理MDX文件,使其成为使用新的“app”目录的Next.js网站的可行选择。Remark强大的处理能力和与Next.js的无缝集成使其成为增强Next.js博客和网站内容以及用户体验的理想选择。
+
+## 入门
+
+尽管我们正在构建一个自定义目录,但我们不必从头开始编写所有内容。为了将Markdown/MDX内容与前置内容分离,我们将使用`Gray-matter`包。如果Markdown文件中没有前置内容,这是可选的。为了处理Markdown本身,我们将使用Remark包。我们还需要`unist-util-visit`包来遍历节点树,以及`mdast-util-to-string`包来获取节点的文本内容。
+
+让我们安装所有这些包。
+
+```shell
+npm i remark mdast-util-to-string gray-matter unist-util-visit
+```
+
+## 用于从内容中提取标题的自定义Remark插件
+
+在呈现目录之前,我们必须从Markdown文件中提取所有标题,并将它们组织成一个节点数组。这个过程可以分为几个步骤:
+
+1. 解析文件内容以将前置内容与内容分离。
+2. 为每个标题元素生成ID。这对于后面实现滚动到部分功能是必要的。
+3. 解析内容,提取标题及其属性。
+
+对于第2步,我们可以手动添加IDs作为自定义Markdown属性,例如`## Heading 1 {#heading-id}`,然后使用类似`Remark-heading-id`的库将它们渲染为HTML。然而,这种方法需要手动添加和维护这些标题,效率较低。更高效的方法是根据标题文本自动生成IDs,例如,当转换为HTML时,标题`Heading 1`将自动获得ID `heading-1`。
+此外,我们可以通过创建自定义Remark插件将步骤2和3结合起来。
+js复制代码
+
+```javascript
+export function headingTree() {
+ return (node, file) => {
+ file.data.headings = getHeadings(node);
+ };
+}
+
+function getHeadings(root) {
+ const nodes = {};
+ const output = [];
+ const indexMap = {};
+ visit(root, "heading", (node) => {
+ addID(node, nodes);
+ transformNode(node, output, indexMap);
+ });
+
+ return output;
+}
+```
+
+在这里,我们有我们的自定义Remark插件`headingTree`,它从文档中提取标题并将它们作为`headings`属性添加到处理后的内容中。
+
+插件的主要组件是`getHeadings`函数,这是一个访问器函数,遍历节点树并操纵它们。为了提高可读性,该函数分为两个部分。
+
+`addID`函数遍历文档中的标题节点,替换它们所有的特殊字符,并将它们作为小写字符串输出,其中空格由破折号替换。这些ID将存储在标题的`hProperties`属性中。
+
+```javascript
+function addID(node, nodes) {
+ const id = node.children.map((c) => c.value).join("");
+ nodes[id] = (nodes[id] || 0) + 1;
+ node.data = node.data || {
+ hProperties: {
+ id: `${id}${nodes[id] > 1 ? ` ${nodes[id] - 1}` : ""}`
+ .replace(/[^a-zA-Z\d\s-]/g, "")
+ .split(" ")
+ .join("-")
+ .toLowerCase(),
+ },
+ };
+}
+```
+
+注意,我们使用`nodes`变量来跟踪每个标题出现的次数。这样做是为了在文档中出现多次的标题(例如,某些部分可能具有相同文本的子标题)前缀带有数字。`transformNode`函数接受从解析后的Markdown抽象语法树(AST)中得到的节点,并将其转换为一个更适用于构建目录的格式。
+
+```javascript
+import { toString } from "mdast-util-to-string";
+
+function transformNode(node, output, indexMap) {
+ const transformedNode = {
+ value: toString(node),
+ depth: node.depth,
+ data: node.data,
+ children: [],
+ };
+
+ if (node.depth === 2) {
+ output.push(transformedNode);
+ indexMap[node.depth] = transformedNode;
+ } else {
+ const parent = indexMap[node.depth - 1];
+ if (parent) {
+ parent.children.push(transformedNode);
+ indexMap[node.depth] = transformedNode;
+ }
+ }
+}
+```
+
+该函数检查节点是否具有深度为2(Markdown中的##元素)。如果是,转换后的节点将添加到输出数组中,并保存在`indexMap`中相应深度的位置。这表示转换后的节点位于目录的顶级。在此处,我们将深度2指定为顶级深度,因为这将在HTML输出中产生``标签。我们不使用深度1,因为在页面上有多个``元素对于页面的可访问性和SEO来说并不好。
+
+如果节点的深度大于2(例如,###或####元素),该函数通过查找`indexMap`中当前节点上一级深度的位置(即`node.depth - 1`)来识别父节点。如果找到父节点,则将转换后的节点添加到父节点的`children`数组中,并相应地更新`indexMap`。这有助于构建目录的嵌套结构,其中深层次的节点成为较高层次节点的子节点。
+
+值得注意的是,为了使该函数正常工作,目录应具有有效的结构,例如,不应该在节点深度2直接跳转到深度4。
+
+现在我们有了实现`getHeadings`函数所需的一切。
+
+```javascript
+import matter from "gray-matter";
+import { remark } from "remark";
+
+import { headingTree } from "./headings";
+
+const postsDirectory = path.join(process.cwd(), "posts");
+
+export async function getHeadings(id) {
+ const fullPath = path.join(postsDirectory, `${id}.mdx`);
+ const fileContents = fs.readFileSync(fullPath, "utf8");
+
+ // Use gray-matter to parse the post metadata section
+ const matterResult = matter(fileContents);
+
+ // Use remark to convert Markdown into HTML string
+ const processedContent = await remark()
+ .use(headingTree)
+ .process(matterResult.content);
+
+ return processedContent.data.headings;
+}
+```
+
+有了这个,我们就有了来自文档的标题数组,以及它们的数据属性。数组的结构如下。
+
+```javascript
+[
+ {
+ value: "Heading 1",
+ depth: 2,
+ data: { hProperties: { id: "heading-1" } },
+ children: [
+ {
+ value: "Heading 2",
+ depth: 3,
+ data: { hProperties: { id: "heading-2" } },
+ children: [
+ {
+ value: "Heading 3",
+ depth: 4,
+ data: { hProperties: { id: "heading-3" } },
+ children: [],
+ },
+ ],
+ },
+ ],
+ },
+ {
+ value: "Heading 4",
+ depth: 2,
+ data: { hProperties: { id: "heading-4" } },
+ children: [],
+ },
+];
+```
+
+## 渲染目录
+
+现在我们有了标题数据,可以使用它来渲染目录。首先,我们将创建一个`TableOfContents`组件,它将是目录渲染逻辑的包装器。
+
+```jsx
+"use client";
+
+export const TableOfContents = ({ nodes }) => {
+ if (!nodes?.length) {
+ return null;
+ }
+
+ return (
+
+
Table of contents
+ {renderNodes(nodes)}
+
+ );
+};
+```
+
+请注意,如果您使用的是Next.js的“app”目录,您需要使用`"use client"`指令将此组件标记为客户端组件。
+
+目录的实际渲染将由`renderNodes`函数管理。由于渲染逻辑是递归的,我们使用单独的函数而不是在组件内部定义它。
+
+```jsx
+function renderNodes(nodes) {
+ return (
+
+ {nodes.map((node) => (
+ -
+ {node.value}
+ {node.children?.length > 0 && renderNodes(node.children)}
+
+ ))}
+
+ );
+}
+```
+
+目录中的每个元素都是一个链接,通过其`href`属性指向相应标题的ID。
+
+## 单击目录链接时添加平滑滚动效果
+
+基本的目录已经完成。在我们渲染文章的页面上,我们可以通过调用`await getHeadings(postId)`或者在使用 "pages" 目录时在`getStaticProps`中执行此操作)获取标题,并将数据传递给 TableOfContents 组件。在文章页面上,当我们点击目录链接时,应该导航到页面的相应部分。然而,与其突然跳转到标题,我们可以启用平滑滚动。作为附加的增强,我们可以根据其深度逐渐减小子链接的字体大小。
+
+为了实现这一点,我们将引入一个`TOCLink`组件,负责平滑滚动和个别链接样式,然后我们将在`renderNodes`中使用它。
+
+```jsx
+function renderNodes(nodes) {
+ return (
+
+ {nodes.map((node) => (
+ -
+
+ {node.children?.length > 0 && renderNodes(node.children)}
+
+ ))}
+
+ );
+}
+
+const TOCLink = ({ node }) => {
+ const fontSizes = { 2: "base", 3: "sm", 4: "xs" };
+ const id = node.data.hProperties.id;
+ return (
+ {
+ e.preventDefault();
+ document
+ .getElementById(id)
+ .scrollIntoView({ behavior: "smooth", block: "start" });
+ }}
+ >
+ {node.value}
+
+ );
+};
+```
+
+为了平滑滚动到网页上的特定元素,我们首先使用其 ID 定位元素,然后使用带有`behavior: "smooth"`选项的`scrollIntoView`方法。有关此方法的更多信息,请参阅`MDN`网站。该方法在`浏览器支持`方面具有广泛的支持,但`smooth`选项可能与一些较旧的浏览器不兼容。通过采用这种方法,现在点击目录链接会产生一个漂亮的滚动动画,而不是之前的突然过渡。
+
+如果您需要在滚动到标题元素时添加偏移量(例如,当页面有一个固定的导航栏时),您可以将`scroll-margin-top` CSS 属性应用于标题元素。
+
+此外,我们可以使用`TailwindCSS`和其`text`实用程序类,根据深度逐渐减小目录链接的字体大小。
+
+## 强调活动链接
+
+为了增强目录导航,最后的一点是在页面上查看其相应标题时突出显示目录链接。
+
+为了检测页面上元素的可见性,我们将使用`Intersection Observer API`,该API具有良好的浏览器支持,但有一些小问题。此外,我们将此功能转移到一个自定义的钩子中,该钩子返回一个布尔值,指示链接是否突出显示,并提供手动设置高亮状态的回调。这个钩子将在`TOCLink`组件中使用。
+
+```jsx
+import { useEffect, useRef, useState } from "react";
+
+function useHighlighted(id) {
+ const observer = useRef();
+ const [activeId, setActiveId] = useState("");
+
+ useEffect(() => {
+ const handleObserver = (entries) => {
+ entries.forEach((entry) => {
+ if (entry?.isIntersecting) {
+ setActiveId(entry.target.id);
+ }
+ });
+ };
+
+ observer.current = new IntersectionObserver(handleObserver, {
+ rootMargin: "0% 0% -35% 0px",
+ });
+
+ const elements = document.querySelectorAll("h2, h3, h4");
+ elements.forEach((elem) => observer.current.observe(elem));
+ return () => observer.current?.disconnect();
+ }, []);
+
+ return [activeId === id, setActiveId];
+}
+
+const TOCLink = ({ node }) => {
+ const fontSizes = { 2: "base", 3: "sm", 4: "xs" };
+ const id = node.data.hProperties.id;
+ const [highlighted, setHighlighted] = useHighlighted(id);
+ return (
+ {
+ e.preventDefault();
+ setHighlighted(id);
+ document
+ .getElementById(id)
+ .scrollIntoView({ behavior: "smooth", block: "start" });
+ }}
+ >
+ {node.value}
+
+ );
+};
+```
+
+在这个钩子中`handleObserver`函数作为`Intersection Observer`的回调函数,处理被观察元素的可见性变化,接受一个条目数组作为其参数。
+`handleObserver`函数遍历条目,其中包括 h2、h3 和 h4 元素,检查`isIntersecting`属性是否为`true` — 表示元素在视口中可见 — 如果是,使用`setActiveId`更新目录中的活动部分。当链接被点击时,我们通过`setHighlighted`回调将其设置为突出显示。
+
+此外,我们将一个新的`Intersection Observer`实例存储在一个`ref`中,以在组件渲染期间保持其身份不变。
+
+通过滚动页面,您可以在此页面上看到这个目录的实际效果,观察随着页面到达相应部分,目录中的活动部分如何更新。
+
+## 结论
+
+总的来说,使用Remark和自定义插件为 Next.js 博客创建目录可以为您的网站的用户体验和可访问性带来许多好处。通过 Remark,这个强大的 Markdown 处理器,以及它丰富的插件范围,可以轻松从 Markdown 文件中提取标题,并将它们转换为交互式和易于导航的目录。
+
+通过引入目录,您可以增强 Next.js 博客上的用户体验,使读者更容易找到他们需要的信息。此外,使用 Remark 创建自定义目录插件使您能够将目录集成到内容之外,从而提高内容的可用性和可访问性。通过使用诸如`mdast-util-to-string`和`unist-util-visit`等插件,可以从内容中提取标题,生成唯一的 ID,并将它们解析为适用于构建目录的格式。
+
+本教程通过创建具有嵌套结构、平滑滚动和活动链接突出显示的自定义目录,引导您完成了这个过程。因此,读者现在可以快速找到并导航到他们感兴趣的内容,增强了博客的整体可用性和价值。
\ No newline at end of file