├── .gitignore ├── Changelog.MD ├── LICENSE ├── Makefile ├── README.md ├── docs └── architecture.md ├── package-lock.json ├── package.json ├── postcss.config.js ├── press ├── Adresbar-icon-opera.png ├── Google-nunl-search.png ├── Sample440x280.png ├── adressbaricon.jpg └── google-search.jpg ├── rollup.config.js ├── store-submissions ├── 128-circle-template.png ├── cws-assets-template.sketch ├── cws-promo-banner.png ├── cws-promo-large.png ├── cws-promo-small.png ├── cws-screenshots-1- grey-hosted.png ├── cws-screenshots-2-green-hosted.png └── cws-screenshots-3-ecosia.png ├── tailwind.config.js ├── thegreenweb ├── background.js ├── green-page-links.js ├── icon.png ├── icons │ ├── 128.png │ ├── 16.png │ ├── 32.png │ └── 48.png ├── images │ ├── gold20x20transp.png │ ├── goldsmiley20x20.png │ ├── green-web-smiley-good.svg │ ├── green20x20.gif │ ├── green20x20transp.png │ ├── greenfan20x20.gif │ ├── greenfan20x20transp.png │ ├── greenhouse20x20.gif │ ├── greenhouse20x20transp.png │ ├── greenquestion20x20.gif │ ├── grey20x20.gif │ ├── grey20x20transp.png │ ├── question20x20transp.png │ └── top-logo-greenweb.png ├── manifest.json ├── options.html ├── options.js ├── popup.html ├── qtip │ ├── jquery.qtip.min.css │ └── jquery.qtip.min.js ├── search-bing.js ├── search-ecosia.js ├── search-google.js ├── search-yahoo.js ├── src │ ├── greencheck.js │ └── search-page-adapter.js ├── tailwind.css ├── thegreenweb-utils.js └── thegreenweb.css └── web-ext-config.js /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | /thegreenweb.crx 3 | /thegreenweb.pem 4 | thegreenweb.zip 5 | thegreenweb/web-ext-artifacts/* 6 | node_modules 7 | 8 | .DS_Store 9 | 10 | # this is auto generated with tailwind now 11 | style.css 12 | dist 13 | *.dist.js 14 | -------------------------------------------------------------------------------- /Changelog.MD: -------------------------------------------------------------------------------- 1 | ## Release notes: 2 | 3 | # 2.4.0 | 16 December 2020 4 | 5 | - Update icons to .png files to show in browsers again 6 | - Add link to options pages on firefox and chrome, to make it easier to switch feature on or off 7 | - Overhaul options page design. 8 | - Update options page to use vanilla js instead of jquery for the new design (it's 2020, we can use modern js now!) 9 | 10 | 2.3.0 | 30 June 2020 11 | - New transparent icons for dark mode and better looking 12 | 13 | 2.2.0 | 13 June 2020 14 | - Add an ethical filtering option, when you enable this option, on search pages results that normally will get a grey smiley as they run on non-green infrastructure will now be filtered out from the webpage, so they are hidden from view. 15 | 16 | 2.1.4 | 25 May 2020 17 | - Add a gold smiley to show our gold partners 18 | 19 | 2.1.3 | 28 Nov 2019 20 | - Fix search mod for google 21 | - Load yahoo search mod on localized yahoo sites 22 | 23 | 2.1.2 | 8th Mar 2019 24 | 25 | 2.1.1 | 17 Nov 2017 26 | - Fix search engine mods 27 | - Upgrade internals to support Firefox Quantum 28 | 29 | 2.0.2 | 29 Apr 2017 30 | - Fix ecosia search engine page mod 31 | 32 | 1.7.5 | 2 Oct 2014 33 | - Fix titles 34 | - Code cleanup 35 | 36 | 1.7.4 | 7 Aug 2014 37 | - Don't underline links to the same domain when all links is on 38 | - Fix bing search page 39 | - Update jquery to 2.1.1 40 | 41 | 42 | 1.7.3 | 22 October 2013 43 | - Don't do lookups on 'localhost' pages 44 | - Update jquery to 2.0.3 45 | 46 | 1.7.1 | 28 June 2013 47 | - Only do a external links lookup on pages with < 100 links, otherwise the browser get's too slow 48 | - Split api requests into 50 lookups per request 49 | - Optimized api requests to half the size 50 | - Don't include querystrings in urls 51 | 52 | 1.6.0 | 06 May 2013 53 | - Fixed google search due to a change in the google page layout 54 | - Don't do a lookup for local files opened with file:// in the addressbar 55 | - Use event pages for faster usage and less memory 56 | - Changed listeners to use webNavigation API so results in earlier greencheck icon in browserbar while page is still loading 57 | 58 | 1.5.0 | 21 April 2013 59 | - Add all links feature to https pages 60 | - Faster and more reliable by using new chrome api's and jquery 2.0 61 | - Some small bugfixes and tweaks 62 | 63 | 1.4.1 | 10 December 2012 64 | - Added a tooltip to the "all links" feature, so you can see the details of the check. It's also possible to click through for more information on the Green Web Foundation website. 65 | - Added tooltips to greencheck results on the Ecosia, Bing and Yahoo Search engines 66 | 67 | 1.3.0 | 14 November 2012 68 | - Added "all links" check for greenness, all external links on a page will be underlined with a green border if they link to a green hosted website 69 | - Added option to options page to disable the "all links" check 70 | 71 | 1.2.0 | 24 September 2012 72 | - Added an options page: It's now possible to disable the search feature on the option page 73 | 74 | 1.1.3 | 20 September 2012 75 | - Fix permission errors while installing 76 | - Updated to faster api for site lookups 77 | - Chrome manifest update 78 | 79 | 1.1.0 | 07 March 2012 80 | - Now works on google search pages on a secure connection (https) 81 | - Simplified needed permissions 82 | 83 | 1.0.4 | 02 March 2012 84 | - Supports Ecosia.org search engine 85 | 86 | 1.0.2 | 16 January 2012 87 | - Fix to correctly check urls with port numbers 88 | - Works correctly with AVG Safe Search 89 | 90 | 1.0.1 | 10-10-2011 91 | - Fix for new google search page layout 92 | 93 | 1.0.0 | 15-09-2011 94 | First stable version 95 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014-2020 The Green Web Foundation 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 | 23 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | firefox.dev: 2 | npm run firefox:dev 3 | 4 | css.dist: 5 | npm run css:dist 6 | 7 | css.build: 8 | npm run css:build 9 | 10 | css.watch: 11 | npm run css:watch 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | The Green Web web-extension 2 | =========================== 3 | 4 | The Green Web Addon 5 | 6 | Tested on Chrome, Brave, Opera and Firefox 7 | 8 | Should also work on other browsers with web-extensions support like Edge (not tested yet) 9 | 10 | ## Usage 11 | 12 | Want to use this extension, install it from the browser webstores : 13 | 14 | For Chrome, Brave: 15 | https://chrome.google.com/webstore/detail/the-green-web/ekiibapogjgmlhlhpoalbppfhhgkcogc 16 | 17 | For Firefox: 18 | https://addons.mozilla.org/nl/firefox/addon/the-green-web/ 19 | 20 | For Opera: 21 | https://addons.opera.com/nl/extensions/details/the-green-web/ 22 | 23 | ## Want to help? 24 | 25 | ### Development on Chrome 26 | 1. git clone git@github.com:thegreenwebfoundation/web-extension.git 27 | 2. Open Chrome, click Extra -> Extensions and click on Developer Mode 28 | 3. Use "Load unpacked extension" to load this directory 29 | 4. Make your changes, test them out (use ctrl+r in the extension screen to reload) 30 | 5. Happy with the changes? Do a commit and make a pull request 31 | 32 | ### Development on Brave 33 | 1. git clone git@github.com:thegreenwebfoundation/web-extension.git 34 | 2. Navigate to `brave://extensions` and toggle on Developer Mode 35 | 3. Follow the same steps as for Chrome 36 | 37 | ### Development on Firefox 38 | 1. git clone git@github.com:thegreenwebfoundation/web-extension.git 39 | 2. Install web-ext (npm install -g web-ext) 40 | 3. Run `web-ext run` inside the `thegreenweb/` folder 41 | 4. Make your changes, test them out (use ctrl+r in the terminal where web-ext is running to reload) 42 | 5. Happy with the changes? Do a commit and make a pull request 43 | 44 | ### Development on Opera 45 | 1. See steps from Chrome but use Opera instead 46 | 47 | ## Publishing hints 48 | ### Chrome 49 | 1. After step 5 of the chrome development above, zip up thegreenweb directory into thegreenweb.zip 50 | 2. login on the chrome webstore developer console : https://chrome.google.com/u/3/webstore/devconsole 51 | 3. Go to the thegreenweb item and upload a new package and click on publish. 52 | 4. it might complain about extended permissions, for now we run this on every website so we need broad host permissions. 53 | Just click submit for review/publish. 54 | 55 | ### Firefox 56 | 1. after step 5 of the firefox development above, run `web-ext build' inside the `thegreenweb/` folder 57 | 2. This will create a file in `thegreenweb/web-ext-artifacts` named `the_green_web-x.x.x.zip` where x.x.x is the version number in the manifest.json file 58 | 3. Login to the addons.mozilla.org page and go to our addon to sumbit a new version 59 | 4. add some version details and then click submit so they can review it. 60 | 61 | ### Opera 62 | 1. After step 5, launch opera and click pack extension. Get the private key from AJ 63 | 2. Submit this crx to the opera webstore at https://addons.opera.com/developer/package/133065/ 64 | 65 | ### Found a bug? 66 | 67 | 1. Make an issue on this repository and we'll look into it 68 | 69 | 70 | ## Credits 71 | 72 | This project uses the [Chrome Web Store Sketch assets template][cws-sketch-template], by [Jean Francois Goncalves][jfg]. 73 | 74 | [cws-sketch-template]: https://www.sketchappsources.com/free-source/409-chrome-web-store-assets-template.html 75 | [jfg]: https://www.sketchappsources.com/contributor/jfgoncalves -------------------------------------------------------------------------------- /docs/architecture.md: -------------------------------------------------------------------------------- 1 | # Key parts 2 | 3 | # Updating the browser chrome on the page 4 | 5 | The web extension works by relying on on the background.js to make calls to the API, in response to listeners being triggered when a page loads, or tab is activated. 6 | 7 | In both cases `doGreencheckForTabReplace` is called, calling `getGreencheck` which then calls `doRequest` to update the chrome for the given tab, with the grey/green icon. 8 | 9 | # Updating the page DOM - links to green sites, and annotating search results 10 | 11 | In addition to updating the browser chrome, the extension also adds extra markup on the page on browsers, using the content scripts, `search-all.js`, `search-bing.js` and so on. 12 | 13 | These send messages to background.js with `runtime.sendMessage`, which triggers `chrome.runtime.onMessage` in background.js. This calls `doSearchRequest`, which triggers a batched API request with `doApiRequest`, to send info back to the tab, to update the DOM. 14 | 15 | More info on: 16 | https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/webNavigation/onCommitted 17 | https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Content_scripts#Communicating_with_background_scripts 18 | https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/runtime/sendMessage 19 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "web-extension", 3 | "version": "0.3.0", 4 | "description": "The green web foundation", 5 | "main": "index.js", 6 | "directories": { 7 | "doc": "docs" 8 | }, 9 | "dependencies": { 10 | "jquery": "^3.5.1", 11 | "tippy.js": "^6.3.1" 12 | }, 13 | "devDependencies": { 14 | "@rollup/plugin-commonjs": "^17.1.0", 15 | "@rollup/plugin-node-resolve": "^11.2.0", 16 | "autoprefixer": "^10.1.0", 17 | "nodemon": "^2.0.20", 18 | "postcss": "^8.2.13", 19 | "postcss-cli": "^8.3.1", 20 | "postcss-import": "^14.0.0", 21 | "rollup": "^2.40.0", 22 | "tailwindcss": "^2.0.2", 23 | "web-ext": "^7.5.0", 24 | "webextension-polyfill": "^0.7.0" 25 | }, 26 | "scripts": { 27 | "css:dist": "NODE_ENV=production npm run css:build", 28 | "css:build": "postcss -c ../postcss.config.js ./thegreenweb/tailwind.css -o ./thegreenweb/style.css", 29 | "css:watch": "postcss -c postcss.config.js ./thegreenweb/tailwind.css -o ./thegreenweb/style.css --watch", 30 | "firefox:dev": "web-ext run --verbose", 31 | "firefox:dev:ecosia": "web-ext run --verbose --url 'https://www.ecosia.org/search?q=trees' --browser-console", 32 | "firefox:dev:bing": "web-ext run --verbose --url 'http://www.bing.com/search?q=trees' --browser-console", 33 | "firefox:dev:yahoo": "web-ext run --verbose --url 'https://search.yahoo.com/search;?p=trees' --browser-console" 34 | }, 35 | "repository": { 36 | "type": "git", 37 | "url": "git+https://github.com/thegreenwebfoundation/web-extension.git" 38 | }, 39 | "keywords": [ 40 | "browser", 41 | "extensions", 42 | "tgwf", 43 | "sustainability", 44 | "green" 45 | ], 46 | "author": "AJ Tetterroo, Chris Adams, and friends", 47 | "license": "Apache-2.0", 48 | "bugs": { 49 | "url": "https://github.com/thegreenwebfoundation/web-extension/issues" 50 | }, 51 | "homepage": "https://github.com/thegreenwebfoundation/web-extension#readme" 52 | } 53 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: [ 3 | require('postcss-import'), 4 | require('tailwindcss'), 5 | require('autoprefixer') 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /press/Adresbar-icon-opera.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thegreenwebfoundation/web-extension/c7d916d307ac8943859fa4c83525bf0f8d31aee1/press/Adresbar-icon-opera.png -------------------------------------------------------------------------------- /press/Google-nunl-search.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thegreenwebfoundation/web-extension/c7d916d307ac8943859fa4c83525bf0f8d31aee1/press/Google-nunl-search.png -------------------------------------------------------------------------------- /press/Sample440x280.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thegreenwebfoundation/web-extension/c7d916d307ac8943859fa4c83525bf0f8d31aee1/press/Sample440x280.png -------------------------------------------------------------------------------- /press/adressbaricon.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thegreenwebfoundation/web-extension/c7d916d307ac8943859fa4c83525bf0f8d31aee1/press/adressbaricon.jpg -------------------------------------------------------------------------------- /press/google-search.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thegreenwebfoundation/web-extension/c7d916d307ac8943859fa4c83525bf0f8d31aee1/press/google-search.jpg -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | // rollup.config.js 2 | 3 | import resolve from '@rollup/plugin-node-resolve' 4 | import commonjs from '@rollup/plugin-commonjs' 5 | 6 | // define the plugins here, as we use them the same way each time 7 | const plugins = [ 8 | resolve(), 9 | commonjs(), 10 | ] 11 | 12 | export default [ 13 | // main background script 14 | { 15 | input: 'thegreenweb/background.js', 16 | output: { 17 | file: 'thegreenweb/background.dist.js', 18 | format: 'iife', 19 | }, 20 | plugins, 21 | }, 22 | // options page 23 | { 24 | input: 'thegreenweb/options.js', 25 | output: { 26 | file: 'thegreenweb/options.dist.js', 27 | format: 'iife', 28 | }, 29 | plugins, 30 | }, 31 | // general-green-links 32 | { 33 | input: 'thegreenweb/green-page-links.js', 34 | output: { 35 | file: 'thegreenweb/green-page-links.dist.js', 36 | format: 'iife', 37 | }, 38 | plugins 39 | }, 40 | { 41 | input: 'thegreenweb/search-bing.js', 42 | output: { 43 | file: 'thegreenweb/search-bing.dist.js', 44 | format: 'iife', 45 | }, 46 | plugins 47 | }, 48 | // ecosia 49 | { 50 | input: 'thegreenweb/search-ecosia.js', 51 | output: { 52 | file: 'thegreenweb/search-ecosia.dist.js', 53 | format: 'iife', 54 | }, 55 | plugins 56 | }, 57 | // yahoo 58 | { 59 | input: 'thegreenweb/search-yahoo.js', 60 | output: { 61 | file: 'thegreenweb/search-yahoo.dist.js', 62 | format: 'iife', 63 | }, 64 | plugins 65 | }, 66 | // google 67 | { 68 | input: 'thegreenweb/search-google.js', 69 | output: { 70 | file: 'thegreenweb/search-google.dist.js', 71 | format: 'iife', 72 | }, 73 | plugins 74 | } 75 | , 76 | 77 | ] 78 | -------------------------------------------------------------------------------- /store-submissions/128-circle-template.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thegreenwebfoundation/web-extension/c7d916d307ac8943859fa4c83525bf0f8d31aee1/store-submissions/128-circle-template.png -------------------------------------------------------------------------------- /store-submissions/cws-assets-template.sketch: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thegreenwebfoundation/web-extension/c7d916d307ac8943859fa4c83525bf0f8d31aee1/store-submissions/cws-assets-template.sketch -------------------------------------------------------------------------------- /store-submissions/cws-promo-banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thegreenwebfoundation/web-extension/c7d916d307ac8943859fa4c83525bf0f8d31aee1/store-submissions/cws-promo-banner.png -------------------------------------------------------------------------------- /store-submissions/cws-promo-large.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thegreenwebfoundation/web-extension/c7d916d307ac8943859fa4c83525bf0f8d31aee1/store-submissions/cws-promo-large.png -------------------------------------------------------------------------------- /store-submissions/cws-promo-small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thegreenwebfoundation/web-extension/c7d916d307ac8943859fa4c83525bf0f8d31aee1/store-submissions/cws-promo-small.png -------------------------------------------------------------------------------- /store-submissions/cws-screenshots-1- grey-hosted.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thegreenwebfoundation/web-extension/c7d916d307ac8943859fa4c83525bf0f8d31aee1/store-submissions/cws-screenshots-1- grey-hosted.png -------------------------------------------------------------------------------- /store-submissions/cws-screenshots-2-green-hosted.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thegreenwebfoundation/web-extension/c7d916d307ac8943859fa4c83525bf0f8d31aee1/store-submissions/cws-screenshots-2-green-hosted.png -------------------------------------------------------------------------------- /store-submissions/cws-screenshots-3-ecosia.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thegreenwebfoundation/web-extension/c7d916d307ac8943859fa4c83525bf0f8d31aee1/store-submissions/cws-screenshots-3-ecosia.png -------------------------------------------------------------------------------- /tailwind.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | purge: [ 3 | './thegreenweb/**/*.html', 4 | './thegreenweb/**/*.js', 5 | ], 6 | darkMode: false, // or 'media' or 'class' 7 | theme: { 8 | extend: { 9 | colors: { 10 | 'tgwf-green': '#76c22d' 11 | }, 12 | }, 13 | }, 14 | variants: { 15 | extend: {}, 16 | }, 17 | plugins: [], 18 | } 19 | 20 | -------------------------------------------------------------------------------- /thegreenweb/background.js: -------------------------------------------------------------------------------- 1 | import browser from "webextension-polyfill" 2 | import { 3 | getUrl, 4 | stripProtocolFromUrl, 5 | stripQueryStringFromUrl, 6 | stripPageFromUrl, 7 | stripPortFromUrl, 8 | getGreenwebLinkNode, 9 | getFooterElement, 10 | getLinkNode, 11 | getImageNode, 12 | getImagePath, 13 | getResultNode, 14 | getIcon, 15 | getTitle, 16 | getTitleWithLink, 17 | } from './thegreenweb-utils' 18 | import { GreenChecker } from './src/greencheck' 19 | 20 | /* 21 | * Chrome functions for The Green Web addon 22 | * 23 | * @author Arend-Jan Tetteroo 24 | * @copyright Cleanbits/The Green Web Foundation 2010-2020 25 | */ 26 | 27 | /** 28 | * On request, send the data to the green web api 29 | */ 30 | 31 | 32 | 33 | async function handledomainLookup(message, sender, sendResponse) { 34 | console.debug({ sender: sender.url }) 35 | console.debug({ message }) 36 | if (message.locs) { 37 | // doSearchRequest(request.locs, sender.tab); 38 | const greenCheckData = await GreenChecker.checkBulkDomains(message.locs) 39 | return Promise.resolve(greenCheckData) 40 | } 41 | // Instead of returning true, we return a promise that 42 | // resolves to true, as runtime.sendMessage now uses 43 | // Promises 44 | 45 | 46 | 47 | } 48 | browser.runtime.onMessage.addListener(handledomainLookup); 49 | /** 50 | * Attach the normal pageAction to the tabs 51 | */ 52 | function doGreencheckForTabReplace(details) { 53 | let tabId = details.tabId; 54 | let chosenTab = browser.tabs.get(tabId) 55 | chosenTab.then(function (tab) { 56 | if (tab && tab.url) { 57 | var url = tab.url; 58 | tabId = tab.id; 59 | 60 | if (isUrl(url)) { 61 | var checkUrl = getUrl(url); 62 | if (checkUrl !== false) { 63 | getGreencheck(checkUrl, tabId); 64 | } 65 | } 66 | } 67 | }) 68 | } 69 | 70 | browser.webNavigation.onCommitted.addListener(function (details) { 71 | doGreencheckForTabReplace(details); 72 | }); 73 | browser.tabs.onActivated.addListener(function (details) { 74 | doGreencheckForTabReplace(details); 75 | }); 76 | 77 | /** 78 | * Check if the url is available for lookup, chrome and file need to be ignored 79 | * 80 | * @param url 81 | * @returns {boolean} 82 | */ 83 | function isUrl(url) { 84 | var prot = url.substring(0, 6); 85 | if (prot === 'chrome' || prot === 'file:/') { 86 | // Don't show anything for chrome pages 87 | return false; 88 | } 89 | return true; 90 | } 91 | 92 | function getCurrentTime() { 93 | var date = new Date(); 94 | return date.getTime(); 95 | } 96 | 97 | /** 98 | * Do a greencheck api call if not cached 99 | * 100 | * @param url 101 | * @param tabId 102 | */ 103 | function getGreencheck(url, tabId) { 104 | var currentTime = getCurrentTime(); 105 | 106 | var cache = window.localStorage.getItem(url); 107 | if (cache !== null) { 108 | // Item in cache, check cachetime 109 | var resp = JSON.parse(cache); 110 | if (resp.time && resp.time > currentTime - 3600000) { 111 | showIcon(resp, tabId); 112 | return; 113 | } 114 | } 115 | doRequest(url, tabId); 116 | } 117 | 118 | /** 119 | * Do the search request 120 | */ 121 | function doSearchRequest(data, tab) { 122 | var length = Object.keys(data).length; 123 | // We ignore sites with more than a 100 urls 124 | if (length <= 100) { 125 | var sites = Object.getOwnPropertyNames(data).splice(0, 50); 126 | var sitesUrl = JSON.stringify(sites); 127 | 128 | doApiRequest(sitesUrl, tab); 129 | 130 | if (length > 50) { 131 | sites = Object.getOwnPropertyNames(data).splice(50, 50); 132 | sitesUrl = JSON.stringify(sites); 133 | console.log(sitesUrl) 134 | 135 | doApiRequest(sitesUrl, tab); 136 | } 137 | } 138 | } 139 | 140 | /** 141 | * Do an api request for multiple sites. 142 | * After checking the sites returns the result, 143 | * plus whether to filter the grey results from the page 144 | * 145 | * @param siteDomains 146 | * @param tab 147 | */ 148 | function doApiRequest(siteDomains, tab) { 149 | console.debug("background:doApiRequest") 150 | const req = browser.storage.local.get("filter-out-grey-search-results") 151 | req.then(function (items) { 152 | 153 | const filterOutGreyResults = items && items['filter-out-grey-search-results'] 154 | 155 | 156 | console.debug("background:doApiRequest", { siteDomains }) 157 | 158 | 159 | var xhr = new XMLHttpRequest(); 160 | 161 | xhr.open("GET", "https://api.thegreenwebfoundation.org/v2/greencheckmulti/" + siteDomains, true); 162 | xhr.onreadystatechange = function () { 163 | if (xhr.readyState === 4) { 164 | var greenChecks = JSON.parse(xhr.responseText); 165 | 166 | // send the checked urls back to the tab that made the request 167 | console.debug("background:doApiRequest", { greenChecks }) 168 | browser.tabs.sendMessage(tab.id, { 169 | data: greenChecks, filter: filterOutGreyResults 170 | }); 171 | } 172 | } 173 | xhr.send(); 174 | }); 175 | } 176 | 177 | /** 178 | * Do the request for a single url 179 | */ 180 | function doRequest(url, tabId) { 181 | var xhr = new XMLHttpRequest(); 182 | xhr.open("GET", "https://api.thegreenwebfoundation.org/greencheck/" + url, true); 183 | xhr.onreadystatechange = function () { 184 | if (xhr.readyState === 4) { 185 | var resp = JSON.parse(xhr.responseText); 186 | resp.time = getCurrentTime(); 187 | window.localStorage.setItem(url, JSON.stringify(resp)); 188 | showIcon(resp, tabId); 189 | } 190 | } 191 | xhr.send(); 192 | } 193 | 194 | /** 195 | * Show the resulting icon based on the response 196 | */ 197 | function showIcon(resp, tabId) { 198 | var icon = getImagePath(getIcon(resp), true); 199 | var title = getTitle(resp); 200 | browser.pageAction.setIcon({ 'tabId': tabId, 'path': icon }); 201 | browser.pageAction.setTitle({ 'tabId': tabId, 'title': title }); 202 | browser.pageAction.show(tabId); 203 | } 204 | 205 | browser.pageAction.onClicked.addListener(function () { 206 | browser.runtime.openOptionsPage() 207 | }); 208 | -------------------------------------------------------------------------------- /thegreenweb/green-page-links.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Pagemod for all external links on site 3 | * 4 | * @author Arend-Jan Tetteroo 5 | * @copyright Cleanbits/The Green Web Foundation 2010-2020 6 | */ 7 | 8 | 9 | /** 10 | * If document is ready, find the urls to check 11 | 12 | 13 | // accept a object of form: 14 | // { 15 | // "www.google.com": { 16 | // data: true 17 | // green: true 18 | // hostedby: "Google Inc." 19 | // hostedbyid: 595 20 | // hostedbywebsite: "www.google.com" 21 | // partner: null 22 | // url: "www.google.com" 23 | // } 24 | // } 25 | */ 26 | function annotateLinksInDom(data) { 27 | var currenturl = getUrl(document.URL); 28 | 29 | 30 | $("a").not('.TGWF-addon').each(function (i) { 31 | 32 | var loc = $(this).attr('href'); 33 | var strippedurl = getUrl(loc); 34 | 35 | if (loc && strippedurl) { 36 | 37 | // this the current url, no need to annotate 38 | if (strippedurl === currenturl) { 39 | return true; 40 | } 41 | const domainResult = data[strippedurl] 42 | 43 | if (domainResult) { 44 | 45 | if (domainResult.green) { 46 | $(this) 47 | .addClass('tgwf_green') 48 | .qtip({ 49 | content: { 50 | text: function (api) { 51 | return getTitleWithLink(domainResult); 52 | } 53 | }, 54 | show: { delay: 700 }, 55 | hide: { fixed: true, delay: 500 }, 56 | style: { 57 | classes: 'qtip-green' 58 | } 59 | }); 60 | } else { 61 | $(this).addClass('tgwf_grey'); 62 | } 63 | } 64 | } 65 | }); 66 | } 67 | 68 | function checkLinkDomains() { 69 | chrome.storage.sync.get("tgwf_all_disabled", async function (items) { 70 | if (items && items.tgwf_all_disabled && items.tgwf_all_disabled === "1") { 71 | // Green web search is disabled, return 72 | return; 73 | } 74 | await getUrlsAndSendRequest(); 75 | }); 76 | } 77 | 78 | async function getUrlsAndSendRequest() { 79 | console.log("Link checking enabled.") 80 | var locs = {}; 81 | $("a").not('.TGWF-addon').each(function () { 82 | var loc = $(this).attr('href'); 83 | var strippedurl = getUrl(loc); 84 | if (loc && strippedurl) { 85 | locs[strippedurl] = strippedurl 86 | } 87 | }); 88 | 89 | if (Object.keys(locs).length > 0) { 90 | const greenCheckData = await GreenChecker.checkBulkDomains(locs) 91 | annotateLinksInDom(greenCheckData) 92 | } 93 | } 94 | $(document).ready(checkLinkDomains); 95 | -------------------------------------------------------------------------------- /thegreenweb/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thegreenwebfoundation/web-extension/c7d916d307ac8943859fa4c83525bf0f8d31aee1/thegreenweb/icon.png -------------------------------------------------------------------------------- /thegreenweb/icons/128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thegreenwebfoundation/web-extension/c7d916d307ac8943859fa4c83525bf0f8d31aee1/thegreenweb/icons/128.png -------------------------------------------------------------------------------- /thegreenweb/icons/16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thegreenwebfoundation/web-extension/c7d916d307ac8943859fa4c83525bf0f8d31aee1/thegreenweb/icons/16.png -------------------------------------------------------------------------------- /thegreenweb/icons/32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thegreenwebfoundation/web-extension/c7d916d307ac8943859fa4c83525bf0f8d31aee1/thegreenweb/icons/32.png -------------------------------------------------------------------------------- /thegreenweb/icons/48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thegreenwebfoundation/web-extension/c7d916d307ac8943859fa4c83525bf0f8d31aee1/thegreenweb/icons/48.png -------------------------------------------------------------------------------- /thegreenweb/images/gold20x20transp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thegreenwebfoundation/web-extension/c7d916d307ac8943859fa4c83525bf0f8d31aee1/thegreenweb/images/gold20x20transp.png -------------------------------------------------------------------------------- /thegreenweb/images/goldsmiley20x20.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thegreenwebfoundation/web-extension/c7d916d307ac8943859fa4c83525bf0f8d31aee1/thegreenweb/images/goldsmiley20x20.png -------------------------------------------------------------------------------- /thegreenweb/images/green-web-smiley-good.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /thegreenweb/images/green20x20.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thegreenwebfoundation/web-extension/c7d916d307ac8943859fa4c83525bf0f8d31aee1/thegreenweb/images/green20x20.gif -------------------------------------------------------------------------------- /thegreenweb/images/green20x20transp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thegreenwebfoundation/web-extension/c7d916d307ac8943859fa4c83525bf0f8d31aee1/thegreenweb/images/green20x20transp.png -------------------------------------------------------------------------------- /thegreenweb/images/greenfan20x20.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thegreenwebfoundation/web-extension/c7d916d307ac8943859fa4c83525bf0f8d31aee1/thegreenweb/images/greenfan20x20.gif -------------------------------------------------------------------------------- /thegreenweb/images/greenfan20x20transp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thegreenwebfoundation/web-extension/c7d916d307ac8943859fa4c83525bf0f8d31aee1/thegreenweb/images/greenfan20x20transp.png -------------------------------------------------------------------------------- /thegreenweb/images/greenhouse20x20.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thegreenwebfoundation/web-extension/c7d916d307ac8943859fa4c83525bf0f8d31aee1/thegreenweb/images/greenhouse20x20.gif -------------------------------------------------------------------------------- /thegreenweb/images/greenhouse20x20transp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thegreenwebfoundation/web-extension/c7d916d307ac8943859fa4c83525bf0f8d31aee1/thegreenweb/images/greenhouse20x20transp.png -------------------------------------------------------------------------------- /thegreenweb/images/greenquestion20x20.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thegreenwebfoundation/web-extension/c7d916d307ac8943859fa4c83525bf0f8d31aee1/thegreenweb/images/greenquestion20x20.gif -------------------------------------------------------------------------------- /thegreenweb/images/grey20x20.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thegreenwebfoundation/web-extension/c7d916d307ac8943859fa4c83525bf0f8d31aee1/thegreenweb/images/grey20x20.gif -------------------------------------------------------------------------------- /thegreenweb/images/grey20x20transp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thegreenwebfoundation/web-extension/c7d916d307ac8943859fa4c83525bf0f8d31aee1/thegreenweb/images/grey20x20transp.png -------------------------------------------------------------------------------- /thegreenweb/images/question20x20transp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thegreenwebfoundation/web-extension/c7d916d307ac8943859fa4c83525bf0f8d31aee1/thegreenweb/images/question20x20transp.png -------------------------------------------------------------------------------- /thegreenweb/images/top-logo-greenweb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thegreenwebfoundation/web-extension/c7d916d307ac8943859fa4c83525bf0f8d31aee1/thegreenweb/images/top-logo-greenweb.png -------------------------------------------------------------------------------- /thegreenweb/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "manifest_version": 2, 3 | "update_url": "http://clients2.google.com/service/update2/crx", 4 | "name": "The Green Web", 5 | "short_name": "The Green Web", 6 | "version": "2.4.0", 7 | "description": "Automatically check the sustainability of a website with The Green Web add-on.", 8 | "background": { 9 | "scripts": [ 10 | "background.dist.js" 11 | ] 12 | }, 13 | 14 | 15 | "options_ui": { 16 | "page": "options.html", 17 | "open_in_tab": true 18 | }, 19 | 20 | 21 | "content_security_policy": "script-src 'self'; object-src 'self';", 22 | "icons": { 23 | "16": "/icons/16.png", 24 | "32": "/icons/32.png", 25 | "48": "/icons/48.png", 26 | "128": "/icons/128.png" 27 | }, 28 | "homepage_url": "https://www.thegreenwebfoundation.org/#the-green-web-app", 29 | "page_action": { 30 | "default_icon": { 31 | "16": "icon.png" 32 | }, 33 | "default_title": "The Green Web" 34 | }, 35 | "web_accessible_resources": [ 36 | "images/green20x20.gif", 37 | "images/greenfan20x20.gif", 38 | "images/greenhouse20x20.gif", 39 | "images/greenquestion20x20.gif", 40 | "images/question20x20transp.png", 41 | "images/grey20x20.gif", 42 | "images/goldsmiley20x20.png", 43 | "images/gold20x20transp.png", 44 | "images/green20x20transp.png", 45 | "images/grey20x20transp.png", 46 | "images/top-logo-greenweb.png" 47 | ], 48 | "permissions": [ 49 | 50 | 51 | "webNavigation", 52 | "tabs", 53 | "storage", 54 | "https://api.thegreenwebfoundation.org", 55 | "https://www.thegreenwebfoundation.org" 56 | ], 57 | "content_scripts": [ 58 | { 59 | "matches": [ 60 | "*://*.google.com/*", 61 | "*://*.google.it/*", 62 | "*://*.google.com.tr/*", 63 | "*://*.google.es/*", 64 | "*://*.google.ch/*", 65 | "*://*.google.nl/*", 66 | "*://*.google.be/*", 67 | "*://*.google.gr/*", 68 | "*://*.google.com.br/*", 69 | "*://*.google.lu/*", 70 | "*://*.google.fi/*", 71 | "*://*.google.pt/*", 72 | "*://*.google.hu/*", 73 | "*://*.google.hr/*", 74 | "*://*.google.bg/*", 75 | "*://*.google.com.mx/*", 76 | "*://*.google.si/*", 77 | "*://*.google.sk/*", 78 | "*://*.google.ro/*", 79 | "*://*.google.ca/*", 80 | "*://*.google.co.uk/*", 81 | "*://*.google.cl/*", 82 | "*://*.google.com.ar/*", 83 | "*://*.google.se/*", 84 | "*://*.google.cz/*", 85 | "*://*.google.dk/*", 86 | "*://*.google.co.th/*", 87 | "*://*.google.com.co/*", 88 | "*://*.google.lt/*", 89 | "*://*.google.co.id/*", 90 | "*://*.google.co.in/*", 91 | "*://*.google.co.il/*", 92 | "*://*.google.com.eg/*", 93 | "*://*.google.cn/*", 94 | "*://*.google.co.ve/*", 95 | "*://*.google.ru/*", 96 | "*://*.google.co.jp/*", 97 | "*://*.google.com.pe/*", 98 | "*://*.google.com.au/*", 99 | "*://*.google.co.ma/*", 100 | "*://*.google.co.za/*", 101 | "*://*.google.com.ph/*", 102 | "*://*.google.com.sa/*", 103 | "*://*.google.ie/*", 104 | "*://*.google.co.kr/*", 105 | "*://*.google.no/*", 106 | "*://*.google.com.ec/*", 107 | "*://*.google.com.vn/*", 108 | "*://*.google.lv/*", 109 | "*://*.google.com.mt/*", 110 | "*://*.google.com.uy/*", 111 | "*://*.google.ae/*", 112 | "*://*.google.ba/*", 113 | "*://*.google.co.nz/*", 114 | "*://*.google.com.ua/*", 115 | "*://*.google.co.cr/*", 116 | "*://*.google.ee/*", 117 | "*://*.google.com.do/*", 118 | "*://*.google.com.tw/*", 119 | "*://*.google.com.hk/*", 120 | "*://*.google.com.my/*", 121 | "*://*.google.com.sv/*", 122 | "*://*.google.com.pr/*", 123 | "*://*.google.lk/*", 124 | "*://*.google.com.gt/*", 125 | "*://*.google.com.bd/*", 126 | "*://*.google.com.pk/*", 127 | "*://*.google.is/*", 128 | "*://*.google.li/*", 129 | "*://*.google.com.bh/*", 130 | "*://*.google.com.ni/*", 131 | "*://*.google.com.py/*", 132 | "*://*.google.com.ng/*", 133 | "*://*.google.com.bo/*", 134 | "*://*.google.co.ke/*", 135 | "*://*.google.hn/*", 136 | "*://*.google.com.sg/*", 137 | "*://*.google.mu/*", 138 | "*://*.google.ci/*", 139 | "*://*.google.jo/*", 140 | "*://*.google.nu/*", 141 | "*://*.google.com.jm/*", 142 | "*://*.google.com.ly/*", 143 | "*://*.google.co.yu/*", 144 | "*://*.google.tt/*", 145 | "*://*.google.com.kh/*", 146 | "*://*.google.ge/*", 147 | "*://*.google.com.na/*", 148 | "*://*.google.com.et/*", 149 | "*://*.google.sm/*", 150 | "*://*.google.cd/*", 151 | "*://*.google.gm/*", 152 | "*://*.google.com.qa/*", 153 | "*://*.google.dj/*", 154 | "*://*.google.com.cu/*", 155 | "*://*.google.com.pa/*", 156 | "*://*.google.gp/*", 157 | "*://*.google.az/*", 158 | "*://*.google.as/*", 159 | "*://*.google.pl/*", 160 | "*://*.google.mn/*", 161 | "*://*.google.ht/*", 162 | "*://*.google.md/*", 163 | "*://*.google.am/*", 164 | "*://*.google.sn/*", 165 | "*://*.google.je/*", 166 | "*://*.googlee.com/*", 167 | "*://*.google.com.bn/*", 168 | "*://*.google.com.ai/*", 169 | "*://*.google.co.zm/*", 170 | "*://*.google.ma/*", 171 | "*://*.google.rw/*", 172 | "*://*.google.co.ug/*", 173 | "*://*.google.com.vc/*", 174 | "*://*.googlenews.de/*", 175 | "*://*.google.at/*", 176 | "*://*.google.com.gi/*", 177 | "*://*.google.to/*", 178 | "*://*.google.com.om/*", 179 | "*://*.google.kz/*", 180 | "*://*.google.co.uz/*" 181 | ], 182 | "js": [ 183 | "search-google.dist.js" 184 | ] 185 | 186 | 187 | }, 188 | { 189 | "matches": [ 190 | "*://search.yahoo.com/*", 191 | "*://*.search.yahoo.com/*" 192 | ], 193 | "js": [ 194 | "search-yahoo.dist.js" 195 | ] 196 | }, 197 | { 198 | "matches": [ 199 | "*://www.bing.com/*" 200 | ], 201 | "js": [ 202 | "search-bing.dist.js" 203 | ] 204 | }, 205 | { 206 | "matches": [ 207 | "*://www.ecosia.org/*" 208 | ], 209 | "js": [ 210 | "search-ecosia.dist.js" 211 | ] 212 | }, 213 | { 214 | "matches": [ 215 | "http://*/*" 216 | ], 217 | "css": [ 218 | "thegreenweb.css", 219 | "qtip/jquery.qtip.min.css" 220 | ], 221 | "js": [ 222 | "green-page-links.dist.js" 223 | ] 224 | } 225 | ] 226 | } 227 | -------------------------------------------------------------------------------- /thegreenweb/options.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | The Green Web Options 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 |
16 | 17 |
18 | The Green Web Foundation Logo 19 |
20 | 21 |
22 | 51 | 52 |
53 | 54 |
55 |
56 |

The Green Web Ethical filter:

57 |
58 | 59 | 69 |
70 | 71 |
72 |

73 | If you enable the green web search, you can also enable filtering, meaning that any grey results will 74 | not be 75 | shown anymore. 76 |

77 |
78 | 79 |
80 | 81 | 108 | 109 |
110 |

111 | 112 | To check that page or link if is hosted green, this extension needs to send its domain name against to The 113 | Green Web Foundation Greencheck API. No other information is sent to other than this domain name, and 114 | these 115 | features can be switched on or off at any time. If you want to know more, please see our privacy statement for more 118 | details.

119 |
120 | 121 |
122 | 126 | 127 | 130 |
131 | 132 | 143 | 144 |
145 | 146 | 147 | 148 | 149 | 150 | 151 | -------------------------------------------------------------------------------- /thegreenweb/options.js: -------------------------------------------------------------------------------- 1 | import browser from "webextension-polyfill" 2 | /* 3 | * Options page 4 | * 5 | * @author Arend-Jan Tetteroo 6 | */ 7 | 8 | 9 | const TGWF_SETTINGS = [ 10 | "annotate-search-results", 11 | "filter-out-grey-search-results", 12 | "check-outbound-links" 13 | ] 14 | 15 | /** 16 | * Saves options using the Browser storage api. 17 | * 18 | * 19 | */ 20 | async function saveOptions(event) { 21 | event.preventDefault(); 22 | const formSubmission = new FormData(event.target) 23 | 24 | // start with everything off, because a form submission only will contain 25 | // 'checked', or 'active' switches. 26 | let payload = { 27 | "annotate-search-results": 0, 28 | "filter-out-grey-search-results": 0, 29 | "check-outbound-links": 0, 30 | } 31 | // this means we only need to check for the existence of the key 32 | // to set it as a truthy value 33 | 34 | for (const key of formSubmission.keys()) { 35 | if (TGWF_SETTINGS.includes(key)) { 36 | payload[key] = 1 37 | } 38 | } 39 | try { 40 | // an empty promise is returned by the storage API, so there's nothing 41 | // to return or inspect when we call set() 42 | await browser.storage.local.set(payload) 43 | const successMessage = "Your settings have been saved. If you have any search pages open you will need to reload them to see the changes." 44 | acknowledgeSave(successMessage) 45 | } catch (error) { 46 | console.error(`Something went wrong syncing: ${error.message}`) 47 | } 48 | } 49 | 50 | /** 51 | * Fetch the settings from storage and update the state of the form to 52 | * include them 53 | */ 54 | async function loadOptions() { 55 | let results 56 | try { 57 | results = await browser.storage.local.get(TGWF_SETTINGS) 58 | 59 | } catch (error) { 60 | console.error(`Something went wrong fetching from the sync: ${error.message}`) 61 | } 62 | return results 63 | } 64 | 65 | 66 | function updateFormSettings(results) { 67 | 68 | 69 | console.log(results) 70 | 71 | if (results) { 72 | 73 | // clear out inputs 74 | const inputs = document.querySelectorAll('form.tgwf-settings input') 75 | for (const input of inputs) { 76 | input.checked = false 77 | } 78 | 79 | for (const [key, value] of Object.entries(results)) { 80 | if (value) { 81 | const input = document.querySelector(`[name='${key}']`) 82 | input.checked = true 83 | } 84 | } 85 | } 86 | } 87 | 88 | /** 89 | * Show a message for when a user makes a submission 90 | * to show if it was successful or not. 91 | */ 92 | function acknowledgeSave(message) { 93 | const announcement = document.querySelector('#announcement') 94 | announcement.querySelector('.message').textContent = message 95 | announcement.classList.remove('hidden') 96 | 97 | // removing the hidden class makes this detectable to 98 | // screen readers, conveying the text to the user 99 | // using accessibility software 100 | // we need the timeout to allow the user 101 | // to register the layout changing before fading in 102 | setTimeout(function () { 103 | announcement.classList.add('opacity-100') 104 | announcement.classList.remove('opacity-0') 105 | }, 100 106 | ) 107 | 108 | } 109 | /** 110 | * dismiss the alert message, fading it out, 111 | * then hiding it from the DOM after a short delay 112 | */ 113 | function dismissAlert() { 114 | announcement.classList.remove('opacity-100') 115 | announcement.classList.add('opacity-0') 116 | // once it's invisible, we want make it hidden, so it 117 | // doesn't show up to screen readers, but we want 118 | // to wait for the fade out first 119 | setTimeout(function () { 120 | announcement.classList.add('hidden') 121 | }, 100 122 | ) 123 | 124 | } 125 | 126 | function catchError(err) { 127 | console.error(`Something went wrong fetching from storage: ${error.message}`) 128 | } 129 | /** 130 | * Resets the form, returning the settings back to 131 | * the saved state. 132 | * @param Event event - the click event from the passed reset button 133 | */ 134 | function resetForm(event) { 135 | event.preventDefault() 136 | event.stopPropagation() 137 | loadOptions().then(updateFormSettings, catchError) 138 | } 139 | 140 | const settingsForm = document.querySelector("form.tgwf-settings") 141 | const resetButton = document.querySelector("button[name='reset']") 142 | const announcement = document.querySelector('#announcement') 143 | const closeButton = announcement.querySelector('button') 144 | 145 | const loadReq = loadOptions() 146 | const results = loadReq.then(updateFormSettings, catchError) 147 | 148 | settingsForm.addEventListener("submit", saveOptions) 149 | resetButton.addEventListener("click", resetForm) 150 | closeButton.addEventListener('click', dismissAlert) 151 | -------------------------------------------------------------------------------- /thegreenweb/popup.html: -------------------------------------------------------------------------------- 1 | 15 |
16 |

This site is

-------------------------------------------------------------------------------- /thegreenweb/qtip/jquery.qtip.min.css: -------------------------------------------------------------------------------- 1 | /*! qtip2 v2.0.0pre | http://craigsworks.com/projects/qtip2/ | Licensed MIT, GPL */.qtip,.qtip{position:absolute;left:-28000px;top:-28000px;display:none;max-width:280px;min-width:50px;font-size:10.5px;line-height:12px;direction:ltr}.qtip-content{position:relative;padding:5px 9px;overflow:hidden;text-align:left;word-wrap:break-word}.qtip-titlebar{position:relative;padding:5px 35px 5px 10px;overflow:hidden;border-width:0 0 1px;font-weight:700}.qtip-titlebar+.qtip-content{border-top-width:0!important}.qtip-close{position:absolute;right:-9px;top:-9px;cursor:pointer;outline:medium none;border-width:1px;border-style:solid;border-color:transparent}.qtip-titlebar .qtip-close{right:4px;top:50%;margin-top:-9px}* html .qtip-titlebar .qtip-close{top:16px}.qtip-titlebar .ui-icon,.qtip-icon .ui-icon{display:block;text-indent:-1000em;direction:ltr;vertical-align:middle}.qtip-icon,.qtip-icon .ui-icon{-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px;text-decoration:none}.qtip-icon .ui-icon{width:18px;height:14px;text-align:center;text-indent:0;font:normal bold 10px/13px Tahoma,sans-serif;color:inherit;background:transparent none no-repeat -100em -100em}.qtip-focus{}.qtip-hover{}.qtip-default{border-width:1px;border-style:solid;border-color:#F1D031;background-color:#FFFFA3;color:#555}.qtip-default .qtip-titlebar{background-color:#FFEF93}.qtip-default .qtip-icon{border-color:#CCC;background:#F1F1F1;color:#777}.qtip-default .qtip-titlebar .qtip-close{border-color:#AAA;color:#111}/*! Light tooltip style */.qtip-light{background-color:#fff;border-color:#E2E2E2;color:#454545}.qtip-light .qtip-titlebar{background-color:#f1f1f1}/*! Dark tooltip style */.qtip-dark{background-color:#505050;border-color:#303030;color:#f3f3f3}.qtip-dark .qtip-titlebar{background-color:#404040}.qtip-dark .qtip-icon{border-color:#444}.qtip-dark .qtip-titlebar .ui-state-hover{border-color:#303030}/*! Cream tooltip style */.qtip-cream{background-color:#FBF7AA;border-color:#F9E98E;color:#A27D35}.qtip-cream .qtip-titlebar{background-color:#F0DE7D}.qtip-cream .qtip-close .qtip-icon{background-position:-82px 0}/*! Red tooltip style */.qtip-red{background-color:#F78B83;border-color:#D95252;color:#912323}.qtip-red .qtip-titlebar{background-color:#F06D65}.qtip-red .qtip-close .qtip-icon{background-position:-102px 0}.qtip-red .qtip-icon{border-color:#D95252}.qtip-red .qtip-titlebar .ui-state-hover{border-color:#D95252}/*! Green tooltip style */.qtip-green{background-color:#CAED9E;border-color:#90D93F;color:#3F6219}.qtip-green .qtip-titlebar{background-color:#B0DE78}.qtip-green .qtip-close .qtip-icon{background-position:-42px 0}/*! Blue tooltip style */.qtip-blue{background-color:#E5F6FE;border-color:#ADD9ED;color:#5E99BD}.qtip-blue .qtip-titlebar{background-color:#D0E9F5}.qtip-blue .qtip-close .qtip-icon{background-position:-2px 0}.qtip-shadow{-webkit-box-shadow:1px 1px 3px 1px rgba(0,0,0,.15);-moz-box-shadow:1px 1px 3px 1px rgba(0,0,0,.15);box-shadow:1px 1px 3px 1px rgba(0,0,0,.15)}.qtip-rounded,.qtip-tipsy,.qtip-bootstrap{-moz-border-radius:5px;-webkit-border-radius:5px;border-radius:5px}.qtip-youtube{-moz-border-radius:2px;-webkit-border-radius:2px;border-radius:2px;-webkit-box-shadow:0 0 3px #333;-moz-box-shadow:0 0 3px #333;box-shadow:0 0 3px #333;color:#fff;border-width:0;background:#4A4A4A;background-image:-webkit-gradient(linear,left top,left bottom,color-stop(0, #4A4A4A),color-stop(100%,black));background-image:-webkit-linear-gradient(top, #4A4A4A 0,black 100%);background-image:-moz-linear-gradient(top, #4A4A4A 0,black 100%);background-image:-ms-linear-gradient(top, #4A4A4A 0,black 100%);background-image:-o-linear-gradient(top, #4A4A4A 0,black 100%)}.qtip-youtube .qtip-titlebar{background-color:#4A4A4A;background-color:rgba(0,0,0,0)}.qtip-youtube .qtip-content{padding:.75em;font:12px arial,sans-serif;filter:progid:DXImageTransform.Microsoft.Gradient(GradientType=0, StartColorStr=#4a4a4a, EndColorStr=#000000);-ms-filter:"progid:DXImageTransform.Microsoft.Gradient(GradientType=0, StartColorStr=#4a4a4a, EndColorStr=#000000);"}.qtip-youtube .qtip-icon{border-color:#222}.qtip-youtube .qtip-titlebar .ui-state-hover{border-color:#303030}.qtip-jtools{background:#232323;background:rgba(0,0,0,.7);background-image:-webkit-gradient(linear,left top,left bottom,from( #717171),to( #232323));background-image:-moz-linear-gradient(top, #717171, #232323);background-image:-webkit-linear-gradient(top, #717171, #232323);background-image:-ms-linear-gradient(top, #717171, #232323);background-image:-o-linear-gradient(top, #717171, #232323);border:2px solid #ddd;border:2px solid rgba(241,241,241,1);-moz-border-radius:2px;-webkit-border-radius:2px;border-radius:2px;-webkit-box-shadow:0 0 12px #333;-moz-box-shadow:0 0 12px #333;box-shadow:0 0 12px #333}.qtip-jtools .qtip-titlebar{background-color:transparent;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr=#717171, endColorstr=#4A4A4A);-ms-filter:"progid:DXImageTransform.Microsoft.gradient(startColorstr=#717171, endColorstr=#4A4A4A)"}.qtip-jtools .qtip-content{filter:progid:DXImageTransform.Microsoft.gradient(startColorstr=#4A4A4A, endColorstr=#232323);-ms-filter:"progid:DXImageTransform.Microsoft.gradient(startColorstr=#4A4A4A, endColorstr=#232323)"}.qtip-jtools .qtip-titlebar,.qtip-jtools .qtip-content{background:transparent;color:#fff;border:0 dashed transparent}.qtip-jtools .qtip-icon{border-color:#555}.qtip-jtools .qtip-titlebar .ui-state-hover{border-color:#333}.qtip-cluetip{-webkit-box-shadow:4px 4px 5px rgba(0,0,0,.4);-moz-box-shadow:4px 4px 5px rgba(0,0,0,.4);box-shadow:4px 4px 5px rgba(0,0,0,.4);background-color:#D9D9C2;color:#111;border:0 dashed transparent}.qtip-cluetip .qtip-titlebar{background-color:#87876A;color:#fff;border:0 dashed transparent}.qtip-cluetip .qtip-icon{border-color:#808064}.qtip-cluetip .qtip-titlebar .ui-state-hover{border-color:#696952;color:#696952}.qtip-tipsy{background:#000;background:rgba(0,0,0,.87);color:#fff;border:0 solid transparent;font-size:11px;font-family:'Lucida Grande',sans-serif;font-weight:700;line-height:16px;text-shadow:0 1px black}.qtip-tipsy .qtip-titlebar{padding:6px 35px 0 10;background-color:transparent}.qtip-tipsy .qtip-content{padding:6px 10}.qtip-tipsy .qtip-icon{border-color:#222;text-shadow:none}.qtip-tipsy .qtip-titlebar .ui-state-hover{border-color:#303030}.qtip-tipped{border:3px solid #959FA9;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px;background-color:#F9F9F9;color:#454545;font-weight:400;font-family:serif}.qtip-tipped .qtip-titlebar{border-bottom-width:0;color:#fff;background:#3A79B8;background-image:-webkit-gradient(linear,left top,left bottom,from( #3A79B8),to( #2E629D));background-image:-webkit-linear-gradient(top, #3A79B8, #2E629D);background-image:-moz-linear-gradient(top, #3A79B8, #2E629D);background-image:-ms-linear-gradient(top, #3A79B8, #2E629D);background-image:-o-linear-gradient(top, #3A79B8, #2E629D);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr=#3A79B8, endColorstr=#2E629D);-ms-filter:"progid:DXImageTransform.Microsoft.gradient(startColorstr=#3A79B8, endColorstr=#2E629D)"}.qtip-tipped .qtip-icon{border:2px solid #285589;background:#285589}.qtip-tipped .qtip-icon .ui-icon{background-color:#FBFBFB;color:#555}.qtip-bootstrap{font-size:14px;line-height:20px;color:#333;padding:1px;background-color:#fff;border:1px solid #ccc;border:1px solid rgba(0,0,0,.2);-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;-webkit-box-shadow:0 5px 10px rgba(0,0,0,.2);-moz-box-shadow:0 5px 10px rgba(0,0,0,.2);box-shadow:0 5px 10px rgba(0,0,0,.2);-webkit-background-clip:padding-box;-moz-background-clip:padding;background-clip:padding-box}.qtip-bootstrap .qtip-titlebar{padding:8px 14px;margin:0;font-size:14px;font-weight:400;line-height:18px;background-color:#f7f7f7;border-bottom:1px solid #ebebeb;-webkit-border-radius:5px 5px 0 0;-moz-border-radius:5px 5px 0 0;border-radius:5px 5px 0 0}.qtip-bootstrap .qtip-titlebar .qtip-close{right:11px;top:45%;border-style:none}.qtip-bootstrap .qtip-content{padding:9px 14px}.qtip-bootstrap .qtip-icon{background:transparent}.qtip-bootstrap .qtip-icon .ui-icon{width:auto;height:auto;float:right;font-size:20px;font-weight:700;line-height:18px;color:#000;text-shadow:0 1px 0 #fff;opacity:.2;filter:alpha(opacity=20)}.qtip-bootstrap .qtip-icon .ui-icon:hover{color:#000;text-decoration:none;cursor:pointer;opacity:.4;filter:alpha(opacity=40)}.qtip:not(.ie9haxors) div.qtip-content,.qtip:not(.ie9haxors) div.qtip-titlebar{filter:none;-ms-filter:none}.qtip .qtip-tip{margin:0 auto;overflow:hidden;z-index:10}.qtip .qtip-tip,.qtip .qtip-tip .qtip-vml{position:absolute;color:#123456;background:transparent;border:0 dashed transparent}.qtip .qtip-tip canvas{top:0;left:0}.qtip .qtip-tip .qtip-vml{behavior:url(#default#VML);display:inline-block;visibility:visible}#qtip-overlay{position:fixed;left:-10000em;top:-10000em}#qtip-overlay.blurs{cursor:pointer}#qtip-overlay div{position:absolute;left:0;top:0;width:100%;height:100%;background-color:#000;opacity:.7;filter:alpha(opacity=70);-ms-filter:"alpha(Opacity=70)"}.qtipmodal-ie6fix{position:absolute!important} -------------------------------------------------------------------------------- /thegreenweb/qtip/jquery.qtip.min.js: -------------------------------------------------------------------------------- 1 | /* qtip2 v3.0.3 | Plugins: tips modal viewport svg imagemap ie6 | Styles: core basic css3 | qtip2.com | Licensed MIT | Wed May 11 2016 22:31:31 */ 2 | 3 | !function(a,b,c){!function(a){"use strict";"function"==typeof define&&define.amd?define(["jquery"],a):jQuery&&!jQuery.fn.qtip&&a(jQuery)}(function(d){"use strict";function e(a,b,c,e){this.id=c,this.target=a,this.tooltip=F,this.elements={target:a},this._id=S+"-"+c,this.timers={img:{}},this.options=b,this.plugins={},this.cache={event:{},target:d(),disabled:E,attr:e,onTooltip:E,lastClass:""},this.rendered=this.destroyed=this.disabled=this.waiting=this.hiddenDuringWait=this.positioning=this.triggering=E}function f(a){return a===F||"object"!==d.type(a)}function g(a){return!(d.isFunction(a)||a&&a.attr||a.length||"object"===d.type(a)&&(a.jquery||a.then))}function h(a){var b,c,e,h;return f(a)?E:(f(a.metadata)&&(a.metadata={type:a.metadata}),"content"in a&&(b=a.content,f(b)||b.jquery||b.done?(c=g(b)?E:b,b=a.content={text:c}):c=b.text,"ajax"in b&&(e=b.ajax,h=e&&e.once!==E,delete b.ajax,b.text=function(a,b){var f=c||d(this).attr(b.options.content.attr)||"Loading...",g=d.ajax(d.extend({},e,{context:b})).then(e.success,F,e.error).then(function(a){return a&&h&&b.set("content.text",a),a},function(a,c,d){b.destroyed||0===a.status||b.set("content.text",c+": "+d)});return h?f:(b.set("content.text",f),g)}),"title"in b&&(d.isPlainObject(b.title)&&(b.button=b.title.button,b.title=b.title.text),g(b.title||E)&&(b.title=E))),"position"in a&&f(a.position)&&(a.position={my:a.position,at:a.position}),"show"in a&&f(a.show)&&(a.show=a.show.jquery?{target:a.show}:a.show===D?{ready:D}:{event:a.show}),"hide"in a&&f(a.hide)&&(a.hide=a.hide.jquery?{target:a.hide}:{event:a.hide}),"style"in a&&f(a.style)&&(a.style={classes:a.style}),d.each(R,function(){this.sanitize&&this.sanitize(a)}),a)}function i(a,b){for(var c,d=0,e=a,f=b.split(".");e=e[f[d++]];)d0?setTimeout(d.proxy(a,this),b):void a.call(this)}function m(a){this.tooltip.hasClass(aa)||(clearTimeout(this.timers.show),clearTimeout(this.timers.hide),this.timers.show=l.call(this,function(){this.toggle(D,a)},this.options.show.delay))}function n(a){if(!this.tooltip.hasClass(aa)&&!this.destroyed){var b=d(a.relatedTarget),c=b.closest(W)[0]===this.tooltip[0],e=b[0]===this.options.show.target[0];if(clearTimeout(this.timers.show),clearTimeout(this.timers.hide),this!==b[0]&&"mouse"===this.options.position.target&&c||this.options.hide.fixed&&/mouse(out|leave|move)/.test(a.type)&&(c||e))try{a.preventDefault(),a.stopImmediatePropagation()}catch(f){}else this.timers.hide=l.call(this,function(){this.toggle(E,a)},this.options.hide.delay,this)}}function o(a){!this.tooltip.hasClass(aa)&&this.options.hide.inactive&&(clearTimeout(this.timers.inactive),this.timers.inactive=l.call(this,function(){this.hide(a)},this.options.hide.inactive))}function p(a){this.rendered&&this.tooltip[0].offsetWidth>0&&this.reposition(a)}function q(a,c,e){d(b.body).delegate(a,(c.split?c:c.join("."+S+" "))+"."+S,function(){var a=y.api[d.attr(this,U)];a&&!a.disabled&&e.apply(a,arguments)})}function r(a,c,f){var g,i,j,k,l,m=d(b.body),n=a[0]===b?m:a,o=a.metadata?a.metadata(f.metadata):F,p="html5"===f.metadata.type&&o?o[f.metadata.name]:F,q=a.data(f.metadata.name||"qtipopts");try{q="string"==typeof q?d.parseJSON(q):q}catch(r){}if(k=d.extend(D,{},y.defaults,f,"object"==typeof q?h(q):F,h(p||o)),i=k.position,k.id=c,"boolean"==typeof k.content.text){if(j=a.attr(k.content.attr),k.content.attr===E||!j)return E;k.content.text=j}if(i.container.length||(i.container=m),i.target===E&&(i.target=n),k.show.target===E&&(k.show.target=n),k.show.solo===D&&(k.show.solo=i.container.closest("body")),k.hide.target===E&&(k.hide.target=n),k.position.viewport===D&&(k.position.viewport=i.container),i.container=i.container.eq(0),i.at=new A(i.at,D),i.my=new A(i.my),a.data(S))if(k.overwrite)a.qtip("destroy",!0);else if(k.overwrite===E)return E;return a.attr(T,c),k.suppress&&(l=a.attr("title"))&&a.removeAttr("title").attr(ca,l).attr("title",""),g=new e(a,k,c,!!j),a.data(S,g),g}function s(a){return a.charAt(0).toUpperCase()+a.slice(1)}function t(a,b){var d,e,f=b.charAt(0).toUpperCase()+b.slice(1),g=(b+" "+va.join(f+" ")+f).split(" "),h=0;if(ua[b])return a.css(ua[b]);for(;d=g[h++];)if((e=a.css(d))!==c)return ua[b]=d,e}function u(a,b){return Math.ceil(parseFloat(t(a,b)))}function v(a,b){this._ns="tip",this.options=b,this.offset=b.offset,this.size=[b.width,b.height],this.qtip=a,this.init(a)}function w(a,b){this.options=b,this._ns="-modal",this.qtip=a,this.init(a)}function x(a){this._ns="ie6",this.qtip=a,this.init(a)}var y,z,A,B,C,D=!0,E=!1,F=null,G="x",H="y",I="width",J="height",K="top",L="left",M="bottom",N="right",O="center",P="flipinvert",Q="shift",R={},S="qtip",T="data-hasqtip",U="data-qtip-id",V=["ui-widget","ui-tooltip"],W="."+S,X="click dblclick mousedown mouseup mousemove mouseleave mouseenter".split(" "),Y=S+"-fixed",Z=S+"-default",$=S+"-focus",_=S+"-hover",aa=S+"-disabled",ba="_replacedByqTip",ca="oldtitle",da={ie:function(){var a,c;for(a=4,c=b.createElement("div");(c.innerHTML="")&&c.getElementsByTagName("i")[0];a+=1);return a>4?a:NaN}(),iOS:parseFloat((""+(/CPU.*OS ([0-9_]{1,5})|(CPU like).*AppleWebKit.*Mobile/i.exec(navigator.userAgent)||[0,""])[1]).replace("undefined","3_2").replace("_",".").replace("_",""))||E};z=e.prototype,z._when=function(a){return d.when.apply(d,a)},z.render=function(a){if(this.rendered||this.destroyed)return this;var b=this,c=this.options,e=this.cache,f=this.elements,g=c.content.text,h=c.content.title,i=c.content.button,j=c.position,k=[];return d.attr(this.target[0],"aria-describedby",this._id),e.posClass=this._createPosClass((this.position={my:j.my,at:j.at}).my),this.tooltip=f.tooltip=d("
",{id:this._id,"class":[S,Z,c.style.classes,e.posClass].join(" "),width:c.style.width||"",height:c.style.height||"",tracking:"mouse"===j.target&&j.adjust.mouse,role:"alert","aria-live":"polite","aria-atomic":E,"aria-describedby":this._id+"-content","aria-hidden":D}).toggleClass(aa,this.disabled).attr(U,this.id).data(S,this).appendTo(j.container).append(f.content=d("
",{"class":S+"-content",id:this._id+"-content","aria-atomic":D})),this.rendered=-1,this.positioning=D,h&&(this._createTitle(),d.isFunction(h)||k.push(this._updateTitle(h,E))),i&&this._createButton(),d.isFunction(g)||k.push(this._updateContent(g,E)),this.rendered=D,this._setWidget(),d.each(R,function(a){var c;"render"===this.initialize&&(c=this(b))&&(b.plugins[a]=c)}),this._unassignEvents(),this._assignEvents(),this._when(k).then(function(){b._trigger("render"),b.positioning=E,b.hiddenDuringWait||!c.show.ready&&!a||b.toggle(D,e.event,E),b.hiddenDuringWait=E}),y.api[this.id]=this,this},z.destroy=function(a){function b(){if(!this.destroyed){this.destroyed=D;var a,b=this.target,c=b.attr(ca);this.rendered&&this.tooltip.stop(1,0).find("*").remove().end().remove(),d.each(this.plugins,function(){this.destroy&&this.destroy()});for(a in this.timers)this.timers.hasOwnProperty(a)&&clearTimeout(this.timers[a]);b.removeData(S).removeAttr(U).removeAttr(T).removeAttr("aria-describedby"),this.options.suppress&&c&&b.attr("title",c).removeAttr(ca),this._unassignEvents(),this.options=this.elements=this.cache=this.timers=this.plugins=this.mouse=F,delete y.api[this.id]}}return this.destroyed?this.target:(a===D&&"hide"!==this.triggering||!this.rendered?b.call(this):(this.tooltip.one("tooltiphidden",d.proxy(b,this)),!this.triggering&&this.hide()),this.target)},B=z.checks={builtin:{"^id$":function(a,b,c,e){var f=c===D?y.nextid:c,g=S+"-"+f;f!==E&&f.length>0&&!d("#"+g).length?(this._id=g,this.rendered&&(this.tooltip[0].id=this._id,this.elements.content[0].id=this._id+"-content",this.elements.title[0].id=this._id+"-title")):a[b]=e},"^prerender":function(a,b,c){c&&!this.rendered&&this.render(this.options.show.ready)},"^content.text$":function(a,b,c){this._updateContent(c)},"^content.attr$":function(a,b,c,d){this.options.content.text===this.target.attr(d)&&this._updateContent(this.target.attr(c))},"^content.title$":function(a,b,c){return c?(c&&!this.elements.title&&this._createTitle(),void this._updateTitle(c)):this._removeTitle()},"^content.button$":function(a,b,c){this._updateButton(c)},"^content.title.(text|button)$":function(a,b,c){this.set("content."+b,c)},"^position.(my|at)$":function(a,b,c){"string"==typeof c&&(this.position[b]=a[b]=new A(c,"at"===b))},"^position.container$":function(a,b,c){this.rendered&&this.tooltip.appendTo(c)},"^show.ready$":function(a,b,c){c&&(!this.rendered&&this.render(D)||this.toggle(D))},"^style.classes$":function(a,b,c,d){this.rendered&&this.tooltip.removeClass(d).addClass(c)},"^style.(width|height)":function(a,b,c){this.rendered&&this.tooltip.css(b,c)},"^style.widget|content.title":function(){this.rendered&&this._setWidget()},"^style.def":function(a,b,c){this.rendered&&this.tooltip.toggleClass(Z,!!c)},"^events.(render|show|move|hide|focus|blur)$":function(a,b,c){this.rendered&&this.tooltip[(d.isFunction(c)?"":"un")+"bind"]("tooltip"+b,c)},"^(show|hide|position).(event|target|fixed|inactive|leave|distance|viewport|adjust)":function(){if(this.rendered){var a=this.options.position;this.tooltip.attr("tracking","mouse"===a.target&&a.adjust.mouse),this._unassignEvents(),this._assignEvents()}}}},z.get=function(a){if(this.destroyed)return this;var b=i(this.options,a.toLowerCase()),c=b[0][b[1]];return c.precedance?c.string():c};var ea=/^position\.(my|at|adjust|target|container|viewport)|style|content|show\.ready/i,fa=/^prerender|show\.ready/i;z.set=function(a,b){if(this.destroyed)return this;var c,e=this.rendered,f=E,g=this.options;return"string"==typeof a?(c=a,a={},a[c]=b):a=d.extend({},a),d.each(a,function(b,c){if(e&&fa.test(b))return void delete a[b];var h,j=i(g,b.toLowerCase());h=j[0][j[1]],j[0][j[1]]=c&&c.nodeType?d(c):c,f=ea.test(b)||f,a[b]=[j[0],j[1],c,h]}),h(g),this.positioning=D,d.each(a,d.proxy(j,this)),this.positioning=E,this.rendered&&this.tooltip[0].offsetWidth>0&&f&&this.reposition("mouse"===g.position.target?F:this.cache.event),this},z._update=function(a,b){var c=this,e=this.cache;return this.rendered&&a?(d.isFunction(a)&&(a=a.call(this.elements.target,e.event,this)||""),d.isFunction(a.then)?(e.waiting=D,a.then(function(a){return e.waiting=E,c._update(a,b)},F,function(a){return c._update(a,b)})):a===E||!a&&""!==a?E:(a.jquery&&a.length>0?b.empty().append(a.css({display:"block",visibility:"visible"})):b.html(a),this._waitForContent(b).then(function(a){c.rendered&&c.tooltip[0].offsetWidth>0&&c.reposition(e.event,!a.length)}))):E},z._waitForContent=function(a){var b=this.cache;return b.waiting=D,(d.fn.imagesLoaded?a.imagesLoaded():(new d.Deferred).resolve([])).done(function(){b.waiting=E}).promise()},z._updateContent=function(a,b){this._update(a,this.elements.content,b)},z._updateTitle=function(a,b){this._update(a,this.elements.title,b)===E&&this._removeTitle(E)},z._createTitle=function(){var a=this.elements,b=this._id+"-title";a.titlebar&&this._removeTitle(),a.titlebar=d("
",{"class":S+"-titlebar "+(this.options.style.widget?k("header"):"")}).append(a.title=d("
",{id:b,"class":S+"-title","aria-atomic":D})).insertBefore(a.content).delegate(".qtip-close","mousedown keydown mouseup keyup mouseout",function(a){d(this).toggleClass("ui-state-active ui-state-focus","down"===a.type.substr(-4))}).delegate(".qtip-close","mouseover mouseout",function(a){d(this).toggleClass("ui-state-hover","mouseover"===a.type)}),this.options.content.button&&this._createButton()},z._removeTitle=function(a){var b=this.elements;b.title&&(b.titlebar.remove(),b.titlebar=b.title=b.button=F,a!==E&&this.reposition())},z._createPosClass=function(a){return S+"-pos-"+(a||this.options.position.my).abbrev()},z.reposition=function(c,e){if(!this.rendered||this.positioning||this.destroyed)return this;this.positioning=D;var f,g,h,i,j=this.cache,k=this.tooltip,l=this.options.position,m=l.target,n=l.my,o=l.at,p=l.viewport,q=l.container,r=l.adjust,s=r.method.split(" "),t=k.outerWidth(E),u=k.outerHeight(E),v=0,w=0,x=k.css("position"),y={left:0,top:0},z=k[0].offsetWidth>0,A=c&&"scroll"===c.type,B=d(a),C=q[0].ownerDocument,F=this.mouse;if(d.isArray(m)&&2===m.length)o={x:L,y:K},y={left:m[0],top:m[1]};else if("mouse"===m)o={x:L,y:K},(!r.mouse||this.options.hide.distance)&&j.origin&&j.origin.pageX?c=j.origin:!c||c&&("resize"===c.type||"scroll"===c.type)?c=j.event:F&&F.pageX&&(c=F),"static"!==x&&(y=q.offset()),C.body.offsetWidth!==(a.innerWidth||C.documentElement.clientWidth)&&(g=d(b.body).offset()),y={left:c.pageX-y.left+(g&&g.left||0),top:c.pageY-y.top+(g&&g.top||0)},r.mouse&&A&&F&&(y.left-=(F.scrollX||0)-B.scrollLeft(),y.top-=(F.scrollY||0)-B.scrollTop());else{if("event"===m?c&&c.target&&"scroll"!==c.type&&"resize"!==c.type?j.target=d(c.target):c.target||(j.target=this.elements.target):"event"!==m&&(j.target=d(m.jquery?m:this.elements.target)),m=j.target,m=d(m).eq(0),0===m.length)return this;m[0]===b||m[0]===a?(v=da.iOS?a.innerWidth:m.width(),w=da.iOS?a.innerHeight:m.height(),m[0]===a&&(y={top:(p||m).scrollTop(),left:(p||m).scrollLeft()})):R.imagemap&&m.is("area")?f=R.imagemap(this,m,o,R.viewport?s:E):R.svg&&m&&m[0].ownerSVGElement?f=R.svg(this,m,o,R.viewport?s:E):(v=m.outerWidth(E),w=m.outerHeight(E),y=m.offset()),f&&(v=f.width,w=f.height,g=f.offset,y=f.position),y=this.reposition.offset(m,y,q),(da.iOS>3.1&&da.iOS<4.1||da.iOS>=4.3&&da.iOS<4.33||!da.iOS&&"fixed"===x)&&(y.left-=B.scrollLeft(),y.top-=B.scrollTop()),(!f||f&&f.adjustable!==E)&&(y.left+=o.x===N?v:o.x===O?v/2:0,y.top+=o.y===M?w:o.y===O?w/2:0)}return y.left+=r.x+(n.x===N?-t:n.x===O?-t/2:0),y.top+=r.y+(n.y===M?-u:n.y===O?-u/2:0),R.viewport?(h=y.adjusted=R.viewport(this,y,l,v,w,t,u),g&&h.left&&(y.left+=g.left),g&&h.top&&(y.top+=g.top),h.my&&(this.position.my=h.my)):y.adjusted={left:0,top:0},j.posClass!==(i=this._createPosClass(this.position.my))&&(j.posClass=i,k.removeClass(j.posClass).addClass(i)),this._trigger("move",[y,p.elem||p],c)?(delete y.adjusted,e===E||!z||isNaN(y.left)||isNaN(y.top)||"mouse"===m||!d.isFunction(l.effect)?k.css(y):d.isFunction(l.effect)&&(l.effect.call(k,this,d.extend({},y)),k.queue(function(a){d(this).css({opacity:"",height:""}),da.ie&&this.style.removeAttribute("filter"),a()})),this.positioning=E,this):this},z.reposition.offset=function(a,c,e){function f(a,b){c.left+=b*a.scrollLeft(),c.top+=b*a.scrollTop()}if(!e[0])return c;var g,h,i,j,k=d(a[0].ownerDocument),l=!!da.ie&&"CSS1Compat"!==b.compatMode,m=e[0];do"static"!==(h=d.css(m,"position"))&&("fixed"===h?(i=m.getBoundingClientRect(),f(k,-1)):(i=d(m).position(),i.left+=parseFloat(d.css(m,"borderLeftWidth"))||0,i.top+=parseFloat(d.css(m,"borderTopWidth"))||0),c.left-=i.left+(parseFloat(d.css(m,"marginLeft"))||0),c.top-=i.top+(parseFloat(d.css(m,"marginTop"))||0),g||"hidden"===(j=d.css(m,"overflow"))||"visible"===j||(g=d(m)));while(m=m.offsetParent);return g&&(g[0]!==k[0]||l)&&f(g,1),c};var ga=(A=z.reposition.Corner=function(a,b){a=(""+a).replace(/([A-Z])/," $1").replace(/middle/gi,O).toLowerCase(),this.x=(a.match(/left|right/i)||a.match(/center/)||["inherit"])[0].toLowerCase(),this.y=(a.match(/top|bottom|center/i)||["inherit"])[0].toLowerCase(),this.forceY=!!b;var c=a.charAt(0);this.precedance="t"===c||"b"===c?H:G}).prototype;ga.invert=function(a,b){this[a]=this[a]===L?N:this[a]===N?L:b||this[a]},ga.string=function(a){var b=this.x,c=this.y,d=b!==c?"center"===b||"center"!==c&&(this.precedance===H||this.forceY)?[c,b]:[b,c]:[b];return a!==!1?d.join(" "):d},ga.abbrev=function(){var a=this.string(!1);return a[0].charAt(0)+(a[1]&&a[1].charAt(0)||"")},ga.clone=function(){return new A(this.string(),this.forceY)},z.toggle=function(a,c){var e=this.cache,f=this.options,g=this.tooltip;if(c){if(/over|enter/.test(c.type)&&e.event&&/out|leave/.test(e.event.type)&&f.show.target.add(c.target).length===f.show.target.length&&g.has(c.relatedTarget).length)return this;e.event=d.event.fix(c)}if(this.waiting&&!a&&(this.hiddenDuringWait=D),!this.rendered)return a?this.render(1):this;if(this.destroyed||this.disabled)return this;var h,i,j,k=a?"show":"hide",l=this.options[k],m=this.options.position,n=this.options.content,o=this.tooltip.css("width"),p=this.tooltip.is(":visible"),q=a||1===l.target.length,r=!c||l.target.length<2||e.target[0]===c.target;return(typeof a).search("boolean|number")&&(a=!p),h=!g.is(":animated")&&p===a&&r,i=h?F:!!this._trigger(k,[90]),this.destroyed?this:(i!==E&&a&&this.focus(c),!i||h?this:(d.attr(g[0],"aria-hidden",!a),a?(this.mouse&&(e.origin=d.event.fix(this.mouse)),d.isFunction(n.text)&&this._updateContent(n.text,E),d.isFunction(n.title)&&this._updateTitle(n.title,E),!C&&"mouse"===m.target&&m.adjust.mouse&&(d(b).bind("mousemove."+S,this._storeMouse),C=D),o||g.css("width",g.outerWidth(E)),this.reposition(c,arguments[2]),o||g.css("width",""),l.solo&&("string"==typeof l.solo?d(l.solo):d(W,l.solo)).not(g).not(l.target).qtip("hide",new d.Event("tooltipsolo"))):(clearTimeout(this.timers.show),delete e.origin,C&&!d(W+'[tracking="true"]:visible',l.solo).not(g).length&&(d(b).unbind("mousemove."+S),C=E),this.blur(c)),j=d.proxy(function(){a?(da.ie&&g[0].style.removeAttribute("filter"),g.css("overflow",""),"string"==typeof l.autofocus&&d(this.options.show.autofocus,g).focus(),this.options.show.target.trigger("qtip-"+this.id+"-inactive")):g.css({display:"",visibility:"",opacity:"",left:"",top:""}),this._trigger(a?"visible":"hidden")},this),l.effect===E||q===E?(g[k](),j()):d.isFunction(l.effect)?(g.stop(1,1),l.effect.call(g,this),g.queue("fx",function(a){j(),a()})):g.fadeTo(90,a?1:0,j),a&&l.target.trigger("qtip-"+this.id+"-inactive"),this))},z.show=function(a){return this.toggle(D,a)},z.hide=function(a){return this.toggle(E,a)},z.focus=function(a){if(!this.rendered||this.destroyed)return this;var b=d(W),c=this.tooltip,e=parseInt(c[0].style.zIndex,10),f=y.zindex+b.length;return c.hasClass($)||this._trigger("focus",[f],a)&&(e!==f&&(b.each(function(){this.style.zIndex>e&&(this.style.zIndex=this.style.zIndex-1)}),b.filter("."+$).qtip("blur",a)),c.addClass($)[0].style.zIndex=f),this},z.blur=function(a){return!this.rendered||this.destroyed?this:(this.tooltip.removeClass($),this._trigger("blur",[this.tooltip.css("zIndex")],a),this)},z.disable=function(a){return this.destroyed?this:("toggle"===a?a=!(this.rendered?this.tooltip.hasClass(aa):this.disabled):"boolean"!=typeof a&&(a=D),this.rendered&&this.tooltip.toggleClass(aa,a).attr("aria-disabled",a),this.disabled=!!a,this)},z.enable=function(){return this.disable(E)},z._createButton=function(){var a=this,b=this.elements,c=b.tooltip,e=this.options.content.button,f="string"==typeof e,g=f?e:"Close tooltip";b.button&&b.button.remove(),e.jquery?b.button=e:b.button=d("",{"class":"qtip-close "+(this.options.style.widget?"":S+"-icon"),title:g,"aria-label":g}).prepend(d("",{"class":"ui-icon ui-icon-close",html:"×"})),b.button.appendTo(b.titlebar||c).attr("role","button").click(function(b){return c.hasClass(aa)||a.hide(b),E})},z._updateButton=function(a){if(!this.rendered)return E;var b=this.elements.button;a?this._createButton():b.remove()},z._setWidget=function(){var a=this.options.style.widget,b=this.elements,c=b.tooltip,d=c.hasClass(aa);c.removeClass(aa),aa=a?"ui-state-disabled":"qtip-disabled",c.toggleClass(aa,d),c.toggleClass("ui-helper-reset "+k(),a).toggleClass(Z,this.options.style.def&&!a),b.content&&b.content.toggleClass(k("content"),a),b.titlebar&&b.titlebar.toggleClass(k("header"),a),b.button&&b.button.toggleClass(S+"-icon",!a)},z._storeMouse=function(a){return(this.mouse=d.event.fix(a)).type="mousemove",this},z._bind=function(a,b,c,e,f){if(a&&c&&b.length){var g="."+this._id+(e?"-"+e:"");return d(a).bind((b.split?b:b.join(g+" "))+g,d.proxy(c,f||this)),this}},z._unbind=function(a,b){return a&&d(a).unbind("."+this._id+(b?"-"+b:"")),this},z._trigger=function(a,b,c){var e=new d.Event("tooltip"+a);return e.originalEvent=c&&d.extend({},c)||this.cache.event||F,this.triggering=a,this.tooltip.trigger(e,[this].concat(b||[])),this.triggering=E,!e.isDefaultPrevented()},z._bindEvents=function(a,b,c,e,f,g){var h=c.filter(e).add(e.filter(c)),i=[];h.length&&(d.each(b,function(b,c){var e=d.inArray(c,a);e>-1&&i.push(a.splice(e,1)[0])}),i.length&&(this._bind(h,i,function(a){var b=this.rendered?this.tooltip[0].offsetWidth>0:!1;(b?g:f).call(this,a)}),c=c.not(h),e=e.not(h))),this._bind(c,a,f),this._bind(e,b,g)},z._assignInitialEvents=function(a){function b(a){return this.disabled||this.destroyed?E:(this.cache.event=a&&d.event.fix(a),this.cache.target=a&&d(a.target),clearTimeout(this.timers.show),void(this.timers.show=l.call(this,function(){this.render("object"==typeof a||c.show.ready)},c.prerender?0:c.show.delay)))}var c=this.options,e=c.show.target,f=c.hide.target,g=c.show.event?d.trim(""+c.show.event).split(" "):[],h=c.hide.event?d.trim(""+c.hide.event).split(" "):[];this._bind(this.elements.target,["remove","removeqtip"],function(){this.destroy(!0)},"destroy"),/mouse(over|enter)/i.test(c.show.event)&&!/mouse(out|leave)/i.test(c.hide.event)&&h.push("mouseleave"),this._bind(e,"mousemove",function(a){this._storeMouse(a),this.cache.onTarget=D}),this._bindEvents(g,h,e,f,b,function(){return this.timers?void clearTimeout(this.timers.show):E}),(c.show.ready||c.prerender)&&b.call(this,a)},z._assignEvents=function(){var c=this,e=this.options,f=e.position,g=this.tooltip,h=e.show.target,i=e.hide.target,j=f.container,k=f.viewport,l=d(b),q=d(a),r=e.show.event?d.trim(""+e.show.event).split(" "):[],s=e.hide.event?d.trim(""+e.hide.event).split(" "):[];d.each(e.events,function(a,b){c._bind(g,"toggle"===a?["tooltipshow","tooltiphide"]:["tooltip"+a],b,null,g)}),/mouse(out|leave)/i.test(e.hide.event)&&"window"===e.hide.leave&&this._bind(l,["mouseout","blur"],function(a){/select|option/.test(a.target.nodeName)||a.relatedTarget||this.hide(a)}),e.hide.fixed?i=i.add(g.addClass(Y)):/mouse(over|enter)/i.test(e.show.event)&&this._bind(i,"mouseleave",function(){clearTimeout(this.timers.show)}),(""+e.hide.event).indexOf("unfocus")>-1&&this._bind(j.closest("html"),["mousedown","touchstart"],function(a){var b=d(a.target),c=this.rendered&&!this.tooltip.hasClass(aa)&&this.tooltip[0].offsetWidth>0,e=b.parents(W).filter(this.tooltip[0]).length>0;b[0]===this.target[0]||b[0]===this.tooltip[0]||e||this.target.has(b[0]).length||!c||this.hide(a)}),"number"==typeof e.hide.inactive&&(this._bind(h,"qtip-"+this.id+"-inactive",o,"inactive"),this._bind(i.add(g),y.inactiveEvents,o)),this._bindEvents(r,s,h,i,m,n),this._bind(h.add(g),"mousemove",function(a){if("number"==typeof e.hide.distance){var b=this.cache.origin||{},c=this.options.hide.distance,d=Math.abs;(d(a.pageX-b.pageX)>=c||d(a.pageY-b.pageY)>=c)&&this.hide(a)}this._storeMouse(a)}),"mouse"===f.target&&f.adjust.mouse&&(e.hide.event&&this._bind(h,["mouseenter","mouseleave"],function(a){return this.cache?void(this.cache.onTarget="mouseenter"===a.type):E}),this._bind(l,"mousemove",function(a){this.rendered&&this.cache.onTarget&&!this.tooltip.hasClass(aa)&&this.tooltip[0].offsetWidth>0&&this.reposition(a)})),(f.adjust.resize||k.length)&&this._bind(d.event.special.resize?k:q,"resize",p),f.adjust.scroll&&this._bind(q.add(f.container),"scroll",p)},z._unassignEvents=function(){var c=this.options,e=c.show.target,f=c.hide.target,g=d.grep([this.elements.target[0],this.rendered&&this.tooltip[0],c.position.container[0],c.position.viewport[0],c.position.container.closest("html")[0],a,b],function(a){return"object"==typeof a});e&&e.toArray&&(g=g.concat(e.toArray())),f&&f.toArray&&(g=g.concat(f.toArray())),this._unbind(g)._unbind(g,"destroy")._unbind(g,"inactive")},d(function(){q(W,["mouseenter","mouseleave"],function(a){var b="mouseenter"===a.type,c=d(a.currentTarget),e=d(a.relatedTarget||a.target),f=this.options;b?(this.focus(a),c.hasClass(Y)&&!c.hasClass(aa)&&clearTimeout(this.timers.hide)):"mouse"===f.position.target&&f.position.adjust.mouse&&f.hide.event&&f.show.target&&!e.closest(f.show.target[0]).length&&this.hide(a),c.toggleClass(_,b)}),q("["+U+"]",X,o)}),y=d.fn.qtip=function(a,b,e){var f=(""+a).toLowerCase(),g=F,i=d.makeArray(arguments).slice(1),j=i[i.length-1],k=this[0]?d.data(this[0],S):F;return!arguments.length&&k||"api"===f?k:"string"==typeof a?(this.each(function(){var a=d.data(this,S);if(!a)return D;if(j&&j.timeStamp&&(a.cache.event=j),!b||"option"!==f&&"options"!==f)a[f]&&a[f].apply(a,i);else{if(e===c&&!d.isPlainObject(b))return g=a.get(b),E;a.set(b,e)}}),g!==F?g:this):"object"!=typeof a&&arguments.length?void 0:(k=h(d.extend(D,{},a)),this.each(function(a){var b,c;return c=d.isArray(k.id)?k.id[a]:k.id,c=!c||c===E||c.length<1||y.api[c]?y.nextid++:c,b=r(d(this),c,k),b===E?D:(y.api[c]=b,d.each(R,function(){"initialize"===this.initialize&&this(b)}),void b._assignInitialEvents(j))}))},d.qtip=e,y.api={},d.each({attr:function(a,b){if(this.length){var c=this[0],e="title",f=d.data(c,"qtip");if(a===e&&f&&f.options&&"object"==typeof f&&"object"==typeof f.options&&f.options.suppress)return arguments.length<2?d.attr(c,ca):(f&&f.options.content.attr===e&&f.cache.attr&&f.set("content.text",b),this.attr(ca,b))}return d.fn["attr"+ba].apply(this,arguments)},clone:function(a){var b=d.fn["clone"+ba].apply(this,arguments);return a||b.filter("["+ca+"]").attr("title",function(){return d.attr(this,ca)}).removeAttr(ca),b}},function(a,b){if(!b||d.fn[a+ba])return D;var c=d.fn[a+ba]=d.fn[a];d.fn[a]=function(){return b.apply(this,arguments)||c.apply(this,arguments)}}),d.ui||(d["cleanData"+ba]=d.cleanData,d.cleanData=function(a){for(var b,c=0;(b=d(a[c])).length;c++)if(b.attr(T))try{b.triggerHandler("removeqtip")}catch(e){}d["cleanData"+ba].apply(this,arguments)}),y.version="3.0.3",y.nextid=0,y.inactiveEvents=X,y.zindex=15e3,y.defaults={prerender:E,id:E,overwrite:D,suppress:D,content:{text:D,attr:"title",title:E,button:E},position:{my:"top left",at:"bottom right",target:E,container:E,viewport:E,adjust:{x:0,y:0,mouse:D,scroll:D,resize:D,method:"flipinvert flipinvert"},effect:function(a,b){d(this).animate(b,{duration:200,queue:E})}},show:{target:E,event:"mouseenter",effect:D,delay:90,solo:E,ready:E,autofocus:E},hide:{target:E,event:"mouseleave",effect:D,delay:0,fixed:E,inactive:E,leave:"window",distance:E},style:{classes:"",widget:E,width:E,height:E,def:D},events:{render:F,move:F,show:F,hide:F,toggle:F,visible:F,hidden:F,focus:F,blur:F}};var ha,ia,ja,ka,la,ma="margin",na="border",oa="color",pa="background-color",qa="transparent",ra=" !important",sa=!!b.createElement("canvas").getContext,ta=/rgba?\(0, 0, 0(, 0)?\)|transparent|#123456/i,ua={},va=["Webkit","O","Moz","ms"];sa?(ka=a.devicePixelRatio||1,la=function(){var a=b.createElement("canvas").getContext("2d");return a.backingStorePixelRatio||a.webkitBackingStorePixelRatio||a.mozBackingStorePixelRatio||a.msBackingStorePixelRatio||a.oBackingStorePixelRatio||1}(),ja=ka/la):ia=function(a,b,c){return"'},d.extend(v.prototype,{init:function(a){var b,c;c=this.element=a.elements.tip=d("
",{"class":S+"-tip"}).prependTo(a.tooltip),sa?(b=d("").appendTo(this.element)[0].getContext("2d"),b.lineJoin="miter",b.miterLimit=1e5,b.save()):(b=ia("shape",'coordorigin="0,0"',"position:absolute;"),this.element.html(b+b),a._bind(d("*",c).add(c),["click","mousedown"],function(a){a.stopPropagation()},this._ns)),a._bind(a.tooltip,"tooltipmove",this.reposition,this._ns,this),this.create()},_swapDimensions:function(){this.size[0]=this.options.height,this.size[1]=this.options.width},_resetDimensions:function(){this.size[0]=this.options.width,this.size[1]=this.options.height},_useTitle:function(a){var b=this.qtip.elements.titlebar;return b&&(a.y===K||a.y===O&&this.element.position().top+this.size[1]/2+this.options.offsetl&&!ta.test(e[1])&&(e[0]=e[1]),this.border=l=p.border!==D?p.border:l):this.border=l=0,k=this.size=this._calculateSize(b),n.css({width:k[0],height:k[1],lineHeight:k[1]+"px"}),j=b.precedance===H?[s(r.x===L?l:r.x===N?k[0]-q[0]-l:(k[0]-q[0])/2),s(r.y===K?k[1]-q[1]:0)]:[s(r.x===L?k[0]-q[0]:0),s(r.y===K?l:r.y===M?k[1]-q[1]-l:(k[1]-q[1])/2)],sa?(g=o[0].getContext("2d"),g.restore(),g.save(),g.clearRect(0,0,6e3,6e3),h=this._calculateTip(r,q,ja),i=this._calculateTip(r,this.size,ja),o.attr(I,k[0]*ja).attr(J,k[1]*ja),o.css(I,k[0]).css(J,k[1]),this._drawCoords(g,i),g.fillStyle=e[1],g.fill(),g.translate(j[0]*ja,j[1]*ja),this._drawCoords(g,h),g.fillStyle=e[0],g.fill()):(h=this._calculateTip(r),h="m"+h[0]+","+h[1]+" l"+h[2]+","+h[3]+" "+h[4]+","+h[5]+" xe",j[2]=l&&/^(r|b)/i.test(b.string())?8===da.ie?2:1:0,o.css({coordsize:k[0]+l+" "+k[1]+l,antialias:""+(r.string().indexOf(O)>-1),left:j[0]-j[2]*Number(f===G),top:j[1]-j[2]*Number(f===H),width:k[0]+l,height:k[1]+l}).each(function(a){var b=d(this);b[b.prop?"prop":"attr"]({coordsize:k[0]+l+" "+k[1]+l,path:h,fillcolor:e[0],filled:!!a,stroked:!a}).toggle(!(!l&&!a)),!a&&b.html(ia("stroke",'weight="'+2*l+'px" color="'+e[1]+'" miterlimit="1000" joinstyle="miter"'))})),a.opera&&setTimeout(function(){m.tip.css({display:"inline-block",visibility:"visible"})},1),c!==E&&this.calculate(b,k)},calculate:function(a,b){if(!this.enabled)return E;var c,e,f=this,g=this.qtip.elements,h=this.element,i=this.options.offset,j={}; 4 | return a=a||this.corner,c=a.precedance,b=b||this._calculateSize(a),e=[a.x,a.y],c===G&&e.reverse(),d.each(e,function(d,e){var h,k,l;e===O?(h=c===H?L:K,j[h]="50%",j[ma+"-"+h]=-Math.round(b[c===H?0:1]/2)+i):(h=f._parseWidth(a,e,g.tooltip),k=f._parseWidth(a,e,g.content),l=f._parseRadius(a),j[e]=Math.max(-f.border,d?k:i+(l>h?l:-h)))}),j[a[c]]-=b[c===G?0:1],h.css({margin:"",top:"",bottom:"",left:"",right:""}).css(j),j},reposition:function(a,b,d){function e(a,b,c,d,e){a===Q&&j.precedance===b&&k[d]&&j[c]!==O?j.precedance=j.precedance===G?H:G:a!==Q&&k[d]&&(j[b]=j[b]===O?k[d]>0?d:e:j[b]===d?e:d)}function f(a,b,e){j[a]===O?p[ma+"-"+b]=o[a]=g[ma+"-"+b]-k[b]:(h=g[e]!==c?[k[b],-g[b]]:[-k[b],g[b]],(o[a]=Math.max(h[0],h[1]))>h[0]&&(d[b]-=k[b],o[b]=E),p[g[e]!==c?e:b]=o[a])}if(this.enabled){var g,h,i=b.cache,j=this.corner.clone(),k=d.adjusted,l=b.options.position.adjust.method.split(" "),m=l[0],n=l[1]||l[0],o={left:E,top:E,x:0,y:0},p={};this.corner.fixed!==D&&(e(m,G,H,L,N),e(n,H,G,K,M),j.string()===i.corner.string()&&i.cornerTop===k.top&&i.cornerLeft===k.left||this.update(j,E)),g=this.calculate(j),g.right!==c&&(g.left=-g.right),g.bottom!==c&&(g.top=-g.bottom),g.user=this.offset,o.left=m===Q&&!!k.left,o.left&&f(G,L,N),o.top=n===Q&&!!k.top,o.top&&f(H,K,M),this.element.css(p).toggle(!(o.x&&o.y||j.x===O&&o.y||j.y===O&&o.x)),d.left-=g.left.charAt?g.user:m!==Q||o.top||!o.left&&!o.top?g.left+this.border:0,d.top-=g.top.charAt?g.user:n!==Q||o.left||!o.left&&!o.top?g.top+this.border:0,i.cornerLeft=k.left,i.cornerTop=k.top,i.corner=j.clone()}},destroy:function(){this.qtip._unbind(this.qtip.tooltip,this._ns),this.qtip.elements.tip&&this.qtip.elements.tip.find("*").remove().end().remove()}}),ha=R.tip=function(a){return new v(a,a.options.style.tip)},ha.initialize="render",ha.sanitize=function(a){if(a.style&&"tip"in a.style){var b=a.style.tip;"object"!=typeof b&&(b=a.style.tip={corner:b}),/string|boolean/i.test(typeof b.corner)||(b.corner=D)}},B.tip={"^position.my|style.tip.(corner|mimic|border)$":function(){this.create(),this.qtip.reposition()},"^style.tip.(height|width)$":function(a){this.size=[a.width,a.height],this.update(),this.qtip.reposition()},"^content.title|style.(classes|widget)$":function(){this.update()}},d.extend(D,y.defaults,{style:{tip:{corner:D,mimic:E,width:6,height:6,border:D,offset:0}}});var wa,xa,ya="qtip-modal",za="."+ya;xa=function(){function a(a){if(d.expr[":"].focusable)return d.expr[":"].focusable;var b,c,e,f=!isNaN(d.attr(a,"tabindex")),g=a.nodeName&&a.nodeName.toLowerCase();return"area"===g?(b=a.parentNode,c=b.name,a.href&&c&&"map"===b.nodeName.toLowerCase()?(e=d("img[usemap=#"+c+"]")[0],!!e&&e.is(":visible")):!1):/input|select|textarea|button|object/.test(g)?!a.disabled:"a"===g?a.href||f:f}function c(a){j.length<1&&a.length?a.not("body").blur():j.first().focus()}function e(a){if(h.is(":visible")){var b,e=d(a.target),g=f.tooltip,i=e.closest(W);b=i.length<1?E:parseInt(i[0].style.zIndex,10)>parseInt(g[0].style.zIndex,10),b||e.closest(W)[0]===g[0]||c(e)}}var f,g,h,i=this,j={};d.extend(i,{init:function(){return h=i.elem=d("
",{id:"qtip-overlay",html:"
",mousedown:function(){return E}}).hide(),d(b.body).bind("focusin"+za,e),d(b).bind("keydown"+za,function(a){f&&f.options.show.modal.escape&&27===a.keyCode&&f.hide(a)}),h.bind("click"+za,function(a){f&&f.options.show.modal.blur&&f.hide(a)}),i},update:function(b){f=b,j=b.options.show.modal.stealfocus!==E?b.tooltip.find("*").filter(function(){return a(this)}):[]},toggle:function(a,e,j){var k=a.tooltip,l=a.options.show.modal,m=l.effect,n=e?"show":"hide",o=h.is(":visible"),p=d(za).filter(":visible:not(:animated)").not(k);return i.update(a),e&&l.stealfocus!==E&&c(d(":focus")),h.toggleClass("blurs",l.blur),e&&h.appendTo(b.body),h.is(":animated")&&o===e&&g!==E||!e&&p.length?i:(h.stop(D,E),d.isFunction(m)?m.call(h,e):m===E?h[n]():h.fadeTo(parseInt(j,10)||90,e?1:0,function(){e||h.hide()}),e||h.queue(function(a){h.css({left:"",top:""}),d(za).length||h.detach(),a()}),g=e,f.destroyed&&(f=F),i)}}),i.init()},xa=new xa,d.extend(w.prototype,{init:function(a){var b=a.tooltip;return this.options.on?(a.elements.overlay=xa.elem,b.addClass(ya).css("z-index",y.modal_zindex+d(za).length),a._bind(b,["tooltipshow","tooltiphide"],function(a,c,e){var f=a.originalEvent;if(a.target===b[0])if(f&&"tooltiphide"===a.type&&/mouse(leave|enter)/.test(f.type)&&d(f.relatedTarget).closest(xa.elem[0]).length)try{a.preventDefault()}catch(g){}else(!f||f&&"tooltipsolo"!==f.type)&&this.toggle(a,"tooltipshow"===a.type,e)},this._ns,this),a._bind(b,"tooltipfocus",function(a,c){if(!a.isDefaultPrevented()&&a.target===b[0]){var e=d(za),f=y.modal_zindex+e.length,g=parseInt(b[0].style.zIndex,10);xa.elem[0].style.zIndex=f-1,e.each(function(){this.style.zIndex>g&&(this.style.zIndex-=1)}),e.filter("."+$).qtip("blur",a.originalEvent),b.addClass($)[0].style.zIndex=f,xa.update(c);try{a.preventDefault()}catch(h){}}},this._ns,this),void a._bind(b,"tooltiphide",function(a){a.target===b[0]&&d(za).filter(":visible").not(b).last().qtip("focus",a)},this._ns,this)):this},toggle:function(a,b,c){return a&&a.isDefaultPrevented()?this:void xa.toggle(this.qtip,!!b,c)},destroy:function(){this.qtip.tooltip.removeClass(ya),this.qtip._unbind(this.qtip.tooltip,this._ns),xa.toggle(this.qtip,E),delete this.qtip.elements.overlay}}),wa=R.modal=function(a){return new w(a,a.options.show.modal)},wa.sanitize=function(a){a.show&&("object"!=typeof a.show.modal?a.show.modal={on:!!a.show.modal}:"undefined"==typeof a.show.modal.on&&(a.show.modal.on=D))},y.modal_zindex=y.zindex-200,wa.initialize="render",B.modal={"^show.modal.(on|blur)$":function(){this.destroy(),this.init(),this.qtip.elems.overlay.toggle(this.qtip.tooltip[0].offsetWidth>0)}},d.extend(D,y.defaults,{show:{modal:{on:E,effect:D,blur:D,stealfocus:D,escape:D}}}),R.viewport=function(c,d,e,f,g,h,i){function j(a,b,c,e,f,g,h,i,j){var k=d[f],s=u[a],t=v[a],w=c===Q,x=s===f?j:s===g?-j:-j/2,y=t===f?i:t===g?-i:-i/2,z=q[f]+r[f]-(n?0:m[f]),A=z-k,B=k+j-(h===I?o:p)-z,C=x-(u.precedance===a||s===u[b]?y:0)-(t===O?i/2:0);return w?(C=(s===f?1:-1)*x,d[f]+=A>0?A:B>0?-B:0,d[f]=Math.max(-m[f]+r[f],k-C,Math.min(Math.max(-m[f]+r[f]+(h===I?o:p),k+C),d[f],"center"===s?k-x:1e9))):(e*=c===P?2:0,A>0&&(s!==f||B>0)?(d[f]-=C+e,l.invert(a,f)):B>0&&(s!==g||A>0)&&(d[f]-=(s===O?-C:C)+e,l.invert(a,g)),d[f]B&&(d[f]=k,l=u.clone())),d[f]-k}var k,l,m,n,o,p,q,r,s=e.target,t=c.elements.tooltip,u=e.my,v=e.at,w=e.adjust,x=w.method.split(" "),y=x[0],z=x[1]||x[0],A=e.viewport,B=e.container,C={left:0,top:0};return A.jquery&&s[0]!==a&&s[0]!==b.body&&"none"!==w.method?(m=B.offset()||C,n="static"===B.css("position"),k="fixed"===t.css("position"),o=A[0]===a?A.width():A.outerWidth(E),p=A[0]===a?A.height():A.outerHeight(E),q={left:k?0:A.scrollLeft(),top:k?0:A.scrollTop()},r=A.offset()||C,"shift"===y&&"shift"===z||(l=u.clone()),C={left:"none"!==y?j(G,H,y,w.x,L,N,I,f,h):0,top:"none"!==z?j(H,G,z,w.y,K,M,J,g,i):0,my:l}):C},R.polys={polygon:function(a,b){var c,d,e,f={width:0,height:0,position:{top:1e10,right:0,bottom:0,left:1e10},adjustable:E},g=0,h=[],i=1,j=1,k=0,l=0;for(g=a.length;g--;)c=[parseInt(a[--g],10),parseInt(a[g+1],10)],c[0]>f.position.right&&(f.position.right=c[0]),c[0]f.position.bottom&&(f.position.bottom=c[1]),c[1]0&&e>0&&i>0&&j>0;)for(d=Math.floor(d/2),e=Math.floor(e/2),b.x===L?i=d:b.x===N?i=f.width-d:i+=Math.floor(d/2),b.y===K?j=e:b.y===M?j=f.height-e:j+=Math.floor(e/2),g=h.length;g--&&!(h.length<2);)k=h[g][0]-f.position.left,l=h[g][1]-f.position.top,(b.x===L&&k>=i||b.x===N&&i>=k||b.x===O&&(i>k||k>f.width-i)||b.y===K&&l>=j||b.y===M&&j>=l||b.y===O&&(j>l||l>f.height-j))&&h.splice(g,1);f.position={left:h[0][0],top:h[0][1]}}return f},rect:function(a,b,c,d){return{width:Math.abs(c-a),height:Math.abs(d-b),position:{left:Math.min(a,c),top:Math.min(b,d)}}},_angles:{tc:1.5,tr:7/4,tl:5/4,bc:.5,br:.25,bl:.75,rc:2,lc:1,c:0},ellipse:function(a,b,c,d,e){var f=R.polys._angles[e.abbrev()],g=0===f?0:c*Math.cos(f*Math.PI),h=d*Math.sin(f*Math.PI);return{width:2*c-Math.abs(g),height:2*d-Math.abs(h),position:{left:a+g,top:b+h},adjustable:E}},circle:function(a,b,c,d){return R.polys.ellipse(a,b,c,c,d)}},R.svg=function(a,c,e){for(var f,g,h,i,j,k,l,m,n,o=c[0],p=d(o.ownerSVGElement),q=o.ownerDocument,r=(parseInt(c.css("stroke-width"),10)||0)/2;!o.getBBox;)o=o.parentNode;if(!o.getBBox||!o.parentNode)return E;switch(o.nodeName){case"ellipse":case"circle":m=R.polys.ellipse(o.cx.baseVal.value,o.cy.baseVal.value,(o.rx||o.r).baseVal.value+r,(o.ry||o.r).baseVal.value+r,e);break;case"line":case"polygon":case"polyline":for(l=o.points||[{x:o.x1.baseVal.value,y:o.y1.baseVal.value},{x:o.x2.baseVal.value,y:o.y2.baseVal.value}],m=[],k=-1,i=l.numberOfItems||l.length;++k';d.extend(x.prototype,{_scroll:function(){var b=this.qtip.elements.overlay;b&&(b[0].style.top=d(a).scrollTop()+"px")},init:function(c){var e=c.tooltip;d("select, object").length<1&&(this.bgiframe=c.elements.bgiframe=d(Ba).appendTo(e),c._bind(e,"tooltipmove",this.adjustBGIFrame,this._ns,this)),this.redrawContainer=d("
",{id:S+"-rcontainer"}).appendTo(b.body),c.elements.overlay&&c.elements.overlay.addClass("qtipmodal-ie6fix")&&(c._bind(a,["scroll","resize"],this._scroll,this._ns,this),c._bind(e,["tooltipshow"],this._scroll,this._ns,this)),this.redraw()},adjustBGIFrame:function(){var a,b,c=this.qtip.tooltip,d={height:c.outerHeight(E),width:c.outerWidth(E)},e=this.qtip.plugins.tip,f=this.qtip.elements.tip;b=parseInt(c.css("borderLeftWidth"),10)||0,b={left:-b,top:-b},e&&f&&(a="x"===e.corner.precedance?[I,L]:[J,K],b[a[1]]-=f[a[0]]()),this.bgiframe.css(b).css(d)},redraw:function(){if(this.qtip.rendered<1||this.drawing)return this;var a,b,c,d,e=this.qtip.tooltip,f=this.qtip.options.style,g=this.qtip.options.position.container;return this.qtip.drawing=1,f.height&&e.css(J,f.height),f.width?e.css(I,f.width):(e.css(I,"").appendTo(this.redrawContainer),b=e.width(),1>b%2&&(b+=1),c=e.css("maxWidth")||"",d=e.css("minWidth")||"",a=(c+d).indexOf("%")>-1?g.width()/100:0,c=(c.indexOf("%")>-1?a:1*parseInt(c,10))||b,d=(d.indexOf("%")>-1?a:1*parseInt(d,10))||0,b=c+d?Math.min(Math.max(b,d),c):b,e.css(I,Math.round(b)).appendTo(g)),this.drawing=0,this},destroy:function(){this.bgiframe&&this.bgiframe.remove(),this.qtip._unbind([a,this.qtip.tooltip],this._ns)}}),Aa=R.ie6=function(a){return 6===da.ie?new x(a):E},Aa.initialize="render",B.ie6={"^content|style$":function(){this.redraw()}}})}(window,document); 5 | //# sourceMappingURL=jquery.qtip.min.map -------------------------------------------------------------------------------- /thegreenweb/search-bing.js: -------------------------------------------------------------------------------- 1 | import browser from "webextension-polyfill" 2 | /* 3 | * Bing search pagemod functions 4 | * 5 | * @author Arend-Jan Tetteroo 6 | * @copyright Cleanbits/The Green Web Foundation 2010-2020 7 | */ 8 | 9 | console.debug("TGWF:search:bing:loading") 10 | 11 | function annotateAndFilterSearchResults(data) { 12 | 13 | $("#b_results").find("li").each(function (result) { 14 | console.debug("first link", { link: result }) 15 | var loc = getUrl($(this).find('a').first().attr('href')); 16 | 17 | if (data[loc]) { 18 | $(this).find('.TGWF').first() 19 | .html(getResultNode(data[loc]).append(' ')) 20 | .qtip({ 21 | content: { 22 | text: function (api) { 23 | return getTitleWithLink(data[loc]); 24 | } 25 | }, 26 | show: { delay: 700 }, 27 | hide: { fixed: true, delay: 500 } 28 | }); 29 | if (data[loc].green) { 30 | $(this).find('.TGWF').first().qtip('option', { 'style.classes': 'qtip-green' }); 31 | } else { 32 | $(this).find('.TGWF').first().qtip('option', { 'style.classes': 'qtip-light' }); 33 | } 34 | 35 | // TODO: add the hiding of results back in once we have 36 | // DRY'ed up the code checkers 37 | // // hide grey results if the filter is on 38 | // if (message.filter && data[loc].green === false) { 39 | // // remove full result from the page 40 | // $(this).hide(); 41 | // } 42 | } 43 | }); 44 | } 45 | 46 | 47 | function addFooter() { 48 | // Remove all tgwf links 49 | $('#thegreenweb').remove(); 50 | 51 | var footer = document.getElementById("b_results"); 52 | footer.appendChild(getFooterElement()); 53 | } 54 | 55 | function gatherSearchLinks() { 56 | var locs = {}; 57 | if ($("#b_results").find(".b_algo").length > 0) { 58 | 59 | // Remove all tgwf links 60 | $('.TGWF').remove(); 61 | 62 | $("#b_results").find(".b_algo").each(function (i) { 63 | console.debug({ link: i }) 64 | 65 | $(this).find('a').first().prepend($('', { class: 'TGWF' }).append(getImageNode('greenquestion')).append(' ')); 66 | 67 | 68 | var loc = getUrl($(this).find('a').first().attr('href')); 69 | 70 | locs[loc] = loc; 71 | }); 72 | } 73 | return locs 74 | } 75 | 76 | async function checkDomains(locs) { 77 | console.debug("TGWF:search:bing:sending domains to be checked", locs) 78 | if (Object.keys(locs).length > 0) { 79 | // send list of domains to background js 80 | const greenCheckData = await browser.runtime.sendMessage({ locs }); 81 | console.debug("TGWF:search:bing:greenCheckData", { greenCheckData }) 82 | return greenCheckData 83 | } 84 | } 85 | 86 | 87 | browser.runtime.onMessage.addListener(annotateAndFilterSearchResults); 88 | 89 | /** 90 | * If document is ready, find the urls to check 91 | */ 92 | $(document).ready(function () { 93 | console.debug("TGWF:search:bing:ready") 94 | const req = browser.storage.local.get("annotate-search-results") 95 | 96 | req.then(async function (items) { 97 | const annotateSearchResults = items && items['annotate-search-results'] 98 | if (!annotateSearchResults) { 99 | // Green web search is disabled, return 100 | console.debug("Green web search annotationed are disabled, doing nothing") 101 | return; 102 | } 103 | // add our footer, to show the extension is working 104 | addFooter() 105 | const searchResults = gatherSearchLinks() 106 | // check the domain of each result 107 | const greenCheckData = await checkDomains(searchResults) 108 | // update the DOM to show the green/grey status 109 | 110 | annotateAndFilterSearchResults(greenCheckData) 111 | 112 | }).catch(function (error) { 113 | console.error("Something went wrong accessing local storage", error) 114 | }) 115 | }); 116 | -------------------------------------------------------------------------------- /thegreenweb/search-ecosia.js: -------------------------------------------------------------------------------- 1 | import $ from 'jquery' 2 | import browser from "webextension-polyfill" 3 | import tippy from 'tippy.js'; 4 | import { getUrl, getResultNode, getTitleWithLink, getImageNode, getFooterElement } from './thegreenweb-utils' 5 | /* 6 | * Ecosia search pagemod functions (Based on bing search) 7 | * 8 | * @author Arend-Jan Tetteroo 9 | * @copyright Cleanbits/The Green Web Foundation 2010-2020 10 | */ 11 | /** 12 | * Accept a message containing data, an object containing the domain, and green state 13 | */ 14 | function annotateAndFilterSearchResults(data) { 15 | console.log("TGWF:search:ecosia:annotateAndFilterSearchResults") 16 | 17 | $(".result").not('.card-relatedsearches .result').each(function () { 18 | var loc = getUrl($(this).find('a').first().attr('href')); 19 | 20 | if (data[loc]) { 21 | 22 | const greenlink = $(this).find('.TGWF').first() 23 | 24 | // replace with text, prepending our green/grey smiley 25 | greenlink.html(getResultNode(data[loc])) 26 | 27 | // add the tooltip to the link 28 | addToolTip(greenlink) 29 | } 30 | }); 31 | } 32 | 33 | /** 34 | * Accept a tooltip, and add tooltip behaviour, that was provided by jquery qtip 35 | * @param {} elem 36 | */ 37 | function addToolTip(elem) { 38 | 39 | // TODO: replace this code, obvs 40 | 41 | // .qtip({ 42 | // content: { 43 | // text: function (api) { 44 | // return getTitleWithLink(data[loc]); 45 | // } 46 | // }, 47 | // show: { delay: 700 }, 48 | // hide: { fixed: true, delay: 500 } 49 | // }); 50 | 51 | 52 | // if (data[loc].green) { 53 | // $(this).find('.TGWF').first().qtip('option', { 'style.classes': 'qtip-green' }); 54 | // } else { 55 | // $(this).find('.TGWF').first().qtip('option', { 'style.classes': 'qtip-light' }); 56 | // } 57 | 58 | // remove link if it's not green and we have filtering enabled 59 | // if (message.filter && data[loc].green === false) { 60 | // // remove full result from the page 61 | // $(this).hide(); 62 | // } 63 | } 64 | 65 | function gatherSearchLinks() { 66 | var ecosiaResults = {}; 67 | console.log("TGWF:search:ecosia:ecosiaResults", { ecosiaResults }) 68 | 69 | if ($(".result").length > 0) { 70 | // Remove all tgwf links 71 | $('.TGWF').remove(); 72 | 73 | $(".result").not('.card-relatedsearches .result').each(function (i) { 74 | 75 | // add question mark, while we wait for a response for our greencheck 76 | $(this).find('a').first().prepend($('', { class: 'TGWF' }).append(getImageNode('greenquestion')).append(' ')); 77 | 78 | // make a note of the link, to pull out the domain 79 | var loc = getUrl($(this).find('a').first().attr('href')); 80 | 81 | 82 | // any check to avoid sending a domain named 'false' to the API 83 | 84 | if (loc != false) { 85 | ecosiaResults[loc] = loc; 86 | } 87 | }); 88 | return ecosiaResults 89 | } 90 | } 91 | 92 | function addFooter() { 93 | $('#thegreenweb').remove(); 94 | var footer = document.getElementsByClassName("small-footer"); 95 | footer[0].appendChild(getFooterElement()); 96 | } 97 | 98 | 99 | async function checkDomains(locs) { 100 | console.debug("TGWF:search:ecosia:sending domains to be checked", locs) 101 | if (Object.keys(locs).length > 0) { 102 | // send list of domains to background js 103 | const greenCheckData = await browser.runtime.sendMessage({ locs }); 104 | console.debug("TGWF:search:ecosia:greenCheckData", { greenCheckData }) 105 | return greenCheckData 106 | } 107 | } 108 | 109 | 110 | function findAndAnnotatedSearchResults() { 111 | console.debug("TGWF:search:ecosia:ready") 112 | 113 | // TODO: try/catch this 114 | let items = null; 115 | async function checkSettings() { 116 | items = await browser.storage.local.get("annotate-search-results") 117 | 118 | const annotateSearchResults = items && items['annotate-search-results'] 119 | 120 | if (!annotateSearchResults) { 121 | console.debug("Green web search annotationed are disabled, doing nothing") 122 | return false; 123 | } 124 | 125 | console.debug("Green web search is on") 126 | 127 | 128 | // Remove all tgwf links 129 | addFooter() 130 | // find the search results on the page, annotating each one with a question 131 | // mark until we have results 132 | const searchResults = gatherSearchLinks() 133 | // check the domain of each result 134 | const greenCheckData = await checkDomains(searchResults) 135 | // update the DOM to show the green/grey status 136 | console.log(greenCheckData) 137 | annotateAndFilterSearchResults(greenCheckData) 138 | } 139 | 140 | // 141 | checkSettings() 142 | 143 | 144 | 145 | } 146 | 147 | /** 148 | * If document is ready, find the urls to check 149 | */ 150 | console.debug("TGWF:search:ecosia:loading") 151 | $(findAndAnnotatedSearchResults); 152 | -------------------------------------------------------------------------------- /thegreenweb/search-google.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Google search pagemod functions 3 | * TODO - this needs to be updated to match the new async/await and fetch based 4 | * approach 5 | * 6 | * @author Arend-Jan Tetteroo 7 | * @copyright Cleanbits/The Green Web Foundation 2010-2020 8 | */ 9 | 10 | /** 11 | * On Request, find all hrefs and assign green or grey icon 12 | */ 13 | 14 | // Thse are switched off for now, until we have refactored the annotation code 15 | 16 | // chrome.runtime.onMessage.addListener( 17 | // function (request, sender, sendResponse) { 18 | // if (request.data) { 19 | // var data = request.data; 20 | // var links = $('.TGWF'); 21 | // $(links).each(function () { 22 | // var loc = getUrl($(this).parent().attr('href')); 23 | // if (data[loc]) { 24 | // $(this).html(getResultNode(data[loc], 'google').append(' ')); 25 | // if (request.filter && data[loc].green === false) { 26 | // // remove full result from the page 27 | // $(this).parents('.rc').hide(); 28 | // } 29 | // } 30 | // }); 31 | // } 32 | // return true; 33 | // }); 34 | 35 | // /** 36 | // * If document is ready, find the urls to check 37 | // */ 38 | // $(document).ready(function () { 39 | // chrome.storage.sync.get("tgwf_search_disabled", function (items) { 40 | // if (items && items.tgwf_search_disabled && items.tgwf_search_disabled === "1") { 41 | // // Green web search is disabled, return 42 | // return; 43 | // } 44 | 45 | // var footer = document.getElementById("fbar"); 46 | // footer.appendChild(getFooterElement()); 47 | 48 | // (function checkLoop() { 49 | // // Check if search results have 'greenweb' link 50 | // var results = $('#res').find('.r > a'); 51 | // if ($('.TGWF').length !== results.length) { 52 | 53 | // // Remove all tgwf links 54 | // $('.TGWF').remove(); 55 | 56 | // // Check urls to see if search results are green/grey 57 | // var locs = {}; 58 | // $(results).each(function (i) { 59 | // // Add TGWF link to each google listing 60 | // $(this).prepend($('', { class: 'TGWF' }).append(getImageNode('greenquestion')).append(' ')); 61 | // var loc = getUrl($(this).attr('href')); 62 | // locs[loc] = loc; 63 | // }); 64 | // if (Object.keys(locs).length > 0) { 65 | // chrome.runtime.sendMessage({ locs: locs }, function (response) { }); 66 | // } 67 | // } 68 | // setTimeout(checkLoop, 100); 69 | // })(); 70 | // }); 71 | // }); 72 | -------------------------------------------------------------------------------- /thegreenweb/search-yahoo.js: -------------------------------------------------------------------------------- 1 | import browser from "webextension-polyfill" 2 | /* 3 | * Yahoo search pagemod functions 4 | * 5 | * @author Arend-Jan Tetteroo 6 | * @copyright Cleanbits/The Green Web Foundation 2010-2020 7 | */ 8 | 9 | 10 | function annotateAndFilterSearchResults(data) { 11 | console.debug("TGWF:search:yahoo:annotateAndFilterSearchResults") 12 | 13 | var list = $("#web").find("ol > li"); 14 | 15 | list.each(function () { 16 | var loc = getUrl($(this).find('a').first().attr('href')); 17 | if (loc && data[loc]) { 18 | 19 | 20 | $(this).find('.TGWF').first() 21 | .html(getResultNode(data[loc]).append(' ')) 22 | .qtip({ 23 | content: { 24 | text: function (api) { 25 | return getTitleWithLink(data[loc]); 26 | } 27 | }, 28 | show: { delay: 700 }, 29 | hide: { fixed: true, delay: 500 } 30 | }); 31 | 32 | 33 | if (data[loc].green) { 34 | $(this).find('.TGWF').first().qtip('option', { 'style.classes': 'qtip-green' }); 35 | } else { 36 | $(this).find('.TGWF').first().qtip('option', { 'style.classes': 'qtip-light' }); 37 | } 38 | 39 | 40 | // remove link if it's not green and we have filtering enabled 41 | // if (message.filter && data[loc].green === false) { 42 | // // remove full result from the page 43 | // $(this).hide(); 44 | // } 45 | } 46 | }); 47 | } 48 | 49 | async function checkDomains(locs) { 50 | console.debug("TGWF:search:yahoo:sending domains to be checked", locs) 51 | if (Object.keys(locs).length > 0) { 52 | // send list of domains to background js 53 | const greenCheckData = await browser.runtime.sendMessage({ locs }); 54 | console.debug("TGWF:search:yahoo:greenCheckData", { greenCheckData }) 55 | return greenCheckData 56 | } 57 | } 58 | 59 | function gatherSearchLinks() { 60 | 61 | var locs = {}; 62 | var list = $("#web").find("ol > li"); 63 | if (list.length > 0) { 64 | 65 | 66 | // Remove all tgwf links 67 | $('.TGWF').remove(); 68 | 69 | list.each(function (i) { 70 | 71 | console.log($(this)) 72 | // add question mark, while we wait for a response for our greencheck 73 | $(this).find('a').parent().first().children().first().prepend($('', { class: 'TGWF' }).append(getImageNode('greenquestion')).append(' ')); 74 | 75 | var loc = getUrl($(this).find('a').first().attr('href')); 76 | locs[loc] = loc; 77 | }); 78 | return locs 79 | } 80 | } 81 | 82 | 83 | function addFooter() { 84 | // Remove all tgwf links 85 | $('#thegreenweb').remove(); 86 | 87 | var footer = document.getElementById("ft"); 88 | footer.appendChild(getFooterElement()); 89 | } 90 | 91 | /** 92 | * If document is ready, find the urls to check 93 | */ 94 | console.debug("TGWF:search:yahoo:loading") 95 | $(document).ready(findAndAnnotatedSearchResults) 96 | 97 | function findAndAnnotatedSearchResults() { 98 | console.debug("TGWF:search:yahoo:ready") 99 | 100 | const req = browser.storage.local.get("annotate-search-results") 101 | req.then(async function (items) { 102 | const annotateSearchResults = items && items['annotate-search-results'] 103 | 104 | if (!annotateSearchResults) { 105 | console.debug("Green web search annotations are disabled, doing nothing") 106 | return; 107 | } 108 | console.debug("Green web search is on") 109 | 110 | addFooter() 111 | 112 | // find the search results on the page, annotating each one with a question 113 | // mark until we have results 114 | const searchResults = gatherSearchLinks() 115 | // check the domain of each result 116 | const greenCheckData = await checkDomains(searchResults) 117 | // update the DOM to show the green/grey status 118 | console.log(greenCheckData) 119 | annotateAndFilterSearchResults(greenCheckData) 120 | 121 | }) 122 | } 123 | -------------------------------------------------------------------------------- /thegreenweb/src/greencheck.js: -------------------------------------------------------------------------------- 1 | 2 | const GreenChecker = { 3 | 4 | /** 5 | * Accept am object, domains, keyed by domains, 6 | * and domainCheckResults, an arrat of Domain objects 7 | */ 8 | buildReturnPayload(domains, domainCheckResults) { 9 | 10 | // make our grey domainObjects 11 | const checkedDomains = {} 12 | for (const domain of Object.keys(domains)) { 13 | checkedDomains[domain] = { 14 | green: false, 15 | url: domain, 16 | data: true 17 | } 18 | } 19 | // then update grey list with the green results 20 | for (const domainCheck of domainCheckResults) { 21 | checkedDomains[domainCheck.url] = domainCheck 22 | } 23 | return checkedDomains 24 | }, 25 | 26 | checkBulkDomains: async function (domains, options) { 27 | let apiHostName = "https://admin.thegreenwebfoundation.org/api/v3/greencheck/" 28 | // let apiHostName = "https://api.thegreenwebfoundation.org/v2/greencheckmulti/" 29 | 30 | // optional override 31 | if (options && options.apiHost) { 32 | apiHostName = options.apiHost 33 | } 34 | var length = Object.keys(domains).length; 35 | 36 | 37 | if (length > 100) { 38 | console.log(`Too many domains to check, skipping our check`) 39 | return false 40 | } 41 | 42 | const sites = Object.getOwnPropertyNames(domains).splice(0, 50); 43 | // make comma separated list 44 | const urlencodedSites = Object.keys(domains).join(',') 45 | 46 | console.debug(`TGWF:GreenChecker: making request for ${sites.length} sites`) 47 | const requestUrl = `${apiHostName}?urls=${urlencodedSites}` 48 | 49 | const res = await fetch(requestUrl) 50 | const domainCheckResults = await res.json() 51 | 52 | console.debug(`TGWF:GreenChecker: got back ${domainCheckResults.length} responses`) 53 | 54 | return this.buildReturnPayload(domains, domainCheckResults) 55 | } 56 | } 57 | 58 | export { GreenChecker } 59 | -------------------------------------------------------------------------------- /thegreenweb/src/search-page-adapter.js: -------------------------------------------------------------------------------- 1 | // Abstract page adapter 2 | -------------------------------------------------------------------------------- /thegreenweb/tailwind.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | 4 | .btn { 5 | @apply px-6 py-2 text-white rounded; 6 | } 7 | 8 | .toggle-checkbox:checked { 9 | @apply: right-0 border-green-400; 10 | right: 0; 11 | border-color: #76c22d; 12 | } 13 | 14 | .toggle-checkbox:checked+.toggle-label { 15 | @apply: bg-green-400; 16 | background-color: #76c22d; 17 | } 18 | 19 | @tailwind utilities; 20 | -------------------------------------------------------------------------------- /thegreenweb/thegreenweb-utils.js: -------------------------------------------------------------------------------- 1 | import $ from 'jquery' 2 | /** 3 | * Utilities for the greenweb add-on 4 | * 5 | * @author Arend-Jan Tetteroo 6 | * @copyright Cleanbits/The Green Web Foundation 2010-2020 7 | */ 8 | 9 | /** 10 | * Get the url from the given location 11 | */ 12 | function getUrl(loc) { 13 | loc = stripProtocolFromUrl(loc); 14 | if (loc === false) { 15 | return false; 16 | } 17 | loc = stripQueryStringFromUrl(loc); 18 | loc = stripPageFromUrl(loc); 19 | loc = stripPortFromUrl(loc); 20 | 21 | // Don't lookup localhost 22 | if (loc === 'localhost') { 23 | return false; 24 | } 25 | 26 | return loc; 27 | } 28 | 29 | /** 30 | * Strip the protocol from the location 31 | * If no http or https given, then return false 32 | */ 33 | function stripProtocolFromUrl(loc) { 34 | if (loc === undefined) { 35 | return false; 36 | } 37 | 38 | var prot = loc.substring(0, 5); 39 | if (prot === 'http:') { 40 | return loc.substring(7); 41 | } 42 | 43 | if (prot === 'https') { 44 | return loc.substring(8); 45 | } 46 | 47 | // No http or https, no lookup 48 | return false; 49 | } 50 | 51 | /** 52 | * Only use the domain.tld, not the querystring behind ? or # 53 | */ 54 | function stripQueryStringFromUrl(loc) { 55 | var temp = loc.split('?'); 56 | loc = temp[0]; 57 | temp = loc.split('#'); 58 | loc = temp[0]; 59 | return loc; 60 | } 61 | 62 | /** 63 | * Only use the domain.tld, not the page 64 | */ 65 | function stripPageFromUrl(loc) { 66 | var temp = loc.split('/'); 67 | loc = temp[0]; 68 | return loc; 69 | } 70 | 71 | /** 72 | * Only use the domain.tld, not the port 73 | */ 74 | function stripPortFromUrl(loc) { 75 | var temp = loc.split(':'); 76 | loc = temp[0]; 77 | return loc; 78 | } 79 | 80 | /** 81 | * Get the image with a cleanbits link around it as a jquery node 82 | */ 83 | function getGreenwebLinkNode(color, tooltip) { 84 | var aItem = document.createElement("a"); 85 | aItem.href = 'https://www.thegreenwebfoundation.org'; 86 | aItem.class = 'TGWF-addon'; 87 | aItem.title = tooltip; 88 | 89 | var imageItem = document.createElement("img"); 90 | imageItem.src = getImagePath(color); 91 | imageItem.style = 'width:16px; height:16px;border:none;'; 92 | aItem.appendChild(imageItem); 93 | 94 | return aItem; 95 | } 96 | 97 | /** 98 | * Get the footer element for displaying the green web extension is working 99 | * 100 | * @returns {HTMLParagraphElement} 101 | */ 102 | function getFooterElement() { 103 | var greenWebEnabledItem = document.createElement("p"); 104 | greenWebEnabledItem.id = 'thegreenweb'; 105 | greenWebEnabledItem.style = 'text-align:center;'; 106 | 107 | var image = getGreenwebLinkNode('green', 'The Green Web extension shows if a site is sustainably hosted'); 108 | var spanItem = document.createElement('span'); 109 | spanItem.id = 'thegreenwebenabled'; 110 | 111 | var text = document.createTextNode("The Green Web is enabled"); 112 | 113 | greenWebEnabledItem.appendChild(spanItem).appendChild(image).appendChild(text); 114 | 115 | return greenWebEnabledItem; 116 | } 117 | 118 | /** 119 | * get a link node based on color 120 | * 121 | * @param color 122 | * @returns {void | * | jQuery} 123 | */ 124 | function getLinkNode(color, type) { 125 | var style = 'width:16px; height:16px;border:none;'; 126 | if (type === 'google') { 127 | style = 'width:16px; height:16px;border:none; margin-left:-20px; margin-top:2px'; 128 | } 129 | 130 | var href = 'https://www.thegreenwebfoundation.org'; 131 | return $("", { href: href, class: 'TGWF-addon' }) 132 | .append($('', { src: getImagePath(color), style: style })); 133 | } 134 | 135 | /** 136 | * Get the image element as a jquery Node 137 | * 138 | * @param color 139 | * @returns {*|jQuery|HTMLElement} 140 | */ 141 | function getImageNode(color) { 142 | return $('', { style: 'width:16px; height:16px;border:none;', src: getImagePath(color) }); 143 | } 144 | 145 | /** 146 | * Get the image path based on file 147 | */ 148 | function getImagePath(file, local) { 149 | var icons = {}; 150 | icons.green = chrome.runtime.getURL("/images/green20x20transp.png"); 151 | icons.grey = chrome.runtime.getURL("/images/grey20x20transp.png"); 152 | icons.greenquestion = chrome.runtime.getURL("/images/question20x20transp.png"); 153 | icons.greenfan = chrome.runtime.getURL("/images/greenfan20x20transp.png"); 154 | icons.greenhouse = chrome.runtime.getURL("/images/greenhouse20x20transp.png"); 155 | icons.goldsmiley = chrome.runtime.getURL("/images/gold20x20transp.png"); 156 | 157 | if (icons[file]) { 158 | return icons[file]; 159 | } 160 | 161 | if (local) { 162 | return chrome.runtime.getURL("/images/green20x20.gif"); 163 | } 164 | 165 | // if the file has http as it's start, it's a full url to a web icon somewhere else, so then return that. 166 | var prot = file.substring(0, 4); 167 | if (prot === 'http') { 168 | return file; 169 | } 170 | 171 | return 'https://api.thegreenwebfoundation.org/icons/' + file + "20x20.gif"; 172 | } 173 | 174 | /** 175 | * Get the resulting image from the data as jquery dom node 176 | */ 177 | function getResultNode(data, type) { 178 | return getLinkNode(getIcon(data), type); 179 | } 180 | 181 | /** 182 | * Get the icon based on the data 183 | */ 184 | function getIcon(data) { 185 | if (data.green) { 186 | // Green 187 | if (data.icon) { 188 | // Special icon 189 | return data.icon; 190 | } 191 | return 'green'; 192 | } 193 | 194 | if (data.data === false) { 195 | // Not enough data for the domain, show question 196 | return 'greenquestion'; 197 | } 198 | return 'grey'; 199 | } 200 | 201 | /** 202 | * Get the title based on the data 203 | */ 204 | function getTitle(data) { 205 | if (data.green) { 206 | // Green 207 | if (data.hostedby) { 208 | // We know the hoster, show it 209 | return 'Sustainably hosted by ' + data.hostedby; 210 | } 211 | return 'is sustainably hosted'; 212 | } 213 | 214 | if (data.data === false) { 215 | // No data available, show help message 216 | return "No data available yet for this country domain. Wanna help? Contact us through www.thegreenwebfoundation.org"; 217 | } 218 | 219 | // Data available, so show grey site 220 | return data.url + ' is hosted grey'; 221 | 222 | } 223 | 224 | /** 225 | * Get the title based on the data 226 | */ 227 | function getTitleWithLink(data) { 228 | if (!data) { 229 | return ''; 230 | } 231 | if (data.green) { 232 | if (data.hostedby) { 233 | return data.url + ' ' + '' + ' is sustainably hosted by ' + ' ' + data.hostedby + ''; 234 | } 235 | return data.url + ' ' + 'is sustainably hosted'; 236 | } 237 | 238 | if (data.data === false) { 239 | // No data available, show help message 240 | return "No data available yet for this country domain. Wanna help? Contact us through " + " www.thegreenwebfoundation.org"; 241 | } 242 | 243 | // Data available, so show grey site 244 | return data.url + ' ' + ' is hosted grey'; 245 | } 246 | 247 | export { 248 | getUrl, 249 | stripProtocolFromUrl, 250 | stripQueryStringFromUrl, 251 | stripPageFromUrl, 252 | stripPortFromUrl, 253 | getGreenwebLinkNode, 254 | getFooterElement, 255 | getLinkNode, 256 | getImageNode, 257 | getImagePath, 258 | getResultNode, 259 | getIcon, 260 | getTitle, 261 | getTitleWithLink, 262 | } 263 | -------------------------------------------------------------------------------- /thegreenweb/thegreenweb.css: -------------------------------------------------------------------------------- 1 | .tgwf_green { 2 | border-bottom:1px dashed #a3c005 !important; 3 | } 4 | 5 | .tgwf_question { 6 | text-decoration: underline; 7 | color: grey !important; 8 | } -------------------------------------------------------------------------------- /web-ext-config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "sourceDir": "thegreenweb/" 3 | } 4 | --------------------------------------------------------------------------------