upstream #1
| @@ -43,6 +43,7 @@ I wanted it to be nearly as feature-rich as popular blogging templates like [bea | ||||
| - Support for multiple authors | ||||
| - Blog templates | ||||
| - Support for nested routing of blog posts | ||||
| - Supports [giscus](https://github.com/laymonage/giscus), [utterances](https://github.com/utterance/utterances) or disqus | ||||
| - Projects page | ||||
| - SEO friendly with RSS feed, sitemaps and more! | ||||
|  | ||||
|   | ||||
							
								
								
									
										37
									
								
								components/comments/Disqus.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								components/comments/Disqus.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,37 @@ | ||||
| import React, { useState } from 'react' | ||||
|  | ||||
| import siteMetadata from '@/data/siteMetadata' | ||||
|  | ||||
| const Disqus = ({ frontMatter }) => { | ||||
|   const [enableLoadComments, setEnabledLoadComments] = useState(true) | ||||
|  | ||||
|   const COMMENTS_ID = 'disqus_thread' | ||||
|  | ||||
|   function LoadComments() { | ||||
|     setEnabledLoadComments(false) | ||||
|  | ||||
|     window.disqus_config = function () { | ||||
|       this.page.url = window.location.href | ||||
|       this.page.identifier = frontMatter.slug | ||||
|     } | ||||
|     if (window.DISQUS === undefined) { | ||||
|       const script = document.createElement('script') | ||||
|       script.src = 'https://' + siteMetadata.comment.disqus.shortname + '.disqus.com/embed.js' | ||||
|       script.setAttribute('data-timestamp', +new Date()) | ||||
|       script.setAttribute('crossorigin', 'anonymous') | ||||
|       script.async = true | ||||
|       document.body.appendChild(script) | ||||
|     } else { | ||||
|       window.DISQUS.reset({ reload: true }) | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   return ( | ||||
|     <div className="pt-6 pb-6 text-center text-gray-700 dark:text-gray-300"> | ||||
|       {enableLoadComments && <button onClick={LoadComments}>Load Comments</button>} | ||||
|       <div className="disqus-frame" id={COMMENTS_ID} /> | ||||
|     </div> | ||||
|   ) | ||||
| } | ||||
|  | ||||
| export default Disqus | ||||
							
								
								
									
										50
									
								
								components/comments/Giscus.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										50
									
								
								components/comments/Giscus.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,50 @@ | ||||
| import React, { useState } from 'react' | ||||
| import { useTheme } from 'next-themes' | ||||
|  | ||||
| import siteMetadata from '@/data/siteMetadata' | ||||
|  | ||||
| const Giscus = ({ mapping }) => { | ||||
|   const [enableLoadComments, setEnabledLoadComments] = useState(true) | ||||
|   const { theme, resolvedTheme } = useTheme() | ||||
|   const commentsTheme = | ||||
|     siteMetadata.comment.giscusConfig.themeURL === '' | ||||
|       ? theme === 'dark' || resolvedTheme === 'dark' | ||||
|         ? siteMetadata.comment.giscusConfig.darkTheme | ||||
|         : siteMetadata.comment.giscusConfig.theme | ||||
|       : siteMetadata.comment.giscusConfig.themeURL | ||||
|  | ||||
|   const COMMENTS_ID = 'comments-container' | ||||
|  | ||||
|   function LoadComments() { | ||||
|     setEnabledLoadComments(false) | ||||
|     const script = document.createElement('script') | ||||
|     script.src = 'https://giscus.app/client.js' | ||||
|     script.setAttribute('data-repo', siteMetadata.comment.giscusConfig.repo) | ||||
|     script.setAttribute('data-repo-id', siteMetadata.comment.giscusConfig.repositoryId) | ||||
|     script.setAttribute('data-category', siteMetadata.comment.giscusConfig.category) | ||||
|     script.setAttribute('data-category-id', siteMetadata.comment.giscusConfig.categoryId) | ||||
|     script.setAttribute('data-mapping', mapping) | ||||
|     script.setAttribute('data-reactions-enabled', siteMetadata.comment.giscusConfig.reactions) | ||||
|     script.setAttribute('data-emit-metadata', siteMetadata.comment.giscusConfig.metadata) | ||||
|     script.setAttribute('data-theme', commentsTheme) | ||||
|     script.setAttribute('crossorigin', 'anonymous') | ||||
|     script.async = true | ||||
|  | ||||
|     const comments = document.getElementById(COMMENTS_ID) | ||||
|     if (comments) comments.appendChild(script) | ||||
|  | ||||
|     return () => { | ||||
|       const comments = document.getElementById(COMMENTS_ID) | ||||
|       if (comments) comments.innerHTML = '' | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   return ( | ||||
|     <div className="pt-6 pb-6 text-center text-gray-700 dark:text-gray-300"> | ||||
|       {enableLoadComments && <button onClick={LoadComments}>Load Comments</button>} | ||||
|       <div className="giscus-frame" id={COMMENTS_ID} /> | ||||
|     </div> | ||||
|   ) | ||||
| } | ||||
|  | ||||
| export default Giscus | ||||
							
								
								
									
										45
									
								
								components/comments/Utterances.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								components/comments/Utterances.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,45 @@ | ||||
| import React, { useState } from 'react' | ||||
| import { useTheme } from 'next-themes' | ||||
|  | ||||
| import siteMetadata from '@/data/siteMetadata' | ||||
|  | ||||
| const Utterances = ({ issueTerm }) => { | ||||
|   const [enableLoadComments, setEnabledLoadComments] = useState(true) | ||||
|   const { theme, resolvedTheme } = useTheme() | ||||
|   const commentsTheme = | ||||
|     theme === 'dark' || resolvedTheme === 'dark' | ||||
|       ? siteMetadata.comment.utterancesConfig.darkTheme | ||||
|       : siteMetadata.comment.utterancesConfig.theme | ||||
|  | ||||
|   const COMMENTS_ID = 'comments-container' | ||||
|  | ||||
|   function LoadComments() { | ||||
|     setEnabledLoadComments(false) | ||||
|     const script = document.createElement('script') | ||||
|     script.src = 'https://utteranc.es/client.js' | ||||
|     script.setAttribute('repo', siteMetadata.comment.utterancesConfig.repo) | ||||
|     script.setAttribute('issue-term', issueTerm) | ||||
|     script.setAttribute('label', siteMetadata.comment.utterancesConfig.label) | ||||
|     script.setAttribute('theme', commentsTheme) | ||||
|     script.setAttribute('crossorigin', 'anonymous') | ||||
|     script.async = true | ||||
|  | ||||
|     const comments = document.getElementById(COMMENTS_ID) | ||||
|     if (comments) comments.appendChild(script) | ||||
|  | ||||
|     return () => { | ||||
|       const comments = document.getElementById(COMMENTS_ID) | ||||
|       if (comments) comments.innerHTML = '' | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   // Added `relative` to fix a weird bug with `utterances-frame` position | ||||
|   return ( | ||||
|     <div className="pt-6 pb-6 text-center text-gray-700 dark:text-gray-300"> | ||||
|       {enableLoadComments && <button onClick={LoadComments}>Load Comments</button>} | ||||
|       <div className="utterances-frame relative" id={COMMENTS_ID} /> | ||||
|     </div> | ||||
|   ) | ||||
| } | ||||
|  | ||||
| export default Utterances | ||||
							
								
								
									
										54
									
								
								components/comments/index.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										54
									
								
								components/comments/index.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,54 @@ | ||||
| import siteMetadata from '@/data/siteMetadata' | ||||
| import dynamic from 'next/dynamic' | ||||
|  | ||||
| const UtterancesComponent = dynamic( | ||||
|   () => { | ||||
|     return import('@/components/comments/Utterances') | ||||
|   }, | ||||
|   { ssr: false } | ||||
| ) | ||||
| const GiscusComponent = dynamic( | ||||
|   () => { | ||||
|     return import('@/components/comments/Giscus') | ||||
|   }, | ||||
|   { ssr: false } | ||||
| ) | ||||
| const DisqusComponent = dynamic( | ||||
|   () => { | ||||
|     return import('@/components/comments/Disqus') | ||||
|   }, | ||||
|   { ssr: false } | ||||
| ) | ||||
|  | ||||
| const Comments = ({ frontMatter }) => { | ||||
|   let term | ||||
|   switch ( | ||||
|     siteMetadata.comment.giscusConfig.mapping || | ||||
|     siteMetadata.comment.utterancesConfig.issueTerm | ||||
|   ) { | ||||
|     case 'pathname': | ||||
|       term = frontMatter.slug | ||||
|       break | ||||
|     case 'url': | ||||
|       term = window.location.href | ||||
|       break | ||||
|     case 'title': | ||||
|       term = frontMatter.title | ||||
|       break | ||||
|   } | ||||
|   return ( | ||||
|     <> | ||||
|       {siteMetadata.comment && siteMetadata.comment.provider === 'giscus' && ( | ||||
|         <GiscusComponent mapping={term} /> | ||||
|       )} | ||||
|       {siteMetadata.comment && siteMetadata.comment.provider === 'utterances' && ( | ||||
|         <UtterancesComponent issueTerm={term} /> | ||||
|       )} | ||||
|       {siteMetadata.comment && siteMetadata.comment.provider === 'disqus' && ( | ||||
|         <DisqusComponent frontMatter={frontMatter} /> | ||||
|       )} | ||||
|     </> | ||||
|   ) | ||||
| } | ||||
|  | ||||
| export default Comments | ||||
| @@ -1,7 +1,7 @@ | ||||
| --- | ||||
| title: 'Introducing Tailwind Nexjs Starter Blog' | ||||
| date: '2021-01-12' | ||||
| lastmod: '2021-07-11' | ||||
| lastmod: '2021-07-18' | ||||
| 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.' | ||||
| @@ -48,6 +48,7 @@ I wanted it to be nearly as feature-rich as popular blogging templates like [bea | ||||
| - Support for multiple authors | ||||
| - Blog templates | ||||
| - Support for nested routing of blog posts | ||||
| - Supports [giscus](https://github.com/laymonage/giscus), [utterances](https://github.com/utterance/utterances) or disqus | ||||
| - Projects page | ||||
| - SEO friendly with RSS feed, sitemaps and more! | ||||
|  | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| --- | ||||
| title: 'New features in v1' | ||||
| date: '2021-07-11' | ||||
| date: '2021-07-18' | ||||
| 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' | ||||
| @@ -15,6 +15,7 @@ A post on the new features introduced in v1.0. New features: | ||||
| - [Xdm MDX compiler](#xdm-mdx-compiler) | ||||
| - [Layouts](#layouts) | ||||
| - [Multiple authors](#multiple-authors) | ||||
| - [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) | ||||
|  | ||||
| @@ -140,6 +141,52 @@ export const MDXLayoutRenderer = ({ layout, mdxSource, ...rest }) => { | ||||
| Use the component is a page where you want to accept a layout name to map to the desired layout. | ||||
| You need to pass the layout name from the layout folder (it has to be an exact match) and the mdxSource content which is an output of the `seralize` function from the `next-mdx-remote` library. | ||||
|  | ||||
| ## Blog comments system | ||||
|  | ||||
| We added support for [giscus](https://github.com/laymonage/giscus), [utterances](https://github.com/utterance/utterances) or disqus. | ||||
| To enable, simply configure `siteMetadata.js` comments property with the desired provider and settings as specified in the config file. | ||||
|  | ||||
| ```js | ||||
| comment: { | ||||
|     provider: '', // supported providers: giscus, utterances, disqus | ||||
|     giscusConfig: { | ||||
|       repo: '', // username/repoName | ||||
|       // Visit the link below and copy/paste the 'repositoryId', 'category' and 'categoryId' | ||||
|       // https://giscus.app/api/discussions/categories?repo={username}%2F{repoName} | ||||
|       repositoryId: '', | ||||
|       category: [], | ||||
|       categoryId: '', | ||||
|       mapping: '', // supported options: pathname, url, title | ||||
|       reactions: '', // Emoji reactions: 1 = enable / 0 = disable | ||||
|       // Send discussion metadata periodically to the parent window: 1 = enable / 0 = disable | ||||
|       metadata: '', | ||||
|       // theme example: light, dark, dark_dimmed, dark_high_contrast | ||||
|       // transparent_dark, preferred_color_scheme, custom | ||||
|       theme: '', | ||||
|       // theme when dark mode | ||||
|       darkTheme: '', | ||||
|       // If the theme option above is set to 'custom` | ||||
|       // please provide a link below to your custom theme css file. | ||||
|       // example: https://giscus.app/themes/custom_example.css | ||||
|       themeURL: '', | ||||
|     }, | ||||
|     utterancesConfig: { | ||||
|       repo: '', // username/repoName | ||||
|       issueTerm: '', // supported options: pathname, url, title | ||||
|       label: '', // label (optional): Comment 💬 | ||||
|       // theme example: github-light, github-dark, preferred-color-scheme | ||||
|       // github-dark-orange, icy-dark, dark-blue, photon-dark, boxy-light | ||||
|       theme: '', | ||||
|       // theme when dark mode | ||||
|       darkTheme: '', | ||||
|     }, | ||||
|     disqus: { | ||||
|       // https://help.disqus.com/en/articles/1717111-what-s-a-shortname | ||||
|       shortname: '', | ||||
|     }, | ||||
|   } | ||||
| ``` | ||||
|  | ||||
| ## Multiple authors | ||||
|  | ||||
| Information on authors is now split from `siteMetadata.json` and stored in its own `data/authors` folder as a markdown file. Minimally, you will need to have a `default.md` file with authorship information. You can create additional files as required and the file name will be used as the reference to the author. | ||||
|   | ||||
							
								
								
									
										65
									
								
								data/siteMetadata.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										65
									
								
								data/siteMetadata.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,65 @@ | ||||
| const siteMetadata = { | ||||
|   title: 'Next.js Starter Blog', | ||||
|   author: 'Tails Azimuth', | ||||
|   headerTitle: 'TailwindBlog', | ||||
|   description: 'A blog created with Next.js and Tailwind.css', | ||||
|   language: 'en-us', | ||||
|   siteUrl: 'https://tailwind-nextjs-starter-blog.vercel.app', | ||||
|   siteRepo: 'https://github.com/timlrx/tailwind-nextjs-starter-blog', | ||||
|   siteLogo: '/static/images/logo.png', | ||||
|   image: '/static/images/avatar.png', | ||||
|   socialBanner: '/static/images/twitter-card.png', | ||||
|   email: 'address@yoursite.com', | ||||
|   github: 'https://github.com', | ||||
|   twitter: 'https://twitter.com/Twitter', | ||||
|   facebook: 'https://facebook.com', | ||||
|   youtube: 'https://youtube.com', | ||||
|   linkedin: 'https://www.linkedin.com', | ||||
|   locale: 'en-US', | ||||
|   comment: { | ||||
|     provider: 'giscus', // supported providers: giscus, utterances, disqus | ||||
|     giscusConfig: { | ||||
|       repo: 'timlrx/tailwind-nextjs-starter-blog', // username/repoName | ||||
|       // Visit the link below and copy/paste the 'repositoryId', 'category' and 'categoryId' | ||||
|       // https://giscus.app/api/discussions/categories?repo={username}%2F{repoName} | ||||
|       repositoryId: 'MDEwOlJlcG9zaXRvcnkzMjgxMjEyNjA=', | ||||
|       category: [ | ||||
|         { emoji: '📣', id: 'MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMzMDM4NDYw', name: 'Announcements' }, | ||||
|         { emoji: '💬', id: 'MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMzMDM4NDYx', name: 'General' }, | ||||
|         { emoji: '🙏', id: 'MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMzMDM4NDYy', name: 'Q&A' }, | ||||
|         { emoji: '💡', id: 'MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMzMDM4NDYz', name: 'Ideas' }, | ||||
|         { emoji: '🙌', id: 'MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMzMDM4NDY0', name: 'Show and tell' }, | ||||
|       ], | ||||
|       categoryId: 'MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMzMDM4NDYw', | ||||
|       mapping: 'pathname', // supported options: pathname, url, title | ||||
|       reactions: '1', // Emoji reactions: 1 = enable / 0 = disable | ||||
|       // Send discussion metadata periodically to the parent window: 1 = enable / 0 = disable | ||||
|       metadata: '0', | ||||
|       // theme example: light, dark, dark_dimmed, dark_high_contrast | ||||
|       // transparent_dark, preferred_color_scheme, custom | ||||
|       theme: 'light', | ||||
|       // theme when dark mode | ||||
|       darkTheme: 'transparent_dark', | ||||
|       // If the theme option above is set to 'custom` | ||||
|       // please provide a link below to your custom theme css file. | ||||
|       // example: https://giscus.app/themes/custom_example.css | ||||
|       themeURL: '', | ||||
|     }, | ||||
|     utterancesConfig: { | ||||
|       repo: '', // username/repoName | ||||
|       issueTerm: '', // supported options: pathname, url, title | ||||
|       label: '', // label (optional): Comment 💬 | ||||
|       // theme example: github-light, github-dark, preferred-color-scheme | ||||
|       // github-dark-orange, icy-dark, dark-blue, photon-dark, boxy-light | ||||
|       theme: '', | ||||
|       // theme when dark mode | ||||
|       darkTheme: '', | ||||
|     }, | ||||
|     disqus: { | ||||
|       // https://help.disqus.com/en/articles/1717111-what-s-a-shortname | ||||
|       shortname: '', | ||||
|     }, | ||||
|   }, | ||||
| } | ||||
|  | ||||
| module.exports = siteMetadata | ||||
| @@ -1,19 +0,0 @@ | ||||
| { | ||||
|   "title": "Next.js Starter Blog", | ||||
|   "author": "Tails Azimuth", | ||||
|   "headerTitle": "TailwindBlog", | ||||
|   "description": "A blog created with Next.js and Tailwind.css", | ||||
|   "language": "en-us", | ||||
|   "siteUrl": "https://tailwind-nextjs-starter-blog.vercel.app", | ||||
|   "siteRepo": "https://github.com/timlrx/tailwind-nextjs-starter-blog", | ||||
|   "siteLogo": "/static/images/logo.png", | ||||
|   "image": "/static/images/avatar.png", | ||||
|   "socialBanner": "/static/images/twitter-card.png", | ||||
|   "email": "address@yoursite.com", | ||||
|   "github": "https://github.com", | ||||
|   "twitter": "https://twitter.com/Twitter", | ||||
|   "facebook": "https://facebook.com", | ||||
|   "youtube": "https://youtube.com", | ||||
|   "linkedin": "https://www.linkedin.com", | ||||
|   "locale": "en-US" | ||||
| } | ||||
| @@ -5,6 +5,7 @@ import { BlogSeo } from '@/components/SEO' | ||||
| import Image from '@/components/Image' | ||||
| import Tag from '@/components/Tag' | ||||
| import siteMetadata from '@/data/siteMetadata' | ||||
| import Comments from '@/components/comments' | ||||
|  | ||||
| const editUrl = (fileName) => `${siteMetadata.siteRepo}/blob/master/data/blog/${fileName}` | ||||
| const discussUrl = (slug) => | ||||
| @@ -91,6 +92,7 @@ export default function PostLayout({ frontMatter, authorDetails, next, prev, chi | ||||
|                 {` • `} | ||||
|                 <Link href={editUrl(fileName)}>{'View on GitHub'}</Link> | ||||
|               </div> | ||||
|               <Comments frontMatter={frontMatter} /> | ||||
|             </div> | ||||
|             <footer> | ||||
|               <div className="text-sm font-medium leading-5 divide-gray-200 xl:divide-y dark:divide-gray-700 xl:col-start-1 xl:row-start-2"> | ||||
|   | ||||
| @@ -4,6 +4,7 @@ import SectionContainer from '@/components/SectionContainer' | ||||
| import { BlogSeo } from '@/components/SEO' | ||||
| import siteMetadata from '@/data/siteMetadata' | ||||
| import formatDate from '@/lib/utils/formatDate' | ||||
| import Comments from '@/components/comments' | ||||
|  | ||||
| export default function PostLayout({ frontMatter, authorDetails, next, prev, children }) { | ||||
|   const { date, title } = frontMatter | ||||
| @@ -35,6 +36,7 @@ export default function PostLayout({ frontMatter, authorDetails, next, prev, chi | ||||
|             <div className="divide-y divide-gray-200 dark:divide-gray-700 xl:pb-0 xl:col-span-3 xl:row-span-2"> | ||||
|               <div className="pt-10 pb-8 prose dark:prose-dark max-w-none">{children}</div> | ||||
|             </div> | ||||
|             <Comments frontMatter={frontMatter} /> | ||||
|             <footer> | ||||
|               <div className="flex flex-col text-sm font-medium sm:flex-row sm:justify-between sm:text-base"> | ||||
|                 {prev && ( | ||||
|   | ||||
		Reference in New Issue
	
	Block a user