├── template ├── .gitignore ├── src │ ├── routes │ │ ├── notfound │ │ │ ├── style.css │ │ │ └── index.js │ │ ├── contact-success │ │ │ ├── index.js │ │ │ └── style.css │ │ ├── contact │ │ │ ├── style.css │ │ │ └── index.js │ │ ├── blog │ │ │ ├── style.css │ │ │ └── index.js │ │ ├── blogs │ │ │ ├── style.css │ │ │ └── index.js │ │ └── home │ │ │ ├── style.css │ │ │ └── index.js │ ├── index.js │ ├── assets │ │ ├── favicon.ico │ │ ├── profile.jpg │ │ ├── icons │ │ │ ├── favicon-16x16.png │ │ │ ├── favicon-32x32.png │ │ │ ├── mstile-150x150.png │ │ │ ├── apple-touch-icon.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 │ ├── components │ │ ├── header │ │ │ ├── index.js │ │ │ └── style.css │ │ └── app.js │ ├── static │ │ ├── admin.html │ │ └── config.yml │ ├── manifest.json │ ├── style │ │ └── index.css │ ├── template.html │ └── crawler │ │ └── index.js ├── netlify.toml ├── 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 ├── prerender-urls.js ├── README.md └── package.json ├── .gitignore └── README.md /template/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .vscode 3 | build 4 | -------------------------------------------------------------------------------- /template/src/routes/notfound/style.css: -------------------------------------------------------------------------------- 1 | .notfound { 2 | padding: 0 5%; 3 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | *.lock 4 | *.log 5 | template/package-lock.json 6 | -------------------------------------------------------------------------------- /template/src/index.js: -------------------------------------------------------------------------------- 1 | import './style'; 2 | import App from './components/app'; 3 | 4 | export default App; 5 | -------------------------------------------------------------------------------- /template/src/assets/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/preactjs-templates/netlify/HEAD/template/src/assets/favicon.ico -------------------------------------------------------------------------------- /template/src/assets/profile.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/preactjs-templates/netlify/HEAD/template/src/assets/profile.jpg -------------------------------------------------------------------------------- /template/src/assets/icons/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/preactjs-templates/netlify/HEAD/template/src/assets/icons/favicon-16x16.png -------------------------------------------------------------------------------- /template/src/assets/icons/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/preactjs-templates/netlify/HEAD/template/src/assets/icons/favicon-32x32.png -------------------------------------------------------------------------------- /template/src/assets/icons/mstile-150x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/preactjs-templates/netlify/HEAD/template/src/assets/icons/mstile-150x150.png -------------------------------------------------------------------------------- /template/src/assets/icons/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/preactjs-templates/netlify/HEAD/template/src/assets/icons/apple-touch-icon.png -------------------------------------------------------------------------------- /template/src/assets/brett-jordan-1329359-unsplash.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/preactjs-templates/netlify/HEAD/template/src/assets/brett-jordan-1329359-unsplash.jpg -------------------------------------------------------------------------------- /template/src/assets/icons/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/preactjs-templates/netlify/HEAD/template/src/assets/icons/android-chrome-192x192.png -------------------------------------------------------------------------------- /template/src/assets/icons/android-chrome-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/preactjs-templates/netlify/HEAD/template/src/assets/icons/android-chrome-512x512.png -------------------------------------------------------------------------------- /template/src/assets/cody-black-nm89mzvar5i-unsplash.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/preactjs-templates/netlify/HEAD/template/src/assets/cody-black-nm89mzvar5i-unsplash.jpg -------------------------------------------------------------------------------- /template/src/assets/lucas-ludwig-dh2ztme9kni-unsplash.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/preactjs-templates/netlify/HEAD/template/src/assets/lucas-ludwig-dh2ztme9kni-unsplash.jpg -------------------------------------------------------------------------------- /template/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" -------------------------------------------------------------------------------- /template/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 | -------------------------------------------------------------------------------- /template/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 | -------------------------------------------------------------------------------- /template/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'; -------------------------------------------------------------------------------- /template/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 | -------------------------------------------------------------------------------- /template/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 | } -------------------------------------------------------------------------------- /template/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; -------------------------------------------------------------------------------- /template/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 | -------------------------------------------------------------------------------- /template/src/static/admin.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Content Manager 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /template/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 | -------------------------------------------------------------------------------- /template/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 | } -------------------------------------------------------------------------------- /template/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 | -------------------------------------------------------------------------------- /template/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 | -------------------------------------------------------------------------------- /template/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"} 16 | - { label: "Cover", name: "cover", widget: "image"} 17 | - { label: "Tags", name: "tags", widget: "string" } 18 | - { label: "Body", name: "body", widget: "markdown"} 19 | 20 | -------------------------------------------------------------------------------- /template/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 | } -------------------------------------------------------------------------------- /template/prerender-urls.js: -------------------------------------------------------------------------------- 1 | const { generateFileList } = require('./src/crawler'); 2 | const { join } = require('path'); 3 | const fs = require('fs'); 4 | 5 | const [blogs] = generateFileList(join(__dirname, 'content')).nodes; 6 | module.exports = () => { 7 | const pages = [ 8 | { 9 | url: '/', 10 | seo: { 11 | cover: '/assets/profile.jpg' 12 | } 13 | }, 14 | { url: '/contact/' }, 15 | { url: '/contact/success' } 16 | ]; 17 | 18 | // adding blogs list posts page 19 | pages.push({ 20 | url: '/blogs/', 21 | data: blogs 22 | }); 23 | 24 | // adding all blog pages 25 | pages.push(...blogs.edges.map(blog => { 26 | const data = fs.readFileSync(join('content', 'blog', blog.id), 'utf-8').replace(/---(.*\n)*---/, ''); 27 | return { 28 | url: `/blog/${blog.id}`, 29 | seo: blog.details, 30 | data: { 31 | details: blog.details, 32 | content: data 33 | } 34 | }; 35 | })); 36 | 37 | return pages; 38 | }; 39 | -------------------------------------------------------------------------------- /template/README.md: -------------------------------------------------------------------------------- 1 | # blog 2 | 3 | ## CLI Commands 4 | 5 | ``` bash 6 | # install dependencies 7 | npm install 8 | 9 | # serve with hot reload at localhost:8080 10 | npm run dev 11 | 12 | # build for production with minification 13 | npm run build 14 | 15 | # run tests with jest and preact-render-spy 16 | npm run 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 | ### How to setup Netlify CMS 22 | 23 | - Deploy on netlify using the CLI or the Netlify bot for [github](https://app.netlify.com/start). 24 | 25 | - Enable Git gateway https://docs.netlify.com/visitor-access/git-gateway/. 26 | 27 | - Enable Identity for your app https://docs.netlify.com/visitor-access/identity/. 28 | 29 | - For most Blogs, change (Identity > Settings > Registration preferences) to invite only. 30 | 31 | - Invite yourself to the Identity tab in Netlify console. 32 | 33 | - Accept the invite from you mail. 34 | 35 | - Done 👍🏻. 36 | 37 | *Note:* Go to `https:///admin` in order to access Netlify CMS. 38 | 39 | -------------------------------------------------------------------------------- /template/src/routes/blog/style.css: -------------------------------------------------------------------------------- 1 | .blogcontainer { 2 | max-width: 728px; 3 | width: 100%; 4 | margin: 0 auto 10em; 5 | line-height: 1.6rem; 6 | } 7 | 8 | .blogtitle { 9 | font-size: 2.2rem; 10 | font-weight: 400; 11 | margin-bottom: 8px; 12 | line-height: 1; 13 | } 14 | 15 | .blogbody { 16 | font-size: 1.1rem; 17 | } 18 | 19 | .blogbody img { 20 | max-width: 100%; 21 | } 22 | 23 | .blogbody code { 24 | max-width: 100%; 25 | } 26 | 27 | .blogbody pre { 28 | max-width: 100%; 29 | overflow: scroll; 30 | } 31 | 32 | .blogsubtitle { 33 | color: #aaa; 34 | display: block; 35 | text-align: left; 36 | font-size: 1.2rem; 37 | margin-bottom: 2rem; 38 | } 39 | 40 | .blogcover { 41 | height: 30vh; 42 | background-size: cover; 43 | background-color: #eee; 44 | background-position: center; 45 | } 46 | 47 | .loadingPlaceholder .blogtitle { 48 | height: 24px; 49 | } 50 | 51 | .loadingPlaceholder .blogsubtitle { 52 | height: 18px; 53 | } 54 | 55 | .loadingBody { 56 | height: 12px; 57 | margin-bottom: 8px; 58 | } 59 | 60 | .loadingBody:nth-child(1) { 61 | width: 90%; 62 | } 63 | 64 | .loadingBody:nth-child(3) { 65 | width: 65%; 66 | } 67 | -------------------------------------------------------------------------------- /template/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 | } -------------------------------------------------------------------------------- /template/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 |