From f0893eb6092913792092bc9d8831f983d58ff576 Mon Sep 17 00:00:00 2001 From: Mohamed El Mahallawy Date: Sun, 19 Jun 2016 03:31:36 -0700 Subject: [PATCH 1/4] Change --- .env | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.env b/.env index f21f36089..610c409b2 100644 --- a/.env +++ b/.env @@ -1,6 +1,6 @@ NODE_ENV=development PORT=8000 -API_URL=http://api.quran.com:3000 +API_URL=http://quran.com:3000 SEGMENTS_KEY= SENTRY_KEY_CLIENT= SENTRY_KEY_SERVER= From fbac6733458e3b4bf936032726f8e024ad93801e Mon Sep 17 00:00:00 2001 From: Mohamed El Mahallawy Date: Thu, 23 Jun 2016 18:35:15 -0700 Subject: [PATCH 2/4] Eslint added (#345) * Eslint added * eslint in travis * ayah require * Fixed * fixed bug that was preventing ayah transition on audioplayer * fix autocomplete thing --- .eslintignore | 1 + .eslintrc | 78 +++--- client.js | 15 +- package.json | 15 +- src/components/Audioplayer/Segments/index.js | 150 ++++++----- .../Audioplayer/Track/Tracker/index.js | 2 +- src/components/Audioplayer/Track/index.js | 71 ++--- src/components/Audioplayer/index.js | 203 ++++++++------- src/components/Ayah/index.js | 148 +++++------ src/components/Bismillah/index.js | 3 +- src/components/ContentDropdown/index.js | 14 +- src/components/Copy/index.js | 7 +- src/components/FontSizeDropdown/index.js | 26 +- src/components/FontStyles/index.js | 6 +- src/components/IndexHeader/Nav/index.js | 53 ++-- src/components/IndexHeader/index.js | 5 +- src/components/InformationToggle/index.js | 13 +- src/components/LazyLoad/index.js | 9 +- src/components/Line/index.js | 17 +- src/components/Loader/index.js | 2 +- src/components/PageBreak/index.js | 4 + src/components/ReadingModeToggle/index.js | 4 +- src/components/ReciterDropdown/index.js | 14 +- src/components/SearchAutocomplete/index.js | 84 +++--- src/components/SearchInput/index.js | 83 +++--- src/components/SurahInfo/index.js | 81 +++--- src/components/SurahsDropdown/index.js | 10 +- src/components/SwitchToggle/index.js | 8 +- src/components/VersesDropdown/index.js | 29 ++- src/config.js | 38 +-- src/containers/About/index.js | 164 ++++++------ src/containers/App/index.js | 53 ++-- src/containers/Contact/index.js | 21 +- src/containers/DevTools/index.js | 6 +- src/containers/Donations/index.js | 118 +++++---- src/containers/Error/index.js | 47 ++-- src/containers/Home/index.js | 135 +++++----- src/containers/Search/Header/index.js | 34 +-- src/containers/Search/index.js | 158 ++++++------ src/containers/Surah/Header/index.js | 13 +- src/containers/Surah/Title/index.js | 63 +++-- src/containers/Surah/descriptions.js | 1 + src/containers/Surah/index.js | 243 +++++++++++------- src/helpers/ApiClient.js | 19 +- src/helpers/Html.js | 109 ++++---- src/helpers/buildAudio.js | 26 +- src/helpers/buildFontFaces.js | 36 +-- src/helpers/debug.js | 10 +- src/helpers/flowType.js | 30 --- src/helpers/metrics.js | 8 +- src/redux/create.js | 1 + src/redux/middleware/clientMiddleware.js | 50 ++-- src/redux/modules/audioplayer.js | 13 +- src/redux/modules/ayahs.js | 20 +- src/redux/modules/options.js | 2 +- src/redux/modules/searchResults.js | 1 - src/redux/modules/surahs.js | 2 +- src/routes.js | 36 +-- src/utils/checkValidSurah.js | 4 +- webpack/dev.config.js | 3 +- 60 files changed, 1433 insertions(+), 1186 deletions(-) create mode 100644 .eslintignore delete mode 100644 src/helpers/flowType.js diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 000000000..1ee2668e7 --- /dev/null +++ b/.eslintignore @@ -0,0 +1 @@ +src/components/SurahInfo/htmls/* diff --git a/.eslintrc b/.eslintrc index 839cbb37b..9df98661b 100644 --- a/.eslintrc +++ b/.eslintrc @@ -1,40 +1,54 @@ { "parser": "babel-eslint", + "extends": "airbnb", "env": { - "es6": true, - "node": true, "browser": true, - "jquery": true - }, - "ecmaFeatures": { - "arrowFunctions": true, - "binaryLiterals": true, - "blockBindings": true, - "classes": true, - "defaultParams": true, - "destructuring": true, - "forOf": true, - "generators": true, - "modules": true, - "objectLiteralComputedProperties": true, - "objectLiteralDuplicateProperties": true, - "objectLiteralShorthandMethods": true, - "objectLiteralShorthandProperties": true, - "octalLiterals": true, - "regexUFlag": true, - "regexYFlag": true, - "spread": true, - "superInFunctions": true, - "templateStrings": true, - "unicodeCodePointEscapes": true, - "globalReturn": true, - "jsx": false + "node": true, + "mocha": true, + "es6": true }, "rules": { - "strict": 0, - "indent": [2, 2], - "quotes": [2, "single"], - "no-unused-vars": 0 + "react/no-multi-comp": 0, + "import/default": 0, + "import/no-duplicates": 0, + "import/named": 0, + "import/namespace": 0, + "import/no-unresolved": 0, + "import/no-named-as-default": 2, + // Temporarirly disabled due to a possible bug in babel-eslint (todomvc example) + "block-scoped-var": 0, + // Temporarily disabled for test/* until babel/babel-eslint#33 is resolved + "padded-blocks": 0, + "comma-dangle": 0, // not sure why airbnb turned this on. gross! + "indent": [2, 2, {"SwitchCase": 1}], + "no-console": 0, + "no-alert": 0, + "object-curly-spacing": 0, + "no-case-declarations": 0 + }, + "plugins": [ + "react", "import" + ], + "settings": { + "import/parser": "babel-eslint", + "import/resolve": { + moduleDirectory: ["node_modules", "src"] + } + }, + "parserOptions":{ + "ecmaFeatures": { + "experimentalObjectRestSpread": true + } }, - "plugins": ["react"] + "globals": { + "__DEVELOPMENT__": true, + "__CLIENT__": true, + "__SERVER__": true, + "__DISABLE_SSR__": true, + "__DEVTOOLS__": true, + "socket": true, + "webpackIsomorphicTools": true, + ga: true, + Raven: true + } } diff --git a/client.js b/client.js index 691b641ed..7a6cf8e1b 100644 --- a/client.js +++ b/client.js @@ -1,4 +1,4 @@ -/*global document, window, $ */ +/* global document, window, $ */ import 'babel-polyfill'; import React from 'react'; @@ -21,15 +21,15 @@ import createStore from './src/redux/create'; import routes from './src/routes'; const client = new ApiClient(); -const store = createStore(browserHistory, client, window.__data); +const store = createStore(browserHistory, client, window.reduxData); const history = syncHistoryWithStore(browserHistory, store); -Raven.config(config.sentryClient).install() +Raven.config(config.sentryClient).install(); window.quranDebug = debug; window.ReactDOM = ReactDOM; // For chrome dev tool support -window.clearCookies = function() { +window.clearCookies = () => { reactCookie.remove('quran'); reactCookie.remove('content'); reactCookie.remove('audio'); @@ -38,7 +38,7 @@ window.clearCookies = function() { // Init tooltip if (typeof window !== 'undefined') { - $(function () { + $(() => { $(document.body).tooltip({ selector: '[data-toggle="tooltip"]', animation: false @@ -69,6 +69,7 @@ match({ history, routes: routes() }, (error, redirectLocation, renderProps) => { {component} , mountNode, () => { - debug('client', 'React Rendered'); - }); + debug('client', 'React Rendered'); + } + ); }); diff --git a/package.json b/package.json index 601b12f2e..57e1bfbf2 100644 --- a/package.json +++ b/package.json @@ -1,12 +1,12 @@ { "name": "quran", - "version": "0.0.0", + "version": "1.0.0", "private": true, "scripts": { "test": "./node_modules/karma/bin/karma start", - "test:ci:unit": "./node_modules/karma/bin/karma start --browsers PhantomJS --single-run", + "test:ci:unit": "./node_modules/karma/bin/karma start --browsers PhantomJS --single-run; npm run test:ci:lint", "test:ci:functional": "node ./nightwatch.js -c ./nightwatch.json -e production", - "test:ci:lint": "eslint ./src/scripts/**/*.js", + "test:ci:lint": "eslint ./src/**/*.js", "test:dev:unit": "./node_modules/karma/bin/karma start", "test:dev:functional": "node ./nightwatch.js -c ./nightwatch.json", "test:dev:lint": "eslint ./src/scripts/**/*.js", @@ -117,9 +117,12 @@ "chromedriver": "^2.19.0", "del": "^2.0.2", "enzyme": "^2.2.0", - "eslint": "^1.4.1", - "eslint-loader": "^1.0.0", - "eslint-plugin-react": "^3.3.2", + "eslint": "^2.13.0", + "eslint-config-airbnb": "^9.0.1", + "eslint-loader": "^1.3.0", + "eslint-plugin-import": "^1.8.1", + "eslint-plugin-jsx-a11y": "^1.5.3", + "eslint-plugin-react": "^5.2.2", "jscs": "^2.1.1", "karma": "^0.13.9", "karma-chai": "^0.1.0", diff --git a/src/components/Audioplayer/Segments/index.js b/src/components/Audioplayer/Segments/index.js index 269f42532..c8035046e 100644 --- a/src/components/Audioplayer/Segments/index.js +++ b/src/components/Audioplayer/Segments/index.js @@ -1,5 +1,4 @@ import React, { Component, PropTypes } from 'react'; -import ReactDOM from 'react-dom'; import { decrypt } from 'sjcl'; export default class Segments extends Component { @@ -15,33 +14,26 @@ export default class Segments extends Component { currentWord: null }; - state = { - intervals: [], - }; - constructor() { - super(...arguments); + super(...arguments); // eslint-disable-line prefer-rest-params this.secret = process.env.SEGMENTS_KEY; this.currentWord = null; } + state = { + intervals: [], + }; + // LIFECYCLE METHODS componentDidMount() { - const builtIntervals = this.buildIntervals(); - //console.debug('Segments componentDidMount', this.props.audio, builtIntervals); this.bindListeners(); } - componentWillUnmount() { - //console.log('Segments componentWillUnmount', this.props.audio, { props: this.props, state: this.state }); - this.unbindListeners(); - } - componentWillReceiveProps(nextProps) { const prevProps = this.props; - if (prevProps.audio != nextProps.audio) { + if (prevProps.audio !== nextProps.audio) { this.unbindListeners(prevProps); this.buildIntervals(nextProps); @@ -52,26 +44,30 @@ export default class Segments extends Component { shouldComponentUpdate(nextProps, nextState) { const prevProps = this.props; const prevState = this.state; + return [ - prevProps.audio != nextProps.audio, - prevProps.segments != nextProps.segments, - prevState.intervals != nextState.intervals, - prevProps.currentWord != nextProps.currentWord, - nextProps.currentWord != this.currentWord + prevProps.audio !== nextProps.audio, + prevProps.segments !== nextProps.segments, + prevState.intervals !== nextState.intervals, + prevProps.currentWord !== nextProps.currentWord, + nextProps.currentWord !== this.currentWord ].some(b => b); - //return false; // TODO: I think we can just 'return false' here since there is nothing to actually render... // oh wait, maybe i need it so that componentDidUpdate will run..., despite render() not // actually being needed... dunno right now } - componentDidUpdate(prevProps, prevState) { - if (this.currentWord != this.props.currentWord) { // currentWord was changed by the user - if (this.props.currentWord != null) { + componentDidUpdate() { + if (this.currentWord !== this.props.currentWord) { // currentWord was changed by the user + if (this.props.currentWord !== null) { const wordInterval = this.state.words[this.props.currentWord.split(/:/).pop()]; - const timeToSeek = wordInterval.startTime + 0.001; // seek to the currentWord starting time and return + // seek to the currentWord starting time and return + const timeToSeek = wordInterval.startTime + 0.001; const isSeekable = this.props.audio.seekable && this.props.audio.seekable.length > 0; - const withinRange = !isSeekable? null : timeToSeek >= this.props.audio.seekable.start(0) && timeToSeek <= this.props.audio.seekable.end(0); + const withinRange = !isSeekable ? + null : + (timeToSeek >= this.props.audio.seekable.start(0) && + timeToSeek <= this.props.audio.seekable.end(0)); if (isSeekable && withinRange) { // seek to it this.props.audio.currentTime = timeToSeek; @@ -83,15 +79,24 @@ export default class Segments extends Component { this.props.audio.addEventListener('canplay', seekToTime); } } - return this.setCurrentWord(this.props.currentWord, 'componentDidUpdate'); // but don't forget to set the change internally for next time + + // but don't forget to set the change internally for next time + return this.setCurrentWord(this.props.currentWord, 'componentDidUpdate'); } + + return false; } - render() { - return (); + componentWillUnmount() { + this.unbindListeners(); } - // END LIFECYCLE METHODS + setCurrentWord(currentWord = null) { + // this is more immediately available but should eventually agree with props + this.currentWord = currentWord; + // calls the redux dispatch function passed down from the Audioplayer + this.props.onSetCurrentWord(currentWord); + } buildIntervals(props = this.props) { let segments = null; @@ -102,28 +107,32 @@ export default class Segments extends Component { } const words = {}; - const intervals = segments.map((segment, segmentIndex) => { - const startTime = segment[0], - endTime = segment[0] + segment[1], - duration = segment[1], - wordIndex = segment[2], - mappedVal = { startTime: startTime/1000, endTime: endTime/1000, duration: duration/1000 }; - - if (wordIndex >= 0 && !words[wordIndex]) + const intervals = segments.map(segment => { + const startTime = segment[0]; + const endTime = segment[0] + segment[1]; + const duration = segment[1]; + const wordIndex = segment[2]; + const mappedVal = { + startTime: startTime / 1000, + endTime: endTime / 1000, + duration: duration / 1000 + }; + + if (wordIndex >= 0 && !words[wordIndex]) { words[wordIndex] = mappedVal; + } - return [startTime/1000, endTime/1000, wordIndex]; + return [startTime / 1000, endTime / 1000, wordIndex]; }); - this.state.intervals = intervals; - this.state.words = words; + this.setState({ intervals, words }); + return { intervals, words }; // for console debugging } - bindListeners(props = this.props, state = this.state) { - const audio = props.audio; - const intervals = state.intervals; - const words = state.words; + bindListeners() { + const { audio, currentAyah, currentWord } = this.props; + const { intervals } = this.state; // Play listener const play = () => { @@ -131,8 +140,6 @@ export default class Segments extends Component { let repeaterId = null; new Promise((done, fail) => { - //console.debug('Play listener for '+ props.currentAyah +' started...'); - const intervalFn = () => { if (audio.seeking) return console.warn('we are seeking right now?'); if (audio.paused || audio.ended) return console.warn('stopped by running?'); @@ -141,13 +148,15 @@ export default class Segments extends Component { // the number of times we need to resort to a search, just in case logarithmic // time isn't good enough const index = this.binarySearch(intervals, audio.currentTime, this.compareFn); - const currentWord = index >= 0 && intervals[index][2] >= 0 ? - this.props.currentAyah +':'+ intervals[index][2] : null; + const word = index >= 0 && intervals[index][2] >= 0 ? + `${currentAyah}:${intervals[index][2]}` : + null; - if (currentWord == this.props.currentWord) return; // no work to be done - else if (currentWord == this.currentWord) return; // still no work to be done - else return this.setCurrentWord(currentWord, 'Play listener Do Stuff block'); // something changed, so we deal with it - } + if (word === currentWord) return false; // no work to be done + else if (word === this.currentWord) return false; // still no work to be done + // something changed, so we deal with it + return this.setCurrentWord(word, 'Play listener Do Stuff block'); + }; intervalFn(); repeaterId = setInterval(intervalFn, 30); @@ -161,14 +170,12 @@ export default class Segments extends Component { listeners[evName] = fail; audio.addEventListener(evName, listeners[evName], false); }); - }).then((ev) => { + }).then(() => { clearInterval(repeaterId); ['pause', 'ended', 'error', 'emptied', 'abort'].forEach((evName) => { audio.removeEventListener(evName, listeners[evName]); }); - - //console.debug('Play listener for '+ props.currentAyah +(ev && ev.type ? ' resolved by '+ ev.type : 'stopped') +' event'); }); }; audio.addEventListener('play', play, false); @@ -176,34 +183,37 @@ export default class Segments extends Component { this.setState({ listeners: { play }}); } - unbindListeners(props = this.props) { - props.audio.removeEventListener('play', this.state.listeners.play); - } + unbindListeners() { + const { audio } = this.props; - setCurrentWord(currentWord = null, debug = null) { - this.currentWord = currentWord; // this is more immediately available but should eventually agree with props - this.props.onSetCurrentWord(currentWord); // calls the redux dispatch function passed down from the Audioplayer - //console.log('setCurrentWord', currentWord, debug ? debug : ''); + audio.removeEventListener('play', this.state.listeners.play); } compareFn(time, interval) { if (time < interval[0]) return -1; else if (time > interval[1]) return 1; - else if (time == interval[0]) return 0; // floor inclusive - else if (time == interval[1]) return 1; - else return 0; + else if (time === interval[0]) return 0; // floor inclusive + else if (time === interval[1]) return 1; + + return 0; } binarySearch(ar, el, compareFn = (a, b) => (a - b)) { - var m = 0; - var n = ar.length - 1; + let m = 0; + let n = ar.length - 1; while (m <= n) { - var k = (n + m) >> 1; - var cmp = compareFn(el, ar[k]); - if (cmp > 0) m = k + 1; + const k = (n + m) >> 1; + const cmp = compareFn(el, ar[k]); + if (cmp > 0) m = k + 1; else if (cmp < 0) n = k - 1; else return k; } return -m - 1; } + + render() { + return ( + + ); + } } diff --git a/src/components/Audioplayer/Track/Tracker/index.js b/src/components/Audioplayer/Track/Tracker/index.js index 55853b7ec..919abbbe3 100644 --- a/src/components/Audioplayer/Track/Tracker/index.js +++ b/src/components/Audioplayer/Track/Tracker/index.js @@ -14,7 +14,7 @@ export default class Tracker extends Component { )}px`; element.parentElement.style.background = ( - `linear-gradient(to right, #2CA4AB 0%,#2CA4AB ${nextProps.progress}%,#635e49 ${nextProps.progress}%,#635e49 100%)` + `linear-gradient(to right, #2CA4AB 0%,#2CA4AB ${nextProps.progress}%,#635e49 ${nextProps.progress}%,#635e49 100%)` // eslint-disable-line max-len ); } diff --git a/src/components/Audioplayer/Track/index.js b/src/components/Audioplayer/Track/index.js index b9e3bd713..0c95f37d6 100644 --- a/src/components/Audioplayer/Track/index.js +++ b/src/components/Audioplayer/Track/index.js @@ -23,21 +23,12 @@ export default class Track extends Component { }; componentDidMount() { - this.setState({ mounted: true }); + this.setState({ mounted: true }); // eslint-disable-line react/no-did-mount-set-state if (this.props.file && __CLIENT__) { - //console.debug('Track componentDidMount', this.props.file, { file: this.props.file }); this.onFileLoad(this.props.file); } } - componentWillUnmount() { - //console.log('Track componentWillUnmount', this.props.file); - this.setState({ mounted: false }); // TODO, yeah, this is bad but we unmount and mount when - this.state.mounted = false; // we lazy load, and it causes problems... - // trace memory profile count - this.onFileUnload(this.props.file); - } - shouldComponentUpdate(nextProps, nextState) { return [ this.props.file.src !== nextProps.file.src, @@ -48,38 +39,38 @@ export default class Track extends Component { ].some(test => test); } - componentDidUpdate(prevProps, prevState) { + componentDidUpdate(prevProps) { if (this.props.file.src !== prevProps.file.src) { if (!prevProps.file.paused) { prevProps.file.pause(); } - prevProps.file.currentTime = 0; + prevProps.file.currentTime = 0; // eslint-disable-line no-param-reassign this.onFileUnload(prevProps.file); - this.setState({ progress: 0 }); + this.setState({ progress: 0 }); // eslint-disable-line react/no-did-update-set-state this.onFileLoad(this.props.file); } } + componentWillUnmount() { + this.setState({ mounted: false }); // TODO, yeah, this is bad but we unmount and mount when + this.state.mounted = false; // we lazy load, and it causes problems... + // trace memory profile count + this.onFileUnload(this.props.file); + } + onFileLoad(file) { // Preload file file.setAttribute('preload', 'auto'); - /* - if (!this.didAlreadyLoad) this.didAlreadyLoad = {}; - if (this.didAlreadyLoad[file.src]) return; - else this.didAlreadyLoad[file.src] = true; - */ - const loadeddata = () => { // Default current time to zero. This will change file.currentTime = 0; // eslint-disable-line no-param-reassign }; - file.onloadeddata = loadeddata; + file.onloadeddata = loadeddata; // eslint-disable-line no-param-reassign const timeupdate = () => { - //console.assert(this.state.mounted, 'timeupdate without being mounted', file, { file, mounted: this.state.mounted }); if (!this.state.mounted) return; // TODO needed? const progress = ( @@ -89,14 +80,14 @@ export default class Track extends Component { this.setState({ progress }); }; - file.ontimeupdate = timeupdate; + + file.ontimeupdate = timeupdate; // eslint-disable-line no-param-reassign const ended = () => { const { shouldRepeat, onEnd, isStarted, doStop, currentAyah, surah } = this.props; // if we're on the last ayah, do a full stop at the playback end - if (currentAyah == surah.id +':'+ surah.ayat) - return doStop(); + if (currentAyah === `${surah.id}:${surah.ayat}`) return doStop(); if (isStarted && shouldRepeat) { file.pause(); @@ -105,8 +96,11 @@ export default class Track extends Component { } else { onEnd(); } + + return false; }; - file.onended = ended; + + file.onended = ended; // eslint-disable-line no-param-reassign const play = () => { if (!this.state.mounted) return; @@ -116,17 +110,19 @@ export default class Track extends Component { this.setState({ currentTime }); }; - file.onplay = play; + + file.onplay = play; // eslint-disable-line no-param-reassign + + return false; } onFileUnload(file) { - //console.warn('onFileUnload'); if (!file.paused) { file.pause(); } } - onTrackerMove(event) { + onTrackerMove = (event) => { const { file } = this.props; const fraction = ( @@ -145,13 +141,20 @@ export default class Track extends Component { } render() { - const { progress, mounted } = this.state; + const { progress } = this.state; const { isStarted, file } = this.props; if (file.readyState >= 3) { - // the Math.round bit prevents us from trying to play again when we're effectively at the end of the audio file; this should allow shouldRepeat to work without getting overridden: - // ...but at the time I monkey-patched it, so we might be able to get rid of it since we cleaned up? Let's not for now... - if (isStarted && file.paused && file.readyState >= 3 && Math.round(file.currentTime) != Math.round(file.duration)) { + // the Math.round bit prevents us from trying to play again when we're + // effectively at the end of the audio file; this should allow + // shouldRepeat to work without getting overridden: + // ...but at the time I monkey-patched it, so we might be able to get + // rid of it since we cleaned up? Let's not for now... + if ( + isStarted && + file.paused && + file.readyState >= 3 && + Math.round(file.currentTime) !== Math.round(file.duration)) { file.play(); } else if (!isStarted && !file.paused) { file.pause(); @@ -159,8 +162,8 @@ export default class Track extends Component { } return ( -
- +
+
); } diff --git a/src/components/Audioplayer/index.js b/src/components/Audioplayer/index.js index d9252bfb9..7553db722 100644 --- a/src/components/Audioplayer/index.js +++ b/src/components/Audioplayer/index.js @@ -5,7 +5,13 @@ import Col from 'react-bootstrap/lib/Col'; import { Tooltip, OverlayTrigger } from 'react-bootstrap'; // Redux -import { start, stop, toggleRepeat, toggleScroll, buildOnClient } from '../../redux/modules/audioplayer'; +import { + start, + stop, + toggleRepeat, + toggleScroll, + buildOnClient +} from '../../redux/modules/audioplayer'; import { setCurrentAyah, setCurrentWord } from '../../redux/modules/ayahs'; // Components @@ -64,7 +70,7 @@ export default class Audioplayer extends Component { className: PropTypes.string, surah: PropTypes.object.isRequired, onLoadAyahs: PropTypes.func.isRequired, - + segments: PropTypes.object, files: PropTypes.object, currentAyah: PropTypes.string, currentWord: PropTypes.string, @@ -79,7 +85,8 @@ export default class Audioplayer extends Component { stop: PropTypes.func.isRequired, toggleRepeat: PropTypes.func.isRequired, toggleScroll: PropTypes.func.isRequired, - ayahIds: PropTypes.array + ayahIds: PropTypes.array, + isStarted: PropTypes.bool }; static defaultProps = { @@ -94,117 +101,124 @@ export default class Audioplayer extends Component { componentDidMount() { const { isLoadedOnClient, buildOnClient, surah } = this.props; // eslint-disable-line no-shadow + debug('component:Audioplayer', 'componentDidMount'); if (!isLoadedOnClient && __CLIENT__) { - //console.debug('Audioplayer componentDidMount'); debug('component:Audioplayer', 'componentDidMount on client'); + return buildOnClient(surah.id); - }// else console.debug('Audioplayer componentDidMount', { notLoadedOnClient: !isLoadedOnClient, client: __CLIENT__ }); + } + + return false; } componentWillUnmount() { debug('component:Audioplayer', 'componentWillUnmount'); - //console.log('Audioplayer componentWillUnmount'); - this.stop(); + + return this.stop(); + } + + getCurrent() { + const { currentAyah, ayahIds } = this.props; + const index = ayahIds.findIndex(id => id === currentAyah); + + return ayahIds[index]; + } + + getPrevious() { + // TODO BUGFIX, we should be able to go to the previous ayah even when + // we started from within a range + // i.e. lazyloading upwards; as this is defined, if you go to /2/100-110 + // then you can't go to 99 from + // the previous button + const { currentAyah, ayahIds } = this.props; + const index = ayahIds.findIndex(id => id === currentAyah) - 1; + return ayahIds[index]; } - onPreviousAyah() { + getNext() { + const { currentAyah, ayahIds, onLoadAyahs } = this.props; + const index = ayahIds.findIndex(id => id === currentAyah) + 1; + + if ((ayahIds.length - 3) <= index) { + onLoadAyahs(); // this doesnt look right, should probably be returned or promise.then? + } + + return ayahIds[index]; + } + + handlePreviousAyah = () => { const { setCurrentAyah, isStarted, shouldScroll } = this.props; // eslint-disable-line no-shadow const prevAyah = this.getPrevious(); if (prevAyah) { - const ayahNum = prevAyah.replace( /^\d+:/, '' ); + const ayahNum = prevAyah.replace(/^\d+:/, ''); setCurrentAyah(prevAyah); if (shouldScroll) { - scroller.scrollTo('ayah:'+ ayahNum, -150); + scroller.scrollTo(`ayah:${ayahNum}`, -150); } - if (isStarted) - this.props.files[prevAyah].play(); - } - } + if (isStarted) return this.props.files[prevAyah].play(); - scrollTo(name, offset = 0) { - const node = document.getElementsByName(name)[0]; - - if (!node) { - return; + return false; } - const nodeRect = node.getBoundingClientRect(); - const bodyRect = document.body.getBoundingClientRect(); - const scrollOffset = nodeRect.top - bodyRect.top; - - window.scrollTo(0, scrollOffset + offset); + return false; } - onNextAyah() { + handleNextAyah = () => { const { setCurrentAyah, isStarted, shouldScroll } = this.props; // eslint-disable-line no-shadow - const file = this.props.files[this.props.currentAyah]; - const nextAyah = this.getNext(); if (!nextAyah) return this.stop(); - const ayahNum = nextAyah.replace( /^\d+:/, '' ); + const ayahNum = nextAyah.replace(/^\d+:/, ''); setCurrentAyah(nextAyah); if (shouldScroll) { - scroller.scrollTo('ayah:'+ ayahNum, -80); + scroller.scrollTo(`ayah:${ayahNum}`, -80); } this.preloadNext(); - if (isStarted) this.props.files[nextAyah].play(); - } - getCurrent() { - const { currentAyah, ayahIds } = this.props; - const index = ayahIds.findIndex(id => id === currentAyah); + if (isStarted) return this.props.files[nextAyah].play(); - return ayahIds[index]; + return false; } - getPrevious() { - // TODO BUGFIX, we should be able to go to the previous ayah even when we started from within a range - // i.e. lazyloading upwards; as this is defined, if you go to /2/100-110 then you can't go to 99 from - // the previous button - const { currentAyah, ayahIds } = this.props; - const index = ayahIds.findIndex(id => id === currentAyah) - 1; - //console.debug('getPrevious', { props: this.props, index, prevAyah: ayahIds[index], currentAyah }) - return ayahIds[index]; - } + scrollTo(name, offset = 0) { + const node = document.getElementsByName(name)[0]; - getNext() { - const { currentAyah, ayahIds, onLoadAyahs } = this.props; - const index = ayahIds.findIndex(id => id === currentAyah) + 1; + if (!node) return; - if ((ayahIds.length - 3) <= index) { - onLoadAyahs(); // this doesnt look right, should probably be returned or promise.then? - } + const nodeRect = node.getBoundingClientRect(); + const bodyRect = document.body.getBoundingClientRect(); + const scrollOffset = nodeRect.top - bodyRect.top; - //console.debug('getNext', { props: this.props, index, nextAyah: ayahIds[index], currentAyah }) - return ayahIds[index]; + window.scrollTo(0, scrollOffset + offset); } - startStopPlayer(event) { + handleStartStopPlayer = (event) => { event.preventDefault(); const { isStarted } = this.props; - if (isStarted) + if (isStarted) { return this.stop(); + } + return this.start(); } start() { - const { shouldScroll, files } = this.props; + const { shouldScroll } = this.props; const currentAyah = this.getCurrent(); - const ayahNum = currentAyah.replace( /^\d+:/, '' ); + const ayahNum = currentAyah.replace(/^\d+:/, ''); if (shouldScroll) { - scroller.scrollTo('ayah:'+ ayahNum, -150); + scroller.scrollTo(`ayah:${ayahNum}`, -150); } this.props.start(); @@ -218,7 +232,7 @@ export default class Audioplayer extends Component { preloadNext() { const { currentAyah, ayahIds, files } = this.props; const index = ayahIds.findIndex(id => id === currentAyah) + 1; - for (var i = index; i <= index + 2; i++) { + for (let i = index; i <= index + 2; i++) { if (ayahIds[i]) { const ayahKey = ayahIds[i]; if (files[ayahKey]) { @@ -228,25 +242,19 @@ export default class Audioplayer extends Component { } } - toggleRepeat(event) { - event.preventDefault(); - - this.props.toggleRepeat(); - } - - toggleScroll(event) { + handleToggleScroll = (event) => { event.preventDefault(); const { shouldScroll } = this.props; const currentAyah = this.getCurrent(); - const ayahNum = currentAyah.replace( /^\d+:/, '' ); + const ayahNum = currentAyah.replace(/^\d+:/, ''); if (!shouldScroll) { // we use the inverse (!) here because we're toggling, so false is true - const elem = document.getElementsByName('ayah:'+ ayahNum)[0]; + const elem = document.getElementsByName(`ayah:${ayahNum}`)[0]; if (elem && elem.getBoundingClientRect().top < 0) { // if the ayah is above our scroll offset - scroller.scrollTo('ayah:'+ ayahNum, -150); + scroller.scrollTo(`ayah:${ayahNum}`, -150); } else { - scroller.scrollTo('ayah:'+ ayahNum, -80); + scroller.scrollTo(`ayah:${ayahNum}`, -80); } } @@ -277,7 +285,7 @@ export default class Audioplayer extends Component { } return ( - + {icon} ); @@ -288,7 +296,10 @@ export default class Audioplayer extends Component { const index = ayahIds.findIndex(id => id === currentAyah); return ( - + ); @@ -296,15 +307,19 @@ export default class Audioplayer extends Component { renderNextButton() { return ( - + ); } renderRepeatButton() { - const { shouldRepeat } = this.props; - const tooltip = (Repeats the current ayah on end...); + const { shouldRepeat, toggleRepeat } = this.props; // eslint-disable-line no-shadow + const tooltip = ( + + Repeats the current ayah on end... + + ); return ( @@ -314,12 +329,13 @@ export default class Audioplayer extends Component { placement="right" trigger={['hover', 'focus']} > - + ); @@ -327,7 +343,11 @@ export default class Audioplayer extends Component { renderScrollButton() { const { shouldScroll } = this.props; - const tooltip = (Automatically scrolls to the currently playing ayah on transitions...); + const tooltip = ( + + Automatically scrolls to the currently playing ayah on transitions... + + ); return ( @@ -337,12 +357,13 @@ export default class Audioplayer extends Component { placement="right" trigger={['hover', 'focus']} > - + ); @@ -358,13 +379,13 @@ export default class Audioplayer extends Component { segments, currentAyah, currentWord, - setCurrentWord, + setCurrentWord, // eslint-disable-line no-shadow isStarted, shouldRepeat, isSupported, isLoadedOnClient, surah - } = this.props; // eslint-disable-line no-shadow + } = this.props; if (!isSupported) { return ( @@ -409,8 +430,8 @@ export default class Audioplayer extends Component { condition); + return conditions.some(condition => condition); + } + + handleWordClick = (event) => { + if (event.target && /^word-/.test(event.target.id)) { + // call onWordClick in Surah + this.props.onWordClick(event.target.id.match(/\d+/g).join(':')); + } + } + + handleWordFocus = (event) => { + if (event.target && /^word-/.test(event.target.id)) { + // call onWordFocus in Surah + this.props.onWordFocus(event.target.id.match(/\d+/g).join(':'), event.target); + } + } + + handlePlay() { + this.setState({ + open: false + }); } renderTranslations() { const { ayah, match } = this.props; - const array = match ? match : ayah.content || []; + const array = match || ayah.content || []; return array.map((content, index) => { const arabic = new RegExp(/[\u0600-\u06FF]/); @@ -50,7 +74,10 @@ export default class Ayah extends Component { const isArabic = arabic.test(character); return ( -
+

{content.name || content.resource.name}

@@ -60,33 +87,19 @@ export default class Ayah extends Component { }); } - onWordClick(event) { - if (event.target && /^word-/.test(event.target.id)) { - // call onWordClick in Surah - this.props.onWordClick(event.target.id.match(/\d+/g).join(':')); - } - } - - onWordFocus(event) { - if (event.target && /^word-/.test(event.target.id)) { - // call onWordFocus in Surah - this.props.onWordFocus(event.target.id.match(/\d+/g).join(':'), event.target); - } - } - renderText() { if (!this.props.ayah.words[0].code) { - return; + return false; } const { currentWord } = this.props; let position = 0; let text = this.props.ayah.words.map(word => { let id = null; - let active = word.charTypeId == CHAR_TYPE_WORD && currentWord === position ? true : false; - let className = `${word.className}${word.highlight? ' '+word.highlight : ''}${active? ' '+ styles.active : ''}`; + const active = word.charTypeId === CHAR_TYPE_WORD && currentWord === position; + const className = `${word.className} ${word.highlight && word.highlight} ${active && styles.active}`; // eslint-disable-line max-len - if (word.charTypeId == CHAR_TYPE_WORD) { + if (word.charTypeId === CHAR_TYPE_WORD) { id = `word-${word.ayahKey.replace(/:/, '-')}-${position++}`; } else { id = `${word.className}-${word.codeDec}`; // just don't include id @@ -99,28 +112,27 @@ export default class Ayah extends Component { ); } - else { - return ( - - ); - } + + return ( + + ); }); return ( @@ -130,37 +142,33 @@ export default class Ayah extends Component { ); } - goToAyah(ayah, e) { - e.preventDefault(); - - this.setState({ - open: false - }); - } - - handleCopy = () => { - const { ayah } = this.props; - - CopyToClipboard(ayah.textTashkeel); - } - renderPlayLink() { - if (!this.props.isSearch) { - - Play - + const { isSearch, ayah } = this.props; + + if (!isSearch) { + return ( + this.handlePlay(ayah.ayahNum)} + className="text-muted" + > + Play + + ); } + + return false; } renderCopyLink() { - const { isSearch, ayah: { textTashkeel } } = this.props; + const { isSearched, ayah: { textTashkeel } } = this.props; - if (!isSearch) { + if (!isSearched) { return ( ); } + + return false; } renderAyahBadge() { @@ -177,7 +185,8 @@ export default class Ayah extends Component { return ( + data-metrics-event-name="Ayah:Searched:Link" + > {content} ); @@ -186,10 +195,6 @@ export default class Ayah extends Component { return content; } - shareDialog(href) { - window.open(href, '', 'menubar=no,toolbar=no,resizable=yes,scrollbars=yes,height=300,width=600') - } - renderControls() { return (
@@ -202,7 +207,7 @@ export default class Ayah extends Component { render() { const { ayah } = this.props; - debug(`component:Ayah`, `Render ${this.props.ayah.ayahNum}`); + debug('component:Ayah', `Render ${this.props.ayah.ayahNum}`); return ( @@ -215,4 +220,3 @@ export default class Ayah extends Component { ); } } - diff --git a/src/components/Bismillah/index.js b/src/components/Bismillah/index.js index 2488eeb0d..a1e39aa31 100644 --- a/src/components/Bismillah/index.js +++ b/src/components/Bismillah/index.js @@ -5,7 +5,8 @@ const Bismillah = ({ surah }) => { return (
+ style={{textAlign: 'center'}} + > ﭑﭒﭓ
); diff --git a/src/components/ContentDropdown/index.js b/src/components/ContentDropdown/index.js index 84b6a59e6..01b1822fb 100644 --- a/src/components/ContentDropdown/index.js +++ b/src/components/ContentDropdown/index.js @@ -1,6 +1,5 @@ import React, { Component, PropTypes } from 'react'; -import DropdownButton from 'react-bootstrap/lib/DropdownButton'; import MenuItem from 'react-bootstrap/lib/MenuItem'; const style = require('./style.scss'); @@ -472,7 +471,7 @@ export default class ContentDropdown extends Component { type="checkbox" className={style.checkbox} id={slug.id + slug.language} - onChange={this.handleOptionSelected.bind(this, slug.id)} + onChange={() => this.handleOptionSelected(slug.id)} checked={checked} /> @@ -502,23 +501,24 @@ export default class ContentDropdown extends Component { return (
    { content.length && - Remove all + Remove all } English {this.renderEnglishList()} - + Other Languages {this.renderLanguagesList()}
diff --git a/src/components/Copy/index.js b/src/components/Copy/index.js index 78a5dbed0..ab407ccfa 100644 --- a/src/components/Copy/index.js +++ b/src/components/Copy/index.js @@ -1,5 +1,5 @@ import React, { Component, PropTypes } from 'react'; -import CopyToClipboard from 'copy-to-clipboard'; +import copyToClipboard from 'copy-to-clipboard'; export default class Copy extends Component { static propTypes = { @@ -11,7 +11,7 @@ export default class Copy extends Component { }; handleCopy = () => { - CopyToClipboard(this.props.text); + copyToClipboard(this.props.text); this.setState({isCopied: true}); setTimeout(() => this.setState({isCopied: false}), 1000); @@ -24,7 +24,8 @@ export default class Copy extends Component { + data-metrics-event-name="Ayah:Copy" + > {isCopied ? 'Copied!' : 'Copy'} ); diff --git a/src/components/FontSizeDropdown/index.js b/src/components/FontSizeDropdown/index.js index ae09bd165..a17fc75a4 100644 --- a/src/components/FontSizeDropdown/index.js +++ b/src/components/FontSizeDropdown/index.js @@ -1,8 +1,8 @@ import React, { Component, PropTypes } from 'react'; -import OverlayTrigger from 'react-bootstrap/lib/OverlayTrigger' -import Popover from 'react-bootstrap/lib/Popover' -import Row from 'react-bootstrap/lib/Row' +import OverlayTrigger from 'react-bootstrap/lib/OverlayTrigger'; +import Popover from 'react-bootstrap/lib/Popover'; +import Row from 'react-bootstrap/lib/Row'; import Col from 'react-bootstrap/lib/Col'; const style = require('./style.scss'); @@ -29,14 +29,11 @@ export default class FontSizeDropdown extends Component { } renderPopup() { - const incrementValueArabic = 5; - const incrementValueTranslation = 2; - return ( - + this.handleOptionSelected('arabic', -1)} className="pointer"> @@ -44,15 +41,15 @@ export default class FontSizeDropdown extends Component { Arabic - - + this.handleOptionSelected('arabic', 1)} className="pointer"> + -
+
- + this.handleOptionSelected('translation', -1)} className="pointer"> @@ -60,8 +57,8 @@ export default class FontSizeDropdown extends Component { Translations - - + this.handleOptionSelected('translation', 1)} className="pointer"> + @@ -75,7 +72,8 @@ export default class FontSizeDropdown extends Component { + data-metrics-event-name="FontSizeDropdown" + > Font size diff --git a/src/components/FontStyles/index.js b/src/components/FontStyles/index.js index 96431ac24..014af1425 100644 --- a/src/components/FontStyles/index.js +++ b/src/components/FontStyles/index.js @@ -3,7 +3,7 @@ import { connect } from 'react-redux'; const bismillah = `@font-face {font-family: 'bismillah'; src: url('http://quran-1f14.kxcdn.com/fonts/ttf/bismillah.ttf') format('truetype')} - .bismillah{font-family: 'bismillah'; font-size: 36px !important; color: #000; padding: 25px 0px;}`; + .bismillah{font-family: 'bismillah'; font-size: 36px !important; color: #000; padding: 25px 0px;}`; // eslint-disable-line class FontStyle extends Component { static propTypes = { @@ -40,7 +40,9 @@ export default class FontStyles extends Component { render() { return (
- {this.props.fontFaces.map((fontFace, index) => )} + {this.props.fontFaces.map((fontFace, index) => ( + + ))}
); } diff --git a/src/components/IndexHeader/Nav/index.js b/src/components/IndexHeader/Nav/index.js index 8ef135df5..04d16b78c 100644 --- a/src/components/IndexHeader/Nav/index.js +++ b/src/components/IndexHeader/Nav/index.js @@ -1,14 +1,14 @@ -import React from 'react'; +import React, { Component, PropTypes } from 'react'; import Link from 'react-router/lib/Link'; -class IndexHeaderNav extends React.Component { - constructor() { - super(); +class IndexHeaderNav extends Component { + static propTypes = { + navlink: PropTypes.bool + }; - this.state = { - open: false - }; - } + state = { + open: false + }; openNav(e) { e.preventDefault(); @@ -33,25 +33,24 @@ class IndexHeaderNav extends React.Component { ); } - else { - return ( - - ); - } + + return ( + + ); } render() { diff --git a/src/components/IndexHeader/index.js b/src/components/IndexHeader/index.js index ace75ac17..37f7fbfa2 100644 --- a/src/components/IndexHeader/index.js +++ b/src/components/IndexHeader/index.js @@ -10,7 +10,8 @@ const logo = require('../../../static/images/logo-lg-w.png'); export default class IndexHeader extends Component { static propTypes = { - noSearch: PropTypes.bool + noSearch: PropTypes.bool, + navlink: PropTypes.any }; renderSearch() { @@ -33,7 +34,7 @@ export default class IndexHeader extends Component {
- + logo

THE NOBLE QUR'AN

{this.renderSearch()} diff --git a/src/components/InformationToggle/index.js b/src/components/InformationToggle/index.js index 941b3bd63..d073e0633 100644 --- a/src/components/InformationToggle/index.js +++ b/src/components/InformationToggle/index.js @@ -1,6 +1,11 @@ -import React, { Component } from 'react'; +import React, { Component, PropTypes } from 'react'; export default class InformationToggle extends Component { + static propTypes = { + isShowingSurahInfo: PropTypes.bool.isRequired, + onToggle: PropTypes.func.isRequired + }; + toggleInformationMode = (event) => { const { isShowingSurahInfo } = this.props; @@ -13,9 +18,11 @@ export default class InformationToggle extends Component { const { isShowingSurahInfo } = this.props; return ( - + onClick={this.toggleInformationMode} + > Surah Info ); diff --git a/src/components/LazyLoad/index.js b/src/components/LazyLoad/index.js index a2a804d06..43668bf4e 100644 --- a/src/components/LazyLoad/index.js +++ b/src/components/LazyLoad/index.js @@ -1,4 +1,4 @@ -import React, { Component, PropTypes } from 'react'; +import { Component, PropTypes } from 'react'; import ReactDOM from 'react-dom'; import debug from '../../helpers/debug'; @@ -28,12 +28,13 @@ export default class LazyLoad extends Component { } onScroll = () => { - const { isLoading, isEnd, offset } = this.props; + const { isLoading, isEnd, offset, onLazyLoad } = this.props; const dom = ReactDOM.findDOMNode(this); + const componentOffset = (dom.offsetParent || dom).offsetTop - (window.pageYOffset + window.innerHeight); // eslint-disable-line max-len - if ((!isLoading && !isEnd) && (dom.offsetParent || dom).offsetTop - (window.pageYOffset + window.innerHeight) < offset) { + if ((!isLoading && !isEnd) && componentOffset < offset) { debug('component:LazyLoad', 'onLazyLoad called'); - return this.props.onLazyLoad(); + return onLazyLoad(); } return false; diff --git a/src/components/Line/index.js b/src/components/Line/index.js index 8258bbc09..073cccc4d 100644 --- a/src/components/Line/index.js +++ b/src/components/Line/index.js @@ -12,10 +12,10 @@ export default class Line extends React.Component { const { line } = this.props; if (!line[0].code) { // TODO shouldn't be possible, remove this clause - return; + return false; } - let text = line.map((word, index) => { + let text = line.map(word => { if (word.translation) { let tooltip = word.translation; @@ -29,8 +29,8 @@ export default class Line extends React.Component { data-page={word.pageNum} data-position={word.position} data-placement="top" title={tooltip} - dangerouslySetInnerHTML={{__html: word.code}}> -
+ dangerouslySetInnerHTML={{__html: word.code}} + /> ); } @@ -40,8 +40,8 @@ export default class Line extends React.Component { key={`${word.pageNum}${word.lineNum}${word.position}${word.code}`} data-line={word.lineNum} data-page={word.pageNum} - dangerouslySetInnerHTML={{__html: word.code}}> - + dangerouslySetInnerHTML={{__html: word.code}} + /> ); }); @@ -55,7 +55,10 @@ export default class Line extends React.Component { render() { const { line } = this.props; - debug('component:Line', `Page: ${line[0].pageNum} - Line: ${line[0].lineNum} - Ayah: ${line[0].ayahKey}`); + debug( + 'component:Line', + `Page: ${line[0].pageNum} - Line: ${line[0].lineNum} - Ayah: ${line[0].ayahKey}` + ); return (
diff --git a/src/components/Loader/index.js b/src/components/Loader/index.js index a52d0700c..617baca42 100644 --- a/src/components/Loader/index.js +++ b/src/components/Loader/index.js @@ -6,7 +6,7 @@ const Loader = () => (
- + Loader

Loading...

diff --git a/src/components/PageBreak/index.js b/src/components/PageBreak/index.js index 31b2fd114..4e1636a03 100644 --- a/src/components/PageBreak/index.js +++ b/src/components/PageBreak/index.js @@ -11,4 +11,8 @@ const PageBreak = ({ pageNum }) => ( ); +PageBreak.propTypes = { + pageNum: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired +}; + export default PageBreak; diff --git a/src/components/ReadingModeToggle/index.js b/src/components/ReadingModeToggle/index.js index 2afd2c6d7..4e650819a 100644 --- a/src/components/ReadingModeToggle/index.js +++ b/src/components/ReadingModeToggle/index.js @@ -1,4 +1,4 @@ -import React, { Component, PropTypes } from 'react'; +import React, { PropTypes } from 'react'; import SwitchToggle from '../SwitchToggle'; @@ -12,6 +12,6 @@ const ReadingModeToggle = ({ onReadingModeToggle, isToggled }) => ( ReadingModeToggle.propTypes = { onReadingModeToggle: PropTypes.func.isRequired, isToggled: PropTypes.bool.isRequired -} +}; export default ReadingModeToggle; diff --git a/src/components/ReciterDropdown/index.js b/src/components/ReciterDropdown/index.js index 0ce04cd86..e6c697cd0 100644 --- a/src/components/ReciterDropdown/index.js +++ b/src/components/ReciterDropdown/index.js @@ -1,5 +1,4 @@ import React, { Component, PropTypes } from 'react'; -import DropdownButton from 'react-bootstrap/lib/DropdownButton'; import MenuItem from 'react-bootstrap/lib/MenuItem'; const style = require('./style.scss'); @@ -191,7 +190,8 @@ export const slugs = [ export default class ReciterDropdown extends Component { static propTypes = { onOptionChange: PropTypes.func, - options: PropTypes.object + options: PropTypes.object, + className: PropTypes.string }; static defaultProps = { @@ -209,7 +209,8 @@ export default class ReciterDropdown extends Component { + onClick={() => onOptionChange({audio: slug.id})} + > {slug.name.english} )); @@ -221,14 +222,15 @@ export default class ReciterDropdown extends Component { return (

© QURAN.COM. ALL RIGHTS RESERVED 2016

-

Quran.com (also known as The Noble Quran, Al Quran, Holy Quran, Koran) is a pro bono project.

+

+ Quran.com (also known as The Noble Quran, Al Quran, Holy Quran, Koran){' '} + is a pro bono project. +

  • Sitemap
  • - {Object.values(surahs).filter(surah => [2, 3, 18, 19, 55, 56, 67, 112].indexOf(surah.id)).map(surah => ( -
  • - - {`Surah ${surah.name.simple} (سورة ${surah.name.arabic})`} - -
  • - ))} + {Object.values(surahs) + .filter(surah => [2, 3, 18, 19, 55, 56, 67, 112].indexOf(surah.id)) + .map(surah => ( +
  • + + {`Surah ${surah.name.simple} (سورة ${surah.name.arabic})`} + +
  • + )) + }
  • + data-metrics-surah-id="36" + > Surah Yasin, Yaseen (يس)
  • @@ -91,7 +107,8 @@ export default class App extends Component { + data-metrics-surah-id="2/255" + > Ayat Al-Kursi (آية الكرسي) @@ -103,4 +120,4 @@ export default class App extends Component {
); } -}; +} diff --git a/src/containers/Contact/index.js b/src/containers/Contact/index.js index 0d1cb4586..dcdc4a77c 100644 --- a/src/containers/Contact/index.js +++ b/src/containers/Contact/index.js @@ -9,8 +9,8 @@ export default class Contact extends Component { success: false }; - submitSupport(e) { - e.preventDefault(); + submitSupport = (event) => { + event.preventDefault(); const form = { subject: ReactDOM.findDOMNode(this.refs.purpose).value.trim(), @@ -33,7 +33,7 @@ export default class Contact extends Component { renderForm() { return ( -
+
@@ -86,28 +86,29 @@ export default class Contact extends Component { render() { let body; + if (this.state.success) { body = this.renderSubmitSuccess(); - } - else { + } else { body = this.renderForm(); } return (
- +

- Contacting us - thank you for taking time to speak to us. Please be as concise as possible + Contacting us - thank you for taking time to speak to us. + Please be as concise as possible and include screenshots where applicable to help us help you as quickly as we can. -
-
+
+

- {body} + {body}
diff --git a/src/containers/DevTools/index.js b/src/containers/DevTools/index.js index d097a84ec..1415aa787 100644 --- a/src/containers/DevTools/index.js +++ b/src/containers/DevTools/index.js @@ -4,8 +4,10 @@ import LogMonitor from 'redux-devtools-log-monitor'; import DockMonitor from 'redux-devtools-dock-monitor'; export default createDevTools( - + ); diff --git a/src/containers/Donations/index.js b/src/containers/Donations/index.js index 041cbd96c..6560e7b47 100644 --- a/src/containers/Donations/index.js +++ b/src/containers/Donations/index.js @@ -1,54 +1,70 @@ -import React, { Component } from 'react'; +import React from 'react'; import IndexHeader from '../../components/IndexHeader'; -export default class Donations extends Component { - render() { - return ( -
- -
-
-
-

Quran.com

-
-
-

Who we are.

-

- Since 2008, Alhamdulilah Quran.com now serves over 3.1 million - visits from all corners of the world - and we continue to grow everyday. -

- This, with the blessing of Allah, is powered by merely 5 volunteers who are working hard on their spare time to keep this project as - beneficial and useful to people all around the world. -

-

How you can help.

-

- Quran.com has an incredible amount of potential from a product standpoint and a team standpoint. - We need to continue innovating the product and the experience to serve you better. To do so we - have overhead costs which include: -

    -
  • Server costs
  • -
  • Data analytics and metrics tools to best learn about your needs (Optimizely, Heap, Keen, etc.)
  • -
  • Design help (we are striving to follow our beautiful religion by making beautiful products)
  • -
-

-

Make a difference.

-

- Making a difference for Quran.com is as simple as a Tweet, Facebook share or email us feedback. - For those looking to make a stronger impact, support us by contributing any monetary amount. -

-
+import Grid from 'react-bootstrap/lib/Grid'; +import Row from 'react-bootstrap/lib/Row'; +import Col from 'react-bootstrap/lib/Col'; -
-

- - Support Quran.com - -

- If you have any questions, please don't hesitate to contact us at support@quran.zendesk.com. -
-
-
-
- ); - } -} +export default () => ( +
+ + + + +

Quran.com

+ + +

Who we are.

+

+ Since 2008, Alhamdulilah Quran.com now serves over 3.1 million + visits from all corners of the world - and we continue to grow everyday. +

+ This, with the blessing of Allah, is powered by merely 5 volunteers who are + working hard on their spare time to keep this project as + beneficial and useful to people all around the world. +

+

How you can help.

+

+ Quran.com has an incredible amount of potential from a product standpoint + and a team standpoint. + We need to continue innovating the product and the experience to serve you + better. To do so we + have overhead costs which include: +

    +
  • Server costs
  • +
  • + Data analytics and metrics tools to best learn about your + needs (Optimizely, Heap, Keen, etc.) +
  • +
  • + Design help (we are striving to follow our beautiful religion + by making beautiful products) +
  • +
+

+

Make a difference.

+

+ Making a difference for Quran.com is as simple as a Tweet, + Facebook share or email us feedback. + For those looking to make a stronger impact, support us by + contributing any monetary amount. +

+ + + +

+ + Support Quran.com + +

+ If you have any questions, please don't hesitate to contact + us at support@quran.zendesk.com. + +
+
+
+); diff --git a/src/containers/Error/index.js b/src/containers/Error/index.js index 73678dd1e..6c15cd412 100644 --- a/src/containers/Error/index.js +++ b/src/containers/Error/index.js @@ -1,26 +1,31 @@ -import React, { Component } from 'react'; +import React, { PropTypes } from 'react'; import IndexHeader from '../../components/IndexHeader'; -import Link from 'react-router/lib/Link'; import Helmet from 'react-helmet'; -export default ({params}) => { - const error = { - 'invalid-surah': 'Surah is out of range', - 'invalid-ayah-range': 'Ayah(s) selected are out of range' - }; - return ( -
- - -
-
-
-

- {error[params.errorKey]}. Please go to the home page and select a Surah/Ayah -

-
-
+const error = { + 'invalid-surah': 'Surah is out of range', + 'invalid-ayah-range': 'Ayah(s) selected are out of range' +}; + +const ErrorPage = ({ params }) => ( +
+ + +
+
+
+

+ {error[params.errorKey]}. Please go to the + home page and select a Surah/Ayah +

- ); -} +
+
+); + +ErrorPage.propTypes = { + params: PropTypes.string.isRequired +}; + +export default ErrorPage; diff --git a/src/containers/Home/index.js b/src/containers/Home/index.js index 78aa98da2..99e13bbed 100644 --- a/src/containers/Home/index.js +++ b/src/containers/Home/index.js @@ -1,9 +1,8 @@ -import React from 'react'; +import React, { PropTypes, Component } from 'react'; import Helmet from 'react-helmet'; import IndexHeader from '../../components/IndexHeader'; import Link from 'react-router/lib/Link'; -import LinkContainer from 'react-router-bootstrap/lib/LinkContainer'; -import { asyncConnect } from 'redux-connect' +import { asyncConnect } from 'redux-connect'; import { connect } from 'react-redux'; import debug from '../../helpers/debug'; @@ -24,40 +23,43 @@ const styles = require('./style.scss'); @connect( state => ({surahs: state.surahs.entities}) ) -class Home extends React.Component { +class Home extends Component { + static propTypes = { + lastVisit: PropTypes.any, + surahs: PropTypes.object.isRequired + }; + renderColumn(array) { debug('component:Index', 'renderColumn'); - return array.map((surah, i) => { - return ( -
  • - -
    - {surah.id} -
    -
    - {surah.name.simple} -
    - {surah.name.english} -
    -
    - {surah.name.arabic} -
    - -
  • - ); - }); + return array.map(surah => ( +
  • + +
    + {surah.id} +
    +
    + {surah.name.simple} +
    + {surah.name.english} +
    +
    + {surah.name.arabic} +
    + +
  • + )); } renderLastVisit() { if (this.props.lastVisit) { - let surah = this.props.surahs[this.props.lastVisit.surah - 1]; + const surah = this.props.surahs[this.props.lastVisit.surah - 1]; if (!surah) { - return; + return false; } - const lastVisitedAyah = parseInt(this.props.lastVisit.ayah); + const lastVisitedAyah = parseInt(this.props.lastVisit.ayah, 10); return (
    @@ -67,7 +69,9 @@ class Home extends React.Component {
    • - +
      {surah.id}:{this.props.lastVisit.ayah}
      @@ -89,7 +93,7 @@ class Home extends React.Component { ); } - return null; + return false; } renderQuickLinks() { @@ -99,21 +103,23 @@ class Home extends React.Component {
    • Quick links:
    • { new Date().getDay() === 5 && -
    • - - Surah Al-Kahf - - {' '}| -
    • +
    • + + Surah Al-Kahf + + {' '}| +
    • }
    • + data-metrics-surah-id="36" + > Surah Yasin (Yaseen) {' '}|
    • @@ -121,7 +127,8 @@ class Home extends React.Component { + data-metrics-surah-id="55" + > Surah Ar-Rahman {' '}| @@ -129,7 +136,8 @@ class Home extends React.Component { + data-metrics-surah-id="67" + > Surah Al Mulk {' '}| @@ -137,7 +145,8 @@ class Home extends React.Component { + data-metrics-surah-id="2/255" + > Ayat Al-Kursi @@ -150,32 +159,32 @@ class Home extends React.Component { debug('component:Index', 'Render'); return ( -
      - - -
      -
      - {this.renderLastVisit()} -
      -

      - SURAHS (CHAPTERS) - {this.renderQuickLinks()} -

      -
      -
        - {this.renderColumn(Object.values(this.props.surahs).slice(0, 38))} -
      -
        - {this.renderColumn(Object.values(this.props.surahs).slice(38, 76))} -
      -
        - {this.renderColumn(Object.values(this.props.surahs).slice(76, 114))} -
      -
      +
      + + +
      +
      + {/* {this.renderLastVisit()} */} +
      +

      + SURAHS (CHAPTERS) + {this.renderQuickLinks()} +

      +
      +
        + {this.renderColumn(Object.values(this.props.surahs).slice(0, 38))} +
      +
        + {this.renderColumn(Object.values(this.props.surahs).slice(38, 76))} +
      +
        + {this.renderColumn(Object.values(this.props.surahs).slice(76, 114))} +
      +
      ); } } diff --git a/src/containers/Search/Header/index.js b/src/containers/Search/Header/index.js index 0c5bab873..148b68687 100644 --- a/src/containers/Search/Header/index.js +++ b/src/containers/Search/Header/index.js @@ -1,27 +1,21 @@ -import React, { Component } from 'react'; +import React from 'react'; import SearchInput from '../../../components/SearchInput'; import Link from 'react-router/lib/Link'; const logo = require('../../../../static/images/logo-lg-w.png'); -class Header extends Component { - render() { - return ( -
      -
      -
      -
      - - - -

      THE NOBLE QUR'AN

      - -
      -
      +export default () => ( +
      +
      +
      +
      + + logo + +

      THE NOBLE QUR'AN

      +
      - ); - } -} - -export default Header; +
      +
      +); diff --git a/src/containers/Search/index.js b/src/containers/Search/index.js index 192b5acf7..6706ee586 100644 --- a/src/containers/Search/index.js +++ b/src/containers/Search/index.js @@ -1,8 +1,7 @@ import React, { Component, PropTypes } from 'react'; -import { PropTypes as MetricsPropTypes } from "react-metrics"; +import { PropTypes as MetricsPropTypes } from 'react-metrics'; import { asyncConnect } from 'redux-connect'; import { connect } from 'react-redux'; -import Link from 'react-router/lib/Link'; import { push } from 'react-router-redux'; import Helmet from 'react-helmet'; import ReactPaginate from 'react-paginate'; @@ -41,7 +40,7 @@ const style = require('./style.scss'); }), { push } ) -class Search extends Component { +export default class Search extends Component { static propTypes = { isErrored: PropTypes.bool, isLoading: PropTypes.bool, @@ -61,12 +60,15 @@ class Search extends Component { metrics: MetricsPropTypes.metrics }; - handlePageChange(payload) { + handlePageChange = (payload) => { const { push, query, page } = this.props; // eslint-disable-line no-shadow const selectedPage = payload.selected + 1; if (page !== selectedPage) { - this.context.metrics.track('Search', {action: 'paginate', label: `${query} - ${selectedPage}`}); + this.context.metrics.track( + 'Search', + {action: 'paginate', label: `${query} - ${selectedPage}`} + ); return push({ pathname: '/search', @@ -78,65 +80,77 @@ class Search extends Component { } renderStatsBar() { - const { total, size, page, from, query } = this.props; - - if (total) { - const pageNum = Math.ceil(total / size); - - return ( -
      - - - - {from}-{from + size - 1} OF - {total} - SEARCH RESULTS FOR: - {query} - - - - -
      - ); - } - - return false; - } - - - - renderBody() { - const { isErrored, isLoading, total, results, ayahs } = this.props; - - if (isErrored) { - return

      Sorry, there was an error with your search.

      ; - } - - if (!total) { - return

      No results found.

      ; - } - - if (isLoading) { - return
      ; - } - - return results.map(result => ); - } + const { total, size, page, from, query } = this.props; + + if (total) { + const pageNum = Math.ceil(total / size); + + return ( +
      + + + + {from}-{from + size - 1} OF + {total} + SEARCH RESULTS FOR: + {query} + + + + +
      + ); + } + + return false; + } + + renderBody() { + const { isErrored, isLoading, total, results, ayahs } = this.props; + + if (isErrored) { + return ( +

      + Sorry, there was an error with your search. +

      + ); + } + + if (!total) { + return

      No results found.

      ; + } + + if (isLoading) { + return
      ; + } + + return results.map(result => ( + + )); + } render() { const { query, options } = this.props; @@ -144,22 +158,22 @@ class Search extends Component { return (
      -