TOC now support entries with the same title
This commit is contained in:
parent
e675a90ed6
commit
a74528fe28
|
@ -180,12 +180,65 @@ export default function Markdawn(props: ScenBreakProps): JSX.Element {
|
||||||
|
|
||||||
export function preprocessMarkDawn(text: string): string {
|
export function preprocessMarkDawn(text: string): string {
|
||||||
let scenebreakIndex = 0;
|
let scenebreakIndex = 0;
|
||||||
|
const visitedSlugs: string[] = [];
|
||||||
|
|
||||||
const result = text.split("\n").map((line) => {
|
const result = text.split("\n").map((line) => {
|
||||||
if (line === "* * *" || line === "---") {
|
if (line === "* * *" || line === "---") {
|
||||||
scenebreakIndex++;
|
scenebreakIndex++;
|
||||||
return `<SceneBreak id="scene-break-${scenebreakIndex}">`;
|
return `<SceneBreak id="scene-break-${scenebreakIndex}">`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (line.startsWith("# ")) {
|
||||||
|
return markdawnHeadersParser(headerLevels.h1, line, visitedSlugs);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (line.startsWith("## ")) {
|
||||||
|
return markdawnHeadersParser(headerLevels.h2, line, visitedSlugs);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (line.startsWith("### ")) {
|
||||||
|
return markdawnHeadersParser(headerLevels.h3, line, visitedSlugs);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (line.startsWith("#### ")) {
|
||||||
|
return markdawnHeadersParser(headerLevels.h4, line, visitedSlugs);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (line.startsWith("##### ")) {
|
||||||
|
return markdawnHeadersParser(headerLevels.h5, line, visitedSlugs);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (line.startsWith("###### ")) {
|
||||||
|
return markdawnHeadersParser(headerLevels.h6, line, visitedSlugs);
|
||||||
|
}
|
||||||
|
|
||||||
return line;
|
return line;
|
||||||
});
|
});
|
||||||
return result.join("\n");
|
return result.join("\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum headerLevels {
|
||||||
|
h1 = 1,
|
||||||
|
h2 = 2,
|
||||||
|
h3 = 3,
|
||||||
|
h4 = 4,
|
||||||
|
h5 = 5,
|
||||||
|
h6 = 6,
|
||||||
|
}
|
||||||
|
|
||||||
|
function markdawnHeadersParser(
|
||||||
|
headerLevel: headerLevels,
|
||||||
|
line: string,
|
||||||
|
visitedSlugs: string[]
|
||||||
|
): string {
|
||||||
|
const lineText = line.slice(headerLevel + 1);
|
||||||
|
let slug = slugify(lineText);
|
||||||
|
let newSlug = slug;
|
||||||
|
let index = 2;
|
||||||
|
while (visitedSlugs.includes(newSlug)) {
|
||||||
|
newSlug = `${slug}-${index}`;
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
visitedSlugs.push(newSlug);
|
||||||
|
return `<${headerLevels[headerLevel]} id="${newSlug}">${lineText}</${headerLevels[headerLevel]}>`;
|
||||||
|
}
|
||||||
|
|
|
@ -13,44 +13,55 @@ export default function TOC(props: TOCProps): JSX.Element {
|
||||||
const toc = getTocFromMarkdawn(preprocessMarkDawn(text), title);
|
const toc = getTocFromMarkdawn(preprocessMarkDawn(text), title);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<>
|
||||||
<h3 className="text-xl">Table of content</h3>
|
<h3 className="text-xl">Table of content</h3>
|
||||||
<ol className="text-left max-w-[14.5rem]">
|
<div className="text-left max-w-[14.5rem]">
|
||||||
<li className="my-2 overflow-x-hidden relative text-ellipsis whitespace-nowrap">
|
<p className="my-2 overflow-x-hidden relative text-ellipsis whitespace-nowrap text-left">
|
||||||
<a className="" onClick={() => router.replace(`#${toc.slug}`)}>
|
<a className="" onClick={() => router.replace(`#${toc.slug}`)}>
|
||||||
{<abbr title={toc.title}>{toc.title}</abbr>}
|
{<abbr title={toc.title}>{toc.title}</abbr>}
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</p>
|
||||||
{toc.children.map((h2, h2Index) => (
|
<TOCLevel
|
||||||
|
tocchildren={toc.children}
|
||||||
|
parentNumbering=""
|
||||||
|
router={router}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
type TOCLevelProps = {
|
||||||
|
tocchildren: TOC[];
|
||||||
|
parentNumbering: string;
|
||||||
|
router: NextRouter;
|
||||||
|
};
|
||||||
|
|
||||||
|
function TOCLevel(props: TOCLevelProps): JSX.Element {
|
||||||
|
const { tocchildren, parentNumbering, router } = props;
|
||||||
|
return (
|
||||||
|
<ol className="pl-4 text-left">
|
||||||
|
{tocchildren.map((child, childIndex) => (
|
||||||
<>
|
<>
|
||||||
<li
|
<li
|
||||||
key={h2.slug}
|
key={child.slug}
|
||||||
className="my-2 overflow-x-hidden w-full text-ellipsis whitespace-nowrap"
|
className="my-2 overflow-x-hidden w-full text-ellipsis whitespace-nowrap"
|
||||||
>
|
>
|
||||||
<span className="text-dark">{`${h2Index + 1}. `}</span>
|
<span className="text-dark">{`${parentNumbering}${
|
||||||
<a onClick={() => router.replace(`#${h2.slug}`)}>
|
childIndex + 1
|
||||||
{<abbr title={h2.title}>{h2.title}</abbr>}
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<ol className="pl-4 text-left">
|
|
||||||
{h2.children.map((h3, h3Index) => (
|
|
||||||
<li
|
|
||||||
key={h3.slug}
|
|
||||||
className="my-2 overflow-x-hidden w-full text-ellipsis whitespace-nowrap"
|
|
||||||
>
|
|
||||||
<span className="text-dark">{`${h2Index + 1}.${
|
|
||||||
h3Index + 1
|
|
||||||
}.`}</span>
|
}.`}</span>
|
||||||
<a onClick={() => router.replace(`#${h3.slug}`)}>
|
<a onClick={() => router.replace(`#${child.slug}`)}>
|
||||||
{<abbr title={h3.title}>{h3.title}</abbr>}
|
{<abbr title={child.title}>{child.title}</abbr>}
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
))}
|
<TOCLevel
|
||||||
</ol>
|
tocchildren={child.children}
|
||||||
|
parentNumbering={`${parentNumbering}${childIndex + 1}.`}
|
||||||
|
router={router}
|
||||||
|
/>
|
||||||
</>
|
</>
|
||||||
))}
|
))}
|
||||||
</ol>
|
</ol>
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -69,13 +80,23 @@ export function getTocFromMarkdawn(text: string, title?: string): TOC {
|
||||||
let h5 = -1;
|
let h5 = -1;
|
||||||
let scenebreak = 0;
|
let scenebreak = 0;
|
||||||
let scenebreakIndex = 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) => {
|
text.split("\n").map((line) => {
|
||||||
if (line.startsWith("# ")) {
|
if (line.startsWith("<h1 id=")) {
|
||||||
toc.slug = slugify(line);
|
toc.title = getTitle(line);
|
||||||
} else if (line.startsWith("## ")) {
|
toc.slug = getSlug(line);
|
||||||
|
} else if (line.startsWith("<h2 id=")) {
|
||||||
toc.children.push({
|
toc.children.push({
|
||||||
title: line.slice("## ".length),
|
title: getTitle(line),
|
||||||
slug: slugify(line),
|
slug: getSlug(line),
|
||||||
children: [],
|
children: [],
|
||||||
});
|
});
|
||||||
h2++;
|
h2++;
|
||||||
|
@ -83,74 +104,64 @@ export function getTocFromMarkdawn(text: string, title?: string): TOC {
|
||||||
h4 = -1;
|
h4 = -1;
|
||||||
h5 = -1;
|
h5 = -1;
|
||||||
scenebreak = 0;
|
scenebreak = 0;
|
||||||
} else if (line.startsWith("### ")) {
|
} else if (line.startsWith("<h3 id=")) {
|
||||||
toc.children[h2].children.push({
|
toc.children[h2].children.push({
|
||||||
title: line.slice("### ".length),
|
title: getTitle(line),
|
||||||
slug: slugify(line),
|
slug: getSlug(line),
|
||||||
children: [],
|
children: [],
|
||||||
});
|
});
|
||||||
h3++;
|
h3++;
|
||||||
h4 = -1;
|
h4 = -1;
|
||||||
h5 = -1;
|
h5 = -1;
|
||||||
scenebreak = 0;
|
scenebreak = 0;
|
||||||
} else if (line.startsWith("#### ")) {
|
} else if (line.startsWith("<h4 id=")) {
|
||||||
toc.children[h2].children[h3].children.push({
|
toc.children[h2].children[h3].children.push({
|
||||||
title: line.slice("#### ".length),
|
title: getTitle(line),
|
||||||
slug: slugify(line),
|
slug: getSlug(line),
|
||||||
children: [],
|
children: [],
|
||||||
});
|
});
|
||||||
h4++;
|
h4++;
|
||||||
h5 = -1;
|
h5 = -1;
|
||||||
scenebreak = 0;
|
scenebreak = 0;
|
||||||
} else if (line.startsWith("##### ")) {
|
} else if (line.startsWith("<h5 id=")) {
|
||||||
toc.children[h2].children[h3].children[h4].children.push({
|
toc.children[h2].children[h3].children[h4].children.push({
|
||||||
title: line.slice("##### ".length),
|
title: getTitle(line),
|
||||||
slug: slugify(line),
|
slug: getSlug(line),
|
||||||
children: [],
|
children: [],
|
||||||
});
|
});
|
||||||
h5++;
|
h5++;
|
||||||
scenebreak = 0;
|
scenebreak = 0;
|
||||||
} else if (line.startsWith("###### ")) {
|
} else if (line.startsWith("<h6 id=")) {
|
||||||
toc.children[h2].children[h3].children[h4].children[h5].children.push({
|
toc.children[h2].children[h3].children[h4].children[h5].children.push({
|
||||||
title: line.slice("###### ".length),
|
title: getTitle(line),
|
||||||
slug: slugify(line),
|
slug: getSlug(line),
|
||||||
children: [],
|
children: [],
|
||||||
});
|
});
|
||||||
} else if (line.startsWith(`<SceneBreak`)) {
|
} else if (line.startsWith(`<SceneBreak`)) {
|
||||||
scenebreak++;
|
scenebreak++;
|
||||||
scenebreakIndex++;
|
scenebreakIndex++;
|
||||||
|
|
||||||
|
const child = {
|
||||||
|
title: `Scene break ${scenebreak}`,
|
||||||
|
slug: slugify(`scene-break-${scenebreakIndex}`),
|
||||||
|
children: [],
|
||||||
|
};
|
||||||
|
|
||||||
if (h5 >= 0) {
|
if (h5 >= 0) {
|
||||||
toc.children[h2].children[h3].children[h4].children[h5].children.push({
|
toc.children[h2].children[h3].children[h4].children[h5].children.push(
|
||||||
title: `Scene break ${scenebreak}`,
|
child
|
||||||
slug: slugify(`scene-break-${scenebreakIndex}`),
|
);
|
||||||
children: [],
|
|
||||||
});
|
|
||||||
} else if (h4 >= 0) {
|
} else if (h4 >= 0) {
|
||||||
toc.children[h2].children[h3].children[h4].children.push({
|
toc.children[h2].children[h3].children[h4].children.push(child);
|
||||||
title: `Scene break ${scenebreak}`,
|
|
||||||
slug: slugify(`scene-break-${scenebreakIndex}`),
|
|
||||||
children: [],
|
|
||||||
});
|
|
||||||
} else if (h3 >= 0) {
|
} else if (h3 >= 0) {
|
||||||
toc.children[h2].children[h3].children.push({
|
toc.children[h2].children[h3].children.push(child);
|
||||||
title: `Scene break ${scenebreak}`,
|
|
||||||
slug: slugify(`scene-break-${scenebreakIndex}`),
|
|
||||||
children: [],
|
|
||||||
});
|
|
||||||
} else if (h2 >= 0) {
|
} else if (h2 >= 0) {
|
||||||
toc.children[h2].children.push({
|
toc.children[h2].children.push(child);
|
||||||
title: `Scene break ${scenebreak}`,
|
|
||||||
slug: slugify(`scene-break-${scenebreakIndex}`),
|
|
||||||
children: [],
|
|
||||||
});
|
|
||||||
} else {
|
} else {
|
||||||
toc.children.push({
|
toc.children.push(child);
|
||||||
title: `Scene break ${scenebreak}`,
|
|
||||||
slug: slugify(`scene-break-${scenebreakIndex}`),
|
|
||||||
children: [],
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return toc;
|
return toc;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue