Skip to content

Commit

Permalink
feat: Flash the favicon of newly-woken tabs.
Browse files Browse the repository at this point in the history
Yeah, it's kinda hacky, but it seems to work, and there aren't any other good APIs for it.  😉

* Also, make the default icon grey.
* Also also, add an eslint-plugin to check our promises.
* And another eslint-plugin to check our vars and lets and consts.

Fixes bwinton#43.
Fixes bwinton#61.
  • Loading branch information
bwinton committed Jan 11, 2017
1 parent bd93684 commit b31e8b2
Show file tree
Hide file tree
Showing 10 changed files with 97 additions and 22 deletions.
15 changes: 13 additions & 2 deletions .eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,10 @@
"webextensions": true
},
"extends": "eslint:recommended",
"plugins": [ "react" ],
"plugins": [
"react",
"promise"
],
"rules": {
"constructor-super": "warn",
"eqeqeq": "error",
Expand All @@ -15,10 +18,18 @@
"no-undef": "warn",
"no-unreachable": "warn",
"no-unused-vars": "warn",
"no-var": "error",
"prefer-const": "error",
"quotes": ["error", "single"],
"valid-typeof": "warn",
"react/jsx-uses-react": "error",
"react/jsx-uses-vars": "error"
"react/jsx-uses-vars": "error",
"promise/avoid-new": "warn",
"promise/catch-or-return": "error",
"promise/no-callback-in-promise": "warn",
"promise/no-promise-in-callback": "warn",
"promise/no-return-wrap": "error",
"promise/param-names": "error"
},
"parserOptions": {
"sourceType": "module",
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
"babel-preset-react": "^6.16.0",
"babel-preset-stage-0": "^6.16.0",
"eslint": "^3.10.2",
"eslint-plugin-promise": "^3.4.0",
"eslint-plugin-react": "^6.8.0",
"node-sass": "^3.13.0",
"npm-run-all": "^3.1.2",
Expand Down
73 changes: 62 additions & 11 deletions src/background.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,22 +16,35 @@ function log(...args) {
if (DEBUG) { console.log('SnoozeTabs (BE):', ...args); } // eslint-disable-line no-console
}

let iconData;

function init() {
log('init()');
browser.alarms.onAlarm.addListener(handleWake);
browser.notifications.onClicked.addListener(handleNotificationClick);
browser.runtime.onMessage.addListener(handleMessage);
if (!iconData) {
fetch(browser.extension.getURL('icons/Color Bell Icon.png')).then(response => {
return response.arrayBuffer();
}).then(function(response) {
iconData = 'data:image/png;base64,' + btoa(String.fromCharCode(...new Uint8Array(response)));
}).catch(reason => {
log('init getUrl rejected', reason);
});
}
browser.storage.local.get().then(items => {
const due = Object.entries(items).filter(item => item[1].time === NEXT_OPEN);
let updated = {}
const updated = {}
due.forEach(item => {
item[1].time = Date.now();
updated[item[0]] = item[1];
});
log('setting next open tabs to now', updated);
browser.storage.local.set(updated).then(() => {
return browser.storage.local.set(updated).then(() => {
updateWakeAlarm();
});
}).catch(reason => {
log('init storage get rejected', reason);
});
}

Expand Down Expand Up @@ -81,14 +94,50 @@ function handleWake() {
return browser.windows.getAll({
windowTypes: ['normal']
}).then(windows => {
let windowIds = windows.map(window => window.id);
const windowIds = windows.map(window => window.id);
return Promise.all(due.map(([, item]) => {
let createProps = {
const createProps = {
active: false,
url: item.url,
windowId: windowIds.includes(item.windowId) ? item.windowId : undefined
};
return browser.tabs.create(createProps).then(tab => {
browser.tabs.executeScript(tab.id, {
'code': `
function flip(newUrl) {
let link = document.createElement('link');
link.rel = 'shortcut icon';
link.href = newUrl;
document.getElementsByTagName('head')[0].appendChild(link);
return link;
}
function reset(link) {
link.remove();
let prev = document.querySelectorAll('link[rel="shortcut icon"]');
if (prev.length) {
document.getElementsByTagName('head')[0].appendChild(prev.item(prev.length - 1));
}
}
let link;
let flip_interval = window.setInterval(() => {
if (link) {
reset(link);
link = undefined;
} else {
link = flip('${iconData}');
}
}, 500);
window.setTimeout(() => {
window.clearInterval(flip_interval);
if (link) {
reset(link);
link = undefined;
}
}, 10000)
`
});
return browser.notifications.create(`${item.windowId}:${tab.id}`, {
'type': 'basic',
'iconUrl': browser.extension.getURL('link.png'),
Expand All @@ -104,18 +153,18 @@ function handleWake() {
}

function handleNotificationClick(notificationId) {
let [windowId, tabId] = notificationId.split(':');
const [windowId, tabId] = notificationId.split(':');
browser.windows.update(+windowId, {focused: true});
browser.tabs.update(+tabId, {active: true});
}

if (browser.contextMenus.ContextType.TAB) {
let parent = chrome.contextMenus.create({
const parent = chrome.contextMenus.create({
contexts: [browser.contextMenus.ContextType.TAB],
title: 'Snooze Tab until…'
});
for (let item in times) {
let time = times[item];
for (const item in times) {
const time = times[item];
chrome.contextMenus.create({
parentId: parent,
id: time.id,
Expand All @@ -125,11 +174,11 @@ if (browser.contextMenus.ContextType.TAB) {
}

browser.contextMenus.onClicked.addListener(function(info/*, tab*/) {
let [time, ] = timeForId(moment(), info.menuItemId);
const [time, ] = timeForId(moment(), info.menuItemId);
browser.tabs.query({currentWindow: true}).then(tabs => {
let addBlank = true;
let closers = [];
for (var tab of tabs) {
const closers = [];
for (const tab of tabs) {
if (!tab.active) {
addBlank = false;
continue;
Expand All @@ -155,6 +204,8 @@ if (browser.contextMenus.ContextType.TAB) {
browser.tabs.remove(closers);
window.close();
}, 500);
}).catch(reason => {
log('handleNotificationClick query rejected', reason);
});
});
}
Expand Down
2 changes: 1 addition & 1 deletion src/icons/Bell Icon.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/icons/Color Bell Icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 2 additions & 2 deletions src/lib/components/MainPanel.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ export default class MainPanel extends React.Component {
}

renderTime(item) {
let [, date] = timeForId(moment(), item.id);
const [, date] = timeForId(moment(), item.id);
return (
<li className="option" key={item.id} id={item.id} onClick={ ev => this.handleOptionClick(ev, item) }>
<img src={ `../icons/${item.icon || 'nightly.svg'}` } className="icon" />
Expand All @@ -57,7 +57,7 @@ export default class MainPanel extends React.Component {
this.setState({ datepickerActive: true });
return;
}
let [time, ] = timeForId(moment(), item.id);
const [time, ] = timeForId(moment(), item.id);
scheduleSnoozedTab(time);
}

Expand Down
2 changes: 1 addition & 1 deletion src/lib/components/ManagePanel.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ export default class ManagePanel extends React.Component {
if (!url) {
return '&nbsp;';
}
var parser = document.createElement('a');
const parser = document.createElement('a');
parser.href = url;
if (parser.protocol.startsWith('http')) {
return parser.host.replace(/^www\./, '');
Expand Down
4 changes: 2 additions & 2 deletions src/lib/times.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ export const times = [
];

export function timeForId(time, id) {
var rv = moment(time);
var text = rv.fromNow();
let rv = moment(time);
let text = rv.fromNow();
switch (id) {
case 'debug':
rv = rv.add(5, 'seconds');
Expand Down
10 changes: 9 additions & 1 deletion src/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,15 @@
"48": "icons/Bell Icon.svg"
},
"version": "1.0",
"permissions": ["alarms", "contextMenus", "notifications", "storage", "tabs"],

"permissions": [
"alarms",
"contextMenus",
"notifications",
"storage",
"tabs",
"<all_urls>"],

"background": {
"scripts": ["background.js"]
},
Expand Down
8 changes: 6 additions & 2 deletions src/popup/snooze-content.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,8 @@ function log(...args) {
function scheduleSnoozedTab(time) {
browser.tabs.query({currentWindow: true}).then(tabs => {
let addBlank = true;
let closers = [];
for (var tab of tabs) {
const closers = [];
for (const tab of tabs) {
if (!tab.active) {
addBlank = false;
continue;
Expand All @@ -58,6 +58,8 @@ function scheduleSnoozedTab(time) {
browser.tabs.remove(closers);
window.close();
}, 500);
}).catch(reason => {
log('scheduleSnoozedTab query rejected', reason);
});
return;
}
Expand Down Expand Up @@ -103,6 +105,8 @@ function fetchEntries() {
browser.storage.local.get().then(items => {
log('fetched items', items);
setState({ entries: Object.values(items || {}) });
}).catch(reason => {
log('fetchEntries storage get rejected', reason);
});
}

Expand Down

0 comments on commit b31e8b2

Please sign in to comment.