diff --git a/frontend/app/element/markdown.tsx b/frontend/app/element/markdown.tsx
index cf0adf9f9..31d507693 100644
--- a/frontend/app/element/markdown.tsx
+++ b/frontend/app/element/markdown.tsx
@@ -11,6 +11,7 @@ import { OverlayScrollbarsComponent, OverlayScrollbarsComponentRef } from "overl
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import ReactMarkdown from "react-markdown";
import rehypeRaw from "rehype-raw";
+import rehypeSlug from "rehype-slug";
import RemarkFlexibleToc, { TocItem } from "remark-flexible-toc";
import remarkGfm from "remark-gfm";
import "./markdown.less";
@@ -24,8 +25,12 @@ const Link = ({ href, children }: { href: string; children: React.ReactNode }) =
);
};
-const Heading = ({ children, hnum }: { children: React.ReactNode; hnum: number }) => {
- return
{children}
;
+const Heading = ({ id, children, hnum }: { id?: string; children: React.ReactNode; hnum: number }) => {
+ return (
+
+ {children}
+
+ );
};
const Code = ({ children }: { children: React.ReactNode }) => {
@@ -148,30 +153,30 @@ const Markdown = ({ text, textAtom, showTocAtom, style, className, resolveOpts,
const showToc = useAtomValueSafe(showTocAtom) ?? false;
const contentsOsRef = useRef(null);
- const onTocClick = useCallback((data: string) => {
+ // Ensure uniqueness of ids between MD preview instances.
+ const [idPrefix] = useState(crypto.randomUUID());
+
+ const onTocClick = useCallback((href: string) => {
if (contentsOsRef.current && contentsOsRef.current.osInstance()) {
const { viewport } = contentsOsRef.current.osInstance().elements();
- const headings = viewport.getElementsByClassName("heading");
- for (const heading of headings) {
- if (heading.textContent === data) {
- const headingBoundingRect = heading.getBoundingClientRect();
- const viewportBoundingRect = viewport.getBoundingClientRect();
- const headingTop = headingBoundingRect.top - viewportBoundingRect.top;
- viewport.scrollBy({ top: headingTop });
- break;
- }
+ const heading = document.getElementById(idPrefix + href.slice(1));
+ if (heading) {
+ const headingBoundingRect = heading.getBoundingClientRect();
+ const viewportBoundingRect = viewport.getBoundingClientRect();
+ const headingTop = headingBoundingRect.top - viewportBoundingRect.top;
+ viewport.scrollBy({ top: headingTop });
}
}
}, []);
const markdownComponents = {
a: Link,
- h1: (props: any) => ,
- h2: (props: any) => ,
- h3: (props: any) => ,
- h4: (props: any) => ,
- h5: (props: any) => ,
- h6: (props: any) => ,
+ h1: (props: any) => ,
+ h2: (props: any) => ,
+ h3: (props: any) => ,
+ h4: (props: any) => ,
+ h5: (props: any) => ,
+ h6: (props: any) => ,
img: (props: any) => ,
source: (props: any) => ,
code: Code,
@@ -186,7 +191,7 @@ const Markdown = ({ text, textAtom, showTocAtom, style, className, resolveOpts,
key={item.href}
className="toc-item"
style={{ "--indent-factor": item.depth } as React.CSSProperties}
- onClick={() => onTocClick(item.value)}
+ onClick={() => onTocClick(item.href)}
>
{item.value}
@@ -206,7 +211,7 @@ const Markdown = ({ text, textAtom, showTocAtom, style, className, resolveOpts,
>
{text}
diff --git a/package.json b/package.json
index 6237b86cf..936b9e59b 100644
--- a/package.json
+++ b/package.json
@@ -113,6 +113,7 @@
"react-gauge-chart": "^0.5.1",
"react-markdown": "^9.0.1",
"rehype-raw": "^7.0.0",
+ "rehype-slug": "^6.0.0",
"remark-flexible-toc": "^1.1.1",
"remark-gfm": "^4.0.0",
"rxjs": "^7.8.1",
diff --git a/yarn.lock b/yarn.lock
index c1e27f23b..19a7c2982 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -12181,6 +12181,7 @@ __metadata:
react-gauge-chart: "npm:^0.5.1"
react-markdown: "npm:^9.0.1"
rehype-raw: "npm:^7.0.0"
+ rehype-slug: "npm:^6.0.0"
remark-flexible-toc: "npm:^1.1.1"
remark-gfm: "npm:^4.0.0"
rollup-plugin-flow: "npm:^1.1.1"