diff --git a/.gitignore b/.gitignore
index 135ab60a2..707bb3209 100644
--- a/.gitignore
+++ b/.gitignore
@@ -15,3 +15,4 @@ tests/functional/output/*
test/functional/screenshots/*
.ssh
webpack-stats.debug.json
+*.DS_Store
diff --git a/src/components/Ayah/index.js b/src/components/Ayah/index.js
index eaba2464b..1ab4d87c4 100644
--- a/src/components/Ayah/index.js
+++ b/src/components/Ayah/index.js
@@ -99,7 +99,7 @@ export default class Ayah extends Component {
renderMedia() {
const { ayah, mediaActions } = this.props;
- if (!ayah.mediaContent.length) return false;
+ if (!!ayah.mediaContent) return false;
return (
diff --git a/src/components/SmartBanner/index.js b/src/components/SmartBanner/index.js
new file mode 100644
index 000000000..ee306d183
--- /dev/null
+++ b/src/components/SmartBanner/index.js
@@ -0,0 +1,183 @@
+import React, { Component, PropTypes } from 'react';
+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: {},
+ deviceType: '',
+ appId: ''
+ };
+
+ setSettings(forceDeviceType) {
+ const agent = useragent.parse(window.navigator.userAgent);
+ let deviceType = '';
+ const osVersion = parseInt(agent.version, 10);
+
+ if (forceDeviceType) {
+ deviceType = forceDeviceType;
+ } else if ((agent.isAndroid || agent.isAndroidTablet) && (agent.isChrome ? osVersion < 44 : true)) {
+ deviceType = 'android';
+ } else if ((agent.isiPad || agent.isiPhone) && (agent.isSafari ? osVersion < 6 : true)) {
+ deviceType = 'ios';
+ }
+
+ this.setState({deviceType: deviceType});
+ if (deviceType) {
+ this.setSettingsForDevice(deviceType);
+ }
+ }
+
+ parseAppId(metaName) {
+ const meta = window.document.querySelector(`meta[name="${metaName}"]`);
+ return /app-id=([^\s,]+)/.exec(meta.getAttribute('content'))[1];;
+ }
+
+ setSettingsForDevice(deviceType) {
+ const mixins = {
+ ios: {
+ icon: 'app-banner-ios.jpg',
+ appMeta: 'google-play-app',
+ getStoreLink: () =>
+ `https://itunes.apple.com/${this.props.appStoreLanguage}/app/id`,
+ },
+ android: {
+ icon: 'app-banner-android.png',
+ appMeta: 'apple-itunes-app',
+ getStoreLink: () =>
+ 'http://play.google.com/store/apps/details?id=',
+ }
+ };
+
+ if (mixins[deviceType]) {
+ this.setState({
+ settings: mixins[deviceType],
+ appId: this.parseAppId(mixins[deviceType].appMeta)
+ });
+ }
+ }
+
+ hide() {
+ window.document.querySelector('html').classList.remove('smartbanner-show');
+ }
+
+ show() {
+ window.document.querySelector('html').classList.add('smartbanner-show');
+ }
+
+ close() {
+ this.hide();
+ cookie.save('smartbanner-closed', 'true', {
+ path: '/',
+ expires: +new Date() + this.props.daysHidden * 1000 * 60 * 60 * 24,
+ });
+ }
+
+ install() {
+ this.hide();
+ cookie.save('smartbanner-installed', 'true', {
+ path: '/',
+ expires: +new Date() + this.props.daysReminder * 1000 * 60 * 60 * 24,
+ });
+ }
+
+ retrieveInfo() {
+ const link = this.state.settings.getStoreLink() + this.state.appId;
+ const inStore = `
+ ${this.props.price[this.state.deviceType]} - ${this.props.storeText[this.state.deviceType]}`;
+ const icon = require(`../../../static/images/${this.state.settings.icon}`);
+
+ return {
+ icon,
+ link,
+ inStore,
+ };
+ }
+
+ componentDidMount() {
+ if (__CLIENT__) {
+ this.setSettings(this.props.force);
+ }
+ }
+
+ render() {
+ // Don't show banner when:
+ // 1) if device isn't iOS or Android
+ // 2) website is loaded in app,
+ // 3) user dismissed banner,
+ // 4) or we have no app id in meta
+
+ if (!this.state.deviceType
+ || window.navigator.standalone
+ || cookie.load('smartbanner-closed')
+ || cookie.load('smartbanner-installed')) {
+ return null;
+ }
+
+ if (!this.state.appId) {
+ return null;
+ }
+
+ this.show();
+
+ const { icon, link, inStore } = this.retrieveInfo();
+ const wrapperClassName = `smartbanner smartbanner-${this.state.deviceType}`;
+ const iconStyle = {
+ backgroundImage: `url(${icon})`,
+ };
+
+ return (
+
+ );
+ }
+}
+
+export default SmartBanner;
diff --git a/src/config.js b/src/config.js
index 53a406b34..1f729c683 100644
--- a/src/config.js
+++ b/src/config.js
@@ -45,11 +45,43 @@ module.exports = Object.assign({
{name: 'twitter:description', content: description},
{name: 'twitter:image', content: 'https://quran.com/images/thumbnail.png'},
{name: 'twitter:image:width', content: '200'},
- {name: 'twitter:image:height', content: '200'}
+ {name: 'twitter:image:height', content: '200'},
+ {name: 'google-play-app', content: 'app-id=com.quran.labs.androidquran'},
+ {name: 'apple-itunes-app', content: 'app-id=1118663303' },
+ {name: 'mobile-web-app-capable', content: 'yes'},
+ {name: 'apple-mobile-web-app-capable', content: 'yes'},
+ {name: 'apple-mobile-web-app-title', content: title},
+ {name: 'apple-mobile-web-app-status-bar-style', content: 'black'},
+ {name: 'application-name', content: 'Al-Quran - القرآن الكريم'},
+ {name: 'msapplication-TileColor', content: '#004f54'},
+ {name: 'msapplication-tooltip', content: description},
+ {name: 'msapplication-starturl', content: "https://quran.com"},
+ {name: 'msapplication-navbutton-color', content: '#004f54'},
+ {name: 'msapplication-square70x70logo', content: '/mstitle-70x70.jpg'},
+ {name: 'msapplication-square150x150logo', content: '/mstitle-150x150.jpg'},
+ {name: 'msapplication-wide310x150logo', content: '/mstitle-310x150.jpg'},
+ {name: 'msapplication-square310x310logo', content: '/mstitle-310x310.jpg'}
],
link: [
- {rel: 'apple-touch-icon', href: '/images/apple-touch-icon.png'},
- {rel: 'apple-touch-icon-precomposed', href: '/images/apple-touch-icon-precomposed.png'},
+ {rel: 'manifest', href: 'manifest.json'},
+ {rel: 'search', type: 'application/opensearchdescription+xml', href: '/opensearch.xml', title: 'Quran.com'},
+ {rel:'fluid-icon', href: '/apple-touch-icon-180x180.png', title: 'Quran.com'},
+ {rel: 'icon', type: 'image/png', href: '/favicon-32x32.png', sizes:'32x32'},
+ {rel: 'icon', type: 'image/png', href: '/android-chrome-192x192.png', sizes: '192x192'},
+ {rel: 'icon', type: 'image/png', href: '/favicon-16x16.png', sizes: '16x16'},
+ {rel: 'mask-icon', href: '/safari-pinned-tab.svg', color: '#004f54'},
+ {rel: 'shortcut icon', href: '/favicon.ico', type: 'image/x-icon'},
+ {rel: 'apple-touch-icon', href: 'apple-touch-icon.png'},
+ {rel: 'apple-touch-icon', sizes: '57x57', href:' /apple-touch-icon-57x57.png'},
+ {rel: 'apple-touch-icon', sizes: '72x72', href: '/apple-touch-icon-72x72.png'},
+ {rel: 'apple-touch-icon', sizes: '76x76', href: '/apple-touch-icon-76x76.png'},
+ {rel: 'apple-touch-icon', sizes: '114x114', href: '/apple-touch-icon-114x114.png'},
+ {rel: 'apple-touch-icon', sizes: '120x120', href: '/apple-touch-icon-120x120.png'},
+ {rel: 'apple-touch-icon', sizes: '144x144', href: '/apple-touch-icon-144x144.png'},
+ {rel: 'apple-touch-icon', sizes: '152x152', href: '/apple-touch-icon-152x152.png'},
+ {rel: 'apple-touch-icon', sizes: '180x180', href: '/apple-touch-icon-180x180.png'},
+ {rel: 'preconnect', href: 'https://quran-1f14.kxcdn.com', crossorigin: ''},
+ {rel: 'preconnect', href: 'https://assets-1f14.kxcdn.com', crossorigin: ''},
{rel: 'stylesheet', href: 'https://maxcdn.bootstrapcdn.com/font-awesome/4.6.3/css/font-awesome.min.css'}
],
/* SEO: https://developers.google.com/structured-data/slsb-overview#markup_examples */
diff --git a/src/containers/App/index.js b/src/containers/App/index.js
index 39c5bb204..8ed722076 100644
--- a/src/containers/App/index.js
+++ b/src/containers/App/index.js
@@ -5,6 +5,8 @@ import { connect } from 'react-redux';
import { asyncConnect } from 'redux-connect';
import Helmet from 'react-helmet';
import Modal from 'react-bootstrap/lib/Modal';
+import SmartBanner from 'components/SmartBanner';
+
const ModalHeader = Modal.Header;
const ModalTitle = Modal.Title;
const ModalBody = Modal.Body;
@@ -40,6 +42,7 @@ class App extends Component {
{children}
+
diff --git a/src/containers/Surah/style.scss b/src/containers/Surah/style.scss
index f61e2eb0a..91f45baec 100644
--- a/src/containers/Surah/style.scss
+++ b/src/containers/Surah/style.scss
@@ -27,9 +27,9 @@
}
:local .surah-container {
- padding-top: 130px;
+ padding-top: 50px;
@media(max-width: $screen-xs-max) {
- padding-top: 100px;
+ padding-top: 50px;
}
}
diff --git a/src/helpers/Html.js b/src/helpers/Html.js
index 2fbad77d6..0077caa1e 100644
--- a/src/helpers/Html.js
+++ b/src/helpers/Html.js
@@ -46,6 +46,12 @@ const Html = ({ store, component, assets }) => {
}}
charSet="UTF-8"
/>
+