├── src ├── containers │ ├── Page │ │ ├── Page.scss │ │ └── Page.js │ ├── Post │ │ ├── Post.scss │ │ └── Post.js │ ├── Homepage │ │ ├── Homepage.scss │ │ └── Homepage.js │ ├── Root │ │ ├── Root.js │ │ ├── Root.prod.js │ │ └── Root.dev.js │ ├── PostsIndex │ │ ├── PostsIndex.scss │ │ ├── query.js │ │ └── PostsIndex.js │ ├── PrimaryMenu │ │ ├── PrimaryMenu.scss │ │ └── PrimaryMenu.js │ ├── App │ │ ├── App.scss │ │ └── App.js │ └── index.js ├── components │ ├── Error │ │ ├── Error.scss │ │ └── Error.js │ ├── NotFound │ │ ├── NotFound.scss │ │ └── NotFound.js │ ├── Loading │ │ └── Loading.js │ ├── MenuItem │ │ ├── MenuItem.scss │ │ └── MenuItem.js │ ├── Header │ │ ├── Header.scss │ │ └── Header.js │ ├── PostListing │ │ ├── PostListing.scss │ │ └── PostListing.js │ ├── DevTools │ │ └── DevTools.js │ └── index.js ├── theme │ ├── style.scss │ └── global.scss ├── assets │ └── tophat.png ├── store │ ├── index.js │ ├── sagas.js │ ├── reducer.js │ ├── wordpress.js │ └── create.js ├── helpers │ ├── index.js │ ├── runSagas.js │ ├── Html.js │ └── htmlParser.js ├── routes │ ├── routes.js │ └── index.js ├── client.js ├── config.js └── server.js ├── .gitignore ├── static └── favicon.ico ├── .editorconfig ├── .babelrc ├── webpack ├── webpack-dev-server.js ├── webpack-isomorphic-tools.js ├── prod.config.js └── dev.config.js ├── LICENSE ├── bin └── www ├── README.md └── package.json /src/containers/Page/Page.scss: -------------------------------------------------------------------------------- 1 | .Page {} 2 | -------------------------------------------------------------------------------- /src/containers/Post/Post.scss: -------------------------------------------------------------------------------- 1 | .Post {} 2 | -------------------------------------------------------------------------------- /src/components/Error/Error.scss: -------------------------------------------------------------------------------- 1 | .Error {} 2 | -------------------------------------------------------------------------------- /src/containers/Homepage/Homepage.scss: -------------------------------------------------------------------------------- 1 | .Homepage {} -------------------------------------------------------------------------------- /src/components/NotFound/NotFound.scss: -------------------------------------------------------------------------------- 1 | .NotFound {} 2 | -------------------------------------------------------------------------------- /src/theme/style.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * # Application Styles 3 | */ 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | .DS_Store 3 | npm-debug.log 4 | 5 | node_modules/ 6 | static/dist/ 7 | -------------------------------------------------------------------------------- /static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/outlandishideas/kasia-boilerplate/master/static/favicon.ico -------------------------------------------------------------------------------- /src/assets/tophat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/outlandishideas/kasia-boilerplate/master/src/assets/tophat.png -------------------------------------------------------------------------------- /src/store/index.js: -------------------------------------------------------------------------------- 1 | export { default as createStore } from './create' 2 | export { WP } from './wordpress' 3 | -------------------------------------------------------------------------------- /src/helpers/index.js: -------------------------------------------------------------------------------- 1 | export { default as Html } from './Html' 2 | export { default as runSagas } from './runSagas' 3 | -------------------------------------------------------------------------------- /src/containers/Root/Root.js: -------------------------------------------------------------------------------- 1 | module.exports = process.env.NODE_ENV === 'production' 2 | ? require('./Root.prod') 3 | : require('./Root.dev') 4 | -------------------------------------------------------------------------------- /src/components/Loading/Loading.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | 3 | export default function Loading () { 4 | return ( 5 |
6 |

Loading...

7 |
8 | ) 9 | } 10 | -------------------------------------------------------------------------------- /src/containers/PostsIndex/PostsIndex.scss: -------------------------------------------------------------------------------- 1 | .PostsIndex { 2 | width: 100%; 3 | float: left; 4 | } 5 | 6 | .PostListings { 7 | float: left; 8 | width: 100%; 9 | list-style-type: none; 10 | padding: 0; 11 | } 12 | -------------------------------------------------------------------------------- /src/containers/PrimaryMenu/PrimaryMenu.scss: -------------------------------------------------------------------------------- 1 | .PrimaryMenu { 2 | width: 100%; 3 | float: left; 4 | list-style-type: none; 5 | margin: 0; 6 | padding: 0; 7 | background-color: lightblue; 8 | border-bottom: 1px solid darkblue; 9 | } 10 | -------------------------------------------------------------------------------- /src/store/sagas.js: -------------------------------------------------------------------------------- 1 | /** 2 | * # Sagas 3 | */ 4 | 5 | import { wpSagas } from './wordpress' 6 | 7 | export default function * () { 8 | yield [ 9 | ...wpSagas 10 | // your sagas go here, e.g. fork(someSagaGenerator) 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /src/containers/App/App.scss: -------------------------------------------------------------------------------- 1 | .App { 2 | width: 100%; 3 | max-width: 1024px; 4 | padding: 25px; 5 | } 6 | 7 | .content { 8 | width: 100%; 9 | float: left; 10 | clear: both; 11 | padding: 15px; 12 | } 13 | 14 | @media screen and (min-width: 1024px) { 15 | .App { 16 | margin: 0 auto; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/components/MenuItem/MenuItem.scss: -------------------------------------------------------------------------------- 1 | .ActiveMenuItem { 2 | text-decoration: underline; 3 | } 4 | 5 | .MenuItem { 6 | float: left; 7 | padding: 15px; 8 | padding-right: 0; 9 | 10 | a { 11 | text-decoration: none; 12 | color: black; 13 | } 14 | 15 | a:hover { 16 | @extend .ActiveMenuItem 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/components/Header/Header.scss: -------------------------------------------------------------------------------- 1 | .Header { 2 | float: left; 3 | clear: both; 4 | width: 100%; 5 | height: 50px; 6 | margin: 15px; 7 | margin-bottom: 30px; 8 | 9 | img, 10 | h1 { 11 | display: block; 12 | float: left; 13 | } 14 | 15 | h1 { 16 | margin-top: 5px; 17 | margin-left: 15px; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/store/reducer.js: -------------------------------------------------------------------------------- 1 | /** 2 | * # Reducer 3 | */ 4 | 5 | import { routerReducer } from 'react-router-redux' 6 | import { combineReducers } from 'redux' 7 | 8 | import { wpReducer } from './wordpress' 9 | 10 | export default combineReducers({ 11 | routing: routerReducer, 12 | ...wpReducer 13 | // your reducers go here, e.g. user: userReducer 14 | }) 15 | -------------------------------------------------------------------------------- /src/theme/global.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * # Global Styles 3 | */ 4 | 5 | @import "./style"; 6 | 7 | * { 8 | box-sizing: border-box; 9 | font-family: sans-serif; 10 | } 11 | 12 | html, 13 | body { 14 | width: 100%; 15 | height: 100%; 16 | min-width: 720px; 17 | background-color: white; 18 | padding: 0; 19 | margin: 0; 20 | } 21 | 22 | body #container { 23 | width: 100%; 24 | height: 100%; 25 | } 26 | -------------------------------------------------------------------------------- /src/helpers/runSagas.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Run all `sagas` until they are complete. 3 | * @param {Object} store Enhanced redux store with `runSaga` method 4 | * @param {Array} sagas Array of saga operations 5 | * @returns {Promise} 6 | */ 7 | export default function runSagas (store, sagas) { 8 | return sagas.reduce((promise, saga) => { 9 | return promise.then(() => store.runSaga(saga).done) 10 | }, Promise.resolve()) 11 | } 12 | -------------------------------------------------------------------------------- /src/components/Header/Header.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | 3 | import config from '../../config' 4 | 5 | import tophatPng from '../../assets/tophat.png' 6 | import styles from './Header.scss' 7 | 8 | export default function Header () { 9 | return ( 10 |
11 | 12 |

{config.app.title}

13 |
14 | ) 15 | } 16 | -------------------------------------------------------------------------------- /src/components/PostListing/PostListing.scss: -------------------------------------------------------------------------------- 1 | .PostListing { 2 | width: 32%; 3 | margin-right: 1%; 4 | margin-bottom: 20px; 5 | padding: 10px; 6 | float: left; 7 | border: 1px solid grey; 8 | } 9 | 10 | .PostListing__title { 11 | width: 100%; 12 | white-space: nowrap; 13 | text-overflow: ellipsis; 14 | font-weight: bold; 15 | overflow: hidden; 16 | } 17 | 18 | .PostListing__excerpt { 19 | font-size: 0.75rem; 20 | } 21 | -------------------------------------------------------------------------------- /src/components/DevTools/DevTools.js: -------------------------------------------------------------------------------- 1 | /** 2 | * # DevTools Container 3 | */ 4 | 5 | import React from 'react' 6 | import { createDevTools } from 'redux-devtools' 7 | import LogMonitor from 'redux-devtools-log-monitor' 8 | import DockMonitor from 'redux-devtools-dock-monitor' 9 | 10 | export default createDevTools( 11 | 12 | 13 | 14 | ) 15 | -------------------------------------------------------------------------------- /src/containers/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * # Containers 3 | */ 4 | 5 | export { default as Root } from './Root/Root' 6 | export { default as App } from './App/App' 7 | export { default as Page } from './Page/Page' 8 | export { default as Homepage } from './Homepage/Homepage' 9 | export { default as Post } from './Post/Post' 10 | export { default as PrimaryMenu } from './PrimaryMenu/PrimaryMenu' 11 | export { default as PostsIndex } from './PostsIndex/PostsIndex' 12 | -------------------------------------------------------------------------------- /src/components/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * # Components 3 | */ 4 | 5 | export { default as Error } from './Error/Error' 6 | export { default as DevTools } from './DevTools/DevTools' 7 | export { default as NotFound } from './NotFound/NotFound' 8 | export { default as Header } from './Header/Header' 9 | export { default as MenuItem } from './MenuItem/MenuItem' 10 | export { default as PostListing } from './PostListing/PostListing' 11 | export { default as Loading } from './Loading/Loading' 12 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps developers define and maintain consistent 2 | # coding styles between different editors and IDEs 3 | # editorconfig.org 4 | 5 | root = true 6 | 7 | [*] 8 | 9 | # Change these settings to your own preference 10 | indent_style = space 11 | indent_size = 2 12 | 13 | # We recommend you to keep these unchanged 14 | end_of_line = lf 15 | charset = utf-8 16 | trim_trailing_whitespace = true 17 | insert_final_newline = false 18 | 19 | [*.js] 20 | 21 | insert_final_newline = true 22 | -------------------------------------------------------------------------------- /src/components/MenuItem/MenuItem.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { Link } from 'react-router' 3 | 4 | import classNames from 'classnames' 5 | 6 | import styles from './MenuItem.scss' 7 | 8 | export default function MenuItem ({ item, to, active }) { 9 | const classes = classNames(styles.MenuItem, { 10 | [styles.ActiveMenuItem]: active 11 | }) 12 | 13 | return ( 14 |
  • 15 | {item.title} 16 |
  • 17 | ) 18 | } 19 | -------------------------------------------------------------------------------- /src/routes/routes.js: -------------------------------------------------------------------------------- 1 | /** 2 | * # App Routes 3 | */ 4 | 5 | import React from 'react' 6 | import { Route } from 'react-router' 7 | 8 | import { Page, Post, PostsIndex } from '../containers' 9 | 10 | const PageBySlug = Page((props) => props.params.slug) 11 | 12 | export default [ 13 | , 14 | , 15 | , 16 | 17 | ] 18 | -------------------------------------------------------------------------------- /src/routes/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * # Router 3 | */ 4 | 5 | import React, { Component } from 'react' 6 | import { IndexRoute, Route, Redirect } from 'react-router' 7 | 8 | import { App, Page } from '../containers' 9 | import { NotFound } from '../components' 10 | import AppRoutes from './routes' 11 | 12 | export default ( 13 | 14 | 15 | 16 | {AppRoutes} 17 | 18 | 19 | ) 20 | -------------------------------------------------------------------------------- /src/components/PostListing/PostListing.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { Link } from 'react-router' 3 | 4 | import parse from '../../helpers/htmlParser' 5 | 6 | import styles from './PostListing.scss' 7 | 8 | export default function PostListing (props) { 9 | const { title, excerpt, slug } = props 10 | 11 | return ( 12 |
  • 13 |
    {title}
    14 |
    {parse(excerpt)}
    15 | Read More 16 |
  • 17 | ) 18 | } 19 | -------------------------------------------------------------------------------- /src/components/NotFound/NotFound.js: -------------------------------------------------------------------------------- 1 | /** 2 | * # Not Found Container 3 | */ 4 | 5 | import React from 'react' 6 | import { Link } from 'react-router' 7 | import Helmet from 'react-helmet' 8 | 9 | import styles from './NotFound.scss' 10 | 11 | export default function NotFound () { 12 | return ( 13 |
    14 | 15 |

    Not Found

    16 |

    The page you have requested was not found.

    17 |

    18 | Go to the Homepage. 19 |

    20 |
    21 | ) 22 | } 23 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "react", 4 | "es2015", 5 | "stage-0" 6 | ], 7 | "plugins": [ 8 | "transform-runtime", 9 | "add-module-exports", 10 | "transform-decorators-legacy", 11 | "transform-react-display-name" 12 | ], 13 | "env": { 14 | "development": { 15 | "plugins": [ 16 | "typecheck", 17 | ["react-transform", { 18 | "transforms": [{ 19 | "transform": "react-transform-catch-errors", 20 | "imports": ["react", "redbox-react"] 21 | }] 22 | }] 23 | ] 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/store/wordpress.js: -------------------------------------------------------------------------------- 1 | /** 2 | * # Kasia Setup 3 | */ 4 | 5 | import { default as _WP } from 'wpapi' 6 | import Kasia from 'kasia' 7 | import KasiaWpMenusPlugin from 'kasia-plugin-wp-api-menus' 8 | 9 | import config from '../config' 10 | 11 | export const WP = new _WP({ endpoint: config.wpapi }) 12 | 13 | const { kasiaSagas, kasiaReducer } = Kasia({ 14 | WP, 15 | plugins: [KasiaWpMenusPlugin], 16 | contentTypes: [{ 17 | name: 'Reports', 18 | plural: 'reports', 19 | slug: 'reports' 20 | }] 21 | }) 22 | 23 | export { 24 | kasiaSagas as wpSagas, 25 | kasiaReducer as wpReducer 26 | } 27 | -------------------------------------------------------------------------------- /src/client.js: -------------------------------------------------------------------------------- 1 | /** 2 | * # Client 3 | */ 4 | 5 | import 'babel-polyfill' 6 | 7 | import React from 'react' 8 | import ReactDOM from 'react-dom' 9 | import { browserHistory } from 'react-router' 10 | import { syncHistoryWithStore } from 'react-router-redux' 11 | 12 | import { Root } from './containers' 13 | import routes from './routes' 14 | import createStore from './store/create' 15 | 16 | const store = createStore(browserHistory, window.__STATE__ || {}) 17 | const history = syncHistoryWithStore(browserHistory, store) 18 | 19 | ReactDOM.render( 20 | , 21 | document.getElementById('container') 22 | ) 23 | -------------------------------------------------------------------------------- /src/components/Error/Error.js: -------------------------------------------------------------------------------- 1 | /** 2 | * # Not Found Container 3 | */ 4 | 5 | import React, { PropTypes } from 'react' 6 | import Helmet from 'react-helmet' 7 | 8 | import styles from './Error.scss' 9 | 10 | export default function Error ({ 11 | code = 500, 12 | message = 'Something went wrong trying to serve your request.' 13 | }) { 14 | return ( 15 |
    16 | 17 |

    An Error Occurred: {code}

    18 |

    {message}

    19 |

    20 | Go to the Homepage. 21 |

    22 |
    23 | ) 24 | } 25 | 26 | Error.propTypes = { 27 | code: PropTypes.number, 28 | message: PropTypes.string 29 | } 30 | -------------------------------------------------------------------------------- /src/containers/Post/Post.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { connectWpPost } from 'kasia/connect' 3 | import ContentTypes from 'kasia/types' 4 | 5 | import { Loading } from '../../components' 6 | import parse from '../../helpers/htmlParser' 7 | 8 | import styles from './Post.scss' 9 | 10 | @connectWpPost(ContentTypes.Post, (props) => props.params.slug) 11 | export default class Post extends Component { 12 | render () { 13 | const { post } = this.props.kasia 14 | 15 | if (!post) { 16 | return 17 | } 18 | 19 | return ( 20 |
    21 |

    {post.title}

    22 | {parse(post.content)} 23 |
    24 | ) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/containers/PostsIndex/query.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Get the page with slug 'posts' and all Post content items. 3 | * @param {Object} wpapi WP-API interface 4 | * @param {Object} props Component's props 5 | * @returns {Promise} 6 | */ 7 | export default function PostsListingQuery (wpapi, props) { 8 | let page = typeof props.params.page !== 'undefined' 9 | ? Number(props.params.page) 10 | : false 11 | 12 | page = page >= 2 ? page : false 13 | 14 | const postsQuery = page 15 | ? wpapi.posts().page(page) 16 | : wpapi.posts() 17 | 18 | const promises = [ 19 | wpapi.pages().slug('posts').get(), 20 | postsQuery.perPage(5).get() 21 | ] 22 | 23 | return Promise 24 | .all(promises) 25 | .then((results) => [].concat(...results)) 26 | } 27 | -------------------------------------------------------------------------------- /src/containers/App/App.js: -------------------------------------------------------------------------------- 1 | /** 2 | * # App Container 3 | */ 4 | 5 | import React, { Component } from 'react' 6 | import Helmet from 'react-helmet' 7 | 8 | import { Header } from '../../components' 9 | import { PrimaryMenu } from '../index' 10 | import config from '../../config' 11 | 12 | import styles from './App.scss' 13 | 14 | export default class App extends Component { 15 | render () { 16 | const { pathname } = this.props.location 17 | 18 | return ( 19 |
    20 | 21 | 22 |
    23 | 24 | 25 | 26 |
    27 | {this.props.children} 28 |
    29 |
    30 | ) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/containers/Root/Root.prod.js: -------------------------------------------------------------------------------- 1 | /** 2 | * # Root (Production) 3 | */ 4 | 5 | import React, { Component, PropTypes } from 'react' 6 | import { Provider } from 'react-redux' 7 | import { Router, RouterContext } from 'react-router' 8 | 9 | export default class Root extends Component { 10 | static propTypes = { 11 | store: PropTypes.object.isRequired, 12 | history: PropTypes.object.isRequired, 13 | routes: PropTypes.node.isRequired 14 | } 15 | 16 | render() { 17 | const { store, history, routes, type, renderProps } = this.props 18 | 19 | return ( 20 | 21 | {type === 'server' 22 | ? 23 | : } 24 | 25 | ) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/containers/Root/Root.dev.js: -------------------------------------------------------------------------------- 1 | /** 2 | * # Root (Development) 3 | */ 4 | 5 | import React, { Component, PropTypes } from 'react' 6 | import { Provider } from 'react-redux' 7 | import { Router, RouterContext } from 'react-router' 8 | 9 | import { DevTools } from '../../components' 10 | 11 | export default class Root extends Component { 12 | static propTypes = { 13 | store: PropTypes.object.isRequired, 14 | history: PropTypes.object.isRequired, 15 | routes: PropTypes.node.isRequired 16 | } 17 | 18 | render() { 19 | const { store, history, routes, type, renderProps } = this.props 20 | 21 | return ( 22 | 23 | {type === 'server' 24 | ? 25 | : } 26 | {/*{type === 'client' && !window.devToolsExtension*/} 27 | {/*? */} 28 | {/*: null}*/} 29 | 30 | ) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /webpack/webpack-dev-server.js: -------------------------------------------------------------------------------- 1 | /** 2 | * # Webpack Dev Server 3 | */ 4 | 5 | var Express = require('express') 6 | var webpack = require('webpack') 7 | 8 | var config = require('../src/config') 9 | var webpackConfig = require('./dev.config') 10 | 11 | var compiler = webpack(webpackConfig) 12 | var host = config.host || 'localhost' 13 | var port = (Number(config.port) + 1) || 3001 14 | 15 | var serverOptions = { 16 | contentBase: 'http://' + host + ':' + port, 17 | quiet: true, 18 | noInfo: true, 19 | hot: true, 20 | inline: true, 21 | lazy: false, 22 | publicPath: webpackConfig.output.publicPath, 23 | headers: { 'Access-Control-Allow-Origin': '*' }, 24 | stats: { colors: true } 25 | } 26 | 27 | var app = new Express() 28 | 29 | app.use(require('webpack-dev-middleware')(compiler, serverOptions)) 30 | app.use(require('webpack-hot-middleware')(compiler)) 31 | 32 | app.listen(port, function onAppListening(err) { 33 | if (err) { 34 | console.error(err) 35 | } else { 36 | console.info('==> Webpack development server listening on port %s', port) 37 | } 38 | }) 39 | -------------------------------------------------------------------------------- /src/containers/Homepage/Homepage.js: -------------------------------------------------------------------------------- 1 | 2 | import React, { Component, PropTypes } from 'react' 3 | import { connectWpPost } from 'kasia/connect' 4 | import ContentTypes from 'kasia/types' 5 | import Helmet from 'react-helmet' 6 | 7 | import { Loading } from '../../components' 8 | import parse from '../../helpers/htmlParser' 9 | 10 | import styles from './Homepage.scss' 11 | 12 | @connectWpPost(ContentTypes.Page, 'homepage') 13 | export default class Homepage extends Component { 14 | static propTypes = { 15 | params: PropTypes.object 16 | } 17 | 18 | render () { 19 | const { page } = this.props.kasia 20 | 21 | let children = 22 | 23 | if (page) { 24 | children = ( 25 |
    26 | 27 |

    {page.title}

    28 | {parse(page.content.rendered)} 29 |
    30 | ) 31 | } 32 | 33 | return ( 34 |
    35 | {children} 36 |
    37 | ) 38 | } 39 | 40 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Outlandish 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /bin/www: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | /** 3 | * # WWW Script 4 | */ 5 | 'use strict' 6 | 7 | var fs = require('fs') 8 | var path = require('path') 9 | var WebpackIsomorphicTools = require('webpack-isomorphic-tools') 10 | 11 | var iso = require('../webpack/webpack-isomorphic-tools') 12 | 13 | var rootDir = path.resolve(__dirname, '../') 14 | var babelrc = fs.readFileSync('.babelrc') 15 | 16 | global.__CLIENT__ = false 17 | global.__SERVER__ = true 18 | global.__DEVELOPMENT__ = process.env.NODE_ENV !== 'production' 19 | 20 | try { 21 | var config = JSON.parse(babelrc) 22 | } catch (err) { 23 | console.error('==> ERROR: Error parsing .babelrc') 24 | console.error(err) 25 | } 26 | 27 | require('babel-register')(config) 28 | require('babel-polyfill') 29 | 30 | if (__DEVELOPMENT__ && !process.env.DEBUG) { 31 | var cond = require('piping')({ 32 | hook: true, 33 | ignore: /(\/\.|~$|\.json|\.scss$)/i 34 | }) 35 | 36 | if (!cond) { 37 | return 38 | } 39 | } 40 | 41 | var tool = new WebpackIsomorphicTools(iso) 42 | .server(rootDir, function () { 43 | require('../src/server') 44 | }) 45 | 46 | global.webpackIsomorphicTools = tool 47 | -------------------------------------------------------------------------------- /src/containers/PrimaryMenu/PrimaryMenu.js: -------------------------------------------------------------------------------- 1 | /** 2 | * # Primary Menu Component 3 | */ 4 | 5 | import React, { Component, PropTypes } from 'react' 6 | import { connect } from 'react-redux' 7 | 8 | import { MenuItem } from '../../components' 9 | import config from '../../config' 10 | 11 | import styles from './PrimaryMenu.scss' 12 | 13 | const getSlug = (url) => { 14 | return url.replace(config.wordpress, '') 15 | } 16 | 17 | const getMenu = (state) => ( 18 | state.wordpress.menuLocations 19 | && state.wordpress.menuLocations.primary 20 | || [] 21 | ) 22 | 23 | @connect((state) => ({ menu: getMenu(state) })) 24 | export default class PrimaryMenu extends Component { 25 | static propTypes = { 26 | activePathName: PropTypes.string.isRequired 27 | } 28 | 29 | render () { 30 | const { menu, activePathName } = this.props 31 | 32 | return ( 33 |
      34 | {menu.map((item) => { 35 | const slug = getSlug(item.url) 36 | const active = slug === activePathName 37 | return 38 | })} 39 |
    40 | ) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/containers/PostsIndex/PostsIndex.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { connectWpQuery } from 'kasia/connect' 3 | import Helmet from 'react-helmet' 4 | 5 | import { Loading, PostListing } from '../../components' 6 | import parse from '../../helpers/htmlParser' 7 | import query from './query' 8 | 9 | import styles from './PostsIndex.scss' 10 | 11 | @connectWpQuery(query) 12 | export default class PostsIndex extends Component { 13 | render () { 14 | const { entities: { pages, posts } } = this.props.kasia 15 | 16 | if (!pages || !posts) { 17 | return 18 | } 19 | 20 | const page = Object.values(pages)[0] 21 | 22 | return ( 23 |
    24 | 25 | 26 |

    27 | {page.title} 28 |

    29 | 30 |
    31 | {parse(page.content)} 32 |
    33 | 34 |
      35 | {Object.values(posts).map((post) => 36 | )} 37 |
    38 |
    39 | ) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * # App Config 3 | */ 4 | 5 | module.exports = { 6 | host: process.env.HOST || 'localhost', 7 | wordpress: process.env.WORDPRESS || 'http://story-tiles.dev', 8 | wpapi: process.env.WORDPRESS_API || 'http://story-tiles.dev/wp-json', 9 | port: process.env.PORT || 3000, 10 | app: { 11 | title: 'Kasia Boilerplate', 12 | description: 'A universal application boilerplate with Kasia', 13 | head: { 14 | // react-helmet configuration 15 | titleTemplate: '%s | Kasia Boilerplate', 16 | defaultTitle: 'Kasia Boilerplate', 17 | meta: [ 18 | { name: 'description', content: 'A universal application boilerplate with Kasia' }, 19 | { charset: 'utf-8' }, 20 | { property: 'og:site_name', content: '' }, 21 | { property: 'og:image', content: '' }, 22 | { property: 'og:locale', content: '' }, 23 | { property: 'og:title', content: '' }, 24 | { property: 'og:description', content: '' }, 25 | { property: 'og:card', content: '' }, 26 | { property: 'og:site', content: '' }, 27 | { property: 'og:creator', content: '' }, 28 | { property: 'og:image:width', content: '' }, 29 | { property: 'og:image:height', content: '' } 30 | ] 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/containers/Page/Page.js: -------------------------------------------------------------------------------- 1 | /** 2 | * # Page Container 3 | */ 4 | 5 | import React, { Component, PropTypes } from 'react' 6 | import { connectWpPost } from 'kasia/connect' 7 | import { push } from 'react-router-redux' 8 | import ContentTypes from 'kasia/types' 9 | import Helmet from 'react-helmet' 10 | 11 | import { Loading } from '../../components' 12 | import parse from '../../helpers/htmlParser' 13 | 14 | import styles from './Page.scss' 15 | 16 | class Page extends Component { 17 | static propTypes = { 18 | params: PropTypes.object 19 | } 20 | 21 | render () { 22 | const { page } = this.props.kasia 23 | 24 | let children = 25 | 26 | if (page) { 27 | children = ( 28 |
    29 | 30 |

    {page.title}

    31 | {parse(page.content.rendered)} 32 |
    33 | ) 34 | } 35 | 36 | return ( 37 |
    38 | {children} 39 |
    40 | ) 41 | } 42 | 43 | componentWillMount () { 44 | this.redirectWithoutPage(this.props) 45 | } 46 | 47 | componentWillReceiveProps (nextProps) { 48 | this.redirectWithoutPage(nextProps) 49 | } 50 | 51 | redirectWithoutPage (props) { 52 | const { query, page } = props.kasia 53 | 54 | if (query.complete && !page) { 55 | this.props.dispatch(push('/not-found')) 56 | } 57 | } 58 | } 59 | 60 | export default function (identifier) { 61 | return connectWpPost(ContentTypes.Page, identifier)(Page) 62 | } 63 | -------------------------------------------------------------------------------- /webpack/webpack-isomorphic-tools.js: -------------------------------------------------------------------------------- 1 | /** 2 | * # Webpack Isomorphic Tools Config 3 | * 4 | * https://github.com/halt-hammerzeit/webpack-isomorphic-tools 5 | */ 6 | 7 | var WebpackIsomorphicToolsPlugin = require('webpack-isomorphic-tools/plugin') 8 | 9 | module.exports = { 10 | webpack_assets_file_path: './static/dist/webpack-assets.json', 11 | assets: { 12 | images: { 13 | extensions: [ 14 | 'jpeg', 15 | 'jpg', 16 | 'png', 17 | 'gif' 18 | ], 19 | parser: WebpackIsomorphicToolsPlugin.url_loader_parser 20 | }, 21 | fonts: { 22 | extensions: [ 23 | 'woff', 24 | 'woff2', 25 | 'ttf', 26 | 'eot' 27 | ], 28 | parser: WebpackIsomorphicToolsPlugin.url_loader_parser 29 | }, 30 | style_modules: { 31 | extensions: [ 32 | 'scss' 33 | ], 34 | filter: function (module, regex, options, log) { 35 | return options.development 36 | ? WebpackIsomorphicToolsPlugin.style_loader_filter(module, regex, options, log) 37 | : regex.test(module.name) 38 | }, 39 | path: function (module, options, log) { 40 | return options.development 41 | ? WebpackIsomorphicToolsPlugin.style_loader_path_extractor(module, options, log) 42 | : module.name 43 | }, 44 | parser: function (module, options, log) { 45 | return options.development 46 | ? WebpackIsomorphicToolsPlugin.css_modules_loader_parser(module, options, log) 47 | : module.source 48 | } 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/store/create.js: -------------------------------------------------------------------------------- 1 | /* global __DEVELOPMENT__:false, __CLIENT__:false, __DEVTOOLS__:false */ 2 | /** 3 | * # Create Redux Store 4 | */ 5 | 6 | import { createStore as _createStore, applyMiddleware, compose } from 'redux' 7 | import { routerMiddleware } from 'react-router-redux' 8 | import { persistState } from 'redux-devtools' 9 | import createSagaMiddleware from 'redux-saga' 10 | 11 | import { DevTools } from '../components' 12 | import rootSaga from './sagas' 13 | import reducer from './reducer' 14 | 15 | export default function createStore (history, data) { 16 | // Sync dispatched route actions to the history 17 | const reduxRouterMiddleware = routerMiddleware(history) 18 | const sagaMiddleware = createSagaMiddleware() 19 | const middleware = [sagaMiddleware, reduxRouterMiddleware] 20 | 21 | const finalCreateStore = (() => { 22 | if (__DEVELOPMENT__ && __CLIENT__ && __DEVTOOLS__) { 23 | return compose( 24 | applyMiddleware(...middleware), 25 | persistState(window.location.href.match(/[?&]debug_session=([^&]+)\b/)), 26 | window.devToolsExtension ? window.devToolsExtension() : DevTools.instrument() 27 | )(_createStore) 28 | } 29 | 30 | return compose(applyMiddleware(...middleware))(_createStore) 31 | })() 32 | 33 | const store = finalCreateStore(reducer, data) 34 | 35 | if (__DEVELOPMENT__ && module.hot) { 36 | module.hot.accept('./reducer', () => { 37 | store.replaceReducer(require('./reducer')) 38 | }) 39 | } 40 | 41 | sagaMiddleware.run(rootSaga) 42 | 43 | return { 44 | ...store, 45 | runSaga: sagaMiddleware.run 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/helpers/Html.js: -------------------------------------------------------------------------------- 1 | /** 2 | * # Html 3 | * 4 | * Wrapper component containing HTML metadata and boilerplate tags. 5 | * Used in server-side code only to wrap the string output of the 6 | * rendered route component. 7 | * 8 | * The only thing this component doesn't (and can't) include is the 9 | * HTML doctype declaration, which is added to the rendered output 10 | * by the `server.js` file. 11 | */ 12 | 13 | import React, { Component, PropTypes } from 'react' 14 | import ReactDOM from 'react-dom/server' 15 | import Helmet from 'react-helmet' 16 | import serialize from 'serialize-javascript' 17 | 18 | export default class Html extends Component { 19 | static propTypes = { 20 | assets: PropTypes.object, 21 | component: PropTypes.node, 22 | store: PropTypes.object, 23 | noscript: PropTypes.bool 24 | } 25 | 26 | render () { 27 | const { assets, component, store, noscript } = this.props 28 | 29 | const head = Helmet.rewind() 30 | const stylesHtml = require('../theme/global.scss')._style 31 | const dataHtml = 'window.__STATE__=' + serialize(store.getState()) 32 | const content = component ? ReactDOM.renderToString(component) : '' 33 | 34 | return ( 35 | 36 | 37 | {head.base.toComponent()} 38 | {head.title.toComponent()} 39 | {head.meta.toComponent()} 40 | {head.link.toComponent()} 41 | {head.script.toComponent()} 42 | 43 | 44 | 45 | 46 | {/* styles for production */} 47 | {Object.keys(assets.styles).map((style, key) => 48 | 50 | )} 51 | 52 | {/* styles for development */} 53 | {Object.keys(assets.styles).length === 0 54 | ?