[components/toc][f]: highlight active h in toc
This commit is contained in:
parent
ccc29184f6
commit
90cbf2be8f
|
|
@ -192,7 +192,7 @@ Understand the deeper theoretical concepts behind the technical and economic cla
|
||||||
* [Predatory inclusion](../concepts/predatory-inclusion.md)
|
* [Predatory inclusion](../concepts/predatory-inclusion.md)
|
||||||
* [Enclosure](../concepts/enclosure.md)
|
* [Enclosure](../concepts/enclosure.md)
|
||||||
|
|
||||||
**Meta**
|
#### Meta
|
||||||
|
|
||||||
* [Value](../concepts/value.md)
|
* [Value](../concepts/value.md)
|
||||||
* [Risk](../concepts/risk.md)
|
* [Risk](../concepts/risk.md)
|
||||||
|
|
|
||||||
|
|
@ -1,24 +1,10 @@
|
||||||
import React, { useEffect } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
|
|
||||||
export const Heading = ({ level, activeHeading, setActiveHeading }) => (props) => {
|
export const Heading = ({ level, observer }) => (props) => {
|
||||||
console.log(activeHeading, setActiveHeading)
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const observer = new IntersectionObserver(
|
if (observer) {
|
||||||
(entries) => {
|
observer.observe(document.getElementById(props.id));
|
||||||
entries.forEach((entry) => {
|
}
|
||||||
if (entry.isIntersecting) {
|
|
||||||
setActiveHeading(entry.target.id);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
{ rootMargin: `0% 0% -80% 0%` }
|
|
||||||
);
|
|
||||||
|
|
||||||
observer.observe(document.getElementById(props.id));
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
observer.unobserve(document.getElementById(props.id));
|
|
||||||
};
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return React.createElement(`h${level}`, { ...props })
|
return React.createElement(`h${level}`, { ...props })
|
||||||
|
|
|
||||||
|
|
@ -17,18 +17,18 @@ export default function Layout({ children }) {
|
||||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossOrigin="" />
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossOrigin="" />
|
||||||
</Head>
|
</Head>
|
||||||
<Nav />
|
<Nav />
|
||||||
<main className="max-w-7xl mx-auto px-2 sm:px-6 md:px-8 border-2 border-green-500">
|
<main className="max-w-7xl relative mx-auto px-2 sm:px-6 md:px-8">
|
||||||
{/* <div className="hidden md:block w-[16rem] fixed top-16 left-[max(0px,calc(50%-40rem))] bottom-0 right-auto px-8 overflow-y-auto border-2 border-red-500"> */}
|
{/* <div className="hidden md:block w-[16rem] fixed top-16 left-[max(0px,calc(50%-40rem))] bottom-0 right-auto px-8 overflow-y-auto border-2 border-red-500"> */}
|
||||||
{/* <Sidebar /> */}
|
{/* <Sidebar /> */}
|
||||||
{/* </div> */}
|
{/* </div> */}
|
||||||
{children}
|
{children}
|
||||||
</main>
|
</main>
|
||||||
<footer className="w-full h-24 mt-16">
|
<footer className="w-full h-24 mt-16">
|
||||||
<div className="max-w-7xl mx-auto py-12 px-4 overflow-hidden sm:px-6 md:px-8">
|
<div className="max-w-7xl mx-auto py-12 px-4 sm:px-6 md:px-8 overflow-hidden">
|
||||||
<nav className="-mx-5 -my-2 flex flex-wrap justify-center" aria-label="Footer">
|
<nav className="-mx-5 -my-2 flex flex-wrap justify-center" aria-label="Footer">
|
||||||
{navLinks.map((item) => (
|
{navLinks.map((item) => (
|
||||||
<div key={item.name} className="px-5 py-2">
|
<div key={item.name} className="px-5 py-2">
|
||||||
<a href={item.href} className="text-base text-gray-500 hover:text-gray-900">
|
<a href={item.href} className="text-base text-gray-400 hover:text-gray-500">
|
||||||
{item.name}
|
{item.name}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -42,19 +42,23 @@ export default function Layout({ children }) {
|
||||||
</a>
|
</a>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
<p className="flex items-center justify-center mt-8">
|
<div className="flex flex-col items-center mt-8 text-gray-400">
|
||||||
Created by
|
<p>
|
||||||
<a
|
Created by
|
||||||
href={siteConfig.authorUrl}
|
<a
|
||||||
target="_blank"
|
href={siteConfig.authorUrl}
|
||||||
rel="noopener noreferrer"
|
target="_blank"
|
||||||
>
|
rel="noopener noreferrer"
|
||||||
<img src={siteConfig.authorLogo} alt={siteConfig.author} width="20" height="20" className="mx-2 h-6 inline-block" />
|
>
|
||||||
{siteConfig.author}
|
<img src={siteConfig.authorLogo} alt={siteConfig.author} width="20" height="20" className="mx-2 h-6 inline-block" />
|
||||||
{' '}
|
{siteConfig.author}
|
||||||
|
{' '}
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
Licensed under a CC-By 4.0 International License
|
Licensed under a CC-By 4.0 International License
|
||||||
</a>
|
</p>
|
||||||
</p>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</footer>
|
</footer>
|
||||||
</>
|
</>
|
||||||
|
|
|
||||||
|
|
@ -2,9 +2,10 @@ import { NextSeo } from "next-seo";
|
||||||
import LiteYouTubeEmbed from "react-lite-youtube-embed";
|
import LiteYouTubeEmbed from "react-lite-youtube-embed";
|
||||||
import { useState, useEffect } from "react";
|
import { useState, useEffect } from "react";
|
||||||
|
|
||||||
import siteConfig from "../config/siteConfig";
|
|
||||||
import { YOUTUBE_REGEX } from "../lib/constants";
|
import { YOUTUBE_REGEX } from "../lib/constants";
|
||||||
|
import siteConfig from "../config/siteConfig";
|
||||||
import getMDXComponents from "./_getMDXComponents";
|
import getMDXComponents from "./_getMDXComponents";
|
||||||
|
import getObserver from "./_getIntersectionObserver"
|
||||||
import { Paragraph } from "./Paragraph";
|
import { Paragraph } from "./Paragraph";
|
||||||
import { Anchor } from "./Anchor";
|
import { Anchor } from "./Anchor";
|
||||||
|
|
||||||
|
|
@ -14,38 +15,29 @@ import { Anchor } from "./Anchor";
|
||||||
|
|
||||||
// const Paragraph = dynamic(() => import("./Paragraph").then(mod => mod.Paragraph))
|
// const Paragraph = dynamic(() => import("./Paragraph").then(mod => mod.Paragraph))
|
||||||
|
|
||||||
// import { Toc } from './Toc'
|
|
||||||
|
|
||||||
export default function MdxPage({ children }) {
|
export default function MdxPage({ children }) {
|
||||||
const [activeHeading, setActiveHeading] = useState("");
|
const [activeHeading, setActiveHeading] = useState("");
|
||||||
|
|
||||||
|
const observer = getObserver((entry) => {
|
||||||
|
if (entry.isIntersecting) {
|
||||||
|
setActiveHeading(entry.target.id);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
const components = getMDXComponents({
|
const components = getMDXComponents({
|
||||||
params: {
|
h: {
|
||||||
h: {
|
observer
|
||||||
activeHeading,
|
}
|
||||||
setActiveHeading,
|
})
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (activeHeading) {
|
if (activeHeading) {
|
||||||
const tocLink = document.querySelector(
|
const tocLink = document.querySelector(`.toc-link[href="#${activeHeading}"]`)
|
||||||
`.toc-link[href="#${activeHeading}"]`
|
|
||||||
);
|
|
||||||
tocLink.classList.add("active");
|
tocLink.classList.add("active");
|
||||||
|
|
||||||
// setTimeout to fix scrolling behavior
|
return () => {
|
||||||
// fix switching on-off when two headings are observed
|
tocLink.classList.remove("active")
|
||||||
// router push
|
|
||||||
}
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
if (activeHeading) {
|
|
||||||
const tocLink = document.querySelector(
|
|
||||||
`.toc-link[href="#${activeHeading}"]`
|
|
||||||
);
|
|
||||||
tocLink.classList.remove("active");
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}, [activeHeading]);
|
}, [activeHeading]);
|
||||||
|
|
@ -128,8 +120,8 @@ export default function MdxPage({ children }) {
|
||||||
{ name: "keywords", content: keywords ? keywords : "" },
|
{ name: "keywords", content: keywords ? keywords : "" },
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
{/*<article className="px-8 md:pl-[14rem] lg:pr-[14rem] prose max-w-none dark:prose-invert prose-a:break-all mx-auto border-2 border-yellow-500">*/}
|
{/*<article className="px-8 md:pl-[14rem] lg:pr-[1rem] prose max-w-none dark:prose-invert prose-a:break-all mx-auto border-2 border-yellow-500">*/}
|
||||||
<article className="px-8 lg:pr-[14rem] prose max-w-none dark:prose-invert prose-a:break-all mx-auto border-2 border-yellow-500">
|
<article className="px-12 lg:pr-[22rem] prose max-w-none dark:prose-invert prose-a:break-all mx-auto">
|
||||||
<header>
|
<header>
|
||||||
<div className="mb-6">
|
<div className="mb-6">
|
||||||
{title && <h1 className="mb-0">{title}</h1>}
|
{title && <h1 className="mb-0">{title}</h1>}
|
||||||
|
|
@ -161,10 +153,8 @@ export default function MdxPage({ children }) {
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
<main>
|
<main className="my-12">
|
||||||
<div className="my-6">
|
<Component components={components} />
|
||||||
<Component components={components} />
|
|
||||||
</div>
|
|
||||||
{editUrl && (
|
{editUrl && (
|
||||||
<div className="mt-12 mb-6">
|
<div className="mt-12 mb-6">
|
||||||
<a
|
<a
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
import Link from 'next/link'
|
||||||
|
|
||||||
|
|
||||||
|
export const Toc = ({ headings }) => {
|
||||||
|
return (
|
||||||
|
<nav>
|
||||||
|
<h4>
|
||||||
|
On this page
|
||||||
|
</h4>
|
||||||
|
{ headings.map(({url, title, level}, i) => (
|
||||||
|
<div key={i} className={`flex leading-5 pl-${(level-1)*4}`}>
|
||||||
|
<Link href={`#${url}`} >
|
||||||
|
<a className="py-1 text-slate-400 hover:text-white no-underline transition-colors">
|
||||||
|
{ title }
|
||||||
|
</a>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</nav>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
const getIntersectionObserver = (callback) => {
|
||||||
|
if (typeof window !== 'undefined') {
|
||||||
|
return new IntersectionObserver(
|
||||||
|
(entries) => {
|
||||||
|
entries.forEach((entry) => {
|
||||||
|
callback(entry);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
{ root: null,
|
||||||
|
rootMargin: `0% 0% -85% 0%`
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default getIntersectionObserver;
|
||||||
|
|
@ -4,16 +4,20 @@ import { Anchor } from './Anchor'
|
||||||
import { Heading } from './Heading'
|
import { Heading } from './Heading'
|
||||||
|
|
||||||
|
|
||||||
const getMDXComponents = ({ params: { h } }) => ({
|
/**
|
||||||
|
* @param {object} [props] Props passed to each component
|
||||||
|
* @param {object} [props.h] Props passed to Heading component
|
||||||
|
*/
|
||||||
|
const getMDXComponents = (props) => ({
|
||||||
Head,
|
Head,
|
||||||
p: Paragraph,
|
p: Paragraph,
|
||||||
a: Anchor,
|
a: Anchor,
|
||||||
h1: Heading({ level: 1, ...h }),
|
h1: Heading({ level: 1, ...props?.h }),
|
||||||
h2: Heading({ level: 2, ...h }),
|
h2: Heading({ level: 2, ...props?.h }),
|
||||||
h3: Heading({ level: 3, ...h }),
|
h3: Heading({ level: 3, ...props?.h }),
|
||||||
h4: Heading({ level: 4, ...h }),
|
h4: Heading({ level: 4, ...props?.h }),
|
||||||
h5: Heading({ level: 5, ...h }),
|
h5: Heading({ level: 5, ...props?.h }),
|
||||||
h6: Heading({ level: 6, ...h }),
|
h6: Heading({ level: 6, ...props?.h }),
|
||||||
});
|
});
|
||||||
|
|
||||||
export default getMDXComponents;
|
export default getMDXComponents;
|
||||||
|
|
|
||||||
|
|
@ -64,7 +64,7 @@ export default makeSource({
|
||||||
rehypePlugins: [
|
rehypePlugins: [
|
||||||
rehypeSlug,
|
rehypeSlug,
|
||||||
rehypeAutolinkHeadings,
|
rehypeAutolinkHeadings,
|
||||||
[ rehypeToc, { position: 'beforeend' } ]
|
[ rehypeToc, { position: 'afterend' } ]
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@
|
||||||
/* OTHERS */
|
/* OTHERS */
|
||||||
|
|
||||||
html {
|
html {
|
||||||
scroll-behavior: smooth;
|
/* scroll-behavior: smooth; */
|
||||||
}
|
}
|
||||||
|
|
||||||
/* bg-neutral-800
|
/* bg-neutral-800
|
||||||
|
|
@ -39,23 +39,30 @@ body {
|
||||||
background: linear-gradient(to right, rgba(255, 255, 255, 0), rgba(255, 255, 255, 1) 100%);
|
background: linear-gradient(to right, rgba(255, 255, 255, 0), rgba(255, 255, 255, 1) 100%);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* rehype-toc plugin classes */
|
/* rehype-toc classes */
|
||||||
.toc {
|
.toc {
|
||||||
@apply hidden lg:block w-[16rem] p-8 pt-14 fixed top-16 bottom-0 right-[max(0px,50%-40rem)] overflow-y-auto border-2 border-blue-500;
|
@apply
|
||||||
|
hidden
|
||||||
|
lg:block
|
||||||
|
w-[22rem]
|
||||||
|
p-12 /* 1.5rem */
|
||||||
|
pl-12
|
||||||
|
py-24
|
||||||
|
fixed
|
||||||
|
top-16
|
||||||
|
/* bottom-0 */
|
||||||
|
right-[max(2rem,calc(50%-40rem+2rem))]
|
||||||
|
overflow-y-auto
|
||||||
}
|
}
|
||||||
|
|
||||||
.toc::before {
|
.toc::before {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
content: "On this page";
|
content: "On this page";
|
||||||
top: 1.5rem;
|
@apply text-white text-xl font-semibold top-12
|
||||||
line-height: 1.125rem;
|
|
||||||
color: white;
|
|
||||||
font-size: 1.125rem;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.toc-item {
|
.toc-item {
|
||||||
@apply leading-5;
|
@apply leading-3;
|
||||||
}
|
}
|
||||||
|
|
||||||
.toc-item-h1 {
|
.toc-item-h1 {
|
||||||
|
|
@ -71,7 +78,11 @@ body {
|
||||||
}
|
}
|
||||||
|
|
||||||
.toc-item .toc-link {
|
.toc-item .toc-link {
|
||||||
@apply text-sm text-slate-400 hover:text-white no-underline break-normal
|
@apply text-sm text-slate-400 no-underline break-normal
|
||||||
|
}
|
||||||
|
|
||||||
|
.toc-item .toc-link:not(.active) {
|
||||||
|
@apply hover:text-white
|
||||||
}
|
}
|
||||||
|
|
||||||
.toc-link.active {
|
.toc-link.active {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue