Create new settings page

[MAILPOET-2676]
The new settings can be accessed via `?page=mailpoet-new-settings`
I don't think we need a flag for this because it's already hidden.
This commit is contained in:
Amine Ben hammou
2020-03-02 18:55:45 +01:00
committed by amine-mp
parent 2917361a98
commit 1d029374e8
16 changed files with 466 additions and 276 deletions

View File

@@ -1,87 +0,0 @@
import React from 'react';
import PropTypes from 'prop-types';
import { partial } from 'underscore';
import MailPoet from 'mailpoet';
import SenderEmailAddressWarning from 'common/sender_email_address_warning.jsx';
class DefaultSender extends React.Component {
constructor(props) {
super(props);
this.state = {
senderAddress: props.senderAddress,
senderName: props.senderName,
replyToName: props.replyToName,
replyToAddress: props.replyToAddress,
};
this.onChange = this.onChange.bind(this);
}
onChange(field, e) {
const newState = {};
newState[field] = e.target.value;
this.setState(newState);
}
render() {
return (
<>
<p>
<label htmlFor="settings[from_name]">{MailPoet.I18n.t('from')}</label>
<input
type="text"
id="settings[from_name]"
data-automation-id="settings-page-from-name-field"
name="sender[name]"
value={this.state.senderName}
onChange={partial(this.onChange, 'senderName')}
placeholder={MailPoet.I18n.t('yourName')}
/>
<input
type="email"
id="settings[from_email]"
name="sender[address]"
data-automation-id="settings-page-from-email-field"
value={this.state.senderAddress}
onChange={partial(this.onChange, 'senderAddress')}
placeholder="from@mydomain.com"
/>
</p>
<div className="regular-text">
<SenderEmailAddressWarning
emailAddress={this.state.senderAddress}
mssActive={this.props.mssActive}
/>
</div>
<p>
<label htmlFor="settings[reply_name]">{MailPoet.I18n.t('replyTo')}</label>
<input
type="text"
id="settings[reply_name]"
name="reply_to[name]"
value={this.state.replyToName}
onChange={partial(this.onChange, 'replyToName')}
placeholder={MailPoet.I18n.t('yourName')}
/>
<input
type="email"
id="settings[reply_email]"
name="reply_to[address]"
value={this.state.replyToAddress}
onChange={partial(this.onChange, 'replyToAddress')}
placeholder="reply_to@mydomain.com"
/>
</p>
</>
);
}
}
DefaultSender.propTypes = {
senderAddress: PropTypes.string.isRequired,
senderName: PropTypes.string.isRequired,
replyToAddress: PropTypes.string.isRequired,
replyToName: PropTypes.string.isRequired,
mssActive: PropTypes.bool.isRequired,
};
export default DefaultSender;

View File

@@ -0,0 +1,9 @@
import React from 'react';
import ReactDOM from 'react-dom';
const App = () => <h1 className="title">Settings</h1>;
const container = document.getElementById('settings_container');
if (container) {
ReactDOM.render(<App />, container);
}

View File

@@ -1,23 +0,0 @@
import PropTypes from 'prop-types';
import React from 'react';
import MailPoet from 'mailpoet';
const keyValidMessage = () => (
<div className="mailpoet_success_item mailpoet_success">
{MailPoet.I18n.t('premiumTabKeyValidMessage')}
</div>
);
const keyNotValidMessage = () => (
<div className="mailpoet_error_item mailpoet_error">
{MailPoet.I18n.t('premiumTabKeyNotValidMessage')}
</div>
);
const KeyMessages = (props) => (props.keyValid ? keyValidMessage() : keyNotValidMessage());
KeyMessages.propTypes = {
keyValid: PropTypes.bool.isRequired,
};
export default KeyMessages;

View File

@@ -1,57 +0,0 @@
import PropTypes from 'prop-types';
import React from 'react';
import MailPoet from 'mailpoet';
const MssStatus = {
KEY_INVALID: 0,
KEY_VALID_MSS_NOT_ACTIVE: 1,
KEY_VALID_MSS_ACTIVE: 2,
};
const activeMessage = () => (
<div className="mailpoet_success mailpoet_mss_key_valid">
{MailPoet.I18n.t('premiumTabMssActiveMessage')}
</div>
);
const notValidMessage = (message) => (
<div className="mailpoet_error">
{message || MailPoet.I18n.t('premiumTabMssKeyNotValidMessage')}
</div>
);
const mssNotActiveMessage = (activationCallback) => (
<div className="mailpoet_error">
{MailPoet.I18n.t('premiumTabMssNotActiveMessage')}
{' '}
<button type="button" className="button-link" onClick={activationCallback}>
{MailPoet.I18n.t('premiumTabMssActivateMessage')}
</button>
</div>
);
const MssMessages = (props) => {
switch (props.keyStatus) {
case MssStatus.KEY_VALID_MSS_ACTIVE:
return activeMessage();
case MssStatus.KEY_VALID_MSS_NOT_ACTIVE:
return mssNotActiveMessage(props.activationCallback);
case MssStatus.KEY_INVALID:
return notValidMessage(props.keyMessage);
default:
return null;
}
};
MssMessages.propTypes = {
keyStatus: PropTypes.number.isRequired,
keyMessage: PropTypes.string,
activationCallback: PropTypes.func.isRequired,
};
MssMessages.defaultProps = {
keyMessage: null,
};
export { MssStatus, MssMessages };

View File

@@ -1,88 +0,0 @@
import PropTypes from 'prop-types';
import React from 'react';
import ReactStringReplace from 'react-string-replace';
import MailPoet from 'mailpoet';
const PremiumInstallationStatus = {
INSTALL_INSTALLING: 0,
INSTALL_ACTIVATING: 1,
INSTALL_DONE: 2,
INSTALL_INSTALLING_ERROR: 3,
INSTALL_ACTIVATING_ERROR: 4,
ACTIVATE_ACTIVATING: 5,
ACTIVATE_DONE: 6,
ACTIVATE_ERROR: 7,
};
const keyPrefix = 'premium-installation-message';
const installingMessage = () => (
<div className="mailpoet_subitem" key={`${keyPrefix}-installing`}>
{MailPoet.I18n.t('premiumTabPremiumInstallationInstallingMessage')}
</div>
);
const activatingMessage = () => (
<div className="mailpoet_subitem" key={`${keyPrefix}-activating`}>
{MailPoet.I18n.t('premiumTabPremiumInstallationActivatingMessage')}
</div>
);
const doneMessage = () => (
<div className="mailpoet_subitem" key={`${keyPrefix}-done`}>
<strong>{MailPoet.I18n.t('premiumTabPremiumInstallationActiveMessage')}</strong>
</div>
);
const errorMessage = () => {
const links = ['https://account.mailpoet.com', 'https://www.mailpoet.com/support/'];
const message = ReactStringReplace(
MailPoet.I18n.t('premiumTabPremiumInstallationErrorMessage'),
/\[link\](.*?)\[\/link\]/g,
(match, i) => (
<a
key={i}
href={links.shift()}
target="_blank"
rel="noopener noreferrer"
>
{match}
</a>
)
);
return (
<div className="mailpoet_subitem" key={`${keyPrefix}-error`}>
<strong>{message}</strong>
</div>
);
};
const PremiumInstallationMessages = (props) => {
switch (props.installationStatus) {
case PremiumInstallationStatus.INSTALL_INSTALLING:
return installingMessage();
case PremiumInstallationStatus.INSTALL_ACTIVATING:
return [installingMessage(), activatingMessage()];
case PremiumInstallationStatus.INSTALL_DONE:
return [installingMessage(), activatingMessage(), doneMessage()];
case PremiumInstallationStatus.INSTALL_INSTALLING_ERROR:
return [installingMessage(), errorMessage()];
case PremiumInstallationStatus.INSTALL_ACTIVATING_ERROR:
return [installingMessage(), activatingMessage(), errorMessage()];
case PremiumInstallationStatus.ACTIVATE_ACTIVATING:
return activatingMessage();
case PremiumInstallationStatus.ACTIVATE_DONE:
return [activatingMessage(), doneMessage()];
case PremiumInstallationStatus.ACTIVATE_ERROR:
return [activatingMessage(), errorMessage()];
default:
return null;
}
};
PremiumInstallationMessages.propTypes = {
installationStatus: PropTypes.number,
};
export { PremiumInstallationStatus, PremiumInstallationMessages };

View File

@@ -1,111 +0,0 @@
import PropTypes from 'prop-types';
import React from 'react';
import MailPoet from 'mailpoet';
import { PremiumInstallationMessages } from 'settings/premium_tab/messages/premium_installation_messages.jsx';
const PremiumStatus = {
KEY_INVALID: 0,
KEY_VALID_PREMIUM_PLUGIN_NOT_INSTALLED: 1,
KEY_VALID_PREMIUM_PLUGIN_NOT_ACTIVE: 2,
KEY_VALID_PREMIUM_PLUGIN_ACTIVE: 3,
KEY_VALID_PREMIUM_PLUGIN_BEING_INSTALLED: 4,
KEY_VALID_PREMIUM_PLUGIN_BEING_ACTIVATED: 5,
};
const activeMessage = () => (
<div className="mailpoet_success">
{MailPoet.I18n.t('premiumTabPremiumActiveMessage')}
</div>
);
const installingMessage = () => (
<div className="mailpoet_success">
{MailPoet.I18n.t('premiumTabPremiumInstallingMessage')}
</div>
);
const activatingMessage = () => (
<div className="mailpoet_success">
{MailPoet.I18n.t('premiumTabPremiumActivatingMessage')}
</div>
);
const premiumNotInstalledMessage = (installationCallback) => (
<div className="mailpoet_error">
{MailPoet.I18n.t('premiumTabPremiumNotInstalledMessage')}
{' '}
<button type="button" className="button-link" onClick={installationCallback}>
{MailPoet.I18n.t('premiumTabPremiumInstallMessage')}
</button>
</div>
);
const premiumNotActiveMessage = (activationCallback) => (
<div className="mailpoet_error">
{MailPoet.I18n.t('premiumTabPremiumNotActiveMessage')}
{' '}
<button type="button" className="button-link" onClick={activationCallback}>
{MailPoet.I18n.t('premiumTabPremiumActivateMessage')}
</button>
</div>
);
const notValidMessage = (message) => (
<div className="mailpoet_error">
{message || MailPoet.I18n.t('premiumTabPremiumKeyNotValidMessage')}
</div>
);
const getMessageFromStatus = (status, message, installationCallback, activationCallback) => {
switch (status) {
case PremiumStatus.KEY_VALID_PREMIUM_PLUGIN_ACTIVE:
return activeMessage();
case PremiumStatus.KEY_VALID_PREMIUM_PLUGIN_NOT_ACTIVE:
return premiumNotActiveMessage(activationCallback);
case PremiumStatus.KEY_VALID_PREMIUM_PLUGIN_NOT_INSTALLED:
return premiumNotInstalledMessage(installationCallback);
case PremiumStatus.KEY_VALID_PREMIUM_PLUGIN_BEING_INSTALLED:
return installingMessage();
case PremiumStatus.KEY_VALID_PREMIUM_PLUGIN_BEING_ACTIVATED:
return activatingMessage();
case PremiumStatus.KEY_INVALID:
return notValidMessage(message);
default:
return null;
}
};
const PremiumMessages = (props) => {
const message = getMessageFromStatus(
props.keyStatus,
props.keyMessage,
props.installationCallback,
props.activationCallback
);
if (!message) {
return null;
}
return (
<>
{message}
<PremiumInstallationMessages installationStatus={props.installationStatus} />
</>
);
};
PremiumMessages.propTypes = {
keyStatus: PropTypes.number.isRequired,
keyMessage: PropTypes.string,
installationStatus: PropTypes.number,
installationCallback: PropTypes.func.isRequired,
activationCallback: PropTypes.func.isRequired,
};
PremiumMessages.defaultProps = {
keyMessage: null,
installationStatus: null,
};
export { PremiumStatus, PremiumMessages };

View File

@@ -1,265 +0,0 @@
import PropTypes from 'prop-types';
import React, { useMemo, useState } from 'react';
import ReactDOM from 'react-dom';
import MailPoet from 'mailpoet';
import KeyMessages from 'settings/premium_tab/messages/key_messages.jsx';
import { MssStatus, MssMessages } from 'settings/premium_tab/messages/mss_messages.jsx';
import { PremiumStatus, PremiumMessages } from 'settings/premium_tab/messages/premium_messages.jsx';
import { PremiumInstallationStatus } from 'settings/premium_tab/messages/premium_installation_messages.jsx';
const requestServicesApi = async (key, action) => MailPoet.Ajax.post({
api_version: window.mailpoet_api_version,
endpoint: 'services',
action,
data: { key },
});
const requestPremiumApi = async (action) => MailPoet.Ajax.post({
api_version: window.mailpoet_api_version,
endpoint: 'premium',
action,
});
const activateMss = async (key) => MailPoet.Ajax.post({
api_version: window.mailpoet_api_version,
endpoint: 'settings',
action: 'set',
data: {
mta_group: 'mailpoet',
mta: {
method: 'MailPoet',
mailpoet_api_key: key,
},
signup_confirmation: {
enabled: '1',
},
},
});
const PremiumTab = (props) => {
const [key, setKey] = useState(props.activationKey);
const [premiumStatus, setPremiumStatus] = useState(key ? props.premiumStatus : null);
const [premiumMessage, setPremiumMessage] = useState(null);
const [premiumInstallationStatus, setPremiumInstallationStatus] = useState(null);
const [mssStatus, setMssStatus] = useState(key ? props.mssStatus : null);
const [mssKeyMessage, setMssKeyMessage] = useState(null);
// key is considered valid if either Premium or MSS check passes
const keyValid = useMemo(() => {
if (premiumStatus > PremiumStatus.KEY_INVALID || mssStatus > MssStatus.KEY_INVALID) {
return true;
}
return (premiumStatus === null || mssStatus === null) ? null : false;
}, [premiumStatus, mssStatus]);
const activatePremiumPlugin = async (isAfterInstall = false) => {
const status = PremiumInstallationStatus;
const activateStatus = isAfterInstall ? status.INSTALL_ACTIVATING : status.ACTIVATE_ACTIVATING;
const doneStatus = isAfterInstall ? status.INSTALL_DONE : status.ACTIVATE_DONE;
const errorStatus = isAfterInstall ? status.INSTALL_ACTIVATING_ERROR : status.ACTIVATE_ERROR;
setPremiumInstallationStatus(activateStatus);
try {
await requestPremiumApi('activatePlugin');
} catch (error) {
setPremiumInstallationStatus(errorStatus);
return false;
}
setPremiumInstallationStatus(doneStatus);
return true;
};
const installPremiumPlugin = async () => {
setPremiumInstallationStatus(PremiumInstallationStatus.INSTALL_INSTALLING);
try {
await requestPremiumApi('installPlugin');
} catch (error) {
setPremiumInstallationStatus(PremiumInstallationStatus.INSTALL_INSTALLING_ERROR);
return false;
}
return activatePremiumPlugin(true);
};
const verifyMailPoetPremiumKey = async () => {
try {
const response = await requestServicesApi(key, 'checkPremiumKey');
setPremiumMessage(null);
// install/activate Premium plugin
let pluginActive = response.meta.premium_plugin_active;
if (!response.meta.premium_plugin_installed) {
setPremiumStatus(PremiumStatus.KEY_VALID_PREMIUM_PLUGIN_BEING_INSTALLED);
pluginActive = await installPremiumPlugin();
} else if (!response.meta.premium_plugin_active) {
setPremiumStatus(PremiumStatus.KEY_VALID_PREMIUM_PLUGIN_BEING_ACTIVATED);
pluginActive = await activatePremiumPlugin();
} else {
setPremiumStatus(PremiumStatus.KEY_VALID_PREMIUM_PLUGIN_ACTIVE);
}
MailPoet.trackEvent(
'User has validated a Premium key',
{
'MailPoet Free version': window.mailpoet_version,
'Premium plugin is active': pluginActive,
}
);
} catch (error) {
setPremiumStatus(PremiumStatus.KEY_INVALID);
setPremiumMessage(error.errors.map((e) => e.message).join(' ') || null);
MailPoet.trackEvent(
'User has failed to validate a Premium key',
{
'MailPoet Free version': window.mailpoet_version,
'Premium plugin is active': props.premiumPluginActive,
}
);
}
};
async function verifyMailPoetSendingServiceKey(activateMssIfKeyValid) {
try {
const response = await requestServicesApi(key, 'checkMSSKey');
setMssKeyMessage(response.data.message || null);
if (activateMssIfKeyValid) {
await activateMss(key);
setMssStatus(MssStatus.KEY_VALID_MSS_ACTIVE);
} else {
setMssStatus(MssStatus.KEY_VALID_MSS_NOT_ACTIVE);
}
} catch (error) {
setMssStatus(MssStatus.KEY_INVALID);
setMssKeyMessage(error.errors.map((e) => e.message).join(' ') || null);
}
window.updateMSSActivationUI();
}
return (
<table className="form-table">
<tbody>
<tr>
<th scope="row">
<label htmlFor="mailpoet_premium_key">
{MailPoet.I18n.t('premiumTabActivationKeyLabel')}
</label>
<p className="description">
{MailPoet.I18n.t('premiumTabDescription')}
</p>
</th>
<td>
<div>
<input
type="text"
id="mailpoet_premium_key"
className="regular-text"
name="premium[premium_key]"
value={key || ''}
onChange={(event) => {
setKey(event.target.value.trim() || null);
setPremiumStatus(null);
setPremiumInstallationStatus(null);
setMssStatus(null);
}}
/>
<button
type="button"
id="mailpoet_premium_key_verify"
className="button-secondary"
onClick={async (event) => {
if (!key) {
MailPoet.Notice.error(
MailPoet.I18n.t('premiumTabNoKeyNotice'),
{ scroll: true },
);
return;
}
setPremiumStatus(null);
setPremiumInstallationStatus(null);
setMssStatus(null);
MailPoet.Modal.loading(true);
const isUserTriggered = event.isTrusted;
await verifyMailPoetSendingServiceKey(isUserTriggered);
await verifyMailPoetPremiumKey();
MailPoet.Modal.loading(false);
}}
>
{MailPoet.I18n.t('premiumTabVerifyButton')}
</button>
</div>
{keyValid !== null && (
<div className="key-activation-messages">
<KeyMessages keyValid={keyValid} />
{mssStatus !== null && (
<MssMessages
keyStatus={mssStatus}
keyMessage={mssKeyMessage}
activationCallback={() => verifyMailPoetSendingServiceKey(true)}
/>
)}
{premiumStatus !== null && (
<PremiumMessages
keyStatus={premiumStatus}
keyMessage={premiumMessage}
installationStatus={premiumInstallationStatus}
installationCallback={installPremiumPlugin}
activationCallback={() => activatePremiumPlugin()}
/>
)}
</div>
)}
</td>
</tr>
</tbody>
</table>
);
};
PremiumTab.propTypes = {
activationKey: PropTypes.string,
premiumStatus: PropTypes.number.isRequired,
mssStatus: PropTypes.number.isRequired,
premiumPluginActive: PropTypes.bool.isRequired,
};
PremiumTab.defaultProps = {
activationKey: null,
};
const container = document.getElementById('settings-premium-tab');
if (container) {
const getPremiumStatus = () => {
const keyValid = window.mailpoet_premium_key_valid;
const pluginInstalled = window.mailpoet_premium_plugin_installed;
const pluginActive = !!window.mailpoet_premium_version;
if (!keyValid) {
return PremiumStatus.KEY_INVALID;
}
if (pluginActive) {
return PremiumStatus.KEY_VALID_PREMIUM_PLUGIN_ACTIVE;
}
return pluginInstalled
? PremiumStatus.KEY_VALID_PREMIUM_PLUGIN_NOT_ACTIVE
: PremiumStatus.KEY_VALID_PREMIUM_PLUGIN_NOT_INSTALLED;
};
const getMssStatus = () => {
const keyValid = window.mailpoet_mss_key_valid;
const mssActive = window.mailpoet_mss_active;
if (!keyValid) {
return MssStatus.KEY_INVALID;
}
return mssActive ? MssStatus.KEY_VALID_MSS_ACTIVE : MssStatus.KEY_VALID_MSS_NOT_ACTIVE;
};
ReactDOM.render(
<PremiumTab
activationKey={window.mailpoet_activation_key}
premiumStatus={getPremiumStatus()}
mssStatus={getMssStatus()}
premiumPluginActive={!!window.mailpoet_premium_version}
/>,
container
);
}

View File

@@ -1,37 +0,0 @@
import MailPoet from 'mailpoet';
var element;
function eventHandler() {
if (confirm(MailPoet.I18n.t('reinstallConfirmation'))) { // eslint-disable-line
MailPoet.trackEvent(
'User has reinstalled MailPoet via Settings',
{ 'MailPoet Free version': window.mailpoet_version }
);
MailPoet.Modal.loading(true);
MailPoet.Ajax.post({
api_version: window.mailpoet_api_version,
endpoint: 'setup',
action: 'reset',
}).always(function alwaysCb() {
MailPoet.Modal.loading(false);
}).done(function doneCb() {
window.location = 'admin.php?page=mailpoet-newsletters';
}).fail(function failCb(response) {
if (response.errors.length > 0) {
MailPoet.Notice.error(
response.errors.map(function responseMapCb(error) {
return error.message;
}),
{ scroll: true }
);
}
});
}
return false;
}
element = document.getElementById('mailpoet_reinstall');
if (element) {
element.addEventListener('click', eventHandler, false);
}

View File

@@ -1,24 +0,0 @@
import React from 'react';
import ReactDOM from 'react-dom';
import DefaultSender from 'settings/default_sender.jsx';
import { GlobalContext, useGlobalContextValue } from 'context/index.jsx';
import Notices from 'notices/notices.jsx';
const App = () => (
<GlobalContext.Provider value={useGlobalContextValue(window)}>
<Notices />
<DefaultSender
senderAddress={window.mailpoet_settings_sender_adddress}
senderName={window.mailpoet_settings_sender_name}
replyToAddress={window.mailpoet_settings_reply_to_address}
replyToName={window.mailpoet_settings_reply_to_name}
mssActive={window.mailpoet_mss_active}
/>
</GlobalContext.Provider>
);
const settingsSenderContainer = document.getElementById('settings_sender_container');
if (settingsSenderContainer) {
ReactDOM.render(<App />, settingsSenderContainer);
}

View File

@@ -1,109 +0,0 @@
import Backbone from 'backbone';
import jQuery from 'jquery';
import mp from 'mailpoet';
var MailPoet = mp;
if (jQuery('#mailpoet_settings').length > 0) {
MailPoet.Router = new (Backbone.Router.extend({
routes: {
'': 'defaultRoute',
'mta(/:group)': 'sendingMethodGroup',
'(:tab)': 'tabs',
},
defaultRoute: function defaultRoute() {
// display basics tab as default
this.tabs('basics');
},
sendingMethodGroup: function sendingMethodGroup(group) {
// display mta tab
this.tabs('mta');
// hide all sending methods' settings
jQuery(
'#mailpoet_sending_method_setup, .mailpoet_sending_method'
).hide();
// hide "save settings" button
jQuery('.mailpoet_settings_submit').hide();
if (group === null) {
// show sending methods
jQuery('.mailpoet_sending_methods, .mailpoet_sending_methods_help').fadeIn();
} else {
// toggle SPF (hidden if the sending method is MailPoet)
jQuery('#mailpoet_mta_spf')[
(group === 'mailpoet')
? 'hide'
: 'show'
]();
// hide sending methods
jQuery('.mailpoet_sending_methods, .mailpoet_sending_methods_help').hide();
// display selected sending method's settings
jQuery('.mailpoet_sending_method[data-group="' + group + '"]').show();
jQuery('#mailpoet_sending_method_setup').fadeIn();
}
},
tabs: function tabs(tab) {
if (tab === 'woocommerce' && !window.mailpoet_woocommerce_active) {
window.location.hash = '#basics';
return;
}
// reset all active tabs
jQuery('.nav-tab-wrapper a').removeClass('nav-tab-active');
// hide panels & sections
jQuery('.mailpoet_tab_panel, .mailpoet_section').hide();
// set active tab
jQuery('a.nav-tab[href="#' + tab + '"]').addClass('nav-tab-active').blur();
// show selected panel
if (jQuery('.mailpoet_tab_panel[data-tab="' + tab + '"]').length > 0) {
jQuery('.mailpoet_tab_panel[data-tab="' + tab + '"]').show();
}
// show "save settings" button
if (tab !== 'premium') {
jQuery('.mailpoet_settings_submit').show();
}
// add 'nav-tab-reload' to all tabs when on '#premium'
if (tab === 'premium') {
jQuery('.nav-tab-wrapper .nav-tab').addClass('nav-tab-reload');
}
MailPoet.trackEvent(
'User has clicked a tab in Settings',
{
'MailPoet Free version': window.mailpoet_version,
'Tab ID': tab,
}
);
},
}))();
// force full reload when going from/to '#premium' page
window.addEventListener('hashchange', function hashchange(e) {
e.preventDefault();
const oldHash = e.oldURL.split('#')[1] || null;
const newHash = e.newURL.split('#')[1] || null;
if (oldHash === 'premium' || newHash === 'premium') {
window.location.reload();
}
});
jQuery(document).ready(function ready() {
if (!Backbone.History.started) Backbone.history.start();
// force full tab reload for tabs with 'nav-tab-reload' class
jQuery('.nav-tab').click(function click(e) {
if (e.target.classList.contains('nav-tab-reload')) {
e.preventDefault();
window.history.replaceState(null, null, e.target.href);
window.location.reload();
}
});
});
}