[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 dynamic from 'next/dynamic'
|
||||
import { NextSeo } from 'next-seo'
|
||||
import siteConfig from "../config/siteConfig"
|
||||
import LiteYouTubeEmbed from "react-lite-youtube-embed"
|
||||
import { YOUTUBE_REGEX } from "../lib/constants"
|
||||
import { NextSeo } from "next-seo";
|
||||
import ReactPlayer from "react-player/lazy";
|
||||
import LiteYouTubeEmbed from "react-lite-youtube-embed";
|
||||
import { useState, useEffect } from "react";
|
||||
|
||||
const Anchor = dynamic(() => import('./Anchor').then(module => module.Anchor), {
|
||||
ssr: false
|
||||
})
|
||||
import siteConfig from "../config/siteConfig";
|
||||
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 = {
|
||||
Head,
|
||||
p: Paragraph,
|
||||
a: Anchor
|
||||
}
|
||||
// const Paragraph = dynamic(() => import("./Paragraph").then(mod => mod.Paragraph))
|
||||
|
||||
export default function MdxPage({ children }) {
|
||||
const { Component, frontmatter: {
|
||||
title, description, date, keywords, youtube, podcast, image, _raw
|
||||
}} = children
|
||||
// import { Toc } from './Toc'
|
||||
|
||||
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 =
|
||||
youtube && YOUTUBE_REGEX.test(youtube) && youtube.split(/^|=|\//).pop();
|
||||
|
|
@ -47,12 +90,14 @@ export default function MdxPage({ children }) {
|
|||
const SeoTitle = title ?? titleFromUrl;
|
||||
const imageUrl = image
|
||||
? siteConfig.url + image
|
||||
: youtubeThumnbnail ? youtubeThumnbnail : null
|
||||
: youtubeThumnbnail
|
||||
? youtubeThumnbnail
|
||||
: null;
|
||||
|
||||
// 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
|
||||
: null
|
||||
: null;
|
||||
|
||||
return (
|
||||
<>
|
||||
|
|
@ -66,25 +111,26 @@ export default function MdxPage({ children }) {
|
|||
url: `${siteConfig.url}/${_raw.flattenedPath}`,
|
||||
type: "article",
|
||||
article: {
|
||||
tags: keywords ? keywords.split(",") : []
|
||||
tags: keywords ? keywords.split(",") : [],
|
||||
},
|
||||
images: imageUrl
|
||||
? ([
|
||||
? [
|
||||
{
|
||||
url: imageUrl,
|
||||
width: 1200,
|
||||
height: 627,
|
||||
alt: title,
|
||||
type: "image/png"
|
||||
type: "image/png",
|
||||
},
|
||||
])
|
||||
]
|
||||
: siteConfig.nextSeo.openGraph.images,
|
||||
}}
|
||||
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>
|
||||
<div className="mb-6">
|
||||
{title && <h1 className="mb-0">{title}</h1>}
|
||||
|
|
@ -93,17 +139,18 @@ export default function MdxPage({ children }) {
|
|||
on {date}
|
||||
</p>
|
||||
)}
|
||||
{description && (
|
||||
<p className="">{description}</p>
|
||||
)}
|
||||
{youtubeId && (
|
||||
<LiteYouTubeEmbed id={youtubeId} />
|
||||
)}
|
||||
{description && <p className="">{description}</p>}
|
||||
{youtubeId && <LiteYouTubeEmbed id={youtubeId} />}
|
||||
{podcast && (
|
||||
<div className="pt-4">
|
||||
<ul className="list-disc">
|
||||
<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">
|
||||
<PodcastIcon />
|
||||
</div>
|
||||
|
|
@ -120,28 +167,33 @@ export default function MdxPage({ children }) {
|
|||
<Component components={components} />
|
||||
</div>
|
||||
{editUrl && (
|
||||
<div className='mt-12 mb-6'>
|
||||
<a className="flex no-underline font-semibold text-yellow-li" href={editUrl} target="_blank">
|
||||
<div className="mt-12 mb-6">
|
||||
<a
|
||||
className="flex no-underline font-semibold text-yellow-li"
|
||||
href={editUrl}
|
||||
target="_blank"
|
||||
>
|
||||
Edit this page
|
||||
<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">
|
||||
<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
|
||||
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>
|
||||
</span>
|
||||
</a>
|
||||
</div>)}
|
||||
</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>
|
||||
)}
|
||||
</main>
|
||||
</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",
|
||||
"@headlessui/react": "^1.4.1",
|
||||
"@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",
|
||||
"contentlayer": "^0.1.2",
|
||||
"framer-motion": "^6.3.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-contentlayer": "^0.1.2",
|
||||
"next-seo": "^4.28.1",
|
||||
|
|
@ -32,6 +39,7 @@
|
|||
"prettier": "^2.6.2",
|
||||
"rehype-autolink-headings": "^6.1.1",
|
||||
"rehype-slug": "^5.0.1",
|
||||
"rehype-stringify": "^9.0.3",
|
||||
"remark-gfm": "^3.0.0",
|
||||
"remark-parse": "^10.0.1",
|
||||
"remark-wiki-link-plus": "^1.0.0",
|
||||
|
|
|
|||
Loading…
Reference in New Issue