import { useRouter } from "next/router"; import { slugify } from "queries/helpers"; import { preprocessMarkDawn } from "./Markdawn"; type TOCProps = { text: string; title?: string; }; export default function TOCComponent(props: TOCProps): JSX.Element { const { text, title } = props; const toc = getTocFromMarkdawn(preprocessMarkDawn(text), title); const router = useRouter(); return ( <> <h3 className="text-xl">Table of content</h3> <div className="text-left max-w-[14.5rem]"> <p className="my-2 overflow-x-hidden relative text-ellipsis whitespace-nowrap text-left"> <a className="" onClick={async () => router.replace(`#${toc.slug}`)}> {<abbr title={toc.title}>{toc.title}</abbr>} </a> </p> <TOCLevel tocchildren={toc.children} parentNumbering="" /> </div> </> ); } type TOCLevelProps = { tocchildren: TOC[]; parentNumbering: string; }; function TOCLevel(props: TOCLevelProps): JSX.Element { const router = useRouter(); const { tocchildren, parentNumbering } = props; return ( <ol className="pl-4 text-left"> {tocchildren.map((child, childIndex) => ( <> <li key={child.slug} className="my-2 overflow-x-hidden w-full text-ellipsis whitespace-nowrap" > <span className="text-dark">{`${parentNumbering}${ childIndex + 1 }.`}</span>{" "} <a onClick={async () => router.replace(`#${child.slug}`)}> {<abbr title={child.title}>{child.title}</abbr>} </a> </li> <TOCLevel tocchildren={child.children} parentNumbering={`${parentNumbering}${childIndex + 1}.`} /> </> ))} </ol> ); } export type TOC = { title: string; slug: string; children: TOC[]; }; export function getTocFromMarkdawn(text: string, title?: string): TOC { const toc: TOC = { title: title ?? "Return to top", slug: slugify(title) ?? "", children: [], }; let h2 = -1; let h3 = -1; let h4 = -1; let h5 = -1; let scenebreak = 0; let scenebreakIndex = 0; function getTitle(line: string): string { return line.slice(line.indexOf(`">`) + 2, line.indexOf("</")); } function getSlug(line: string): string { return line.slice(line.indexOf(`id="`) + 4, line.indexOf(`">`)); } text.split("\n").map((line) => { if (line.startsWith("<h1 id=")) { toc.title = getTitle(line); toc.slug = getSlug(line); } else if (line.startsWith("<h2 id=")) { toc.children.push({ title: getTitle(line), slug: getSlug(line), children: [], }); h2 += 1; h3 = -1; h4 = -1; h5 = -1; scenebreak = 0; } else if (line.startsWith("<h3 id=")) { toc.children[h2].children.push({ title: getTitle(line), slug: getSlug(line), children: [], }); h3 += 1; h4 = -1; h5 = -1; scenebreak = 0; } else if (line.startsWith("<h4 id=")) { toc.children[h2].children[h3].children.push({ title: getTitle(line), slug: getSlug(line), children: [], }); h4 += 1; h5 = -1; scenebreak = 0; } else if (line.startsWith("<h5 id=")) { toc.children[h2].children[h3].children[h4].children.push({ title: getTitle(line), slug: getSlug(line), children: [], }); h5 += 1; scenebreak = 0; } else if (line.startsWith("<h6 id=")) { toc.children[h2].children[h3].children[h4].children[h5].children.push({ title: getTitle(line), slug: getSlug(line), children: [], }); } else if (line.startsWith(`<SceneBreak`)) { scenebreak += 1; scenebreakIndex += 1; const child = { title: `Scene break ${scenebreak}`, slug: slugify(`scene-break-${scenebreakIndex}`), children: [], }; if (h5 >= 0) { toc.children[h2].children[h3].children[h4].children[h5].children.push( child ); } else if (h4 >= 0) { toc.children[h2].children[h3].children[h4].children.push(child); } else if (h3 >= 0) { toc.children[h2].children[h3].children.push(child); } else if (h2 >= 0) { toc.children[h2].children.push(child); } else { toc.children.push(child); } } }); return toc; }