diff --git a/README.md b/README.md index fe595b07c..8df3ca96f 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ Read the [contributing] section before creating an issue. - Ensure you have [nodejs] installed - Get the source by running `git clone https://github.com/quran/quran.com-frontend/` or creating a [fork] - Run `npm install` to do first time installation of all dependencies -- Run `npm run dev` to start the dev server +- Run `npm run dev` to start the dev server. Make sure you have pm2 installed globally! `npm install -g pm2` - Open `http://localhost:8000` in your browser to see the app. ## Staging diff --git a/package.json b/package.json index fdfced3a1..808f1ea8a 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ "test:dev:lint": "./node_modules/eslint/bin/eslint.js ./src", "test:stylelint": "stylelint './src/**/*.scss' --config ./webpack/.stylelintrc", "dev-old": "env NODE_PATH='./src' PORT=8000 UV_THREADPOOL_SIZE=100 node ./webpack/webpack-dev-server.js & env NODE_PATH='./src' PORT=8000 node ./bin/server.js", - "dev": "env NODE_PATH='./src' PORT=8000 UV_THREADPOOL_SIZE=100 node --expose-gc ./node_modules/webpack-dev-server/bin/webpack-dev-server.js --config ./webpack/dev.config.js & env NODE_PATH='./src' PORT=8000 node --expose-gc ./bin/server.js", + "dev": "env NODE_PATH='./src' PORT=8000 UV_THREADPOOL_SIZE=100 node --expose-gc ./node_modules/webpack-dev-server/bin/webpack-dev-server.js --config ./webpack/dev.config.js --progress & env NODE_PATH='./src' PORT=8000 node --expose-gc ./bin/server.js", "start": "NODE_PATH='src' node ./start", "build": "npm run build:client & npm run build:server", "build:server": "babel ./src -d ./dist -D", @@ -44,7 +44,7 @@ "autoprefixer-loader": "3.2.0", "babel-cli": "6.11.4", "babel-core": "^6.24.0", - "babel-loader": "^6.4.1", + "babel-loader": "^7.0.0", "babel-plugin-add-module-exports": "0.1.4", "babel-plugin-syntax-dynamic-import": "^6.18.0", "babel-plugin-system-import-transformer": "^3.1.0", @@ -58,12 +58,14 @@ "babel-polyfill": "6.13.0", "babel-preset-es2015": "^6.24.0", "babel-preset-react": "6.11.1", + "babel-preset-react-optimize": "^1.0.1", "babel-preset-stage-0": "6.5.0", "babel-preset-stage-2": "6.13.0", "babel-register": "6.11.6", "babel-runtime": "6.11.6", + "babili-webpack-plugin": "^0.0.11", "body-parser": "1.15.2", - "bootstrap-loader": "2.0.0-beta.19", + "bootstrap-loader": "^2.1.0", "bootstrap-sass": "3.3.7", "bundle-loader": "0.5.4", "cache-manager": "1.5.0", @@ -82,7 +84,7 @@ "express": "4.14.0", "express-state": "1.4.0", "express-useragent": "1.0.4", - "extract-text-webpack-plugin": "2.0.0-beta.5", + "extract-text-webpack-plugin": "^2.1.0", "file-loader": "0.8.5", "fontfaceobserver": "1.7.3", "history": "^3.0.0", @@ -112,7 +114,7 @@ "react-cookie": "1.0.4", "react-dom": "15.4.1", "react-helmet": "3.1.0", - "react-hot-loader": "3.0.0-beta.6", + "react-hot-loader": "next", "react-inlinesvg": "0.5.4", "react-intl": "2.1.5", "react-loadable": "^3.3.1", @@ -131,7 +133,7 @@ "reselect": "2.5.3", "resolve-url": "0.2.1", "resolve-url-loader": "1.6.1", - "sass-loader": "4.1.1", + "sass-loader": "^6.0.5", "serialize-javascript": "1.3.0", "serve-favicon": "2.3.0", "sitemap": "1.8.1", @@ -140,8 +142,9 @@ "superagent": "3.3.1", "url": "0.11.0", "url-loader": "0.5.7", - "webpack": "2.2.0", - "webpack-dev-server": "2.1.0-beta.0", + "webpack": "^2.5.1", + "webpack-dev-middleware": "^1.10.2", + "webpack-dev-server": "^2.4.5", "webpack-isomorphic-tools": "2.5.7", "winston": "1.1.2" }, @@ -186,7 +189,7 @@ "react-transform-catch-errors": "1.0.0", "react-transform-hmr": "1.0.1", "redbox-react": "1.1.1", - "redux-devtools": "3.1.1", + "redux-devtools": "^3.4.0", "redux-devtools-dock-monitor": "1.1.0", "redux-devtools-log-monitor": "1.0.5", "sinon": "1.15.3", @@ -194,6 +197,7 @@ "stylelint": "7.1.0", "stylelint-webpack-plugin": "0.2.0", "webpack-bundle-analyzer": "2.2.1", + "webpack-dashboard": "^0.4.0", "webpack-dev-server": "2.1.0-beta.0", "webpack-hot-middleware": "2.12.2" } diff --git a/src/client.js b/src/client.js index cb2f06a95..e47e3194e 100644 --- a/src/client.js +++ b/src/client.js @@ -1,10 +1,7 @@ /* global document, window, $ */ -import 'babel-polyfill'; - import React from 'react'; import ReactDOM from 'react-dom'; import reactCookie from 'react-cookie'; -import Provider from 'react-redux/lib/components/Provider'; import Router from 'react-router/lib/Router'; import match from 'react-router/lib/match'; import browserHistory from 'react-router/lib/browserHistory'; @@ -12,7 +9,7 @@ import applyRouterMiddleware from 'react-router/lib/applyRouterMiddleware'; import useScroll from 'react-router-scroll'; import { ReduxAsyncConnect } from 'redux-connect'; import { syncHistoryWithStore } from 'react-router-redux'; -import { IntlProvider } from 'react-intl'; +import { AppContainer } from 'react-hot-loader'; import debug from 'debug'; @@ -20,7 +17,7 @@ import config from './config'; import ApiClient from './helpers/ApiClient'; import createStore from './redux/create'; import routes from './routes'; -import getLocalMessages from './helpers/setLocal'; +import Root from './containers/Root'; const client = new ApiClient(); const store = createStore(browserHistory, client, window.reduxData); @@ -45,31 +42,47 @@ window.clearCookies = () => { reactCookie.remove('smartbanner-installed'); }; -match({ history, routes: routes(store) }, (error, redirectLocation, renderProps) => { - const component = ( - ( - - )} - /> - ); +match( + { history, routes: routes(store) }, + (error, redirectLocation, renderProps) => { + const component = ( + ( + + )} + /> + ); + + const mountNode = document.getElementById('app'); + + debug('client', 'React Rendering'); - const mountNode = document.getElementById('app'); + ReactDOM.render( + + + , + mountNode, + () => { + debug('client', 'React Rendered'); + } + ); - debug('client', 'React Rendering'); + if (module.hot) { + module.hot.accept('./containers/Root', () => { + const NextRoot = require('./containers/Root'); // eslint-disable-line global-require - ReactDOM.render( - - - {component} - - , mountNode, () => { - debug('client', 'React Rendered'); + ReactDOM.render( + + + , + document.getElementById('root') + ); + }); } - ); -}); + } +); diff --git a/src/components/Audioplayer/RepeatDropdown/index.js b/src/components/Audioplayer/RepeatDropdown/index.js index 250714cea..c712c3abe 100644 --- a/src/components/Audioplayer/RepeatDropdown/index.js +++ b/src/components/Audioplayer/RepeatDropdown/index.js @@ -12,7 +12,6 @@ import LocaleFormattedMessage from 'components/LocaleFormattedMessage'; const style = require('../style.scss'); class RepeatButton extends Component { - handleToggle = () => { const { repeat, setRepeat, current } = this.props; @@ -41,7 +40,7 @@ class RepeatButton extends Component { from: current, to: current + 3 }); - } + }; renderRangeAyahs() { const { chapter, repeat, setRepeat } = this.props; @@ -54,7 +53,9 @@ class RepeatButton extends Component { {' '}: + /> + {' '} + :
- { - array.reduce((options, ayah, index) => { - if (index + 1 < chapter.versesCount) { // Exclude last verse - options.push( - - ); - } - return options; - }, []) - } + {array.reduce((options, ayah, index) => { + if (index + 1 < chapter.versesCount) { + // Exclude last verse + options.push( + + ); + } + return options; + }, [])}
  • -
  • @@ -88,25 +88,30 @@ class RepeatButton extends Component { {' '}: + /> + {' '} + :
    setRepeat({ ...repeat, to: parseInt(event.target.value, 10) })} + onChange={event => + setRepeat({ ...repeat, to: parseInt(event.target.value, 10) })} > - { - array.reduce((options, ayah, index) => { - if ((repeat.from ? repeat.from : 1) < index + 1 && index + 1 <= chapter.versesCount) { // eslint-disable-line max-len - options.push( - - ); - } - return options; - }, []) - } + {array.reduce((options, ayah, index) => { + if ( + (repeat.from ? repeat.from : 1) < index + 1 && + index + 1 <= chapter.versesCount + ) { + // eslint-disable-line max-len + options.push( + + ); + } + return options; + }, [])} @@ -123,23 +128,26 @@ class RepeatButton extends Component { {' '}:
    + /> + {' '} + : + {' '} +
    setRepeat({ - ...repeat, - from: parseInt(event.target.value, 10), - to: parseInt(event.target.value, 10) - })} + onChange={event => + setRepeat({ + ...repeat, + from: parseInt(event.target.value, 10), + to: parseInt(event.target.value, 10) + })} > - { - array.map((ayah, index) => ( - - )) - } + {array.map((ayah, index) => ( + + ))} ); @@ -179,7 +187,9 @@ class RepeatButton extends Component { return (
    - {repeat.from === repeat.to ? this.renderSingleAyah() : this.renderRangeAyahs()} + {repeat.from === repeat.to + ? this.renderSingleAyah() + : this.renderRangeAyahs()}
    ); } @@ -194,27 +204,30 @@ class RepeatButton extends Component { :
    + /> + : + {' '} +
    setRepeat({ - ...repeat, - times: parseInt(event.target.value, 10) - })} + onChange={event => + setRepeat({ + ...repeat, + times: parseInt(event.target.value, 10) + })} > - { - times.map((ayah, index) => ( - - )) - } + {times.map((ayah, index) => ( + + ))} @@ -234,7 +247,8 @@ class RepeatButton extends Component { {' '} + /> + {' '} import('./RepeatDropdown'), + loader: () => + import(/* webpackChunkName: "repeatdropdown" */ './RepeatDropdown'), LoadingComponent: ComponentLoader }); export class Audioplayer extends Component { - componentDidMount() { - const { isLoadedOnClient, buildOnClient, chapter, currentFile } = this.props; // eslint-disable-line no-shadow, max-len + const { + isLoadedOnClient, + buildOnClient, + chapter, + currentFile + } = this.props; // eslint-disable-line no-shadow, max-len debug('component:Audioplayer', 'componentDidMount'); @@ -140,11 +145,11 @@ export class Audioplayer extends Component { if (previouslyPlaying) play(); return false; - } + }; scrollToVerse = (ayahNum = this.props.currentVerse) => { scroller.scrollTo(`verse:${ayahNum}`, -45); - } + }; handleScrollTo = (ayahNum) => { const { shouldScroll } = this.props; @@ -152,14 +157,14 @@ export class Audioplayer extends Component { if (shouldScroll) { this.scrollToVerse(ayahNum); } - } + }; play = () => { this.handleScrollTo(); this.props.play(); this.preloadNext(); - } + }; preloadNext() { const { currentVerse, files } = this.props; @@ -184,7 +189,9 @@ export class Audioplayer extends Component { setRepeat, // eslint-disable-line no-shadow setAyah // eslint-disable-line no-shadow } = this.props; - const [chapter, ayah] = currentVerse.split(':').map(val => parseInt(val, 10)); + const [chapter, ayah] = currentVerse + .split(':') + .map(val => parseInt(val, 10)); file.pause(); @@ -234,19 +241,20 @@ export class Audioplayer extends Component { } return false; - } + }; handleScrollToggle = (event) => { event.preventDefault(); const { shouldScroll, currentVerse } = this.props; - if (!shouldScroll) { // we use the inverse (!) here because we're toggling, so false is true + if (!shouldScroll) { + // we use the inverse (!) here because we're toggling, so false is true this.scrollToVerse(currentVerse); } this.props.toggleScroll(); - } + }; handleAddFileListeners(file) { // NOTE: if no file, just wait. @@ -260,11 +268,8 @@ export class Audioplayer extends Component { const onLoadeddata = () => { // Default current time to zero. This will change - file.currentTime = ( // eslint-disable-line no-param-reassign - file.currentTime || - currentTime || - 0 - ); + file.currentTime = // eslint-disable-line no-param-reassign + file.currentTime || currentTime || 0; return update({ duration: file.duration, @@ -272,10 +277,11 @@ export class Audioplayer extends Component { }); }; - const onTimeupdate = () => update({ - currentTime: file.currentTime, - duration: file.duration - }); + const onTimeupdate = () => + update({ + currentTime: file.currentTime, + duration: file.duration + }); const onEnded = () => { const { repeat } = this.props; @@ -299,7 +305,7 @@ export class Audioplayer extends Component { file.ontimeupdate = null; // eslint-disable-line no-param-reassign }; - file.onloadeddata = onLoadeddata; // eslint-disable-line no-param-reassign + file.onloadeddata = onLoadeddata; // eslint-disable-line no-param-reassign file.onpause = onPause; // eslint-disable-line no-param-reassign file.onplay = onPlay; // eslint-disable-line no-param-reassign file.onended = onEnded; // eslint-disable-line no-param-reassign @@ -316,7 +322,7 @@ export class Audioplayer extends Component { file.onPause = null; // eslint-disable-line no-param-reassign file.onended = null; // eslint-disable-line no-param-reassign file.onprogress = null; // eslint-disable-line no-param-reassign - } + }; handleTrackChange = (fraction) => { const { currentFile, update } = this.props; // eslint-disable-line no-shadow @@ -326,7 +332,7 @@ export class Audioplayer extends Component { }); currentFile.currentTime = fraction * currentFile.duration; - } + }; renderPlayStopButtons() { const { isPlaying, pause } = this.props; // eslint-disable-line no-shadow @@ -361,7 +367,8 @@ export class Audioplayer extends Component { renderNextButton() { const { chapter, currentVerse } = this.props; if (!chapter) return false; - const isEnd = chapter.versesCount === parseInt(currentVerse.split(':')[1], 10); + const isEnd = + chapter.versesCount === parseInt(currentVerse.split(':')[1], 10); return ( +
    - { - currentFile && + {currentFile && - } - { - segments && + />} + {segments && segments[currentVerse] && - - } + }
    • : {currentVerse.split(':')[1]} + /> + : + {' '} + {currentVerse.split(':')[1]}
    • {this.renderPreviousButton()} @@ -450,7 +458,10 @@ export class Audioplayer extends Component { />
    • - +
    @@ -459,7 +470,8 @@ export class Audioplayer extends Component { } const mapStateToProps = (state, ownProps) => { - const currentVerse = state.audioplayer.currentVerse || ownProps.startVerse.verseKey; + const currentVerse = + state.audioplayer.currentVerse || ownProps.startVerse.verseKey; const files = state.audioplayer.files[ownProps.chapter.id]; return { @@ -474,7 +486,7 @@ const mapStateToProps = (state, ownProps) => { repeat: state.audioplayer.repeat, shouldScroll: state.audioplayer.shouldScroll, duration: state.audioplayer.duration, - currentTime: state.audioplayer.currentTime, + currentTime: state.audioplayer.currentTime }; }; diff --git a/src/components/Audioplayer/style.scss b/src/components/Audioplayer/style.scss index eecb3d786..ba8b44253 100644 --- a/src/components/Audioplayer/style.scss +++ b/src/components/Audioplayer/style.scss @@ -6,6 +6,7 @@ display: block; user-select: none; height: auto; + z-index: 1; padding: 10px 20px 5px; background: #fff; box-shadow: 0 0 0.5rem 0 rgba(0,0,0,.2); diff --git a/src/components/FontStyles/index.js b/src/components/FontStyles/index.js index 800566818..c97646357 100644 --- a/src/components/FontStyles/index.js +++ b/src/components/FontStyles/index.js @@ -6,17 +6,12 @@ import load from 'redux/actions/fontFace.js'; import debug from 'helpers/debug'; import selector from './selector'; -@connect( - state => ({ - fontFaces: selector(state) - }), - { load } -) - class FontStyles extends Component { - shouldComponentUpdate(nextProps) { - return JSON.stringify(this.props.fontFaces) !== JSON.stringify(nextProps.fontFaces); + return ( + JSON.stringify(this.props.fontFaces) !== + JSON.stringify(nextProps.fontFaces) + ); } render() { @@ -26,27 +21,27 @@ class FontStyles extends Component { if (__CLIENT__) { const FontFaceObserver = require('fontfaceobserver'); // eslint-disable-line global-require - Object.keys(fontFaces).filter(className => !fontFaces[className]).forEach((className) => { - const font = new FontFaceObserver(className); + Object.keys(fontFaces) + .filter(className => !fontFaces[className]) + .forEach((className) => { + const font = new FontFaceObserver(className); - font.load().then(() => load(className), () => load(className)); - }); + font.load().then(() => load(className), () => load(className)); + }); } return (
    - { - Object.keys(fontFaces).map(className => ( -