Skip to content

Commit

Permalink
toc scroll fix
Browse files Browse the repository at this point in the history
  • Loading branch information
Nickid2018 committed Sep 23, 2023
1 parent 9c4f986 commit 8b76085
Show file tree
Hide file tree
Showing 6 changed files with 499 additions and 483 deletions.
47 changes: 23 additions & 24 deletions src/app/blog/[...slug]/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,36 +7,36 @@ import Prism from "prismjs";
export default function Layout({ children }: { children: ReactNode }) {
useEffect(() => {
const mainContent = document.querySelector(".markdown-content");
if (mainContent) {
require("prismjs/components/prism-diff.js");
require("prismjs/plugins/diff-highlight/prism-diff-highlight.js");
require("prismjs/plugins/line-numbers/prism-line-numbers.js");
require("prismjs/plugins/line-highlight/prism-line-highlight.js");
const useLanguages = mainContent.querySelectorAll('pre[class*="language-"]');
const collectedLanguages = [] as string[];
for (let i = 0; i < useLanguages.length; i++) {
const splitData = useLanguages[i].className.split(" ");
for (let j = 0; j < splitData.length; j++) {
const data = splitData[j];
if (data.startsWith("language-")) {
const language = data.startsWith("language-diff-") ? data.substring(14) : data.substring(9);
if (!collectedLanguages.includes(language)) collectedLanguages.push(language);
}
if (!mainContent) return;

require("prismjs/components/prism-diff.js");
require("prismjs/plugins/diff-highlight/prism-diff-highlight.js");
require("prismjs/plugins/line-numbers/prism-line-numbers.js");
require("prismjs/plugins/line-highlight/prism-line-highlight.js");
const useLanguages = mainContent.querySelectorAll('pre[class*="language-"]');
const collectedLanguages = [] as string[];
for (let i = 0; i < useLanguages.length; i++) {
const splitData = useLanguages[i].className.split(" ");
for (let j = 0; j < splitData.length; j++) {
const data = splitData[j];
if (data.startsWith("language-")) {
const language = data.startsWith("language-diff-") ? data.substring(14) : data.substring(9);
if (!collectedLanguages.includes(language)) collectedLanguages.push(language);
}
}
collectedLanguages.sort();
for (let i = 0; i < collectedLanguages.length; i++) {
const language = collectedLanguages[i];
if (language) require(`prismjs/components/prism-${language}.js`);
}
Prism.highlightAll();
}
collectedLanguages.sort();
for (let i = 0; i < collectedLanguages.length; i++) {
const language = collectedLanguages[i];
if (language) require(`prismjs/components/prism-${language}.js`);
}
Prism.highlightAll();

const buttons = document.getElementsByClassName("copy-content");
for (let i = 0; i < buttons.length; i++) {
buttons[i].addEventListener("click", () => {
const button = buttons[i] as HTMLButtonElement;
navigator.clipboard.writeText(button.value).then(() => {
navigator.clipboard.writeText((button.nextElementSibling as HTMLPreElement).innerText).then(() => {
button.innerText = "inventory";
setTimeout(() => (button.innerText = "content_paste"), 1000);
});
Expand Down Expand Up @@ -107,7 +107,6 @@ export default function Layout({ children }: { children: ReactNode }) {
const linkTo = document.getElementById(
"cite-note-data_" + refLink.id.substring(refLink.id.lastIndexOf("_") + 1).split("-")[0]
);
console.log(refLink.id.substring(refLink.id.lastIndexOf("_") + 1).split("-")[0]);
if (linkTo) {
hoverElement.innerHTML = `<p>${linkTo.innerHTML}</p>`;
hoverElement.style.bottom = `${
Expand All @@ -128,7 +127,7 @@ export default function Layout({ children }: { children: ReactNode }) {
return (
<div className="relative top-[60px] flex h-full w-full font-sans">
<div className="flex-1 bg-neutral-100 "></div>
<div className="min-h-[calc(100vh-60px)] flex-1 justify-center border-x-neutral-300 px-20 pb-20 lg:min-w-[800px] ">
<div className="min-h-[calc(100vh-60px)] flex-1 border-x-neutral-300 px-20 pb-80 lg:min-w-[800px]">
<div> {children} </div>
</div>
<div className="w-full flex-1 bg-neutral-100">
Expand Down
2 changes: 1 addition & 1 deletion src/app/blog/[...slug]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ function makeCodeBlock(code: string, lang: string, flags: string[]) {
}
}
const buttonCopy = `
<button class="copy-content" value="${code}">
<button class="copy-content">
<span>content_paste</span>
</button>`;
const langText = flags.includes("no-copy")
Expand Down
118 changes: 41 additions & 77 deletions src/app/components/toc.tsx
Original file line number Diff line number Diff line change
@@ -1,107 +1,71 @@
"use client";
import { log } from "console";
import { slug } from "github-slugger";
import { title } from "process";
import * as React from "react";
import { useEffect, useState } from "react";

function prepareTitle(title: string) {
const preprocessed = title.startsWith("#") ? title.substring(2) : title;
if (/[^(())]+[((].+?[))]/.test(preprocessed)) {
const matchedSubs = preprocessed.match(/[((].+[))]/);
if (matchedSubs) {
const matchFirst = matchedSubs[0];
const processed = preprocessed.replace(matchFirst, "");
return (
<div>
<span>{processed}</span>
<br />
<span className="text-sm">{matchFirst}</span>
</div>
);
} else return <span>{preprocessed}</span>;
}
return <span>{preprocessed}</span>;
}

interface Node {
children: Node[] | string[];
interface TreeNode {
children: TreeNode[] | null;
this: HTMLHeadElement;
}

function parseTitleTree(titles: HTMLHeadElement[]) {
const root: Array<Node> = [];
const root: Array<TreeNode> = [];

for (let i = 0; i < titles.length; i++) {
let tagName = titles[i].tagName;
if (tagName === "H2") {
root.push({
this: titles[i],
children: [] as Node[]
children: []
});
} else {
if (tagName === "H3") {
(root[root.length - 1].children as Node[]).push({
root.at(-1)?.children?.push({
this: titles[i],
children: [] as string[]
children: []
});
} else {
(
(root[root.length - 1].children as Node[])[(root[root.length - 1].children as Node[]).length - 1]
.children as string[]
).push(titles[i].innerText);
root.at(-1)?.children?.at(-1)?.children?.push({
this: titles[i],
children: null
});
}
}
}
return root;
}

function setToActive(title: HTMLHeadElement) {}

export default function Toc() {
const [titles, setTitles] = useState([] as Element[]);
const [currentCursor, setCursor] = useState(0);
const [titleTree, setTitleTree] = useState([] as Node[]);
const [titleTree, setTitleTree] = useState([] as TreeNode[]);
useEffect(() => {
const titlesGet = Array.from(document.querySelectorAll(".markdown-content :is(h2,h3,h4)"));
setTitles(titlesGet);
setTitleTree(parseTitleTree(titlesGet as HTMLHeadElement[]) as Node[]);
if (document.body.getElementsByTagName("toc-completed").length > 0) return;

const observer = new IntersectionObserver(
entries => {
let leastIndex = 2147483647;
let maxIndex = -1;
for (let i = 0; i < entries.length; i++) {
const entry = entries[i];
const indexNow = titles.indexOf(entry.target);
leastIndex = Math.min(leastIndex, indexNow);
maxIndex = Math.max(maxIndex, indexNow);
}
const leastTitle = titles[leastIndex];
const maxTitle = titles[maxIndex];
if (!leastTitle || !maxTitle) return;
const leastRect = leastTitle.getBoundingClientRect();
const maxRect = maxTitle.getBoundingClientRect();
const currentTitles = Array.from(document.querySelectorAll("nav li"));
let computedCursor = 0;
for (; computedCursor < currentTitles.length; computedCursor++) {
const currentTitle = currentTitles[computedCursor];
const attr = currentTitle.attributes.getNamedItem("aria-selected");
if (attr && attr.value === "true") break;
}
if (leastRect.bottom < 60 && computedCursor < leastIndex) setCursor(leastIndex);
else if (maxRect.bottom > window.innerHeight && computedCursor >= maxIndex)
setCursor(Math.max(0, maxIndex - 1));
},
{
rootMargin: "-60px 0px 0px 0px",
threshold: 0
}
);
titlesGet.forEach(title => observer.observe(title));
const titles = Array.from(document.querySelectorAll(".markdown-content :is(h2,h3,h4)"));
const titleTree = parseTitleTree(titles as HTMLHeadElement[]);
setTitleTree(titleTree);

const observer = new IntersectionObserver(entries => {}, {
rootMargin: "-60px 0px 0px 0px",
threshold: 0
});
titles.forEach(title => observer.observe(title));
const mainTitle = document.querySelector("h1.title-text");
if (mainTitle) {
const mainTitleObserver = new IntersectionObserver(
entries => {
if (entries[0].isIntersecting) setCursor(0);
if (entries[0].isIntersecting) {
let setTo: TreeNode = titleTree[0];
setTo = setTo?.children?.[0] ?? setTo;
setTo = setTo?.children?.[0] ?? setTo;
setToActive(setTo.this);
}
},
{
rootMargin: "-60px 0px 0px 0px",
Expand All @@ -110,35 +74,35 @@ export default function Toc() {
);
mainTitleObserver.observe(mainTitle);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [titles.length]);
document.body.appendChild(document.createElement("toc-completed"));
}, [titleTree.length]);
return (
<div className="sticky left-20 top-[calc(60px+2.5rem)] m-10 w-64 rounded-2xl bg-neutral-200 px-3 py-5 pb-[1em] ">
<div className="h-8">
<div id="toc" className="sticky left-20 top-[calc(60px+2.5rem)] m-10 w-64 pb-[1em]">
<div className="mx-3 my-4 h-8">
<span className="font-sans font-bold text-neutral-500">On this page</span>
</div>
{/* // TODO change toc to compatible with menu tabs */}
<nav>
<nav className="mb-5 border-l border-l-neutral-300 px-3">
<ul className="text-sm text-neutral-500">
{titleTree.map((titleL1: Node, index: number) => {
{titleTree.map((titleL1: TreeNode, index: number) => {
return (
<li key={index}>
<a href={"#" + slug(titleL1.this.innerText)}>
<span className="hover:text-neutral-600">{prepareTitle(titleL1.this.innerText)}</span>
</a>
<ul className="ml-3">
{(titleL1.children as Node[]).map((titleL2: Node, index: number) => {
<ul className="my-2 ml-3 leading-6">
{titleL1.children?.map((titleL2: TreeNode, index: number) => {
return (
<li key={index}>
<a href={"#" + slug(titleL2.this.innerText)}>
<span className="hover:text-neutral-600">{prepareTitle(titleL2.this.innerText)}</span>
</a>
<ul className="ml-3">
{(titleL2.children as string[]).map((titleL3: string, index: number) => {
<ul className="my-2 ml-3 leading-6">
{titleL2.children?.map((titleL3: TreeNode, index: number) => {
return (
<li key={index}>
<a href={"#" + slug(titleL3)}>
<span className="hover:text-neutral-600">{prepareTitle(titleL3)}</span>
<a href={"#" + slug(titleL3.this.innerText)}>
<span className="hover:text-neutral-600">{prepareTitle(titleL3.this.innerText)}</span>
</a>
</li>
);
Expand Down
2 changes: 2 additions & 0 deletions src/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,8 @@ export default function RootLayout({ children }: { children: any }) {
${notoSerif.variable}
${notoSans.variable}
${jetbrainsMono.variable}
scroll-smooth
scroll-pt-[60px]
`}
>
<body>
Expand Down
Loading

0 comments on commit 8b76085

Please sign in to comment.