├── .gitignore ├── audio ├── coin.mp3 └── goomba-stomp.wav ├── background-scripts ├── background.js ├── initialize.js ├── mario.js └── urlbar.js ├── icon.svg ├── manifest.json ├── newtab-page ├── newtab-content.css └── newtab.html ├── package.json ├── settings ├── options.css ├── options.html └── options.js ├── src └── newtab.js └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | newtab-page/bundle.js 4 | web-ext-artifacts 5 | -------------------------------------------------------------------------------- /audio/coin.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bwinton/whimsy/4a610c269618730888393a1962138d93c08f683e/audio/coin.mp3 -------------------------------------------------------------------------------- /audio/goomba-stomp.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bwinton/whimsy/4a610c269618730888393a1962138d93c08f683e/audio/goomba-stomp.wav -------------------------------------------------------------------------------- /background-scripts/background.js: -------------------------------------------------------------------------------- 1 | function openPage(){ 2 | browser.tabs.create({ 3 | url: "https://chilloutandwatchsomecatgifs.github.io/" 4 | }); 5 | } 6 | 7 | browser.browserAction.onClicked.addListener(openPage); 8 | -------------------------------------------------------------------------------- /background-scripts/initialize.js: -------------------------------------------------------------------------------- 1 | var placeholders = []; 2 | function initialize(url, callback){ 3 | fetch(url).then(function(response){ 4 | if(response.ok){ 5 | response.text().then(function(data){ 6 | placeholders = data; 7 | placeholders = placeholders.split('\n'); 8 | placeholders = placeholders.map(function(x){ 9 | return x.trim(); 10 | }).filter(function(x){ 11 | return !x.startsWith('#') && (x !== ''); 12 | }) 13 | callback(placeholders); 14 | }) 15 | } else { 16 | throw new Error('Network response was not ok.'); 17 | } 18 | }).catch(function(error) { 19 | console.log('There has been a problem with your fetch operation: ' + error.message); 20 | }); 21 | } 22 | -------------------------------------------------------------------------------- /background-scripts/mario.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | function playSound(url) { 4 | if (pref) { 5 | //play coin/goomba stomp sound 6 | var audio = new Audio(); 7 | audio.src=browser.extension.getURL(url); 8 | audio.autoplay=true; 9 | audio.load(); 10 | } 11 | } 12 | 13 | browser.bookmarks.onCreated.addListener(() => playSound("audio/coin.mp3")); 14 | browser.bookmarks.onRemoved.addListener(() => playSound("audio/goomba-stomp.wav")); 15 | 16 | var pref = false; 17 | 18 | browser.storage.sync.get('mario').then((result) => { 19 | pref = result.mario || result.mario == null; 20 | }); 21 | 22 | browser.storage.onChanged.addListener((changes, area) => { 23 | if (area === 'sync' && changes.mario) { 24 | pref = changes.mario.newValue || changes.mario.newValue == null; 25 | } 26 | }); 27 | -------------------------------------------------------------------------------- /background-scripts/urlbar.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | var placeholders = []; 4 | var url = 'https://bwinton.github.io/whimsy/urlbar-sayings.txt'; 5 | 6 | setDefaults([ 7 | 'Where do you want to go today?', 8 | 'Just type ”google.com“. You know you’re going to.', 9 | 'Let’s do this thing!', 10 | 'Hey, I wonder what we should have for lunch?', 11 | 'Where to, boss?', 12 | 'I hear Facebook is nice this time of year…', 13 | 'You know you can search from here, right?', 14 | 'Have you thought about trying Private Browsing Mode?', 15 | 'You are in a maze of twisty web pages, all alike.', 16 | 'Hi! My name is Url.' 17 | ]); 18 | initialize('https://bwinton.github.io/whimsy/urlbar-sayings.txt', setTitle); 19 | 20 | function setDefaults(defaults){ 21 | placeholders = defaults; 22 | } 23 | function setTitle(placeholders){ 24 | var getting = browser.storage.sync.get('sayings'); 25 | getting.then((result)=>{ 26 | if (result.sayings || result.sayings == null){ 27 | //set browserAction title to random saying 28 | let rand = Math.floor(Math.random() * placeholders.length); 29 | browser.tabs.onActivated.addListener(onActivated); 30 | browser.browserAction.setTitle({ 31 | title: placeholders[rand] 32 | }); 33 | } else { 34 | browser.tabs.onActivated.removeListener(onActivated); 35 | browser.browserAction.setTitle({ 36 | title: "Whimsy" 37 | }); 38 | } 39 | }) 40 | } 41 | function onChange(text, suggest){ 42 | //get preference setting 43 | var getting = browser.storage.sync.get('sayings'); 44 | getting.then((result)=>{ 45 | if (result.sayings || result.sayings == null){ 46 | addSuggestions() 47 | .then(suggest); 48 | } 49 | }); 50 | } 51 | function addSuggestions(){ 52 | return new Promise(resolve => { 53 | let suggestions = []; 54 | //pick a random saying 55 | let rand = Math.floor(Math.random() * placeholders.length); 56 | //add as suggestion 57 | suggestions.push({ 58 | content: placeholders[rand], 59 | description: placeholders[rand], 60 | }); 61 | return resolve(suggestions); 62 | }); 63 | } 64 | function onActivated(activeInfo){ 65 | let rand = Math.floor(Math.random() * placeholders.length); 66 | browser.browserAction.setTitle({ 67 | title: placeholders[rand], 68 | tabId: activeInfo.tabId 69 | }); 70 | } 71 | function onStorageChange(changes, area) { 72 | if (changes.sayings.oldValue != changes.sayings.newValue){ 73 | setTitle(placeholders); 74 | } 75 | } 76 | 77 | browser.omnibox.setDefaultSuggestion({description: "Whimsy sayings"}); 78 | browser.omnibox.onInputChanged.addListener(onChange); 79 | browser.tabs.onActivated.addListener(onActivated); 80 | browser.storage.onChanged.addListener(onStorageChange); 81 | -------------------------------------------------------------------------------- /icon.svg: -------------------------------------------------------------------------------- 1 | Whimsycorn-v3.0-Sticker -------------------------------------------------------------------------------- /manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "manifest_version": 2, 3 | "name": "Whimsy", 4 | "version": "3.2.0", 5 | "description": "Add some Whimsy to your Firefox! 🦄🌈", 6 | "homepage_url": "https://github.com/bwinton/whimsy/tree/webextension", 7 | "author": "Blake Winton ", 8 | "contributors": [ 9 | "Daniela Arcese", 10 | "Erica Wright" 11 | ], 12 | "icons": { 13 | "48": "icon.svg", 14 | "64": "icon.svg", 15 | "96": "icon.svg" 16 | }, 17 | 18 | "applications": { 19 | "gecko": { 20 | "id": "jid1-6mUPixNFCjAgkg@jetpack" 21 | } 22 | }, 23 | "permissions": [ 24 | "tabs", 25 | "storage", 26 | "bookmarks", 27 | "topSites", 28 | "contextMenus", 29 | "clipboardWrite" 30 | ], 31 | "background": { 32 | "scripts": [ 33 | "background-scripts/background.js", 34 | "background-scripts/initialize.js", 35 | "background-scripts/mario.js", 36 | "background-scripts/urlbar.js" 37 | ] 38 | }, 39 | "browser_action": { 40 | "default_icon": { 41 | "48": "icon.svg", 42 | "64": "icon.svg", 43 | "96": "icon.svg" 44 | }, 45 | "default_title": "Whimsy" 46 | }, 47 | "options_ui": { 48 | "page": "settings/options.html", 49 | "chrome_style": true 50 | }, 51 | "omnibox": { 52 | "keyword": "whimsy" 53 | }, 54 | "chrome_url_overrides": { 55 | "newtab": "newtab-page/newtab.html" 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /newtab-page/newtab-content.css: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | html { 6 | width: 100%; 7 | height: 100%; 8 | } 9 | 10 | body { 11 | font: message-box; 12 | width: 100%; 13 | height: 100%; 14 | padding: 0; 15 | margin: 0; 16 | background-color: #F9F9FA; 17 | position: relative; 18 | -moz-user-focus: normal; 19 | } 20 | 21 | /* SEARCH */ 22 | #newtab-search-container { 23 | margin: 55px 0 15px; 24 | } 25 | 26 | /* GRID */ 27 | #newtab-grid { 28 | margin: 3em 10%; 29 | display: grid; 30 | grid-template-columns: repeat(4, minmax(200px, 320px)); 31 | grid-gap: 2%; 32 | grid-auto-rows: minmax(180px, auto); 33 | text-align: center; 34 | transition: 100ms ease-out; 35 | transition-property: opacity; 36 | } 37 | 38 | .newtab-cell { 39 | background-color: rgba(255,255,255,.2); 40 | border-radius: 4px; 41 | box-shadow: 0 2px 4px #c1c1c1; 42 | text-decoration: none; 43 | overflow: hidden; 44 | } 45 | 46 | .newtab-link { 47 | overflow: hidden; 48 | } 49 | 50 | .newtab-thumbnail, 51 | .newtab-title { 52 | display: block; 53 | } 54 | 55 | .newtab-thumbnail { 56 | background-origin: padding-box; 57 | background-clip: padding-box; 58 | background-repeat: no-repeat; 59 | background-size: cover; 60 | min-height: 180px; 61 | transition: opacity 100ms ease-out; 62 | } 63 | 64 | .newtab-title { 65 | overflow: hidden; 66 | text-align: center; 67 | white-space: nowrap; 68 | text-overflow: ellipsis; 69 | vertical-align: middle; 70 | background-color: #F9F9FA; 71 | font-size: 14px; 72 | line-height: 30px; 73 | border: 1px solid #fff; 74 | border-radius: 0 0 4px 4px; 75 | color: rgba(12, 12, 13, 0.76); 76 | } 77 | -------------------------------------------------------------------------------- /newtab-page/newtab.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | New Tab 4 | 5 | 6 | 7 | 8 | 9 | 10 |
11 | 12 |
13 |
14 |

Your Top Sites

15 |
16 | 17 | Copy Thumbnail URL 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "whimsy", 3 | "version": "3.2.0", 4 | "dependencies": { 5 | "tippy-top-sites": "1.2.2" 6 | }, 7 | "scripts": { 8 | "build": "browserify src/newtab.js -o newtab-page/bundle.js && web-ext build -i src -i package*", 9 | "dev": "watchify src/newtab.js -o newtab-page/bundle.js && web-ext run -i src -i package*", 10 | "start": "browserify src/newtab.js -o newtab-page/bundle.js && web-ext run -f nightly -i src -i package*" 11 | }, 12 | "devDependencies": { 13 | "browserify": "14.4.0", 14 | "watchify": "3.9.0", 15 | "web-ext": "2.0.0" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /settings/options.css: -------------------------------------------------------------------------------- 1 | body{ 2 | font: 14px Arial; 3 | padding: .5em; 4 | } 5 | 6 | form { 7 | display: grid; 8 | grid-template-columns: auto 1fr 4fr; 9 | grid-gap: 8px; 10 | justify-items: start; 11 | } 12 | 13 | i { 14 | font: 12px Arial; 15 | font-style: italic; 16 | } 17 | -------------------------------------------------------------------------------- /settings/options.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | adds fun Mario sounds 11 | 12 | 13 | type 'whimsy' plus 2 spaces into urlbar 14 | 20 | 21 | adds better icons for the new tab page 22 |
23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /settings/options.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | //save user's preferences in storage 4 | function saveOptions(){ 5 | browser.storage.sync.set({ 6 | mario: document.querySelector('#mario').checked, 7 | sayings: document.querySelector('#sayings').checked, 8 | newtab: document.querySelector('#newtab').value 9 | }); 10 | } 11 | //load preferences or set to default true 12 | function restoreOptions(){ 13 | var getting = browser.storage.sync.get(null); 14 | getting.then((result)=>{ 15 | if (result.mario==null){ 16 | document.querySelector("#mario").checked=true; 17 | } 18 | else{ 19 | document.querySelector("#mario").checked = result.mario ; 20 | } 21 | if (result.sayings==null){ 22 | document.querySelector("#sayings").checked=true; 23 | } 24 | else{ 25 | document.querySelector("#sayings").checked = result.sayings ; 26 | } 27 | if (result.newtab==null){ 28 | document.querySelector("#newtab").value="on"; 29 | } 30 | else{ 31 | document.querySelector("#newtab").value = result.newtab ; 32 | } 33 | }, onError); 34 | 35 | function onError(error){ 36 | console.log('Error: ${error}'); 37 | } 38 | } 39 | 40 | document.addEventListener('DOMContentLoaded', restoreOptions); 41 | document.querySelector("#mario").addEventListener("click", saveOptions); 42 | document.querySelector("#sayings").addEventListener("click", saveOptions); 43 | document.querySelector("#newtab").addEventListener("change", saveOptions); 44 | -------------------------------------------------------------------------------- /src/newtab.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | var listener = false; 4 | var tippyTopSites = require('tippy-top-sites'); 5 | var pref = null; 6 | 7 | browser.storage.sync.get('newtab').then((result) => { 8 | pref = result.newtab; 9 | }); 10 | 11 | function onError(error) { 12 | console.log(error); 13 | } 14 | 15 | function createElement(label, attrs, children=[]) { 16 | let rv = document.createElement(label); 17 | if (attrs) { 18 | for (let attr in attrs) { 19 | rv.setAttribute(attr, attrs[attr]); 20 | } 21 | } 22 | for (var index = 0; index < children.length; index++) { 23 | rv.appendChild(children[index]); 24 | } 25 | return rv; 26 | } 27 | 28 | function createTopSite(topSite) { 29 | console.log("Topsite:", topSite.title || topSite.url, topSite); 30 | const rv = createElement('div', {'class': 'newtab-cell', 'contextmenu': 'context-menu'}, [ 31 | createElement('a', {'class': 'newtab-link', 'title': topSite.title || '', 'href':topSite.url}, [ 32 | createElement('span', {'class': 'newtab-thumbnail thumbnail'}), 33 | createElement('span', {'class': 'newtab-title'}, [ 34 | document.createTextNode(topSite.title || topSite.url) 35 | ]) 36 | ]) 37 | ]); 38 | return rv; 39 | } 40 | 41 | function loadTopSites(){ 42 | //display top sites 43 | const menu = document.getElementById('context-menu'); 44 | menu.addEventListener('click', e => { 45 | let copyElement = document.createElement('input'); 46 | copyElement.setAttribute('type', 'text'); 47 | copyElement.setAttribute('value', menu.dataset.url); 48 | copyElement = document.body.appendChild(copyElement); 49 | copyElement.select(); 50 | document.execCommand('copy'); 51 | copyElement.remove(); 52 | }); 53 | browser.topSites.get().then((topSitesArray) => { 54 | const node = document.getElementById('newtab-grid'); 55 | 56 | const sites = document.createDocumentFragment(); 57 | for (let topSite of topSitesArray) { 58 | sites.appendChild(createTopSite(topSite)); 59 | } 60 | const grid = node.cloneNode(false); 61 | grid.append(sites); 62 | node.parentNode.replaceChild(grid, node); 63 | grid.addEventListener('contextmenu', e => { 64 | let target = e.target; 65 | while (target && !target.classList.contains('newtab-link')) { 66 | target = target.parentNode; 67 | } 68 | if (!target) { 69 | e.preventDefault(); 70 | return false; 71 | } 72 | menu.dataset.url = target.dataset.url; 73 | }); 74 | }, onError) 75 | .then(initialize('https://bwinton.github.io/whimsy/thumbnail-gifs.txt', setThumbnail)) 76 | .then(initialize('https://bwinton.github.io/whimsy/urlbar-sayings.txt', setTitle)); 77 | } 78 | 79 | function setTitle(placeholders) { 80 | let rand = Math.floor(Math.random() * placeholders.length); 81 | document.title = `New Tab: ${placeholders[rand]}`; 82 | } 83 | 84 | function setThumbnail(placeholders){ 85 | if (pref == "on" || pref == null){ 86 | document.querySelectorAll(".thumbnail").forEach(function(img){ 87 | if(listener){ 88 | img.removeEventListener("mouseover", onMouseOver); 89 | img.removeEventListener("mouseover", onMouseLeave); 90 | } 91 | let rand = Math.floor(Math.random() * placeholders.length); 92 | img.parentNode.dataset.url = placeholders[rand]; 93 | img.style.backgroundImage = "url("+placeholders[rand]+")"; 94 | }) 95 | } else if (pref == "hover"){ 96 | document.querySelectorAll(".newtab-link").forEach(function(link){ 97 | //set default thumbnail 98 | var href = link.href; 99 | link.querySelectorAll(".thumbnail").forEach(function(img){ 100 | createCanvas(href, link, img); 101 | }) 102 | }) 103 | } else if (pref == "pics"){ 104 | document.querySelectorAll(".thumbnail").forEach(function(img){ 105 | if(listener){ 106 | img.removeEventListener("mouseover", onMouseOver); 107 | img.removeEventListener("mouseout", onMouseLeave); 108 | } 109 | var newImg = document.createElement('img'); 110 | img.parentNode.replaceChild(newImg, img); 111 | let rand = Math.floor(Math.random() * placeholders.length); 112 | newImg.src = placeholders[rand]; 113 | newImg.style.opacity = 0; 114 | newImg.style.marginTop = "-180px"; 115 | //set to paused gif 116 | var c = document.createElement('canvas'); 117 | var width = c.width = newImg.width = 290; 118 | var height = c.height = newImg.height = 180; 119 | var freeze = function(){ 120 | c.getContext('2d').drawImage(newImg,0,0,width,height); 121 | // c.style.position = 'absolute'; 122 | newImg.parentNode.insertBefore(c, newImg); 123 | } 124 | newImg.addEventListener('load', freeze, true); 125 | newImg.addEventListener("mouseover", onMouseOver); 126 | newImg.addEventListener("mouseout", onMouseLeave); 127 | }) 128 | } else if (pref == "off"){ 129 | document.querySelectorAll(".newtab-link").forEach(function(link){ 130 | //set thumbnail to favicon 131 | var href = link.href; 132 | link.querySelectorAll(".thumbnail").forEach(function(img){ 133 | if(listener){ 134 | img.removeEventListener("mouseover", onMouseOver); 135 | img.removeEventListener("mouseout", onMouseLeave); 136 | } 137 | var info = tippyTopSites.getSiteData(href); 138 | if(info.image_url != null){ 139 | img.style.backgroundImage = "url(../node_modules/tippy-top-sites/images/"+info.image_url+")"; 140 | img.style.backgroundColor = info.background_color; 141 | img.style.backgroundSize = "auto"; 142 | img.style.backgroundPosition = "center"; 143 | } else { 144 | var newUrl = new URL("/favicon.ico", href); 145 | img.style.backgroundImage = "url("+newUrl+")"; 146 | img.style.backgroundSize = "100px"; 147 | img.style.backgroundPosition = "center"; 148 | } 149 | }) 150 | }) 151 | } 152 | } 153 | function createCanvas(href, link, img){ 154 | //set gifs 155 | let rand = Math.floor(Math.random() * placeholders.length); 156 | img.parentNode.dataset.url = placeholders[rand]; 157 | img.style.backgroundImage = "url("+placeholders[rand]+")"; 158 | 159 | //add canvas on every gif 160 | var canvas = document.createElement('canvas'); 161 | canvas.class = "newtab-thumbnail thumbnail"; 162 | canvas.width = 290; 163 | canvas.height = 180; 164 | 165 | var ctx = canvas.getContext("2d"); 166 | ctx.fillStyle = "rgba(255, 255, 255, 0)"; 167 | ctx.fillRect(0,0,290,180); 168 | img.appendChild(canvas); 169 | 170 | //show favicon on canvas 171 | var info = tippyTopSites.getSiteData(href); 172 | if(info.image_url != null){ 173 | canvas.style.backgroundImage = "url(../node_modules/tippy-top-sites/images/"+info.image_url+")"; 174 | canvas.style.backgroundColor = info.background_color; 175 | canvas.style.backgroundSize = "auto"; 176 | canvas.style.backgroundPosition = "center"; 177 | canvas.style.backgroundRepeat = "no-repeat"; 178 | } else { 179 | var newUrl = new URL("/favicon.ico", href); 180 | canvas.style.backgroundImage = "url("+newUrl+")"; 181 | canvas.style.backgroundColor = "white"; 182 | canvas.style.backgroundSize = "100px"; 183 | canvas.style.backgroundPosition = "center"; 184 | canvas.style.backgroundRepeat = "no-repeat"; 185 | } 186 | img.addEventListener("mouseover", onMouseOver); 187 | img.addEventListener("mouseout", onMouseLeave); 188 | listener = true; 189 | } 190 | 191 | function onMouseOver(e){ 192 | //hide canvas/show gif 193 | // console.log("Got over", e.target); 194 | var i = 1.0; 195 | var interval = setInterval(fade, 35); 196 | function fade(){ 197 | e.target.style.opacity = i; 198 | i-=0.1; 199 | if (i <= 0){ 200 | clearInterval(interval); 201 | } 202 | } 203 | } 204 | function onMouseLeave(e){ 205 | //show canvas/hide gif 206 | var i = 0.0; 207 | var interval = setInterval(fade, 35); 208 | function fade(){ 209 | e.target.style.opacity = i; 210 | i+=0.1; 211 | if (i >= 1.0){ 212 | clearInterval(interval); 213 | } 214 | } 215 | } 216 | function onStorageChange(changes, area) { 217 | if(changes.newtab.newValue != changes.newtab.oldValue){ 218 | pref = changes.newtab.newValue; 219 | //refresh open new tab pages 220 | var querying = browser.tabs.query({title: "New Tab"}); 221 | querying.then((tabs) => { 222 | tabs.forEach(function(tab){ 223 | browser.tabs.reload(tab.id); 224 | }) 225 | }); 226 | } 227 | } 228 | 229 | document.addEventListener('DOMContentLoaded', loadTopSites); 230 | browser.storage.onChanged.addListener(onStorageChange); 231 | --------------------------------------------------------------------------------