diff --git a/package-lock.json b/package-lock.json index a624e63..18d54f3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,12 +14,12 @@ "@fortawesome/react-fontawesome": "^0.1.14", "@graphql-tools/merge": "^8.2.1", "@graphql-tools/schema": "^8.2.0", + "@graphql-tools/utils": "^8.6.5", "@next/bundle-analyzer": "^12.0.3", "@reach/listbox": "^0.16.2", "@reach/menu-button": "^0.16.2", "common-tags": "^1.8.0", "framer-motion": "^6.2.1", - "github-slugger": "^1.4.0", "graphql": "^15.6.1", "graphql-tag": "^2.12.6", "knex": "^0.95.11", @@ -474,9 +474,9 @@ } }, "node_modules/@graphql-tools/utils": { - "version": "8.5.3", - "resolved": "https://registry.npmjs.org/@graphql-tools/utils/-/utils-8.5.3.tgz", - "integrity": "sha512-HDNGWFVa8QQkoQB0H1lftvaO1X5xUaUDk1zr1qDe0xN1NL0E/CrQdJ5UKLqOvH4hkqVUPxQsyOoAZFkaH6rLHg==", + "version": "8.6.5", + "resolved": "https://registry.npmjs.org/@graphql-tools/utils/-/utils-8.6.5.tgz", + "integrity": "sha512-mjOtaWiS2WIqRz/cq5gaeM3sVrllcu2xbtHROw1su1v3xWa3D3dKgn8Lrl7+tvWs5WUVySsBss/VZ3WdoPkCrA==", "dependencies": { "tslib": "~2.3.0" }, @@ -2332,11 +2332,6 @@ "resolved": "https://registry.npmjs.org/getopts/-/getopts-2.2.5.tgz", "integrity": "sha512-9jb7AW5p3in+IiJWhQiZmmwkpLaR/ccTWdWQCtZM66HJcHHLegowh4q4tSD7gouUyeNvFWRavfK9GXosQHDpFA==" }, - "node_modules/github-slugger": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/github-slugger/-/github-slugger-1.4.0.tgz", - "integrity": "sha512-w0dzqw/nt51xMVmlaV1+JRzN+oCa1KfcgGEWhxUG16wbdA+Xnt/yoFO8Z8x/V82ZcZ0wy6ln9QDup5avbhiDhQ==" - }, "node_modules/glob": { "version": "7.1.7", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz", @@ -5116,9 +5111,9 @@ } }, "@graphql-tools/utils": { - "version": "8.5.3", - "resolved": "https://registry.npmjs.org/@graphql-tools/utils/-/utils-8.5.3.tgz", - "integrity": "sha512-HDNGWFVa8QQkoQB0H1lftvaO1X5xUaUDk1zr1qDe0xN1NL0E/CrQdJ5UKLqOvH4hkqVUPxQsyOoAZFkaH6rLHg==", + "version": "8.6.5", + "resolved": "https://registry.npmjs.org/@graphql-tools/utils/-/utils-8.6.5.tgz", + "integrity": "sha512-mjOtaWiS2WIqRz/cq5gaeM3sVrllcu2xbtHROw1su1v3xWa3D3dKgn8Lrl7+tvWs5WUVySsBss/VZ3WdoPkCrA==", "requires": { "tslib": "~2.3.0" } @@ -6469,11 +6464,6 @@ "resolved": "https://registry.npmjs.org/getopts/-/getopts-2.2.5.tgz", "integrity": "sha512-9jb7AW5p3in+IiJWhQiZmmwkpLaR/ccTWdWQCtZM66HJcHHLegowh4q4tSD7gouUyeNvFWRavfK9GXosQHDpFA==" }, - "github-slugger": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/github-slugger/-/github-slugger-1.4.0.tgz", - "integrity": "sha512-w0dzqw/nt51xMVmlaV1+JRzN+oCa1KfcgGEWhxUG16wbdA+Xnt/yoFO8Z8x/V82ZcZ0wy6ln9QDup5avbhiDhQ==" - }, "glob": { "version": "7.1.7", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz", diff --git a/package.json b/package.json index 0cbbf96..228389f 100644 --- a/package.json +++ b/package.json @@ -15,12 +15,12 @@ "@fortawesome/react-fontawesome": "^0.1.14", "@graphql-tools/merge": "^8.2.1", "@graphql-tools/schema": "^8.2.0", + "@graphql-tools/utils": "^8.6.5", "@next/bundle-analyzer": "^12.0.3", "@reach/listbox": "^0.16.2", "@reach/menu-button": "^0.16.2", "common-tags": "^1.8.0", "framer-motion": "^6.2.1", - "github-slugger": "^1.4.0", "graphql": "^15.6.1", "graphql-tag": "^2.12.6", "knex": "^0.95.11", diff --git a/src/components/footer/Footer.js b/src/components/footer/Footer.js index 33b837d..e033957 100644 --- a/src/components/footer/Footer.js +++ b/src/components/footer/Footer.js @@ -1,3 +1,4 @@ +import Link from "next/link"; import { Column, Row, Solid } from "components/box"; import { Container } from "components/container"; import { Text } from "components/text"; @@ -6,6 +7,7 @@ import { faDiscord, faGithub, faReddit, faTwitter } from "@fortawesome/free-bran import { Button } from "components/button"; import styled from "styled-components"; import theme from "theme"; +import { forwardRef } from "react"; const StyledFooter = styled(Solid)` margin-top: auto; @@ -46,12 +48,16 @@ export function Footer() { Transparency - - Donate - - - FAQ - + + + Donate + + + + + FAQ + + @@ -91,18 +97,18 @@ export function Footer() { ); } -function FooterLink({ children, ...props }) { +const FooterLink = forwardRef(function FooterLink({ children, ...props }, ref) { return ( - + {children} ); -} +}); -function FooterTextLink({ children, ...props }) { +const FooterTextLink = forwardRef(function FooterTextLink({ children, ...props }, ref) { return ( - + {children} ); -} +}); diff --git a/src/pages/page/[...pageSlug].js b/src/pages/page/[...pageSlug].js index 6d80a88..13d075c 100644 --- a/src/pages/page/[...pageSlug].js +++ b/src/pages/page/[...pageSlug].js @@ -7,7 +7,6 @@ import { Text } from "components/text"; import { createElement, Fragment, useEffect, useState } from "react"; import rehypeParse from "rehype-parse"; import styled from "styled-components"; -import { slug } from "github-slugger"; import { motion } from "framer-motion"; import theme from "theme"; import { SEO } from "components/seo"; @@ -99,6 +98,7 @@ const StyledMarkdown = styled.div` } & pre { + margin-bottom: 16px; overflow-x: auto; } @@ -177,7 +177,7 @@ export default function DocumentPage({ page }) { } function TableOfContents({ headings }) { - const [currentHeading, setCurrentHeading] = useState(); + const [currentSlug, setCurrentSlug] = useState(); useEffect(() => { function onScroll() { @@ -193,7 +193,7 @@ function TableOfContents({ headings }) { break; } } - setCurrentHeading(currentHeading?.id); + setCurrentSlug(currentHeading?.id); } window.addEventListener("scroll", onScroll); @@ -203,12 +203,12 @@ function TableOfContents({ headings }) { return ( - {headings.map(({ text }) => ( + {headings.map(({ text, slug }) => (
  • - {slug(text) === currentHeading && ( + {slug === currentSlug && ( )} - {text} + {text}
  • ))}
    diff --git a/src/utils/markdownToHtml.js b/src/utils/markdownToHtml.js index bf88d1a..6e5d95e 100644 --- a/src/utils/markdownToHtml.js +++ b/src/utils/markdownToHtml.js @@ -6,16 +6,32 @@ import "prismjs/components/prism-json"; export default function markdownToHtml(markdown) { const headings = []; - const html = marked.parse(markdown, { + const slugger = new marked.Slugger(); + + const html = marked(markdown, { highlight(code, lang) { if (lang) { - return Prism.highlight(code, Prism.languages[lang], lang); + if (lang in Prism.languages) { + return Prism.highlight(code, Prism.languages[lang], lang); + } + return `[!] Unknown syntax highlighting language: ${lang}\n${code}`; } return code; }, walkTokens(token) { - if (token.type === "heading" && token.depth === 2) { - headings.push({ text: token.text, level: token.depth }); + if (token.type === "heading") { + // To remove links and other markdown entities from the text we join only the text parts together. + const text = joinTextDeep(token.tokens); + // Slugger keeps track of seen slugs, so we have to generate a slug for every heading. + // This way links will work in the table of contents later. + const slug = slugger.slug(text); + if (token.depth === 2) { + headings.push({ + text: text, + level: token.depth, + slug + }); + } } } }); @@ -25,3 +41,7 @@ export default function markdownToHtml(markdown) { headings }; } + +function joinTextDeep(tokens) { + return tokens.map((token) => token.tokens ? joinTextDeep(token.tokens) : token.text).join(""); +}