├── .eslintrc ├── .gitignore ├── .nvmrc ├── .prettierrc ├── .travis.yml ├── .vscode └── settings.json ├── LICENSE ├── README.md ├── __mocks__ ├── file.js ├── gatsby-link.js └── gatsby.js ├── content ├── draft.md ├── images │ └── kittens.jpg ├── kittens.md ├── markdown.md ├── mdx.mdx ├── sample01.md ├── sample02.md ├── sample03.md ├── sample04.md ├── sample05.md ├── sample06.md ├── sample07.md ├── sample08.md ├── sample09.md └── sample10.md ├── gatsby-browser.js ├── gatsby-config.js ├── gatsby-node.js ├── gatsby-ssr.js ├── netlify.toml ├── package-lock.json ├── package.json ├── setup-jest.js ├── src ├── components │ ├── ContentList.tsx │ ├── Counter.tsx │ ├── Header.tsx │ ├── Layout.tsx │ ├── LunrSearch.tsx │ └── Pager.tsx ├── context.ts ├── globals.d.ts ├── pages │ ├── 404.tsx │ ├── another-page.tsx │ ├── index.tsx │ └── tags.tsx ├── templates │ ├── IndexTemplate.tsx │ ├── SingleTemplate.tsx │ └── TagTemplate.tsx └── typography.ts ├── static └── favicon │ ├── 192.png │ └── 512.png ├── test ├── components │ ├── ContentList.spec.tsx │ ├── Header.spec.tsx │ ├── LunrSearch.spec.tsx │ └── Pager.spec.tsx └── pages │ ├── fixtures │ └── site.json │ └── index.spec.tsx ├── tsconfig.json └── yarn.lock /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["@pacote/eslint-config", "@pacote/eslint-config-react", "@pacote/eslint-config-jest"] 3 | } 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Project dependencies 2 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git 3 | .cache 4 | node_modules/ 5 | yarn-error.log 6 | 7 | *.log 8 | 9 | # Build directory 10 | /public/ 11 | /coverage/ -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | v12.8.1 2 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 100, 3 | "semi": false, 4 | "singleQuote": true 5 | } 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 'stable' 4 | - '10' 5 | before_script: 6 | - 'yarn' 7 | - 'yarn build' 8 | - 'yarn lint' 9 | cache: 10 | directories: 11 | - ~/.cache 12 | - ~/.npm 13 | - node_modules 14 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.formatOnSave": true, 3 | "workbench.colorCustomizations": { 4 | "activityBar.background": "#7f40bf", 5 | "activityBar.activeBorder": "#c78f58", 6 | "activityBar.foreground": "#e7e7e7", 7 | "activityBar.inactiveForeground": "#e7e7e799", 8 | "activityBarBadge.background": "#c78f58", 9 | "activityBarBadge.foreground": "#15202b", 10 | "titleBar.activeBackground": "#663399", 11 | "titleBar.inactiveBackground": "#66339999", 12 | "titleBar.activeForeground": "#e7e7e7", 13 | "titleBar.inactiveForeground": "#e7e7e799", 14 | "statusBar.background": "#663399", 15 | "statusBarItem.hoverBackground": "#7f40bf", 16 | "statusBar.foreground": "#e7e7e7" 17 | }, 18 | "peacock.color": "#663399" 19 | } 20 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2018 Luís Rodrigues. 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 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # gatsby-starter-typescript 2 | 3 | [![Build Status](https://travis-ci.org/goblindegook/gatsby-starter-typescript.svg?branch=master)](https://travis-ci.org/goblindegook/gatsby-starter-typescript) 4 | 5 | A Gatsby starter using [TypeScript](https://www.typescriptlang.org) and [emotion](https://emotion.sh). 6 | 7 | View the [demo site](https://gatsby-starter-typescript.netlify.com). 8 | 9 | ## Install 10 | 11 | Make sure that you have the Gatsby CLI program installed: 12 | 13 | ```sh 14 | $ npm install --global gatsby-cli 15 | ``` 16 | 17 | And run from your CLI: 18 | 19 | ```sh 20 | $ gatsby new gatsby-example-site https://github.com/goblindegook/gatsby-starter-typescript 21 | ``` 22 | 23 | Then you can run it by: 24 | 25 | ```sh 26 | $ cd gatsby-example-site 27 | $ npm run develop 28 | ``` 29 | 30 | ## Gatsby Plugins 31 | 32 | - gatsby-plugin-catch-links 33 | - gatsby-plugin-emotion 34 | - gatsby-plugin-feed 35 | - gatsby-plugin-lunr 36 | - gatsby-plugin-manifest 37 | - gatsby-plugin-mdx 38 | - gatsby-plugin-netlify 39 | - gatsby-plugin-nprogress 40 | - gatsby-plugin-offline 41 | - gatsby-plugin-react-helmet 42 | - gatsby-plugin-sharp 43 | - gatsby-plugin-sitemap 44 | - gatsby-plugin-typegen 45 | - gatsby-plugin-typescript 46 | - gatsby-plugin-typography 47 | - gatsby-remark-copy-linked-files 48 | - gatsby-remark-images 49 | - gatsby-remark-prismjs 50 | - gatsby-remark-smartypants 51 | - gatsby-source-filesystem 52 | - gatsby-transformer-sharp 53 | 54 | ## Tools 55 | 56 | - Testing: [Jest](https://jestjs.io) and [react-testing-library](https://github.com/testing-library/react-testing-library) 57 | - Linter: [ESLint](https://eslint.org) with support for TypeScript, React, [Standard](https://standardjs.com) and [Prettier](https://prettier.io). 58 | -------------------------------------------------------------------------------- /__mocks__/file.js: -------------------------------------------------------------------------------- 1 | module.exports = 'test-file-stub' 2 | -------------------------------------------------------------------------------- /__mocks__/gatsby-link.js: -------------------------------------------------------------------------------- 1 | jest.unmock('gatsby') 2 | module.exports = jest.requireActual('gatsby-link') 3 | -------------------------------------------------------------------------------- /__mocks__/gatsby.js: -------------------------------------------------------------------------------- 1 | const gatsby = jest.requireActual('gatsby') 2 | module.exports = { 3 | ...gatsby, 4 | graphql: jest.fn(), 5 | } 6 | -------------------------------------------------------------------------------- /content/draft.md: -------------------------------------------------------------------------------- 1 | --- 2 | path: "/content/draft/" 3 | date: "2017-12-07" 4 | title: "Draft" 5 | draft: true 6 | tags: ["markdown", "draft", "example"] 7 | --- 8 | 9 | This is a draft file, it should not be published. -------------------------------------------------------------------------------- /content/images/kittens.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/goblindegook/gatsby-starter-typescript/11c71951913256809e2db7b9349daa23e0563ba0/content/images/kittens.jpg -------------------------------------------------------------------------------- /content/kittens.md: -------------------------------------------------------------------------------- 1 | --- 2 | path: '/content/kittens/' 3 | date: '2018-01-07' 4 | title: 'Kittens' 5 | tags: ['markdown', 'kittens', 'example'] 6 | --- 7 | 8 | This is an example of how you can build your site around Markdown files. 9 | 10 | Images referenced in Markdown are copied to the generated site, like these kittens: 11 | 12 | ![Kittens](images/kittens.jpg) 13 | 14 | Finally, here's some sample code with beautiful syntax highlighting thanks to PrismJS: 15 | 16 | ```typescript 17 | function greeting(name: string): string { 18 | return 'Hello, ' + name + '!' 19 | } 20 | 21 | console.log(greeting('GatsbyJS')) 22 | ``` 23 | 24 | Go back to the [content index](/all). 25 | -------------------------------------------------------------------------------- /content/markdown.md: -------------------------------------------------------------------------------- 1 | --- 2 | path: "/content/markdown-syntax/" 3 | date: "2017-11-07" 4 | title: "Markdown Syntax" 5 | tags: ["markdown", "example"] 6 | --- 7 | 8 | * [Overview](#overview) 9 | * [Philosophy](#philosophy) 10 | * [Inline HTML](#html) 11 | * [Automatic Escaping for Special Characters](#autoescape) 12 | * [Block Elements](#block) 13 | * [Paragraphs and Line Breaks](#p) 14 | * [Headers](#header) 15 | * [Blockquotes](#blockquote) 16 | * [Lists](#list) 17 | * [Code Blocks](#precode) 18 | * [Horizontal Rules](#hr) 19 | * [Span Elements](#span) 20 | * [Links](#link) 21 | * [Emphasis](#em) 22 | * [Code](#code) 23 | * [Images](#img) 24 | * [Miscellaneous](#misc) 25 | * [Backslash Escapes](#backslash) 26 | * [Automatic Links](#autolink) 27 | 28 | * * * 29 | 30 |

Overview

31 | 32 |

Philosophy

33 | 34 | Markdown is intended to be as easy-to-read and easy-to-write as is feasible. 35 | 36 | Readability, however, is emphasized above all else. A Markdown-formatted 37 | document should be publishable as-is, as plain text, without looking 38 | like it's been marked up with tags or formatting instructions. While 39 | Markdown's syntax has been influenced by several existing text-to-HTML 40 | filters -- including [Setext] [1], [atx] [2], [Textile] [3], [reStructuredText] [4], 41 | [Grutatext] [5], and [EtText] [6] -- the single biggest source of 42 | inspiration for Markdown's syntax is the format of plain text email. 43 | 44 | [1]: http://docutils.sourceforge.net/mirror/setext.html 45 | [2]: http://www.aaronsw.com/2002/atx/ 46 | [3]: http://textism.com/tools/textile/ 47 | [4]: http://docutils.sourceforge.net/rst.html 48 | [5]: http://www.triptico.com/software/grutatxt.html 49 | [6]: http://ettext.taint.org/doc/ 50 | 51 | To this end, Markdown's syntax is comprised entirely of punctuation 52 | characters, which punctuation characters have been carefully chosen so 53 | as to look like what they mean. E.g., asterisks around a word actually 54 | look like \*emphasis\*. Markdown lists look like, well, lists. Even 55 | blockquotes look like quoted passages of text, assuming you've ever 56 | used email. 57 | 58 | 59 | 60 |

Inline HTML

61 | 62 | Markdown's syntax is intended for one purpose: to be used as a 63 | format for *writing* for the web. 64 | 65 | Markdown is not a replacement for HTML, or even close to it. Its 66 | syntax is very small, corresponding only to a very small subset of 67 | HTML tags. The idea is *not* to create a syntax that makes it easier 68 | to insert HTML tags. In my opinion, HTML tags are already easy to 69 | insert. The idea for Markdown is to make it easy to read, write, and 70 | edit prose. HTML is a *publishing* format; Markdown is a *writing* 71 | format. Thus, Markdown's formatting syntax only addresses issues that 72 | can be conveyed in plain text. 73 | 74 | For any markup that is not covered by Markdown's syntax, you simply 75 | use HTML itself. There's no need to preface it or delimit it to 76 | indicate that you're switching from Markdown to HTML; you just use 77 | the tags. 78 | 79 | The only restrictions are that block-level HTML elements -- e.g. `
`, 80 | ``, `
`, `

`, etc. -- must be separated from surrounding 81 | content by blank lines, and the start and end tags of the block should 82 | not be indented with tabs or spaces. Markdown is smart enough not 83 | to add extra (unwanted) `

` tags around HTML block-level tags. 84 | 85 | For example, to add an HTML table to a Markdown article: 86 | 87 | This is a regular paragraph. 88 | 89 |

90 | 91 | 92 | 93 |
Foo
94 | 95 | This is another regular paragraph. 96 | 97 | Note that Markdown formatting syntax is not processed within block-level 98 | HTML tags. E.g., you can't use Markdown-style `*emphasis*` inside an 99 | HTML block. 100 | 101 | Span-level HTML tags -- e.g. ``, ``, or `` -- can be 102 | used anywhere in a Markdown paragraph, list item, or header. If you 103 | want, you can even use HTML tags instead of Markdown formatting; e.g. if 104 | you'd prefer to use HTML `` or `` tags instead of Markdown's 105 | link or image syntax, go right ahead. 106 | 107 | Unlike block-level HTML tags, Markdown syntax *is* processed within 108 | span-level tags. 109 | 110 | 111 |

Automatic Escaping for Special Characters

112 | 113 | In HTML, there are two characters that demand special treatment: `<` 114 | and `&`. Left angle brackets are used to start tags; ampersands are 115 | used to denote HTML entities. If you want to use them as literal 116 | characters, you must escape them as entities, e.g. `<`, and 117 | `&`. 118 | 119 | Ampersands in particular are bedeviling for web writers. If you want to 120 | write about 'AT&T', you need to write '`AT&T`'. You even need to 121 | escape ampersands within URLs. Thus, if you want to link to: 122 | 123 | http://images.google.com/images?num=30&q=larry+bird 124 | 125 | you need to encode the URL as: 126 | 127 | http://images.google.com/images?num=30&q=larry+bird 128 | 129 | in your anchor tag `href` attribute. Needless to say, this is easy to 130 | forget, and is probably the single most common source of HTML validation 131 | errors in otherwise well-marked-up web sites. 132 | 133 | Markdown allows you to use these characters naturally, taking care of 134 | all the necessary escaping for you. If you use an ampersand as part of 135 | an HTML entity, it remains unchanged; otherwise it will be translated 136 | into `&`. 137 | 138 | So, if you want to include a copyright symbol in your article, you can write: 139 | 140 | © 141 | 142 | and Markdown will leave it alone. But if you write: 143 | 144 | AT&T 145 | 146 | Markdown will translate it to: 147 | 148 | AT&T 149 | 150 | Similarly, because Markdown supports [inline HTML](#html), if you use 151 | angle brackets as delimiters for HTML tags, Markdown will treat them as 152 | such. But if you write: 153 | 154 | 4 < 5 155 | 156 | Markdown will translate it to: 157 | 158 | 4 < 5 159 | 160 | However, inside Markdown code spans and blocks, angle brackets and 161 | ampersands are *always* encoded automatically. This makes it easy to use 162 | Markdown to write about HTML code. (As opposed to raw HTML, which is a 163 | terrible format for writing about HTML syntax, because every single `<` 164 | and `&` in your example code needs to be escaped.) 165 | 166 | 167 | * * * 168 | 169 | 170 |

Block Elements

171 | 172 | 173 |

Paragraphs and Line Breaks

174 | 175 | A paragraph is simply one or more consecutive lines of text, separated 176 | by one or more blank lines. (A blank line is any line that looks like a 177 | blank line -- a line containing nothing but spaces or tabs is considered 178 | blank.) Normal paragraphs should not be indented with spaces or tabs. 179 | 180 | The implication of the "one or more consecutive lines of text" rule is 181 | that Markdown supports "hard-wrapped" text paragraphs. This differs 182 | significantly from most other text-to-HTML formatters (including Movable 183 | Type's "Convert Line Breaks" option) which translate every line break 184 | character in a paragraph into a `
` tag. 185 | 186 | When you *do* want to insert a `
` break tag using Markdown, you 187 | end a line with two or more spaces, then type return. 188 | 189 | Yes, this takes a tad more effort to create a `
`, but a simplistic 190 | "every line break is a `
`" rule wouldn't work for Markdown. 191 | Markdown's email-style [blockquoting][bq] and multi-paragraph [list items][l] 192 | work best -- and look better -- when you format them with hard breaks. 193 | 194 | [bq]: #blockquote 195 | [l]: #list 196 | 197 | 198 | 199 | 200 | 201 | Markdown supports two styles of headers, [Setext] [1] and [atx] [2]. 202 | 203 | Setext-style headers are "underlined" using equal signs (for first-level 204 | headers) and dashes (for second-level headers). For example: 205 | 206 | ```markdown 207 | This is an H1 208 | ============= 209 | 210 | This is an H2 211 | ------------- 212 | ``` 213 | 214 | Any number of underlining `=`'s or `-`'s will work. 215 | 216 | Atx-style headers use 1-6 hash characters at the start of the line, 217 | corresponding to header levels 1-6. For example: 218 | 219 | ```markdown 220 | # This is an H1 221 | 222 | ## This is an H2 223 | 224 | ###### This is an H6 225 | ``` 226 | 227 | Optionally, you may "close" atx-style headers. This is purely 228 | cosmetic -- you can use this if you think it looks better. The 229 | closing hashes don't even need to match the number of hashes 230 | used to open the header. (The number of opening hashes 231 | determines the header level.) : 232 | 233 | ```markdown 234 | # This is an H1 # 235 | 236 | ## This is an H2 ## 237 | 238 | ### This is an H3 ###### 239 | ``` 240 | 241 |

Blockquotes

242 | 243 | Markdown uses email-style `>` characters for blockquoting. If you're 244 | familiar with quoting passages of text in an email message, then you 245 | know how to create a blockquote in Markdown. It looks best if you hard 246 | wrap the text and put a `>` before every line: 247 | 248 | ```markdown 249 | > This is a blockquote with two paragraphs. Lorem ipsum dolor sit amet, 250 | > consectetuer adipiscing elit. Aliquam hendrerit mi posuere lectus. 251 | > Vestibulum enim wisi, viverra nec, fringilla in, laoreet vitae, risus. 252 | > 253 | > Donec sit amet nisl. Aliquam semper ipsum sit amet velit. Suspendisse 254 | > id sem consectetuer libero luctus adipiscing. 255 | ``` 256 | 257 | Markdown allows you to be lazy and only put the `>` before the first 258 | line of a hard-wrapped paragraph: 259 | 260 | ```markdown 261 | > This is a blockquote with two paragraphs. Lorem ipsum dolor sit amet, 262 | consectetuer adipiscing elit. Aliquam hendrerit mi posuere lectus. 263 | Vestibulum enim wisi, viverra nec, fringilla in, laoreet vitae, risus. 264 | 265 | > Donec sit amet nisl. Aliquam semper ipsum sit amet velit. Suspendisse 266 | id sem consectetuer libero luctus adipiscing. 267 | ``` 268 | 269 | Blockquotes can be nested (i.e. a blockquote-in-a-blockquote) by 270 | adding additional levels of `>`: 271 | 272 | ```markdown 273 | > This is the first level of quoting. 274 | > 275 | > > This is nested blockquote. 276 | > 277 | > Back to the first level. 278 | ``` 279 | 280 | Blockquotes can contain other Markdown elements, including headers, lists, 281 | and code blocks: 282 | 283 | ```markdown 284 | > ## This is a header. 285 | > 286 | > 1. This is the first list item. 287 | > 2. This is the second list item. 288 | > 289 | > Here's some example code: 290 | > 291 | > return shell_exec("echo $input | $markdown_script"); 292 | ``` 293 | 294 | Any decent text editor should make email-style quoting easy. For 295 | example, with BBEdit, you can make a selection and choose Increase 296 | Quote Level from the Text menu. 297 | 298 | 299 |

Lists

300 | 301 | Markdown supports ordered (numbered) and unordered (bulleted) lists. 302 | 303 | Unordered lists use asterisks, pluses, and hyphens -- interchangably 304 | -- as list markers: 305 | 306 | ```markdown 307 | * Red 308 | * Green 309 | * Blue 310 | ``` 311 | 312 | is equivalent to: 313 | 314 | ```markdown 315 | + Red 316 | + Green 317 | + Blue 318 | ``` 319 | 320 | and: 321 | 322 | ```markdown 323 | - Red 324 | - Green 325 | - Blue 326 | ``` 327 | 328 | Ordered lists use numbers followed by periods: 329 | 330 | ```markdown 331 | 1. Bird 332 | 2. McHale 333 | 3. Parish 334 | ``` 335 | 336 | It's important to note that the actual numbers you use to mark the 337 | list have no effect on the HTML output Markdown produces. The HTML 338 | Markdown produces from the above list is: 339 | 340 | ```markup 341 |
    342 |
  1. Bird
  2. 343 |
  3. McHale
  4. 344 |
  5. Parish
  6. 345 |
346 | ``` 347 | 348 | If you instead wrote the list in Markdown like this: 349 | 350 | ```markdown 351 | 1. Bird 352 | 1. McHale 353 | 1. Parish 354 | ``` 355 | 356 | or even: 357 | 358 | ```markdown 359 | 3. Bird 360 | 1. McHale 361 | 8. Parish 362 | ``` 363 | 364 | you'd get the exact same HTML output. The point is, if you want to, 365 | you can use ordinal numbers in your ordered Markdown lists, so that 366 | the numbers in your source match the numbers in your published HTML. 367 | But if you want to be lazy, you don't have to. 368 | 369 | If you do use lazy list numbering, however, you should still start the 370 | list with the number 1. At some point in the future, Markdown may support 371 | starting ordered lists at an arbitrary number. 372 | 373 | List markers typically start at the left margin, but may be indented by 374 | up to three spaces. List markers must be followed by one or more spaces 375 | or a tab. 376 | 377 | To make lists look nice, you can wrap items with hanging indents: 378 | 379 | ```markdown 380 | * Lorem ipsum dolor sit amet, consectetuer adipiscing elit. 381 | Aliquam hendrerit mi posuere lectus. Vestibulum enim wisi, 382 | viverra nec, fringilla in, laoreet vitae, risus. 383 | * Donec sit amet nisl. Aliquam semper ipsum sit amet velit. 384 | Suspendisse id sem consectetuer libero luctus adipiscing. 385 | ``` 386 | 387 | But if you want to be lazy, you don't have to: 388 | 389 | ```markdown 390 | * Lorem ipsum dolor sit amet, consectetuer adipiscing elit. 391 | Aliquam hendrerit mi posuere lectus. Vestibulum enim wisi, 392 | viverra nec, fringilla in, laoreet vitae, risus. 393 | * Donec sit amet nisl. Aliquam semper ipsum sit amet velit. 394 | Suspendisse id sem consectetuer libero luctus adipiscing. 395 | ``` 396 | 397 | If list items are separated by blank lines, Markdown will wrap the 398 | items in `

` tags in the HTML output. For example, this input: 399 | 400 | ```markdown 401 | * Bird 402 | * Magic 403 | ``` 404 | 405 | will turn into: 406 | 407 | ```markup 408 |

    409 |
  • Bird
  • 410 |
  • Magic
  • 411 |
412 | ``` 413 | 414 | But this: 415 | 416 | ```markdown 417 | * Bird 418 | * Magic 419 | ``` 420 | 421 | will turn into: 422 | 423 | ```markup 424 |
    425 |
  • Bird

  • 426 |
  • Magic

  • 427 |
428 | ``` 429 | 430 | List items may consist of multiple paragraphs. Each subsequent 431 | paragraph in a list item must be indented by either 4 spaces 432 | or one tab: 433 | 434 | ```markdown 435 | 1. This is a list item with two paragraphs. Lorem ipsum dolor 436 | sit amet, consectetuer adipiscing elit. Aliquam hendrerit 437 | mi posuere lectus. 438 | 439 | Vestibulum enim wisi, viverra nec, fringilla in, laoreet 440 | vitae, risus. Donec sit amet nisl. Aliquam semper ipsum 441 | sit amet velit. 442 | 443 | 2. Suspendisse id sem consectetuer libero luctus adipiscing. 444 | ``` 445 | 446 | It looks nice if you indent every line of the subsequent 447 | paragraphs, but here again, Markdown will allow you to be 448 | lazy: 449 | 450 | ```markdown 451 | * This is a list item with two paragraphs. 452 | 453 | This is the second paragraph in the list item. You're 454 | only required to indent the first line. Lorem ipsum dolor 455 | sit amet, consectetuer adipiscing elit. 456 | 457 | * Another item in the same list. 458 | ``` 459 | 460 | To put a blockquote within a list item, the blockquote's `>` 461 | delimiters need to be indented: 462 | 463 | ```markdown 464 | * A list item with a blockquote: 465 | 466 | > This is a blockquote 467 | > inside a list item. 468 | ``` 469 | 470 | To put a code block within a list item, the code block needs 471 | to be indented *twice* -- 8 spaces or two tabs: 472 | 473 | ```markdown 474 | * A list item with a code block: 475 | 476 | 477 | ``` 478 | 479 | It's worth noting that it's possible to trigger an ordered list by 480 | accident, by writing something like this: 481 | 482 | ```markdown 483 | 1986. What a great season. 484 | ``` 485 | 486 | In other words, a *number-period-space* sequence at the beginning of a 487 | line. To avoid this, you can backslash-escape the period: 488 | 489 | ```markdown 490 | 1986\. What a great season. 491 | ``` 492 | 493 | 494 |

Code Blocks

495 | 496 | Pre-formatted code blocks are used for writing about programming or 497 | markup source code. Rather than forming normal paragraphs, the lines 498 | of a code block are interpreted literally. Markdown wraps a code block 499 | in both `
` and `` tags.
 500 | 
 501 | To produce a code block in Markdown, simply indent every line of the
 502 | block by at least 4 spaces or 1 tab. For example, given this input:
 503 | 
 504 | ```markdown
 505 | This is a normal paragraph:
 506 | 
 507 |     This is a code block.
 508 | ```
 509 | 
 510 | Markdown will generate:
 511 | 
 512 | ```markup
 513 | 

This is a normal paragraph:

514 | 515 |
This is a code block.
 516 | 
517 | ``` 518 | 519 | One level of indentation -- 4 spaces or 1 tab -- is removed from each 520 | line of the code block. For example, this: 521 | 522 | ```markdown 523 | Here is an example of AppleScript: 524 | 525 | tell application "Foo" 526 | beep 527 | end tell 528 | ``` 529 | 530 | will turn into: 531 | 532 | ```markup 533 |

Here is an example of AppleScript:

534 | 535 |
tell application "Foo"
 536 |     beep
 537 | end tell
 538 | 
539 | ``` 540 | 541 | A code block continues until it reaches a line that is not indented 542 | (or the end of the article). 543 | 544 | Within a code block, ampersands (`&`) and angle brackets (`<` and `>`) 545 | are automatically converted into HTML entities. This makes it very 546 | easy to include example HTML source code using Markdown -- just paste 547 | it and indent it, and Markdown will handle the hassle of encoding the 548 | ampersands and angle brackets. For example, this: 549 | 550 | ```markdown 551 | 554 | ``` 555 | 556 | will turn into: 557 | 558 | ```markup 559 |
<div class="footer">
 560 |     &copy; 2004 Foo Corporation
 561 | </div>
 562 | 
563 | ``` 564 | 565 | Regular Markdown syntax is not processed within code blocks. E.g., 566 | asterisks are just literal asterisks within a code block. This means 567 | it's also easy to use Markdown to write about Markdown's own syntax. 568 | 569 |

Horizontal Rules

570 | 571 | You can produce a horizontal rule tag (`
`) by placing three or 572 | more hyphens, asterisks, or underscores on a line by themselves. If you 573 | wish, you may use spaces between the hyphens or asterisks. Each of the 574 | following lines will produce a horizontal rule: 575 | 576 | ```markdown 577 | * * * 578 | 579 | *** 580 | 581 | ***** 582 | 583 | - - - 584 | 585 | --------------------------------------- 586 | ``` 587 | 588 | * * * 589 | 590 |

Span Elements

591 | 592 | 593 | 594 | Markdown supports two style of links: *inline* and *reference*. 595 | 596 | In both styles, the link text is delimited by [square brackets]. 597 | 598 | To create an inline link, use a set of regular parentheses immediately 599 | after the link text's closing square bracket. Inside the parentheses, 600 | put the URL where you want the link to point, along with an *optional* 601 | title for the link, surrounded in quotes. For example: 602 | 603 | ```markdown 604 | This is [an example](http://example.com/ "Title") inline link. 605 | 606 | [This link](http://example.net/) has no title attribute. 607 | ``` 608 | 609 | Will produce: 610 | 611 | ```markup 612 |

This is 613 | an example inline link.

614 | 615 |

This link has no 616 | title attribute.

617 | ``` 618 | 619 | If you're referring to a local resource on the same server, you can 620 | use relative paths: 621 | 622 | ```markdown 623 | See my [About](/about/) page for details. 624 | ``` 625 | 626 | Reference-style links use a second set of square brackets, inside 627 | which you place a label of your choosing to identify the link: 628 | 629 | ```markdown 630 | This is [an example][id] reference-style link. 631 | ``` 632 | 633 | You can optionally use a space to separate the sets of brackets: 634 | 635 | ```markdown 636 | This is [an example] [id] reference-style link. 637 | ``` 638 | 639 | Then, anywhere in the document, you define your link label like this, 640 | on a line by itself: 641 | 642 | ```markdown 643 | [id]: http://example.com/ "Optional Title Here" 644 | ``` 645 | 646 | That is: 647 | 648 | * Square brackets containing the link identifier (optionally 649 | indented from the left margin using up to three spaces); 650 | * followed by a colon; 651 | * followed by one or more spaces (or tabs); 652 | * followed by the URL for the link; 653 | * optionally followed by a title attribute for the link, enclosed 654 | in double or single quotes, or enclosed in parentheses. 655 | 656 | The following three link definitions are equivalent: 657 | 658 | ```markdown 659 | [foo]: http://example.com/ "Optional Title Here" 660 | [foo]: http://example.com/ 'Optional Title Here' 661 | [foo]: http://example.com/ (Optional Title Here) 662 | ``` 663 | 664 | **Note:** There is a known bug in Markdown.pl 1.0.1 which prevents 665 | single quotes from being used to delimit link titles. 666 | 667 | The link URL may, optionally, be surrounded by angle brackets: 668 | 669 | ```markdown 670 | [id]: "Optional Title Here" 671 | ``` 672 | 673 | You can put the title attribute on the next line and use extra spaces 674 | or tabs for padding, which tends to look better with longer URLs: 675 | 676 | ```markdown 677 | [id]: http://example.com/longish/path/to/resource/here 678 | "Optional Title Here" 679 | ``` 680 | 681 | Link definitions are only used for creating links during Markdown 682 | processing, and are stripped from your document in the HTML output. 683 | 684 | Link definition names may consist of letters, numbers, spaces, and 685 | punctuation -- but they are *not* case sensitive. E.g. these two 686 | links: 687 | 688 | ```markdown 689 | [link text][a] 690 | [link text][A] 691 | ``` 692 | 693 | are equivalent. 694 | 695 | The *implicit link name* shortcut allows you to omit the name of the 696 | link, in which case the link text itself is used as the name. 697 | Just use an empty set of square brackets -- e.g., to link the word 698 | "Google" to the google.com web site, you could simply write: 699 | 700 | ```markdown 701 | [Google][] 702 | ``` 703 | 704 | And then define the link: 705 | 706 | ```markdown 707 | [Google]: http://google.com/ 708 | ``` 709 | 710 | Because link names may contain spaces, this shortcut even works for 711 | multiple words in the link text: 712 | 713 | ```markdown 714 | Visit [Daring Fireball][] for more information. 715 | ``` 716 | 717 | And then define the link: 718 | 719 | ```markdown 720 | [Daring Fireball]: http://daringfireball.net/ 721 | ``` 722 | 723 | Link definitions can be placed anywhere in your Markdown document. I 724 | tend to put them immediately after each paragraph in which they're 725 | used, but if you want, you can put them all at the end of your 726 | document, sort of like footnotes. 727 | 728 | Here's an example of reference links in action: 729 | 730 | ```markdown 731 | I get 10 times more traffic from [Google] [1] than from 732 | [Yahoo] [2] or [MSN] [3]. 733 | 734 | [1]: http://google.com/ "Google" 735 | [2]: http://search.yahoo.com/ "Yahoo Search" 736 | [3]: http://search.msn.com/ "MSN Search" 737 | ``` 738 | 739 | Using the implicit link name shortcut, you could instead write: 740 | 741 | ```markdown 742 | I get 10 times more traffic from [Google][] than from 743 | [Yahoo][] or [MSN][]. 744 | 745 | [google]: http://google.com/ "Google" 746 | [yahoo]: http://search.yahoo.com/ "Yahoo Search" 747 | [msn]: http://search.msn.com/ "MSN Search" 748 | ``` 749 | 750 | Both of the above examples will produce the following HTML output: 751 | 752 | ```markup 753 |

I get 10 times more traffic from Google than from 755 | Yahoo 756 | or MSN.

757 | ``` 758 | 759 | For comparison, here is the same paragraph written using 760 | Markdown's inline link style: 761 | 762 | ```markdown 763 | I get 10 times more traffic from [Google](http://google.com/ "Google") 764 | than from [Yahoo](http://search.yahoo.com/ "Yahoo Search") or 765 | [MSN](http://search.msn.com/ "MSN Search"). 766 | ``` 767 | 768 | The point of reference-style links is not that they're easier to 769 | write. The point is that with reference-style links, your document 770 | source is vastly more readable. Compare the above examples: using 771 | reference-style links, the paragraph itself is only 81 characters 772 | long; with inline-style links, it's 176 characters; and as raw HTML, 773 | it's 234 characters. In the raw HTML, there's more markup than there 774 | is text. 775 | 776 | With Markdown's reference-style links, a source document much more 777 | closely resembles the final output, as rendered in a browser. By 778 | allowing you to move the markup-related metadata out of the paragraph, 779 | you can add links without interrupting the narrative flow of your 780 | prose. 781 | 782 | 783 |

Emphasis

784 | 785 | Markdown treats asterisks (`*`) and underscores (`_`) as indicators of 786 | emphasis. Text wrapped with one `*` or `_` will be wrapped with an 787 | HTML `` tag; double `*`'s or `_`'s will be wrapped with an HTML 788 | `` tag. E.g., this input: 789 | 790 | ```markdown 791 | *single asterisks* 792 | 793 | _single underscores_ 794 | 795 | **double asterisks** 796 | 797 | __double underscores__ 798 | ``` 799 | 800 | will produce: 801 | 802 | ```markup 803 | single asterisks 804 | 805 | single underscores 806 | 807 | double asterisks 808 | 809 | double underscores 810 | ``` 811 | 812 | You can use whichever style you prefer; the lone restriction is that 813 | the same character must be used to open and close an emphasis span. 814 | 815 | Emphasis can be used in the middle of a word: 816 | 817 | ```markdown 818 | un*frigging*believable 819 | ``` 820 | 821 | But if you surround an `*` or `_` with spaces, it'll be treated as a 822 | literal asterisk or underscore. 823 | 824 | To produce a literal asterisk or underscore at a position where it 825 | would otherwise be used as an emphasis delimiter, you can backslash 826 | escape it: 827 | 828 | ```markdown 829 | \*this text is surrounded by literal asterisks\* 830 | ``` 831 | 832 | 833 |

Code

834 | 835 | To indicate a span of code, wrap it with backtick quotes (`` ` ``). 836 | Unlike a pre-formatted code block, a code span indicates code within a 837 | normal paragraph. For example: 838 | 839 | ```markdown 840 | Use the `printf()` function. 841 | ``` 842 | 843 | will produce: 844 | 845 | ```markup 846 |

Use the printf() function.

847 | ``` 848 | 849 | To include a literal backtick character within a code span, you can use 850 | multiple backticks as the opening and closing delimiters: 851 | 852 | ```markdown 853 | ``There is a literal backtick (`) here.`` 854 | ``` 855 | 856 | which will produce this: 857 | 858 | ```markup 859 |

There is a literal backtick (`) here.

860 | ``` 861 | 862 | The backtick delimiters surrounding a code span may include spaces -- 863 | one after the opening, one before the closing. This allows you to place 864 | literal backtick characters at the beginning or end of a code span: 865 | 866 | ```markdown 867 | A single backtick in a code span: `` ` `` 868 | 869 | A backtick-delimited string in a code span: `` `foo` `` 870 | ``` 871 | 872 | will produce: 873 | 874 | ```markup 875 |

A single backtick in a code span: `

876 | 877 |

A backtick-delimited string in a code span: `foo`

878 | ``` 879 | 880 | With a code span, ampersands and angle brackets are encoded as HTML 881 | entities automatically, which makes it easy to include example HTML 882 | tags. Markdown will turn this: 883 | 884 | ```markdown 885 | Please don't use any `` tags. 886 | ``` 887 | 888 | into: 889 | 890 | ```markup 891 |

Please don't use any <blink> tags.

892 | ``` 893 | 894 | You can write this: 895 | 896 | ```markdown 897 | `—` is the decimal-encoded equivalent of `—`. 898 | ``` 899 | 900 | to produce: 901 | 902 | ```markup 903 |

&#8212; is the decimal-encoded 904 | equivalent of &mdash;.

905 | ``` 906 | 907 |

Images

908 | 909 | Admittedly, it's fairly difficult to devise a "natural" syntax for 910 | placing images into a plain text document format. 911 | 912 | Markdown uses an image syntax that is intended to resemble the syntax 913 | for links, allowing for two styles: *inline* and *reference*. 914 | 915 | Inline image syntax looks like this: 916 | 917 | ```markdown 918 | ![Alt text](/path/to/img.jpg) 919 | 920 | ![Alt text](/path/to/img.jpg "Optional title") 921 | ``` 922 | 923 | That is: 924 | 925 | * An exclamation mark: `!`; 926 | * followed by a set of square brackets, containing the `alt` 927 | attribute text for the image; 928 | * followed by a set of parentheses, containing the URL or path to 929 | the image, and an optional `title` attribute enclosed in double 930 | or single quotes. 931 | 932 | Reference-style image syntax looks like this: 933 | 934 | ```markdown 935 | ![Alt text][id] 936 | ``` 937 | 938 | Where "id" is the name of a defined image reference. Image references 939 | are defined using syntax identical to link references: 940 | 941 | ```markdown 942 | [id]: url/to/image "Optional title attribute" 943 | ``` 944 | 945 | As of this writing, Markdown has no syntax for specifying the 946 | dimensions of an image; if this is important to you, you can simply 947 | use regular HTML `` tags. 948 | 949 | * * * 950 | 951 |

Miscellaneous

952 | 953 | 954 | 955 | Markdown supports a shortcut style for creating "automatic" links for URLs and email addresses: simply surround the URL or email address with angle brackets. What this means is that if you want to show the actual text of a URL or email address, and also have it be a clickable link, you can do this: 956 | 957 | ```markdown 958 | 959 | ``` 960 | 961 | Markdown will turn this into: 962 | 963 | ```markup 964 | http://example.com/ 965 | ``` 966 | 967 | Automatic links for email addresses work similarly, except that 968 | Markdown will also perform a bit of randomized decimal and hex 969 | entity-encoding to help obscure your address from address-harvesting 970 | spambots. For example, Markdown will turn this: 971 | 972 | ```markup 973 | 974 | ``` 975 | 976 | into something like this: 977 | 978 | ```markup 979 | address@exa 982 | mple.com 983 | ``` 984 | 985 | which will render in a browser as a clickable link to "address@example.com". 986 | 987 | (This sort of entity-encoding trick will indeed fool many, if not 988 | most, address-harvesting bots, but it definitely won't fool all of 989 | them. It's better than nothing, but an address published in this way 990 | will probably eventually start receiving spam.) 991 | 992 | 993 | 994 |

Backslash Escapes

995 | 996 | Markdown allows you to use backslash escapes to generate literal 997 | characters which would otherwise have special meaning in Markdown's 998 | formatting syntax. For example, if you wanted to surround a word 999 | with literal asterisks (instead of an HTML `` tag), you can use 1000 | backslashes before the asterisks, like this: 1001 | 1002 | ```markdown 1003 | \*literal asterisks\* 1004 | ``` 1005 | 1006 | Markdown provides backslash escapes for the following characters: 1007 | 1008 | ```markdown 1009 | \ backslash 1010 | ` backtick 1011 | * asterisk 1012 | _ underscore 1013 | {} curly braces 1014 | [] square brackets 1015 | () parentheses 1016 | # hash mark 1017 | + plus sign 1018 | - minus sign (hyphen) 1019 | . dot 1020 | ! exclamation mark 1021 | ``` -------------------------------------------------------------------------------- /content/mdx.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | path: '/content/mdx/' 3 | date: '2019-03-02' 4 | title: 'MDX support' 5 | tags: ['markdown', 'mdx'] 6 | --- 7 | 8 | import { Counter } from '../src/components/Counter' 9 | 10 | This starter supports [MDX](https://mdxjs.com). MDX is a format that lets you 11 | seamlessly use JSX in your Markdown documents. You can import components, like 12 | interactive charts or notifs, and export metadata. 13 | 14 | 15 | 16 | Find the above React component in `/src/components/Counter.tsx`. 17 | -------------------------------------------------------------------------------- /content/sample01.md: -------------------------------------------------------------------------------- 1 | --- 2 | path: "/content/sample01/" 3 | date: "2016-11-07" 4 | title: "Sample 1" 5 | tags: ["example"] 6 | --- 7 | 8 | Sample content. -------------------------------------------------------------------------------- /content/sample02.md: -------------------------------------------------------------------------------- 1 | --- 2 | path: "/content/sample02/" 3 | date: "2016-11-07" 4 | title: "Sample 2" 5 | tags: ["example"] 6 | --- 7 | 8 | Sample content. -------------------------------------------------------------------------------- /content/sample03.md: -------------------------------------------------------------------------------- 1 | --- 2 | path: "/content/sample03/" 3 | date: "2016-11-07" 4 | title: "Sample 3" 5 | tags: ["example"] 6 | --- 7 | 8 | Sample content. -------------------------------------------------------------------------------- /content/sample04.md: -------------------------------------------------------------------------------- 1 | --- 2 | path: "/content/sample04/" 3 | date: "2016-11-07" 4 | title: "Sample 4" 5 | tags: ["example"] 6 | --- 7 | 8 | Sample content. -------------------------------------------------------------------------------- /content/sample05.md: -------------------------------------------------------------------------------- 1 | --- 2 | path: "/content/sample05/" 3 | date: "2016-11-07" 4 | title: "Sample 5" 5 | tags: ["example"] 6 | --- 7 | 8 | Sample content. -------------------------------------------------------------------------------- /content/sample06.md: -------------------------------------------------------------------------------- 1 | --- 2 | path: "/content/sample06/" 3 | date: "2016-11-07" 4 | title: "Sample 6" 5 | tags: ["example"] 6 | --- 7 | 8 | Sample content. -------------------------------------------------------------------------------- /content/sample07.md: -------------------------------------------------------------------------------- 1 | --- 2 | path: "/content/sample07/" 3 | date: "2016-11-07" 4 | title: "Sample 7" 5 | tags: ["example"] 6 | --- 7 | 8 | Sample content. -------------------------------------------------------------------------------- /content/sample08.md: -------------------------------------------------------------------------------- 1 | --- 2 | path: "/content/sample08/" 3 | date: "2016-11-07" 4 | title: "Sample 8" 5 | tags: ["example"] 6 | --- 7 | 8 | Sample content. -------------------------------------------------------------------------------- /content/sample09.md: -------------------------------------------------------------------------------- 1 | --- 2 | path: "/content/sample09/" 3 | date: "2016-11-07" 4 | title: "Sample 9" 5 | tags: ["example"] 6 | --- 7 | 8 | Sample content. -------------------------------------------------------------------------------- /content/sample10.md: -------------------------------------------------------------------------------- 1 | --- 2 | path: "/content/sample10/" 3 | date: "2016-11-07" 4 | title: "Sample 10" 5 | tags: ["example"] 6 | --- 7 | 8 | Sample content. -------------------------------------------------------------------------------- /gatsby-browser.js: -------------------------------------------------------------------------------- 1 | import 'normalize.css' 2 | import 'typeface-domine' 3 | import 'typeface-montserrat' 4 | import 'prismjs/themes/prism-okaidia.css' 5 | -------------------------------------------------------------------------------- /gatsby-config.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/camelcase */ 2 | const gatsbyRemarkPlugins = [ 3 | 'gatsby-plugin-typegen', 4 | { 5 | resolve: 'gatsby-remark-smartypants', 6 | options: { 7 | dashes: 'oldschool' 8 | } 9 | }, 10 | { 11 | resolve: 'gatsby-remark-prismjs', 12 | options: { 13 | classPrefix: 'language-', 14 | inlineCodeMarker: { 15 | tsx: 'tsx' 16 | }, 17 | aliases: {} 18 | } 19 | }, 20 | { 21 | resolve: 'gatsby-remark-images', 22 | options: { 23 | maxWidth: 1200 24 | } 25 | }, 26 | { 27 | resolve: 'gatsby-remark-copy-linked-files', 28 | options: {} 29 | } 30 | ] 31 | 32 | module.exports = { 33 | siteMetadata: { 34 | title: 'TypeScript Gatsby Starter', 35 | author: 'Luís Rodrigues', 36 | description: 'A Gatsby starter using TypeScript.', 37 | siteUrl: 'https://goblindegook-gatsby-starter-typescript.netlify.com' 38 | }, 39 | plugins: [ 40 | 'gatsby-plugin-typescript', 41 | 'gatsby-plugin-react-helmet', 42 | 'gatsby-plugin-emotion', 43 | { 44 | resolve: 'gatsby-plugin-typography', 45 | options: { 46 | pathToConfigModule: 'src/typography', 47 | omitGoogleFont: true 48 | } 49 | }, 50 | 'gatsby-plugin-catch-links', 51 | { 52 | resolve: `gatsby-source-filesystem`, 53 | options: { 54 | name: 'content', 55 | path: `${__dirname}/content` 56 | } 57 | }, 58 | { 59 | resolve: 'gatsby-plugin-nprogress', 60 | options: { 61 | color: '#ff5700', 62 | showSpinner: false 63 | } 64 | }, 65 | { 66 | resolve: 'gatsby-transformer-remark', 67 | options: { 68 | plugins: gatsbyRemarkPlugins 69 | } 70 | }, 71 | 'gatsby-transformer-sharp', 72 | 'gatsby-plugin-sharp', 73 | { 74 | resolve: 'gatsby-plugin-mdx', 75 | options: { 76 | extensions: ['.md', '.mdx'], 77 | gatsbyRemarkPlugins 78 | } 79 | }, 80 | { 81 | resolve: 'gatsby-plugin-lunr', 82 | options: { 83 | languages: [ 84 | { 85 | name: 'en', 86 | filterNodes: node => !node.frontmatter || node.frontmatter.draft !== true, 87 | customEntries: [ 88 | { 89 | title: 'Another Page', 90 | content: 'Welcome to page 2', 91 | path: '/another-page/' 92 | } 93 | ] 94 | } 95 | ], 96 | fields: [ 97 | { name: 'title', store: true, attributes: { boost: 20 } }, 98 | { name: 'path', store: true }, 99 | { name: 'content' }, 100 | { name: 'tags' } 101 | ], 102 | resolvers: { 103 | Mdx: { 104 | title: node => node.frontmatter.title, 105 | path: node => node.frontmatter.path, 106 | content: node => node.rawBody, 107 | tags: node => node.frontmatter.tags 108 | } 109 | } 110 | } 111 | }, 112 | { 113 | resolve: 'gatsby-plugin-feed', 114 | options: { 115 | /** 116 | * no need to specify the other options, since they will be merged with this 117 | */ 118 | feeds: [ 119 | { 120 | title: 'Feed', 121 | serialize: ({ query: { site, allMdx } }) => { 122 | return allMdx.edges.map(({ node }) => { 123 | return { 124 | ...node.frontmatter, 125 | description: node.excerpt, 126 | url: site.siteMetadata.siteUrl + node.frontmatter.path, 127 | guid: site.siteMetadata.siteUrl + node.frontmatter.path, 128 | custom_elements: [{ 'content:encoded': node.html }] 129 | } 130 | }) 131 | }, 132 | query: ` 133 | { 134 | allMdx( 135 | limit: 1000, 136 | sort: { order: DESC, fields: [frontmatter___date] }, 137 | filter: { frontmatter: { draft: { ne: true } } } 138 | ) { 139 | edges { 140 | node { 141 | frontmatter { 142 | path 143 | title 144 | date 145 | } 146 | excerpt 147 | html 148 | } 149 | } 150 | } 151 | } 152 | `, 153 | output: 'rss.xml' 154 | } 155 | ] 156 | } 157 | }, 158 | { 159 | resolve: 'gatsby-plugin-manifest', 160 | options: { 161 | name: 'gatsby-starter-typescript', 162 | short_name: 'GatsbyTS', 163 | start_url: '/', 164 | background_color: '#f7f0eb', 165 | theme_color: '#a2466c', 166 | display: 'minimal-ui', 167 | icons: [ 168 | { 169 | // Everything in /static will be copied to an equivalent 170 | // directory in /public during development and build, so 171 | // assuming your favicons are in /static/favicon, 172 | // you can reference them here 173 | src: '/favicon/192.png', 174 | sizes: '192x192', 175 | type: 'image/png' 176 | }, 177 | { 178 | src: '/favicon/512.png', 179 | sizes: '512x512', 180 | type: 'image/png' 181 | } 182 | ] 183 | } 184 | }, 185 | 'gatsby-plugin-sitemap', 186 | 'gatsby-plugin-offline', 187 | 'gatsby-plugin-netlify' 188 | ] 189 | } 190 | -------------------------------------------------------------------------------- /gatsby-node.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-var-requires */ 2 | 3 | /** 4 | * Implement Gatsby's Node APIs in this file. 5 | * 6 | * See: https://www.gatsbyjs.org/docs/node-apis/ 7 | */ 8 | 9 | const path = require('path') 10 | const { kebabCase } = require('lodash') 11 | 12 | function groupCountBy(field, edges) { 13 | const groupCounts = edges.reduce((acc, { node }) => { 14 | const groups = node.frontmatter[field] || [] 15 | groups.forEach(group => { 16 | acc[group] = (acc[group] || 0) + 1 17 | }) 18 | return acc 19 | }, {}) 20 | 21 | return Object.entries(groupCounts) 22 | } 23 | 24 | exports.createPages = async ({ actions, graphql, reporter }) => { 25 | const { createPage } = actions 26 | 27 | function createContentListPages({ itemTotal, prefix, component, context, limit = 10 }) { 28 | const pageTotal = Math.ceil(itemTotal / limit) 29 | 30 | for (let page = 1; page <= pageTotal; page++) { 31 | const path = page > 1 ? `${prefix}/${page}` : `${prefix}` 32 | const skip = (page - 1) * limit 33 | 34 | createPage({ 35 | path, 36 | component, 37 | context: { 38 | ...context, 39 | itemTotal, 40 | limit, 41 | page, 42 | pageTotal, 43 | prefix, 44 | skip 45 | } 46 | }) 47 | } 48 | } 49 | 50 | const IndexTemplate = path.resolve('src/templates/IndexTemplate.tsx') 51 | const TagTemplate = path.resolve('src/templates/TagTemplate.tsx') 52 | const SingleTemplate = path.resolve('src/templates/SingleTemplate.tsx') 53 | 54 | const { data, errors } = await graphql(` 55 | { 56 | allMdx(filter: { frontmatter: { draft: { ne: true } } }) { 57 | edges { 58 | node { 59 | parent { 60 | ... on File { 61 | name 62 | sourceInstanceName 63 | } 64 | } 65 | frontmatter { 66 | path 67 | tags 68 | } 69 | } 70 | } 71 | } 72 | } 73 | `) 74 | 75 | if (errors) { 76 | reporter.panicOnBuild('Error fetching data', errors) 77 | return 78 | } 79 | 80 | const edges = data.allMdx.edges 81 | 82 | edges.forEach(({ node }) => { 83 | const { frontmatter, parent } = node 84 | const path = frontmatter.path || `/${parent.sourceInstanceName}/${parent.name}` 85 | createPage({ 86 | path, 87 | component: SingleTemplate 88 | }) 89 | }) 90 | 91 | reporter.info(`Articles (${edges.length})`) 92 | 93 | createContentListPages({ 94 | itemTotal: edges.length, 95 | prefix: '/all', 96 | component: IndexTemplate 97 | }) 98 | 99 | reporter.info(`Index (${Math.ceil(edges.length / 10)})`) 100 | 101 | groupCountBy('tags', edges).forEach(([tag, itemTotal]) => { 102 | createContentListPages({ 103 | itemTotal, 104 | prefix: `/tags/${kebabCase(tag)}`, 105 | component: TagTemplate, 106 | context: { tag } 107 | }) 108 | 109 | reporter.info(`Tag: ${tag} (${Math.ceil(itemTotal / 10)})`) 110 | }) 111 | } 112 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /netlify.toml: -------------------------------------------------------------------------------- 1 | [build] 2 | publish = "public" 3 | command = "yarn lint && yarn build && yarn test" -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@goblindegook/gatsby-starter-typescript", 3 | "description": "A Gatsby starter the way I like it.", 4 | "version": "1.0.0", 5 | "author": "Luís Rodrigues ", 6 | "dependencies": { 7 | "@emotion/core": "^10.0.4", 8 | "@emotion/styled": "^10.0.4", 9 | "@mdx-js/mdx": "^1.0.20", 10 | "@mdx-js/react": "^1.0.20", 11 | "@pacote/react-use-outside": "^1.0.2", 12 | "@reach/router": "^1.2.0", 13 | "@types/lunr": "^2.1.6", 14 | "core-js": "^3.1.4", 15 | "emotion": "^10.0.4", 16 | "emotion-server": "^10.0.4", 17 | "gatsby": "^2.0.7", 18 | "gatsby-page-utils": "^0.1.0", 19 | "gatsby-plugin-catch-links": "^2.0.2", 20 | "gatsby-plugin-emotion": "^4.0.4", 21 | "gatsby-plugin-feed": "^2.0.5", 22 | "gatsby-plugin-lunr": "^1.2.0", 23 | "gatsby-plugin-manifest": "^2.0.2", 24 | "gatsby-plugin-mdx": "^1.0.17", 25 | "gatsby-plugin-netlify": "^2.0.0", 26 | "gatsby-plugin-nprogress": "^2.0.5", 27 | "gatsby-plugin-offline": "^3.0.8", 28 | "gatsby-plugin-react-helmet": "^3.0.0", 29 | "gatsby-plugin-sharp": "^2.0.5", 30 | "gatsby-plugin-sitemap": "^2.0.1", 31 | "gatsby-plugin-typegen": "^0.2.0", 32 | "gatsby-plugin-typescript": "^2.0.0", 33 | "gatsby-plugin-typography": "^2.2.0", 34 | "gatsby-remark-copy-linked-files": "^2.0.9", 35 | "gatsby-remark-images": "^3.0.6", 36 | "gatsby-remark-prismjs": "^3.2.6", 37 | "gatsby-remark-smartypants": "^2.0.8", 38 | "gatsby-source-filesystem": "^2.0.1", 39 | "gatsby-transformer-remark": "^2.6.10", 40 | "gatsby-transformer-sharp": "^2.1.1", 41 | "graphql": "^14.5.8", 42 | "graphql-tag-pluck": "^0.8.5", 43 | "gray-percentage": "^2.0.0", 44 | "lodash": "^4.17.5", 45 | "lunr": "^2.3.3", 46 | "normalize.css": "^8.0.0", 47 | "prismjs": "^1.15.0", 48 | "ramda": "^0.27.0", 49 | "react": "^16.5.2", 50 | "react-dom": "^16.2.0", 51 | "react-helmet": "^5.2.0", 52 | "react-typography": "^0.16.13", 53 | "regenerator-runtime": "^0.13.2", 54 | "typeface-domine": "^0.0.72", 55 | "typeface-montserrat": "^0.0.75", 56 | "typography": "^0.16.17", 57 | "typography-breakpoint-constants": "^0.16.18" 58 | }, 59 | "devDependencies": { 60 | "@babel/core": "^7.8.4", 61 | "@babel/polyfill": "^7.2.5", 62 | "@pacote/eslint-config": "^2.0.0", 63 | "@pacote/eslint-config-jest": "^1.0.0", 64 | "@pacote/eslint-config-react": "^1.0.0", 65 | "@testing-library/jest-dom": "^5.1.1", 66 | "@testing-library/react": "^9.0.2", 67 | "@types/compass-vertical-rhythm": "^1.4.0", 68 | "@types/gray-percentage": "^2.0.0", 69 | "@types/jest": "^25.1.2", 70 | "@types/lodash": "^4.14.104", 71 | "@types/node": "^13.7.0", 72 | "@types/ramda": "^0.27.0", 73 | "@types/react": "^16.4.14", 74 | "@types/react-dom": "^16.0.7", 75 | "@types/react-helmet": "^5.0.5", 76 | "@types/typography": "^0.16.0", 77 | "@types/typography-breakpoint-constants": "^0.16.0", 78 | "@typescript-eslint/eslint-plugin": "^2.19.0", 79 | "@typescript-eslint/parser": "^2.19.0", 80 | "eslint": "^6.0.1", 81 | "eslint-config-prettier": "^6.10.0", 82 | "eslint-config-standard": "^14.0.0", 83 | "eslint-plugin-import": "^2.20.1", 84 | "eslint-plugin-jsx-a11y": "^6.2.3", 85 | "eslint-plugin-node": "^11.0.0", 86 | "eslint-plugin-prettier": "^3.1.0", 87 | "eslint-plugin-promise": "^4.2.1", 88 | "eslint-plugin-react": "^7.18.3", 89 | "eslint-plugin-react-hooks": "^2.1.2", 90 | "eslint-plugin-standard": "^4.0.0", 91 | "husky": "^4.2.1", 92 | "identity-obj-proxy": "^3.0.0", 93 | "jest": "^25.1.0", 94 | "lint-staged": "^10.0.7", 95 | "prettier": "^2.0.1", 96 | "ts-jest": "^25.2.0", 97 | "typescript": "^3.0.3", 98 | "webpack": "^4.19.1" 99 | }, 100 | "keywords": [ 101 | "gatsby", 102 | "markdown", 103 | "typescript" 104 | ], 105 | "license": "MIT", 106 | "scripts": { 107 | "build": "gatsby build", 108 | "develop": "gatsby develop", 109 | "format": "prettier --write '{__mocks__,src,test}/**/*.{ts,tsx}' '*.js'", 110 | "lint": "eslint '{__mocks__,src,test}/**/*.{js,ts,tsx}'", 111 | "test": "jest" 112 | }, 113 | "husky": { 114 | "hooks": { 115 | "pre-commit": "lint-staged" 116 | } 117 | }, 118 | "lint-staged": { 119 | "*.{ts,tsx}": [ 120 | "prettier --write", 121 | "eslint" 122 | ], 123 | "*.{js,json,md}": [ 124 | "prettier --write" 125 | ] 126 | }, 127 | "jest": { 128 | "setupFilesAfterEnv": [ 129 | "@testing-library/jest-dom/extend-expect", 130 | "./setup-jest.js" 131 | ], 132 | "globals": { 133 | "__PATH_PREFIX__": "" 134 | }, 135 | "testURL": "http://localhost", 136 | "transform": { 137 | "^.+\\.(tsx?|jsx?)$": "ts-jest" 138 | }, 139 | "transformIgnorePatterns": [ 140 | "node_modules/(?!(gatsby)/)" 141 | ], 142 | "testRegex": "(/__tests__/.*|\\.(test|spec))\\.(ts|tsx)$", 143 | "testPathIgnorePatterns": [ 144 | "node_modules", 145 | ".cache" 146 | ], 147 | "moduleFileExtensions": [ 148 | "ts", 149 | "tsx", 150 | "js" 151 | ], 152 | "moduleNameMapper": { 153 | "typeface-*": "identity-obj-proxy", 154 | ".+\\.(css|styl|less|sass|scss)$": "identity-obj-proxy", 155 | ".+\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$": "/__mocks__/file.js" 156 | }, 157 | "collectCoverage": false, 158 | "coverageReporters": [ 159 | "lcov", 160 | "text", 161 | "html" 162 | ] 163 | } 164 | } 165 | -------------------------------------------------------------------------------- /setup-jest.js: -------------------------------------------------------------------------------- 1 | /* global jest */ 2 | global.___loader = { 3 | enqueue: jest.fn() 4 | } 5 | -------------------------------------------------------------------------------- /src/components/ContentList.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { css } from '@emotion/core' 3 | import { IndexPageQuery, TagPageQuery } from 'generated/types/gatsby' 4 | import { Link } from 'gatsby' 5 | 6 | interface ContentListProps { 7 | readonly edges: IndexPageQuery['allMdx']['edges'] | TagPageQuery['allMdx']['edges'] 8 | } 9 | 10 | const list = css` 11 | line-height: 1.8; 12 | list-style: none; 13 | padding: 0; 14 | margin: 1rem 0 2rem; 15 | ` 16 | 17 | const item = css`` 18 | 19 | export const ContentList = ({ edges }: ContentListProps) => ( 20 |
    21 | {edges.map(({ node }) => { 22 | const { path, title } = node.frontmatter 23 | return ( 24 |
  • 25 | {title} ({node.frontmatter.date}) 26 |
  • 27 | ) 28 | })} 29 |
30 | ) 31 | -------------------------------------------------------------------------------- /src/components/Counter.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react' 2 | import styled from '@emotion/styled' 3 | 4 | const Wrapper = styled('div')` 5 | margin: 1rem 0; 6 | text-align: center; 7 | ` 8 | 9 | const Button = styled('button')` 10 | padding: 1rem 2rem; 11 | ` 12 | 13 | const Count = styled('span')` 14 | padding: 1rem 2rem; 15 | ` 16 | 17 | export const Counter = () => { 18 | const [count, setCount] = useState(0) 19 | return ( 20 | 21 | 22 | {count} 23 | 24 | 25 | ) 26 | } 27 | -------------------------------------------------------------------------------- /src/components/Header.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import styled from '@emotion/styled' 3 | import { css } from '@emotion/core' 4 | import { Link } from 'gatsby' 5 | import { LunrSearch } from './LunrSearch' 6 | 7 | const style = { 8 | container: css` 9 | background: #ff5700; 10 | margin-bottom: 1.45rem; 11 | `, 12 | wrapper: css` 13 | display: grid; 14 | grid-template-columns: auto 10rem; 15 | grid-template-rows: auto; 16 | margin: 0 auto; 17 | max-width: 960px; 18 | padding: 1.45rem 1.0875rem; 19 | `, 20 | title: css` 21 | margin: 0; 22 | display: inline-block; 23 | `, 24 | } 25 | 26 | const TitleLink = styled(Link)` 27 | color: #fff; 28 | 29 | &:active, 30 | &:hover { 31 | color: #fff; 32 | } 33 | ` 34 | 35 | interface HeaderProps { 36 | readonly title: string 37 | } 38 | 39 | export const Header = ({ title }: HeaderProps) => ( 40 |
41 |
42 |

43 | {title} 44 |

45 | 46 |
47 |
48 | ) 49 | -------------------------------------------------------------------------------- /src/components/Layout.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { graphql, useStaticQuery } from 'gatsby' 3 | import { Helmet } from 'react-helmet' 4 | import { SiteMetadataQuery } from 'generated/types/gatsby' 5 | import { css } from '@emotion/core' 6 | import { Header } from './Header' 7 | 8 | const wrapper = css` 9 | margin: 0 auto; 10 | max-width: 960px; 11 | padding: 0 1.0875rem 1.45rem; 12 | ` 13 | 14 | interface LayoutProps { 15 | readonly children?: React.ReactNode | readonly React.ReactNode[] 16 | } 17 | 18 | export const Layout = ({ children }: LayoutProps) => { 19 | const data = useStaticQuery(graphql` 20 | query SiteMetadata { 21 | site { 22 | siteMetadata { 23 | title 24 | description 25 | } 26 | } 27 | } 28 | `) 29 | 30 | return ( 31 |
32 | 46 |
47 |
{children}
48 |
49 | ) 50 | } 51 | -------------------------------------------------------------------------------- /src/components/LunrSearch.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react' 2 | import { useOutside } from '@pacote/react-use-outside' 3 | import { css } from '@emotion/core' 4 | import { Link } from 'gatsby' 5 | import lunr from 'lunr' 6 | 7 | declare global { 8 | interface Window { 9 | __LUNR__: { 10 | readonly [language: string]: { 11 | readonly index: lunr.Index 12 | readonly store: { 13 | readonly [key: string]: any 14 | } 15 | } 16 | } 17 | } 18 | } 19 | 20 | interface SearchResult extends lunr.Index.Result { 21 | readonly title: string 22 | readonly path: string 23 | } 24 | 25 | const accent = '#ff5700' 26 | 27 | const styles = { 28 | wrapper: css` 29 | display: inline-block; 30 | `, 31 | input: css` 32 | padding: 0.25rem 0.5rem; 33 | width: 12rem; 34 | `, 35 | list: css` 36 | background-color: #fff; 37 | border: 1px solid ${accent}; 38 | display: block; 39 | list-style: none; 40 | margin: 0; 41 | padding: 0; 42 | position: absolute; 43 | width: 12rem; 44 | z-index: 2; 45 | `, 46 | item: css` 47 | border-bottom: 1px dotted ${accent}; 48 | margin: 0; 49 | `, 50 | link: css` 51 | display: block; 52 | padding: 0.25rem 0.5rem; 53 | `, 54 | footer: css` 55 | font-size: 0.75rem; 56 | margin: 0; 57 | padding: 0.5rem; 58 | border: 0; 59 | `, 60 | hidden: css` 61 | position: absolute; 62 | left: -10000px; 63 | top: auto; 64 | width: 1px; 65 | height: 1px; 66 | overflow: hidden; 67 | `, 68 | } 69 | 70 | const search = (query: string): readonly SearchResult[] => { 71 | const { index, store } = window.__LUNR__ && window.__LUNR__.en 72 | return query ? index.search(query).map(({ ref }) => store[ref]) : [] 73 | } 74 | 75 | interface LunrSearchProps { 76 | readonly limit?: number 77 | } 78 | 79 | export const LunrSearch = ({ limit }: LunrSearchProps) => { 80 | const [query, setQuery] = useState('') 81 | const [results, setResults] = useState([]) 82 | const [isActive, setActive] = useState(false) 83 | 84 | const ref = useOutside('click', () => { 85 | setActive(false) 86 | }) 87 | 88 | return ( 89 |
90 | 103 | {isActive ? ( 104 |
    105 | {results.slice(0, limit).map((result, index) => ( 106 |
  • 107 | 108 | {result.title} 109 | 110 |
  • 111 | ))} 112 |
  • 113 | Showing {limit ? `${Math.min(limit, results.length)} of` : null} {results.length}{' '} 114 | {results.length === 1 ? 'result' : 'results'}. 115 |
  • 116 |
117 | ) : null} 118 |
119 | ) 120 | } 121 | -------------------------------------------------------------------------------- /src/components/Pager.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import styled from '@emotion/styled' 3 | import { Link } from 'gatsby' 4 | 5 | interface PagerProps { 6 | readonly prefix: string 7 | readonly page: number 8 | readonly total: number 9 | } 10 | 11 | function pageUrl(prefix: string, page: number): string { 12 | return page <= 1 ? `/${prefix}` : `/${prefix}/${page}` 13 | } 14 | 15 | const NavLink = styled(Link)` 16 | background-color: #ff5700; 17 | border-radius: 3px; 18 | color: #fff; 19 | font-family: sans-serif; 20 | margin: 0 1rem 0 0; 21 | padding: 0.25rem 0.5rem; 22 | text-decoration: none; 23 | 24 | &:active, 25 | &:hover { 26 | color: #fff; 27 | } 28 | ` 29 | 30 | export const Pager = ({ prefix, page, total }: PagerProps) => ( 31 |
32 | {page > 1 && Previous} 33 | {page < total && Next} 34 |
35 | ) 36 | -------------------------------------------------------------------------------- /src/context.ts: -------------------------------------------------------------------------------- 1 | export type ArchivePageContext = { 2 | readonly itemTotal: number 3 | readonly page: number 4 | readonly pageTotal: number 5 | readonly prefix: string 6 | } 7 | -------------------------------------------------------------------------------- /src/globals.d.ts: -------------------------------------------------------------------------------- 1 | declare type DeepPartial = { 2 | [P in keyof T]?: T[P] extends (infer U)[] 3 | ? DeepPartial[] 4 | : T[P] extends readonly (infer U)[] 5 | ? readonly DeepPartial[] 6 | : DeepPartial 7 | } 8 | -------------------------------------------------------------------------------- /src/pages/404.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Layout } from '../components/Layout' 3 | 4 | const NotFoundPage = () => ( 5 | 6 |

Nothing Here

7 |

Check that you followed the correct address.

8 |
9 | ) 10 | 11 | export default NotFoundPage 12 | -------------------------------------------------------------------------------- /src/pages/another-page.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Link } from 'gatsby' 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 |
11 | ) 12 | 13 | export default SecondPage 14 | -------------------------------------------------------------------------------- /src/pages/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Link } from 'gatsby' 3 | import { Layout } from '../components/Layout' 4 | 5 | export const IndexPage = () => ( 6 | <> 7 |

Hi people

8 |

Welcome to your new Gatsby site.

9 |

Now go build something great.

10 |

11 | Go to another page 12 |

13 |

14 | See content generated from Markdown files 15 |

16 | 17 | ) 18 | 19 | const LayoutIndexPage = () => ( 20 | 21 | 22 | 23 | ) 24 | 25 | export default LayoutIndexPage 26 | -------------------------------------------------------------------------------- /src/pages/tags.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { graphql, Link } from 'gatsby' 3 | import Helmet from 'react-helmet' 4 | import { kebabCase } from 'lodash' 5 | import { TagListPageQuery } from 'generated/types/gatsby' 6 | import { Layout } from '../components/Layout' 7 | 8 | interface TagsPageProps { 9 | readonly data: TagListPageQuery 10 | } 11 | 12 | const TagsPage = ({ data }: TagsPageProps) => { 13 | return ( 14 | 15 | 16 |
17 |

Tags

18 |
    19 | {data.allMdx.group.map(({ tag, totalCount }) => ( 20 |
  • 21 | {tag} ({totalCount}) 22 |
  • 23 | ))} 24 |
25 |
26 |
27 | ) 28 | } 29 | 30 | export default TagsPage 31 | 32 | export const query = graphql` 33 | query TagListPage { 34 | allMdx(filter: { frontmatter: { draft: { ne: true } } }) { 35 | group(field: frontmatter___tags) { 36 | tag: fieldValue 37 | totalCount 38 | } 39 | } 40 | } 41 | ` 42 | -------------------------------------------------------------------------------- /src/templates/IndexTemplate.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Link, graphql } from 'gatsby' 3 | import { Helmet } from 'react-helmet' 4 | import { IndexPageQuery, IndexPageQueryVariables } from 'generated/types/gatsby' 5 | import { ContentList } from '../components/ContentList' 6 | import { Pager } from '../components/Pager' 7 | import { Layout } from '../components/Layout' 8 | import { ArchivePageContext } from '../context' 9 | 10 | interface IndexPageProps { 11 | readonly data: IndexPageQuery 12 | 13 | readonly pageContext: ArchivePageContext & IndexPageQueryVariables 14 | } 15 | 16 | const IndexTemplate = ({ data, pageContext }: IndexPageProps) => ( 17 | 18 | 24 |

All Markdown Content

25 | 26 | 27 |
28 | All tags 29 |
30 | ) 31 | 32 | export default IndexTemplate 33 | 34 | export const query = graphql` 35 | query IndexPage($skip: Int!, $limit: Int!) { 36 | allMdx( 37 | filter: { frontmatter: { draft: { ne: true } } } 38 | sort: { order: DESC, fields: [frontmatter___date] } 39 | limit: $limit 40 | skip: $skip 41 | ) { 42 | edges { 43 | node { 44 | frontmatter { 45 | date(formatString: "MMMM D, YYYY") 46 | path 47 | title 48 | } 49 | } 50 | } 51 | } 52 | } 53 | ` 54 | -------------------------------------------------------------------------------- /src/templates/SingleTemplate.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { graphql } from 'gatsby' 3 | import Helmet from 'react-helmet' 4 | import { SinglePageQuery } from 'generated/types/gatsby' 5 | import { Layout } from '../components/Layout' 6 | 7 | // eslint-disable-next-line @typescript-eslint/no-var-requires 8 | const MDXRenderer = require('gatsby-plugin-mdx/mdx-renderer') 9 | 10 | interface ContentTemplateProps { 11 | readonly data: SinglePageQuery 12 | } 13 | 14 | const ContentTemplate = ({ data }: ContentTemplateProps) => { 15 | const { 16 | mdx: { frontmatter, body }, 17 | } = data 18 | 19 | return ( 20 | 21 | 22 |

{frontmatter.title}

23 |

{frontmatter.date}

24 | {body} 25 |
26 | ) 27 | } 28 | 29 | export default ContentTemplate 30 | 31 | export const query = graphql` 32 | query SinglePage($path: String!) { 33 | mdx(frontmatter: { draft: { ne: true }, path: { eq: $path } }) { 34 | body 35 | frontmatter { 36 | date(formatString: "MMMM D, YYYY") 37 | path 38 | title 39 | } 40 | } 41 | } 42 | ` 43 | -------------------------------------------------------------------------------- /src/templates/TagTemplate.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Link, graphql } from 'gatsby' 3 | import Helmet from 'react-helmet' 4 | import { TagPageQuery, TagPageQueryVariables } from 'generated/types/gatsby' 5 | import { ContentList } from '../components/ContentList' 6 | import { Pager } from '../components/Pager' 7 | import { Layout } from '../components/Layout' 8 | import { ArchivePageContext } from '../context' 9 | 10 | interface TagTemplateProps { 11 | readonly data: TagPageQuery 12 | 13 | readonly pageContext: ArchivePageContext & TagPageQueryVariables 14 | } 15 | 16 | const TagTemplate = (props: TagTemplateProps) => { 17 | const { edges } = props.data.allMdx 18 | const { page, prefix, pageTotal, tag } = props.pageContext 19 | 20 | return ( 21 | 22 | 23 |

{`Content tagged with "${tag}"`}

24 | 25 | 26 |
27 | All tags 28 |
29 | ) 30 | } 31 | 32 | export default TagTemplate 33 | 34 | export const query = graphql` 35 | query TagPage($tag: String!, $skip: Int!, $limit: Int!) { 36 | allMdx( 37 | filter: { frontmatter: { draft: { ne: true }, tags: { in: [$tag] } } } 38 | sort: { order: DESC, fields: [frontmatter___date] } 39 | limit: $limit 40 | skip: $skip 41 | ) { 42 | edges { 43 | node { 44 | frontmatter { 45 | date(formatString: "MMMM D, YYYY") 46 | path 47 | title 48 | } 49 | } 50 | } 51 | } 52 | } 53 | ` 54 | -------------------------------------------------------------------------------- /src/typography.ts: -------------------------------------------------------------------------------- 1 | import Typography from 'typography' 2 | import gray from 'gray-percentage' 3 | import { MOBILE_MEDIA_QUERY } from 'typography-breakpoint-constants' 4 | 5 | const typography = new Typography({ 6 | baseFontSize: '20px', 7 | baseLineHeight: 1.45, 8 | blockMarginBottom: 0.8, 9 | headerFontFamily: ['Montserrat', 'sans-serif'], 10 | bodyFontFamily: ['Domine', 'serif'], 11 | bodyColor: gray(10), 12 | headerWeight: 600, 13 | bodyWeight: 300, 14 | boldWeight: 600, 15 | overrideStyles: ({ adjustFontSizeTo, scale, rhythm }, options) => ({ 16 | 'h1,h2,h3,h4,h5,h6': { 17 | lineHeight: 1.2, 18 | }, 19 | a: { 20 | color: '#ff5700', 21 | textDecoration: 'none', 22 | }, 23 | 'a:hover, a:active': { 24 | color: options.bodyColor, 25 | }, 26 | blockquote: { 27 | ...scale(1 / 5), 28 | color: gray(41), 29 | fontStyle: 'italic', 30 | paddingLeft: rhythm(13 / 16), 31 | marginLeft: 0, 32 | borderLeft: `${rhythm(3 / 16)} solid ${gray(10)}`, 33 | }, 34 | 'blockquote > :last-child': { 35 | marginBottom: 0, 36 | }, 37 | 'blockquote cite': { 38 | ...adjustFontSizeTo(options.baseFontSize), 39 | color: options.bodyColor, 40 | fontWeight: options.bodyWeight, 41 | }, 42 | 'blockquote cite:before': { 43 | content: '"— "', 44 | }, 45 | [MOBILE_MEDIA_QUERY]: { 46 | html: { 47 | fontSize: `${(16 / 16) * 100}%`, 48 | }, 49 | blockquote: { 50 | marginLeft: rhythm(-3 / 4), 51 | marginRight: 0, 52 | paddingLeft: rhythm(9 / 16), 53 | }, 54 | }, 55 | }), 56 | }) 57 | 58 | export default typography 59 | -------------------------------------------------------------------------------- /static/favicon/192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/goblindegook/gatsby-starter-typescript/11c71951913256809e2db7b9349daa23e0563ba0/static/favicon/192.png -------------------------------------------------------------------------------- /static/favicon/512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/goblindegook/gatsby-starter-typescript/11c71951913256809e2db7b9349daa23e0563ba0/static/favicon/512.png -------------------------------------------------------------------------------- /test/components/ContentList.spec.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-non-null-assertion */ 2 | import React from 'react' 3 | import { merge } from 'ramda' 4 | import { render } from '@testing-library/react' 5 | import { Mdx, MdxEdge } from 'generated/types/gatsby' 6 | import { ContentList } from '../../src/components/ContentList' 7 | 8 | function createEdge(override: DeepPartial): MdxEdge { 9 | return { 10 | node: merge( 11 | { 12 | frontmatter: { 13 | path: '', 14 | title: '', 15 | }, 16 | }, 17 | override 18 | ), 19 | } as MdxEdge 20 | } 21 | 22 | describe('', () => { 23 | it('renders a list of content links', () => { 24 | const edges = [ 25 | createEdge({ 26 | frontmatter: { path: '/path/1', title: 'Content 1' }, 27 | }), 28 | createEdge({ 29 | frontmatter: { path: '/path/2', title: 'Content 2' }, 30 | }), 31 | ] 32 | const { getByText } = render() 33 | expect( 34 | ['Content 1', 'Content 2'].map((text) => getByText(text).closest('a')!.getAttribute('href')) 35 | ).toEqual(['/path/1', '/path/2']) 36 | }) 37 | }) 38 | -------------------------------------------------------------------------------- /test/components/Header.spec.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { render } from '@testing-library/react' 3 | import { Header } from '../../src/components/Header' 4 | 5 | describe('
', () => { 6 | it('renders the title', () => { 7 | const title = 'Test Title' 8 | const { getByText } = render(
) 9 | expect(getByText(title).tagName).toBeTruthy() 10 | }) 11 | }) 12 | -------------------------------------------------------------------------------- /test/components/LunrSearch.spec.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import '@testing-library/jest-dom/extend-expect' 3 | import { render, fireEvent } from '@testing-library/react' 4 | import lunr from 'lunr' 5 | import { LunrSearch } from '../../src/components/LunrSearch' 6 | 7 | function change(element: HTMLElement, value: string): void { 8 | fireEvent.change(element, { 9 | target: { value }, 10 | }) 11 | } 12 | 13 | function setupLunrIndex(store: { readonly [key: string]: {} }): void { 14 | window.__LUNR__ = { 15 | en: { 16 | index: lunr(function () { 17 | this.field('path') 18 | this.field('title') 19 | Object.entries(store).map(([id, document]) => this.add({ id, ...document })) 20 | }), 21 | store, 22 | }, 23 | } 24 | } 25 | 26 | function cleanupLunrIndex(): void { 27 | delete window.__LUNR__ 28 | } 29 | 30 | describe('LunrSearch', () => { 31 | afterEach(cleanupLunrIndex) 32 | 33 | it('displays search results from the global Lunr index', () => { 34 | setupLunrIndex({ 35 | '1': { path: '/1', title: 'Number One' }, 36 | '2': { path: '/2', title: 'Number Two' }, 37 | }) 38 | 39 | const { getByText, queryByText, getByLabelText } = render() 40 | change(getByLabelText('Search'), 'two') 41 | 42 | expect(queryByText('Number One')).not.toBeInTheDocument() 43 | expect(getByText('Number Two')).toHaveAttribute('href', '/2') 44 | expect(getByText('Showing 1 result.')).toBeTruthy() 45 | }) 46 | 47 | it('limit number of search results displayed', () => { 48 | setupLunrIndex({ 49 | '1': { path: '/1', title: 'Number One' }, 50 | '2': { path: '/2', title: 'Number Two' }, 51 | '3': { path: '/2', title: 'Number Three' }, 52 | }) 53 | 54 | const { getByText, getAllByText, getByLabelText } = render() 55 | change(getByLabelText('Search'), 'number') 56 | 57 | expect(getAllByText(/Number/)).toHaveLength(2) 58 | expect(getByText('Showing 2 of 3 results.')).toBeTruthy() 59 | }) 60 | 61 | it('shows the number of results if limit is greater', () => { 62 | setupLunrIndex({ 63 | '1': { path: '/1', title: 'Number One' }, 64 | '2': { path: '/2', title: 'Number Two' }, 65 | '3': { path: '/3', title: 'Number Three' }, 66 | }) 67 | 68 | const { getByText, getByLabelText } = render() 69 | change(getByLabelText('Search'), 'number') 70 | 71 | expect(getByText('Showing 3 of 3 results.')).toBeTruthy() 72 | }) 73 | 74 | it('hides search results on clicking outside the component', () => { 75 | setupLunrIndex({ 76 | '1': { path: '/test', title: 'Test' }, 77 | }) 78 | 79 | const { queryByText, getByLabelText, getByTestId } = render( 80 |
81 | 82 | 83 |
84 | ) 85 | 86 | change(getByLabelText('Search'), 'test') 87 | fireEvent.click(getByTestId('outside')) 88 | 89 | expect(queryByText('Test')).not.toBeInTheDocument() 90 | }) 91 | }) 92 | -------------------------------------------------------------------------------- /test/components/Pager.spec.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { render } from '@testing-library/react' 3 | import { Pager } from '../../src/components/Pager' 4 | 5 | describe('', () => { 6 | it('renders a pager for the first page', () => { 7 | const { getByText } = render() 8 | expect(getByText('Next').getAttribute('href')).toBe('/prefix/2') 9 | }) 10 | 11 | it('renders a pager for the second page', () => { 12 | const { getByText } = render() 13 | expect(['Previous', 'Next'].map((text) => getByText(text).getAttribute('href'))).toEqual([ 14 | '/prefix', 15 | '/prefix/3', 16 | ]) 17 | }) 18 | 19 | it('renders a pager for the last page', () => { 20 | const { getByText } = render() 21 | expect(getByText('Previous').getAttribute('href')).toBe('/prefix/2') 22 | }) 23 | }) 24 | -------------------------------------------------------------------------------- /test/pages/fixtures/site.json: -------------------------------------------------------------------------------- 1 | { 2 | "site": { 3 | "siteMetadata": { 4 | "title": "Gatsby Test" 5 | } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /test/pages/index.spec.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import '@testing-library/jest-dom/extend-expect' 3 | import { render } from '@testing-library/react' 4 | import { IndexPage } from '../../src/pages/index' 5 | 6 | describe('IndexPage', () => { 7 | it('renders correctly', () => { 8 | const { getByText } = render() 9 | 10 | expect(getByText('Go to another page')).toHaveAttribute('href', '/another-page/') 11 | expect(getByText('See content generated from Markdown files')).toHaveAttribute('href', '/all/') 12 | }) 13 | }) 14 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "allowSyntheticDefaultImports": true, 4 | "esModuleInterop": true, 5 | "allowJs": true, 6 | "declaration": false, 7 | "importHelpers": true, 8 | "jsx": "react", 9 | "lib": ["dom", "es2015", "es2017"], 10 | "module": "commonjs", 11 | "noEmitHelpers": false, 12 | "noImplicitAny": true, 13 | "noResolve": false, 14 | "noUnusedLocals": true, 15 | "noUnusedParameters": false, 16 | "preserveConstEnums": true, 17 | "removeComments": true, 18 | "sourceMap": true, 19 | "strictNullChecks": true, 20 | "target": "esnext" 21 | }, 22 | "include": ["./src/**/*", "./test/**/*"] 23 | } 24 | --------------------------------------------------------------------------------