feat: custom toc component

This commit is contained in:
DIYgod 2022-10-06 01:44:14 +01:00
parent 020ca20e01
commit 039e39b3ea
No known key found for this signature in database
GPG Key ID: D159328F47A80DCA
5 changed files with 56 additions and 36 deletions

View File

@ -1,6 +1,7 @@
import clsx from "clsx"
import { useCodeCopy } from "~/hooks/useCodeCopy"
import { renderPageContent } from "~/markdown"
import { PostToc } from "~/components/site/PostToc"
export const PageContent: React.FC<{
content?: string
@ -9,20 +10,17 @@ export const PageContent: React.FC<{
}> = ({ className, content, toc }) => {
useCodeCopy()
// TODO
// const [element, setElement] = useState<ReactElement>()
// const renderMarkdown = useCallback((doc: string) => {
// setElement(renderPageContent(doc).element)
// }, [])
// useEffect(() => {
// if (content) {
// renderMarkdown(content)
// }
// }, [content, renderMarkdown])
let parsedContent: ReturnType<typeof renderPageContent> | null = null
if (content) {
parsedContent = renderPageContent(content)
}
return (
<div className={clsx("xlog-post-content", `prose`, className)}>
{content && renderPageContent(content, false, toc).element}
</div>
<>
<div className={clsx("xlog-post-content", `prose`, className)}>
{parsedContent?.element}
</div>
{toc && parsedContent?.toc && <PostToc data={parsedContent?.toc} />}
</>
)
}

View File

@ -1,7 +1,6 @@
import { Link } from "react-scroll"
export const PostLink: React.FC<any> = ({ children, ...props }) => {
console.log(props.href)
if (props.href?.startsWith("#")) {
return (
<Link

View File

@ -1,8 +1,41 @@
import { useRef, useEffect, useState } from "react"
import React, { useEffect, useState, useRef } from "react"
import type { Result as TocResult } from "mdast-util-toc"
import { Link } from "react-scroll"
function renderItems(items: TocResult["map"], activeId: string, prefix = "") {
return (
<ol className={prefix ? "pl-5" : ""}>
{items?.children?.map((item, index) => (
<li key={index}>
{item.children.map((child: any) => (
<>
{child.type === "paragraph" && child.children?.[0]?.url && (
<Link
to={decodeURI(child.children[0].url.slice(1))}
spy={true}
smooth={true}
duration={500}
offset={-20}
href={child.children[0].url}
>
{`${prefix}${index + 1}. ${
child.children[0].children?.[0]?.value
}`}
</Link>
)}
{child.type === "list" &&
renderItems(child, activeId, `${index + 1}.`)}
</>
))}
</li>
))}
</ol>
)
}
export const PostToc: React.FC<{
children: React.ReactNode
}> = ({ children }) => {
data: TocResult
}> = ({ data }) => {
const containerRef = useRef<HTMLDivElement | null>(null)
const [maxWidth, setMaxWidth] = useState(0)
@ -19,14 +52,14 @@ export const PostToc: React.FC<{
return (
<div
className="xlog-post-toc absolute left-full pl-10 h-full top-0"
className="xlog-post-toc absolute left-full pl-10 h-full top-[124px]"
style={{
maxWidth: maxWidth > 40 ? maxWidth : 0,
}}
ref={containerRef}
>
<div className="sticky top-14 text-sm leading-loose whitespace-nowrap truncate text-ellipsis">
{children}
{renderItems(data?.map, "activeId")}
</div>
</div>
)

View File

@ -47,7 +47,7 @@ export const SiteLayout: React.FC<SiteLayoutProps> = ({ children, title }) => {
<SiteHeader site={site.data} subscriptions={subscriptions.data} />
<style>{site.data?.css}</style>
<div
className={`xlog-post-id-${page.data?.id} max-w-screen-md mx-auto px-5 pt-12`}
className={`xlog-post-id-${page.data?.id} max-w-screen-md mx-auto px-5 pt-12 relative`}
>
{children}
</div>

View File

@ -24,9 +24,7 @@ import { remarkYoutube } from "./remark-youtube"
import sanitizeScheme from "./sanitize-schema"
import rehypeSlug from "rehype-slug"
import rehypeAutolinkHeadings from "rehype-autolink-headings"
import { toc } from "mdast-util-toc"
import { PostToc } from "~/components/site/PostToc"
import { PostLink } from "~/components/site/PostLink"
import { toc, Result as TocResult } from "mdast-util-toc"
import { Element } from "react-scroll"
export type MarkdownEnv = {
@ -34,6 +32,7 @@ export type MarkdownEnv = {
frontMatter: Record<string, any>
__internal: Record<string, any>
cover: string
toc: TocResult | null
}
export type Rendered = {
@ -42,6 +41,7 @@ export type Rendered = {
excerpt: string
frontMatter: Record<string, any>
cover: string
toc: TocResult | null
}
refractor.alias("html", ["svelte", "vue"])
@ -58,6 +58,7 @@ export const renderPageContent = (
__internal: {},
frontMatter: {},
cover: "",
toc: null,
}
const result = unified()
@ -75,17 +76,7 @@ export const renderPageContent = (
}
})
.use(() => (tree) => {
if (enableToc) {
const result = toc(tree, { tight: true, ordered: true })
if (result.map) {
tree.children = [
...tree.children,
{ type: "html", value: "<toc>" },
result.map,
{ type: "html", value: "</toc>" },
]
}
}
env.toc = toc(tree, { tight: true, ordered: true })
})
.use(remarkGfm, {})
.use(remarkExcerpt, { env })
@ -137,8 +128,6 @@ export const renderPageContent = (
createElement: createElement,
components: {
img: Image,
a: PostLink,
toc: PostToc,
anchor: Element,
} as any,
})
@ -152,5 +141,6 @@ export const renderPageContent = (
excerpt: env.excerpt,
frontMatter: env.frontMatter,
cover: env.cover,
toc: env.toc,
}
}