upstream #1
16
.eslintrc.js
16
.eslintrc.js
@ -1,17 +1,5 @@
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
root: true,
|
root: true,
|
||||||
parserOptions: {
|
|
||||||
ecmaVersion: 2020,
|
|
||||||
sourceType: 'module',
|
|
||||||
ecmaFeatures: {
|
|
||||||
jsx: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
settings: {
|
|
||||||
react: {
|
|
||||||
version: 'detect',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
env: {
|
env: {
|
||||||
browser: true,
|
browser: true,
|
||||||
amd: true,
|
amd: true,
|
||||||
@ -20,10 +8,10 @@ module.exports = {
|
|||||||
},
|
},
|
||||||
extends: [
|
extends: [
|
||||||
'eslint:recommended',
|
'eslint:recommended',
|
||||||
'plugin:react/recommended',
|
|
||||||
'plugin:react-hooks/recommended',
|
|
||||||
'plugin:jsx-a11y/recommended',
|
'plugin:jsx-a11y/recommended',
|
||||||
'plugin:prettier/recommended',
|
'plugin:prettier/recommended',
|
||||||
|
'next',
|
||||||
|
'next/core-web-vitals',
|
||||||
],
|
],
|
||||||
rules: {
|
rules: {
|
||||||
'prettier/prettier': 'error',
|
'prettier/prettier': 'error',
|
||||||
|
6
components/Image.js
Normal file
6
components/Image.js
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
import NextImage from 'next/image'
|
||||||
|
|
||||||
|
// eslint-disable-next-line jsx-a11y/alt-text
|
||||||
|
const Image = ({ ...rest }) => <NextImage {...rest} />
|
||||||
|
|
||||||
|
export default Image
|
@ -1,5 +1,5 @@
|
|||||||
import { MDXRemote } from 'next-mdx-remote'
|
import { MDXRemote } from 'next-mdx-remote'
|
||||||
import Image from 'next/image'
|
import Image from './Image'
|
||||||
import CustomLink from './Link'
|
import CustomLink from './Link'
|
||||||
import Pre from './Pre'
|
import Pre from './Pre'
|
||||||
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import siteMetadata from '@/data/siteMetadata'
|
|
||||||
import SocialIcon from '@/components/social-icons'
|
import SocialIcon from '@/components/social-icons'
|
||||||
|
import Image from '@/components/Image'
|
||||||
import { PageSeo } from '@/components/SEO'
|
import { PageSeo } from '@/components/SEO'
|
||||||
|
|
||||||
export default function AuthorLayout({ children, frontMatter }) {
|
export default function AuthorLayout({ children, frontMatter }) {
|
||||||
@ -16,7 +16,13 @@ export default function AuthorLayout({ children, frontMatter }) {
|
|||||||
</div>
|
</div>
|
||||||
<div className="items-start space-y-2 xl:grid xl:grid-cols-3 xl:gap-x-8 xl:space-y-0">
|
<div className="items-start space-y-2 xl:grid xl:grid-cols-3 xl:gap-x-8 xl:space-y-0">
|
||||||
<div className="flex flex-col items-center pt-8 space-x-2">
|
<div className="flex flex-col items-center pt-8 space-x-2">
|
||||||
<img src={avatar} alt="avatar" className="w-48 h-48 rounded-full" />
|
<Image
|
||||||
|
src={avatar}
|
||||||
|
alt="avatar"
|
||||||
|
width="192px"
|
||||||
|
height="192px"
|
||||||
|
className="w-48 h-48 rounded-full"
|
||||||
|
/>
|
||||||
<h3 className="pt-4 pb-2 text-2xl font-bold leading-8 tracking-tight">{name}</h3>
|
<h3 className="pt-4 pb-2 text-2xl font-bold leading-8 tracking-tight">{name}</h3>
|
||||||
<div className="text-gray-500 dark:text-gray-400">{occupation}</div>
|
<div className="text-gray-500 dark:text-gray-400">{occupation}</div>
|
||||||
<div className="text-gray-500 dark:text-gray-400">{company}</div>
|
<div className="text-gray-500 dark:text-gray-400">{company}</div>
|
||||||
|
@ -2,6 +2,7 @@ import Link from '@/components/Link'
|
|||||||
import PageTitle from '@/components/PageTitle'
|
import PageTitle from '@/components/PageTitle'
|
||||||
import SectionContainer from '@/components/SectionContainer'
|
import SectionContainer from '@/components/SectionContainer'
|
||||||
import { BlogSeo } from '@/components/SEO'
|
import { BlogSeo } from '@/components/SEO'
|
||||||
|
import Image from '@/components/Image'
|
||||||
import Tag from '@/components/Tag'
|
import Tag from '@/components/Tag'
|
||||||
import siteMetadata from '@/data/siteMetadata'
|
import siteMetadata from '@/data/siteMetadata'
|
||||||
|
|
||||||
@ -18,7 +19,7 @@ export default function PostLayout({ frontMatter, authorDetails, next, prev, chi
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<SectionContainer>
|
<SectionContainer>
|
||||||
<BlogSeo url={`${siteMetadata.siteUrl}/blog/${frontMatter.slug}`} {...frontMatter} />
|
<BlogSeo url={`${siteMetadata.siteUrl}/blog/${slug}`} {...frontMatter} />
|
||||||
<article>
|
<article>
|
||||||
<div className="xl:divide-y xl:divide-gray-200 xl:dark:divide-gray-700">
|
<div className="xl:divide-y xl:divide-gray-200 xl:dark:divide-gray-700">
|
||||||
<header className="pt-6 xl:pb-6">
|
<header className="pt-6 xl:pb-6">
|
||||||
@ -49,7 +50,13 @@ export default function PostLayout({ frontMatter, authorDetails, next, prev, chi
|
|||||||
{authorDetails.map((author) => (
|
{authorDetails.map((author) => (
|
||||||
<li className="flex items-center space-x-2" key={author.name}>
|
<li className="flex items-center space-x-2" key={author.name}>
|
||||||
{author.avatar && (
|
{author.avatar && (
|
||||||
<img src={author.avatar} alt="avatar" className="w-10 h-10 rounded-full" />
|
<Image
|
||||||
|
src={siteMetadata.image}
|
||||||
|
width="38px"
|
||||||
|
height="38px"
|
||||||
|
alt="avatar"
|
||||||
|
className="w-10 h-10 rounded-full"
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
<dl className="text-sm font-medium leading-5 whitespace-nowrap">
|
<dl className="text-sm font-medium leading-5 whitespace-nowrap">
|
||||||
<dt className="sr-only">Name</dt>
|
<dt className="sr-only">Name</dt>
|
||||||
|
@ -1,11 +1,13 @@
|
|||||||
|
import { escape } from '@/lib/utils/htmlEscaper'
|
||||||
|
|
||||||
import siteMetadata from '@/data/siteMetadata'
|
import siteMetadata from '@/data/siteMetadata'
|
||||||
|
|
||||||
const generateRssItem = (post) => `
|
const generateRssItem = (post) => `
|
||||||
<item>
|
<item>
|
||||||
<guid>${siteMetadata.siteUrl}/blog/${post.slug}</guid>
|
<guid>${siteMetadata.siteUrl}/blog/${post.slug}</guid>
|
||||||
<title>${post.title}</title>
|
<title>${escape(post.title)}</title>
|
||||||
<link>${siteMetadata.siteUrl}/blog/${post.slug}</link>
|
<link>${siteMetadata.siteUrl}/blog/${post.slug}</link>
|
||||||
<description>${post.summary}</description>
|
<description>${escape(post.summary)}</description>
|
||||||
<pubDate>${new Date(post.date).toUTCString()}</pubDate>
|
<pubDate>${new Date(post.date).toUTCString()}</pubDate>
|
||||||
<author>${siteMetadata.email} (${siteMetadata.author})</author>
|
<author>${siteMetadata.email} (${siteMetadata.author})</author>
|
||||||
${post.tags.map((t) => `<category>${t}</category>`).join('')}
|
${post.tags.map((t) => `<category>${t}</category>`).join('')}
|
||||||
@ -15,9 +17,9 @@ const generateRssItem = (post) => `
|
|||||||
const generateRss = (posts, page = 'index.xml') => `
|
const generateRss = (posts, page = 'index.xml') => `
|
||||||
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
|
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
|
||||||
<channel>
|
<channel>
|
||||||
<title>${siteMetadata.title}</title>
|
<title>${escape(siteMetadata.title)}</title>
|
||||||
<link>${siteMetadata.siteUrl}/blog</link>
|
<link>${siteMetadata.siteUrl}/blog</link>
|
||||||
<description>${siteMetadata.description}</description>
|
<description>${escape(siteMetadata.description)}</description>
|
||||||
<language>${siteMetadata.language}</language>
|
<language>${siteMetadata.language}</language>
|
||||||
<managingEditor>${siteMetadata.email} (${siteMetadata.author})</managingEditor>
|
<managingEditor>${siteMetadata.email} (${siteMetadata.author})</managingEditor>
|
||||||
<webMaster>${siteMetadata.email} (${siteMetadata.author})</webMaster>
|
<webMaster>${siteMetadata.email} (${siteMetadata.author})</webMaster>
|
||||||
|
@ -98,6 +98,10 @@ export async function getAllFilesFrontMatter(folder) {
|
|||||||
files.forEach((file) => {
|
files.forEach((file) => {
|
||||||
// Replace is needed to work on Windows
|
// Replace is needed to work on Windows
|
||||||
const fileName = file.slice(prefixPaths.length + 1).replace(/\\/g, '/')
|
const fileName = file.slice(prefixPaths.length + 1).replace(/\\/g, '/')
|
||||||
|
// Remove Unexpected File
|
||||||
|
if (path.extname(fileName) !== '.md' && path.extname(fileName) !== '.mdx') {
|
||||||
|
return
|
||||||
|
}
|
||||||
const source = fs.readFileSync(file, 'utf8')
|
const source = fs.readFileSync(file, 'utf8')
|
||||||
const { data } = matter(source)
|
const { data } = matter(source)
|
||||||
if (data.draft !== true) {
|
if (data.draft !== true) {
|
||||||
|
23
lib/utils/htmlEscaper.js
Normal file
23
lib/utils/htmlEscaper.js
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
const { replace } = ''
|
||||||
|
|
||||||
|
// escape
|
||||||
|
const es = /&(?:amp|#38|lt|#60|gt|#62|apos|#39|quot|#34);/g
|
||||||
|
const ca = /[&<>'"]/g
|
||||||
|
|
||||||
|
const esca = {
|
||||||
|
'&': '&',
|
||||||
|
'<': '<',
|
||||||
|
'>': '>',
|
||||||
|
"'": ''',
|
||||||
|
'"': '"',
|
||||||
|
}
|
||||||
|
const pe = (m) => esca[m]
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Safely escape HTML entities such as `&`, `<`, `>`, `"`, and `'`.
|
||||||
|
* @param {string} es the input to safely escape
|
||||||
|
* @returns {string} the escaped input, and it **throws** an error if
|
||||||
|
* the input type is unexpected, except for boolean and numbers,
|
||||||
|
* converted as string.
|
||||||
|
*/
|
||||||
|
export const escape = (es) => replace.call(es, ca, pe)
|
@ -5,8 +5,8 @@ const withBundleAnalyzer = require('@next/bundle-analyzer')({
|
|||||||
module.exports = withBundleAnalyzer({
|
module.exports = withBundleAnalyzer({
|
||||||
reactStrictMode: true,
|
reactStrictMode: true,
|
||||||
pageExtensions: ['js', 'jsx', 'md', 'mdx'],
|
pageExtensions: ['js', 'jsx', 'md', 'mdx'],
|
||||||
future: {
|
eslint: {
|
||||||
webpack5: true,
|
dirs: ['pages', 'components', 'lib', 'layouts', 'scripts'],
|
||||||
},
|
},
|
||||||
webpack: (config, { dev, isServer }) => {
|
webpack: (config, { dev, isServer }) => {
|
||||||
config.module.rules.push({
|
config.module.rules.push({
|
||||||
|
3135
package-lock.json
generated
3135
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
21
package.json
21
package.json
@ -1,13 +1,14 @@
|
|||||||
{
|
{
|
||||||
"name": "tailwind-nextjs-starter-blog",
|
"name": "tailwind-nextjs-starter-blog",
|
||||||
"version": "0.3.4",
|
"version": "0.4.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "cross-env TAILWIND_MODE=watch next-remote-watch ./data",
|
"start": "next-remote-watch ./data",
|
||||||
"dev": "cross-env TAILWIND_MODE=watch next dev",
|
"dev": "next dev",
|
||||||
"build": "next build && node ./scripts/generate-sitemap",
|
"build": "next build && node ./scripts/generate-sitemap",
|
||||||
"serve": "next start",
|
"serve": "next start",
|
||||||
"analyze": "cross-env ANALYZE=true next build",
|
"analyze": "cross-env ANALYZE=true next build",
|
||||||
|
"lint": "next lint --fix --dir pages --dir components --dir lib --dir layouts --dir scripts",
|
||||||
"prepare": "husky install"
|
"prepare": "husky install"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@ -17,10 +18,10 @@
|
|||||||
"autoprefixer": "^10.2.5",
|
"autoprefixer": "^10.2.5",
|
||||||
"gray-matter": "^4.0.2",
|
"gray-matter": "^4.0.2",
|
||||||
"image-size": "1.0.0",
|
"image-size": "1.0.0",
|
||||||
"next": "10.2.3",
|
"next": "11.0.1",
|
||||||
"next-mdx-remote": "^3.0.1",
|
"next-mdx-remote": "^3.0.1",
|
||||||
"next-themes": "^0.0.14",
|
"next-themes": "^0.0.14",
|
||||||
"postcss": "^8.2.15",
|
"postcss": "^8.3.5",
|
||||||
"preact": "^10.5.13",
|
"preact": "^10.5.13",
|
||||||
"react": "17.0.2",
|
"react": "17.0.2",
|
||||||
"react-dom": "17.0.2",
|
"react-dom": "17.0.2",
|
||||||
@ -31,23 +32,21 @@
|
|||||||
"remark-footnotes": "^3.0.0",
|
"remark-footnotes": "^3.0.0",
|
||||||
"remark-math": "^3.0.1",
|
"remark-math": "^3.0.1",
|
||||||
"remark-slug": "6.0.0",
|
"remark-slug": "6.0.0",
|
||||||
"tailwindcss": "^2.1.1"
|
"tailwindcss": "^2.2.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@next/bundle-analyzer": "10.2.3",
|
"@next/bundle-analyzer": "11.0.1",
|
||||||
"@svgr/webpack": "^5.5.0",
|
"@svgr/webpack": "^5.5.0",
|
||||||
"cross-env": "^7.0.3",
|
"cross-env": "^7.0.3",
|
||||||
"eslint": "^7.17.0",
|
"eslint": "^7.29.0",
|
||||||
|
"eslint-config-next": "11.0.1",
|
||||||
"eslint-config-prettier": "^8.3.0",
|
"eslint-config-prettier": "^8.3.0",
|
||||||
"eslint-plugin-jsx-a11y": "^6.4.1",
|
"eslint-plugin-jsx-a11y": "^6.4.1",
|
||||||
"eslint-plugin-prettier": "^3.3.1",
|
"eslint-plugin-prettier": "^3.3.1",
|
||||||
"eslint-plugin-react": "^7.22.0",
|
|
||||||
"eslint-plugin-react-hooks": "^4.2.0",
|
|
||||||
"file-loader": "^6.0.0",
|
"file-loader": "^6.0.0",
|
||||||
"globby": "11.0.3",
|
"globby": "11.0.3",
|
||||||
"husky": "^6.0.0",
|
"husky": "^6.0.0",
|
||||||
"lint-staged": "^11.0.0",
|
"lint-staged": "^11.0.0",
|
||||||
"next-compose-plugins": "^2.2.1",
|
|
||||||
"next-remote-watch": "^1.0.0",
|
"next-remote-watch": "^1.0.0",
|
||||||
"prettier": "2.2.1",
|
"prettier": "2.2.1",
|
||||||
"rehype": "11.0.0",
|
"rehype": "11.0.0",
|
||||||
|
@ -28,9 +28,12 @@ const siteMetadata = require('../data/siteMetadata')
|
|||||||
.replace('.md', '')
|
.replace('.md', '')
|
||||||
.replace('/index.xml', '')
|
.replace('/index.xml', '')
|
||||||
const route = path === '/index' ? '' : path
|
const route = path === '/index' ? '' : path
|
||||||
|
if (page === `pages/404.js` || page === `pages/blog/[...slug].js`) {
|
||||||
|
return
|
||||||
|
}
|
||||||
return `
|
return `
|
||||||
<url>
|
<url>
|
||||||
<loc>${`${siteMetadata.siteUrl}${route}`}</loc>
|
<loc>${siteMetadata.siteUrl}${route}</loc>
|
||||||
</url>
|
</url>
|
||||||
`
|
`
|
||||||
})
|
})
|
||||||
|
Loading…
x
Reference in New Issue
Block a user