upstream #1
| @@ -31,7 +31,7 @@ I wanted it to be nearly as feature-rich as popular blogging templates like [bea | ||||
|  | ||||
| - Easy styling customization with [Tailwind 2.0](https://blog.tailwindcss.com/tailwindcss-v2) and primary color attribute | ||||
| - Near perfect lighthouse score - [Lighthouse report](https://www.webpagetest.org/result/210111_DiC1_08f3670c3430bf4a9b76fc3b927716c5/) | ||||
| - Lightweight, 38kB first load JS, uses Preact in production build | ||||
| - Lightweight, 39kB first load JS, uses Preact in production build | ||||
| - Mobile-friendly view | ||||
| - Light and dark theme | ||||
| - Supports [plausible](https://plausible.io/), [simple analytics](https://simpleanalytics.com/) and google analytics | ||||
| @@ -43,6 +43,7 @@ I wanted it to be nearly as feature-rich as popular blogging templates like [bea | ||||
| - Support for tags - each unique tag will be its own page | ||||
| - Support for multiple authors | ||||
| - Blog templates | ||||
| - TOC component | ||||
| - Support for nested routing of blog posts | ||||
| - Supports [giscus](https://github.com/laymonage/giscus), [utterances](https://github.com/utterance/utterances) or disqus | ||||
| - Projects page | ||||
|   | ||||
| @@ -3,10 +3,12 @@ import { useMemo } from 'react' | ||||
| import { getMDXComponent } from 'mdx-bundler/client' | ||||
| import Image from './Image' | ||||
| import CustomLink from './Link' | ||||
| import TOCInline from './TOCInline' | ||||
| import Pre from './Pre' | ||||
|  | ||||
| export const MDXComponents = { | ||||
|   Image, | ||||
|   TOCInline, | ||||
|   a: CustomLink, | ||||
|   pre: Pre, | ||||
|   wrapper: ({ components, layout, ...rest }) => { | ||||
|   | ||||
							
								
								
									
										64
									
								
								components/TOCInline.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										64
									
								
								components/TOCInline.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,64 @@ | ||||
| /** | ||||
|  * @typedef TocHeading | ||||
|  * @prop {string} value | ||||
|  * @prop {number} depth | ||||
|  * @prop {string} url | ||||
|  */ | ||||
|  | ||||
| /** | ||||
|  * Generates an inline table of contents | ||||
|  * Exclude titles matching this string (new RegExp('^(' + string + ')$', 'i')). | ||||
|  * If an array is passed the array gets joined with a pipe (new RegExp('^(' + array.join('|') + ')$', 'i')). | ||||
|  * | ||||
|  * @param {{ | ||||
|  *  toc: TocHeading[], | ||||
|  *  indentDepth?: number, | ||||
|  *  fromHeading?: number, | ||||
|  *  toHeading?: number, | ||||
|  *  asDisclosure?: boolean, | ||||
|  *  exclude?: string|string[] | ||||
|  * }} props | ||||
|  * | ||||
|  */ | ||||
| const TOCInline = ({ | ||||
|   toc, | ||||
|   indentDepth = 3, | ||||
|   fromHeading = 1, | ||||
|   toHeading = 6, | ||||
|   asDisclosure = false, | ||||
|   exclude = '', | ||||
| }) => { | ||||
|   const re = Array.isArray(exclude) | ||||
|     ? new RegExp('^(' + exclude.join('|') + ')$', 'i') | ||||
|     : new RegExp('^(' + exclude + ')$', 'i') | ||||
|  | ||||
|   const filteredToc = toc.filter( | ||||
|     (heading) => | ||||
|       heading.depth >= fromHeading && heading.depth <= toHeading ** !re.test(heading.value) | ||||
|   ) | ||||
|  | ||||
|   const tocList = ( | ||||
|     <ul> | ||||
|       {filteredToc.map((heading) => ( | ||||
|         <li key={heading.value} className={`${heading.depth >= indentDepth && 'ml-6'}`}> | ||||
|           <a href={heading.url}>{heading.value}</a> | ||||
|         </li> | ||||
|       ))} | ||||
|     </ul> | ||||
|   ) | ||||
|  | ||||
|   return ( | ||||
|     <> | ||||
|       {asDisclosure ? ( | ||||
|         <details open> | ||||
|           <summary className="pt-2 pb-2 ml-6 text-xl font-bold">Table of Contents</summary> | ||||
|           <div className="ml-6">{tocList}</div> | ||||
|         </details> | ||||
|       ) : ( | ||||
|         tocList | ||||
|       )} | ||||
|     </> | ||||
|   ) | ||||
| } | ||||
|  | ||||
| export default TOCInline | ||||
| @@ -1,7 +1,7 @@ | ||||
| --- | ||||
| title: 'Introducing Tailwind Nextjs Starter Blog' | ||||
| date: '2021-01-12' | ||||
| lastmod: '2021-08-02' | ||||
| lastmod: '2021-08-07' | ||||
| tags: ['next-js', 'tailwind', 'guide'] | ||||
| draft: false | ||||
| summary: 'Looking for a performant, out of the box template, with all the best in web technology to support your blogging needs? Checkout the Tailwind Nextjs Starter Blog template.' | ||||
| @@ -36,7 +36,7 @@ I wanted it to be nearly as feature-rich as popular blogging templates like [bea | ||||
|  | ||||
| - Easy styling customization with [Tailwind 2.0](https://blog.tailwindcss.com/tailwindcss-v2) and primary color attribute | ||||
| - Near perfect lighthouse score - [Lighthouse report](https://www.webpagetest.org/result/210111_DiC1_08f3670c3430bf4a9b76fc3b927716c5/) | ||||
| - Lightweight, 38kB first load JS, uses Preact in production build | ||||
| - Lightweight, 39kB first load JS, uses Preact in production build | ||||
| - Mobile-friendly view | ||||
| - Light and dark theme | ||||
| - Supports [plausible](https://plausible.io/), [simple analytics](https://simpleanalytics.com/) and google analytics | ||||
| @@ -48,6 +48,7 @@ I wanted it to be nearly as feature-rich as popular blogging templates like [bea | ||||
| - Support for tags - each unique tag will be its own page | ||||
| - Support for multiple authors | ||||
| - Blog templates | ||||
| - TOC component | ||||
| - Support for nested routing of blog posts | ||||
| - Supports [giscus](https://github.com/laymonage/giscus), [utterances](https://github.com/utterance/utterances) or disqus | ||||
| - Projects page | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| --- | ||||
| title: 'New features in v1' | ||||
| date: 2021-07-28T15:32:14Z | ||||
| date: 2021-08-07T15:32:14Z | ||||
| tags: ['next-js', 'tailwind', 'guide'] | ||||
| draft: false | ||||
| summary: 'An overview of the new features released in v1 - code block copy, multiple authors, frontmatter layout and more' | ||||
| @@ -11,16 +11,9 @@ layout: PostSimple | ||||
|  | ||||
| A post on the new features introduced in v1.0. New features: | ||||
|  | ||||
| - [Theme colors](#theme-colors) | ||||
| - [Xdm MDX compiler](#xdm-mdx-compiler) | ||||
| - [Layouts](#layouts) | ||||
| - [Multiple authors](#multiple-authors) | ||||
| - [Analytics](#analytics) | ||||
| - [Blog comments system](#blog-comments-system) | ||||
| - [Copy button for code blocks](#copy-button-for-code-blocks) | ||||
| - [Line highlighting and line numbers](#line-highlighting-and-line-numbers) | ||||
| <TOCInline toc={props.toc} exclude="Overview" toHeading={2} /> | ||||
|  | ||||
| First load JS decreased from 43kB to 38kB despite all the new features added! | ||||
| First load JS decreased from 43kB to 39kB despite all the new features added! | ||||
|  | ||||
| See [upgrade guide](#upgrade-guide) below if you are migrating from v0 version of the template. | ||||
|  | ||||
| @@ -75,6 +68,30 @@ Components which require external image loaders would require additional esbuild | ||||
| Components which are dependent on global application state on lifecycle like the Nextjs `Link` component would also not work with this setup as each mdx file is built indepedently. | ||||
| For such cases, it is better to use component substitution. | ||||
|  | ||||
| ## Table of contents component | ||||
|  | ||||
| Inspired by [Docusaurus](https://docusaurus.io/docs/next/markdown-features/inline-toc) and Gatsby's [gatsby-remark-table-of-contents](https://www.gatsbyjs.com/plugins/gatsby-remark-table-of-contents/), | ||||
| the `toc` variable containing all the top level headings of the document is passed to the MDX file and can be styled accordingly. | ||||
| To make generating a TOC simple, you can use the existing `TOCInline` component. | ||||
|  | ||||
| For example, the TOC in this post was generated with the following code: | ||||
|  | ||||
| ```js | ||||
| <TOCInline toc={props.toc} exclude="Overview" toHeading={2} /> | ||||
| ``` | ||||
|  | ||||
| You can customise the headings to be generated by configuring the `fromHeading` and `toHeading` props as well as exclude particular headings | ||||
| by passing a string or a string array. By default, all headings that are of depth 3 or smaller are indented. This can be configured by changing the `indentDepth` property. | ||||
| A `asDisclosure` prop can be used to render the TOC within an expandable disclosure element. | ||||
|  | ||||
| Here's the full TOC rendered in a disclosure element. | ||||
|  | ||||
| ```js | ||||
| <TOCInline toc={props.toc} asDisclosure /> | ||||
| ``` | ||||
|  | ||||
| <TOCInline toc={props.toc} asDisclosure /> | ||||
|  | ||||
| ## Layouts | ||||
|  | ||||
| You can map mdx blog content to layout components by configuring the frontmatter field. For example, this post is written with the new `PostSimple` layout! | ||||
|   | ||||
| @@ -5,6 +5,7 @@ import path from 'path' | ||||
| import readingTime from 'reading-time' | ||||
| import visit from 'unist-util-visit' | ||||
| import codeTitles from './remark-code-title' | ||||
| import remarkTocHeadings from './remark-toc-headings' | ||||
| import imgToJsx from './img-to-jsx' | ||||
| import getAllFilesRecursively from './utils/files' | ||||
|  | ||||
| @@ -66,6 +67,8 @@ export async function getFileBySlug(type, slug) { | ||||
|     ) | ||||
|   } | ||||
|  | ||||
|   let toc = [] | ||||
|  | ||||
|   const { frontmatter, code } = await bundleMDX(source, { | ||||
|     // mdx imports can be automatically source from the components directory | ||||
|     cwd: path.join(process.cwd(), 'components'), | ||||
| @@ -77,6 +80,7 @@ export async function getFileBySlug(type, slug) { | ||||
|         ...(options.remarkPlugins ?? []), | ||||
|         require('remark-slug'), | ||||
|         require('remark-autolink-headings'), | ||||
|         [remarkTocHeadings, { exportRef: toc }], | ||||
|         require('remark-gfm'), | ||||
|         codeTitles, | ||||
|         [require('remark-footnotes'), { inlineNotes: true }], | ||||
| @@ -111,6 +115,7 @@ export async function getFileBySlug(type, slug) { | ||||
|  | ||||
|   return { | ||||
|     mdxSource: code, | ||||
|     toc, | ||||
|     frontMatter: { | ||||
|       readingTime: readingTime(code), | ||||
|       slug: slug || null, | ||||
|   | ||||
							
								
								
									
										12
									
								
								lib/remark-toc-headings.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								lib/remark-toc-headings.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,12 @@ | ||||
| import visit from 'unist-util-visit' | ||||
|  | ||||
| module.exports = function (options) { | ||||
|   return (tree) => | ||||
|     visit(tree, 'heading', (node, index, parent) => { | ||||
|       options.exportRef.push({ | ||||
|         value: node.children[1].value, | ||||
|         url: node.children[0].url, | ||||
|         depth: node.depth, | ||||
|       }) | ||||
|     }) | ||||
| } | ||||
| @@ -39,13 +39,14 @@ export async function getStaticProps({ params }) { | ||||
| } | ||||
|  | ||||
| export default function Blog({ post, authorDetails, prev, next }) { | ||||
|   const { mdxSource, frontMatter } = post | ||||
|   const { mdxSource, toc, frontMatter } = post | ||||
|  | ||||
|   return ( | ||||
|     <> | ||||
|       {frontMatter.draft !== true ? ( | ||||
|         <MDXLayoutRenderer | ||||
|           layout={frontMatter.layout || DEFAULT_LAYOUT} | ||||
|           toc={toc} | ||||
|           mdxSource={mdxSource} | ||||
|           frontMatter={frontMatter} | ||||
|           authorDetails={authorDetails} | ||||
|   | ||||
| @@ -74,6 +74,14 @@ module.exports = { | ||||
|             'code:after': { | ||||
|               content: 'none', | ||||
|             }, | ||||
|             details: { | ||||
|               backgroundColor: theme('colors.gray.100'), | ||||
|               paddingLeft: '4px', | ||||
|               paddingRight: '4px', | ||||
|               paddingTop: '2px', | ||||
|               paddingBottom: '2px', | ||||
|               borderRadius: '0.25rem', | ||||
|             }, | ||||
|             hr: { borderColor: theme('colors.gray.200') }, | ||||
|             'ol li:before': { | ||||
|               fontWeight: '600', | ||||
| @@ -119,6 +127,9 @@ module.exports = { | ||||
|             code: { | ||||
|               backgroundColor: theme('colors.gray.800'), | ||||
|             }, | ||||
|             details: { | ||||
|               backgroundColor: theme('colors.gray.800'), | ||||
|             }, | ||||
|             hr: { borderColor: theme('colors.gray.700') }, | ||||
|             'ol li:before': { | ||||
|               fontWeight: '600', | ||||
|   | ||||
		Reference in New Issue
	
	Block a user