зеркало из https://github.com/mozilla/email-tabs.git
466 строки
13 KiB
JavaScript
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'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'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();
|