├── .gitignore ├── README.md ├── package.json └── src ├── assets ├── favicon.ico ├── icons │ ├── android-chrome-192x192.png │ ├── android-chrome-512x512.png │ ├── apple-touch-icon.png │ ├── favicon-16x16.png │ ├── favicon-32x32.png │ └── mstile-150x150.png └── images │ └── splash-image.jpg ├── components ├── app.js └── header │ ├── index.js │ └── style.css ├── constants.js ├── index.js ├── manifest.json ├── routes ├── 404 │ ├── index.js │ └── style.css ├── blog │ ├── post.js │ ├── posts.js │ └── style.css ├── home │ ├── index.js │ └── style.css └── profile │ ├── index.js │ └── style.css └── style └── index.css /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (http://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # Typescript v1 declaration files 40 | typings/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # Yarn Integrity file 55 | .yarn-integrity 56 | 57 | # dotenv environment variables file 58 | .env 59 | 60 | .DS_Store 61 | 62 | # lock files 63 | yarn.lock 64 | package-lock.json -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # preact material app 2 | 3 | Super performant `Material` app for preact world using [preact-material-components](https://github.com/prateekbh/preact-material-components) 4 | 5 | ## CLI Commands 6 | 7 | ``` bash 8 | # install dependencies 9 | npm install 10 | 11 | # serve with hot reload at localhost:8080 12 | npm run dev 13 | 14 | # build for production with minification 15 | npm run build 16 | 17 | # test the production build locally 18 | npm run serve 19 | ``` 20 | 21 | For detailed explanation on how things work, checkout the [CLI Readme](https://github.com/developit/preact-cli/blob/master/README.md). 22 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "name": "preact-wordpress", 4 | "version": "0.0.0", 5 | "license": "MIT", 6 | "scripts": { 7 | "start": "if-env NODE_ENV=production && npm run -s serve || npm run -s dev", 8 | "build": "preact build", 9 | "serve": "preact build && preact serve", 10 | "dev": "preact watch", 11 | "lint": "eslint src" 12 | }, 13 | "eslintConfig": { 14 | "extends": "eslint-config-synacor", 15 | "rules": { 16 | "no-unused-vars": "warn", 17 | "react/sort-comp": "off", 18 | "lines-around-comment": "off", 19 | "react/prefer-stateless-function": "off" 20 | } 21 | }, 22 | "eslintIgnore": [ 23 | "build/*" 24 | ], 25 | "devDependencies": { 26 | "eslint": "^4.5.0", 27 | "eslint-config-synacor": "^1.1.0", 28 | "if-env": "^1.0.0", 29 | "preact-cli": "^2.0.0" 30 | }, 31 | "dependencies": { 32 | "npm": "^6.8.0", 33 | "preact": "^8.2.1", 34 | "preact-compat": "^3.17.0", 35 | "preact-helmet": "^4.0.0-alpha-3", 36 | "preact-markup": "^1.6.0", 37 | "preact-material-components": "^1.4.3", 38 | "preact-router": "^2.5.5" 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/assets/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Michael-Brooks/preact-wp/2e3f17750d50409fee9941e942ec17faad573a11/src/assets/favicon.ico -------------------------------------------------------------------------------- /src/assets/icons/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Michael-Brooks/preact-wp/2e3f17750d50409fee9941e942ec17faad573a11/src/assets/icons/android-chrome-192x192.png -------------------------------------------------------------------------------- /src/assets/icons/android-chrome-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Michael-Brooks/preact-wp/2e3f17750d50409fee9941e942ec17faad573a11/src/assets/icons/android-chrome-512x512.png -------------------------------------------------------------------------------- /src/assets/icons/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Michael-Brooks/preact-wp/2e3f17750d50409fee9941e942ec17faad573a11/src/assets/icons/apple-touch-icon.png -------------------------------------------------------------------------------- /src/assets/icons/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Michael-Brooks/preact-wp/2e3f17750d50409fee9941e942ec17faad573a11/src/assets/icons/favicon-16x16.png -------------------------------------------------------------------------------- /src/assets/icons/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Michael-Brooks/preact-wp/2e3f17750d50409fee9941e942ec17faad573a11/src/assets/icons/favicon-32x32.png -------------------------------------------------------------------------------- /src/assets/icons/mstile-150x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Michael-Brooks/preact-wp/2e3f17750d50409fee9941e942ec17faad573a11/src/assets/icons/mstile-150x150.png -------------------------------------------------------------------------------- /src/assets/images/splash-image.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Michael-Brooks/preact-wp/2e3f17750d50409fee9941e942ec17faad573a11/src/assets/images/splash-image.jpg -------------------------------------------------------------------------------- /src/components/app.js: -------------------------------------------------------------------------------- 1 | import { h, Component } from 'preact'; 2 | import { Router } from 'preact-router'; 3 | 4 | import Header from './header'; 5 | import Home from '../routes/home'; 6 | import Posts from '../routes/blog/posts'; 7 | import Post from '../routes/blog/post'; 8 | import NotFound from '../routes/404'; 9 | 10 | export default class App extends Component { 11 | /** Gets fired when the route changes. 12 | * @param {Object} event "change" event from [preact-router](http://git.io/preact-router) 13 | * @param {string} event.url The newly routed URL 14 | */ 15 | handleRoute = e => { 16 | this.setState({ 17 | currentUrl: e.url 18 | }); 19 | }; 20 | 21 | render() { 22 | return ( 23 |
24 |
25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 |
33 | ); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/components/header/index.js: -------------------------------------------------------------------------------- 1 | import { h, Component } from 'preact'; 2 | import { route } from 'preact-router'; 3 | import TopAppBar from 'preact-material-components/TopAppBar'; 4 | import Drawer from 'preact-material-components/Drawer'; 5 | import List from 'preact-material-components/List'; 6 | import { appName } from '../../constants'; 7 | import 'preact-material-components/Switch/style.css'; 8 | import 'preact-material-components/Dialog/style.css'; 9 | import 'preact-material-components/Drawer/style.css'; 10 | import 'preact-material-components/List/style.css'; 11 | import 'preact-material-components/TopAppBar/style.css'; 12 | import style from './style'; 13 | 14 | export default class Header extends Component { 15 | closeDrawer() { 16 | this.drawer.MDComponent.open = false; 17 | } 18 | 19 | openDrawer = () => (this.drawer.MDComponent.open = true); 20 | 21 | drawerRef = drawer => (this.drawer = drawer); 22 | 23 | linkTo = path => () => { 24 | route(path); 25 | this.closeDrawer(); 26 | }; 27 | 28 | goHome = this.linkTo('/'); 29 | goToBlog = this.linkTo('/blog'); 30 | nonBlogLinks = [ 31 | '/', 32 | '/blog' 33 | ]; 34 | 35 | render(props) { 36 | return ( 37 |
38 | 39 | 40 | 41 | {appName} 42 | 43 | 44 | 45 | menu 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | home 54 | Home 55 | 56 | 59 | toc 60 | Blog 61 | 62 | 63 | 64 |
65 | ); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/components/header/style.css: -------------------------------------------------------------------------------- 1 | a { 2 | cursor: pointer; 3 | } 4 | 5 | .topbar { 6 | top: 0; 7 | background-color: #fff; 8 | color: #000; 9 | -webkit-box-shadow: 0 0 2px 0 rgba(0, 0, 0, 0.2), 0 0 2px 0 rgba(0, 0, 0, 0.14), 0 0 2px 0 rgba(0, 0, 0, 0.12); 10 | box-shadow: 0 0 10px 0 rgba(0, 0, 0, 0.2), 0 0 10px 0 rgba(0, 0, 0, 0.14), 0 0 10px 0 rgba(0, 0, 0, 0.12); 11 | } 12 | 13 | .topbar__row { 14 | max-width: 100%; 15 | width: 1250px; 16 | margin: 0 auto; 17 | } 18 | 19 | .mdc-drawer--modal { 20 | top: 0; 21 | background-color: #000; 22 | } -------------------------------------------------------------------------------- /src/constants.js: -------------------------------------------------------------------------------- 1 | const protocol = 'https'; 2 | const baseUrl = 'www.codeinwp.com/wp-json/wp/v2/'; 3 | const appName = 'Michael Brooks'; 4 | const homeTitle = `${appName} | Website Developer | Newton Abbot`; 5 | const blogTitle = `Blog | ${appName}`; 6 | 7 | module.exports = { 8 | baseUrl, 9 | protocol, 10 | appName, 11 | homeTitle, 12 | blogTitle 13 | }; 14 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import './style'; 2 | import App from './components/app'; 3 | 4 | export default App; 5 | -------------------------------------------------------------------------------- /src/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "preact-wordpress", 3 | "short_name": "preact-wordpress", 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/routes/404/index.js: -------------------------------------------------------------------------------- 1 | import { h, Component } from 'preact'; 2 | import Card from 'preact-material-components/Card'; 3 | import 'preact-material-components/Card/style.css'; 4 | import style from './style'; 5 | 6 | export default class NotFound extends Component { 7 | render() { 8 | return ( 9 |
10 | 11 |
12 |

404! Page not found.

13 |
14 |
15 | Looks like the page you are trying to access, doesn't exist. 16 |
17 |
18 |
19 | ); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/routes/404/style.css: -------------------------------------------------------------------------------- 1 | .home { 2 | padding: 20px; 3 | min-height: 100%; 4 | width: 100%; 5 | } 6 | 7 | .cardHeader { 8 | padding: 16px; 9 | } 10 | 11 | .cardBody { 12 | padding: 16px; 13 | } 14 | -------------------------------------------------------------------------------- /src/routes/blog/post.js: -------------------------------------------------------------------------------- 1 | import { h, Component } from 'preact'; 2 | import Markup from 'preact-markup'; 3 | import Helmet from 'preact-helmet'; 4 | import Card from 'preact-material-components/Card'; 5 | import { protocol, baseUrl } from '../../constants'; 6 | import 'preact-material-components/Card/style.css'; 7 | import 'preact-material-components/Button/style.css'; 8 | import style from './style'; 9 | 10 | export default class Post extends Component { 11 | constructor() { 12 | super(); 13 | this.state = { 14 | loading: true, 15 | posts: [] 16 | }; 17 | } 18 | 19 | componentDidMount() { 20 | fetch(`${protocol}://${baseUrl}posts?slug=${this.props.slug}`) 21 | .then(response => response.json()) 22 | .then((data) => { 23 | let posts = data.map((post) => { 24 | return ( 25 | 26 | 27 |
28 |

29 |
30 |
31 | 32 |
33 |
34 | ); 35 | }); 36 | this.setState({ loading: false, posts }); 37 | }) 38 | .catch(error => { 39 | this.setState({ loading: false }); 40 | }); 41 | } 42 | 43 | loadPosts() { 44 | if (this.state.loading) { 45 | return ( 46 |
47 |

Loading...

48 |
49 | ); 50 | } 51 | 52 | if (this.state.loading === false && this.state.posts.length === 0) { 53 | return ( 54 |
55 |

No posts

56 |
57 | ); 58 | } 59 | 60 | return ( 61 | this.state.posts 62 | ); 63 | } 64 | 65 | render() { 66 | return ( 67 |
68 | {this.loadPosts()} 69 |
70 | ); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/routes/blog/posts.js: -------------------------------------------------------------------------------- 1 | import { h, Component } from 'preact'; 2 | import Markup from 'preact-markup'; 3 | import Helmet from 'preact-helmet'; 4 | import Card from 'preact-material-components/Card'; 5 | import { protocol, baseUrl, blogTitle } from '../../constants'; 6 | import 'preact-material-components/Card/style.css'; 7 | import 'preact-material-components/Button/style.css'; 8 | import style from './style'; 9 | 10 | export default class Posts extends Component { 11 | constructor() { 12 | super(); 13 | this.state = { 14 | loading: true, 15 | posts: [] 16 | }; 17 | } 18 | 19 | componentDidMount() { 20 | fetch(`${protocol}://${baseUrl}posts?page=${this.props.page || 1}`) 21 | .then(response => response.json()) 22 | .then((data) => { 23 | let posts = data.map((post) => { 24 | const renderExcerpt = ` 25 | ${post.excerpt.rendered.split(' ').splice(0, 54).join(' ')}...

26 | `; 27 | 28 | return ( 29 | 30 | 31 |
32 |

33 |
34 |
35 | 36 | Read More 37 |
38 |
39 | ); 40 | }); 41 | this.setState({ loading: false, posts }); 42 | }) 43 | .catch(error => { 44 | this.setState({ loading: false }); 45 | }); 46 | } 47 | 48 | loadPosts() { 49 | if (this.state.loading) { 50 | return ( 51 |
52 |

Loading...

53 |
54 | ); 55 | } 56 | 57 | if (this.state.loading === false && this.state.posts.length === 0) { 58 | return ( 59 |
60 |

No posts

61 |
62 | ); 63 | } 64 | 65 | return ( 66 | this.state.posts 67 | ); 68 | } 69 | 70 | render(props) { 71 | return ( 72 |
73 |

Blog {props.page && ` - Page ${props.page}`}

74 | {this.loadPosts()} 75 |
76 | ); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/routes/blog/style.css: -------------------------------------------------------------------------------- 1 | .blog { 2 | min-height: 100%; 3 | max-width: 1080px; 4 | width: 90%; 5 | background: #fff; 6 | margin: 72px 15px 0; 7 | padding: 15px; 8 | } 9 | 10 | #app .page { 11 | padding-top: 0; 12 | } 13 | 14 | .cardHeader { 15 | padding: 16px; 16 | } 17 | 18 | .cardBody { 19 | padding: 16px; 20 | } 21 | 22 | .mdcCard { 23 | margin: 50px 0; 24 | } 25 | 26 | .screen-reader-text { 27 | border: 0; 28 | clip: rect(1px, 1px, 1px, 1px); 29 | -webkit-clip-path: inset(50%); 30 | clip-path: inset(50%); 31 | height: 1px; 32 | margin: -1px; 33 | overflow: hidden; 34 | padding: 0; 35 | position: absolute !important; 36 | width: 1px; 37 | word-wrap: normal !important; 38 | } -------------------------------------------------------------------------------- /src/routes/home/index.js: -------------------------------------------------------------------------------- 1 | import { h, Component } from 'preact'; 2 | import Helmet from 'preact-helmet'; 3 | import { homeTitle } from '../../constants'; 4 | import 'preact-material-components/Card/style.css'; 5 | import 'preact-material-components/Button/style.css'; 6 | import style from './style'; 7 | 8 | export default class Home extends Component { 9 | render() { 10 | return ( 11 |
12 | 13 | 14 |
15 | ); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/routes/home/style.css: -------------------------------------------------------------------------------- 1 | .home { 2 | margin-top: 56px; 3 | min-height: 100%; 4 | width: 100%; 5 | } 6 | 7 | .cardHeader { 8 | padding: 16px; 9 | } 10 | 11 | .cardBody { 12 | padding: 16px; 13 | } 14 | 15 | img { 16 | width: 100%; 17 | } -------------------------------------------------------------------------------- /src/routes/profile/index.js: -------------------------------------------------------------------------------- 1 | import { h, Component } from 'preact'; 2 | import Button from 'preact-material-components/Button'; 3 | import 'preact-material-components/Button/style.css'; 4 | import style from './style'; 5 | 6 | export default class Profile extends Component { 7 | state = { 8 | time: Date.now(), 9 | count: 10 10 | }; 11 | 12 | // gets called when this route is navigated to 13 | componentDidMount() { 14 | // start a timer for the clock: 15 | this.timer = setInterval(this.updateTime, 1000); 16 | } 17 | 18 | // gets called just before navigating away from the route 19 | componentWillUnmount() { 20 | clearInterval(this.timer); 21 | } 22 | 23 | // update the current time 24 | updateTime = () => { 25 | this.setState({ time: Date.now() }); 26 | }; 27 | 28 | increment = () => { 29 | this.setState({ count: this.state.count+1 }); 30 | }; 31 | 32 | // Note: `user` comes from the URL, courtesy of our router 33 | render({ user }, { time, count }) { 34 | return ( 35 |
36 |

Profile: {user}

37 |

This is the user profile for a user named { user }.

38 | 39 |
Current time: {new Date(time).toLocaleString()}
40 | 41 |

42 | 43 | {' '} 44 | Clicked {count} times. 45 |

46 |
47 | ); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/routes/profile/style.css: -------------------------------------------------------------------------------- 1 | .profile { 2 | padding: 20px; 3 | min-height: 100%; 4 | width: 100%; 5 | } 6 | -------------------------------------------------------------------------------- /src/style/index.css: -------------------------------------------------------------------------------- 1 | html, body { 2 | width: 100%; 3 | padding: 0; 4 | margin: 0; 5 | background: #FAFAFA; 6 | font-family: 'Helvetica Neue', arial, sans-serif; 7 | font-weight: 400; 8 | color: #444; 9 | -webkit-font-smoothing: antialiased; 10 | -moz-osx-font-smoothing: grayscale; 11 | } 12 | 13 | /* fallback */ 14 | @font-face { 15 | font-family: 'Material Icons'; 16 | font-style: normal; 17 | font-weight: 400; 18 | src: url(https://fonts.gstatic.com/s/materialicons/v29/2fcrYFNaTjcS6g4U3t-Y5UEw0lE80llgEseQY3FEmqw.woff2) format('woff2'); 19 | } 20 | 21 | .material-icons { 22 | font-family: 'Material Icons'; 23 | font-weight: normal; 24 | font-style: normal; 25 | font-size: 24px; 26 | line-height: 1; 27 | letter-spacing: normal; 28 | text-transform: none; 29 | display: inline-block; 30 | white-space: nowrap; 31 | word-wrap: normal; 32 | direction: ltr; 33 | -webkit-font-feature-settings: 'liga'; 34 | -webkit-font-smoothing: antialiased; 35 | } 36 | 37 | * { 38 | box-sizing: border-box; 39 | } 40 | 41 | .mdc-theme--dark { 42 | background-color: #333; 43 | color: #fff; 44 | } 45 | 46 | .mdc-theme--dark .mdc-card { 47 | color: #444; 48 | } --------------------------------------------------------------------------------