├── .babelrc ├── .gitignore ├── .tern-project ├── example.gif ├── source_media ├── logo_36.png ├── logo_48.png ├── page-32.png ├── page-48.png ├── page-48.psd ├── logo_36_blue.png ├── logo_36_blue.psd ├── page-32_white.png ├── page-32_white.psd └── page-48_white.png ├── filewatch.sh ├── tabist ├── icons │ ├── page-32_white.png │ ├── page-48_white.png │ └── LICENSE ├── .web-extension-id ├── styles │ └── tabs.css ├── LICENSE ├── background.js ├── manifest.json ├── options.html ├── tabs.html └── lib │ └── js │ ├── tether.min.js │ ├── bootstrap.min.js │ └── jquery.min.js ├── .eslintrc.js ├── LICENSE ├── webpack.config.js ├── package.json ├── README.md └── src └── js ├── options.js ├── lib └── utils.js ├── components └── WindowCollection.jsx └── tabs.jsx /.babelrc: -------------------------------------------------------------------------------- 1 | {"presets": ["react", "env" ]} 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | web-ext-artifacts 2 | tabist/*.js 3 | *.map 4 | -------------------------------------------------------------------------------- /.tern-project: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": { 3 | "node": {} 4 | } 5 | } -------------------------------------------------------------------------------- /example.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fiveNinePlusR/tabist/HEAD/example.gif -------------------------------------------------------------------------------- /source_media/logo_36.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fiveNinePlusR/tabist/HEAD/source_media/logo_36.png -------------------------------------------------------------------------------- /source_media/logo_48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fiveNinePlusR/tabist/HEAD/source_media/logo_48.png -------------------------------------------------------------------------------- /source_media/page-32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fiveNinePlusR/tabist/HEAD/source_media/page-32.png -------------------------------------------------------------------------------- /source_media/page-48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fiveNinePlusR/tabist/HEAD/source_media/page-48.png -------------------------------------------------------------------------------- /source_media/page-48.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fiveNinePlusR/tabist/HEAD/source_media/page-48.psd -------------------------------------------------------------------------------- /filewatch.sh: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env sh 2 | 3 | echo "watching and building" 4 | webpack --display-error-details -w 5 | -------------------------------------------------------------------------------- /source_media/logo_36_blue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fiveNinePlusR/tabist/HEAD/source_media/logo_36_blue.png -------------------------------------------------------------------------------- /source_media/logo_36_blue.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fiveNinePlusR/tabist/HEAD/source_media/logo_36_blue.psd -------------------------------------------------------------------------------- /source_media/page-32_white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fiveNinePlusR/tabist/HEAD/source_media/page-32_white.png -------------------------------------------------------------------------------- /source_media/page-32_white.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fiveNinePlusR/tabist/HEAD/source_media/page-32_white.psd -------------------------------------------------------------------------------- /source_media/page-48_white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fiveNinePlusR/tabist/HEAD/source_media/page-48_white.png -------------------------------------------------------------------------------- /tabist/icons/page-32_white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fiveNinePlusR/tabist/HEAD/tabist/icons/page-32_white.png -------------------------------------------------------------------------------- /tabist/icons/page-48_white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fiveNinePlusR/tabist/HEAD/tabist/icons/page-48_white.png -------------------------------------------------------------------------------- /tabist/.web-extension-id: -------------------------------------------------------------------------------- 1 | # This file was created by https://github.com/mozilla/web-ext 2 | # Your auto-generated extension ID for addons.mozilla.org is: 3 | {4e7028c0-c17e-4039-b588-2abe693fb5c4} -------------------------------------------------------------------------------- /tabist/icons/LICENSE: -------------------------------------------------------------------------------- 1 | 2 | The "page-32.png" and "page-48.png" icons are taken from the miu iconset created by Linh Pham Thi Dieu, and are used under the terms of its license: http://linhpham.me/miu/. 3 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "env": { 3 | "browser": true, 4 | "es6": true 5 | }, 6 | "extends": "eslint:recommended", 7 | "parserOptions": { 8 | "sourceType": "module" 9 | }, 10 | "globals":{ 11 | "chrome": 1, 12 | "Bacon": 1, 13 | "require": 1 14 | }, 15 | 16 | "rules": { 17 | "indent": [ 18 | "error", 19 | 2 20 | ], 21 | "linebreak-style": [ 22 | "error", 23 | "unix" 24 | ], 25 | "quotes": [ 26 | "error", 27 | "double" 28 | ], 29 | "semi": [ 30 | "error", 31 | "always" 32 | ] 33 | } 34 | }; 35 | -------------------------------------------------------------------------------- /tabist/styles/tabs.css: -------------------------------------------------------------------------------- 1 | /* #content { */ 2 | body { 3 | margin: 0 auto; 4 | max-width: 50em; 5 | font-family: "Helvetica", "Arial", sans-serif; 6 | line-height: 1.5; 7 | color: #555; 8 | } 9 | 10 | ul{ 11 | line-height: 1.5; 12 | } 13 | 14 | h2 { 15 | color: #333; 16 | } 17 | 18 | #controls { 19 | /* margin-bottom: 1.5em; */ 20 | } 21 | 22 | .controls-selections { 23 | /* margin-bottom: 1.5em; */ 24 | } 25 | 26 | .container div ul li a, .links { 27 | color: #444 !important; 28 | } 29 | 30 | .linkurl { 31 | display: none; 32 | } 33 | 34 | .backup-restore-div { 35 | padding-top: 2em; 36 | } 37 | 38 | 39 | .back-button { 40 | margin-bottom: 1em; 41 | } 42 | 43 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 fiveNinePlusR 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /tabist/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 fiveNinePlusR 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | // can also use an array of configurations [{}, ..., {}] 4 | module.exports = { 5 | // mode: "production", // enable many optimizations for production builds 6 | mode: "development", // enabled useful tools for development 7 | // devtool: "nosources-source-map", // enables stack traces but disables sources 8 | devtool: "source-map", // enables stack traces but disables sources 9 | 10 | entry: { 11 | tabs: "./src/js/tabs.jsx", 12 | options: "./src/js/options.js" 13 | }, 14 | 15 | output: { 16 | path: path.resolve(__dirname, "tabist/"), 17 | filename: "[name].js" 18 | }, 19 | 20 | //https://blog.usejournal.com/creating-a-react-app-from-scratch-f3c693b84658 21 | module: { 22 | rules: [ 23 | { 24 | test: /\.css$/, 25 | use: [ 'style-loader', 'css-loader' ] 26 | }, 27 | { 28 | test: /\.(js|jsx)$/, 29 | exclude: /(node_modules|bower_components)/, 30 | loader: 'babel-loader', 31 | options: { presets: ['env']} 32 | } 33 | ] 34 | }, 35 | 36 | resolve: { extensions: ['*', '.js', '.jsx'] }, 37 | 38 | }; 39 | 40 | -------------------------------------------------------------------------------- /tabist/background.js: -------------------------------------------------------------------------------- 1 | var extURL = chrome.extension.getURL("tabs.html"); 2 | 3 | function toggleTabist() { 4 | chrome.windows.getCurrent({}, function(window) { 5 | chrome.tabs.query({windowId: window.id}, function(tabs) { 6 | //check for a tabist tab first 7 | for(let tab of tabs) { 8 | if(tab.title == "Tabist"){ 9 | //open that tab 10 | chrome.tabs.update(tab.id, {active: true}); 11 | return; 12 | } 13 | } 14 | 15 | //loop through again to see if there is an active new tab to replace 16 | for(let tab of tabs) { 17 | if(tab.title == "New Tab" && tab.active){ 18 | chrome.tabs.update(tab.id, {"url": extURL + "?main=true"}); 19 | return; 20 | } 21 | } 22 | 23 | //fallback to making a new tabist tab 24 | chrome.tabs.create({ 25 | "url": extURL + "?main=true" 26 | //,"pinned": true // TODO: add pinned option 27 | }); 28 | return; 29 | }); 30 | }); 31 | } 32 | 33 | chrome.browserAction.onClicked.addListener(toggleTabist); 34 | 35 | // listen for the keyboard shortcut command 36 | chrome.commands.onCommand.addListener(toggleTabist); 37 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tabist", 3 | "version": "0.1.6", 4 | "description": "Simple Tab Switcher", 5 | "main": " ", 6 | "dependencies": { 7 | "gulp": "^3.9.1", 8 | "react": "^16.4.2", 9 | "react-dom": "^16.4.2", 10 | "underscore": "^1.9.1", 11 | "webextension-polyfill": "^0.2.1", 12 | "webpack": "^4.17.1" 13 | }, 14 | "devDependencies": { 15 | "@types/underscore": "^1.8.9", 16 | "babel-core": "^6.26.3", 17 | "babel-loader": "^7.1.5", 18 | "babel-preset-env": "^1.7.0", 19 | "babel-preset-react": "^6.24.1", 20 | "baconjs": "^0.7.95", 21 | "css-loader": "^0.25.0", 22 | "style-loader": "^0.13.2", 23 | "webpack-cli": "^2.1.5" 24 | }, 25 | "scripts": { 26 | "test": "echo \"Error: no test specified\" && exit 1", 27 | "build": "webpack --display-error-details -w", 28 | "lint": "web-ext lint" 29 | }, 30 | "repository": { 31 | "type": "git", 32 | "url": "git+https://github.com/fiveNinePlusR/tabist.git" 33 | }, 34 | "keywords": [ 35 | "tabs", 36 | "webextension", 37 | "chrome", 38 | "firefox" 39 | ], 40 | "author": "fiveNinePlusR", 41 | "license": "MIT", 42 | "bugs": { 43 | "url": "https://github.com/fiveNinePlusR/tabist/issues" 44 | }, 45 | "homepage": "https://github.com/fiveNinePlusR/tabist#readme" 46 | } 47 | -------------------------------------------------------------------------------- /tabist/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | 3 | "description": "Quick Tab Switcher", 4 | "manifest_version": 2, 5 | "name": "Tabist", 6 | "version": "0.1.7", 7 | "homepage_url": "https://github.com/fiveNinePlusR/tabist", 8 | "icons": { 9 | "48": "icons/page-48_white.png" 10 | }, 11 | 12 | "permissions": [ 13 | "tabs", 14 | "storage", 15 | "downloads" 16 | ], 17 | 18 | "background": { 19 | "scripts": ["background.js"] 20 | }, 21 | 22 | "browser_action": { 23 | "default_icon": "icons/page-32_white.png" 24 | }, 25 | 26 | "sidebar_action": { 27 | "default_icon": "icons/page-32_white.png", 28 | "default_title" : "Tabist", 29 | "default_panel": "tabs.html", 30 | "open_at_install": false 31 | }, 32 | 33 | "commands": { 34 | "toggle-tabist": { 35 | "suggested_key": { 36 | "default": "Ctrl+Shift+E", 37 | "mac": "Ctrl+Shift+E", 38 | "windows": "Ctrl+Shift+E" 39 | }, 40 | "description": "Toggle or activate a Tabist tab" 41 | }, 42 | "_execute_sidebar_action": { 43 | "suggested_key": { 44 | "default": "Ctrl+Shift+Z", 45 | "mac": "Ctrl+Shift+S", 46 | "windows": "Ctrl+Shift+Z" 47 | }, 48 | "description": "Toggle or activate a Tabist sidebar" 49 | } 50 | } 51 | } 52 | 53 | -------------------------------------------------------------------------------- /tabist/options.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Tabist 9 | 10 | 11 | 12 |

Tabist Options

13 | Back to Tabist 14 |
15 |
16 | 17 | 18 | 21 |
22 | 23 | 24 |
25 | 26 | 29 | 30 |
31 | 32 | 33 |
34 |
35 | 36 |
37 |

Tab Backup and Restore (Experimental)

38 | 39 | 40 |
41 | 42 |
43 |
44 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Tabist 2 | 3 | ## About 4 | 5 | Simple extension to help you find where that tab you were looking for is and activates it when you click on the link 6 | 7 | Available for [Chrome](https://chrome.google.com/webstore/detail/tabist/hdjegjggiogfkaldbfphfdkpggjemaha) and [Firefox](https://addons.mozilla.org/en-US/firefox/addon/tabist/) 8 | 9 | ## Example 10 | 11 | ![Example](example.gif) 12 | 13 | ## Development Setup 14 | 15 | To setup the build environment the first time you need to install npm and webpack. After that you can compile the sources by navigating to the project directory and run `npm install` to install dependencies for webpack to run successfully. 16 | 17 | To build the final javascript bundle you can run `webpack` or to have webpack try to rebuild the files when you save you can use `webpack --display-error-details -w` from the same folder. 18 | 19 | To install the bare webextension in firefox you can navigate to `about:debugging` and then click on `Load Temporary Add-on` and navigate to the `manifest.json` file. 20 | 21 | To install in chrome you can navigate to `chrome://extensions/` and check the `Developer mode` checkbox and then click on `Load unpacked extension` and navigate to the `manifest.json` file. 22 | 23 | ## Limitations 24 | 25 | Right now there is a [bug in firefox](https://bugzilla.mozilla.org/show_bug.cgi?id=1289213) that prevents the unloaded tabs from displaying the cached title until they are re-loaded. This is landed and scheduled for Firefox 51 (Will be released to the general public on 1/24/2017). 26 | 27 | [Additional bugfix related to this extension that will be landed in Firefox 51](https://bugzilla.mozilla.org/show_bug.cgi?id=1291830) 28 | -------------------------------------------------------------------------------- /tabist/tabs.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Tabist 10 | 11 | 12 | 13 |
14 |

Tabist

15 | Refresh 16 | Show Options 17 |
18 |

19 |
20 |
21 |
22 |
23 |

Group By

24 | By Window 25 |
26 | By Domain
27 |
28 | 37 |
38 | Preferences 39 |
40 |
41 |
42 |
43 | 44 |
45 | 46 | 47 |

48 |

49 | Problems? Suggestions? Report feedback or issues here 50 | 51 | 52 | 53 | 54 | 55 | 56 |
57 | 58 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /src/js/options.js: -------------------------------------------------------------------------------- 1 | let Utils = require("./lib/utils.js"); 2 | let Bacon = require("baconjs"); 3 | 4 | function saveOptions() { 5 | chrome.storage.local.set({ 6 | closeTab: document.querySelector("#close_tab").checked, 7 | // pinTab: document.querySelector("#pin_tab").checked, 8 | autoRefresh: document.querySelector("#autorefresh_tab").checked, 9 | // newTab: document.querySelector("#newtab_tab").checked, 10 | showDomain: document.querySelector("#show_domain_tab").checked 11 | }); 12 | } 13 | 14 | function restoreOptions() { 15 | chrome.storage.local.get(["closeTab", "pinTab", "autoRefresh", "newtab", "showDomain"], res => { 16 | let closetab = res.closeTab; 17 | let pintab = res.pinTab; 18 | let autorefresh = res.autoRefresh; 19 | let newtab = res.newTab; 20 | let showdomain = res.showDomain; 21 | document.querySelector("#close_tab").checked = closetab || false; 22 | // document.querySelector("#pin_tab").checked = pintab || false; 23 | document.querySelector("#autorefresh_tab").checked = autorefresh || false; 24 | // document.querySelector("#newtab_tab").checked = newtab || false; 25 | document.querySelector("#show_domain_tab").checked = showdomain || false; 26 | }); 27 | } 28 | 29 | // Bacon.fromEvent(); 30 | 31 | document.addEventListener("DOMContentLoaded", restoreOptions); 32 | document.querySelector("form").addEventListener("submit", saveOptions); 33 | 34 | // tab backup and restore 35 | document.getElementById("backup_tabs").onclick = function() { 36 | chrome.tabs.query({}, function(tabs) { 37 | download(JSON.stringify(tabs, null, " ")); 38 | }); 39 | }; 40 | 41 | let restoreTab = Bacon.fromEvent(document.getElementById("restore_tabs_file"), "change"); 42 | let fileinput = document.getElementById("restore_tabs_file"); 43 | 44 | document.getElementById("restore_tabs").onclick = function() { 45 | fileinput.click(); 46 | }; 47 | 48 | restoreTab.onValue(() => { 49 | let filelist = fileinput.files; 50 | var reader = new FileReader(); 51 | 52 | function sleep(ms) { 53 | return new Promise(resolve => setTimeout(resolve, ms)); 54 | } 55 | 56 | reader.onload = (function() { return function(e) { 57 | let restoredata = e.target.result.replace(/^[^,]*,/g, ""); 58 | try{ 59 | let data = window.atob(restoredata); 60 | let json = JSON.parse(data); 61 | if (json) { 62 | let wins = Utils.groupByWindow(json); 63 | for (let [_, tabs] of wins) { 64 | let links = Utils.getLinksFromTabs(tabs); 65 | chrome.windows.create({ url: links }); 66 | // await sleep(1000); 67 | } 68 | } 69 | } catch(e){ 70 | console.error(e); 71 | } 72 | }; })(); 73 | 74 | reader.readAsDataURL(filelist[0]); 75 | }); 76 | 77 | function download(data) { 78 | var blob = new Blob([data], {type: "text/json"}); 79 | var url = window.URL.createObjectURL(blob); 80 | chrome.downloads.download({url:url, filename:"tabsbackup.json", conflictAction:"uniquify"}, function() { 81 | // console.log(downloadId); 82 | }); 83 | } 84 | -------------------------------------------------------------------------------- /src/js/lib/utils.js: -------------------------------------------------------------------------------- 1 | let Utils = { 2 | groupTabs(tabs, method) { 3 | if (method == "window") { 4 | return this.groupByWindow(tabs); 5 | } else if ( method == "domain") { 6 | return this.groupByDomain(tabs); 7 | } 8 | 9 | console.log("problem getting group by preference"); 10 | return this.groupByWindow(tabs); 11 | }, 12 | 13 | // returns tabs grouped by window 14 | // input [tabs] 15 | // Map {window_id => [tab]+} 16 | groupByWindow(tabs) { 17 | return tabs.reduce(function(memo, cur) { 18 | let win = memo.get(cur.windowId) || []; 19 | 20 | win.push(cur); 21 | memo.set(cur.windowId, win); 22 | 23 | return memo; 24 | }, new Map()); 25 | }, 26 | 27 | // returns tabs grouped by domain name 28 | // input [tabs] 29 | // Map {baseDomain => [tab]+} 30 | groupByDomain(tabs) { 31 | let returnValue = new Map(); 32 | let tabmap = tabs.reduce(function(memo, cur) { 33 | let url = new URL(cur.url); 34 | let win = memo.get(url.hostname) || []; 35 | 36 | win.push(cur); 37 | memo.set(url.hostname, win); 38 | 39 | return memo; 40 | }, new Map()); 41 | 42 | for(let [domain, tablist] of tabmap) { 43 | if (tablist.length == 1){ 44 | // group the misc domains into one group 45 | let misc = returnValue.get("Misc") || []; 46 | 47 | misc.push(tablist[0]); 48 | returnValue.set("Misc", misc); 49 | } else { 50 | returnValue.set(domain, tablist); 51 | } 52 | } 53 | 54 | return returnValue; 55 | }, 56 | 57 | lexSort(l,r) { 58 | let left = new URL(l.url); 59 | let right = new URL(r.url); 60 | left = left.hostname || left.href; 61 | right = right.hostname || right.href; 62 | 63 | if(left > right) { 64 | return 1; 65 | } 66 | if(left < right) { 67 | return -1; 68 | } 69 | return 0; 70 | }, 71 | 72 | isNumeric(value) { 73 | return !isNaN(value - parseFloat(value)); 74 | }, 75 | 76 | /** 77 | * Sorts a Map of window_ids with an array of tabs as the values by the domain of the url. 78 | * @param {Boolean} sortByDoemainValue - Parameter description. 79 | * @param {Map} windows - input Map {window_ids => [tab]+} 80 | * @returns {Map} returns Map {window_ids => [sorted_tabs]} 81 | */ 82 | sortByDomain(sortByDomainValue, windows) { 83 | if(sortByDomainValue){ 84 | let sortedWindows = new Map(); 85 | 86 | for(let [windowId, tabs] of windows){ 87 | let sortedTabs = tabs.sort(this.lexSort); 88 | sortedWindows.set(windowId, sortedTabs); 89 | } 90 | return sortedWindows; 91 | } 92 | return windows; 93 | }, 94 | 95 | // returns an array of tabs for a set of tabs 96 | // input [tabs] 97 | // returns [urls] 98 | getLinksFromTabs(tabs) { 99 | return tabs.reduce((memo, cur) => { 100 | memo = memo || []; 101 | memo.push(this.handleSpecialURIs(cur.url)); 102 | return memo; 103 | }, []) 104 | .filter(val => val); 105 | }, 106 | 107 | handleSpecialURIs(url) { 108 | //might need to handle about:config, performance etc. 109 | if (url.search("http") >= 0) { 110 | return decodeURIComponent(url.replace("about:reader?url=","")); 111 | } 112 | 113 | return null; 114 | } 115 | 116 | 117 | }; 118 | 119 | module.exports = Utils; 120 | -------------------------------------------------------------------------------- /src/js/components/WindowCollection.jsx: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | import Utils from '../lib/utils'; 3 | import _ from 'underscore'; 4 | 5 | class WindowCollection extends Component{ 6 | 7 | render(){ 8 | let windows = this.props.windowdata; 9 | let renderedWindows = Array.from(windows.keys()).map((window, index) => { 10 | return ; 11 | }); 12 | 13 | let audible = ; 14 | let footer =