├── .gitignore ├── src ├── routes │ ├── notfound │ │ ├── style.css │ │ └── index.js │ ├── contact-success │ │ ├── index.js │ │ └── style.css │ ├── contact │ │ ├── style.css │ │ └── index.js │ ├── blogs │ │ ├── style.css │ │ └── index.js │ ├── home │ │ ├── style.css │ │ └── index.js │ └── blog │ │ ├── formatted-code-block.js │ │ ├── index.js │ │ └── style.css ├── assets │ ├── favicon.ico │ ├── profile.jpg │ ├── icons │ │ ├── favicon-16x16.png │ │ ├── favicon-32x32.png │ │ ├── apple-touch-icon.png │ │ ├── mstile-150x150.png │ │ ├── android-chrome-192x192.png │ │ └── android-chrome-512x512.png │ ├── brett-jordan-1329359-unsplash.jpg │ ├── cody-black-nm89mzvar5i-unsplash.jpg │ ├── lucas-ludwig-dh2ztme9kni-unsplash.jpg │ └── quotes.svg ├── index.js ├── components │ ├── header │ │ ├── index.js │ │ └── style.css │ └── app.js ├── manifest.json ├── static │ ├── preview.css │ ├── config.yml │ └── admin.html ├── style │ └── index.css ├── template.html └── crawler │ └── index.js ├── .netlify └── state.json ├── netlify.toml ├── .babelrc ├── content ├── images │ ├── cyclist.md │ └── lighthouse.md └── blog │ ├── visiting-goa.md │ ├── wanderlust.md │ └── how-to-share-a-screenshot-of-the-page.md ├── tests ├── __mocks__ │ ├── fileMocks.js │ └── browserMocks.js └── header.test.js ├── preact.config.js ├── README.md ├── prerender-urls.js └── package.json /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | /build 3 | /*.log 4 | 5 | package-lock.json -------------------------------------------------------------------------------- /src/routes/notfound/style.css: -------------------------------------------------------------------------------- 1 | .notfound { 2 | padding: 0 5%; 3 | } -------------------------------------------------------------------------------- /.netlify/state.json: -------------------------------------------------------------------------------- 1 | { 2 | "siteId": "7ae42434-edff-45ba-ba1d-ffbf86110acd" 3 | } -------------------------------------------------------------------------------- /src/assets/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/preactjs/preact-netlify/HEAD/src/assets/favicon.ico -------------------------------------------------------------------------------- /src/assets/profile.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/preactjs/preact-netlify/HEAD/src/assets/profile.jpg -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import './style'; 2 | import App from './components/app'; 3 | 4 | export default App; 5 | -------------------------------------------------------------------------------- /src/assets/icons/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/preactjs/preact-netlify/HEAD/src/assets/icons/favicon-16x16.png -------------------------------------------------------------------------------- /src/assets/icons/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/preactjs/preact-netlify/HEAD/src/assets/icons/favicon-32x32.png -------------------------------------------------------------------------------- /src/assets/icons/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/preactjs/preact-netlify/HEAD/src/assets/icons/apple-touch-icon.png -------------------------------------------------------------------------------- /src/assets/icons/mstile-150x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/preactjs/preact-netlify/HEAD/src/assets/icons/mstile-150x150.png -------------------------------------------------------------------------------- /src/assets/brett-jordan-1329359-unsplash.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/preactjs/preact-netlify/HEAD/src/assets/brett-jordan-1329359-unsplash.jpg -------------------------------------------------------------------------------- /src/assets/icons/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/preactjs/preact-netlify/HEAD/src/assets/icons/android-chrome-192x192.png -------------------------------------------------------------------------------- /src/assets/icons/android-chrome-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/preactjs/preact-netlify/HEAD/src/assets/icons/android-chrome-512x512.png -------------------------------------------------------------------------------- /src/assets/cody-black-nm89mzvar5i-unsplash.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/preactjs/preact-netlify/HEAD/src/assets/cody-black-nm89mzvar5i-unsplash.jpg -------------------------------------------------------------------------------- /src/assets/lucas-ludwig-dh2ztme9kni-unsplash.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/preactjs/preact-netlify/HEAD/src/assets/lucas-ludwig-dh2ztme9kni-unsplash.jpg -------------------------------------------------------------------------------- /netlify.toml: -------------------------------------------------------------------------------- 1 | 2 | [build] 3 | command = "yarn build" 4 | publish = "build" 5 | [build.environment] 6 | YARN_VERSION = "1.9.4" 7 | YARN_FLAGS = "--no-ignore-optional" -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "test": { 4 | "presets": [ 5 | ["preact-cli/babel", { "modules": "commonjs" }] 6 | ] 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /content/images/cyclist.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Cyclist 3 | date: 2019-08-27T18:55:16.273Z 4 | photo: /assets/brett-jordan-1329359-unsplash.jpg 5 | --- 6 | Cyclist through a city 7 | -------------------------------------------------------------------------------- /content/images/lighthouse.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Lighthouse 3 | date: 2019-10-10T23:57:09.340Z 4 | photo: /assets/lucas-ludwig-dh2ztme9kni-unsplash.jpg 5 | --- 6 | A light house captured from the shore 7 | -------------------------------------------------------------------------------- /tests/__mocks__/fileMocks.js: -------------------------------------------------------------------------------- 1 | // This fixed an error related to the CSS and loading gif breaking my Jest test 2 | // See https://facebook.github.io/jest/docs/en/webpack.html#handling-static-assets 3 | module.exports = 'test-file-stub'; -------------------------------------------------------------------------------- /src/routes/contact-success/index.js: -------------------------------------------------------------------------------- 1 | import { h } from 'preact'; 2 | import style from './style'; 3 | 4 | const photographs = (props) => { 5 | return ( 6 |
7 |

Thanks! I'll be in touch soon.

8 |
9 | ); 10 | }; 11 | 12 | export default photographs; 13 | -------------------------------------------------------------------------------- /src/routes/contact-success/style.css: -------------------------------------------------------------------------------- 1 | .pageContact { 2 | padding: 0 5%; 3 | } 4 | 5 | .pageTitle { 6 | position: relative; 7 | display: inline-block; 8 | font-weight: 500; 9 | } 10 | 11 | .pageTitle:after { 12 | position: absolute; 13 | bottom: -5px; 14 | left: 0px; 15 | content: ''; 16 | width: 75%; 17 | border-bottom: 2px solid #FFC107; 18 | } -------------------------------------------------------------------------------- /src/routes/notfound/index.js: -------------------------------------------------------------------------------- 1 | import { h } from 'preact'; 2 | import style from './style'; 3 | import { Link } from 'preact-router/match'; 4 | 5 | const Notfound = () => { 6 | 7 | return ( 8 |
9 |

Error 404

10 |

That page doesn't exist.

11 |

Back to Home

12 |
13 | ) 14 | } 15 | 16 | export default Notfound; -------------------------------------------------------------------------------- /src/components/header/index.js: -------------------------------------------------------------------------------- 1 | import { h } from 'preact'; 2 | import { Link } from 'preact-router/match'; 3 | import style from './style'; 4 | 5 | const Header = () => ( 6 |
7 |

Jane Doe

8 | 12 |
13 | ); 14 | 15 | export default Header; 16 | -------------------------------------------------------------------------------- /tests/header.test.js: -------------------------------------------------------------------------------- 1 | import Header from '../src/components/header'; 2 | import { Link } from 'preact-router/match'; 3 | // See: https://github.com/mzgoddard/preact-render-spy 4 | import { shallow } from 'preact-render-spy'; 5 | 6 | describe('Initial Test of the Header', () => { 7 | test('Header renders 3 nav items', () => { 8 | const context = shallow(
); 9 | expect(context.find('h1').text()).toBe('Preact App'); 10 | expect(context.find().length).toBe(3); 11 | }); 12 | }); 13 | -------------------------------------------------------------------------------- /src/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "blog", 3 | "short_name": "blog", 4 | "start_url": "/", 5 | "display": "standalone", 6 | "orientation": "portrait", 7 | "background_color": "#fff", 8 | "theme_color": "#673ab8", 9 | "icons": [ 10 | { 11 | "src": "/assets/icons/android-chrome-192x192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "/assets/icons/android-chrome-512x512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ] 21 | } -------------------------------------------------------------------------------- /src/static/preview.css: -------------------------------------------------------------------------------- 1 | html { 2 | font-family: Helvetica Neue,arial,sans-serif; 3 | color: #333; 4 | max-width: 728px; 5 | width: 100%; 6 | margin: 0 auto 10em; 7 | line-height: 1.6rem; 8 | } 9 | 10 | img { 11 | max-width: 100%; 12 | } 13 | 14 | a { 15 | color: #000; 16 | } 17 | 18 | .blogTitle { 19 | font-size: 2.2rem; 20 | font-weight: 400; 21 | margin-bottom: 8px; 22 | line-height: 1; 23 | } 24 | 25 | .blogSubTitle{ 26 | color: #aaa; 27 | display: block; 28 | text-align: left; 29 | font-size: 1.2rem; 30 | margin-bottom: 2rem; 31 | } -------------------------------------------------------------------------------- /preact.config.js: -------------------------------------------------------------------------------- 1 | const netlifyPlugin = require('preact-cli-plugin-netlify'); 2 | const ImageminPlugin = require('imagemin-webpack-plugin').default; 3 | const imageminMozjpeg = require('imagemin-mozjpeg'); 4 | 5 | module.exports = (config, env) => { 6 | netlifyPlugin(config); 7 | env.production && !env.ssr && config.plugins.push(new ImageminPlugin({ 8 | from: './build/assets/**', 9 | pngquant: { 10 | quality: '60' 11 | }, 12 | plugins: [ 13 | imageminMozjpeg({ 14 | quality: 50, 15 | progressive: true 16 | }) 17 | ] 18 | })); 19 | return config; 20 | }; 21 | -------------------------------------------------------------------------------- /tests/__mocks__/browserMocks.js: -------------------------------------------------------------------------------- 1 | // Mock Browser API's which are not supported by JSDOM, e.g. ServiceWorker, LocalStorage 2 | /** 3 | * An example how to mock localStorage is given below 👇 4 | */ 5 | 6 | /* 7 | // Mocks localStorage 8 | const localStorageMock = (function() { 9 | let store = {}; 10 | 11 | return { 12 | getItem: (key) => store[key] || null, 13 | setItem: (key, value) => store[key] = value.toString(), 14 | clear: () => store = {} 15 | }; 16 | 17 | })(); 18 | 19 | Object.defineProperty(window, 'localStorage', { 20 | value: localStorageMock 21 | }); */ 22 | -------------------------------------------------------------------------------- /src/static/config.yml: -------------------------------------------------------------------------------- 1 | backend: 2 | name: git-gateway 3 | branch: master 4 | media_folder: src/assets 5 | public_folder: /assets 6 | collections: 7 | - name: "pages" 8 | label: "Pages" 9 | label_singular: "Page" # Used in the UI, ie: "New Post" 10 | folder: "content/blog" 11 | create: true 12 | fields: 13 | - { label: "Title", name: "title", widget: "string"} 14 | - { label: "Publish Date", name: "date", widget: "datetime" } 15 | - { label: "Subtitle", name: "subtitle", widget: "string", required: false} 16 | - { label: "Cover", name: "cover", widget: "image", required: false} 17 | - { label: "Tags", name: "tags", widget: "string" } 18 | - { label: "Body", name: "body", widget: "markdown"} 19 | 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # blog 2 | 3 | ## CLI Commands 4 | 5 | ``` bash 6 | # install dependencies 7 | yarn 8 | 9 | # serve with hot reload at localhost:8080 10 | yarn dev 11 | 12 | # build for production with minification 13 | yarn build 14 | 15 | # run tests with jest and preact-render-spy 16 | yarn test 17 | ``` 18 | 19 | For detailed explanation on how things work, checkout the [CLI Readme](https://github.com/developit/preact-cli/blob/master/README.md). 20 | 21 | Note: When pre-rendering, your module as components are executed in a Node.js environment, where most Web APIs are not available. To account for this, wrap that code in a check like `if (typeof window !== 'undefined')`. 22 | Do not add `preact build --no-prerender` to the build command as it will prevent blog posts from rendering. 23 | -------------------------------------------------------------------------------- /src/routes/contact/style.css: -------------------------------------------------------------------------------- 1 | .pageContact { 2 | padding: 0 5%; 3 | } 4 | 5 | .pageTitle { 6 | position: relative; 7 | display: inline-block; 8 | font-weight: 500; 9 | } 10 | 11 | .pageTitle:after { 12 | position: absolute; 13 | bottom: -5px; 14 | left: 0px; 15 | content: ''; 16 | width: 75%; 17 | border-bottom: 2px solid #FFC107; 18 | } 19 | 20 | .pageBody { 21 | font-size: 1.2rem; 22 | flex: 1; 23 | } 24 | 25 | .pageContact input, .pageContact textarea { 26 | width: 100%; 27 | font-size: 1.1rem; 28 | padding: 8px; 29 | border: 1px solid #999; 30 | } 31 | 32 | .pageContact .formWrapper { 33 | display: flex; 34 | } 35 | 36 | .pageContact form { 37 | flex: 1; 38 | } 39 | 40 | .pageContact button { 41 | padding: 8px; 42 | font-size: 1rem; 43 | border-radius: 5px; 44 | } 45 | 46 | @media all and (max-width: 728px) { 47 | .formWrapper { 48 | flex-direction: column; 49 | } 50 | } -------------------------------------------------------------------------------- /prerender-urls.js: -------------------------------------------------------------------------------- 1 | const { generateFileList } = require('./src/crawler'); 2 | const { join } = require('path'); 3 | const fs = require('fs'); 4 | const parseMD = require('parse-md').default; 5 | 6 | const [blogs] = generateFileList(join(__dirname, 'content')).nodes; 7 | module.exports = () => { 8 | const pages = [ 9 | { 10 | url: '/', 11 | seo: { 12 | cover: '/assets/profile.jpg' 13 | } 14 | }, 15 | { url: '/contact/' }, 16 | { url: '/contact/success' } 17 | ]; 18 | 19 | // adding blogs list posts page 20 | pages.push({ 21 | url: '/blogs/', 22 | data: blogs 23 | }); 24 | 25 | // adding all blog pages 26 | pages.push(...blogs.edges.map(blog => { 27 | let data; 28 | if (blog.format === 'md') { 29 | const { content } = parseMD(fs.readFileSync(join('content', 'blog', blog.id), 'utf-8')); 30 | data = content; 31 | } else { 32 | data = fs.readFileSync(join('content', 'blog', blog.id), 'utf-8').replace(/---(.*(\r)?\n)*---/, ''); 33 | } 34 | return { 35 | url: `/blog/${blog.id}`, 36 | seo: blog.details, 37 | data: { 38 | details: blog.details, 39 | content: data 40 | } 41 | }; 42 | })); 43 | 44 | return pages; 45 | }; 46 | -------------------------------------------------------------------------------- /src/style/index.css: -------------------------------------------------------------------------------- 1 | @import url("https://fonts.googleapis.com/css?family=Courgette&text=Jane%20Doe"); 2 | 3 | html, body { 4 | height: 100%; 5 | width: 100%; 6 | padding: 0 1vw; 7 | margin: 0; 8 | background: #FAFAFA; 9 | font-family: 'Helvetica Neue', arial, sans-serif; 10 | font-weight: 400; 11 | color: #444; 12 | -webkit-font-smoothing: antialiased; 13 | -moz-osx-font-smoothing: grayscale; 14 | } 15 | 16 | * { 17 | box-sizing: border-box; 18 | } 19 | 20 | #app { 21 | height: 100%; 22 | } 23 | 24 | 25 | a { 26 | text-decoration: none; 27 | color: #333; 28 | } 29 | 30 | .loading { 31 | animation-duration: 1s; 32 | animation-fill-mode: forwards; 33 | animation-iteration-count: infinite; 34 | animation-name: placeHolderShimmer; 35 | animation-timing-function: linear; 36 | background: #f6f7f9; 37 | background-image: linear-gradient(to right, #f6f7f9 0%, #e9ebee 20%, #f6f7f9 40%, #f6f7f9 100%); 38 | background-repeat: no-repeat; 39 | background-size: 800px 104px; 40 | position: relative; 41 | } 42 | 43 | @keyframes placeHolderShimmer{ 44 | 0% { 45 | background-position: -468px 0; 46 | } 47 | 100% { 48 | background-position: 468px 0; 49 | } 50 | } -------------------------------------------------------------------------------- /src/routes/contact/index.js: -------------------------------------------------------------------------------- 1 | import { h } from 'preact'; 2 | import style from './style'; 3 | 4 | const photographs = (props) => { 5 | return ( 6 |
7 |

Contact me

8 |
9 |

10 |

Hi!
11 |
If you are interested in my work and are looking to contact me for a contract please use the following form to contact me.
12 |
 
13 |
Cheers 🍻
14 |

15 |
16 | 17 |

18 | 19 |

20 |

21 | 22 |

23 |

24 |