├── .gitignore ├── .prettierrc ├── README.md ├── gatsby-browser.js ├── gatsby-config.js ├── gatsby-node.js ├── gatsby-ssr.js ├── images.d.ts ├── jest.setup.js ├── package.json ├── src ├── components │ ├── header.tsx │ ├── image.tsx │ ├── layout.css │ ├── layout.tsx │ ├── listing.tsx │ └── postLayout.tsx ├── hooks │ ├── useLayoutQuery.ts │ └── useListingQuery.ts ├── images │ ├── gatsby-astronaut.png │ └── gatsby-icon.png ├── interfaces │ ├── EdgeNode.interface.ts │ ├── LayoutQuery.interface.ts │ ├── PostQuery.interface.ts │ └── PostsQuery.interface.ts ├── pages │ ├── 404.tsx │ ├── index.tsx │ └── page-2.tsx └── posts │ └── post-one │ ├── components │ └── hello.tsx │ └── post-one.mdx ├── tsconfig.json ├── tslint.json └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (http://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # Typescript v1 declaration files 40 | typings/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # dotenv environment variables file 55 | .env 56 | 57 | # gatsby files 58 | .cache/ 59 | public 60 | 61 | # Mac files 62 | .DS_Store 63 | 64 | # Yarn 65 | yarn-error.log 66 | .pnp/ 67 | .pnp.js 68 | # Yarn Integrity file 69 | .yarn-integrity -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "semi": true, 3 | "singleQuote": true, 4 | "trailingComma": "es5" 5 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Gatsby TypeScript MDX Prismjs starter 2 | 3 | Demo: 4 | 5 | ## Getting started 6 | 7 | Install this by running the following from your CLI: 8 | 9 | ```bash 10 | $ gatsby new my-website https://github.com/tylergreulich/gatsby-typescript-mdx-prismjs-starter 11 | ``` 12 | 13 | Run `yarn dev` to serve your website on . 14 | 15 | Run `yarn build` to create a static site ready to host from (`/public`) 16 | 17 | ## Features 18 | 19 | - [x] Gatsby 2.0 20 | - [x] sharp 21 | - [x] offline support 22 | - [x] manifest 23 | - [x] typescript 24 | - [x] blog in mdx 25 | - [x] Tools 26 | - [x] [Jest](https://facebook.github.io/jest/) / [react-testing-library](https://github.com/kentcdodds/react-testing-library) 27 | - [x] [Typescript](https://www.typescriptlang.org/) / [tslint](https://palantir.github.io/tslint/) 28 | - [x] SEO 29 | - [x] [Helmet](https://github.com/nfl/react-helmet) 30 | - [x] [styled-components](https://www.styled-components.com/) for styling 31 | - [x] [Prismjs](https://prismjs.com/) for syntax highlighting in blog posts 32 | 33 | ## Changing theme of Prismjs 34 | 35 | Go into gatsby-browser.js and replace the theme with what you want 36 | 37 | Some of their default themes can be found [here](https://prismjs.com/) 38 | 39 | [List of other themes](https://github.com/PrismJS/prism-themes) 40 | -------------------------------------------------------------------------------- /gatsby-browser.js: -------------------------------------------------------------------------------- 1 | require('prismjs/themes/prism-tomorrow.css'); 2 | -------------------------------------------------------------------------------- /gatsby-config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | siteMetadata: { 3 | title: 'Gatsby-TS-MDX-PrismJs-Starter', 4 | description: 'Gatsby-TS-MDX-PrismJs-Starter', 5 | keywords: 'One keyword, two keywords', 6 | }, 7 | plugins: [ 8 | 'gatsby-plugin-react-helmet', 9 | 'gatsby-transformer-sharp', 10 | 'gatsby-plugin-sharp', 11 | 'gatsby-plugin-typescript', 12 | 'gatsby-transformer-remark', 13 | 'gatsby-image', 14 | 'gatsby-plugin-styled-components', 15 | 'gatsby-plugin-offline', 16 | { 17 | resolve: `gatsby-mdx`, 18 | options: { 19 | decks: [], 20 | defaultLayouts: { 21 | default: require.resolve('./src/components/postLayout.tsx'), 22 | }, 23 | extensions: ['.mdx', '.md'], 24 | gatsbyRemarkPlugins: [ 25 | { 26 | resolve: 'gatsby-remark-prismjs', 27 | options: { 28 | classPrefix: 'language-', 29 | inlineCodeMarker: { 30 | tsx: 'tsx', 31 | }, 32 | aliases: {}, 33 | }, 34 | }, 35 | ], 36 | }, 37 | }, 38 | { 39 | resolve: 'gatsby-source-filesystem', 40 | options: { 41 | name: 'posts', 42 | path: `${__dirname}/src/posts`, 43 | ignore: ['**/.tsx*'], 44 | }, 45 | }, 46 | { 47 | resolve: 'gatsby-source-filesystem', 48 | options: { 49 | name: 'images', 50 | path: `${__dirname}/src/images`, 51 | }, 52 | }, 53 | // { 54 | // resolve: `gatsby-plugin-manifest`, 55 | // options: { 56 | // name: "Gatsby-TS-MDX-PrismJs-Starter", 57 | // short_name: 'Gatsby-Starter', 58 | // start_url: '/', 59 | // background_color: '#663399', 60 | // theme_color: '#663399', 61 | // display: 'minimal-ui', 62 | // icon: 'src/images/gatsby-icon.png', // This path is relative to the root of the site. 63 | // }, 64 | // }, 65 | // this (optional) plugin enables Progressive Web App + Offline functionality 66 | // To learn more, visit: https://gatsby.app/offline 67 | ], 68 | }; 69 | -------------------------------------------------------------------------------- /gatsby-node.js: -------------------------------------------------------------------------------- 1 | const componentWithMDXScope = require('gatsby-mdx/component-with-mdx-scope'); 2 | const { resolve } = require('path'); 3 | 4 | exports.createPages = async ({ graphql, actions }) => { 5 | const { createPage } = actions; 6 | 7 | const { data } = await graphql(` 8 | { 9 | allMdx(limit: 5, sort: { fields: [frontmatter___date], order: DESC }) { 10 | edges { 11 | node { 12 | fileAbsolutePath 13 | frontmatter { 14 | path 15 | title 16 | date 17 | } 18 | } 19 | } 20 | } 21 | } 22 | `).catch(error => console.error(error)); 23 | 24 | data.allMdx.edges.forEach(({ node }) => { 25 | createPage({ 26 | path: `/posts${node.frontmatter.path}`, 27 | component: node.fileAbsolutePath, 28 | context: { 29 | pagePath: node.frontmatter.path, 30 | }, 31 | }); 32 | }); 33 | }; 34 | -------------------------------------------------------------------------------- /gatsby-ssr.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Implement Gatsby's SSR (Server Side Rendering) APIs in this file. 3 | * 4 | * See: https://www.gatsbyjs.org/docs/ssr-apis/ 5 | */ 6 | 7 | // You can delete this file if you're not using it 8 | -------------------------------------------------------------------------------- /images.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.png' 2 | declare module '*.jpg' 3 | declare module '*.jpeg' 4 | declare module '*.gif' 5 | -------------------------------------------------------------------------------- /jest.setup.js: -------------------------------------------------------------------------------- 1 | import 'jest-dom/extend-expect'; 2 | import 'react-testing-library/cleanup-after-each'; 3 | 4 | global.___loader = { 5 | enqueue: jest.fn(), 6 | }; 7 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "gatsby-typescript-mdx-prismjs-starter", 3 | "description": "Gatsby TS MDX Prismsjs Starter", 4 | "version": "1.0.0", 5 | "author": "Tyler Greulich ", 6 | "dependencies": { 7 | "babel-plugin-styled-components": "^1.10.0", 8 | "gatsby": "^2.1.31", 9 | "gatsby-image": "^2.0.33", 10 | "gatsby-mdx": "^0.4.3", 11 | "gatsby-plugin-manifest": "^2.0.24", 12 | "gatsby-plugin-offline": "^2.0.25", 13 | "gatsby-plugin-react-helmet": "^3.0.9", 14 | "gatsby-plugin-sharp": "^2.0.28", 15 | "gatsby-plugin-styled-components": "^3.0.7", 16 | "gatsby-plugin-typescript": "^2.0.11", 17 | "gatsby-remark-prismjs": "^3.2.5", 18 | "gatsby-source-filesystem": "^2.0.24", 19 | "gatsby-transformer-remark": "^2.3.2", 20 | "gatsby-transformer-sharp": "^2.1.17", 21 | "prismjs": "^1.15.0", 22 | "prop-types": "^15.7.2", 23 | "react": "^16.8.4", 24 | "react-dom": "^16.8.4", 25 | "react-helmet": "^5.2.0", 26 | "react-hooks-testing-library": "^0.3.6", 27 | "react-testing-library": "^6.0.0", 28 | "styled-components": "^4.1.3" 29 | }, 30 | "keywords": [ 31 | "gatsby", 32 | "mdx", 33 | "typescript", 34 | "prismjs", 35 | "styled-components" 36 | ], 37 | "license": "MIT", 38 | "scripts": { 39 | "build": "gatsby build", 40 | "dev": "gatsby develop", 41 | "test": "jest" 42 | }, 43 | "devDependencies": { 44 | "@mdx-js/loader": "^0.20.3", 45 | "@mdx-js/mdx": "^0.20.3", 46 | "@mdx-js/tag": "^0.20.3", 47 | "@types/jest": "^24.0.11", 48 | "@types/react-helmet": "^5.0.8", 49 | "@types/styled-components": "^4.1.12", 50 | "jest": "^24.5.0", 51 | "jest-dom": "^3.1.3", 52 | "loader-utils": "^1.2.3", 53 | "prettier": "^1.16.4", 54 | "ts-jest": "^24.0.0", 55 | "tslint": "^5.13.1", 56 | "typescript": "^3.3.3333" 57 | }, 58 | "repository": { 59 | "type": "git", 60 | "url": "https://github.com/tylergreulich/gatsby-typescript-mdx-prismjs-starter" 61 | }, 62 | "jest": { 63 | "setupTestFrameworkScriptFile": "./jest.setup.js", 64 | "globals": { 65 | "__PATH_PREFIX__": "" 66 | }, 67 | "testURL": "http://localhost", 68 | "transform": { 69 | "^.+\\.(tsx?|jsx?)$": "ts-jest" 70 | }, 71 | "transformIgnorePatterns": [ 72 | "node_modules/(?!(gatsby)/)" 73 | ], 74 | "testRegex": "(/__tests__/.*|\\.(test|spec))\\.(ts|tsx)$", 75 | "testPathIgnorePatterns": [ 76 | "node_modules", 77 | ".cache" 78 | ], 79 | "moduleFileExtensions": [ 80 | "ts", 81 | "tsx", 82 | "js" 83 | ], 84 | "moduleNameMapper": { 85 | "typeface-*": "identity-obj-proxy", 86 | ".+\\.(css|styl|less|sass|scss)$": "identity-obj-proxy", 87 | ".+\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$": "/src/__tests__/__mocks__/file.js" 88 | }, 89 | "collectCoverage": false, 90 | "coverageReporters": [ 91 | "lcov", 92 | "text", 93 | "html" 94 | ] 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/components/header.tsx: -------------------------------------------------------------------------------- 1 | import { Link } from 'gatsby'; 2 | import * as React from 'react'; 3 | import styled from 'styled-components'; 4 | import GatsbyLogo from '../images/gatsby-icon.png'; 5 | 6 | interface HeaderProps { 7 | siteTitle: string; 8 | } 9 | 10 | const HeaderWrapper = styled.div` 11 | background: ${props => props.theme.colorPrimary}; 12 | img { 13 | margin-bottom: 0; 14 | } 15 | `; 16 | 17 | const HeaderContainer = styled.div` 18 | margin: 0 auto; 19 | max-width: 96rem; 20 | padding: 1rem; 21 | `; 22 | 23 | const Header: React.FunctionComponent = () => ( 24 | 25 | 26 |

27 | 34 | Gatsby Logo 35 | 36 |

37 |
38 |
39 | ); 40 | 41 | export default Header; 42 | -------------------------------------------------------------------------------- /src/components/image.tsx: -------------------------------------------------------------------------------- 1 | import { graphql, useStaticQuery } from 'gatsby'; 2 | import Img from 'gatsby-image'; 3 | import * as React from 'react'; 4 | 5 | /* 6 | * This component is built using `gatsby-image` to automatically serve optimized 7 | * images with lazy loading and reduced file sizes. The image is loaded using a 8 | * `StaticQuery`, which allows us to load the image from directly within this 9 | * component, rather than having to pass the image data down from pages. 10 | * 11 | * For more information, see the docs: 12 | * - `gatsby-image`: https://gatsby.app/gatsby-image 13 | * - `StaticQuery`: https://gatsby.app/staticquery 14 | */ 15 | 16 | const Image = () => { 17 | const { placeholderImage } = useStaticQuery(graphql` 18 | query { 19 | placeholderImage: file(relativePath: { eq: "gatsby-astronaut.png" }) { 20 | childImageSharp { 21 | fluid(maxWidth: 300) { 22 | ...GatsbyImageSharpFluid 23 | } 24 | } 25 | } 26 | } 27 | `); 28 | 29 | return ; 30 | }; 31 | export default Image; 32 | -------------------------------------------------------------------------------- /src/components/layout.css: -------------------------------------------------------------------------------- 1 | html { 2 | font-family: sans-serif; 3 | -ms-text-size-adjust: 100%; 4 | -webkit-text-size-adjust: 100%; 5 | } 6 | 7 | body { 8 | margin: 0; 9 | } 10 | 11 | article, 12 | aside, 13 | details, 14 | figcaption, 15 | figure, 16 | footer, 17 | header, 18 | main, 19 | menu, 20 | nav, 21 | section, 22 | summary { 23 | display: block; 24 | } 25 | 26 | audio, 27 | canvas, 28 | progress, 29 | video { 30 | display: inline-block; 31 | } 32 | 33 | audio:not([controls]) { 34 | display: none; 35 | height: 0; 36 | } 37 | 38 | progress { 39 | vertical-align: baseline; 40 | } 41 | 42 | [hidden], 43 | template { 44 | display: none; 45 | } 46 | 47 | a { 48 | background-color: transparent; 49 | -webkit-text-decoration-skip: objects; 50 | } 51 | 52 | a:active, 53 | a:hover { 54 | outline-width: 0; 55 | } 56 | 57 | abbr[title] { 58 | border-bottom: none; 59 | text-decoration: underline; 60 | text-decoration: underline dotted; 61 | } 62 | 63 | b, 64 | strong { 65 | font-weight: inherit; 66 | font-weight: bolder; 67 | } 68 | 69 | dfn { 70 | font-style: italic; 71 | } 72 | 73 | h1 { 74 | font-size: 2em; 75 | margin: .67em 0; 76 | } 77 | 78 | mark { 79 | background-color: #ff0; 80 | color: #000; 81 | } 82 | 83 | small { 84 | font-size: 80%; 85 | } 86 | 87 | sub, 88 | sup { 89 | font-size: 75%; 90 | line-height: 0; 91 | position: relative; 92 | vertical-align: baseline; 93 | } 94 | 95 | sub { 96 | bottom: -.25em; 97 | } 98 | 99 | sup { 100 | top: -.5em; 101 | } 102 | 103 | img { 104 | border-style: none; 105 | } 106 | 107 | svg:not(:root) { 108 | overflow: hidden; 109 | } 110 | 111 | code, 112 | kbd, 113 | pre, 114 | samp { 115 | font-family: monospace, monospace; 116 | font-size: 1em; 117 | } 118 | 119 | figure { 120 | margin: 1em 40px; 121 | } 122 | 123 | hr { 124 | box-sizing: content-box; 125 | height: 0; 126 | overflow: visible; 127 | } 128 | 129 | button, 130 | input, 131 | optgroup, 132 | select, 133 | textarea { 134 | font: inherit; 135 | margin: 0; 136 | } 137 | 138 | optgroup { 139 | font-weight: 700; 140 | } 141 | 142 | button, 143 | input { 144 | overflow: visible; 145 | } 146 | 147 | button, 148 | select { 149 | text-transform: none; 150 | } 151 | 152 | [type=reset], 153 | [type=submit], 154 | button, 155 | html [type=button] { 156 | -webkit-appearance: button; 157 | } 158 | 159 | [type=button]::-moz-focus-inner, 160 | [type=reset]::-moz-focus-inner, 161 | [type=submit]::-moz-focus-inner, 162 | button::-moz-focus-inner { 163 | border-style: none; 164 | padding: 0; 165 | } 166 | 167 | [type=button]:-moz-focusring, 168 | [type=reset]:-moz-focusring, 169 | [type=submit]:-moz-focusring, 170 | button:-moz-focusring { 171 | outline: 1px dotted ButtonText; 172 | } 173 | 174 | fieldset { 175 | border: 1px solid silver; 176 | margin: 0 2px; 177 | padding: .35em .625em .75em; 178 | } 179 | 180 | legend { 181 | box-sizing: border-box; 182 | color: inherit; 183 | display: table; 184 | max-width: 100%; 185 | padding: 0; 186 | white-space: normal; 187 | } 188 | 189 | textarea { 190 | overflow: auto; 191 | } 192 | 193 | [type=checkbox], 194 | [type=radio] { 195 | box-sizing: border-box; 196 | padding: 0; 197 | } 198 | 199 | [type=number]::-webkit-inner-spin-button, 200 | [type=number]::-webkit-outer-spin-button { 201 | height: auto; 202 | } 203 | 204 | [type=search] { 205 | -webkit-appearance: textfield; 206 | outline-offset: -2px; 207 | } 208 | 209 | [type=search]::-webkit-search-cancel-button, 210 | [type=search]::-webkit-search-decoration { 211 | -webkit-appearance: none; 212 | } 213 | 214 | ::-webkit-input-placeholder { 215 | color: inherit; 216 | opacity: .54; 217 | } 218 | 219 | ::-webkit-file-upload-button { 220 | -webkit-appearance: button; 221 | font: inherit; 222 | } 223 | 224 | html { 225 | font: 112.5%/1.45em georgia, serif; 226 | box-sizing: border-box; 227 | overflow-y: scroll; 228 | } 229 | 230 | * { 231 | box-sizing: inherit; 232 | } 233 | 234 | *:before { 235 | box-sizing: inherit; 236 | } 237 | 238 | *:after { 239 | box-sizing: inherit; 240 | } 241 | 242 | body { 243 | color: hsla(0, 0%, 0%, 0.8); 244 | font-family: georgia, serif; 245 | font-weight: normal; 246 | word-wrap: break-word; 247 | font-kerning: normal; 248 | -moz-font-feature-settings: "kern", "liga", "clig", "calt"; 249 | -ms-font-feature-settings: "kern", "liga", "clig", "calt"; 250 | -webkit-font-feature-settings: "kern", "liga", "clig", "calt"; 251 | font-feature-settings: "kern", "liga", "clig", "calt"; 252 | } 253 | 254 | img { 255 | max-width: 100%; 256 | margin-left: 0; 257 | margin-right: 0; 258 | margin-top: 0; 259 | padding-bottom: 0; 260 | padding-left: 0; 261 | padding-right: 0; 262 | padding-top: 0; 263 | margin-bottom: 1.45rem; 264 | } 265 | 266 | h1 { 267 | margin-left: 0; 268 | margin-right: 0; 269 | margin-top: 0; 270 | padding-bottom: 0; 271 | padding-left: 0; 272 | padding-right: 0; 273 | padding-top: 0; 274 | margin-bottom: 1.45rem; 275 | color: inherit; 276 | font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, 277 | Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif; 278 | font-weight: bold; 279 | text-rendering: optimizeLegibility; 280 | font-size: 2.25rem; 281 | line-height: 1.1; 282 | } 283 | 284 | h2 { 285 | margin-left: 0; 286 | margin-right: 0; 287 | margin-top: 0; 288 | padding-bottom: 0; 289 | padding-left: 0; 290 | padding-right: 0; 291 | padding-top: 0; 292 | margin-bottom: 1.45rem; 293 | color: inherit; 294 | font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, 295 | Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif; 296 | font-weight: bold; 297 | text-rendering: optimizeLegibility; 298 | font-size: 1.62671rem; 299 | line-height: 1.1; 300 | } 301 | 302 | h3 { 303 | margin-left: 0; 304 | margin-right: 0; 305 | margin-top: 0; 306 | padding-bottom: 0; 307 | padding-left: 0; 308 | padding-right: 0; 309 | padding-top: 0; 310 | margin-bottom: 1.45rem; 311 | color: inherit; 312 | font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, 313 | Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif; 314 | font-weight: bold; 315 | text-rendering: optimizeLegibility; 316 | font-size: 1.38316rem; 317 | line-height: 1.1; 318 | } 319 | 320 | h4 { 321 | margin-left: 0; 322 | margin-right: 0; 323 | margin-top: 0; 324 | padding-bottom: 0; 325 | padding-left: 0; 326 | padding-right: 0; 327 | padding-top: 0; 328 | margin-bottom: 1.45rem; 329 | color: inherit; 330 | font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, 331 | Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif; 332 | font-weight: bold; 333 | text-rendering: optimizeLegibility; 334 | font-size: 1rem; 335 | line-height: 1.1; 336 | } 337 | 338 | h5 { 339 | margin-left: 0; 340 | margin-right: 0; 341 | margin-top: 0; 342 | padding-bottom: 0; 343 | padding-left: 0; 344 | padding-right: 0; 345 | padding-top: 0; 346 | margin-bottom: 1.45rem; 347 | color: inherit; 348 | font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, 349 | Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif; 350 | font-weight: bold; 351 | text-rendering: optimizeLegibility; 352 | font-size: 0.85028rem; 353 | line-height: 1.1; 354 | } 355 | 356 | h6 { 357 | margin-left: 0; 358 | margin-right: 0; 359 | margin-top: 0; 360 | padding-bottom: 0; 361 | padding-left: 0; 362 | padding-right: 0; 363 | padding-top: 0; 364 | margin-bottom: 1.45rem; 365 | color: inherit; 366 | font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, 367 | Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif; 368 | font-weight: bold; 369 | text-rendering: optimizeLegibility; 370 | font-size: 0.78405rem; 371 | line-height: 1.1; 372 | } 373 | 374 | hgroup { 375 | margin-left: 0; 376 | margin-right: 0; 377 | margin-top: 0; 378 | padding-bottom: 0; 379 | padding-left: 0; 380 | padding-right: 0; 381 | padding-top: 0; 382 | margin-bottom: 1.45rem; 383 | } 384 | 385 | ul { 386 | margin-left: 1.45rem; 387 | margin-right: 0; 388 | margin-top: 0; 389 | padding-bottom: 0; 390 | padding-left: 0; 391 | padding-right: 0; 392 | padding-top: 0; 393 | margin-bottom: 1.45rem; 394 | list-style-position: outside; 395 | list-style-image: none; 396 | } 397 | 398 | ol { 399 | margin-left: 1.45rem; 400 | margin-right: 0; 401 | margin-top: 0; 402 | padding-bottom: 0; 403 | padding-left: 0; 404 | padding-right: 0; 405 | padding-top: 0; 406 | margin-bottom: 1.45rem; 407 | list-style-position: outside; 408 | list-style-image: none; 409 | } 410 | 411 | dl { 412 | margin-left: 0; 413 | margin-right: 0; 414 | margin-top: 0; 415 | padding-bottom: 0; 416 | padding-left: 0; 417 | padding-right: 0; 418 | padding-top: 0; 419 | margin-bottom: 1.45rem; 420 | } 421 | 422 | dd { 423 | margin-left: 0; 424 | margin-right: 0; 425 | margin-top: 0; 426 | padding-bottom: 0; 427 | padding-left: 0; 428 | padding-right: 0; 429 | padding-top: 0; 430 | margin-bottom: 1.45rem; 431 | } 432 | 433 | p { 434 | margin-left: 0; 435 | margin-right: 0; 436 | margin-top: 0; 437 | padding-bottom: 0; 438 | padding-left: 0; 439 | padding-right: 0; 440 | padding-top: 0; 441 | margin-bottom: 1.45rem; 442 | } 443 | 444 | figure { 445 | margin-left: 0; 446 | margin-right: 0; 447 | margin-top: 0; 448 | padding-bottom: 0; 449 | padding-left: 0; 450 | padding-right: 0; 451 | padding-top: 0; 452 | margin-bottom: 1.45rem; 453 | } 454 | 455 | pre { 456 | margin-left: 0; 457 | margin-right: 0; 458 | margin-top: 0; 459 | padding-bottom: 0; 460 | padding-left: 0; 461 | padding-right: 0; 462 | padding-top: 0; 463 | margin-bottom: 1.45rem; 464 | font-size: 0.85rem; 465 | line-height: 1.42; 466 | background: hsla(0, 0%, 0%, 0.04); 467 | border-radius: 3px; 468 | overflow: auto; 469 | word-wrap: normal; 470 | padding: 1.45rem; 471 | } 472 | 473 | table { 474 | margin-left: 0; 475 | margin-right: 0; 476 | margin-top: 0; 477 | padding-bottom: 0; 478 | padding-left: 0; 479 | padding-right: 0; 480 | padding-top: 0; 481 | margin-bottom: 1.45rem; 482 | font-size: 1rem; 483 | line-height: 1.45rem; 484 | border-collapse: collapse; 485 | width: 100%; 486 | } 487 | 488 | fieldset { 489 | margin-left: 0; 490 | margin-right: 0; 491 | margin-top: 0; 492 | padding-bottom: 0; 493 | padding-left: 0; 494 | padding-right: 0; 495 | padding-top: 0; 496 | margin-bottom: 1.45rem; 497 | } 498 | 499 | blockquote { 500 | margin-left: 1.45rem; 501 | margin-right: 1.45rem; 502 | margin-top: 0; 503 | padding-bottom: 0; 504 | padding-left: 0; 505 | padding-right: 0; 506 | padding-top: 0; 507 | margin-bottom: 1.45rem; 508 | } 509 | 510 | form { 511 | margin-left: 0; 512 | margin-right: 0; 513 | margin-top: 0; 514 | padding-bottom: 0; 515 | padding-left: 0; 516 | padding-right: 0; 517 | padding-top: 0; 518 | margin-bottom: 1.45rem; 519 | } 520 | 521 | noscript { 522 | margin-left: 0; 523 | margin-right: 0; 524 | margin-top: 0; 525 | padding-bottom: 0; 526 | padding-left: 0; 527 | padding-right: 0; 528 | padding-top: 0; 529 | margin-bottom: 1.45rem; 530 | } 531 | 532 | iframe { 533 | margin-left: 0; 534 | margin-right: 0; 535 | margin-top: 0; 536 | padding-bottom: 0; 537 | padding-left: 0; 538 | padding-right: 0; 539 | padding-top: 0; 540 | margin-bottom: 1.45rem; 541 | } 542 | 543 | hr { 544 | margin-left: 0; 545 | margin-right: 0; 546 | margin-top: 0; 547 | padding-bottom: 0; 548 | padding-left: 0; 549 | padding-right: 0; 550 | padding-top: 0; 551 | margin-bottom: calc(1.45rem - 1px); 552 | background: hsla(0, 0%, 0%, 0.2); 553 | border: none; 554 | height: 1px; 555 | } 556 | 557 | address { 558 | margin-left: 0; 559 | margin-right: 0; 560 | margin-top: 0; 561 | padding-bottom: 0; 562 | padding-left: 0; 563 | padding-right: 0; 564 | padding-top: 0; 565 | margin-bottom: 1.45rem; 566 | } 567 | 568 | b { 569 | font-weight: bold; 570 | } 571 | 572 | strong { 573 | font-weight: bold; 574 | } 575 | 576 | dt { 577 | font-weight: bold; 578 | } 579 | 580 | th { 581 | font-weight: bold; 582 | } 583 | 584 | li { 585 | margin-bottom: calc(1.45rem / 2); 586 | } 587 | 588 | ol li { 589 | padding-left: 0; 590 | } 591 | 592 | ul li { 593 | padding-left: 0; 594 | } 595 | 596 | li>ol { 597 | margin-left: 1.45rem; 598 | margin-bottom: calc(1.45rem / 2); 599 | margin-top: calc(1.45rem / 2); 600 | } 601 | 602 | li>ul { 603 | margin-left: 1.45rem; 604 | margin-bottom: calc(1.45rem / 2); 605 | margin-top: calc(1.45rem / 2); 606 | } 607 | 608 | blockquote *:last-child { 609 | margin-bottom: 0; 610 | } 611 | 612 | li *:last-child { 613 | margin-bottom: 0; 614 | } 615 | 616 | p *:last-child { 617 | margin-bottom: 0; 618 | } 619 | 620 | li>p { 621 | margin-bottom: calc(1.45rem / 2); 622 | } 623 | 624 | code { 625 | font-size: 0.85rem; 626 | line-height: 1.45rem; 627 | } 628 | 629 | kbd { 630 | font-size: 0.85rem; 631 | line-height: 1.45rem; 632 | } 633 | 634 | samp { 635 | font-size: 0.85rem; 636 | line-height: 1.45rem; 637 | } 638 | 639 | abbr { 640 | border-bottom: 1px dotted hsla(0, 0%, 0%, 0.5); 641 | cursor: help; 642 | } 643 | 644 | acronym { 645 | border-bottom: 1px dotted hsla(0, 0%, 0%, 0.5); 646 | cursor: help; 647 | } 648 | 649 | abbr[title] { 650 | border-bottom: 1px dotted hsla(0, 0%, 0%, 0.5); 651 | cursor: help; 652 | text-decoration: none; 653 | } 654 | 655 | thead { 656 | text-align: left; 657 | } 658 | 659 | td, 660 | th { 661 | text-align: left; 662 | border-bottom: 1px solid hsla(0, 0%, 0%, 0.12); 663 | font-feature-settings: "tnum"; 664 | -moz-font-feature-settings: "tnum"; 665 | -ms-font-feature-settings: "tnum"; 666 | -webkit-font-feature-settings: "tnum"; 667 | padding-left: 0.96667rem; 668 | padding-right: 0.96667rem; 669 | padding-top: 0.725rem; 670 | padding-bottom: calc(0.725rem - 1px); 671 | } 672 | 673 | th:first-child, 674 | td:first-child { 675 | padding-left: 0; 676 | } 677 | 678 | th:last-child, 679 | td:last-child { 680 | padding-right: 0; 681 | } 682 | 683 | tt, 684 | code { 685 | background-color: hsla(0, 0%, 0%, 0.04); 686 | border-radius: 3px; 687 | font-family: "SFMono-Regular", Consolas, "Roboto Mono", "Droid Sans Mono", 688 | "Liberation Mono", Menlo, Courier, monospace; 689 | padding: 0; 690 | padding-top: 0.2em; 691 | padding-bottom: 0.2em; 692 | } 693 | 694 | pre code { 695 | background: none; 696 | line-height: 1.42; 697 | } 698 | 699 | code:before, 700 | code:after, 701 | tt:before, 702 | tt:after { 703 | letter-spacing: -0.2em; 704 | content: " "; 705 | } 706 | 707 | pre code:before, 708 | pre code:after, 709 | pre tt:before, 710 | pre tt:after { 711 | content: ""; 712 | } 713 | 714 | @media only screen and (max-width: 480px) { 715 | html { 716 | font-size: 100%; 717 | } 718 | } -------------------------------------------------------------------------------- /src/components/layout.tsx: -------------------------------------------------------------------------------- 1 | import { RouterProps } from '@reach/router'; 2 | import * as React from 'react'; 3 | import Helmet from 'react-helmet'; 4 | import styled, { ThemeProvider } from 'styled-components'; 5 | import { useLayoutQuery } from '../hooks/useLayoutQuery'; 6 | import Header from './header'; 7 | import './layout.css'; 8 | 9 | interface ThemeProps { 10 | colorPrimary: string; 11 | } 12 | 13 | const theme: ThemeProps = { 14 | colorPrimary: '#663399', 15 | }; 16 | 17 | const MainLayout = styled.main` 18 | max-width: 90%; 19 | margin: 1rem auto; 20 | display: grid; 21 | grid-template-columns: 3fr 1fr; 22 | grid-gap: 4rem; 23 | `; 24 | 25 | type LayoutProps = React.ReactNode & RouterProps; 26 | 27 | const Layout: React.FunctionComponent = ({ children }) => { 28 | const { site } = useLayoutQuery(); 29 | 30 | const { title, description, keywords } = site.siteMetadata; 31 | 32 | return ( 33 | 34 | <> 35 | 42 | 43 | 44 |
45 | 46 |
{children}
47 |
48 | 49 | 50 | ); 51 | }; 52 | 53 | export default Layout; 54 | -------------------------------------------------------------------------------- /src/components/listing.tsx: -------------------------------------------------------------------------------- 1 | import { Link } from 'gatsby'; 2 | import * as React from 'react'; 3 | import styled from 'styled-components'; 4 | import { useListingQuery } from '../hooks/useListingQuery'; 5 | 6 | const Post = styled.article` 7 | box-shadow: 0 0.3rem 1rem rgba(25, 17, 34, 0.05); 8 | padding: 0.5rem; 9 | border-radius: 0.4rem; 10 | margin-bottom: 1rem; 11 | 12 | a { 13 | color: black; 14 | text-decoration: none; 15 | } 16 | 17 | h2 { 18 | margin-bottom: 0; 19 | } 20 | 21 | p { 22 | font-size: 0.8rem; 23 | } 24 | `; 25 | 26 | const ReadMoreLink = styled(Link)` 27 | font-size: 0.8rem; 28 | text-decoration: underline; 29 | color: ${props => props.theme.colorPrimary}; 30 | `; 31 | 32 | const Listing = () => { 33 | const { allMdx } = useListingQuery(); 34 | 35 | return ( 36 | <> 37 | {allMdx && 38 | allMdx.edges && 39 | allMdx.edges.map(({ node }) => { 40 | const { path, title, date } = node.frontmatter; 41 | 42 | return ( 43 | 44 | 45 |

{title}

46 | 47 |

{date}

48 |

{node.excerpt}

49 | Read More 50 |
51 | ); 52 | })} 53 | 54 | ); 55 | }; 56 | 57 | export default Listing; 58 | -------------------------------------------------------------------------------- /src/components/postLayout.tsx: -------------------------------------------------------------------------------- 1 | import { RouterProps } from '@reach/router'; 2 | import * as React from 'react'; 3 | import { PostQueryData } from '../interfaces/PostQuery.interface'; 4 | import Layout from './layout'; 5 | 6 | type PostLayoutProps = PostQueryData & RouterProps; 7 | 8 | const PostLayout: React.FunctionComponent = ({ 9 | data, 10 | ...props 11 | }) => { 12 | if (!data) { 13 | return null 14 | } 15 | 16 | const { title, date } = data.mdx.frontmatter; 17 | const { location, children } = props; 18 | 19 | return ( 20 | 21 |

{title}

22 | {date} 23 | {children} 24 |
25 | ); 26 | }; 27 | 28 | export default PostLayout; 29 | -------------------------------------------------------------------------------- /src/hooks/useLayoutQuery.ts: -------------------------------------------------------------------------------- 1 | import { graphql, useStaticQuery } from 'gatsby'; 2 | import { LayoutQueryData } from '../interfaces/LayoutQuery.interface'; 3 | 4 | export const useLayoutQuery = () => { 5 | const { site }: LayoutQueryData = useStaticQuery(graphql` 6 | query SiteTitleQuery { 7 | site { 8 | siteMetadata { 9 | # change siteMetaData in 'gatsby-config.js' 10 | title 11 | description 12 | keywords 13 | } 14 | } 15 | } 16 | `); 17 | 18 | return { site }; 19 | }; 20 | -------------------------------------------------------------------------------- /src/hooks/useListingQuery.ts: -------------------------------------------------------------------------------- 1 | import { graphql, useStaticQuery } from 'gatsby'; 2 | import { PostsQueryData } from '../interfaces/PostsQuery.interface'; 3 | 4 | export const useListingQuery = () => { 5 | const { allMdx }: PostsQueryData = useStaticQuery(graphql` 6 | query LISTING_QUERY { 7 | allMdx(limit: 10, sort: { fields: [frontmatter___date], order: DESC }) { 8 | edges { 9 | node { 10 | excerpt 11 | frontmatter { 12 | path 13 | title 14 | date(formatString: "MMMM DD, YYYY") 15 | } 16 | } 17 | } 18 | } 19 | } 20 | `); 21 | 22 | return { allMdx }; 23 | }; 24 | -------------------------------------------------------------------------------- /src/images/gatsby-astronaut.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tylergreulich/gatsby-typescript-mdx-prismjs-starter/64c6f8a3fc7896b8cdf5319ca9441c3c536354a4/src/images/gatsby-astronaut.png -------------------------------------------------------------------------------- /src/images/gatsby-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tylergreulich/gatsby-typescript-mdx-prismjs-starter/64c6f8a3fc7896b8cdf5319ca9441c3c536354a4/src/images/gatsby-icon.png -------------------------------------------------------------------------------- /src/interfaces/EdgeNode.interface.ts: -------------------------------------------------------------------------------- 1 | export interface EdgeNode { 2 | node: { 3 | frontmatter: { [Property in keyof Type]: Type[Property] }; 4 | excerpt?: string; 5 | }; 6 | } 7 | -------------------------------------------------------------------------------- /src/interfaces/LayoutQuery.interface.ts: -------------------------------------------------------------------------------- 1 | interface Site { 2 | siteMetadata: { 3 | title: string; 4 | description: string; 5 | keywords: string | undefined; 6 | }; 7 | } 8 | 9 | export interface LayoutQueryData { 10 | site: Site; 11 | } 12 | -------------------------------------------------------------------------------- /src/interfaces/PostQuery.interface.ts: -------------------------------------------------------------------------------- 1 | export interface PostQueryData { 2 | data: { 3 | mdx: { 4 | frontmatter: { 5 | path: string; 6 | title: string; 7 | date: string; 8 | }; 9 | }; 10 | }; 11 | }; 12 | -------------------------------------------------------------------------------- /src/interfaces/PostsQuery.interface.ts: -------------------------------------------------------------------------------- 1 | import { EdgeNode } from './EdgeNode.interface'; 2 | 3 | interface Post { 4 | path: string; 5 | title: string; 6 | date: string; 7 | } 8 | 9 | export interface PostsQueryData { 10 | allMdx: { 11 | edges?: [EdgeNode]; 12 | }; 13 | } 14 | -------------------------------------------------------------------------------- /src/pages/404.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import Layout from '../components/layout'; 3 | 4 | const NotFoundPage = () => ( 5 | 6 |

NOT FOUND

7 |

You just hit a route that doesn't exist... the sadness.

8 |
9 | ); 10 | 11 | export default NotFoundPage; 12 | -------------------------------------------------------------------------------- /src/pages/index.tsx: -------------------------------------------------------------------------------- 1 | import { RouterProps } from '@reach/router'; 2 | import * as React from 'react'; 3 | import Layout from '../components/layout'; 4 | import Listing from '../components/listing'; 5 | 6 | const IndexPage: React.FunctionComponent = ({ location }) => ( 7 | 8 | 9 | 10 | ); 11 | 12 | export default IndexPage; 13 | -------------------------------------------------------------------------------- /src/pages/page-2.tsx: -------------------------------------------------------------------------------- 1 | import { Link } from 'gatsby'; 2 | import * as React from 'react'; 3 | import Layout from '../components/layout'; 4 | 5 | const SecondPage = () => ( 6 | 7 |

Hi from the second page

8 |

Welcome to page 2

9 | Go back to the homepage 10 | Go to the About page 11 |
12 | ); 13 | 14 | export default SecondPage; 15 | -------------------------------------------------------------------------------- /src/posts/post-one/components/hello.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | const Hello = () => ( 4 |
5 |

This Heading

6 |

7 | Lorem ipsum dolor amet health goth jianbing tacos, pour-over readymade 8 | copper mug shoreditch flexitarian cold-pressed pickled brooklyn four 9 | dollar toast. Man braid irony scenester shaman chia. Lyft fixie bushwick 10 | activated charcoal, bicycle rights seitan vinyl hoodie blue bottle four 11 | dollar toast 3 wolf moon food truck helvetica austin bespoke. Scenester 12 | franzen YOLO gentrify freegan vaporware. Food truck hoodie seitan yuccie. 13 |

14 |

15 | Franzen vinyl flexitarian readymade literally. IPhone hella gastropub 16 | flannel, ugh vaporware PBR&B stumptown try-hard swag man braid glossier 17 | godard. IPhone tilde vice, man braid pinterest humblebrag twee fanny pack 18 | heirloom iceland migas. Typewriter gochujang ethical helvetica polaroid 19 | single-origin coffee. 20 |

21 |

More Heading

22 |

23 | Cliche poke typewriter, kitsch health goth literally distillery iceland 24 | hella. Lo-fi helvetica lyft stumptown iPhone lomo flannel DIY cronut 25 | iceland pickled mumblecore dreamcatcher yr wolf. +1 next level vinyl 26 | edison bulb slow-carb food truck drinking vinegar, hexagon small batch 27 | salvia skateboard. Franzen cardigan yuccie, mlkshk artisan pop-up hell of 28 | before they sold out offal pug schlitz. Letterpress vegan tumblr, pop-up 29 | prism pok pok drinking vinegar. 30 |

31 |
32 | ); 33 | 34 | export default Hello; 35 | -------------------------------------------------------------------------------- /src/posts/post-one/post-one.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | path: '/first-post' 3 | title: 'Our first post' 4 | date: '2018-12-18' 5 | --- 6 | 7 | 8 | 9 | import { graphql } from 'gatsby'; 10 | import Hello from './components/hello.tsx'; 11 | 12 | export const postOneQuery = graphql` 13 | query PostOneQuery($pagePath: String!) { 14 | mdx(frontmatter: { path: { eq: $pagePath } }) { 15 | frontmatter { 16 | path 17 | title 18 | date(formatString: "MMMM DD, YYYY") 19 | } 20 | } 21 | } 22 | `; 23 | 24 | # Hello 25 | 26 | 27 | 28 | ```tsx 29 | import * as React from 'react'; 30 | 31 | interface PrismTestProps { 32 | name: string; 33 | } 34 | 35 | export const PrismTest: React.FunctionComponent = ({ 36 | name, 37 | }) => ( 38 |
39 |

Hello there {name}!

40 |
41 | ); 42 | ``` 43 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "allowJs": true, 4 | "target": "es6", 5 | "module": "commonjs", 6 | "lib": ["dom", "es6", "es2017", "esnext.asynciterable"], 7 | "sourceMap": true, 8 | "outDir": "./dist", 9 | "jsx": "react", 10 | "moduleResolution": "node", 11 | "removeComments": true, 12 | "noImplicitAny": true, 13 | "strictNullChecks": true, 14 | "strictFunctionTypes": true, 15 | "noImplicitThis": true, 16 | "noUnusedLocals": true, 17 | "noUnusedParameters": true, 18 | "noImplicitReturns": true, 19 | "noFallthroughCasesInSwitch": true, 20 | "allowSyntheticDefaultImports": false, 21 | "skipLibCheck": true, 22 | "baseUrl": "." 23 | }, 24 | "exclude": ["node_modules"], 25 | "include": ["./src/**/*.tsx", "./src/**/*.ts", "./images.d.ts"] 26 | } -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "defaultSeverity": "error", 3 | "extends": ["tslint:recommended"], 4 | "jsRules": {}, 5 | "rules": { 6 | "no-console": false, 7 | "member-access": false, 8 | "object-literal-sort-keys": false, 9 | "ordered-imports": false, 10 | "interface-name": false, 11 | "no-submodule-imports": false, 12 | "quotemark": [true, "single", "jsx-double"], 13 | "arrow-parens": false, 14 | }, 15 | "rulesDirectory": [] 16 | } --------------------------------------------------------------------------------