├── .gitignore ├── README.md └── template ├── .gitignore ├── README.md ├── content ├── blog │ ├── how-to-share-a-screenshot-of-the-page.md │ ├── visiting-goa.md │ └── wanderlust.md └── images │ ├── cyclist.md │ └── lighthouse.md ├── netlify.toml ├── package.json ├── preact.config.js ├── prerender-urls.js ├── src ├── assets │ ├── brett-jordan-1329359-unsplash.jpg │ ├── cody-black-nm89mzvar5i-unsplash.jpg │ ├── favicon.ico │ ├── icons │ │ ├── android-chrome-192x192.png │ │ ├── android-chrome-512x512.png │ │ ├── apple-touch-icon.png │ │ ├── favicon-16x16.png │ │ ├── favicon-32x32.png │ │ └── mstile-150x150.png │ ├── lucas-ludwig-dh2ztme9kni-unsplash.jpg │ ├── profile.jpg │ └── quotes.svg ├── components │ ├── app.js │ └── header │ │ ├── index.js │ │ └── style.css ├── crawler │ └── index.js ├── index.js ├── manifest.json ├── routes │ ├── blog │ │ ├── index.js │ │ └── style.css │ ├── blogs │ │ ├── index.js │ │ └── style.css │ ├── contact-success │ │ ├── index.js │ │ └── style.css │ ├── contact │ │ ├── index.js │ │ └── style.css │ ├── home │ │ ├── index.js │ │ └── style.css │ └── notfound │ │ ├── index.js │ │ └── style.css ├── static │ ├── admin.html │ └── config.yml ├── style │ └── index.css └── template.html └── tests ├── __mocks__ ├── browserMocks.js └── fileMocks.js └── header.test.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | *.lock 4 | *.log 5 | template/package-lock.json 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # netlify 2 | A preactjs and netlify CMS template. 3 | 4 | ![Lighthouse-Badge](https://img.shields.io/badge/lighthouse-100%2F100-brightgreen.svg) 5 | 6 | ## Documentation 7 | - This is the netlify template for [preact-cli](https://github.com/developit/preact-cli). 8 | - [For Preact](https://preactjs.com/): General information about how to work with Preact, not specific to this template 9 | 10 | ## Usage 11 | 12 | ``` bash 13 | $ npm install -g preact-cli 14 | $ preact create netlify my-project 15 | $ cd my-project 16 | $ npm install 17 | $ npm run dev 18 | ``` 19 | 20 | Development server runs on port `8080`. If the default port is already in use on your machine it will start the development server on a random port. 21 | 22 | ## Commands 23 | 24 | - `npm run start`: Runs `serve` or `dev`, depending on `NODE_ENV` value. Defaults to `dev server` 25 | 26 | - `npm run dev`: Run a development, HMR server 27 | 28 | - `npm run build`: Production-ready build 29 | 30 | - `npm run lint`: Pass JavaScript files using ESLint 31 | 32 | - `npm run test`: Run Jest and [`preact-render-spy`](https://github.com/mzgoddard/preact-render-spy) for your tests 33 | 34 | ### How to setup Netlify CMS 35 | 36 | - Deploy on netlify using the CLI or the Netlify bot for [github](https://app.netlify.com/start). 37 | 38 | - Enable Git gateway https://docs.netlify.com/visitor-access/git-gateway/. 39 | 40 | - Enable Identity for your app https://docs.netlify.com/visitor-access/identity/. 41 | 42 | - For most Blogs, change (Identity > Settings > Registration preferences) to invite only. 43 | 44 | - Invite yourself to the Identity tab in Netlify console. 45 | 46 | - Accept the invite from you mail. 47 | 48 | - Done 👍🏻. 49 | 50 | *Note:* Go to `https:///admin` in order to access Netlify CMS. 51 | 52 | 53 | ### Fork It And Make Your Own 54 | 55 | You can fork this repo to create your own boilerplate, and use it with `preact-cli`: 56 | 57 | ``` bash 58 | preact create username/repo my-project 59 | ``` 60 | -------------------------------------------------------------------------------- /template/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .vscode 3 | build 4 | -------------------------------------------------------------------------------- /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/content/blog/how-to-share-a-screenshot-of-the-page.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: How to share a screenshot of the page 3 | date: 2019-09-07T05:42:28.441Z 4 | subtitle: If you have ever built an application which needs to take a screenshot of itself, then this blog tells you how to do the same for web. 5 | cover: /assets/cody-black-nm89mzvar5i-unsplash.jpg 6 | tags: 'technology, javascript' 7 | --- 8 | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Sodales neque sodales ut etiam sit amet nisl purus in. Nunc sed augue lacus viverra vitae congue eu. At augue eget arcu dictum varius. Et ligula ullamcorper malesuada proin. Aliquam eleifend mi in nulla posuere sollicitudin aliquam ultrices sagittis. Mauris in aliquam sem fringilla ut morbi tincidunt. In vitae turpis massa sed elementum tempus egestas. Neque gravida in fermentum et sollicitudin ac orci phasellus. Nibh praesent tristique magna sit amet purus gravida. Nibh sed pulvinar proin gravida hendrerit. Nibh tortor id aliquet lectus proin nibh nisl condimentum id. A iaculis at erat pellentesque adipiscing commodo elit at. At varius vel pharetra vel turpis nunc eget lorem. Ultrices in iaculis nunc sed augue. Senectus et netus et malesuada fames ac turpis egestas. 9 | 10 | Neque vitae tempus quam pellentesque nec nam aliquam sem. Praesent tristique magna sit amet. Blandit massa enim nec dui nunc. Ultrices vitae auctor eu augue ut lectus arcu bibendum at. Mauris augue neque gravida in fermentum et sollicitudin ac. Arcu risus quis varius quam quisque id diam vel. Tortor aliquam nulla facilisi cras fermentum. Sit amet tellus cras adipiscing. Convallis aenean et tortor at risus viverra adipiscing. Porttitor eget dolor morbi non. Cras pulvinar mattis nunc sed blandit libero volutpat sed. Et leo duis ut diam quam. Accumsan sit amet nulla facilisi. Leo urna molestie at elementum eu facilisis sed odio morbi. Metus dictum at tempor commodo ullamcorper a lacus vestibulum sed. Eros in cursus turpis massa tincidunt dui ut ornare. Vel fringilla est ullamcorper eget nulla facilisi. Nec nam aliquam sem et tortor. Eu consequat ac felis donec. 11 | 12 | Quis enim lobortis scelerisque fermentum dui faucibus. Pulvinar sapien et ligula ullamcorper malesuada proin. Commodo nulla facilisi nullam vehicula ipsum. Facilisis gravida neque convallis a cras semper. Duis convallis convallis tellus id interdum velit laoreet id donec. Eget mauris pharetra et ultrices neque ornare aenean euismod elementum. Justo donec enim diam vulputate ut pharetra. Elementum integer enim neque volutpat. Est velit egestas dui id ornare. Dis parturient montes nascetur ridiculus mus mauris vitae ultricies leo. Tincidunt vitae semper quis lectus nulla at volutpat diam ut. Justo nec ultrices dui sapien eget mi proin sed libero. Egestas erat imperdiet sed euismod nisi porta. Facilisis mauris sit amet massa vitae tortor condimentum lacinia. Cursus in hac habitasse platea dictumst quisque sagittis. Amet consectetur adipiscing elit pellentesque habitant morbi. 13 | 14 | Morbi tempus iaculis urna id volutpat lacus. Sit amet venenatis urna cursus eget nunc scelerisque viverra. Amet risus nullam eget felis. Sit amet purus gravida quis blandit. Malesuada bibendum arcu vitae elementum curabitur vitae nunc sed. Ut lectus arcu bibendum at varius vel pharetra vel turpis. Scelerisque viverra mauris in aliquam sem fringilla ut. Fringilla urna porttitor rhoncus dolor purus. Id velit ut tortor pretium viverra suspendisse potenti nullam. Dignissim convallis aenean et tortor at risus viverra adipiscing at. Donec enim diam vulputate ut. Mauris a diam maecenas sed enim ut sem. 15 | 16 | Fames ac turpis egestas integer eget aliquet nibh praesent. Magna fringilla urna porttitor rhoncus dolor. Mauris commodo quis imperdiet massa tincidunt nunc. Enim sed faucibus turpis in eu mi bibendum. Bibendum enim facilisis gravida neque convallis. Proin fermentum leo vel orci. Id velit ut tortor pretium viverra suspendisse potenti nullam ac. Nunc consequat interdum varius sit amet mattis vulputate enim nulla. Ac felis donec et odio. Mauris a diam maecenas sed enim ut sem viverra aliquet. Sollicitudin nibh sit amet commodo nulla facilisi. 17 | -------------------------------------------------------------------------------- /template/content/blog/visiting-goa.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Visiting Goa 3 | date: 2019-09-07T05:43:53.681Z 4 | tags: 'travel, india, goa' 5 | --- 6 | Lorem ipsum dolor sit amet, mei in iracundia comprehensam, dicit phaedrum ea vis. Ei purto iusto quo, nonumy honestatis usu no. Eam at lorem audire eligendi. Modus iriure dignissim has in, sed ut liber dicit mediocritatem. Et sed harum graecis, vitae admodum mel et, iracundia referrentur an sea. 7 | 8 | At usu tale everti philosophia, consequat gloriatur voluptatum mel cu. Ea quo tota modus debet, ad vitae consetetur conclusionemque usu. Nam cu eripuit luptatum. At posse nemore persequeris per. Mea ut aeterno vivendo, quodsi aeterno dissentiunt te cum. 9 | 10 | Vim id expetenda instructior, eu dolorem temporibus vis. Delenit voluptua id eum, nobis accumsan complectitur pri ut. Eu est enim quaeque lobortis, et tale essent fabulas cum. Eu ornatus debitis nec, paulo dicam dicunt pro ea, ad eam posse nusquam. Ne quis interesset comprehensam has, ex aperiam verterem tincidunt eum. Modus vitae viderer vim cu, id nec ferri fastidii. 11 | 12 | Ex legendos mandamus periculis vim, suscipit fabellas ne vim. At adhuc dicant mea, vix ea quidam voluptatum ullamcorper. Ex sea dicit theophrastus, ne sint offendit sit, wisi consequat sit ne. Sed labores utroque consectetuer no, an his inani expetenda ocurreret. Quaestio efficiantur mea ne. 13 | 14 | Tale noster timeam mea ut, etiam mnesarchum usu ne, cu sit quot ridens vulputate. Minim error qui ne, causae eruditi senserit pro ex. Epicurei qualisque ea duo, pro ei posse offendit, ad nam habeo melius. An nam brute vivendum, at vim amet sonet cotidieque. Cu debitis abhorreant nam, mel ut quot explicari. 15 | -------------------------------------------------------------------------------- /template/content/blog/wanderlust.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Wanderlust 3 | date: 2019-08-27T18:44:24.217Z 4 | tags: 'travel, philosophy, hobby' 5 | --- 6 | Sed ut perspiciatis unde omnis iste natus error sit voluptatem #accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. 7 | 8 | ## Heading 1 9 | 10 | ![](/assets/brett-jordan-1329359-unsplash.jpg) 11 | 12 | ## Heading 2 13 | 14 | Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem. 15 | 16 | ``` 17 | navigator.serviceWorker.register('/sw.js').then(reg => { 18 | reg.onupdatefound = () => { 19 | const w = reg.installing; 20 | w.onstatechange = () => { 21 | if (w.state === 'installed' && navigator.serviceWorker.controller) { 22 | // probably best to avoid reloading if someone has clicked 23 | if (hasInteracted) showUpdateToast(); 24 | 25 | location.reload(); 26 | } 27 | }; 28 | }; 29 | }); 30 | 31 | // if you want the bit on line 7 32 | let hasInteracted = false; 33 | addEventListener('click', () => { hasInteracted = true; }); 34 | ``` 35 | 36 | Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis autem vel eum iure reprehenderit qui in ea voluptate velit esse quam nihil molestiae consequatur, vel illum qui dolorem eum fugiat quo voluptas nulla pariatur? 37 | -------------------------------------------------------------------------------- /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/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/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "name": "blog", 4 | "version": "0.0.0", 5 | "license": "MIT", 6 | "scripts": { 7 | "build": "preact build --prerenderUrls ./prerender-urls.js", 8 | "serve": "sirv build --port 8080 --cors --single", 9 | "dev": "preact watch --prerenderUrls ./prerender-urls.js", 10 | "lint": "eslint src", 11 | "test": "jest" 12 | }, 13 | "eslintConfig": { 14 | "extends": "eslint-config-synacor" 15 | }, 16 | "eslintIgnore": [ 17 | "build/*" 18 | ], 19 | "devDependencies": { 20 | "eslint": "^4.9.0", 21 | "eslint-config-synacor": "^2.0.2", 22 | "esm": "^3.2.25", 23 | "glob": "^7.1.4", 24 | "jest": "^21.2.1", 25 | "jest-preset-preact": "^1.0.0", 26 | "markdown": "^0.5.0", 27 | "per-env": "^1.0.2", 28 | "preact-cli": "^3.0.0", 29 | "preact-render-spy": "^1.2.1", 30 | "sirv-cli": "1.0.3", 31 | "webpack-bundle-analyzer": "^3.4.1" 32 | }, 33 | "dependencies": { 34 | "@preact/prerender-data-provider": "^3.0.0", 35 | "imagemin-mozjpeg": "^8.0.0", 36 | "imagemin-webpack-plugin": "^2.4.2", 37 | "markdown-to-jsx": "^6.10.3", 38 | "preact": "^10.0.0", 39 | "preact-cli-plugin-netlify": "^1.5.0", 40 | "preact-render-to-string": "^5.0.6", 41 | "preact-router": "^3.0.1" 42 | }, 43 | "jest": { 44 | "preset": "jest-preset-preact" 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /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/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/src/assets/brett-jordan-1329359-unsplash.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/preactjs-templates/netlify/3b823d82274b0f1c0fb7248d623256df2cc2c15e/template/src/assets/brett-jordan-1329359-unsplash.jpg -------------------------------------------------------------------------------- /template/src/assets/cody-black-nm89mzvar5i-unsplash.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/preactjs-templates/netlify/3b823d82274b0f1c0fb7248d623256df2cc2c15e/template/src/assets/cody-black-nm89mzvar5i-unsplash.jpg -------------------------------------------------------------------------------- /template/src/assets/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/preactjs-templates/netlify/3b823d82274b0f1c0fb7248d623256df2cc2c15e/template/src/assets/favicon.ico -------------------------------------------------------------------------------- /template/src/assets/icons/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/preactjs-templates/netlify/3b823d82274b0f1c0fb7248d623256df2cc2c15e/template/src/assets/icons/android-chrome-192x192.png -------------------------------------------------------------------------------- /template/src/assets/icons/android-chrome-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/preactjs-templates/netlify/3b823d82274b0f1c0fb7248d623256df2cc2c15e/template/src/assets/icons/android-chrome-512x512.png -------------------------------------------------------------------------------- /template/src/assets/icons/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/preactjs-templates/netlify/3b823d82274b0f1c0fb7248d623256df2cc2c15e/template/src/assets/icons/apple-touch-icon.png -------------------------------------------------------------------------------- /template/src/assets/icons/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/preactjs-templates/netlify/3b823d82274b0f1c0fb7248d623256df2cc2c15e/template/src/assets/icons/favicon-16x16.png -------------------------------------------------------------------------------- /template/src/assets/icons/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/preactjs-templates/netlify/3b823d82274b0f1c0fb7248d623256df2cc2c15e/template/src/assets/icons/favicon-32x32.png -------------------------------------------------------------------------------- /template/src/assets/icons/mstile-150x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/preactjs-templates/netlify/3b823d82274b0f1c0fb7248d623256df2cc2c15e/template/src/assets/icons/mstile-150x150.png -------------------------------------------------------------------------------- /template/src/assets/lucas-ludwig-dh2ztme9kni-unsplash.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/preactjs-templates/netlify/3b823d82274b0f1c0fb7248d623256df2cc2c15e/template/src/assets/lucas-ludwig-dh2ztme9kni-unsplash.jpg -------------------------------------------------------------------------------- /template/src/assets/profile.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/preactjs-templates/netlify/3b823d82274b0f1c0fb7248d623256df2cc2c15e/template/src/assets/profile.jpg -------------------------------------------------------------------------------- /template/src/assets/quotes.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | icon-closequote 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /template/src/components/app.js: -------------------------------------------------------------------------------- 1 | import { h, Component } from 'preact'; 2 | import { Router } from 'preact-router'; 3 | import { Provider } from '@preact/prerender-data-provider'; 4 | import Header from './header'; 5 | import NotFoundPage from '../routes/notfound'; 6 | 7 | // Code-splitting is automated for routes 8 | import Home from '../routes/home'; 9 | import Blogs from '../routes/blogs'; 10 | import Blog from '../routes/blog'; 11 | import Contact from '../routes/contact'; 12 | import ContactSuccess from '../routes/contact-success'; 13 | 14 | export default class App extends Component { 15 | 16 | /** Gets fired when the route changes. 17 | * @param {Object} event "change" event from [preact-router](http://git.io/preact-router) 18 | * @param {string} event.url The newly routed URL 19 | */ 20 | handleRoute = e => { 21 | this.currentUrl = e.url; 22 | }; 23 | 24 | render(props) { 25 | return ( 26 | 27 |
28 |
29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 |
38 |
39 | ); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /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/components/header/style.css: -------------------------------------------------------------------------------- 1 | .header { 2 | height: 100px;; 3 | } 4 | 5 | .header h1 { 6 | float: left; 7 | margin: 0; 8 | padding: 0 15px; 9 | font-size: 40px; 10 | line-height: 100px; 11 | font-weight: 400; 12 | font-family: 'Courgette', 'Helvetica Neue', arial, sans-serif;; 13 | color: #FFC107; 14 | } 15 | 16 | .header nav { 17 | float: right; 18 | font-size: 100%; 19 | } 20 | 21 | .header nav a { 22 | display: inline-block; 23 | height: 56px; 24 | line-height: 100px; 25 | margin: 0 15px; 26 | min-width: 50px; 27 | text-align: center; 28 | background: rgba(255,255,255,0); 29 | text-decoration: none; 30 | color: #333; 31 | will-change: background-color; 32 | transition-property: color; 33 | transition-duration: 300ms; 34 | text-transform: uppercase; 35 | position: relative; 36 | } 37 | 38 | .header nav a:hover, 39 | .header nav a:active, 40 | .header nav a.active { 41 | color: #FFC107; 42 | } 43 | 44 | .header nav a:after { 45 | content: ''; 46 | width: 0%; 47 | border-bottom: 3px solid #FFC107; 48 | position: absolute; 49 | bottom: -12px; 50 | left: 0; 51 | transition-duration: width; 52 | transition-duration: 300ms; 53 | 54 | } 55 | 56 | .header nav a:hover:after, .header nav a.active:after { 57 | width: 70% 58 | } 59 | 60 | @media all and (max-width: 728px) { 61 | .header h1{ 62 | font-size: 2rem; 63 | } 64 | .header nav a{ 65 | line-height: 100px; 66 | margin: 0 8px; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /template/src/crawler/index.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const { join } = require('path'); 3 | 4 | function getDetails(data) { 5 | const matadata = data.match(/---(.*\n)*---/)[0]; 6 | const details = matadata.match(/(.*):(.*)/g).reduce((obj, detail) => { 7 | const value = detail.substr(detail.indexOf(':') + 2); 8 | const key = detail.substr(0, detail.indexOf(':')); 9 | obj[key] = value; 10 | return obj; 11 | }, {}); 12 | return details; 13 | } 14 | 15 | function getPreview(data) { 16 | let preview = data.replace(/---(.*\n)*---/, '').replace(/\[.*\]\(.*\)/g, '').replace(/\n/,''); 17 | preview = preview.substr(0, (preview.indexOf('\n') -1)); 18 | return preview.length < 500? preview : preview.substr(0, 500); 19 | } 20 | 21 | function getFolders(source) { 22 | const isDirectory = source => fs.lstatSync(source).isDirectory(); 23 | const isValidFile = source => !fs.lstatSync(source).isDirectory() && source.endsWith('.md'); 24 | const getAllListings = source => 25 | fs.readdirSync(source).map(name => join(source, name)); 26 | let allContent = getAllListings(source); 27 | const edges = allContent.filter(isValidFile).map(file => { 28 | const data = fs.readFileSync(file, 'utf-8'); 29 | return { 30 | id: file.substr(file.lastIndexOf('/') + 1), 31 | path: file, 32 | details: getDetails(data), 33 | preview: getPreview(data) 34 | }; 35 | }); 36 | const nodes = allContent.filter(isDirectory).map(dir => getFolders(dir)); 37 | const result = { 38 | id: source.substr(source.lastIndexOf('/') + 1) 39 | }; 40 | if (nodes.length) { 41 | result.nodes = nodes; 42 | } 43 | if (edges.length) { 44 | result.edges = edges; 45 | } 46 | return result; 47 | } 48 | 49 | function generateFileList(src) { 50 | return getFolders(src); 51 | } 52 | 53 | module.exports = { 54 | generateFileList 55 | }; 56 | -------------------------------------------------------------------------------- /template/src/index.js: -------------------------------------------------------------------------------- 1 | import './style'; 2 | import App from './components/app'; 3 | 4 | export default App; 5 | -------------------------------------------------------------------------------- /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/src/routes/blog/index.js: -------------------------------------------------------------------------------- 1 | import { h } from 'preact'; 2 | import { usePrerenderData } from '@preact/prerender-data-provider'; 3 | import Markdown from 'markdown-to-jsx'; 4 | import style from './style'; 5 | 6 | const blogs = (props) => { 7 | const [data, isLoading] = usePrerenderData(props); 8 | return ( 9 |
10 | {getBlogBody(data, isLoading)} 11 |
12 | ); 13 | }; 14 | 15 | function getBlogBody(data, isLoading) { 16 | if (isLoading) { 17 | return ( 18 |
19 |

 

20 |   21 |
22 |
23 |
24 |
25 |
26 |
27 | ); 28 | } 29 | 30 | if (data && data.data) { 31 | const { details, content } = data.data; 32 | return ( 33 |
34 |

{details.title}

35 | { details.subtitle && {details.subtitle} } 36 | { details.cover &&
} 37 |
38 | { content } 39 |
40 |
41 | ); 42 | } 43 | } 44 | 45 | export default blogs; 46 | -------------------------------------------------------------------------------- /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/routes/blogs/index.js: -------------------------------------------------------------------------------- 1 | import { h } from 'preact'; 2 | import { Link } from 'preact-router'; 3 | import { usePrerenderData } from '@preact/prerender-data-provider'; 4 | import style from './style'; 5 | 6 | const blogs = (props) => { 7 | const [data, isLoading] = usePrerenderData(props); 8 | return ( 9 |
10 |

My Blogs

11 | { getBlogsListing(data, isLoading) } 12 |
13 | ); 14 | }; 15 | 16 | function getBlogsListing(data, isLoading) { 17 | if (isLoading) { 18 | return ( 19 |
20 |

 

21 |
 
22 |
 
23 |
 
24 |
25 | ); 26 | } 27 | if (data && data.data) { 28 | const { data: blogs } = data; 29 | return ( 30 | <> 31 | {blogs.edges.map(blog => ( 32 | 33 |
34 |

{blog.details.title}

35 |
36 | { 37 | (blog.details.tags.substr(1, blog.details.tags.length - 2).split(',') || []).map(tag => {tag}) 38 | } 39 |
40 |

41 | {blog.preview} 42 |

43 |
44 | 45 | ))} 46 | 47 | ); 48 | } 49 | } 50 | 51 | export default blogs; 52 | -------------------------------------------------------------------------------- /template/src/routes/blogs/style.css: -------------------------------------------------------------------------------- 1 | .pageBlogs { 2 | padding: 0 5%; 3 | } 4 | 5 | .pageTitle { 6 | position: relative; 7 | display: inline-block; 8 | font-weight: 500; 9 | font-family: serif; 10 | } 11 | 12 | .pageTitle:after { 13 | position: absolute; 14 | bottom: -5px; 15 | left: 0px; 16 | content: ''; 17 | width: 75%; 18 | border-bottom: 2px solid #FFC107; 19 | } 20 | 21 | h2 { 22 | font-weight: 400; 23 | font-size: 24px; 24 | margin: 16px 0 12px 0; 25 | } 26 | 27 | .tag { 28 | padding: 4px 8px; 29 | margin-left: 8px; 30 | text-transform: lowercase; 31 | background-color: #ffdd037c; 32 | border-radius: 5px; 33 | color: #999; 34 | font-size: 12px; 35 | } 36 | 37 | .tag:first-child { 38 | margin-left: 0px; 39 | } 40 | 41 | .preview { 42 | width: 50%; 43 | line-height: 1.3; 44 | color: #666; 45 | max-height: 80px; 46 | overflow: hidden; 47 | text-overflow: ellipsis; 48 | } 49 | 50 | .loadingPlaceholder .blogtitle { 51 | height: 24px; 52 | } 53 | 54 | .loadingPlaceholder .loadingBody { 55 | height: 12px; 56 | margin-bottom: 8px; 57 | } 58 | 59 | .loadingPlaceholder .loadingBody:nth-child(2) { 60 | width: 90%; 61 | } 62 | 63 | .loadingPlaceholder .loadingBody:nth-child(3) { 64 | width: 80%; 65 | } 66 | 67 | .loadingPlaceholder .loadingBody:nth-child(4) { 68 | width: 65%; 69 | } 70 | 71 | @media all and (max-width: 728px) { 72 | .preview { 73 | width: 100%; 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /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/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 |