[components/toc][f]: following current h on scroll
This commit is contained in:
parent
dba48484b9
commit
2ff44763df
|
|
@ -0,0 +1,25 @@
|
||||||
|
import React, { useEffect } from 'react';
|
||||||
|
|
||||||
|
export const Heading = ({ level, activeHeading, setActiveHeading }) => (props) => {
|
||||||
|
console.log(activeHeading, setActiveHeading)
|
||||||
|
useEffect(() => {
|
||||||
|
const observer = new IntersectionObserver(
|
||||||
|
(entries) => {
|
||||||
|
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 })
|
||||||
|
}
|
||||||
|
|
@ -1,28 +1,71 @@
|
||||||
import Head from 'next/head'
|
import { NextSeo } from "next-seo";
|
||||||
import dynamic from 'next/dynamic'
|
import ReactPlayer from "react-player/lazy";
|
||||||
import { NextSeo } from 'next-seo'
|
import LiteYouTubeEmbed from "react-lite-youtube-embed";
|
||||||
import siteConfig from "../config/siteConfig"
|
import { useState, useEffect } from "react";
|
||||||
import LiteYouTubeEmbed from "react-lite-youtube-embed"
|
|
||||||
import { YOUTUBE_REGEX } from "../lib/constants"
|
|
||||||
|
|
||||||
const Anchor = dynamic(() => import('./Anchor').then(module => module.Anchor), {
|
import siteConfig from "../config/siteConfig";
|
||||||
ssr: false
|
import { YOUTUBE_REGEX } from "../lib/constants";
|
||||||
})
|
import getMDXComponents from "./_getMDXComponents";
|
||||||
|
import { Paragraph } from "./Paragraph";
|
||||||
|
import { Anchor } from "./Anchor";
|
||||||
|
|
||||||
const Paragraph = dynamic(() => import("./Paragraph").then(mod => mod.Paragraph))
|
// const Anchor = dynamic(() => import('./Anchor').then(module => module.Anchor), {
|
||||||
|
// ssr: false
|
||||||
|
// })
|
||||||
|
|
||||||
const components = {
|
// const Paragraph = dynamic(() => import("./Paragraph").then(mod => mod.Paragraph))
|
||||||
Head,
|
|
||||||
p: Paragraph,
|
|
||||||
a: Anchor
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function MdxPage({ children }) {
|
// import { Toc } from './Toc'
|
||||||
const { Component, frontmatter: {
|
|
||||||
title, description, date, keywords, youtube, podcast, image, _raw
|
|
||||||
}} = children
|
|
||||||
|
|
||||||
let youtubeThumnbnail
|
export default function MdxPage({ children, editUrl }) {
|
||||||
|
const [activeHeading, setActiveHeading] = useState("");
|
||||||
|
|
||||||
|
const components = getMDXComponents({
|
||||||
|
params: {
|
||||||
|
h: {
|
||||||
|
activeHeading,
|
||||||
|
setActiveHeading,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (activeHeading) {
|
||||||
|
const tocLink = document.querySelector(
|
||||||
|
`.toc-link[href="#${activeHeading}"]`
|
||||||
|
);
|
||||||
|
tocLink.classList.add("active");
|
||||||
|
|
||||||
|
// setTimeout to fix scrolling behavior
|
||||||
|
// fix switching on-off when two headings are observed
|
||||||
|
// router push
|
||||||
|
}
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
if (activeHeading) {
|
||||||
|
const tocLink = document.querySelector(
|
||||||
|
`.toc-link[href="#${activeHeading}"]`
|
||||||
|
);
|
||||||
|
tocLink.classList.remove("active");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}, [activeHeading]);
|
||||||
|
|
||||||
|
const {
|
||||||
|
Component,
|
||||||
|
frontmatter: {
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
date,
|
||||||
|
keywords,
|
||||||
|
youtube,
|
||||||
|
podcast,
|
||||||
|
image,
|
||||||
|
_raw,
|
||||||
|
},
|
||||||
|
} = children;
|
||||||
|
|
||||||
|
let youtubeThumnbnail;
|
||||||
|
|
||||||
const youtubeId =
|
const youtubeId =
|
||||||
youtube && YOUTUBE_REGEX.test(youtube) && youtube.split(/^|=|\//).pop();
|
youtube && YOUTUBE_REGEX.test(youtube) && youtube.split(/^|=|\//).pop();
|
||||||
|
|
@ -47,12 +90,14 @@ export default function MdxPage({ children }) {
|
||||||
const SeoTitle = title ?? titleFromUrl;
|
const SeoTitle = title ?? titleFromUrl;
|
||||||
const imageUrl = image
|
const imageUrl = image
|
||||||
? siteConfig.url + image
|
? siteConfig.url + image
|
||||||
: youtubeThumnbnail ? youtubeThumnbnail : null
|
: youtubeThumnbnail
|
||||||
|
? youtubeThumnbnail
|
||||||
|
: null;
|
||||||
|
|
||||||
// enable editing content only for claims, concepts, and guide for now
|
// enable editing content only for claims, concepts, and guide for now
|
||||||
const editUrl = ['claims', 'concepts', 'guide'].includes(_raw.sourceFileDir)
|
const editUrl = ["claims", "concepts", "guide"].includes(_raw.sourceFileDir)
|
||||||
? siteConfig.repoRoot + siteConfig.repoEditPath + _raw.sourceFilePath
|
? siteConfig.repoRoot + siteConfig.repoEditPath + _raw.sourceFilePath
|
||||||
: null
|
: null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
|
@ -66,25 +111,26 @@ export default function MdxPage({ children }) {
|
||||||
url: `${siteConfig.url}/${_raw.flattenedPath}`,
|
url: `${siteConfig.url}/${_raw.flattenedPath}`,
|
||||||
type: "article",
|
type: "article",
|
||||||
article: {
|
article: {
|
||||||
tags: keywords ? keywords.split(",") : []
|
tags: keywords ? keywords.split(",") : [],
|
||||||
},
|
},
|
||||||
images: imageUrl
|
images: imageUrl
|
||||||
? ([
|
? [
|
||||||
{
|
{
|
||||||
url: imageUrl,
|
url: imageUrl,
|
||||||
width: 1200,
|
width: 1200,
|
||||||
height: 627,
|
height: 627,
|
||||||
alt: title,
|
alt: title,
|
||||||
type: "image/png"
|
type: "image/png",
|
||||||
},
|
},
|
||||||
])
|
]
|
||||||
: siteConfig.nextSeo.openGraph.images,
|
: siteConfig.nextSeo.openGraph.images,
|
||||||
}}
|
}}
|
||||||
additionalMetaTags={[
|
additionalMetaTags={[
|
||||||
{ 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-[14rem] 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">
|
||||||
<header>
|
<header>
|
||||||
<div className="mb-6">
|
<div className="mb-6">
|
||||||
{title && <h1 className="mb-0">{title}</h1>}
|
{title && <h1 className="mb-0">{title}</h1>}
|
||||||
|
|
@ -93,17 +139,18 @@ export default function MdxPage({ children }) {
|
||||||
on {date}
|
on {date}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
{description && (
|
{description && <p className="">{description}</p>}
|
||||||
<p className="">{description}</p>
|
{youtubeId && <LiteYouTubeEmbed id={youtubeId} />}
|
||||||
)}
|
|
||||||
{youtubeId && (
|
|
||||||
<LiteYouTubeEmbed id={youtubeId} />
|
|
||||||
)}
|
|
||||||
{podcast && (
|
{podcast && (
|
||||||
<div className="pt-4">
|
<div className="pt-4">
|
||||||
<ul className="list-disc">
|
<ul className="list-disc">
|
||||||
<li>
|
<li>
|
||||||
<a className="flex items-center" target="_blank" rel="noopener" href={podcast}>
|
<a
|
||||||
|
className="flex items-center"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener"
|
||||||
|
href={podcast}
|
||||||
|
>
|
||||||
<div className="w-4 mr-2">
|
<div className="w-4 mr-2">
|
||||||
<PodcastIcon />
|
<PodcastIcon />
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -120,28 +167,33 @@ export default function MdxPage({ children }) {
|
||||||
<Component components={components} />
|
<Component components={components} />
|
||||||
</div>
|
</div>
|
||||||
{editUrl && (
|
{editUrl && (
|
||||||
<div className='mt-12 mb-6'>
|
<div className="mt-12 mb-6">
|
||||||
<a className="flex no-underline font-semibold text-yellow-li" href={editUrl} target="_blank">
|
<a
|
||||||
|
className="flex no-underline font-semibold text-yellow-li"
|
||||||
|
href={editUrl}
|
||||||
|
target="_blank"
|
||||||
|
>
|
||||||
Edit this page
|
Edit this page
|
||||||
<span className="mx-1">
|
<span className="mx-1">
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" className="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth="2">
|
<svg
|
||||||
<path strokeLinecap="round" strokeLinejoin="round" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" />
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
className="h-6 w-6"
|
||||||
|
fill="none"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
stroke="currentColor"
|
||||||
|
strokeWidth="2"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14"
|
||||||
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
</span>
|
</span>
|
||||||
</a>
|
</a>
|
||||||
</div>)}
|
</div>
|
||||||
|
)}
|
||||||
</main>
|
</main>
|
||||||
<div className="hidden lg:block w-[16rem] px-8 fixed top-16 right-[max(0px,50%-40rem)] bottom-0 overflow-y-auto border-2 border-blue-500">
|
|
||||||
<nav>
|
|
||||||
<ul>
|
|
||||||
<li>Heading 1</li>
|
|
||||||
<li>Heading 2</li>
|
|
||||||
<li>Heading 3</li>
|
|
||||||
<li>Heading 4</li>
|
|
||||||
<li>Heading 5</li>
|
|
||||||
</ul>
|
|
||||||
</nav>
|
|
||||||
</div>
|
|
||||||
</article>
|
</article>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
import Head from 'next/head'
|
||||||
|
import { Paragraph } from './Paragraph'
|
||||||
|
import { Anchor } from './Anchor'
|
||||||
|
import { Heading } from './Heading'
|
||||||
|
|
||||||
|
|
||||||
|
const getMDXComponents = ({ params: { h } }) => ({
|
||||||
|
Head,
|
||||||
|
p: Paragraph,
|
||||||
|
a: Anchor,
|
||||||
|
h1: Heading({ level: 1, ...h }),
|
||||||
|
h2: Heading({ level: 2, ...h }),
|
||||||
|
h3: Heading({ level: 3, ...h }),
|
||||||
|
h4: Heading({ level: 4, ...h }),
|
||||||
|
h5: Heading({ level: 5, ...h }),
|
||||||
|
h6: Heading({ level: 6, ...h }),
|
||||||
|
});
|
||||||
|
|
||||||
|
export default getMDXComponents;
|
||||||
|
|
@ -11,10 +11,17 @@
|
||||||
"@floating-ui/react-dom-interactions": "^0.6.0",
|
"@floating-ui/react-dom-interactions": "^0.6.0",
|
||||||
"@headlessui/react": "^1.4.1",
|
"@headlessui/react": "^1.4.1",
|
||||||
"@heroicons/react": "^1.0.4",
|
"@heroicons/react": "^1.0.4",
|
||||||
|
"@jsdevtools/rehype-toc": "^3.0.2",
|
||||||
|
"@mdx-js/loader": "^2.0.0",
|
||||||
|
"@mdx-js/react": "^2.0.0",
|
||||||
|
"@next/mdx": "^12.1.0",
|
||||||
|
"@silvenon/remark-smartypants": "^1.0.0",
|
||||||
"@tailwindcss/typography": "^0.5.2",
|
"@tailwindcss/typography": "^0.5.2",
|
||||||
"contentlayer": "^0.1.2",
|
"contentlayer": "^0.1.2",
|
||||||
"framer-motion": "^6.3.3",
|
"framer-motion": "^6.3.3",
|
||||||
"gray-matter": "^4.0.3",
|
"gray-matter": "^4.0.3",
|
||||||
|
"hast-util-to-string": "^2.0.0",
|
||||||
|
"mdast-util-to-hast": "^12.1.1",
|
||||||
"next": "^12.1.0",
|
"next": "^12.1.0",
|
||||||
"next-contentlayer": "^0.1.2",
|
"next-contentlayer": "^0.1.2",
|
||||||
"next-seo": "^4.28.1",
|
"next-seo": "^4.28.1",
|
||||||
|
|
@ -32,6 +39,7 @@
|
||||||
"prettier": "^2.6.2",
|
"prettier": "^2.6.2",
|
||||||
"rehype-autolink-headings": "^6.1.1",
|
"rehype-autolink-headings": "^6.1.1",
|
||||||
"rehype-slug": "^5.0.1",
|
"rehype-slug": "^5.0.1",
|
||||||
|
"rehype-stringify": "^9.0.3",
|
||||||
"remark-gfm": "^3.0.0",
|
"remark-gfm": "^3.0.0",
|
||||||
"remark-parse": "^10.0.1",
|
"remark-parse": "^10.0.1",
|
||||||
"remark-wiki-link-plus": "^1.0.0",
|
"remark-wiki-link-plus": "^1.0.0",
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue