├── .babelrc.js ├── .eslintignore ├── .eslintrc.js ├── .gitignore ├── CHANGELOG.md ├── LICENSE.md ├── README.md ├── gatsby-browser.js ├── gatsby-config.js ├── gatsby-node.js ├── gatsby-ssr.js ├── package-lock.json ├── package.json └── src ├── components ├── App │ ├── index.jsx │ └── styled.js ├── Bio │ ├── index.jsx │ └── styled.js ├── CategorizedList │ └── index.jsx ├── Common │ ├── Card │ │ ├── index.jsx │ │ └── styled.js │ ├── Clearfix │ │ └── index.js │ ├── Pagination │ │ ├── index.jsx │ │ └── styled.js │ ├── PortfolioCard │ │ └── index.js │ ├── PostWrapper │ │ └── index.js │ ├── PostsWrapper │ │ └── index.js │ ├── SimpleWrapper │ │ └── index.js │ ├── Wrapper │ │ ├── index.js │ │ └── styled.js │ └── constants.js ├── Footer │ ├── index.jsx │ └── styled.js ├── Gnb │ ├── index.jsx │ └── styled.js ├── Home │ ├── index.jsx │ └── styled.js ├── List │ └── index.jsx ├── Portfolio │ ├── index.jsx │ └── styled.js ├── Portfolios │ ├── index.jsx │ └── styled.js ├── Post │ ├── index.jsx │ └── styled.js ├── Resume │ ├── index.jsx │ └── styled.js ├── TaggedList │ └── index.jsx └── layout │ ├── index.jsx │ └── styled.js ├── constants └── index.js ├── html.jsx ├── pages ├── .sample.md ├── 404.jsx ├── index.jsx ├── post-1 │ └── index.md ├── post-10 │ └── index.md ├── post-2 │ └── index.md ├── post-3 │ └── index.md ├── post-4 │ └── index.md ├── post-5 │ └── index.md ├── post-6 │ └── index.md ├── post-7 │ └── index.md ├── post-8 │ └── index.md └── post-9 │ └── index.md ├── postComponents └── sample │ └── index.jsx ├── reset.css ├── resources ├── images │ ├── 1.jpg │ └── 2.jpg ├── me.png ├── resume │ └── index.md ├── test-1 │ ├── 1.png │ ├── 2.png │ └── index.md ├── test-2 │ ├── 3.png │ ├── 4.png │ └── index.md ├── test-3 │ ├── 5.png │ ├── 6.png │ └── index.md └── test-4 │ ├── 7.png │ ├── 8.png │ ├── 9.png │ └── index.md ├── templates ├── CategorizedList.jsx ├── List.jsx ├── Portfolio.jsx ├── Portfolios.jsx ├── Post.jsx ├── Resume.jsx └── TaggedList.jsx └── utils ├── formattedDate.js ├── getPage.js └── getPosts.js /.babelrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | '@babel/preset-react', 4 | [ 5 | '@babel/preset-env', 6 | { 7 | corejs: '2', 8 | useBuiltIns: 'usage', 9 | }, 10 | ], 11 | ], 12 | plugins: [ 13 | [ 14 | '@babel/plugin-proposal-class-properties', 15 | { 16 | loose: true, 17 | }, 18 | ], 19 | [ 20 | '@babel/plugin-proposal-pipeline-operator', 21 | { 22 | proposal: 'minimal', 23 | }, 24 | ], 25 | '@babel/plugin-syntax-dynamic-import', 26 | ], 27 | }; 28 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | **/node_modules 2 | **/external_dependencies 3 | src/html.jsx 4 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | const off = 0; 2 | const warn = 1; 3 | const error = 2; 4 | 5 | module.exports = { 6 | extends: ['airbnb', 'plugin:import/errors', 'plugin:import/warnings'], 7 | plugins: ['react', 'import'], 8 | env: { 9 | es6: true, 10 | node: true, 11 | browser: true, 12 | }, 13 | rules: { 14 | 'comma-dangle': [ 15 | error, 16 | { 17 | arrays: 'always-multiline', 18 | objects: 'always-multiline', 19 | imports: 'always-multiline', 20 | exports: 'always-multiline', 21 | functions: 'only-multiline', 22 | }, 23 | ], 24 | 'function-paren-newline': [error, 'consistent'], 25 | 'global-require': off, 26 | 'implicit-arrow-linebreak': off, 27 | 'import/extensions': off, 28 | 'import/no-deprecated': warn, 29 | 'import/no-dynamic-require': off, 30 | 'import/no-unresolved': off, 31 | 'import/no-webpack-loader-syntax': off, 32 | 'import/prefer-default-export': off, 33 | indent: [ 34 | error, 35 | 2, 36 | { 37 | SwitchCase: 1, 38 | }, 39 | ], 40 | 'jsx-a11y/anchor-is-valid': off, 41 | 'jsx-a11y/click-events-have-key-events': error, 42 | 'jsx-a11y/heading-has-content': off, 43 | 'jsx-a11y/href-no-hash': off, 44 | 'jsx-a11y/label-has-for': off, 45 | 'jsx-a11y/label-has-associated-control': off, 46 | 'jsx-a11y/mouse-events-have-key-events': off, 47 | 'jsx-a11y/no-autofocus': off, 48 | 'max-len': [error, 150, { ignoreComments: true, ignoreStrings: true, ignoreTemplateLiterals: true }], 49 | 'no-console': error, 50 | 'no-lonely-if': off, 51 | 'no-multiple-empty-lines': [error, { max: error, maxEOF: error }], 52 | 'no-implicit-coercion': error, 53 | 'no-shadow': off, 54 | 'no-underscore-dangle': off, 55 | 'no-unused-vars': [error, { args: 'after-used', ignoreRestSiblings: false }], 56 | 'object-curly-newline': [error, { consistent: true }], 57 | 'prefer-spread': off, 58 | 'react/jsx-filename-extension': [error, { extensions: ['.js', '.jsx'] }], 59 | 'react/jsx-no-target-blank': error, 60 | 'react/no-typos': error, 61 | 'react/no-unescaped-entities': off, 62 | }, 63 | parser: 'babel-eslint', 64 | parserOptions: { 65 | ecmaVersion: 6, 66 | sourceType: 'module', 67 | allowImportExportEverywhere: false, 68 | ecmaFeatures: { 69 | jsx: true, 70 | modules: true, 71 | globalReturn: false, 72 | experimentalObjectRestSpread: true, 73 | }, 74 | babelOptions: { 75 | configFile: '.babelrc.js', 76 | }, 77 | }, 78 | overrides: [ 79 | { 80 | files: ['gatsby-node.js'], 81 | rules: { 82 | 'import/no-extraneous-dependencies': off, 83 | }, 84 | }, 85 | { 86 | files: ['src/components/Resume/index.jsx', 'src/components/Portfolio/index.jsx', 'src/components/Post/index.jsx', 'src/templates/*.jsx'], 87 | rules: { 88 | 'react/no-danger': off, 89 | }, 90 | }, 91 | { 92 | files: ['src/html.jsx'], 93 | rules: { 94 | 'react/prefer-stateless-function': off, 95 | 'react/prop-types': off, 96 | 'react/no-danger': off, 97 | }, 98 | }, 99 | { 100 | files: ['src/**/*.test.js'], 101 | rules: {}, 102 | }, 103 | ], 104 | settings: { 105 | react: { 106 | pragma: 'React', 107 | version: '16.8.5', 108 | } 109 | } 110 | }; 111 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # OS X 2 | .DS_Store* 3 | Icon? 4 | ._* 5 | 6 | # Windows 7 | Thumbs.db 8 | ehthumbs.db 9 | Desktop.ini 10 | 11 | # Linux 12 | .directory 13 | *~ 14 | 15 | # npm 16 | node_modules/ 17 | *.log 18 | *.gz 19 | build/ 20 | 21 | # configure 22 | config/ 23 | .env.development 24 | .env.production 25 | 26 | # log 27 | dump.rdb 28 | nohup.out 29 | 30 | # dotenv 31 | .env 32 | 33 | # virtualenv 34 | .venv/ 35 | venv/ 36 | ENV/ 37 | 38 | # cached files 39 | .cache/ 40 | 41 | # generated files 42 | public/ 43 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## TODO 4 | - [ ] use typescript for typing 5 | 6 | ## 1.1.5 - 2019-07-28 7 | - [x] remove `filter` style of body 8 | 9 | ## 1.1.4 - 2019-06-16 10 | - [x] add sitemap.xml & robots.txt 11 | 12 | ## 1.1.3 - 2019-06-07 13 | - [x] update packages to fix dependency error 14 | - [x] fix typo in README 15 | 16 | ## 1.1.2 - 2019-04-07 17 | - [x] fix ui bug (dropdown in GNB) 18 | 19 | ## 1.1.1 - 2019-04-07 20 | - [x] add theming features 21 | 22 | ## 1.0.0 - 2019-04-06 (unpublished) 23 | - [x] update packages (React, Gatsby, etc.) 24 | - [x] use hooks instead of redux 25 | 26 | ## 0.2.3 - 2018-09-26 27 | - [x] update `gatsby` version (`v2.0.8`) 28 | 29 | ## 0.2.2 - 2018-08-24 30 | - [x] fix issue that is related with media query for print ((ISSUE 11)[https://github.com/wonism/gatsby-advanced-blog/issues/11]) 31 | 32 | ## 0.2.1 - 2018-08-10 33 | - [x] update `gatsby` to use `Link` in `gatsby` instead of `gatsby-link` ((ISSUE 8)[https://github.com/wonism/gatsby-advanced-blog/issues/8]) 34 | - [x] remove all `css`s and add styles into `Styled Components`. ((ISSUE 9)[https://github.com/wonism/gatsby-advanced-blog/issues/9]) 35 | - [x] fix SSR ((ISSUE 9)[https://github.com/wonism/gatsby-advanced-blog/issues/9]) 36 | - [x] update `.eslintrc` to change some rules 37 | 38 | ## 0.2.0-1 - 2018-07-22 39 | - [x] add `LICENCE.md` 40 | 41 | ## 0.2.0 - 2018-07-08 42 | - [x] migrate `gatsby` into `v2 beta` (from `v2 alpha`) 43 | - [x] add `gatsby` with specific `version` into `peerDependencies` 44 | - [x] use `css` instead of `less` 45 | - [x] update `dependencides` 46 | - [x] `babel`, `redux`, `react` etcetera 47 | 48 | ## 0.1.9 - 2018-04-22 49 | - [x] fix bug (post's html) 50 | - [x] remove console 51 | - [x] change eslint rules 52 | 53 | ## 0.1.7 - 2018-04-22 54 | - [x] fix bug (related with `styled-components`) 55 | - [x] change logics for adding image into post 56 | - [x] add `react-helmet` into some pages 57 | - [x] change some styles 58 | - [x] add font(`Inconsolata`) for ``'s `font-family` 59 | 60 | ## 0.1.6 - 2018-04-14 61 | - [x] fix lint 62 | - [x] add husky for hooking commit 63 | 64 | ## 0.1.5 - 2018-04-06 65 | - [x] remove `height: 100vh;` 66 | 67 | ## 0.1.4 - 2018-04-05 68 | - [x] fix ui bug in production mode 69 | 70 | ## 0.1.3 - 2018-04-04 71 | - [x] add image to post 72 | - [x] add copy button in post 73 | - [x] add `border-radius` to profile image 74 | - [x] fix card UI (tags are overflowed) 75 | - [x] improve post readablity 76 | - [x] move logics for rendering tweets & components into `redux-saga` 77 | 78 | ## 0.1.2-alpha - 2018-04-03 79 | - [x] update `READEME.md` 80 | - [x] customize `404` page 81 | - [x] add `min-height` to contents wrapper 82 | 83 | ## 0.1.1-alpha - 2018-04-03 84 | - [x] support mobile version 85 | 86 | ## 0.1.0-alpha - 2018-03-29 87 | - [x] initial commit 88 | - [x] features 89 | - [x] Post 90 | - [x] Pagination 91 | - [x] Categories 92 | - [x] Tags 93 | - [x] Search 94 | - [x] Put React Application into post 95 | - [x] Put Tweet into post 96 | - [x] Draft (set `hide` to `true`) 97 | - [x] Portfolio 98 | - [x] Resume 99 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2018 wonism 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. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # gatsby-advanced-blog 2 | > Gatsby starter for advanced blog 3 | 4 | ## Install 5 | ``` 6 | $ git clone git@github.com:wonism/gatsby-advanced-blog.git <> 7 | # Recommend you to delete `.git` 8 | $ cd <> && rm -r .git 9 | ``` 10 | 11 | ## Start with gatsby-cli 12 | ``` 13 | $ gatsby new <> https://github.com/wonism/gatsby-advanced-blog 14 | ``` 15 | 16 | ## Development 17 | ``` 18 | $ npm run dev 19 | ``` 20 | 21 | ## Build 22 | ``` 23 | $ npm run build 24 | ``` 25 | 26 | ## ⚠️ Caution 27 | You **SHOULD** change some constants variable in `src/constants/index.js`. 28 | They are related with site informations, social media and Disqus. 29 | 30 | ## Features 31 | - Post 32 | - Pagination 33 | - Categories 34 | - Tags 35 | - Search 36 | - Put React Application into post 37 | - Put Tweet into post 38 | - Draft (set `hide` to `true`) 39 | - Copy codes with clicking button 40 | - Portfolio 41 | - Resume 42 | - UI Theme 43 | -------------------------------------------------------------------------------- /gatsby-browser.js: -------------------------------------------------------------------------------- 1 | // import '@babel/polyfill'; 2 | 3 | export const onClientEntry = () => { 4 | console.log('%cStart Gatsby Advanced Blog!', 'display: block; color: #9f63f0; font-size: 40px;'); 5 | }; 6 | -------------------------------------------------------------------------------- /gatsby-config.js: -------------------------------------------------------------------------------- 1 | const { TITLE, AUTHOR, SITE_URL } = require('./src/constants'); 2 | 3 | const siteMetadata = { 4 | title: TITLE, 5 | author: AUTHOR, 6 | homepage: SITE_URL, 7 | siteUrl: SITE_URL, 8 | }; 9 | 10 | module.exports = { 11 | siteMetadata, 12 | pathPrefix: '/', 13 | plugins: [ 14 | { 15 | resolve: 'gatsby-source-filesystem', 16 | options: { 17 | path: `${__dirname}/src/pages`, 18 | name: 'pages', 19 | }, 20 | }, 21 | { 22 | resolve: 'gatsby-source-filesystem', 23 | options: { 24 | path: `${__dirname}/src/resources`, 25 | name: 'resources', 26 | }, 27 | }, 28 | { 29 | resolve: 'gatsby-transformer-remark', 30 | options: { 31 | plugins: [ 32 | { 33 | resolve: 'gatsby-remark-images', 34 | options: { 35 | maxWidth: 640, 36 | }, 37 | }, 38 | { 39 | resolve: 'gatsby-remark-responsive-iframe', 40 | options: { 41 | wrapperStyle: 'margin-bottom: 1.0725rem', 42 | }, 43 | }, 44 | { 45 | resolve: 'gatsby-remark-prismjs', 46 | options: { 47 | classPrefix: 'hljs-', 48 | }, 49 | }, 50 | 'gatsby-remark-copy-linked-files', 51 | 'gatsby-remark-smartypants', 52 | ], 53 | }, 54 | }, 55 | { 56 | resolve: 'gatsby-plugin-google-analytics', 57 | options: { 58 | trackingId: '<>', 59 | }, 60 | }, 61 | 'gatsby-plugin-offline', 62 | 'gatsby-plugin-react-helmet', 63 | 'gatsby-plugin-styled-components', 64 | { 65 | resolve: 'gatsby-plugin-manifest', 66 | options: { 67 | name: 'Gatsby Advanced Blog', 68 | short_name: 'Gatsby Blog', 69 | start_url: '/', 70 | background_color: '#fff', 71 | theme_color: '#3B9CFF', 72 | display: 'minimal-ui', 73 | /* 74 | icons: [{ 75 | // Everything in /static will be copied to an equivalent 76 | // directory in /public during development and build, so 77 | // assuming your favicons are in /static/favicons, 78 | // you can reference them here 79 | src: `/favicons/android-chrome-192x192.png`, 80 | sizes: `192x192`, 81 | type: `image/png`, 82 | }, { 83 | src: `/favicons/android-chrome-512x512.png`, 84 | sizes: `512x512`, 85 | type: `image/png`, 86 | }], 87 | */ 88 | }, 89 | }, 90 | 'gatsby-plugin-sitemap', 91 | { 92 | resolve: 'gatsby-plugin-robots-txt', 93 | options: { 94 | host: SITE_URL, 95 | sitemap: `${SITE_URL}/sitemap.xml`, 96 | policy: [{ userAgent: '*', allow: '/' }], 97 | }, 98 | }, 99 | ], 100 | }; 101 | -------------------------------------------------------------------------------- /gatsby-node.js: -------------------------------------------------------------------------------- 1 | require('dotenv').config({ 2 | path: `.env.${process.env.NODE_ENV}`, 3 | }); 4 | 5 | const path = require('path'); 6 | const { createFilePath } = require('gatsby-source-filesystem'); 7 | const { 8 | CONTENT_PER_PAGE, 9 | POST, 10 | PORTFOLIO, 11 | RESUME, 12 | } = require('./src/constants'); 13 | 14 | exports.onCreateWebpackConfig = ({ 15 | stage, 16 | plugins, 17 | actions, 18 | }) => { 19 | actions.setWebpackConfig({ 20 | externals: { 21 | document: true, 22 | discus_config: true, 23 | }, 24 | resolve: { 25 | alias: { 26 | '~': path.resolve(__dirname, 'src'), 27 | }, 28 | }, 29 | plugins: [ 30 | plugins.define({ 31 | __DEVELOPMENT__: stage === 'develop' || stage === 'develop-html', 32 | }), 33 | ], 34 | }); 35 | }; 36 | 37 | exports.createPages = ({ graphql, actions }) => { 38 | const { createPage } = actions; 39 | 40 | return new Promise((resolve, reject) => { 41 | const post = path.resolve('./src/templates/Post.jsx'); 42 | const list = path.resolve('./src/templates/List.jsx'); 43 | const taggedList = path.resolve('./src/templates/TaggedList.jsx'); 44 | const categorizedList = path.resolve('./src/templates/CategorizedList.jsx'); 45 | const resume = path.resolve('./src/templates/Resume.jsx'); 46 | const portfolios = path.resolve('./src/templates/Portfolios.jsx'); 47 | const portfolio = path.resolve('./src/templates/Portfolio.jsx'); 48 | 49 | resolve( 50 | graphql(` 51 | { 52 | allMarkdownRemark(limit: 10000) { 53 | edges { 54 | node { 55 | frontmatter { 56 | path 57 | category 58 | tags 59 | type 60 | hide 61 | } 62 | } 63 | } 64 | } 65 | } 66 | `).then(({ errors, data: { allMarkdownRemark: { edges } } }) => { 67 | if (errors) { 68 | console.log(errors); // eslint-disable-line no-console 69 | reject(errors); 70 | } 71 | 72 | const tagMatrix = []; 73 | const categoryMatrix = []; 74 | 75 | // Create blog posts pages. 76 | edges.forEach(({ node: { frontmatter: { path, tags, category, type, hide } } }) => { 77 | if (hide !== true) { 78 | if (Array.isArray(tags)) { 79 | tagMatrix.push(tags); 80 | } 81 | 82 | if (typeof category === 'string') { 83 | categoryMatrix.push(category); 84 | } 85 | 86 | let component = null; 87 | switch (type) { 88 | case PORTFOLIO: 89 | component = portfolio; 90 | break; 91 | case RESUME: 92 | component = resume; 93 | break; 94 | case POST: 95 | default: 96 | component = post; 97 | break; 98 | } 99 | 100 | if (component !== null) { 101 | createPage({ 102 | path, 103 | component, 104 | context: {}, 105 | }); 106 | } 107 | } 108 | }); 109 | 110 | const portfoliosCount = edges 111 | .filter(({ node: { frontmatter: { type } } }) => (type === PORTFOLIO)) 112 | .length; 113 | 114 | if (portfoliosCount) { 115 | createPage({ 116 | path: '/portfolios', 117 | component: portfolios, 118 | context: { 119 | }, 120 | }); 121 | } 122 | 123 | const postsCount = edges 124 | .filter(({ node: { frontmatter: { hide, type } } }) => (!hide && (type || POST) === POST)) 125 | .length; 126 | const pagesCount = postsCount ? (Math.ceil(postsCount / CONTENT_PER_PAGE) + 1) : 1; 127 | const pages = Array.from(new Array(pagesCount), (el, i) => i + 1); 128 | 129 | if (pages.length > 0) { 130 | pages.forEach((page) => { 131 | createPage({ 132 | path: `/pages/${page}`, 133 | component: list, 134 | context: { 135 | }, 136 | }); 137 | }); 138 | } else { 139 | createPage({ 140 | path: '/pages/1', 141 | component: list, 142 | context: { 143 | }, 144 | }); 145 | } 146 | 147 | const tags = [...new Set(tagMatrix.reduce((prev, curr) => (curr !== null ? [...prev, ...curr] : prev), []))]; 148 | 149 | tags.forEach((tag) => { 150 | const taggedPostCount = edges.reduce((count, { node: { frontmatter: { tags: postTags } } }) => { 151 | if (postTags !== null && postTags.includes(tag)) { 152 | return count + 1; 153 | } 154 | 155 | return count; 156 | }, 0); 157 | const taggedListCount = taggedPostCount ? (Math.ceil(taggedPostCount / CONTENT_PER_PAGE) + 1) : 1; 158 | const taggedListPages = Array.from(new Array(taggedListCount), (el, i) => i + 1); 159 | 160 | taggedListPages.forEach((taggedListPage) => { 161 | createPage({ 162 | path: `/tags/${tag}/${taggedListPage}`, 163 | component: taggedList, 164 | context: { 165 | }, 166 | }); 167 | }); 168 | }); 169 | 170 | const categories = [...new Set(categoryMatrix)]; 171 | 172 | categories.forEach((category) => { 173 | const categorizedPostCount = edges.reduce((count, { node: { frontmatter: { category: postCategory } } }) => { 174 | if (postCategory !== null && postCategory.includes(category)) { 175 | return count + 1; 176 | } 177 | 178 | return count; 179 | }, 0); 180 | const categorizedListCount = categorizedPostCount ? (Math.ceil(categorizedPostCount / CONTENT_PER_PAGE) + 1) : 1; 181 | const categorizedListPages = Array.from(new Array(categorizedListCount), (el, i) => i + 1); 182 | 183 | categorizedListPages.forEach((categorizedListPage) => { 184 | createPage({ 185 | path: `/categories/${category}/${categorizedListPage}`, 186 | component: categorizedList, 187 | context: { 188 | }, 189 | }); 190 | }); 191 | }); 192 | }) 193 | ); 194 | }); 195 | }; 196 | 197 | exports.onCreateNode = ({ node, actions, getNode }) => { 198 | const { createNodeField } = actions; 199 | 200 | if (node.internal.type === 'MarkdownRemark') { 201 | const value = createFilePath({ node, getNode }); 202 | 203 | createNodeField({ 204 | name: 'slug', 205 | node, 206 | value, 207 | }); 208 | } 209 | }; 210 | -------------------------------------------------------------------------------- /gatsby-ssr.js: -------------------------------------------------------------------------------- 1 | import { renderToString } from 'react-dom/server'; 2 | import Helmet from 'react-helmet'; 3 | import { ServerStyleSheet } from 'styled-components'; 4 | 5 | export const replaceRenderer = ({ 6 | bodyComponent, 7 | replaceBodyHTMLString, 8 | setHeadComponents 9 | }) => { 10 | const sheet = new ServerStyleSheet(); 11 | const body = renderToString(sheet.collectStyles(bodyComponent)); 12 | 13 | replaceBodyHTMLString(body); 14 | setHeadComponents([sheet.getStyleElement()]); 15 | 16 | return; 17 | }; 18 | 19 | export const onRenderBody = ({ 20 | setHeadComponents, 21 | setHtmlAttributes, 22 | setBodyAttributes, 23 | }, pluginOptions) => { 24 | const helmet = Helmet.renderStatic(); 25 | setHtmlAttributes(helmet.htmlAttributes.toComponent()); 26 | setBodyAttributes(helmet.bodyAttributes.toComponent()); 27 | setHeadComponents([ 28 | helmet.title.toComponent(), 29 | helmet.link.toComponent(), 30 | helmet.meta.toComponent(), 31 | helmet.noscript.toComponent(), 32 | helmet.script.toComponent(), 33 | helmet.style.toComponent(), 34 | ]); 35 | }; 36 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "gatsby-advanced-blog", 3 | "version": "1.1.5", 4 | "description": "Gatsby starter for advanced blog", 5 | "main": "n/a", 6 | "author": "wonism", 7 | "license": "MIT", 8 | "homepage": "https://github.com/wonism/gatsby-advanced-blog", 9 | "keywords": [ 10 | "gatsby" 11 | ], 12 | "repository": { 13 | "type": "git", 14 | "url": "git+https://github.com/wonism/gatsby-advanced-blog.git" 15 | }, 16 | "bugs": { 17 | "url": "https://github.com/wonism/gatsby-advanced-blo/issues" 18 | }, 19 | "peerDependencies": { 20 | "gatsby": ">=2.2.9", 21 | "react": ">=16.8.5", 22 | "react-dom": ">=16.8.5", 23 | "prop-types": ">=15.7.2", 24 | "styled-components": ">=4.2.0" 25 | }, 26 | "dependencies": { 27 | "@babel/polyfill": "^7.4.0", 28 | "clipboard": "^2.0.4", 29 | "core-js": "^2.6.5", 30 | "gatsby": "^2.8.5", 31 | "npm": "^6.9.0", 32 | "react": "^16.8.5", 33 | "react-dom": "^16.8.5", 34 | "react-helmet": "^5.2.0", 35 | "react-icons": "^3.5.0", 36 | "react-toggle": "^4.0.2", 37 | "react-truncate": "^2.4.0", 38 | "react-twitter-widgets": "^1.7.1", 39 | "styled-components": "^4.2.0" 40 | }, 41 | "devDependencies": { 42 | "@babel/plugin-proposal-class-properties": "^7.4.0", 43 | "@babel/plugin-proposal-pipeline-operator": "^7.3.2", 44 | "@babel/plugin-syntax-dynamic-import": "^7.2.0", 45 | "@babel/preset-env": "^7.4.2", 46 | "@babel/preset-react": "^7.0.0", 47 | "babel-eslint": "^10.0.1", 48 | "babel-loader": "^8.0.5", 49 | "babel-plugin-emotion": "^10.0.9", 50 | "babel-plugin-styled-components": "^1.10.0", 51 | "eslint-config-airbnb": "^17.1.0", 52 | "eslint-import-resolver-webpack": "^0.11.0", 53 | "eslint-loader": "^2.1.2", 54 | "eslint-plugin-import": "^2.16.0", 55 | "eslint-plugin-jsx-a11y": "^6.2.1", 56 | "eslint-plugin-react": "^7.12.4", 57 | "gatsby-plugin-google-analytics": "^2.0.17", 58 | "gatsby-plugin-manifest": "^2.0.24", 59 | "gatsby-plugin-offline": "^2.0.25", 60 | "gatsby-plugin-react-helmet": "^3.0.10", 61 | "gatsby-plugin-robots-txt": "^1.4.0", 62 | "gatsby-plugin-sharp": "^2.0.31", 63 | "gatsby-plugin-sitemap": "^2.1.0", 64 | "gatsby-plugin-styled-components": "^3.0.7", 65 | "gatsby-remark-copy-linked-files": "^2.0.11", 66 | "gatsby-remark-images": "^3.0.10", 67 | "gatsby-remark-prismjs": "^3.2.6", 68 | "gatsby-remark-responsive-iframe": "^2.1.1", 69 | "gatsby-remark-smartypants": "^2.0.9", 70 | "gatsby-source-filesystem": "^2.0.28", 71 | "gatsby-transformer-remark": "^2.3.8", 72 | "husky": "^1.3.1", 73 | "lint-staged": "^8.1.5", 74 | "prettier": "^1.16.4", 75 | "prettier-eslint-cli": "^4.7.1", 76 | "prismjs": "^1.15.0" 77 | }, 78 | "scripts": { 79 | "precommit": "lint-staged", 80 | "lint": "eslint --ext .js,.jsx --config .eslintrc.js", 81 | "dev": "gatsby develop", 82 | "clean": "rm -rf .cache && rm -rf public", 83 | "build": "gatsby build", 84 | "serve": "gatsby serve", 85 | "deploy": "gatsby build --prefix-paths && gh-pages -d public -b master", 86 | "test": "echo \"Error: no test specified\" && exit 1" 87 | }, 88 | "browserslist": [ 89 | "> 10%", 90 | "IE >= 9", 91 | "last 2 versions" 92 | ], 93 | "lint-staged": { 94 | "linters": { 95 | "*.{js,jsx}": [ 96 | "npm run lint", 97 | "git add" 98 | ] 99 | }, 100 | "ignore": [ 101 | "./.*.js" 102 | ] 103 | }, 104 | "husky": { 105 | "hooks": { 106 | "pre-commit": "npm run precommit" 107 | } 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /src/components/App/index.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import { ThemeProvider } from 'styled-components'; 4 | import Gnb from '~/components/Gnb'; 5 | import Footer from '~/components/Footer'; 6 | import { BLACK_COLOR, WHITE_COLOR } from '~/components/Common/constants'; 7 | import { Wrapper } from './styled'; 8 | 9 | export default class App extends Component { 10 | static propTypes = { 11 | children: PropTypes.node.isRequired, 12 | location: PropTypes.shape({ pathname: PropTypes.string.isRequired }).isRequired, 13 | categories: PropTypes.arrayOf(PropTypes.shape({})).isRequired, 14 | postInformations: PropTypes.arrayOf(PropTypes.shape({})).isRequired, 15 | hasPortfolio: PropTypes.bool.isRequired, 16 | } 17 | 18 | state = { 19 | isDracula: global.localStorage && global.localStorage.getItem('theme') === 'dracula', 20 | } 21 | 22 | toggleTheme = () => { 23 | const { isDracula } = this.state; 24 | 25 | if (isDracula) { 26 | if (global.localStorage) { 27 | global.localStorage.setItem('theme', 'normal'); 28 | } 29 | } else { 30 | if (global.localStorage) { 31 | global.localStorage.setItem('theme', 'dracula'); 32 | } 33 | } 34 | 35 | this.setState({ 36 | isDracula: !isDracula, 37 | }); 38 | }; 39 | 40 | render() { 41 | const { 42 | location, 43 | categories, 44 | postInformations, 45 | hasPortfolio, 46 | children, 47 | } = this.props; 48 | const { isDracula } = this.state; 49 | const theme = isDracula ? { 50 | color: WHITE_COLOR, 51 | backgroundColor: BLACK_COLOR, 52 | } : { 53 | color: BLACK_COLOR, 54 | backgroundColor: WHITE_COLOR, 55 | }; 56 | 57 | return ( 58 | 59 | 60 | 70 |
71 | {children} 72 |
73 |