email-tabs/addon/popup.jsx

466 строки
13 KiB
JavaScript

/* globals React, ReactDOM, ReactDOMServer, templateMetadata */
/* eslint jsx-a11y/click-events-have-key-events: 0 */
/* eslint jsx-a11y/no-noninteractive-element-interactions: 0 */
let activeTabLi;
let activeTabSelected = false;
let selected = new Map();
let mailProvider;
let showAdditionalProviders;
let isSelectingMailProvider = false;
const LOGIN_ERROR_TIME = 90 * 1000; // 90 seconds
/* True if this is a tab we can "send". Doesn't include about:preferences, etc. */
function isSelectableTabUrl(url) {
return url.startsWith("http");
}
class Tab extends React.Component {
render() {
let tab = this.props.tab;
let checkId = `checkbox-${this.props.tab.id}`;
let checked = this.props.selected.get(tab.id);
let liClass;
if (tab.active) {
liClass = "active";
}
let image = <span className="tab__image" style={{backgroundImage: `url(${tab.favIconUrl})`}} />;
return <li className={liClass} ref={li => {
if (this.props.tab.active) {
activeTabLi = li;
}
}}>
<div className={isSelectableTabUrl(tab.url) ? "tab" : "tab disabled"}>
<label htmlFor={checkId}>
{ isSelectableTabUrl(tab.url) ? <input type="checkbox" value={tab.id} checked={checked}
onChange={this.onChange.bind(this)} id={checkId} ref={checkbox => this.checkbox = checkbox} /> : <input type="checkbox" disabled /> }
<label htmlFor={checkId} className="styled-checkbox"></label>
<label htmlFor={checkId} className="tab__label">
{ image }
<span className="tab__text">{tab.title}</span>
</label>
</label>
</div>
</li>;
}
async onFeedback() {
browser.runtime.sendMessage({
type: "sendEvent",
ec: "interface",
ea: "button-click",
el: "feedback",
cd1: await browser.tabs.query({currentWindow: true}).length,
cd2: getSelectedCount(),
cd3: activeTabSelected,
cd7: mailProvider,
});
}
onChange() {
selected.set(this.props.tab.id, this.checkbox.checked);
if (this.props.onChange) {
this.props.onChange();
}
render();
}
}
function getSelectedCount() {
return Array.from(selected.values()).filter(x => x).length;
}
class TabList extends React.Component {
render() {
let tabElements = this.props.tabs.map(
tab => <Tab tab={tab} key={tab.id} selected={this.props.selected} onChange={this.onChangeSelection.bind(this)} />
);
return <div className="tabs-wrapper">
<section className="tabs-section" style={{display: "flex"}}>
<ul className="tabs-section__list" role="navigation">{tabElements}</ul>
</section>
</div>;
}
onChangeSelection() {
selectionCache.saveSelectedTabs(this.props.tabs);
}
}
class Popup extends React.Component {
constructor(props) {
super(props);
this.state = {
showLoginError: this.props.showLoginError,
};
}
dismissError() {
this.setState({showLoginError: false});
}
render() {
let anyChecked = false;
let allChecked = true;
this.indeterminate = false;
const linkableTabs = this.props.tabs.filter((t) => isSelectableTabUrl(t.url));
for (let tab of linkableTabs) {
if (!this.props.selected.get(tab.id)) {
allChecked = false;
} else {
anyChecked = true;
this.indeterminate = true;
}
}
if (allChecked) {
this.indeterminate = false;
}
let emailTabsTitle = null;
if (this.props.incognito) {
emailTabsTitle = "Emailing tabs from a Private Browsing window is not supported";
}
return <div>
{ this.state.showLoginError ? <LoginError dismissError={this.dismissError.bind(this)} /> : null }
<div className="controls">
<div>
<input checked={allChecked} ref={allCheckbox => this.allCheckbox = allCheckbox} type="checkbox" id="allCheckbox" onChange={this.onClickCheckAll.bind(this)} />
<label htmlFor="allCheckbox" className="styled-checkbox"></label>
<label htmlFor="allCheckbox">Select All</label>
<button className="settings-icon-wrap" onClick={this.onSelectProvider.bind(this)}><img className="settings-icon" src="images/settings.svg" alt="settings icon"/></button>
</div>
</div>
<div className="separator"></div>
<div className="tabList">
<TabList tabs={this.props.tabs} selected={this.props.selected} />
</div>
<div className="separator"></div>
<p className="feedback-link">What do you think of Email Tabs? <a href="mailto:team-email-tabs@mozilla.com" onClick={this.onFeedback}>Let us know.</a></p>
<footer className="panel-footer toggle-enabled">
<button onClick={this.copyTabs.bind(this)} disabled={!anyChecked}>
Copy Tabs to Clipboard
</button>
<button onClick={this.sendEmail.bind(this)} disabled={!anyChecked || this.props.incognito} title={emailTabsTitle} >
Email Tabs
</button>
</footer>
</div>;
}
componentDidMount() {
this.allCheckbox.indeterminate = this.indeterminate;
}
componentDidUpdate() {
this.componentDidMount();
}
async onClickCheckAll() {
let allChecked = true;
let selectableTabs = this.props.tabs.filter(tab => isSelectableTabUrl(tab.url));
for (let tab of selectableTabs) {
allChecked = allChecked && this.props.selected.get(tab.id);
}
for (let tab of selectableTabs) {
selected.set(tab.id, !allChecked);
}
selectionCache.clear();
render();
await browser.runtime.sendMessage({
type: "sendEvent",
ec: "interface",
ea: "select-all",
el: "browser-action",
cd1: await browser.tabs.query({currentWindow: true}).length,
cd7: mailProvider,
});
}
async sendEmail() {
let sendTabs = this.props.tabs.filter(tab => this.props.selected.get(tab.id));
if (!sendTabs.length) {
console.info("Tried to send tabs with nothing selected");
return;
}
localStorage.removeItem("loginInterrupt");
sendTabs = sendTabs.map(tab => tab.id);
await browser.runtime.sendMessage({
type: "sendEmail",
mailProvider,
tabIds: sendTabs,
customDimensions: {
cd1: await browser.tabs.query({currentWindow: true}).length,
cd2: getSelectedCount(),
cd3: activeTabSelected,
cd6: this.allCheckbox.checked,
cd7: mailProvider,
},
});
browser.runtime.sendMessage({
type: "sendEvent",
ec: "interface",
ea: "button-click",
el: "email-tabs",
cd1: await browser.tabs.query({currentWindow: true}).length,
cd2: getSelectedCount(),
cd3: activeTabSelected,
cd6: this.allCheckbox.checked,
cd7: mailProvider,
});
window.close();
}
async copyTabs() {
let sendTabs = this.props.tabs.filter(tab => this.props.selected.get(tab.id));
if (!sendTabs.length) {
console.info("Tried to copy tabs with nothing selected");
return;
}
sendTabs = sendTabs.map(tab => tab.id);
await browser.runtime.sendMessage({
type: "copyTabHtml",
tabIds: sendTabs,
});
setTimeout(() => {
window.close();
}, 300);
browser.runtime.sendMessage({
type: "sendEvent",
ec: "interface",
ea: "button-click",
el: "copy-tabs-to-clipboard",
cd1: await browser.tabs.query({currentWindow: true}).length,
cd2: getSelectedCount(),
cd3: activeTabSelected,
cd7: mailProvider,
});
}
async onSelectProvider() {
isSelectingMailProvider = true;
render();
}
}
class LoginError extends React.Component {
render() {
return <div id="login-error">
<img className="warn" src="images/warning.svg" alt="warning icon"/>
<p>Don&#39;t forget to sign in to your email account to use Email Tabs.</p>
<img className="close" src="images/close.svg" onClick={this.props.dismissError} alt="close" />
</div>;
}
}
class MailPreference extends React.Component {
render() {
let footer = (
<footer className="panel-footer toggle-enabled">
<button onClick={this.onCancel.bind(this)}>
Cancel
</button>
</footer>
);
const providerCb = showAdditionalProviders ? this.onSelect : () => {};
return <div>
<p className="provider-heading">Select Default Mail Provider</p>
<div className="separator"></div>
<div className="providers-container">
<button className="provider-button" onClick={this.onSelect.bind(this, "gmail")}>
<img className="provider-icon" src="images/gmail.svg" alt=""/>
<p>Gmail</p>
</button>
<button className="provider-button" disabled={!showAdditionalProviders} onClick={providerCb.bind(this, "yahoo")}>
<img className="provider-icon" src="images/yahoo.svg" alt=""/>
<p>Yahoo Mail</p>
</button>
<button className="provider-button" disabled={!showAdditionalProviders} onClick={providerCb.bind(this, "outlook")}>
<img className="provider-icon" src="images/outlook.svg" alt=""/>
<p>Outlook</p>
</button>
</div>
<div className="separator"></div>
<p className="sorry">Sorry we don&#39;t support any other mail providers. <a href="https://github.com/mozilla/email-tabs/blob/master/docs/faq.md#frequently-asked-questions">Learn more</a></p>
{this.props.mailProvider ? footer : null}
</div>;
}
async onSelect(provider) {
let previousMailProvider = mailProvider;
mailProvider = provider;
browser.storage.local.set({mailProvider});
isSelectingMailProvider = false;
await browser.runtime.sendMessage({
type: "sendEvent",
ec: "interface",
ea: "provider-select",
el: provider,
cd1: await browser.tabs.query({currentWindow: true}).length,
cd7: previousMailProvider,
});
render();
}
async onLearnMore() {
await browser.runtime.sendMessage({
type: "sendEvent",
ec: "interface",
ea: "provider-learn-more",
cd1: await browser.tabs.query({currentWindow: true}).length,
cd7: mailProvider,
});
}
async onCancel() {
isSelectingMailProvider = false;
await browser.runtime.sendMessage({
type: "sendEvent",
ec: "interface",
ea: "provider-cancel",
cd1: await browser.tabs.query({currentWindow: true}).length,
cd7: mailProvider,
});
render();
}
}
async function render(firstRun) {
let tabs = await browser.tabs.query({currentWindow: true});
let incognito = tabs.some(tab => tab.incognito);
if (firstRun) {
if (!selectionCache.loadSelectedTabs(tabs)) {
for (let tab of tabs) {
if (tab.active && isSelectableTabUrl(tab.url)) {
activeTabSelected = true;
selected.set(tab.id, true);
}
}
}
}
let showLoginError = parseInt(localStorage.getItem("loginInterrupt") || "0", 10);
if (Date.now() - showLoginError > LOGIN_ERROR_TIME) {
showLoginError = false;
}
let page;
if (mailProvider && !isSelectingMailProvider) {
page = <Popup selected={selected} tabs={tabs} showLoginError={showLoginError} incognito={incognito} />;
} else {
await browser.runtime.sendMessage({
type: "sendEvent",
ec: "interface",
ea: "provider-preference",
el: isSelectingMailProvider ? "settings" : "first-time",
cd1: await browser.tabs.query({currentWindow: true}).length,
cd7: mailProvider,
});
page = <MailPreference mailProvider={mailProvider} />;
}
ReactDOM.render(page, document.getElementById("panel"));
if (firstRun) {
activeTabLi.scrollIntoView({
behavior: "instant",
block: "center",
});
}
}
const selectionCache = {
timeout: 30 * 60 * 1000, // 30 minutes
key: "selectionCache",
load() {
let value = localStorage.getItem(this.key);
if (!value) {
return null;
}
value = JSON.parse(value);
if (Date.now() - value.time > this.timeout) {
localStorage.removeItem(this.key);
return null;
}
return value.cache;
},
loadSelectedTabs(tabs) {
let value = this.load();
if (!value) {
return false;
}
let anyFound = false;
for (let tab of tabs) {
if (value[tab.id] && value[tab.id].url === tab.url) {
anyFound = true;
selected.set(tab.id, true);
}
}
return anyFound;
},
save(value) {
localStorage.setItem(this.key, JSON.stringify({
cache: value,
time: Date.now(),
}));
},
saveSelectedTabs(tabs) {
let newValue = {};
for (let tab of tabs) {
if (selected.get(tab.id)) {
newValue[tab.id] = {url: tab.url};
}
}
this.save(newValue);
},
clear() {
localStorage.removeItem(this.key);
},
};
/** Calls render(), then calls it again soon */
function renderWithDelay() {
render();
setTimeout(render, 300);
}
for (let eventName of ["onAttached", "onCreated", "onDetached", "onMoved", "onUpdated"]) {
browser.tabs[eventName].addListener(render);
}
browser.tabs.onRemoved.addListener(renderWithDelay);
async function init() {
let result = await browser.storage.local.get("mailProvider");
mailProvider = result.mailProvider;
let providerResult = await browser.storage.local.get("showAdditionalProviders");
showAdditionalProviders = providerResult.showAdditionalProviders;
render(true);
browser.runtime.sendMessage({
type: "sendEvent",
ec: "interface",
ea: "expand-panel",
el: "browser-action",
cd1: await browser.tabs.query({currentWindow: true}).length,
cd7: mailProvider,
});
}
init();