diff --git a/package.json b/package.json index a30f1cf0..2314178d 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "description": "Netlify CMS lets content editors work on structured content stored in git", "main": "index.js", "scripts": { - "start": "webpack-dev-server -d --inline --config webpack.config.js", + "start": "webpack-dev-server --config webpack.config.js", "test": "NODE_ENV=test mocha --recursive --compilers js:babel-register --require ./test/setup.js", "test:watch": "npm test -- --watch", "build": "webpack --config webpack.config.js", @@ -45,8 +45,6 @@ "eslint": "^3.5.0", "eslint-plugin-react": "^5.1.1", "exports-loader": "^0.6.3", - "express": "^4.13.4", - "extract-text-webpack-plugin": "^1.0.1", "file-loader": "^0.8.5", "immutable": "^3.7.6", "imports-loader": "^0.6.5", @@ -61,6 +59,7 @@ "pre-commit": "^1.1.3", "react": "^15.1.0", "react-dom": "^15.1.0", + "react-hot-loader": "^3.0.0-beta.2", "react-immutable-proptypes": "^1.6.0", "react-lazy-load": "^3.0.3", "react-pure-render": "^1.0.2", @@ -71,8 +70,8 @@ "redux-thunk": "^1.0.3", "style-loader": "^0.13.0", "url-loader": "^0.5.7", - "webpack": "^1.12.13", - "webpack-dev-server": "^1.14.1", + "webpack": "^1.13.2", + "webpack-dev-server": "^1.15.1", "webpack-postcss-tools": "^1.1.1", "whatwg-fetch": "^1.0.0" }, diff --git a/src/components/UnpublishedListing.css b/src/components/UnpublishedListing.css index a4b97b67..b37d8bf2 100644 --- a/src/components/UnpublishedListing.css +++ b/src/components/UnpublishedListing.css @@ -4,6 +4,9 @@ vertical-align: top; text-align: center; width: 28%; + & h2 { + font-size: 16px; + } } .column:not(:last-child) { diff --git a/src/components/UnpublishedListing.js b/src/components/UnpublishedListing.js index 01b35dcb..97a711f4 100644 --- a/src/components/UnpublishedListing.js +++ b/src/components/UnpublishedListing.js @@ -13,7 +13,7 @@ export default class UnpublishedListing extends React.Component { if (!column) { return entries.entrySeq().map(([currColumn, currEntries]) => (
-

{statusDescriptions.get(currColumn)}

+

{statusDescriptions.get(currColumn)}

{this.renderColumns(currEntries, currColumn)}
)); @@ -22,7 +22,7 @@ export default class UnpublishedListing extends React.Component { {entries.map(entry => { // Look for an "author" field. Fallback to username on backend implementation; const author = entry.getIn(['data', 'author'], entry.getIn(['metaData', 'user'])); - const timeStamp = moment(entry.getIn(['metaData', 'timeStamp'])).formate('llll'); + const timeStamp = moment(entry.getIn(['metaData', 'timeStamp'])).format('llll'); const link = `/editorialworkflow/${entry.getIn(['metaData', 'collection'])}/${entry.getIn(['metaData', 'status'])}/${entry.get('slug')}`; return ( @@ -41,6 +41,7 @@ export default class UnpublishedListing extends React.Component { return (
+

Editorial Workflow

{columns}
); diff --git a/src/containers/editorialWorkflow/CollectionPageHOC.js b/src/containers/editorialWorkflow/CollectionPageHOC.js index 4894e79a..bee4a2ac 100644 --- a/src/containers/editorialWorkflow/CollectionPageHOC.js +++ b/src/containers/editorialWorkflow/CollectionPageHOC.js @@ -6,6 +6,7 @@ import { selectUnpublishedEntries } from '../../reducers'; import { EDITORIAL_WORKFLOW, status } from '../../constants/publishModes'; import UnpublishedListing from '../../components/UnpublishedListing'; import { connect } from 'react-redux'; +import styles from '../CollectionPage.css'; export default function CollectionPageHOC(CollectionPage) { class CollectionPageHOC extends CollectionPage { @@ -23,7 +24,7 @@ export default function CollectionPageHOC(CollectionPage) { if (!isEditorialWorkflow) return super.render(); return ( -
+
{super.render()}
diff --git a/src/index.css b/src/index.css index 13806227..91c45f0d 100644 --- a/src/index.css +++ b/src/index.css @@ -3,8 +3,9 @@ html { -ms-text-size-adjust: 100%; -webkit-text-size-adjust: 100%; margin: 0; - font-family: Roboto,"Helvetica Neue",HelveticaNeue,Helvetica,Arial,sans-serif; + font-family: Roboto, "Helvetica Neue", HelveticaNeue, Helvetica, Arial, sans-serif; } + *, *:before, *:after { box-sizing: inherit; } @@ -13,15 +14,15 @@ body { font-family: 'Roboto', sans-serif; height: 100%; background-color: #f2f5f4; - color:#7c8382; + color: #7c8382; margin: 0; } header { background-color: #272e30; - box-shadow: 0 1px 2px 0 rgba(0,0,0,0.22); + box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.22); height: 54px; - border-bottom:2px solid #3ab7a5; + border-bottom: 2px solid #3ab7a5; position: fixed; width: 100%; z-index: 999; @@ -35,23 +36,24 @@ h1, h2, h3, h4, h5, h6, p { margin: 0; } -h1{ - color: #3ab7a5; - border-bottom: 1px solid #3ab7a5; - margin: 30px auto 25px; - padding-bottom: 15px; - font-size: 25px; +h1 { + color: #3ab7a5; + border-bottom: 1px solid #3ab7a5; + margin: 30px auto 25px; + padding-bottom: 15px; + font-size: 25px; } -header input{ - margin-bottom:0; +header input { + margin-bottom: 0; } -button{ + +button { border: 1px solid #3ab7a5; padding: 3px 20px; font-size: 12px; line-height: 18px; - background-color:#fff; + background-color: #fff; cursor: pointer; } @@ -120,7 +122,7 @@ button{ margin-top: 1px; z-index: 99999 !important; background: #fff; - box-shadow: 0 1px 3px rgba(0,0,0,.1); + box-shadow: 0 1px 3px rgba(0, 0, 0, .1); border: 1px solid #f9f9f9; } & .rdtOpen .rdtPicker { @@ -217,10 +219,10 @@ button{ & .rdtNext span { display: block; -webkit-touch-callout: none; /* iOS Safari */ - -webkit-user-select: none; /* Chrome/Safari/Opera */ - -khtml-user-select: none; /* Konqueror */ - -moz-user-select: none; /* Firefox */ - -ms-user-select: none; /* Internet Explorer/Edge */ + -webkit-user-select: none; /* Chrome/Safari/Opera */ + -khtml-user-select: none; /* Konqueror */ + -moz-user-select: none; /* Firefox */ + -ms-user-select: none; /* Internet Explorer/Edge */ user-select: none; } @@ -293,10 +295,10 @@ button{ display: block; -webkit-touch-callout: none; /* iOS Safari */ - -webkit-user-select: none; /* Chrome/Safari/Opera */ - -khtml-user-select: none; /* Konqueror */ - -moz-user-select: none; /* Firefox */ - -ms-user-select: none; /* Internet Explorer/Edge */ + -webkit-user-select: none; /* Chrome/Safari/Opera */ + -khtml-user-select: none; /* Konqueror */ + -moz-user-select: none; /* Firefox */ + -ms-user-select: none; /* Internet Explorer/Edge */ user-select: none; } & .rdtCounter .rdtBtn:hover { diff --git a/src/index.js b/src/index.js index 965e5bd2..ae127d1e 100644 --- a/src/index.js +++ b/src/index.js @@ -1,31 +1,33 @@ import React from 'react'; import { render } from 'react-dom'; -import { Provider } from 'react-redux'; -import { Router } from 'react-router'; +import { AppContainer } from 'react-hot-loader' +import Root from './root' import registry from './lib/registry'; -import configureStore from './store/configureStore'; -import routes from './routing/routes'; -import history, { syncHistory } from './routing/history'; import 'file?name=index.html!../example/index.html'; import './index.css'; -const store = configureStore(); - -// Create an enhanced history that syncs navigation events with the store -syncHistory(store); - +// Create mount element dynamically const el = document.createElement('div'); el.id = 'root'; document.body.appendChild(el); render(( - - - {routes} - - + + + ), el); +if (module.hot) { + module.hot.accept('./root', () => { + const NextRoot = require('./root').default; + render(( + + + + ), el); + }); +} + window.CMS = {}; console.log('reg: ', registry); for (const method in registry) { diff --git a/src/reducers/combinedReducer.js b/src/reducers/combinedReducer.js new file mode 100644 index 00000000..97e00e3f --- /dev/null +++ b/src/reducers/combinedReducer.js @@ -0,0 +1,8 @@ +import { combineReducers } from 'redux'; +import { routerReducer } from 'react-router-redux'; +import reducers from '.'; + +export default combineReducers({ + ...reducers, + routing: routerReducer +}); diff --git a/src/root.js b/src/root.js new file mode 100644 index 00000000..dcfd944a --- /dev/null +++ b/src/root.js @@ -0,0 +1,21 @@ +import React from 'react'; +import { Provider } from 'react-redux'; +import { Router } from 'react-router'; +import routes from './routing/routes'; +import history, { syncHistory } from './routing/history'; +import configureStore from './store/configureStore'; + +const store = configureStore(); + +// Create an enhanced history that syncs navigation events with the store +syncHistory(store); + +const Root = () => ( + + + {routes} + + +); + +export default Root; diff --git a/src/store/configureStore.js b/src/store/configureStore.js index d7552c00..19fe8a45 100644 --- a/src/store/configureStore.js +++ b/src/store/configureStore.js @@ -1,18 +1,20 @@ -import { createStore, applyMiddleware, combineReducers, compose } from 'redux'; +import { createStore, applyMiddleware, compose } from 'redux'; import thunkMiddleware from 'redux-thunk'; -import { routerReducer } from 'react-router-redux'; -import reducers from '../reducers'; +import reducer from '../reducers/combinedReducer'; -const reducer = combineReducers({ - ...reducers, - routing: routerReducer -}); +export default function configureStore(initialState) { + const store = createStore(reducer, initialState, compose( + applyMiddleware(thunkMiddleware), + window.devToolsExtension ? window.devToolsExtension() : f => f + )); -const createStoreWithMiddleware = compose( - applyMiddleware(thunkMiddleware), - window.devToolsExtension ? window.devToolsExtension() : (f) => f -)(createStore); + if (module.hot) { + // Enable Webpack hot module replacement for reducers + module.hot.accept('../reducers/combinedReducer', () => { + const nextReducer = require('../reducers/combinedReducer') // eslint-disable-line + store.replaceReducer(nextReducer); + }); + } -export default (initialState) => ( - createStoreWithMiddleware(reducer, initialState) -); + return store; +} diff --git a/webpack.config.js b/webpack.config.js index 32e97dd8..94222a49 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -1,8 +1,10 @@ /* global module, __dirname, require */ var webpack = require('webpack'); -var ExtractTextPlugin = require('extract-text-webpack-plugin'); var path = require('path'); +const HOST = 'localhost'; +const PORT = '8080'; + module.exports = { module: { loaders: [ @@ -13,7 +15,7 @@ module.exports = { { test: /\.json$/, loader: 'json-loader' }, { test: /\.css$/, - loader: ExtractTextPlugin.extract('style', 'css?modules&importLoaders=1&&localIdentName=cms__[name]__[local]!postcss'), + loader: 'style!css?modules&importLoaders=1&&localIdentName=cms__[name]__[local]!postcss', }, { loader: 'babel', @@ -22,7 +24,13 @@ module.exports = { query: { cacheDirectory: true, presets: ['react', 'es2015'], - plugins: ['transform-class-properties', 'transform-object-assign', 'transform-object-rest-spread', 'lodash'] + plugins: [ + 'transform-class-properties', + 'transform-object-assign', + 'transform-object-rest-spread', + 'lodash', + 'react-hot-loader/babel' + ] } } ] @@ -34,7 +42,9 @@ module.exports = { ], plugins: [ - new ExtractTextPlugin('cms.css', { allChunks: true }), + new webpack.optimize.OccurenceOrderPlugin(), + new webpack.HotModuleReplacementPlugin(), + new webpack.NoErrorsPlugin(), new webpack.ProvidePlugin({ 'fetch': 'imports?this=>global!exports?global.fetch!whatwg-fetch' }) @@ -42,16 +52,23 @@ module.exports = { context: path.join(__dirname, 'src'), entry: { - cms: './index', + cms: [ + 'webpack/hot/dev-server', + `webpack-dev-server/client?http://${HOST}:${PORT}/`, + 'react-hot-loader/patch', + './index' + ], }, output: { path: path.join(__dirname, 'dist'), - filename: '[name].js' + filename: '[name].js', + publicPath: `http://${HOST}:${PORT}/`, }, - externals: [/^vendor\/.+\.js$/], + externals: [/^vendor\/.+\.js$/], devServer: { + hot: true, contentBase: 'example/', historyApiFallback: true, - devTool: 'source-map' + devTool: 'cheap-module-source-map' }, };