- {surah.name.arabic}
+
+
+
+ {chapter.translatedName.name}
+
@@ -32,7 +35,7 @@ const SurahsList = (props) => {
};
SurahsList.propTypes = {
- surahs: PropTypes.arrayOf(surahType).isRequired
+ chapters: customPropTypes.chapters.isRequired
};
export default SurahsList;
diff --git a/src/components/Home/SurahsList/style.scss b/src/components/Home/SurahsList/style.scss
index 08aedee52..e0e170ee4 100644
--- a/src/components/Home/SurahsList/style.scss
+++ b/src/components/Home/SurahsList/style.scss
@@ -12,7 +12,7 @@
padding: 10px 10px;
}
- .english {
+ .translated_name {
font-size: 10px;
}
.arabic {
diff --git a/src/components/IndexHeader/index.js b/src/components/IndexHeader/index.js
index adbb0af15..90d01ba1b 100644
--- a/src/components/IndexHeader/index.js
+++ b/src/components/IndexHeader/index.js
@@ -1,15 +1,13 @@
import React, { Component, PropTypes } from 'react';
import Link from 'react-router/lib/Link';
-
import SearchInput from 'components/SearchInput';
import debug from 'helpers/debug';
+import Jumbotron from 'quran-components/lib/Jumbotron';
const logo = require('../../../static/images/logo-lg-w.png');
+const styles = require('./style.scss');
-export default class IndexHeader extends Component {
- static propTypes = {
- noSearch: PropTypes.bool
- };
+class IndexHeader extends Component {
renderSearch() {
if (this.props.noSearch) {
@@ -25,19 +23,26 @@ export default class IndexHeader extends Component {
debug('component:IndexHeader', 'Render');
return (
-
+
-
-
+
+
-
THE NOBLE QUR'AN
+
THE NOBLE QUR'AN
{this.renderSearch()}
-
+
+
);
}
}
+
+IndexHeader.propTypes = {
+ noSearch: PropTypes.bool
+};
+
+export default IndexHeader;
diff --git a/src/components/IndexHeader/style.scss b/src/components/IndexHeader/style.scss
new file mode 100644
index 000000000..2211ac9df
--- /dev/null
+++ b/src/components/IndexHeader/style.scss
@@ -0,0 +1,18 @@
+@import '../../styles/variables.scss';
+
+.link{
+ display: inline-block;
+ width: 30%;
+ margin-top: 35px;
+}
+.logo{
+ padding-top: 10px;
+ padding-bottom: 10px;
+ height: auto;
+ width: 100%;
+}
+.title{
+ color: lighten($brand-primary, 30%);
+ font-size: 160%;
+ padding-bottom: 3.5%;
+}
diff --git a/src/components/InformationToggle/index.js b/src/components/InformationToggle/index.js
index 929b5cb30..74438fa9d 100644
--- a/src/components/InformationToggle/index.js
+++ b/src/components/InformationToggle/index.js
@@ -1,19 +1,14 @@
import React, { PropTypes } from 'react';
import LocaleFormattedMessage from 'components/LocaleFormattedMessage';
+import { MenuItem } from 'quran-components/lib/Menu';
const InformationToggle = ({ isToggled, onToggle }) => (
-
- onToggle({ isShowingSurahInfo: !isToggled })}
- >
-
- {' '}
-
-
+
}
+ onClick={() => onToggle({ isShowingSurahInfo: !isToggled })}
+ >
+
+
);
InformationToggle.propTypes = {
diff --git a/src/components/LazyLoad/index.js b/src/components/LazyLoad/index.js
index 84f52cb74..028c0d55c 100644
--- a/src/components/LazyLoad/index.js
+++ b/src/components/LazyLoad/index.js
@@ -4,21 +4,7 @@ import ReactDOM from 'react-dom';
import debug from 'helpers/debug';
-export default class LazyLoad extends Component {
- static propTypes = {
- isLoading: PropTypes.bool.isRequired,
- isEnd: PropTypes.bool.isRequired,
- onLazyLoad: PropTypes.func.isRequired,
- loadingComponent: PropTypes.element,
- endComponent: PropTypes.element,
- offset: PropTypes.number
- }
-
- static defaultProps = {
- loadingComponent: 'Loading...',
- endComponent: 'End.',
- offset: 1000
- }
+class LazyLoad extends Component {
componentDidMount() {
if (__CLIENT__) {
@@ -57,3 +43,20 @@ export default class LazyLoad extends Component {
return loadingComponent;
}
}
+
+LazyLoad.propTypes = {
+ isLoading: PropTypes.bool.isRequired,
+ isEnd: PropTypes.bool.isRequired,
+ onLazyLoad: PropTypes.func.isRequired,
+ loadingComponent: PropTypes.element,
+ endComponent: PropTypes.element,
+ offset: PropTypes.number
+};
+
+LazyLoad.defaultProps = {
+ loadingComponent: 'Loading...',
+ endComponent: 'End.',
+ offset: 1000
+};
+
+export default LazyLoad;
diff --git a/src/components/Line/index.js b/src/components/Line/index.js
index 585354fe1..a7a529e77 100644
--- a/src/components/Line/index.js
+++ b/src/components/Line/index.js
@@ -1,29 +1,16 @@
import React, { PropTypes } from 'react';
+import * as customPropTypes from 'customPropTypes';
import debug from 'helpers/debug';
-
-import { wordType } from 'types';
import Word from 'components/Word';
-const styles = require('../Ayah/style.scss');
+const styles = require('../Verse/style.scss');
-export default class Line extends React.Component {
- static propTypes = {
- line: PropTypes.arrayOf(wordType).isRequired,
- tooltip: PropTypes.string,
- currentAyah: PropTypes.string.isRequired,
- audioActions: PropTypes.shape({
- pause: PropTypes.func.isRequired,
- setAyah: PropTypes.func.isRequired,
- play: PropTypes.func.isRequired,
- setCurrentWord: PropTypes.func.isRequired,
- }),
- isPlaying: PropTypes.bool
- };
+class Line extends React.Component {
// NOTE: this is commented out as it caused problems with 55:31 with missing text.
// shouldComponentUpdate(nextProps) {
// const conditions = [
- // this.props.currentAyah !== nextProps.currentAyah,
+ // this.props.currentVerse !== nextProps.currentVerse,
// this.props.line !== nextProps.line,
// this.props.isPlaying !== nextProps.isPlaying
// ];
@@ -34,16 +21,17 @@ export default class Line extends React.Component {
// }
renderText() {
- const { tooltip, currentAyah, audioActions, isPlaying, line } = this.props;
+ const { tooltip, currentVerse, audioActions, isPlaying, line, useTextFont } = this.props;
const text = line.map(word => (
));
@@ -59,15 +47,26 @@ export default class Line extends React.Component {
debug(
'component:Line',
- `Page: ${line[0].pageNum} - Line: ${line[0].lineNum} - Ayah: ${line[0].ayahKey}`
+ `Page: ${line[0].pageNum} - Line: ${line[0].lineNum} - Ayah: ${line[0].verseKey}`
);
return (
-
);
}
}
+
+Line.propTypes = {
+ line: customPropTypes.line.isRequired,
+ tooltip: PropTypes.string,
+ currentVerse: PropTypes.string.isRequired,
+ audioActions: customPropTypes.audioActions,
+ isPlaying: PropTypes.bool,
+ useTextFont: PropTypes.bool
+};
+
+export default Line;
diff --git a/src/components/Loader/index.js b/src/components/Loader/index.js
deleted file mode 100644
index 57f48b889..000000000
--- a/src/components/Loader/index.js
+++ /dev/null
@@ -1,9 +0,0 @@
-import React from 'react';
-
-const Loader = () => (
-
-
-
-);
-
-export default Loader;
diff --git a/src/components/Loader/style.scss b/src/components/Loader/style.scss
deleted file mode 100644
index 80fded77d..000000000
--- a/src/components/Loader/style.scss
+++ /dev/null
@@ -1,49 +0,0 @@
-@import '../../styles/variables.scss';
-
-$timing: 1s;
-$size: 4px;
-$border-size: $size * 1.25;
-
-.loader,
-.loader:after {
- border-radius: 50%;
- width: 10em;
- height: 10em;
-}
-
-.loader {
- margin: 30px auto;
- font-size: $size;
- position: relative;
- text-indent: -9999em;
- border-top: $border-size solid $brand-primary;
- border-right: $border-size solid $brand-primary;
- border-bottom: $border-size solid $brand-primary;
- border-left: $border-size solid #ffffff;
- -webkit-transform: translateZ(0);
- -ms-transform: translateZ(0);
- transform: translateZ(0);
- -webkit-animation: load $timing infinite linear;
- animation: load $timing infinite linear;
-}
-
-@-webkit-keyframes load {
- 0% {
- -webkit-transform: rotate(0deg);
- transform: rotate(0deg);
- }
- 100% {
- -webkit-transform: rotate(360deg);
- transform: rotate(360deg);
- }
-}
-@keyframes load {
- 0% {
- -webkit-transform: rotate(0deg);
- transform: rotate(0deg);
- }
- 100% {
- -webkit-transform: rotate(360deg);
- transform: rotate(360deg);
- }
-}
diff --git a/src/components/LocaleSwitcher/index.js b/src/components/LocaleSwitcher/index.js
index 12ce4f84e..863dd4118 100644
--- a/src/components/LocaleSwitcher/index.js
+++ b/src/components/LocaleSwitcher/index.js
@@ -3,12 +3,11 @@ import React, { PropTypes, Component } from 'react';
import cookie from 'react-cookie';
import NavDropdown from 'react-bootstrap/lib/NavDropdown';
import MenuItem from 'react-bootstrap/lib/MenuItem';
-import { locales, defaultLocale } from '../../config';
+import config from '../../config';
-export default class LocaleSwitcher extends Component {
- static propTypes = {
- className: PropTypes.string
- };
+const { locales, defaultLocale } = config;
+
+class LocaleSwitcher extends Component {
state = {
currentLocale: defaultLocale,
@@ -43,7 +42,7 @@ export default class LocaleSwitcher extends Component {
);
}
}
+NightModeToggle.propTypes = {
+ isNightMode: PropTypes.bool.isRequired,
+ onToggle: PropTypes.func.isRequired
+};
+
export default NightModeToggle;
diff --git a/src/components/PageBreak/index.js b/src/components/PageBreak/index.js
index 209293af2..634dde62f 100644
--- a/src/components/PageBreak/index.js
+++ b/src/components/PageBreak/index.js
@@ -1,12 +1,11 @@
import React, { PropTypes } from 'react';
-import Col from 'react-bootstrap/lib/Col';
const PageBreak = ({ pageNum }) => (
);
diff --git a/src/components/PageView/index.js b/src/components/PageView/index.js
new file mode 100644
index 000000000..64e679c70
--- /dev/null
+++ b/src/components/PageView/index.js
@@ -0,0 +1,61 @@
+import React, { PropTypes } from 'react';
+import * as customPropTypes from 'customPropTypes';
+import { connect } from 'react-redux';
+
+import Line from 'components/Line';
+import PageBreak from 'components/PageBreak';
+
+const PageView = ({ lines, keys, currentVerse, options, isPlaying, audioActions, userAgent }) => { // eslint-disable-line
+ const elements = keys.map((lineNum, index) => {
+ const nextNum = keys[index + 1];
+ const pageNum = lineNum.split('-')[0];
+ const line = lines[lineNum];
+ const renderText = false; // userAgent.isBot;
+
+ if (index + 1 !== keys.length && pageNum !== nextNum.split('-')[0]) {
+ return [
+
,
+
+ ];
+ }
+
+ return (
+
+ );
+ });
+
+ return (
+
{elements}
+ );
+};
+
+PageView.propTypes = {
+ keys: PropTypes.array, // eslint-disable-line
+ lines: PropTypes.object.isRequired, // eslint-disable-line
+ audioActions: customPropTypes.audioActions.isRequired, // eslint-disable-line
+ currentVerse: PropTypes.string,
+ bookmarks: PropTypes.object.isRequired, // eslint-disable-line
+ options: PropTypes.object.isRequired, // eslint-disable-line
+ isPlaying: PropTypes.bool,
+ userAgent: PropTypes.func
+};
+
+export default connect(state => ({
+ userAgent: state.options.userAgent
+}))(PageView);
diff --git a/src/components/Radio/index.js b/src/components/Radio/index.js
deleted file mode 100644
index 1d94fb551..000000000
--- a/src/components/Radio/index.js
+++ /dev/null
@@ -1,30 +0,0 @@
-import React, { PropTypes } from 'react';
-
-const styles = require('./style.scss');
-
-const Radio = ({ id, name, checked, handleChange, children }) => (
-
-);
-
-Radio.propTypes = {
- id: PropTypes.string.isRequired,
- name: PropTypes.string.isRequired,
- checked: PropTypes.bool.isRequired,
- handleChange: PropTypes.func.isRequired,
- children: PropTypes.element.isRequired,
-};
-
-export default Radio;
diff --git a/src/components/Radio/style.scss b/src/components/Radio/style.scss
deleted file mode 100644
index a00b1fdee..000000000
--- a/src/components/Radio/style.scss
+++ /dev/null
@@ -1,130 +0,0 @@
-@import '../../styles/variables.scss';
-
-@-webkit-keyframes cardEnter {
- 0%, 20%, 40%, 60%, 80%, 100% {
- -webkit-transition-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1);
- transition-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1);
- }
- 0% {
- opacity: 0;
- -webkit-transform: scale3d(0.3, 0.3, 0.3);
- }
- 20% {
- -webkit-transform: scale3d(1.1, 1.1, 1.1);
- }
- 40% {
- -webkit-transform: scale3d(0.9, 0.9, 0.9);
- }
- 60% {
- opacity: 1;
- -webkit-transform: scale3d(1.03, 1.03, 1.03);
- }
- 80% {
- -webkit-transform: scale3d(0.97, 0.97, 0.97);
- }
- 100% {
- opacity: 1;
- -webkit-transform: scale3d(1, 1, 1);
- }
-}
-
-@keyframes cardEnter {
- 0%, 20%, 40%, 60%, 80%, 100% {
- -webkit-transition-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1);
- transition-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1);
- }
- 0% {
- opacity: 0;
- -webkit-transform: scale3d(0.3, 0.3, 0.3);
- transform: scale3d(0.3, 0.3, 0.3);
- }
- 20% {
- -webkit-transform: scale3d(1.1, 1.1, 1.1);
- transform: scale3d(1.1, 1.1, 1.1);
- }
- 40% {
- -webkit-transform: scale3d(0.9, 0.9, 0.9);
- transform: scale3d(0.9, 0.9, 0.9);
- }
- 60% {
- opacity: 1;
- -webkit-transform: scale3d(1.03, 1.03, 1.03);
- transform: scale3d(1.03, 1.03, 1.03);
- }
- 80% {
- -webkit-transform: scale3d(0.97, 0.97, 0.97);
- transform: scale3d(0.97, 0.97, 0.97);
- }
- 100% {
- opacity: 1;
- -webkit-transform: scale3d(1, 1, 1);
- transform: scale3d(1, 1, 1);
- }
-}
-
-.radio {
- display: inline-block;
- padding-right: 20px;
- font-weight: 500;
- color: #777;
- line-height: 40px;
- cursor: pointer;
-}
-
-.radio:hover .inner {
- -webkit-transform: scale(0.5);
- -ms-transform: scale(0.5);
- transform: scale(0.5);
- opacity: .5;
-}
-
-.radio .input {
- width: 1px;
- height: 1px;
- opacity: 0;
-}
-
-.radio .input:checked + .outer .inner {
- -webkit-transform: scale(1);
- -ms-transform: scale(1);
- transform: scale(1);
- opacity: 1;
-}
-
-.radio .input:checked + .outer {
- border: 2px solid $brand-primary;
-}
-
-.radio .input:focus + .outer .inner {
- -webkit-transform: scale(1);
- -ms-transform: scale(1);
- transform: scale(1);
- opacity: 1;
- background-color: $brand-primary;
-}
-
-.radio .outer {
- width: 20px;
- height: 20px;
- display: block;
- float: left;
- margin: 10px 9px 10px 10px;
- border: 2px solid $brand-primary;
- border-radius: 50%;
- background-color: #fff;
-}
-
-.radio .inner {
- -webkit-transition: all 0.25s ease-in-out;
- transition: all 0.25s ease-in-out;
- width: 10px;
- height: 10px;
- -webkit-transform: scale(0);
- -ms-transform: scale(0);
- transform: scale(0);
- display: block;
- margin: 3px;
- border-radius: 50%;
- background-color: $brand-primary;
- opacity: 0;
-}
diff --git a/src/components/ReadingModeToggle/index.js b/src/components/ReadingModeToggle/index.js
index ff75185c0..76146bdaa 100644
--- a/src/components/ReadingModeToggle/index.js
+++ b/src/components/ReadingModeToggle/index.js
@@ -1,19 +1,14 @@
import React, { PropTypes } from 'react';
import LocaleFormattedMessage from 'components/LocaleFormattedMessage';
+import { MenuItem } from 'quran-components/lib/Menu';
const ReadingModeToggle = ({ onToggle, isToggled }) => (
-
- onToggle({ isReadingMode: !isToggled })}
- >
-
- {' '}
-
-
+
}
+ onClick={() => onToggle({ isReadingMode: !isToggled })}
+ >
+
+
);
ReadingModeToggle.propTypes = {
diff --git a/src/components/ReciterDropdown/index.js b/src/components/ReciterDropdown/index.js
index 3f11060ff..b6e407407 100644
--- a/src/components/ReciterDropdown/index.js
+++ b/src/components/ReciterDropdown/index.js
@@ -1,229 +1,69 @@
import React, { Component, PropTypes } from 'react';
-import ButtonToolbar from 'react-bootstrap/lib/ButtonToolbar';
-import DropdownButton from 'react-bootstrap/lib/DropdownButton';
-import MenuItem from 'react-bootstrap/lib/MenuItem';
+import * as customPropTypes from 'customPropTypes';
+import { connect } from 'react-redux';
+import Menu, { MenuItem } from 'quran-components/lib/Menu';
+import Radio from 'quran-components/lib/Radio';
+import Loader from 'quran-components/lib/Loader';
+import Icon from 'quran-components/lib/Icon';
+import LocaleFormattedMessage from 'components/LocaleFormattedMessage';
+import { loadRecitations } from 'redux/actions/options';
-const style = require('./style.scss');
+class ReciterDropdown extends Component {
-// To save API calls.
-export const slugs = [
- {
- reciter: {
- slug: 'abdulbaset',
- id: 1
- },
- name: {
- english: 'AbdulBaset AbdulSamad (Mujawwad)',
- arabic: 'عبد الباسط عبد الصمد (مجود)'
- },
- style: {
- slug: 'mujawwad',
- id: 1
- },
- id: 1
- },
- {
- reciter: {
- slug: 'abdulbaset',
- id: 1
- },
- name: {
- english: 'AbdulBaset AbdulSamad (Murattal)',
- arabic: 'عبد الباسط عبد الصمد (مرتل)'
- },
- style: {
- slug: 'murattal',
- id: 2
- },
- id: 2
- },
- {
- reciter: {
- slug: 'sudais',
- id: 2
- },
- name: {
- english: 'Abdur-Rahman as-Sudais',
- arabic: 'عبدالرحمن السديس'
- },
- style: {
- slug: null,
- id: null
- },
- id: 3
- },
- {
- reciter: {
- slug: 'shatri',
- id: 3
- },
- name: {
- english: 'Abu Bakr al-Shatri',
- arabic: 'أبو بكر الشاطرى'
- },
- style: {
- slug: null,
- id: null
- },
- id: 4
- },
- {
- reciter: {
- slug: 'rifai',
- id: 4
- },
- name: {
- english: 'Hani ar-Rifai',
- arabic: 'هاني الرفاعي'
- },
- style: {
- slug: null,
- id: null
- },
- id: 5
- },
- {
- reciter: {
- slug: 'alafasy',
- id: 6
- },
- name: {
- english: 'Mishari Rashid al-`Afasy',
- arabic: 'مشاري راشد العفاسي'
- },
- style: {
- slug: null,
- id: null
- },
- id: 8
- },
- {
- reciter: {
- slug: 'minshawi',
- id: 7
- },
- name: {
- english: 'Mohamed Siddiq al-Minshawi (Mujawwad)',
- arabic: 'محمد صديق المنشاوي (مجود)'
- },
- style: {
- slug: 'mujawwad',
- id: 1
- },
- id: 9
- },
- {
- reciter: {
- slug: 'minshawi',
- id: 7
- },
- name: {
- english: 'Mohamed Siddiq al-Minshawi (Murattal)',
- arabic: 'محمد صديق المنشاوي (مرتل)'
- },
- style: {
- slug: 'murattal',
- id: 2
- },
- id: 10
- },
- {
- reciter: {
- slug: 'altablawi',
- id: 9
- },
- name: {
- english: 'Mohamed al-Tablawi',
- arabic: 'محمد الطبلاوي'
- },
- style: {
- slug: null,
- id: null
- },
- id: 12
- },
- {
- reciter: {
- slug: 'alhusary',
- id: 5
- },
- name: {
- english: 'Mahmoud Khalil Al-Husary',
- arabic: 'محمود خليل الحصري'
- },
- style: {
- slug: null,
- id: null
- },
- id: 7
- },
- {
- reciter: {
- slug: 'muallim', // i'm just making up values for slug, i dont think we need this at all
- id: 5
- },
- name: {
- english: 'Mahmoud Khalil Al-Husary (Muallim)',
- arabic: 'محمود خليل الحصري'
- },
- style: {
- slug: 'muallim',
- id: 3
- },
- id: 13
- },
- {
- reciter: {
- slug: 'shuraym',
- id: 8
- },
- name: {
- english: 'Sa`ud ash-Shuraym',
- arabic: 'سعود الشريم'
- },
- style: {
- slug: null,
- id: null
- },
- id: 11
- }
-];
+ componentDidMount() {
+ if (!this.props.recitations.length) {
+ return this.props.loadRecitations();
+ }
-export default class ReciterDropdown extends Component {
- static propTypes = {
- onOptionChange: PropTypes.func,
- audio: PropTypes.number,
- className: PropTypes.string
- };
+ return false;
+ }
renderMenu() {
- const { audio, onOptionChange } = this.props;
+ const { audio, onOptionChange, recitations } = this.props;
- return slugs.map(slug => (
+ return recitations.map(slug => (
onOptionChange({ audio: slug.id })}
+ key={slug.id}
>
- {slug.name.english}
+ onOptionChange({ audio: slug.id })}
+ >
+
+ {slug.reciterNameEng} {slug.style ? `(${slug.style})` : ''}
+
+
));
}
render() {
- const { className, audio } = this.props;
+ const { recitations } = this.props;
return (
-
- slug.id === audio).name.english}
- >
- {this.renderMenu()}
-
-
+
}
+ menu={
+ recitations.length ?
:
+ }
+ >
+
+
);
}
}
+
+ReciterDropdown.propTypes = {
+ onOptionChange: PropTypes.func,
+ audio: PropTypes.number,
+ loadRecitations: PropTypes.func.isRequired,
+ recitations: customPropTypes.recitations
+};
+
+export default connect(state => ({
+ recitations: state.options.options.recitations,
+ loadingRecitations: state.options.loadingRecitations,
+ audio: state.options.audio
+}), { loadRecitations })(ReciterDropdown);
diff --git a/src/components/SearchAutocomplete/index.js b/src/components/SearchAutocomplete/index.js
index 7f34aa059..1392cf8b1 100644
--- a/src/components/SearchAutocomplete/index.js
+++ b/src/components/SearchAutocomplete/index.js
@@ -1,9 +1,8 @@
// TODO: Should be handled by redux and not component states.
import React, { Component, PropTypes } from 'react';
+import * as customPropTypes from 'customPropTypes';
import { connect } from 'react-redux';
import { push } from 'react-router-redux';
-import { surahType } from 'types';
-
import { suggest } from 'redux/actions/suggest';
const styles = require('./style.scss');
@@ -11,25 +10,6 @@ const styles = require('./style.scss');
const ayahRegex = /^(\d+)(?::(\d+))?$/;
class SearchAutocomplete extends Component {
- static propTypes = {
- surahs: PropTypes.objectOf(surahType).isRequired,
- value: PropTypes.string,
- // TODO: This should not be doing html stuff. Should use react onKeydown.
- input: PropTypes.any, // eslint-disable-line
- push: PropTypes.func.isRequired,
- suggest: PropTypes.func.isRequired,
- suggestions: PropTypes.arrayOf(PropTypes.shape({
- ayah: PropTypes.string,
- href: PropTypes.string.isRequired,
- text: PropTypes.string.isRequired
- })),
- lang: PropTypes.string,
- delay: PropTypes.number,
- };
-
- static defaultProps = {
- delay: 200
- }
componentDidMount() {
this.props.input.addEventListener('keydown', this.handleInputKeyDown.bind(this));
@@ -64,23 +44,23 @@ class SearchAutocomplete extends Component {
if (!value) return matches;
- const isAyahKeySearch = ayahRegex.test(value);
+ const isverseKeySearch = ayahRegex.test(value);
- if (isAyahKeySearch) {
+ if (isverseKeySearch) {
const captures = value.match(ayahRegex);
- const surahId = captures[1];
+ const chapterId = captures[1];
const ayahNum = captures[2];
- const surah = this.props.surahs[surahId];
- matches.push([surah.name.simple, surah.id + (ayahNum ? `/${ayahNum}` : '')]);
+ const chapter = this.props.chapters[chapterId];
+ matches.push([chapter.nameSimple, chapter.chapterNumber + (ayahNum ? `/${ayahNum}` : '')]);
} else if (value.length >= 2) {
const escaped = value.replace(/[-\\^$*+?.()|[\]{}]/g, '\\$&');
- Object.keys(this.props.surahs).forEach((surahId) => {
- const surah = this.props.surahs[surahId];
- if (RegExp(escaped, 'i').test(surah.name.simple.replace(/['-]/g, ''))) {
- matches.push([surah.name.simple, surah.id]);
- } else if (RegExp(escaped, 'i').test(surah.name.arabic)) {
- matches.push([surah.name.arabic, surah.id]);
+ Object.keys(this.props.chapters).forEach((chapterId) => {
+ const chapter = this.props.chapters[chapterId];
+ if (RegExp(escaped, 'i').test(chapter.nameSimple.replace(/['-]/g, ''))) {
+ matches.push([chapter.nameSimple, chapter.chapterNumber]);
+ } else if (RegExp(escaped, 'i').test(chapter.nameArabic)) {
+ matches.push([chapter.nameArabic, chapter.chapterNumber]);
}
});
}
@@ -174,7 +154,7 @@ class SearchAutocomplete extends Component {
onKeyDown={event => this.handleItemKeyDown(event, item)}
>
@@ -195,17 +175,17 @@ class SearchAutocomplete extends Component {
}
function mapStateToProps(state, ownProps) {
- const surahs = state.surahs.entities;
- const surahId = state.surahs.current;
+ const chapters = state.chapters.entities;
+ const chapterId = state.chapters.current;
const suggestions = state.suggestResults.results[ownProps.value];
let lang = 'en';
- if (state.ayahs && state.ayahs.entities && state.ayahs.entities[surahId]) {
- const ayahs = state.ayahs.entities[surahId];
- const ayahKey = Object.keys(ayahs)[0];
+ if (state.verses && state.verses.entities && state.verses.entities[chapterId]) {
+ const ayahs = state.verses.entities[chapterId];
+ const verseKey = Object.keys(ayahs)[0];
- if (ayahKey) {
- const ayah = ayahs[ayahKey];
+ if (verseKey) {
+ const ayah = ayahs[verseKey];
if (ayah.content && ayah.content[0] && ayah.content[0].lang) {
lang = ayah.content[0].lang;
@@ -214,10 +194,26 @@ function mapStateToProps(state, ownProps) {
}
return {
- surahs,
+ chapters,
suggestions,
lang
};
}
+SearchAutocomplete.propTypes = {
+ chapters: customPropTypes.chapters.isRequired,
+ value: PropTypes.string,
+ // TODO: This should not be doing html stuff. Should use react onKeydown.
+ input: PropTypes.any, // eslint-disable-line
+ push: PropTypes.func.isRequired,
+ suggest: PropTypes.func.isRequired,
+ suggestions: customPropTypes.suggestions,
+ lang: PropTypes.string,
+ delay: PropTypes.number,
+};
+
+SearchAutocomplete.defaultProps = {
+ delay: 200
+};
+
export default connect(mapStateToProps, { push, suggest })(SearchAutocomplete);
diff --git a/src/components/SearchInput/index.js b/src/components/SearchInput/index.js
index a500c8d60..df9885d4a 100644
--- a/src/components/SearchInput/index.js
+++ b/src/components/SearchInput/index.js
@@ -9,11 +9,6 @@ import SearchAutocomplete from 'components/SearchAutocomplete';
import debug from 'helpers/debug';
class SearchInput extends Component {
- static propTypes = {
- push: PropTypes.func.isRequired,
- className: PropTypes.string,
- intl: intlShape.isRequired
- };
static contextTypes = {
metrics: MetricsPropTypes.metrics
@@ -116,4 +111,10 @@ class SearchInput extends Component {
}
}
+SearchInput.propTypes = {
+ push: PropTypes.func.isRequired,
+ className: PropTypes.string,
+ intl: intlShape.isRequired
+};
+
export default injectIntl(connect(null, { push })(SearchInput));
diff --git a/src/components/SettingsModal/index.js b/src/components/SettingsModal/index.js
index 2c46faca4..2b8114410 100644
--- a/src/components/SettingsModal/index.js
+++ b/src/components/SettingsModal/index.js
@@ -1,22 +1,20 @@
import React, { PropTypes } from 'react';
+import * as customProptypes from 'customPropTypes';
import { connect } from 'react-redux';
import Modal from 'react-bootstrap/lib/Modal';
-
import LocaleFormattedMessage from 'components/LocaleFormattedMessage';
import ReciterDropdown from 'components/ReciterDropdown';
import ContentDropdown from 'components/ContentDropdown';
import TooltipDropdown from 'components/TooltipDropdown';
-
import { setOption } from 'redux/actions/options.js';
-import { load } from 'redux/actions/ayahs.js';
-import { optionsType, surahType } from 'types';
+import { load } from 'redux/actions/verses.js';
const ModalHeader = Modal.Header;
const ModalTitle = Modal.Title;
const ModalBody = Modal.Body;
const SettingsModal = ({
- surah,
+ chapter,
ayahIds,
open,
handleHide,
@@ -27,10 +25,10 @@ const SettingsModal = ({
const handleOptionChange = (payload) => {
setOption(payload);
- if (surah) {
+ if (chapter) {
const first = [...ayahIds][0];
const last = [...ayahIds][[...ayahIds].length - 1];
- load(surah.id, first, last, { ...options, ...payload });
+ load(chapter.chapterNumber, first, last, { ...options, ...payload });
}
};
@@ -48,7 +46,6 @@ const SettingsModal = ({
@@ -57,7 +54,6 @@ const SettingsModal = ({
@@ -75,11 +71,11 @@ const SettingsModal = ({
};
SettingsModal.propTypes = {
- surah: surahType,
+ chapter: customProptypes.surahType,
ayahIds: PropTypes.instanceOf(Set),
open: PropTypes.bool,
handleHide: PropTypes.func.isRequired,
- options: optionsType,
+ options: customProptypes.optionsType,
setOption: PropTypes.func.isRequired,
load: PropTypes.func.isRequired,
};
diff --git a/src/components/Share/index.js b/src/components/Share/index.js
index 7385f5f5a..f3b8699ba 100644
--- a/src/components/Share/index.js
+++ b/src/components/Share/index.js
@@ -1,7 +1,6 @@
-/* global window */
-import React, { Component } from 'react';
+import React, { PropTypes } from 'react';
import { ShareButtons, generateShareIcon } from 'react-share';
-import { surahType } from 'types';
+import * as customPropTypes from 'customPropTypes';
const styles = require('./style.scss');
@@ -9,38 +8,38 @@ const { FacebookShareButton, TwitterShareButton } = ShareButtons;
const FacebookIcon = generateShareIcon('facebook');
const TwitterIcon = generateShareIcon('twitter');
-export default class Share extends Component {
- static propTypes = {
- surah: surahType.isRequired
- };
+const Share = ({ chapter, verseKey }) => {
+ // Fallback to Surah Id
+ const path = verseKey ? verseKey.replace(':', '/') : chapter.chapterNumber;
+ const shareUrl = `https://quran.com/${path}`;
+ const title = verseKey ? `Surah ${chapter.nameSimple} [${verseKey}]` : `Surah ${chapter.nameSimple}`;
+ const iconProps = verseKey ? { iconBgStyle: { fill: '#d1d0d0' } } : {};
- onClickPopup = (url, title) => {
- window.open(url, title, 'width=670,height=540,scrollbars=no,toolbar=0');
- }
+ return (
+
+
+
+
+
+
+
+
+ );
+};
- render() {
- const { surahId, name } = this.props.surah;
- const surahUrl = `https://quran.com/${surahId}`;
+Share.propTypes = {
+ verseKey: PropTypes.string,
+ chapter: customPropTypes.surahType.isRequired
+};
- return (
-
-
-
-
-
-
-
-
- );
- }
-}
+export default Share;
diff --git a/src/components/Share/style.scss b/src/components/Share/style.scss
index 11025dba5..92b6684bb 100644
--- a/src/components/Share/style.scss
+++ b/src/components/Share/style.scss
@@ -3,13 +3,14 @@
.shareContainer {
position: relative;
top: 7px;
- display: -webkit-box; /* OLD - iOS 6-, Safari 3.1-6, BB7 */
- display: -ms-flexbox; /* TWEENER - IE 10 */
- display: -webkit-flex; /* NEW - Safari 6.1+. iOS 7.1+, BB10 */
- display: flex; /* NEW, Spec - Firefox, Chrome, Opera */
+ display: inline-block;
.iconContainer {
- padding: 0 5px;
+ display: inline-block;
+
+ &:last-child{
+ padding-left: 5px;
+ }
&:hover {
cursor: pointer;
diff --git a/src/components/SmartBanner/index.js b/src/components/SmartBanner/index.js
index 2d0dd362e..3dd954306 100644
--- a/src/components/SmartBanner/index.js
+++ b/src/components/SmartBanner/index.js
@@ -1,42 +1,10 @@
/* global window */
import React, { Component, PropTypes } from 'react';
+import * as customPropTypes from 'customPropTypes';
import useragent from 'express-useragent';
import cookie from 'react-cookie';
class SmartBanner extends Component {
- static propTypes = {
- daysHidden: PropTypes.number,
- daysReminder: PropTypes.number,
- appStoreLanguage: PropTypes.string,
- button: PropTypes.string,
- storeText: PropTypes.objectOf(PropTypes.string),
- price: PropTypes.objectOf(PropTypes.string),
- force: PropTypes.string,
- title: PropTypes.string,
- author: PropTypes.string,
- };
-
- static defaultProps = {
- daysHidden: 15,
- daysReminder: 90,
- appStoreLanguage: 'us',
- button: 'View',
- storeText: {
- ios: 'On the App Store',
- android: 'In Google Play',
- windows: 'In Windows Store',
- kindle: 'In the Amazon Appstore',
- },
- price: {
- ios: 'Free',
- android: 'Free',
- windows: 'Free',
- kindle: 'Free',
- },
- force: '',
- title: '',
- author: '',
- };
state = {
settings: {},
@@ -199,4 +167,38 @@ class SmartBanner extends Component {
}
}
+SmartBanner.propTypes = {
+ daysHidden: PropTypes.number,
+ daysReminder: PropTypes.number,
+ appStoreLanguage: PropTypes.string,
+ button: PropTypes.string,
+ storeText: customPropTypes.storeText,
+ price: customPropTypes.storeText,
+ force: PropTypes.string,
+ title: PropTypes.string,
+ author: PropTypes.string,
+};
+
+SmartBanner.defaultProps = {
+ daysHidden: 15,
+ daysReminder: 90,
+ appStoreLanguage: 'us',
+ button: 'View',
+ storeText: {
+ ios: 'On the App Store',
+ android: 'In Google Play',
+ windows: 'In Windows Store',
+ kindle: 'In the Amazon Appstore',
+ },
+ price: {
+ ios: 'Free',
+ android: 'Free',
+ windows: 'Free',
+ kindle: 'Free',
+ },
+ force: '',
+ title: '',
+ author: '',
+};
+
export default SmartBanner;
diff --git a/src/components/SurahInfo/index.js b/src/components/SurahInfo/index.js
index 0ea4aece1..16d13b1b4 100644
--- a/src/components/SurahInfo/index.js
+++ b/src/components/SurahInfo/index.js
@@ -1,58 +1,58 @@
import React, { PropTypes } from 'react';
-
-import Col from 'react-bootstrap/lib/Col';
-import { surahType } from 'types';
-import Loader from 'components/Loader';
+import * as customPropTypes from 'customPropTypes';
+import Loader from 'quran-components/lib/Loader';
const style = require('./style.scss');
-const SurahInfo = ({ surah, isShowingSurahInfo, onClose }) => {
+const SurahInfo = ({ chapter, info, isShowingSurahInfo, onClose }) => {
// So we don't need to load images and files unless needed
if (!isShowingSurahInfo) return
;
- if (!surah.info) {
- return
;
+ if (!info) {
+ return
;
}
return (
-
-
@@ -48,7 +47,7 @@ const Home = (props) => {
};
Home.propTypes = {
- surahs: PropTypes.objectOf(surahType).isRequired
+ chapters: customPropTypes.chapters.isRequired
};
const AsyncHome = asyncConnect([{
@@ -61,4 +60,4 @@ const AsyncHome = asyncConnect([{
}
}])(Home);
-export default connect(state => ({ surahs: state.surahs.entities }))(AsyncHome);
+export default connect(state => ({ chapters: state.chapters.entities }))(AsyncHome);
diff --git a/src/containers/MobileLanding/index.js b/src/containers/MobileLanding/index.js
index 38746d799..04d17f88a 100644
--- a/src/containers/MobileLanding/index.js
+++ b/src/containers/MobileLanding/index.js
@@ -1,8 +1,5 @@
import React from 'react';
import Helmet from 'react-helmet';
-
-import Grid from 'react-bootstrap/lib/Grid';
-import Col from 'react-bootstrap/lib/Col';
import Button from 'react-bootstrap/lib/Button';
import makeHeadTags from 'helpers/makeHeadTags';
@@ -13,7 +10,7 @@ const play = require('../../../static/images/play-store.svg');
const phones = require('../../../static/images/mockup-desktop@2x.png');
export default () => (
-
+
(
}
/>
-
+
Quran
by quran.com القرآن
@@ -37,7 +34,7 @@ export default () => (
-
+
(
/>
Download
-
-
+
+
(
Download
-
+
-
-
+
+
-
+
-
+
);
diff --git a/src/containers/Profile/index.js b/src/containers/Profile/index.js
index ead7b02e3..7b834041d 100644
--- a/src/containers/Profile/index.js
+++ b/src/containers/Profile/index.js
@@ -1,23 +1,15 @@
import React, { Component } from 'react';
+import * as customPropTypes from 'customPropTypes';
import Helmet from 'react-helmet';
import { connect } from 'react-redux';
import { Link } from 'react-router';
-
-import Grid from 'react-bootstrap/lib/Grid';
-import Col from 'react-bootstrap/lib/Col';
import Image from 'react-bootstrap/lib/Image';
import Tabs from 'react-bootstrap/lib/Tabs';
import Tab from 'react-bootstrap/lib/Tab';
-import { bookmarkType, userType } from 'types';
-
const styles = require('./style.scss');
class Profile extends Component { // eslint-disable-line
- static propTypes = {
- user: userType.isRequired,
- bookmarks: bookmarkType.isRequired
- };
render() {
const { user, bookmarks } = this.props;
@@ -26,24 +18,24 @@ class Profile extends Component { // eslint-disable-line
-
+
-
+
{
Object.values(bookmarks).map(bookmark => (
-
- {bookmark.ayahKey}
+
+ {bookmark.verseKey}
))
}
@@ -53,14 +45,19 @@ class Profile extends Component { // eslint-disable-line
Notes...
-
+
-
+
);
}
}
+Profile.propTypes = {
+ user: customPropTypes.userType.isRequired,
+ bookmarks: customPropTypes.bookmarkType.isRequired
+};
+
export default connect(
state => ({
user: state.auth.user,
diff --git a/src/containers/Search/Header/index.js b/src/containers/Search/Header/index.js
deleted file mode 100644
index 833d64dba..000000000
--- a/src/containers/Search/Header/index.js
+++ /dev/null
@@ -1,21 +0,0 @@
-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');
-
-export default () => (
-
-
-
-
-
-
-
-
THE NOBLE QURAN
-
-
-
-
-
-);
diff --git a/src/containers/Search/index.js b/src/containers/Search/index.js
index a2162989d..16ce18edb 100644
--- a/src/containers/Search/index.js
+++ b/src/containers/Search/index.js
@@ -1,58 +1,31 @@
import React, { Component, PropTypes } from 'react';
+import * as customPropTypes from 'customPropTypes';
import { PropTypes as MetricsPropTypes } from 'react-metrics';
-
import { asyncConnect } from 'redux-connect';
import { connect } from 'react-redux';
import { push } from 'react-router-redux';
import Helmet from 'react-helmet';
import ReactPaginate from 'react-paginate';
import { FormattedHTMLMessage } from 'react-intl';
-
-// Bootstrap
-import Grid from 'react-bootstrap/lib/Grid';
-import Col from 'react-bootstrap/lib/Col';
-
-import Ayah from 'components/Ayah';
-import Loader from 'components/Loader';
-
+import IndexHeader from 'components/IndexHeader';
+import Verse from 'components/Verse';
+import Loader from 'quran-components/lib/Loader';
import { search } from 'redux/actions/search.js';
-
import LocaleFormattedMessage from 'components/LocaleFormattedMessage';
-import { ayahType, optionsType } from 'types';
-
-import Header from './Header';
-
const style = require('./style.scss');
class Search extends Component {
- static propTypes = {
- isErrored: PropTypes.bool,
- isLoading: PropTypes.bool,
- total: PropTypes.number,
- page: PropTypes.number,
- size: PropTypes.number,
- from: PropTypes.number,
- query: PropTypes.string,
- results: PropTypes.array, // eslint-disable-line
- ayahs: PropTypes.objectOf(ayahType),
- push: PropTypes.func.isRequired,
- options: optionsType
- };
-
- static defaultProps = {
- results: []
- };
static contextTypes = {
metrics: MetricsPropTypes.metrics
};
handlePageChange = (payload) => {
- const { push, query, page } = this.props; // eslint-disable-line no-shadow
+ const { push, query, currentPage } = this.props; // eslint-disable-line no-shadow
const selectedPage = payload.selected + 1;
- if (page !== selectedPage) {
+ if (currentPage !== selectedPage) {
this.context.metrics.track(
'Search',
{ action: 'paginate', label: `${query} - ${selectedPage}` }
@@ -68,25 +41,24 @@ class Search extends Component {
}
renderStatsBar() {
- const { total, size, page, from, query } = this.props;
- const values = { from: 2, to: (from + size) - 1, total: 10, query };
-
-
- if (total) {
- const pageNum = Math.ceil(total / size);
+ const { totalCount, totalPages, currentPage, query, perPage } = this.props;
+ const from = Math.max(...[(currentPage - 1) * perPage, 1]);
+ const to = Math.min(...[currentPage * perPage, totalCount]);
+ const values = { from, to, query, total: totalCount };
+ if (totalPages) {
return (
-
+
-
+
-
-
+
+
@@ -99,20 +71,21 @@ class Search extends Component {
}
breakLabel={...}
- pageNum={pageNum}
+ pageNum={currentPage}
marginPagesDisplayed={2}
pageRangeDisplayed={5}
- initialSelected={page - 1}
- forceSelected={page - 1}
+ initialSelected={currentPage}
+ forceSelected={currentPage}
onPageChange={this.handlePageChange}
containerClassName="pagination"
subContainerClassName="pages pagination"
- pageLinkClassName="pointer:"
+ pageLinkClassName="pointer"
activeClass={style.active}
+ pageCount={totalPages}
/>
-
+
-
+
);
}
@@ -121,7 +94,15 @@ class Search extends Component {
}
renderBody() {
- const { isErrored, isLoading, results, options, ayahs } = this.props;
+ const { isErrored, isLoading, results, entities, options, location: { query } } = this.props;
+
+ if (!query || !query.q) {
+ return (
+
+
+
+ );
+ }
if (isErrored) {
return (
@@ -132,7 +113,7 @@ class Search extends Component {
}
if (isLoading) {
- return
;
+ return ;
}
if (!results.length) {
@@ -144,11 +125,12 @@ class Search extends Component {
}
return results.map(result => (
-
));
@@ -166,13 +148,13 @@ class Search extends Component {
.text-translation{font-size: ${options.fontSize.translation}rem;}`
}]}
/>
-
+
{this.renderStatsBar()}
-
+
{this.renderBody()}
-
+
@@ -180,14 +162,36 @@ class Search extends Component {
}
}
+Search.propTypes = {
+ isErrored: PropTypes.bool,
+ isLoading: PropTypes.bool,
+ totalCount: PropTypes.number,
+ totalPages: PropTypes.number,
+ currentPage: PropTypes.number,
+ perPage: PropTypes.number,
+ query: PropTypes.string,
+ results: PropTypes.arrayOf(PropTypes.string),
+ entities: PropTypes.arrayOf(customPropTypes.verseType),
+ push: PropTypes.func.isRequired,
+ location: PropTypes.shape({ // eslint-disable-line
+ q: PropTypes.string,
+ p: PropTypes.string
+ }),
+ options: customPropTypes.optionsType
+};
+
+Search.defaultProps = {
+ results: []
+};
+
const AsyncSearch = asyncConnect([{
promise({ store: { dispatch }, location }) {
if (__CLIENT__) {
- dispatch(search(location.query));
+ dispatch(search(location.query || location.q));
return false;
}
- return dispatch(search(location.query));
+ return dispatch(search(location.query || location.q));
}
}])(Search);
@@ -195,14 +199,14 @@ function mapStateToProps(state) {
return {
isErrored: state.searchResults.errored,
isLoading: state.searchResults.loading,
- total: state.searchResults.total,
- page: state.searchResults.page,
- size: state.searchResults.size,
- from: state.searchResults.from,
+ totalCount: state.searchResults.totalCount,
+ currentPage: state.searchResults.currentPage,
+ totalPages: state.searchResults.totalPages,
+ perPage: state.searchResults.perPage,
took: state.searchResults.took,
query: state.searchResults.query,
results: state.searchResults.results,
- ayahs: state.searchResults.entities,
+ entities: state.searchResults.entities,
options: state.options
};
}
diff --git a/src/containers/Surah/Header/index.js b/src/containers/Surah/Header/index.js
deleted file mode 100644
index 05379afda..000000000
--- a/src/containers/Surah/Header/index.js
+++ /dev/null
@@ -1,90 +0,0 @@
-import React, { PropTypes } from 'react';
-import { Link } from 'react-router';
-import Col from 'react-bootstrap/lib/Col';
-import Navbar from 'react-bootstrap/lib/Navbar';
-
-import LocaleFormattedMessage from 'components/LocaleFormattedMessage';
-
-import debug from 'helpers/debug';
-import surahType from 'types/surahType';
-
-const Header = Navbar.Header;
-
-// const ornamentLeft = require('../../../../static/images/ornament-left.png');
-// const ornamentRight = require('../../../../static/images/ornament-right.png');
-
-const styles = require('./style.scss');
-
-const SurahHeader = ({ surah, handleToggleSidebar }) => {
- debug('component:SurahHeader', 'Render');
-
- return (
-
-
-
-
-
- Toggle navigation
-
-
-
-
-
-
-
-
-
-
-
-
- );
-};
-
-SurahHeader.propTypes = {
- surah: surahType.isRequired,
- handleToggleSidebar: PropTypes.func.isRequired
-};
-
-export default SurahHeader;
diff --git a/src/containers/Surah/Header/style.scss b/src/containers/Surah/Header/style.scss
deleted file mode 100644
index 32726d33e..000000000
--- a/src/containers/Surah/Header/style.scss
+++ /dev/null
@@ -1,15 +0,0 @@
-@import '../../../styles/variables.scss';
-
-.container{
- margin: 0px;
- width: 100%;
- margin-left: 15px;
- display: inline-block;
-}
-.verticalAlign{
- vertical-align: middle;
-}
-.ornament{
- height: $navbar-height;
- opacity: 0.15;
-}
diff --git a/src/containers/Surah/Title/index.js b/src/containers/Surah/Title/index.js
deleted file mode 100644
index 5bb040429..000000000
--- a/src/containers/Surah/Title/index.js
+++ /dev/null
@@ -1,33 +0,0 @@
-import React from 'react';
-import Isvg from 'react-inlinesvg';
-import { surahType } from 'types';
-
-const styles = require('./style.scss');
-
-const zeroPad = (num, places) => {
- const zero = (places - num.toString().length) + 1;
-
- return Array(+(zero > 0 && zero)).join('0') + num;
-};
-
-const Title = ({ surah }) => {
- const title = require(`../../../../static/images/titles/${zeroPad(surah.id, 3)}.svg`); // eslint-disable-line
-
- if (!surah) return
;
-
- return (
-
-
-
- );
-};
-
-Title.propTypes = {
- surah: surahType.isRequired
-};
-
-export default Title;
diff --git a/src/containers/Surah/Title/spec.js b/src/containers/Surah/Title/spec.js
deleted file mode 100644
index fca1e74e0..000000000
--- a/src/containers/Surah/Title/spec.js
+++ /dev/null
@@ -1,37 +0,0 @@
-import React from 'react';
-import { shallow } from 'enzyme';
-
-import Title from './index';
-
-let wrapper;
-const surah = {
- id: 1,
- name: {
- arabic: 'الفاتحة',
- simple: 'Al-Fatihah',
- complex: 'Al-Fātiĥah',
- english: 'The Opener'
- }
-};
-
-const renderComponent = data => shallow(
);
-
-describe('
', () => {
- it('should render', () => {
- wrapper = renderComponent(surah);
- expect(wrapper).to.be.ok; // eslint-disable-line
- });
-
- it('should not show previous surah if on the first surah', () => {
- wrapper = renderComponent(surah);
- const previous = wrapper.find('.previous-chapter').length;
- expect(previous).to.equal(0);
- });
-
- it('should not show next surah if on the last surah', () => {
- surah.id = 114;
- wrapper = renderComponent(surah);
- const next = wrapper.find('.next-chapter').length;
- expect(next).to.equal(0);
- });
-});
diff --git a/src/containers/Surah/Title/style.scss b/src/containers/Surah/Title/style.scss
deleted file mode 100644
index 8774b7003..000000000
--- a/src/containers/Surah/Title/style.scss
+++ /dev/null
@@ -1,7 +0,0 @@
-.title{
- width: 30%;
- display: inherit;
- position: absolute;
- top: 0px;
- transform: translateY(-50%);
-}
diff --git a/src/containers/Surah/connect.js b/src/containers/Surah/connect.js
index 220c9d382..4754b0057 100644
--- a/src/containers/Surah/connect.js
+++ b/src/containers/Surah/connect.js
@@ -2,89 +2,93 @@ import {
isAllLoaded,
loadAll,
loadInfo,
- setCurrent as setCurrentSurah
- } from 'redux/actions/surahs.js';
+ setCurrent as setCurrentSurah,
+ isInfoLoaded
+} from 'redux/actions/chapters.js';
import {
clearCurrent,
- load as loadAyahs
- } from 'redux/actions/ayahs.js';
+ load as loadVerses,
+ isLoaded
+ } from 'redux/actions/verses.js';
-import { debug, isLoaded } from 'helpers';
+import { debug } from 'helpers';
-const ayahRangeSize = 30;
+const determinePage = (range) => {
+ let from;
+ let to;
-export const surahsConnect = ({ store: { getState, dispatch } }) => {
- debug('component:Surah:surahsConnect', 'Init');
+ if (range) {
+ if (range.includes('-')) {
+ [from, to] = range.split('-').map(value => parseInt(value, 10));
- if (!isAllLoaded(getState())) {
- debug('component:Surah:surahsConnect', 'Surahs not loaded');
+ if (isNaN(from) || isNaN(to)) return {};
- if (__CLIENT__) {
- dispatch(loadAll());
- return true;
+ return {
+ offset: from - 1,
+ limit: to - from
+ };
}
- return dispatch(loadAll());
+ const offset = parseInt(range, 10);
+
+ if (isNaN(offset)) return {};
+
+ return {
+ offset: offset - 1,
+ limit: 1
+ };
}
- return true;
+ return {};
};
-export const surahInfoConnect = ({ store: { dispatch }, params }) => {
+export const chaptersConnect = ({ store: { getState, dispatch } }) => {
+ debug('component:Surah:chaptersConnect', 'Init');
+ if (isAllLoaded(getState())) return false;
+
+ debug('component:Surah:chaptersConnect', 'Surahs not loaded');
+
if (__CLIENT__) {
- dispatch(loadInfo(params.surahId));
+ dispatch(loadAll());
return true;
}
- return dispatch(loadInfo(params.surahId));
+ return dispatch(loadAll());
};
-export const ayahsConnect = ({ store: { dispatch, getState }, params }) => {
- debug('component:Surah:ayahsConnect', 'Init');
-
- const range = params.range;
- const surahId = parseInt(params.surahId, 10);
+export const chapterInfoConnect = ({ store: { dispatch, getState }, params }) => {
+ if (isInfoLoaded(getState(), params.chapterId)) return false;
- let from;
- let to;
+ if (__CLIENT__) {
+ dispatch(loadInfo(params));
+ return true;
+ }
- if (range) {
- if (range.includes('-')) {
- [from, to] = range.split('-');
- } else {
- // Single ayah. For example /2/30
- from = range;
- to = parseInt(range, 10) + ayahRangeSize;
- }
+ return dispatch(loadInfo(params));
+};
- if (isNaN(from) || isNaN(to)) {
- // Something wrong happened like /2/SOMETHING
- // going to rescue by giving beginning of surah.
- [from, to] = [1, ayahRangeSize];
- }
- } else {
- [from, to] = [1, ayahRangeSize];
- }
+export const versesConnect = ({ store: { dispatch, getState }, params }) => {
+ debug('component:Surah:versesConnect', 'Init');
- from = parseInt(from, 10);
- to = parseInt(to, 10);
+ const chapterId = parseInt(params.chapterId, 10);
+ const paging = determinePage(params.range);
- if (surahId !== getState().surahs.current) {
- dispatch(setCurrentSurah(surahId));
+ if (chapterId !== getState().chapters.current) {
+ dispatch(setCurrentSurah(chapterId));
}
- if (!isLoaded(getState(), surahId, from, to)) {
- debug('component:Surah:ayahsConnect', 'Not loaded');
+ if (!isLoaded(getState(), chapterId, paging)) {
+ debug('component:Surah:versesConnect', 'Not loaded');
- dispatch(clearCurrent(surahId)); // In the case where you go to same surah but later ayahs.
+ dispatch(clearCurrent(chapterId)); // In the case where you go to same surah but later ayahs.
if (__CLIENT__) {
- dispatch(loadAyahs(surahId, from, to, getState().options));
+ dispatch(loadVerses(chapterId, paging, getState().options));
return true;
}
- return dispatch(loadAyahs(surahId, from, to, getState().options));
+ return dispatch(loadVerses(chapterId, paging, getState().options));
}
return true;
diff --git a/src/containers/Surah/index.js b/src/containers/Surah/index.js
index 688ee107f..8cf81268b 100644
--- a/src/containers/Surah/index.js
+++ b/src/containers/Surah/index.js
@@ -1,5 +1,6 @@
/* global window, document */
import React, { Component, PropTypes } from 'react';
+import * as customPropTypes from 'customPropTypes';
import Link from 'react-router/lib/Link';
// redux
import { bindActionCreators } from 'redux';
@@ -7,62 +8,52 @@ import { connect } from 'react-redux';
import { asyncConnect } from 'redux-connect';
import { push } from 'react-router-redux';
-// bootstrap
-import Col from 'react-bootstrap/lib/Col';
-
import Helmet from 'react-helmet';
+import Loadable from 'react-loadable';
// components
-import Loader from 'components/Loader';
+import Loader from 'quran-components/lib/Loader';
import LazyLoad from 'components/LazyLoad';
-import PageBreak from 'components/PageBreak';
-import Audioplayer from 'components/Audioplayer';
-import SurahInfo from 'components/SurahInfo';
-import Ayah from 'components/Ayah';
-import Line from 'components/Line';
+import Verse from 'components/Verse';
+import ComponentLoader from 'components/ComponentLoader';
import Bismillah from 'components/Bismillah';
-import TopOptions from 'components/TopOptions';
import LocaleFormattedMessage from 'components/LocaleFormattedMessage';
-// utils
-import scroller from 'utils/scroller';
-
// Helpers
import makeHeadTags from 'helpers/makeHeadTags';
import debug from 'helpers/debug';
-import { surahType, ayahType } from 'types';
-
import * as AudioActions from 'redux/actions/audioplayer.js';
-import * as AyahActions from 'redux/actions/ayahs.js';
+import * as AyahActions from 'redux/actions/verses.js';
import * as BookmarkActions from 'redux/actions/bookmarks.js';
import * as OptionsActions from 'redux/actions/options.js';
import * as MediaActions from 'redux/actions/media.js';
-import { surahsConnect, surahInfoConnect, ayahsConnect } from './connect';
+import { chaptersConnect, chapterInfoConnect, versesConnect } from './connect';
+
+const LoaderStyle = { width: '10em', height: '10em' };
const style = require('./style.scss');
+const PageView = Loadable({
+ loader: () => import('components/PageView'),
+ LoadingComponent: ComponentLoader
+});
+
+const Audioplayer = Loadable({
+ loader: () => import('components/Audioplayer'),
+ LoadingComponent: ComponentLoader
+});
+const SurahInfo = Loadable({
+ loader: () => import('components/SurahInfo'),
+ LoadingComponent: ComponentLoader
+});
+const TopOptions = Loadable({
+ loader: () => import('components/TopOptions'),
+ LoadingComponent: ComponentLoader
+});
+
class Surah extends Component {
- static propTypes = {
- surah: surahType.isRequired,
- surahs: PropTypes.objectOf(surahType).isRequired,
- actions: PropTypes.object.isRequired, // eslint-disable-line
- lines: PropTypes.object.isRequired, // eslint-disable-line
- isEndOfSurah: PropTypes.bool.isRequired,
- ayahIds: PropTypes.instanceOf(Set),
- currentAyah: PropTypes.string,
- bookmarks: PropTypes.object.isRequired, // eslint-disable-line
- isLoading: PropTypes.bool.isRequired,
- isLoaded: PropTypes.bool.isRequired,
- isAuthenticated: PropTypes.bool.isRequired,
- options: PropTypes.object.isRequired, // eslint-disable-line
- params: PropTypes.shape({
- surahId: PropTypes.string.isRequired
- }).isRequired,
- ayahs: PropTypes.objectOf(ayahType),
- isPlaying: PropTypes.bool
- };
state = {
lazyLoading: false,
@@ -70,13 +61,13 @@ class Surah extends Component {
};
componentWillMount() {
- const { params, surah, actions } = this.props; // eslint-disable-line no-shadow
+ const { params, chapter, actions } = this.props; // eslint-disable-line no-shadow
if (params.range && params.range.includes('-')) {
const start = parseInt(params.range.split('-')[0], 10);
- if (start > surah.ayat || isNaN(start)) {
- return actions.push.push('/error/invalid-ayah-range');
+ if (start > chapter.versesCount || isNaN(start)) {
+ return actions.push.push('/error/invalid-verse-range');
}
return false;
@@ -89,15 +80,15 @@ class Surah extends Component {
const conditions = [
this.state.lazyLoading !== nextState.lazyLoading,
this.state.sidebarOpen !== nextState.sidebarOpen,
- this.props.surah !== nextProps.surah,
+ this.props.chapter !== nextProps.chapter,
this.props.isEndOfSurah !== nextProps.isEndOfSurah,
- this.props.ayahIds.length !== nextProps.ayahIds.length,
- this.props.surahs !== nextProps.surahs,
+ this.props.verseIds.length !== nextProps.verseIds.length,
+ this.props.chapters !== nextProps.chapters,
this.props.bookmarks !== nextProps.bookmarks,
this.props.isLoading !== nextProps.isLoading,
this.props.isLoaded !== nextProps.isLoaded,
this.props.options !== nextProps.options,
- this.props.currentAyah !== nextProps.currentAyah,
+ this.props.currentVerse !== nextProps.currentVerse,
this.props.isPlaying !== nextProps.isPlaying,
];
@@ -105,61 +96,34 @@ class Surah extends Component {
}
getLast() {
- const { ayahIds } = this.props;
+ const { verseIds } = this.props;
- return [...ayahIds][[...ayahIds].length - 1];
+ return [...verseIds][[...verseIds].length - 1];
}
getFirst() {
- const { ayahIds } = this.props;
+ const { verseIds } = this.props;
- return [...ayahIds][0];
+ return [...verseIds][0];
}
hasAyahs() {
- return Object.keys(this.props.ayahs).length;
- }
-
- handleVerseDropdownClick = (ayahNum) => {
- const { ayahIds, surah, actions } = this.props; // eslint-disable-line no-shadow
-
- actions.ayah.setCurrentAyah(`${surah.id}:${ayahNum}`);
-
- if (ayahIds.has(ayahNum)) {
- return false;
- }
-
- if (ayahNum > (this.getLast() + 10) || ayahNum < this.getFirst()) {
- // This is beyond lazy loading next page.
- if (actions.push) {
- return actions.push.push(`/${surah.id}/${ayahNum}-${ayahNum + 10}`);
- }
- }
-
- return this.handleLazyLoadAyahs(() => setTimeout(() =>
- scroller.scrollTo(`ayah:${surah.id}:${ayahNum}`),
- 1000)); // then scroll to it
+ return Object.keys(this.props.verses).length;
}
handleLazyLoadAyahs = (callback) => {
- const { ayahIds, surah, isEndOfSurah, options, actions } = this.props; // eslint-disable-line no-shadow, max-len
+ const { verseIds, chapter, isEndOfSurah, options, actions } = this.props; // eslint-disable-line no-shadow, max-len
const range = [this.getFirst(), this.getLast()];
- let size = 10;
-
- if (((range[1] - range[0]) + 1) < 10) {
- size = (range[1] - range[0]) + 1;
- }
-
+ const size = 10;
const from = range[1];
const to = (from + size);
+ const paging = { offset: from, limit: to - from };
- if (!isEndOfSurah && !ayahIds.has(to)) {
- actions.ayah.load(surah.id, from, to, options).then(() => {
+ if (!isEndOfSurah && !verseIds.has(to)) {
+ actions.verse.load(chapter.chapterNumber, paging, options).then(() => {
this.setState({ lazyLoading: false });
- if (callback) {
- callback();
- }
+ return callback && callback();
});
}
@@ -173,27 +137,27 @@ class Surah extends Component {
}
title() {
- const { params, surah } = this.props;
+ const { params, chapter } = this.props;
if (params.range) {
- return `Surah ${surah.name.simple} [${surah.id}:${params.range}]`;
+ return `Surah ${chapter.nameSimple} [${chapter.chapterNumber}:${params.range}]`;
}
- return `Surah ${surah.name.simple} [${surah.id}]`;
+ return `Surah ${chapter.nameSimple} [${chapter.chapterNumber}]`;
}
description() {
- const { params, ayahs, surah } = this.props;
+ const { params, verses, chapter, info } = this.props;
if (params.range) {
if (params.range.includes('-')) {
const [from, to] = params.range.split('-').map(num => parseInt(num, 10));
const array = Array(to - from).fill(from);
const translations = array.map((fromAyah, index) => {
- const ayah = ayahs[`${surah.id}:${fromAyah + index}`];
+ const verse = verses[`${chapter.chapterNumber}:${fromAyah + index}`];
- if (ayah && ayah.content && ayah.content[0]) {
- return ayah.content[0].text;
+ if (verse && verse.content && verse.content[0]) {
+ return verse.content[0].text;
}
return '';
@@ -201,23 +165,51 @@ class Surah extends Component {
const content = translations.join(' - ').slice(0, 250);
- return `Surat ${surah.name.simple} [verse ${params.range}] - ${content}`;
+ return `Surat ${chapter.nameSimple} [verse ${params.range}] - ${content}`;
}
- const ayah = ayahs[`${surah.id}:${params.range}`];
+ const verse = verses[`${chapter.chapterNumber}:${params.range}`];
- if (ayah && ayah.content && ayah.content[0]) {
- return `Surat ${surah.name.simple} [verse ${params.range}] - ${ayah.content[0].text}`;
+ if (verse && verse.content && verse.content[0]) {
+ return `Surat ${chapter.nameSimple} [verse ${params.range}] - ${verse.content[0].text}`;
}
- return `Surat ${surah.name.simple} [verse ${params.range}]`;
+ return `Surat ${chapter.nameSimple} [verse ${params.range}]`;
}
- return `${surah.info ? surah.info.shortDescription : ''} This Surah has ${surah.ayat} ayahs and resides between pages ${surah.page[0]} to ${surah.page[1]} in the Quran.`; // eslint-disable-line max-len
+ return `${info ? info.shortText : ''} This Surah has ${chapter.versesCount} verses and resides between pages ${chapter.pages[0]} to ${chapter.pages[1]} in the Quran.`; // eslint-disable-line max-len
+ }
+
+ renderNoAyah() {
+ const { isLoading } = this.props;
+
+ const noAyah = (
+
+
+ );
+
+ return isLoading ?
: noAyah;
}
renderPagination() {
- const { isLoading, isEndOfSurah, surah } = this.props;
+ const { isSingleAyah, isLoading, isEndOfSurah, chapter } = this.props;
+
+ // If single verse, eh. /2/30
+ if (isSingleAyah) {
+ const to = this.getFirst() + 10 > chapter.versesCount ?
+ chapter.versesCount :
+ this.getFirst() + 10;
+
+ return (
+
+ );
+ }
return (
{
- surah.id > 1 &&
+ chapter.chapterNumber > 1 &&
-
+
←
}
-
+
{
- surah.id < 114 &&
+ chapter.chapterNumber < 114 &&
-
+
→
@@ -260,83 +252,67 @@ class Surah extends Component {
}
}
- loadingComponent={}
+ loadingComponent={}
/>
);
}
renderAyahs() {
const {
- ayahs,
+ chapter,
+ verses,
actions,
options,
bookmarks,
isPlaying,
isAuthenticated,
- currentAyah,
+ currentVerse,
} = this.props; // eslint-disable-line no-shadow
- return Object.values(ayahs).map(ayah => (
- (
+
));
}
renderLines() {
- const { lines, options, currentAyah, isPlaying, actions } = this.props;
+ const { lines, options, currentVerse, isPlaying, actions } = this.props;
const keys = Object.keys(lines);
- return keys.map((lineNum, index) => {
- const nextNum = keys[index + 1];
- const pageNum = lineNum.split('-')[0];
- const line = lines[lineNum];
-
- if (index + 1 !== keys.length && pageNum !== nextNum.split('-')[0]) {
- return [
- ,
-
- ];
- }
-
- return (
-
- );
- });
+ return (
+
+ );
}
render() {
- const { surah, options, actions } = this.props; // eslint-disable-line no-shadow
+ const { chapter, verses, options, info, actions } = this.props; // eslint-disable-line no-shadow
debug('component:Surah', 'Render');
- if (!this.hasAyahs()) return
;
+ if (!this.hasAyahs()) return {this.renderNoAyah()}
;
return (
-
+
-
-
-
+
+
+
{options.isReadingMode ? this.renderLines() : this.renderAyahs()}
-
-
+
+
{this.renderPagination()}
-
+
@@ -402,34 +375,60 @@ class Surah extends Component {
}
}
+Surah.propTypes = {
+ chapter: customPropTypes.surahType.isRequired,
+ chapters: customPropTypes.chapters.isRequired,
+ actions: PropTypes.object.isRequired, // eslint-disable-line
+ lines: PropTypes.object.isRequired, // eslint-disable-line
+ isEndOfSurah: PropTypes.bool.isRequired,
+ verseIds: PropTypes.instanceOf(Set),
+ currentVerse: PropTypes.string,
+ info: customPropTypes.infoType,
+ bookmarks: PropTypes.object.isRequired, // eslint-disable-line
+ isLoading: PropTypes.bool.isRequired,
+ isLoaded: PropTypes.bool.isRequired,
+ isSingleAyah: PropTypes.bool.isRequired,
+ isAuthenticated: PropTypes.bool.isRequired,
+ options: PropTypes.object.isRequired, // eslint-disable-line
+ params: PropTypes.shape({
+ chapterId: PropTypes.string.isRequired
+ }).isRequired,
+ verses: customPropTypes.verses,
+ isPlaying: PropTypes.bool
+};
+
const AsyncSurah = asyncConnect([
- { promise: surahsConnect },
- { promise: surahInfoConnect },
- { promise: ayahsConnect }
+ { promise: chaptersConnect },
+ { promise: chapterInfoConnect },
+ { promise: versesConnect }
])(Surah);
function mapStateToProps(state, ownProps) {
- const surahId = parseInt(ownProps.params.surahId, 10);
- const surah: Object = state.surahs.entities[surahId];
- const ayahs: Object = state.ayahs.entities[surahId];
- const ayahArray = ayahs ? Object.keys(ayahs).map(key => parseInt(key.split(':')[1], 10)) : [];
- const ayahIds = new Set(ayahArray);
- const lastAyahInArray = ayahArray.slice(-1)[0];
+ const chapterId = parseInt(ownProps.params.chapterId, 10);
+ const chapter: Object = state.chapters.entities[chapterId];
+ const verses: Object = state.verses.entities[chapterId];
+ const verseArray = verses ? Object.keys(verses).map(key => parseInt(key.split(':')[1], 10)) : [];
+ const verseIds = new Set(verseArray);
+ const lastAyahInArray = verseArray.slice(-1)[0];
+ const isSingleAyah = !!ownProps.params.range && !ownProps.params.range.includes('-');
+
return {
- surah,
- ayahs,
- ayahIds,
+ chapter,
+ verses,
+ verseIds,
+ isSingleAyah,
+ info: state.chapters.infos[ownProps.params.chapterId],
isStarted: state.audioplayer.isStarted,
isPlaying: state.audioplayer.isPlaying,
- currentAyah: state.audioplayer.currentAyah,
+ currentVerse: state.audioplayer.currentVerse,
isAuthenticated: state.auth.loaded,
- currentWord: state.ayahs.currentWord,
- isEndOfSurah: lastAyahInArray === surah.ayat,
- surahs: state.surahs.entities,
+ currentWord: state.verses.currentWord,
+ isEndOfSurah: lastAyahInArray === chapter.versesCount,
+ chapters: state.chapters.entities,
bookmarks: state.bookmarks.entities,
- isLoading: state.ayahs.loading,
- isLoaded: state.ayahs.loaded,
+ isLoading: state.verses.loading,
+ isLoaded: state.verses.loaded,
lines: state.lines.lines,
options: state.options
};
@@ -439,7 +438,7 @@ function mapDispatchToProps(dispatch) {
return {
actions: {
options: bindActionCreators(OptionsActions, dispatch),
- ayah: bindActionCreators(AyahActions, dispatch),
+ verse: bindActionCreators(AyahActions, dispatch),
audio: bindActionCreators(AudioActions, dispatch),
bookmark: bindActionCreators(BookmarkActions, dispatch),
media: bindActionCreators(MediaActions, dispatch),
diff --git a/src/customPropTypes.js b/src/customPropTypes.js
new file mode 100644
index 000000000..edc2b5d3f
--- /dev/null
+++ b/src/customPropTypes.js
@@ -0,0 +1,226 @@
+import { PropTypes } from 'react';
+
+export const bookmarkActions = PropTypes.shape({
+ isLoaded: PropTypes.func.isRequired,
+ load: PropTypes.func.isRequired,
+ addBookmark: PropTypes.func.isRequired,
+ removeBookmark: PropTypes.func.isRequired
+});
+
+export const bookmarkType = PropTypes.shape({
+ id: PropTypes.string.isRequired,
+ verseKey: PropTypes.string.isRequired,
+ createdAt: PropTypes.string.isRequired,
+ updatedAt: PropTypes.string.isRequired,
+});
+
+export const contentType = PropTypes.shape({
+ id: PropTypes.number,
+ authorName: PropTypes.string,
+ languageName: PropTypes.string
+});
+
+export const footNoteType = PropTypes.shape({
+ id: PropTypes.number,
+ text: PropTypes.string,
+ language_name: PropTypes.string
+});
+
+export const infoType = PropTypes.shape({
+ chapterId: PropTypes.number.isRequired,
+ text: PropTypes.string.isRequired,
+ source: PropTypes.string.isRequired,
+ shortText: PropTypes.string.isRequired,
+ languageName: PropTypes.string.isRequired
+});
+
+export const media = PropTypes.shape({
+ content: PropTypes.object
+});
+
+export const mediaActions = PropTypes.shape({
+ setMedia: PropTypes.func.isRequired,
+ removeMedia: PropTypes.func.isRequired
+});
+
+export const audioActions = PropTypes.shape({
+ pause: PropTypes.func.isRequired,
+ setAyah: PropTypes.func.isRequired,
+ play: PropTypes.func.isRequired,
+ setCurrentWord: PropTypes.func.isRequired,
+});
+
+export const language = PropTypes.shape({
+ beta: PropTypes.bool,
+ direction: PropTypes.string.isRequired,
+ english: PropTypes.string.isRequired,
+ esAnalyzerDefault: PropTypes.string,
+ languageCode: PropTypes.string.isRequired,
+ priority: PropTypes.number.isRequired,
+ unicode: PropTypes.string,
+});
+
+export const matchType = PropTypes.shape({
+ score: PropTypes.number.isRequired,
+ text: PropTypes.string.isRequired,
+ languageCode: PropTypes.string.isRequired,
+ subType: PropTypes.string.isRequired,
+ cardinalityType: PropTypes.string.isRequired,
+ name: PropTypes.string.isRequired,
+ resourceId: PropTypes.number.isRequired,
+ description: PropTypes.string,
+ language: language.isRequired,
+ sourceId: PropTypes.number.isRequired,
+ type: PropTypes.string.isRequired,
+ authorId: PropTypes.number.isRequired,
+ slug: PropTypes.string.isRequired,
+ isAvailable: PropTypes.bool
+});
+
+export const fontSize = PropTypes.shape({
+ arabic: PropTypes.number,
+ translations: PropTypes.number
+});
+
+export const location = PropTypes.shape({
+ action: PropTypes.string,
+ hash: PropTypes.string,
+ pathname: PropTypes.string,
+ search: PropTypes.string,
+ query: PropTypes.objectOf(PropTypes.string)
+});
+
+export const optionsType = PropTypes.shape({
+ isReadingMode: PropTypes.bool,
+ isShowingSurahInfo: PropTypes.bool,
+ audio: PropTypes.number,
+ quran: PropTypes.number,
+ content: PropTypes.arrayOf(PropTypes.number),
+ tooltip: PropTypes.string,
+ fontSize: fontSize.isRequired
+});
+
+export const recitationTypes = PropTypes.shape({
+ id: PropTypes.number,
+ style: PropTypes.string,
+ reciter_name_eng: PropTypes.string
+});
+
+export const surahType = PropTypes.shape({
+ id: PropTypes.number.isRequired,
+ versesCount: PropTypes.number.isRequired,
+ bismillahPre: PropTypes.bool.isRequired,
+ revelationOrder: PropTypes.number.isRequired,
+ revelationPlace: PropTypes.string.isRequired,
+ pages: PropTypes.arrayOf(PropTypes.number).isRequired,
+ nameComplex: PropTypes.string.isRequired,
+ nameSimple: PropTypes.string.isRequired,
+ nameArabic: PropTypes.string.isRequired,
+});
+
+export const timeInterval = PropTypes.shape({
+ from: PropTypes.number,
+ to: PropTypes.number,
+ time: PropTypes.number,
+});
+
+export const translationType = PropTypes.shape({
+ languageName: PropTypes.string.isRequired,
+ text: PropTypes.string.isRequired,
+ resourceName: PropTypes.string.isRequired
+});
+
+export const translation = PropTypes.shape({
+ languageName: PropTypes.string,
+ text: PropTypes.string
+});
+
+export const transliteration = PropTypes.shape({
+ languageName: PropTypes.string,
+ text: PropTypes.string
+});
+
+export const translationOptions = PropTypes.arrayOf(contentType);
+
+export const userType = PropTypes.shape({
+ provider: PropTypes.string,
+ uid: PropTypes.string,
+ firstName: PropTypes.string,
+ lastName: PropTypes.string,
+ username: PropTypes.string,
+ link: PropTypes.string,
+ image: PropTypes.string,
+ email: PropTypes.string,
+ name: PropTypes.string
+});
+
+export const wordType = PropTypes.shape({
+ arabic: PropTypes.string,
+ verseKey: PropTypes.string.isRequired,
+ charType: PropTypes.string.isRequired,
+ className: PropTypes.string.isRequired,
+ code: PropTypes.string.isRequired,
+ lineNumber: PropTypes.number.isRequired,
+ pageNumber: PropTypes.number.isRequired,
+ position: PropTypes.number.isRequired,
+ translation,
+ transliteration,
+ wordId: PropTypes.number
+});
+
+export const verseType = PropTypes.shape({
+ id: PropTypes.number.isRequired,
+ chapterId: PropTypes.number.isRequired,
+ pageNumber: PropTypes.number.isRequired,
+ juzNumber: PropTypes.number.isRequired,
+ hizbNumber: PropTypes.number.isRequired,
+ rubNumber: PropTypes.number.isRequired,
+ verseKey: PropTypes.string.isRequired,
+ sajdah: PropTypes.bool,
+ words: PropTypes.arrayOf(wordType).isRequired,
+ textMadani: PropTypes.string.isRequired,
+ textSimple: PropTypes.string.isRequired,
+ translations: PropTypes.arrayOf(translationType), // NOTE: In search, it is not required.
+ audio: PropTypes.object // NOTE: In search, it is not required.
+});
+
+export const verses = PropTypes.objectOf(verseType);
+
+export const words = PropTypes.shape({
+ startTime: PropTypes.number.isRequired,
+ endTime: PropTypes.number.isRequired,
+ duration: PropTypes.number.isRequired,
+});
+
+export const segmentType = PropTypes.shape({
+ words,
+ intervals: PropTypes.oneOfType([
+ PropTypes.array,
+ PropTypes.object
+ ]) // TODO: This should be done a better way.
+});
+
+export const segments = PropTypes.objectOf(segmentType);
+
+export const match = PropTypes.arrayOf(matchType);
+
+export const chapters = PropTypes.objectOf(surahType);
+
+export const line = PropTypes.arrayOf(wordType);
+
+export const recitations = PropTypes.arrayOf(recitationTypes);
+
+export const suggestion = PropTypes.shape({
+ ayah: PropTypes.string,
+ href: PropTypes.string.isRequired,
+ text: PropTypes.string.isRequired
+});
+
+export const suggestions = PropTypes.arrayOf(suggestion);
+
+export const storeText = PropTypes.shape({
+ ios: PropTypes.string.isRequired,
+ android: PropTypes.string.isRequired,
+ windows: PropTypes.string.isRequired,
+ kindle: PropTypes.string.isRequired,
+});
diff --git a/src/helpers/ApiClient.js b/src/helpers/ApiClient.js
index 0462f9461..bf1af4062 100644
--- a/src/helpers/ApiClient.js
+++ b/src/helpers/ApiClient.js
@@ -7,6 +7,10 @@ import config from 'config';
const methods = ['get', 'post', 'put', 'patch', 'del'];
+function contentLanguage() {
+ return cookie.load('currentLocale') || config.defaultLocale;
+}
+
function formatUrl(path) {
const adjustedPath = path[0] !== '/' ? `/${path}` : path;
@@ -32,11 +36,13 @@ export default class {
new Promise((resolve, reject) => {
const request = superagent[method](formatUrl(path));
- if (params) {
- request.query(qs.stringify(decamelizeKeys(params), {
- arrayFormat: arrayFormat || 'brackets'
- }));
- }
+ params = params || {}; // eslint-disable-line no-param-reassign
+
+ params.language = params.language || contentLanguage(); // eslint-disable-line
+
+ request.query(qs.stringify(decamelizeKeys(params), {
+ arrayFormat: arrayFormat || 'brackets'
+ }));
if (cookie.load('auth')) {
const headers = cookie.load('auth');
diff --git a/src/helpers/Html.js b/src/helpers/Html.js
index 2b5ecbdff..be1b935c1 100644
--- a/src/helpers/Html.js
+++ b/src/helpers/Html.js
@@ -42,6 +42,7 @@ const Html = ({ store, component, assets }) => {
})(window,document,'script','//www.google-analytics.com/analytics.js','ga');
ga('create', 'UA-8496014-1', 'auto');
+ ga('require', 'GTM-PNMFTW3');
`
}}
charSet="UTF-8"
@@ -79,9 +80,6 @@ const Html = ({ store, component, assets }) => {
{Object.keys(assets.javascript).map((script, i) =>
)}
- {
-
- }