├── .gitignore ├── LICENSE ├── README.md ├── content ├── media │ └── salty_egg.jpg ├── pages │ └── about.md └── posts │ ├── hello-world.md │ ├── my-second-post.md │ └── new-beginnings.md ├── gatsby-browser.js ├── gatsby-config.js ├── gatsby-node.js ├── package.json ├── postcss.config.js ├── src ├── components │ ├── Footer.js │ ├── Header.js │ ├── Layout.js │ ├── PostCard.js │ ├── pagination.js │ └── seo.js ├── images │ └── icon.png ├── pages │ └── 404.js ├── styles │ └── global.css └── templates │ ├── blog-page.js │ ├── blog-post.js │ └── index.js ├── tailwind.config.js └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | .cache/ 3 | public 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Whatk 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## 介绍 2 | 基于 Gatsby、Tailwindcss 的博客,emmmmm 不知道属不属于 Gatsby 主题,个人认为算是一个手脚架? 3 | 是在上手 Gatsby 过程中摸出来的,很简单很基本的一个博客,可以自行小改后拿来使用,或作为开发参考。 4 | 5 | * Tailwindcss 6 | * MarkDown 7 | * 夜间模式 8 | * 多页文章列表 9 | * 深色模式 10 | * 响应式 11 | * ~~简洁~~ 12 | 13 | 演示:https://gatsby-simple-tailwindcss-blog.pages.dev/ 14 | 15 | --- 16 | content 17 | ├── media (媒体(图片等)) 18 | ├── pages (独立页面) 19 | └── posts (文章) 20 | --- 21 | 22 | ## 🚀 Quick start 23 | 24 | 1. **Create a Gatsby site.** 25 | 26 | Use the Gatsby CLI ([install instructions](https://www.gatsbyjs.com/docs/tutorial/part-0/#gatsby-cli)) to create a new site, specifying the blog starter. 27 | 28 | ```shell 29 | # create a new Gatsby site using the blog starter 30 | gatsby new gatsby-simple-tailwindcss-blog https://github.com/whatk233/gatsby-simple-tailwindcss-blog 31 | ``` 32 | 33 | 1. **Start developing.** 34 | 35 | Navigate into your new site’s directory and start it up. 36 | 37 | ```shell 38 | cd gatsby-simple-tailwindcss-blog/ 39 | gatsby develop 40 | ``` 41 | 42 | 1. **Open the source code and start editing!** 43 | 44 | Your site is now running at `http://localhost:8000`! 45 | 46 | _Note: You'll also see a second link: _`http://localhost:8000/___graphql`_. This is a tool you can use to experiment with querying your data. Learn more about using this tool in the [Gatsby tutorial](https://www.gatsbyjs.com/tutorial/part-five/#introducing-graphiql)._ 47 | 48 | 49 | ## 🎓 Learning Gatsby 50 | 51 | Looking for more guidance? Full documentation for Gatsby lives [on the website](https://www.gatsbyjs.com/). Here are some places to start: 52 | 53 | - **For most developers, we recommend starting with our [in-depth tutorial for creating a site with Gatsby](https://www.gatsbyjs.com/tutorial/).** It starts with zero assumptions about your level of ability and walks through every step of the process. 54 | 55 | - **To dive straight into code samples, head [to our documentation](https://www.gatsbyjs.com/docs/).** In particular, check out the _Guides_, _API Reference_, and _Advanced Tutorials_ sections in the sidebar. 56 | 57 | ## 💫 Deploy 58 | [![Deploy to Netlify](https://www.netlify.com/img/deploy/button.svg)](https://app.netlify.com/start/deploy?repository=https://github.com/whatk233/gatsby-simple-tailwindcss-blog) -------------------------------------------------------------------------------- /content/media/salty_egg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/whatk233/gatsby-simple-tailwindcss-blog/bedd560804132b750055b4f483d2ccb9a2d02b31/content/media/salty_egg.jpg -------------------------------------------------------------------------------- /content/pages/about.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 关于 3 | date: "2021-07-23T02:12:03.284Z" 4 | description: "关于" 5 | --- 6 | 7 |
8 | simple blog , simple function 9 |
10 | but bug is not simple :< 11 |
-------------------------------------------------------------------------------- /content/posts/hello-world.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Hello World 3 | date: "2015-05-01T22:12:03.284Z" 4 | description: "Hello World" 5 | --- 6 | 7 | This is my first post on my new fake blog! How exciting! 8 | 9 | I'm sure I'll write a lot more interesting things in the future. 10 | 11 | Oh, and here's a great quote from this Wikipedia on 12 | [salted duck eggs](https://en.wikipedia.org/wiki/Salted_duck_egg). 13 | 14 | > A salted duck egg is a Chinese preserved food product made by soaking duck 15 | > eggs in brine, or packing each egg in damp, salted charcoal. In Asian 16 | > supermarkets, these eggs are sometimes sold covered in a thick layer of salted 17 | > charcoal paste. The eggs may also be sold with the salted paste removed, 18 | > wrapped in plastic, and vacuum packed. From the salt curing process, the 19 | > salted duck eggs have a briny aroma, a gelatin-like egg white and a 20 | > firm-textured, round yolk that is bright orange-red in color. 21 | 22 | ![Chinese Salty Egg](../media/salty_egg.jpg) 23 | 24 | You can also write code blocks here! 25 | 26 | ```js 27 | const saltyDuckEgg = "chinese preserved food product" 28 | ``` 29 | 30 | | Number | Title | Year | 31 | | :----- | :--------------------------------------- | ---: | 32 | | 1 | Harry Potter and the Philosopher’s Stone | 2001 | 33 | | 2 | Harry Potter and the Chamber of Secrets | 2002 | 34 | | 3 | Harry Potter and the Prisoner of Azkaban | 2004 | 35 | 36 | [View raw (TEST.md)](https://raw.github.com/adamschwartz/github-markdown-kitchen-sink/master/README.md) 37 | 38 | This is a paragraph. 39 | 40 | This is a paragraph. 41 | 42 | # Header 1 43 | 44 | ## Header 2 45 | 46 | Header 1 47 | ======== 48 | 49 | Header 2 50 | -------- 51 | 52 | # Header 1 53 | 54 | ## Header 2 55 | 56 | ### Header 3 57 | 58 | #### Header 4 59 | 60 | ##### Header 5 61 | 62 | ###### Header 6 63 | 64 | # Header 1 65 | ## Header 2 66 | ### Header 3 67 | #### Header 4 68 | ##### Header 5 69 | ###### Header 6 70 | 71 | # Header 1 72 | 73 | ## Header 2 74 | 75 | ### Header 3 76 | 77 | #### Header 4 78 | 79 | ##### Header 5 80 | 81 | ###### Header 6 82 | 83 | # Header 1 # 84 | ## Header 2 ## 85 | ### Header 3 ### 86 | #### Header 4 #### 87 | ##### Header 5 ##### 88 | ###### Header 6 ###### 89 | 90 | > Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aliquam hendrerit mi posuere lectus. Vestibulum enim wisi, viverra nec, fringilla in, laoreet vitae, risus. 91 | 92 | > Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aliquam hendrerit mi posuere lectus. Vestibulum enim wisi, viverra nec, fringilla in, laoreet vitae, risus. 93 | 94 | > ## This is a header. 95 | > 96 | > 1. This is the first list item. 97 | > 2. This is the second list item. 98 | > 99 | > Here's some example code: 100 | > 101 | > Markdown.generate(); 102 | 103 | > ## This is a header. 104 | > 1. This is the first list item. 105 | > 2. This is the second list item. 106 | > 107 | > Here's some example code: 108 | > 109 | > Markdown.generate(); 110 | 111 | - Red 112 | - Green 113 | - Blue 114 | 115 | * Red 116 | * Green 117 | * Blue 118 | 119 | - Red 120 | - Green 121 | - Blue 122 | 123 | ```markdown 124 | - Red 125 | - Green 126 | - Blue 127 | 128 | * Red 129 | * Green 130 | * Blue 131 | 132 | - Red 133 | - Green 134 | - Blue 135 | ``` 136 | 137 | - `code goes` here in this line 138 | - **bold** goes here 139 | 140 | ```markdown 141 | - `code goes` here in this line 142 | - **bold** goes here 143 | ``` 144 | 145 | 1. Buy flour and salt 146 | 1. Mix together with water 147 | 1. Bake 148 | 149 | ```markdown 150 | 1. Buy flour and salt 151 | 1. Mix together with water 152 | 1. Bake 153 | ``` 154 | 155 | 1. `code goes` here in this line 156 | 1. **bold** goes here 157 | 158 | ```markdown 159 | 1. `code goes` here in this line 160 | 1. **bold** goes here 161 | ``` 162 | 163 | Paragraph: 164 | 165 | Code 166 | 167 | 168 | 169 | Paragraph: 170 | 171 | Code 172 | 173 | --- 174 | 175 | --- 176 | 177 | --- 178 | 179 | --- 180 | 181 | --- 182 | 183 | * * * 184 | 185 | *** 186 | 187 | ***** 188 | 189 | - - - 190 | 191 | --------------------------------------- 192 | 193 | This is [an example](http://example.com "Example") link. 194 | 195 | [This link](http://example.com) has no title attr. 196 | 197 | This is [an example][id] reference-style link. 198 | 199 | [id]: http://example.com "Optional Title" 200 | 201 | This is [an example](http://example.com "Example") link. 202 | 203 | [This link](http://example.com) has no title attr. 204 | 205 | This is [an example] [id] reference-style link. 206 | 207 | [id]: http://example.com "Optional Title" 208 | 209 | _single asterisks_ 210 | 211 | _single underscores_ 212 | 213 | **double asterisks** 214 | 215 | **double underscores** 216 | 217 | *single asterisks* 218 | 219 | _single underscores_ 220 | 221 | **double asterisks** 222 | 223 | __double underscores__ 224 | 225 | This paragraph has some `code` in it. 226 | 227 | This paragraph has some `code` in it. 228 | 229 | ![Alt Text](https://placehold.it/200x50 "Image Title") 230 | 231 | ![Alt Text](https://placehold.it/200x50 "Image Title") 232 | -------------------------------------------------------------------------------- /content/posts/my-second-post.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: My Second Post! 3 | date: "2015-05-06T23:46:37.121Z" 4 | --- 5 | 6 | Wow! I love blogging so much already. 7 | 8 | Did you know that "despite its name, salted duck eggs can also be made from 9 | chicken eggs, though the taste and texture will be somewhat different, and the 10 | egg yolk will be less rich."? 11 | ([Wikipedia Link](https://en.wikipedia.org/wiki/Salted_duck_egg)) 12 | 13 | Yeah, I didn't either. 14 | -------------------------------------------------------------------------------- /content/posts/new-beginnings.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: New Beginnings 3 | date: "2015-05-28T22:40:32.169Z" 4 | description: This is a custom description for SEO and Open Graph purposes, rather than the default generated excerpt. Simply add a description field to the frontmatter. 5 | --- 6 | 7 | Far far away, behind the word mountains, far from the countries Vokalia and 8 | Consonantia, there live the blind texts. Separated they live in Bookmarksgrove 9 | right at the coast of the Semantics, a large language ocean. A small river named 10 | Duden flows by their place and supplies it with the necessary regelialia. 11 | 12 | ## On deer horse aboard tritely yikes and much 13 | 14 | The Big Oxmox advised her not to do so, because there were thousands of bad 15 | Commas, wild Question Marks and devious Semikoli, but the Little Blind Text 16 | didn’t listen. She packed her seven versalia, put her initial into the belt and 17 | made herself on the way. 18 | 19 | - This however showed weasel 20 | - Well uncritical so misled 21 | - this is very interesting 22 | - Goodness much until that fluid owl 23 | 24 | When she reached the first hills of the **Italic Mountains**, she had a last 25 | view back on the skyline of her hometown _Bookmarksgrove_, the headline of 26 | [Alphabet Village](http://google.com) and the subline of her own road, the Line 27 | Lane. Pityful a rhetoric question ran over her cheek, then she continued her 28 | way. On her way she met a copy. 29 | 30 | ### Overlaid the jeepers uselessly much excluding 31 | 32 | But nothing the copy said could convince her and so it didn’t take long until a 33 | few insidious Copy Writers ambushed her, made her drunk with 34 | [Longe and Parole](http://google.com) and dragged her into their agency, where 35 | they abused her for their projects again and again. And if she hasn’t been 36 | rewritten, then they are still using her. 37 | 38 | > Far far away, behind the word mountains, far from the countries Vokalia and 39 | > Consonantia, there live the blind texts. Separated they live in Bookmarksgrove 40 | > right at the coast of the Semantics, a large language ocean. 41 | 42 | It is a paradisematic country, in which roasted parts of sentences fly into your 43 | mouth. Even the all-powerful Pointing has no control about the blind texts it is 44 | an almost unorthographic life One day however a small line of blind text by the 45 | name of Lorem Ipsum decided to leave for the far World of Grammar. 46 | 47 | ### According a funnily until pre-set or arrogant well cheerful 48 | 49 | The Big Oxmox advised her not to do so, because there were thousands of bad 50 | Commas, wild Question Marks and devious Semikoli, but the Little Blind Text 51 | didn’t listen. She packed her seven versalia, put her initial into the belt and 52 | made herself on the way. 53 | 54 | 1. So baboon this 55 | 2. Mounted militant weasel gregariously admonishingly straightly hey 56 | 3. Dear foresaw hungry and much some overhung 57 | 4. Rash opossum less because less some amid besides yikes jeepers frenetic 58 | impassive fruitlessly shut 59 | 60 | When she reached the first hills of the Italic Mountains, she had a last view 61 | back on the skyline of her hometown Bookmarksgrove, the headline of Alphabet 62 | Village and the subline of her own road, the Line Lane. Pityful a rhetoric 63 | question ran over her cheek, then she continued her way. On her way she met a 64 | copy. 65 | 66 | > The copy warned the Little Blind Text, that where it came from it would have 67 | > been rewritten a thousand times and everything that was left from its origin 68 | > would be the word "and" and the Little Blind Text should turn around and 69 | > return to its own, safe country. 70 | 71 | But nothing the copy said could convince her and so it didn’t take long until a 72 | few insidious Copy Writers ambushed her, made her drunk with Longe and Parole 73 | and dragged her into their agency, where they abused her for their projects 74 | again and again. And if she hasn’t been rewritten, then they are still using 75 | her. Far far away, behind the word mountains, far from the countries Vokalia and 76 | Consonantia, there live the blind texts. 77 | 78 | #### Silent delightfully including because before one up barring chameleon 79 | 80 | Separated they live in Bookmarksgrove right at the coast of the Semantics, a 81 | large language ocean. A small river named Duden flows by their place and 82 | supplies it with the necessary regelialia. It is a paradisematic country, in 83 | which roasted parts of sentences fly into your mouth. 84 | 85 | Even the all-powerful Pointing has no control about the blind texts it is an 86 | almost unorthographic life One day however a small line of blind text by the 87 | name of Lorem Ipsum decided to leave for the far World of Grammar. The Big Oxmox 88 | advised her not to do so, because there were thousands of bad Commas, wild 89 | Question Marks and devious Semikoli, but the Little Blind Text didn’t listen. 90 | 91 | ##### Wherever far wow thus a squirrel raccoon jeez jaguar this from along 92 | 93 | She packed her seven versalia, put her initial into the belt and made herself on 94 | the way. When she reached the first hills of the Italic Mountains, she had a 95 | last view back on the skyline of her hometown Bookmarksgrove, the headline of 96 | Alphabet Village and the subline of her own road, the Line Lane. Pityful a 97 | rhetoric question ran over her cheek, then she continued her way. On her way she 98 | met a copy. 99 | 100 | ###### Slapped cozy a that lightheartedly and far 101 | 102 | The copy warned the Little Blind Text, that where it came from it would have 103 | been rewritten a thousand times and everything that was left from its origin 104 | would be the word "and" and the Little Blind Text should turn around and return 105 | to its own, safe country. But nothing the copy said could convince her and so it 106 | didn’t take long until a few insidious Copy Writers ambushed her, made her drunk 107 | with Longe and Parole and dragged her into their agency, where they abused her 108 | for their projects again and again. 109 | -------------------------------------------------------------------------------- /gatsby-browser.js: -------------------------------------------------------------------------------- 1 | import './src/styles/global.css'; -------------------------------------------------------------------------------- /gatsby-config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | siteMetadata: { 3 | siteUrl: "http://localhost/", 4 | title: "Simple Blog", 5 | description: "学习 Gatsby 过程做出来的简单博客 😶", 6 | navlink: [ 7 | { url: "/", label: "首页" }, 8 | { url: "/about", label: "关于" }, 9 | { url: "https://github.com/whatk233/gatsby-simple-tailwindcss-blog", label: "Github" }, 10 | ], 11 | }, 12 | plugins: [ 13 | "gatsby-plugin-react-helmet", 14 | "gatsby-plugin-postcss", 15 | "gatsby-plugin-sitemap", 16 | "gatsby-plugin-image", 17 | "gatsby-plugin-sharp", 18 | { 19 | resolve: `gatsby-transformer-remark`, 20 | options: { 21 | plugins: [ 22 | { 23 | resolve: `gatsby-remark-images`, 24 | options: { 25 | // It's important to specify the maxWidth (in pixels) of 26 | // the content container as this plugin uses this as the 27 | // base for generating different widths of each image. 28 | maxWidth: 590, 29 | }, 30 | }, 31 | ], 32 | }, 33 | }, 34 | { 35 | resolve: "gatsby-plugin-manifest", 36 | options: { 37 | icon: "src/images/icon.png", 38 | }, 39 | }, 40 | // pages createFilePath() slug is "/" 41 | { 42 | resolve: `gatsby-source-filesystem`, 43 | options: { 44 | path: `${__dirname}/content/pages`, 45 | name: `pages`, 46 | }, 47 | }, 48 | { 49 | resolve: `gatsby-source-filesystem`, 50 | options: { 51 | path: `${__dirname}/content`, 52 | name: `content`, 53 | }, 54 | }, 55 | { 56 | resolve: `gatsby-plugin-emotion`, 57 | options: { 58 | // Accepts the following options, all of which are defined by `@emotion/babel-plugin` plugin. 59 | // The values for each key in this example are the defaults the plugin uses. 60 | sourceMap: true, 61 | autoLabel: "dev-only", 62 | labelFormat: `[local]`, 63 | cssPropOptimization: true, 64 | }, 65 | }, 66 | ], 67 | }; 68 | -------------------------------------------------------------------------------- /gatsby-node.js: -------------------------------------------------------------------------------- 1 | const path = require(`path`); 2 | const { createFilePath } = require(`gatsby-source-filesystem`); 3 | 4 | exports.createPages = async ({ graphql, actions }) => { 5 | const { createPage } = actions; 6 | 7 | const postsListTemplate = path.resolve(`./src/templates/index.js`); 8 | const blogPostTemplate = path.resolve(`./src/templates/blog-post.js`); 9 | const blogPageTemplate = path.resolve(`./src/templates/blog-page.js`); 10 | const result = await graphql( 11 | ` 12 | query MyQuery { 13 | posts: allMarkdownRemark( 14 | sort: { order: DESC, fields: frontmatter___date } 15 | filter: { fileAbsolutePath: { regex: "/content/posts/" } } 16 | limit: 1000 17 | ) { 18 | nodes { 19 | id 20 | frontmatter { 21 | title 22 | description 23 | date(formatString: "YYYY.MM.DD") 24 | } 25 | fields { 26 | slug 27 | } 28 | excerpt 29 | } 30 | } 31 | pages: allMarkdownRemark( 32 | sort: { order: DESC, fields: frontmatter___date } 33 | filter: { fileAbsolutePath: { regex: "/content/pages/" } } 34 | limit: 1000 35 | ) { 36 | nodes { 37 | id 38 | fields { 39 | slug 40 | } 41 | } 42 | } 43 | site { 44 | siteMetadata { 45 | title 46 | siteUrl 47 | description 48 | navlink { 49 | label 50 | url 51 | } 52 | } 53 | } 54 | } 55 | ` 56 | ); 57 | 58 | if (result.errors) { 59 | throw result.errors; 60 | } 61 | 62 | // Create single pages. 63 | const pages = result.data.pages.nodes; 64 | if (pages.length > 0) { 65 | pages.forEach((page) => { 66 | createPage({ 67 | path: page.fields.slug, 68 | component: blogPageTemplate, 69 | context: { 70 | id: page.id, 71 | }, 72 | }); 73 | }); 74 | } 75 | 76 | // Create blog posts pages. 77 | const posts = result.data.posts.nodes; 78 | if (posts.length > 0) { 79 | posts.forEach((post, index) => { 80 | const previousPostId = 81 | index === posts.length - 1 ? null : posts[index + 1].id; 82 | const nextPostId = index === 0 ? null : posts[index - 1].id; 83 | createPage({ 84 | path: post.fields.slug, 85 | component: blogPostTemplate, 86 | context: { 87 | id: post.id, 88 | previousPostId, 89 | nextPostId, 90 | }, 91 | }); 92 | }); 93 | } 94 | 95 | // Create pagination for posts list 96 | if (posts.length > 0) { 97 | // 每页显示的文章数量 98 | const postsOnPage = 8; 99 | // 获取文章总数 100 | const postsCount = Number(posts.length); 101 | // 需要页数 102 | const postsPagesCount = Math.ceil(postsCount / postsOnPage); 103 | // 文章列表 104 | let postsLists = []; 105 | // 当前文章顺序 106 | let nowPostRank = 0; 107 | // 分页生成文章列表 108 | for (let nowPage = 0; nowPage < postsPagesCount; nowPage++) { 109 | // 生成当前页面文章列表 110 | let nowPagePostsList = []; 111 | for (let index = 0; index < postsOnPage; index++) { 112 | // 将文章信息加入到当前列表中 113 | nowPagePostsList.push(posts[nowPostRank]); 114 | nowPostRank++; 115 | // 如果已到文章总数 116 | if (nowPostRank >= posts.length) { 117 | break; 118 | } 119 | } 120 | postsLists.push(nowPagePostsList); 121 | } 122 | // 创建分页 123 | postsLists.forEach((postsList, index) => { 124 | createPage({ 125 | path: index == 0 ? "/" : `/page/${index + 1}`, 126 | component: postsListTemplate, 127 | context: { 128 | sitedata: result.data.site, 129 | posts: postsList, 130 | nowPageNum: index + 1, 131 | allPagesNum: postsPagesCount, 132 | previousPage: index == 0 ? null : index == 1 ? "/" : `/page/${index}`, 133 | nextPage: 134 | index < postsPagesCount - 1 135 | ? index == 0 136 | ? "/page/2" 137 | : `/page/${index + 2}` 138 | : null, 139 | }, 140 | }); 141 | }); 142 | } 143 | }; 144 | 145 | exports.onCreateNode = ({ node, actions, getNode }) => { 146 | // from https://github.com/keithnull/gatsby-starter-breeze/blob/HEAD/gatsby-node.js 147 | const { createNodeField } = actions; 148 | if (node.internal.type === `MarkdownRemark`) { 149 | // slug 150 | let slug = node.frontmatter.slug || createFilePath({ node, getNode }); 151 | createNodeField({ 152 | node, 153 | name: "slug", 154 | value: slug, 155 | }); 156 | // date: front matter -> file name -> default date 157 | try { 158 | var date = node.frontmatter.date; 159 | if (!date) { 160 | const filename = node.fileAbsolutePath 161 | .split(/.*[\/|\\]/)[1] 162 | .split(".")[0]; 163 | date = new Date(filename.substring(0, 10)); 164 | if (isNaN(date)) { 165 | throw "Invalid Date"; 166 | } 167 | } 168 | } catch (error) { 169 | console.warn( 170 | "Failed to get date from frontmatter or filename, use default date instead.", 171 | { 172 | slug: slug, 173 | filepath: node.fileAbsolutePath, 174 | error: error, 175 | } 176 | ); 177 | date = new Date("1999-11-26"); 178 | } finally { 179 | createNodeField({ 180 | node, 181 | name: "date", 182 | value: date, 183 | }); 184 | } 185 | } 186 | }; 187 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "gatsby-simple-tailwindcss-blog", 3 | "version": "1.0.0", 4 | "private": true, 5 | "description": "a simple gatsby blog.", 6 | "author": "Whatk ", 7 | "bugs": { 8 | "url": "https://github.com/whatk233/gatsby-simple-tailwindcss-blog/issues" 9 | }, 10 | "scripts": { 11 | "develop": "gatsby develop", 12 | "start": "gatsby develop", 13 | "build": "gatsby build", 14 | "serve": "gatsby serve", 15 | "clean": "gatsby clean" 16 | }, 17 | "dependencies": { 18 | "@emotion/react": "^11.4.0", 19 | "@emotion/styled": "^11.3.0", 20 | "@tailwindcss/typography": "^0.4.1", 21 | "autoprefixer": "^10.3.1", 22 | "gatsby": "^3.9.1", 23 | "gatsby-plugin-emotion": "^6.9.0", 24 | "gatsby-plugin-image": "^1.10.1", 25 | "gatsby-plugin-manifest": "^3.9.0", 26 | "gatsby-plugin-postcss": "^4.9.0", 27 | "gatsby-plugin-react-helmet": "^4.9.0", 28 | "gatsby-plugin-sharp": "^3.10.2", 29 | "gatsby-plugin-sitemap": "^4.5.0", 30 | "gatsby-remark-images": "^5.7.0", 31 | "gatsby-source-filesystem": "^3.10.0", 32 | "gatsby-transformer-remark": "^4.6.0", 33 | "gatsby-transformer-sharp": "^3.10.0", 34 | "postcss": "^8.3.5", 35 | "react": "^17.0.1", 36 | "react-dom": "^17.0.1", 37 | "react-helmet": "^6.1.0", 38 | "tailwindcss": "^2.2.4" 39 | }, 40 | "homepage": "https://github.com/gatsbyjs/gatsby-starter-blog#readme", 41 | "keywords": [ 42 | "gatsby" 43 | ], 44 | "license": "MIT", 45 | "repository": { 46 | "type": "git", 47 | "url": "git+https://github.com/whatk233/gatsby-simple-tailwindcss-blog.git" 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /src/components/Footer.js: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | 3 | const Footer = ({ sitedata }) => { 4 | return ( 5 | 21 | ); 22 | }; 23 | 24 | export default Footer; 25 | -------------------------------------------------------------------------------- /src/components/Header.js: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { Link } from "gatsby"; 3 | 4 | const Header = ({ sitedata, pagedata }) => { 5 | // switch btn color 6 | const switchBtnColor = 7 | typeof window !== "undefined" 8 | ? localStorage.theme === "dark" 9 | ? "text-white" 10 | : null 11 | : null; 12 | 13 | const switchTheme = () => { 14 | if (typeof window !== "undefined") { 15 | document.body.classList.add("transition"); 16 | if (localStorage.theme === "dark") { 17 | document.body.classList.remove("dark"); 18 | document.body.classList.remove("bg-gray-800"); 19 | document 20 | .getElementById("switchThemeBtn") 21 | .classList.remove("text-white"); 22 | localStorage.removeItem("theme"); 23 | } else { 24 | document.body.classList.add("dark"); 25 | document.body.classList.add("bg-gray-800"); 26 | document.getElementById("switchThemeBtn").classList.add("text-white"); 27 | localStorage.theme = "dark"; 28 | } 29 | } 30 | }; 31 | return ( 32 | <> 33 | {pagedata ? ( 34 | // page header 35 |
36 |
37 | {sitedata.siteMetadata.title} 38 |
39 |

{pagedata.title}

40 | {pagedata.date} 41 |
42 | ) : ( 43 | // home header 44 |
45 |

46 | {sitedata.siteMetadata.title} 47 |

48 |
49 | {sitedata.siteMetadata.description} 50 |
51 |
52 | )} 53 | 95 | 96 | ); 97 | }; 98 | 99 | export default Header; 100 | -------------------------------------------------------------------------------- /src/components/Layout.js: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { useEffect } from "react"; 3 | import Header from "./Header"; 4 | import Footer from "./Footer"; 5 | 6 | const Layout = ({ sitedata, pagedata, children }) => { 7 | useEffect(() => { 8 | // switch dark mode 9 | // transition class 10 | if (typeof window !== "undefined") { 11 | if ( 12 | localStorage.theme === "dark" || 13 | (!("theme" in localStorage) && 14 | window.matchMedia("(prefers-color-scheme: dark)").matches) 15 | ) { 16 | document.body.classList.add("dark"); 17 | document.body.classList.add("bg-gray-800"); 18 | } else { 19 | document.body.classList.remove("dark"); 20 | document.body.classList.remove("bg-gray-800"); 21 | } 22 | } 23 | }, []); 24 | return ( 25 |
26 |
27 |
28 |
29 |
{children}
30 |
31 |
32 |
33 |
34 | ); 35 | }; 36 | 37 | export default Layout; 38 | -------------------------------------------------------------------------------- /src/components/PostCard.js: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { Link } from "gatsby"; 3 | 4 | const PostCard = ({ post }) => { 5 | const postData = post.frontmatter; 6 | return ( 7 |
8 |

9 | {postData.title} 10 |

11 | 12 | {postData.date} 13 | 14 | {/* 无描述则抽正文截断 */} 15 |

16 | {postData.description || post.excerpt} 17 |

18 |
19 | ); 20 | }; 21 | 22 | export default PostCard; 23 | -------------------------------------------------------------------------------- /src/components/pagination.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Link } from "gatsby"; 3 | // 😵 4 | const Pagination = ({ allPagesNum, nextPage, previousPage, nowPageNum }) => { 5 | // icon 6 | const prevIcon = ( 7 | 20 | ); 21 | const nextIcon = ( 22 | 35 | ); 36 | // btnStyle 37 | const normalStyle = 38 | "bg-white border-gray-300 text-gray-500 hover:bg-gray-50 relative inline-flex items-center px-4 py-2 border text-sm font-medium"; 39 | const activeStyle = 40 | "z-10 bg-indigo-50 border-indigo-500 text-indigo-600 relative inline-flex items-center px-4 py-2 border text-sm font-medium"; 41 | const prevBtnStyle = 42 | "relative inline-flex items-center px-2 py-2 rounded-l-md border border-gray-300 bg-white text-sm font-medium text-gray-500 hover:bg-gray-50"; 43 | const nextBtnStyle = 44 | "relative inline-flex items-center px-2 py-2 rounded-r-md border border-gray-300 bg-white text-sm font-medium text-gray-500 hover:bg-gray-50"; 45 | const prevBtnDisableStyle = 46 | "opacity-50 relative inline-flex items-center px-2 py-2 rounded-l-md border border-gray-300 bg-white text-sm font-medium text-gray-500"; 47 | const nextBtnDisableStyle = 48 | "opacity-50 relative inline-flex items-center px-2 py-2 rounded-r-md border border-gray-300 bg-white text-sm font-medium text-gray-500"; 49 | // render items btn 50 | let items = []; 51 | for (let i = 1; i <= allPagesNum; i++) { 52 | items.push( 53 | i === nowPageNum ? ( 54 | 55 | {i} 56 | 57 | ) : ( 58 | 59 | {i} 60 | 61 | ) 62 | ); 63 | } 64 | 65 | return ( 66 |
67 |
68 |
69 | 99 |
100 |
101 |
102 | ); 103 | }; 104 | 105 | export default Pagination; 106 | -------------------------------------------------------------------------------- /src/components/seo.js: -------------------------------------------------------------------------------- 1 | /** 2 | * SEO component that queries for data with 3 | * Gatsby's useStaticQuery React hook 4 | * 5 | * See: https://www.gatsbyjs.com/docs/use-static-query/ 6 | */ 7 | 8 | import * as React from "react"; 9 | import PropTypes from "prop-types"; 10 | import { Helmet } from "react-helmet"; 11 | import { useStaticQuery, graphql } from "gatsby"; 12 | 13 | const Seo = ({ description, lang, meta, title }) => { 14 | const { site } = useStaticQuery( 15 | graphql` 16 | query { 17 | site { 18 | siteMetadata { 19 | title 20 | description 21 | } 22 | } 23 | } 24 | ` 25 | ); 26 | 27 | const metaDescription = description || site.siteMetadata.description; 28 | const defaultTitle = site.siteMetadata?.title; 29 | 30 | return ( 31 | 68 | ); 69 | }; 70 | 71 | Seo.defaultProps = { 72 | lang: `en`, 73 | meta: [], 74 | description: ``, 75 | }; 76 | 77 | Seo.propTypes = { 78 | description: PropTypes.string, 79 | lang: PropTypes.string, 80 | meta: PropTypes.arrayOf(PropTypes.object), 81 | title: PropTypes.string.isRequired, 82 | }; 83 | 84 | export default Seo; 85 | -------------------------------------------------------------------------------- /src/images/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/whatk233/gatsby-simple-tailwindcss-blog/bedd560804132b750055b4f483d2ccb9a2d02b31/src/images/icon.png -------------------------------------------------------------------------------- /src/pages/404.js: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | import { Link } from "gatsby" 3 | 4 | // styles 5 | const pageStyles = { 6 | color: "#232129", 7 | padding: "96px", 8 | fontFamily: "-apple-system, Roboto, sans-serif, serif", 9 | } 10 | const headingStyles = { 11 | marginTop: 0, 12 | marginBottom: 64, 13 | maxWidth: 320, 14 | } 15 | 16 | const paragraphStyles = { 17 | marginBottom: 48, 18 | } 19 | const codeStyles = { 20 | color: "#8A6534", 21 | padding: 4, 22 | backgroundColor: "#FFF4DB", 23 | fontSize: "1.25rem", 24 | borderRadius: 4, 25 | } 26 | 27 | // markup 28 | const NotFoundPage = () => { 29 | return ( 30 |
31 | Not found 32 |

Page not found

33 |

34 | Sorry{" "} 35 | 36 | 😔 37 | {" "} 38 | we couldn’t find what you were looking for. 39 |
40 | {process.env.NODE_ENV === "development" ? ( 41 | <> 42 |
43 | Try creating a page in src/pages/. 44 |
45 | 46 | ) : null} 47 |
48 | Go home. 49 |

50 |
51 | ) 52 | } 53 | 54 | export default NotFoundPage 55 | -------------------------------------------------------------------------------- /src/styles/global.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | @layer base { 6 | h1 { 7 | @apply text-5xl; 8 | } 9 | h2 { 10 | @apply text-4xl; 11 | } 12 | h3 { 13 | @apply text-3xl; 14 | } 15 | h4 { 16 | @apply text-2xl; 17 | } 18 | h5 { 19 | @apply text-xl; 20 | } 21 | } -------------------------------------------------------------------------------- /src/templates/blog-page.js: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import Layout from "../components/Layout"; 3 | import Seo from "../components/seo"; 4 | import { graphql } from "gatsby"; 5 | 6 | const PageTemplates = ({ data }) => { 7 | const page = data.markdownRemark; 8 | const { site } = data; 9 | return ( 10 | 11 | 15 |
19 | 20 | ); 21 | }; 22 | 23 | export default PageTemplates; 24 | 25 | export const pageQuery = graphql` 26 | query BlogPageBySlug($id: String!) { 27 | site { 28 | siteMetadata { 29 | title 30 | siteUrl 31 | description 32 | navlink { 33 | label 34 | url 35 | } 36 | } 37 | } 38 | markdownRemark(id: { eq: $id }) { 39 | id 40 | excerpt(pruneLength: 160) 41 | html 42 | frontmatter { 43 | title 44 | date(formatString: "YYYY.MM.DD HH:mm:SS") 45 | description 46 | } 47 | } 48 | } 49 | `; 50 | -------------------------------------------------------------------------------- /src/templates/blog-post.js: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import Layout from "../components/Layout"; 3 | import Seo from "../components/seo"; 4 | import { Link, graphql } from "gatsby"; 5 | 6 | const PostPage = ({ data }) => { 7 | const post = data.markdownRemark; 8 | const { site, previous, next } = data; 9 | return ( 10 | 11 | 15 |
19 |
    20 |
  • 21 | {previous && ( 22 | 23 |
    24 | 31 | 37 | 38 |
    39 | {previous.frontmatter.title} 40 | 上一篇 41 |
    42 |
    43 | 44 | )} 45 |
  • 46 |
  • 47 | {next && ( 48 | 49 |
    50 |
    51 | {next.frontmatter.title} 52 | 下一篇 53 |
    54 | 61 | 67 | 68 |
    69 | 70 | )} 71 |
  • 72 |
73 | 74 | ); 75 | }; 76 | 77 | export default PostPage; 78 | 79 | export const pageQuery = graphql` 80 | query BlogPostBySlug( 81 | $id: String! 82 | $previousPostId: String 83 | $nextPostId: String 84 | ) { 85 | site { 86 | siteMetadata { 87 | title 88 | siteUrl 89 | description 90 | navlink { 91 | label 92 | url 93 | } 94 | } 95 | } 96 | markdownRemark(id: { eq: $id }) { 97 | id 98 | excerpt(pruneLength: 160) 99 | html 100 | frontmatter { 101 | title 102 | date(formatString: "YYYY.MM.DD HH:mm:SS") 103 | description 104 | } 105 | } 106 | previous: markdownRemark(id: { eq: $previousPostId }) { 107 | fields { 108 | slug 109 | } 110 | frontmatter { 111 | title 112 | } 113 | } 114 | next: markdownRemark(id: { eq: $nextPostId }) { 115 | fields { 116 | slug 117 | } 118 | frontmatter { 119 | title 120 | } 121 | } 122 | } 123 | `; 124 | -------------------------------------------------------------------------------- /src/templates/index.js: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import Layout from "../components/Layout"; 3 | import PostCard from "../components/PostCard"; 4 | import Seo from "../components/seo"; 5 | import Pagination from "../components/pagination"; 6 | 7 | const BlogIndex = ({ pageContext }) => { 8 | const { posts, sitedata, allPagesNum, nextPage, previousPage, nowPageNum } = pageContext; 9 | return ( 10 | 11 | 12 |
13 | {posts.map((node) => ( 14 | 15 | ))} 16 |
17 | 23 |
24 | ); 25 | }; 26 | 27 | export default BlogIndex; 28 | -------------------------------------------------------------------------------- /tailwind.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | purge: [ 3 | "./src/**/*.{js,jsx,ts,tsx}", 4 | "./content/pages/*.md", 5 | "./content/posts/*.md", 6 | ], 7 | darkMode: "class", // or 'media' or 'class' 8 | theme: { 9 | extend: { 10 | typography: (theme) => ({ 11 | // typography dark mode style 12 | dark: { 13 | css: [ 14 | { 15 | color: theme("colors.gray.200"), 16 | '[class~="lead"]': { 17 | color: theme("colors.gray.100"), 18 | }, 19 | a: { 20 | color: theme("colors.pink.500"), 21 | "&:hover": { 22 | color: theme("colors.pink.700"), 23 | }, 24 | }, 25 | strong: { 26 | color: theme("colors.white"), 27 | }, 28 | "ol > li::before": { 29 | color: theme("colors.gray.400"), 30 | }, 31 | "ul > li::before": { 32 | backgroundColor: theme("colors.gray.600"), 33 | }, 34 | hr: { 35 | borderColor: theme("colors.gray.200"), 36 | }, 37 | blockquote: { 38 | color: theme("colors.gray.200"), 39 | borderLeftColor: theme("colors.gray.600"), 40 | }, 41 | h1: { 42 | color: theme("colors.white"), 43 | }, 44 | h2: { 45 | color: theme("colors.white"), 46 | }, 47 | h3: { 48 | color: theme("colors.white"), 49 | }, 50 | h4: { 51 | color: theme("colors.white"), 52 | }, 53 | "figure figcaption": { 54 | color: theme("colors.gray.400"), 55 | }, 56 | code: { 57 | color: theme("colors.white"), 58 | }, 59 | "a code": { 60 | color: theme("colors.white"), 61 | }, 62 | pre: { 63 | color: theme("colors.gray.200"), 64 | backgroundColor: theme("colors.gray.700"), 65 | }, 66 | thead: { 67 | color: theme("colors.white"), 68 | borderBottomColor: theme("colors.gray.400"), 69 | }, 70 | "tbody tr": { 71 | borderBottomColor: theme("colors.gray.600"), 72 | }, 73 | }, 74 | ], 75 | }, 76 | }), 77 | }, 78 | }, 79 | variants: { 80 | extend: { 81 | fontWeight: ["hover", "focus"], 82 | }, 83 | typography: ["dark"], 84 | }, 85 | plugins: [require("@tailwindcss/typography")], 86 | }; 87 | --------------------------------------------------------------------------------