├── assets ├── artists │ ├── xkcd.png │ ├── bitcorati.png │ ├── p_emerson.jpg │ ├── pinguino.png │ ├── soumitra.jpg │ ├── tom_woods.jpg │ ├── dan_carlin.jpg │ ├── john_light.png │ ├── oliver_masiosare.jpg │ ├── singing_pictures.jpg │ ├── pink_house_music_tribe.jpg │ └── histories_of_things_to_come.jpg ├── images │ ├── heart.ai │ ├── star.png │ ├── heart16x16.png │ ├── heart48x48.png │ ├── ajax_loader.gif │ ├── heart128x128.png │ ├── one_time_pass.png │ ├── heart16x16_passive.png │ ├── heart48x48_passive.png │ ├── pass_icon_active.png │ ├── pass_icon_active2.png │ ├── pass_icon_default.png │ ├── pass_icon_default2.png │ ├── passes_screen_shot.jpg │ └── protip_subscription.png ├── fonts │ ├── FontAwesome.otf │ ├── fontawesome-webfont.eot │ ├── fontawesome-webfont.ttf │ ├── fontawesome-webfont.woff │ └── fontawesome-webfont.woff2 ├── jquery-ui │ └── images │ │ ├── ui-icons_222222_256x240.png │ │ ├── ui-icons_228ef1_256x240.png │ │ ├── ui-icons_ef8c08_256x240.png │ │ ├── ui-icons_ffd27a_256x240.png │ │ ├── ui-icons_ffffff_256x240.png │ │ ├── ui-bg_flat_10_000000_40x100.png │ │ ├── ui-bg_glass_100_f6f6f6_1x400.png │ │ ├── ui-bg_glass_100_fdf5ce_1x400.png │ │ ├── ui-bg_glass_65_ffffff_1x400.png │ │ ├── ui-bg_gloss-wave_35_f6a828_500x100.png │ │ ├── ui-bg_diagonals-thick_18_b81900_40x40.png │ │ ├── ui-bg_diagonals-thick_20_666666_40x40.png │ │ ├── ui-bg_highlight-soft_100_eeeeee_1x100.png │ │ └── ui-bg_highlight-soft_75_ffe45c_1x100.png ├── bootstrap │ ├── fonts │ │ ├── glyphicons-halflings-regular.eot │ │ ├── glyphicons-halflings-regular.ttf │ │ ├── glyphicons-halflings-regular.woff │ │ └── glyphicons-halflings-regular.woff2 │ ├── js │ │ └── npm.js │ └── css │ │ └── bootstrap-non-responsive-override.css ├── promotional │ ├── 1_protip_cartoon_rock_barcellos.jpg │ ├── 2_protip_cartoon_rock_barcellos.jpg │ ├── 3_protip_cartoon_rock_barcellos.jpg │ ├── 4_protip_cartoon_rock_barcellos.jpg │ ├── 5_protip_cartoon_rock_barcellos.jpg │ ├── 6_protip_cartoon_rock_barcellos.jpg │ └── 7_protip_cartoon_rock_barcellos.jpg └── css │ ├── switchery.min.css │ └── protip.css ├── controllers ├── feedback.js ├── redirect.js ├── options.js ├── blacklist.js ├── subscriptions.js ├── install.js └── home.js ├── js ├── utils-bitcoin.js ├── reminder-countdown.js ├── ydn-db-schema.js ├── weekly-budget-widget.js ├── alarm-manager.js ├── weekly-browsing-widget.js ├── ui-utils.js └── payment-manager.js ├── features ├── manual-donate.feature └── world.js ├── .gitignore ├── init └── error-log.js ├── views ├── redirect.html ├── options.html ├── install.html ├── popup.html ├── blacklist.html ├── feedback.html ├── passes.html └── subscriptions.html ├── RELEASE_NOTES.txt ├── manifest.json ├── README.md └── lib ├── date.js ├── util.js ├── promise.min.js ├── preferences.js ├── currency-manager.js └── cryptojs.min.js /assets/artists/xkcd.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProTipHQ/ProTip/HEAD/assets/artists/xkcd.png -------------------------------------------------------------------------------- /assets/images/heart.ai: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProTipHQ/ProTip/HEAD/assets/images/heart.ai -------------------------------------------------------------------------------- /assets/images/star.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProTipHQ/ProTip/HEAD/assets/images/star.png -------------------------------------------------------------------------------- /assets/artists/bitcorati.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProTipHQ/ProTip/HEAD/assets/artists/bitcorati.png -------------------------------------------------------------------------------- /assets/artists/p_emerson.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProTipHQ/ProTip/HEAD/assets/artists/p_emerson.jpg -------------------------------------------------------------------------------- /assets/artists/pinguino.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProTipHQ/ProTip/HEAD/assets/artists/pinguino.png -------------------------------------------------------------------------------- /assets/artists/soumitra.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProTipHQ/ProTip/HEAD/assets/artists/soumitra.jpg -------------------------------------------------------------------------------- /assets/artists/tom_woods.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProTipHQ/ProTip/HEAD/assets/artists/tom_woods.jpg -------------------------------------------------------------------------------- /assets/fonts/FontAwesome.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProTipHQ/ProTip/HEAD/assets/fonts/FontAwesome.otf -------------------------------------------------------------------------------- /assets/images/heart16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProTipHQ/ProTip/HEAD/assets/images/heart16x16.png -------------------------------------------------------------------------------- /assets/images/heart48x48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProTipHQ/ProTip/HEAD/assets/images/heart48x48.png -------------------------------------------------------------------------------- /assets/artists/dan_carlin.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProTipHQ/ProTip/HEAD/assets/artists/dan_carlin.jpg -------------------------------------------------------------------------------- /assets/artists/john_light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProTipHQ/ProTip/HEAD/assets/artists/john_light.png -------------------------------------------------------------------------------- /assets/images/ajax_loader.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProTipHQ/ProTip/HEAD/assets/images/ajax_loader.gif -------------------------------------------------------------------------------- /assets/images/heart128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProTipHQ/ProTip/HEAD/assets/images/heart128x128.png -------------------------------------------------------------------------------- /assets/images/one_time_pass.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProTipHQ/ProTip/HEAD/assets/images/one_time_pass.png -------------------------------------------------------------------------------- /assets/artists/oliver_masiosare.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProTipHQ/ProTip/HEAD/assets/artists/oliver_masiosare.jpg -------------------------------------------------------------------------------- /assets/artists/singing_pictures.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProTipHQ/ProTip/HEAD/assets/artists/singing_pictures.jpg -------------------------------------------------------------------------------- /assets/fonts/fontawesome-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProTipHQ/ProTip/HEAD/assets/fonts/fontawesome-webfont.eot -------------------------------------------------------------------------------- /assets/fonts/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProTipHQ/ProTip/HEAD/assets/fonts/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /assets/images/heart16x16_passive.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProTipHQ/ProTip/HEAD/assets/images/heart16x16_passive.png -------------------------------------------------------------------------------- /assets/images/heart48x48_passive.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProTipHQ/ProTip/HEAD/assets/images/heart48x48_passive.png -------------------------------------------------------------------------------- /assets/images/pass_icon_active.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProTipHQ/ProTip/HEAD/assets/images/pass_icon_active.png -------------------------------------------------------------------------------- /assets/images/pass_icon_active2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProTipHQ/ProTip/HEAD/assets/images/pass_icon_active2.png -------------------------------------------------------------------------------- /assets/images/pass_icon_default.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProTipHQ/ProTip/HEAD/assets/images/pass_icon_default.png -------------------------------------------------------------------------------- /assets/images/pass_icon_default2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProTipHQ/ProTip/HEAD/assets/images/pass_icon_default2.png -------------------------------------------------------------------------------- /assets/images/passes_screen_shot.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProTipHQ/ProTip/HEAD/assets/images/passes_screen_shot.jpg -------------------------------------------------------------------------------- /assets/fonts/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProTipHQ/ProTip/HEAD/assets/fonts/fontawesome-webfont.woff -------------------------------------------------------------------------------- /assets/fonts/fontawesome-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProTipHQ/ProTip/HEAD/assets/fonts/fontawesome-webfont.woff2 -------------------------------------------------------------------------------- /assets/images/protip_subscription.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProTipHQ/ProTip/HEAD/assets/images/protip_subscription.png -------------------------------------------------------------------------------- /assets/artists/pink_house_music_tribe.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProTipHQ/ProTip/HEAD/assets/artists/pink_house_music_tribe.jpg -------------------------------------------------------------------------------- /assets/artists/histories_of_things_to_come.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProTipHQ/ProTip/HEAD/assets/artists/histories_of_things_to_come.jpg -------------------------------------------------------------------------------- /assets/jquery-ui/images/ui-icons_222222_256x240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProTipHQ/ProTip/HEAD/assets/jquery-ui/images/ui-icons_222222_256x240.png -------------------------------------------------------------------------------- /assets/jquery-ui/images/ui-icons_228ef1_256x240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProTipHQ/ProTip/HEAD/assets/jquery-ui/images/ui-icons_228ef1_256x240.png -------------------------------------------------------------------------------- /assets/jquery-ui/images/ui-icons_ef8c08_256x240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProTipHQ/ProTip/HEAD/assets/jquery-ui/images/ui-icons_ef8c08_256x240.png -------------------------------------------------------------------------------- /assets/jquery-ui/images/ui-icons_ffd27a_256x240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProTipHQ/ProTip/HEAD/assets/jquery-ui/images/ui-icons_ffd27a_256x240.png -------------------------------------------------------------------------------- /assets/jquery-ui/images/ui-icons_ffffff_256x240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProTipHQ/ProTip/HEAD/assets/jquery-ui/images/ui-icons_ffffff_256x240.png -------------------------------------------------------------------------------- /assets/bootstrap/fonts/glyphicons-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProTipHQ/ProTip/HEAD/assets/bootstrap/fonts/glyphicons-halflings-regular.eot -------------------------------------------------------------------------------- /assets/bootstrap/fonts/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProTipHQ/ProTip/HEAD/assets/bootstrap/fonts/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /assets/bootstrap/fonts/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProTipHQ/ProTip/HEAD/assets/bootstrap/fonts/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /assets/jquery-ui/images/ui-bg_flat_10_000000_40x100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProTipHQ/ProTip/HEAD/assets/jquery-ui/images/ui-bg_flat_10_000000_40x100.png -------------------------------------------------------------------------------- /assets/jquery-ui/images/ui-bg_glass_100_f6f6f6_1x400.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProTipHQ/ProTip/HEAD/assets/jquery-ui/images/ui-bg_glass_100_f6f6f6_1x400.png -------------------------------------------------------------------------------- /assets/jquery-ui/images/ui-bg_glass_100_fdf5ce_1x400.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProTipHQ/ProTip/HEAD/assets/jquery-ui/images/ui-bg_glass_100_fdf5ce_1x400.png -------------------------------------------------------------------------------- /assets/jquery-ui/images/ui-bg_glass_65_ffffff_1x400.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProTipHQ/ProTip/HEAD/assets/jquery-ui/images/ui-bg_glass_65_ffffff_1x400.png -------------------------------------------------------------------------------- /assets/promotional/1_protip_cartoon_rock_barcellos.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProTipHQ/ProTip/HEAD/assets/promotional/1_protip_cartoon_rock_barcellos.jpg -------------------------------------------------------------------------------- /assets/promotional/2_protip_cartoon_rock_barcellos.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProTipHQ/ProTip/HEAD/assets/promotional/2_protip_cartoon_rock_barcellos.jpg -------------------------------------------------------------------------------- /assets/promotional/3_protip_cartoon_rock_barcellos.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProTipHQ/ProTip/HEAD/assets/promotional/3_protip_cartoon_rock_barcellos.jpg -------------------------------------------------------------------------------- /assets/promotional/4_protip_cartoon_rock_barcellos.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProTipHQ/ProTip/HEAD/assets/promotional/4_protip_cartoon_rock_barcellos.jpg -------------------------------------------------------------------------------- /assets/promotional/5_protip_cartoon_rock_barcellos.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProTipHQ/ProTip/HEAD/assets/promotional/5_protip_cartoon_rock_barcellos.jpg -------------------------------------------------------------------------------- /assets/promotional/6_protip_cartoon_rock_barcellos.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProTipHQ/ProTip/HEAD/assets/promotional/6_protip_cartoon_rock_barcellos.jpg -------------------------------------------------------------------------------- /assets/promotional/7_protip_cartoon_rock_barcellos.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProTipHQ/ProTip/HEAD/assets/promotional/7_protip_cartoon_rock_barcellos.jpg -------------------------------------------------------------------------------- /assets/bootstrap/fonts/glyphicons-halflings-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProTipHQ/ProTip/HEAD/assets/bootstrap/fonts/glyphicons-halflings-regular.woff2 -------------------------------------------------------------------------------- /assets/jquery-ui/images/ui-bg_gloss-wave_35_f6a828_500x100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProTipHQ/ProTip/HEAD/assets/jquery-ui/images/ui-bg_gloss-wave_35_f6a828_500x100.png -------------------------------------------------------------------------------- /assets/jquery-ui/images/ui-bg_diagonals-thick_18_b81900_40x40.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProTipHQ/ProTip/HEAD/assets/jquery-ui/images/ui-bg_diagonals-thick_18_b81900_40x40.png -------------------------------------------------------------------------------- /assets/jquery-ui/images/ui-bg_diagonals-thick_20_666666_40x40.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProTipHQ/ProTip/HEAD/assets/jquery-ui/images/ui-bg_diagonals-thick_20_666666_40x40.png -------------------------------------------------------------------------------- /assets/jquery-ui/images/ui-bg_highlight-soft_100_eeeeee_1x100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProTipHQ/ProTip/HEAD/assets/jquery-ui/images/ui-bg_highlight-soft_100_eeeeee_1x100.png -------------------------------------------------------------------------------- /assets/jquery-ui/images/ui-bg_highlight-soft_75_ffe45c_1x100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProTipHQ/ProTip/HEAD/assets/jquery-ui/images/ui-bg_highlight-soft_75_ffe45c_1x100.png -------------------------------------------------------------------------------- /controllers/feedback.js: -------------------------------------------------------------------------------- 1 | $(function(){ 2 | if(!localStorage['proTipInstalled']) { 3 | window.location.replace("install.html"); 4 | } 5 | 6 | allowExternalLinks(); 7 | }); 8 | -------------------------------------------------------------------------------- /js/utils-bitcoin.js: -------------------------------------------------------------------------------- 1 | function validAddress(address){ 2 | try { 3 | new bitcoin.address.fromBase58Check(address.trim()); 4 | } catch (e) { 5 | return false; 6 | } 7 | return true; 8 | } 9 | -------------------------------------------------------------------------------- /controllers/redirect.js: -------------------------------------------------------------------------------- 1 | // On the first load when using a version held on Chrome Store it fails to load Jquery. 2 | // on the second load everything is fine. 3 | // So the 'full screen' link points here, then redirects to home, hacking around the failure. 4 | // 5 | window.location.replace("home.html"); 6 | -------------------------------------------------------------------------------- /features/manual-donate.feature: -------------------------------------------------------------------------------- 1 | Feature: Example feature 2 | As a user of cucumber.js 3 | I want to have documentation on cucumber 4 | So that I can concentrate on building awesome applications 5 | 6 | Scenario: Reading documentation 7 | Given I am on the Cucumber.js GitHub repository 8 | When I go to the README file 9 | Then I should see "Usage" as the page title -------------------------------------------------------------------------------- /features/world.js: -------------------------------------------------------------------------------- 1 | // features/support/world.js 2 | var zombie = require('zombie'); 3 | function World() { 4 | this.browser = new zombie(); // this.browser will be available in step definitions 5 | 6 | this.visit = function (url, callback) { 7 | this.browser.visit(url, callback); 8 | }; 9 | } 10 | 11 | module.exports = function() { 12 | this.World = World; 13 | }; -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ### OSX ### 2 | .DS_Store 3 | .AppleDouble 4 | .LSOverride 5 | 6 | # Icon must end with two \r 7 | Icon 8 | web-ext-artifacts/ 9 | 10 | # Thumbnails 11 | ._* 12 | 13 | # Files that might appear in the root of a volume 14 | .DocumentRevisions-V100 15 | .fseventsd 16 | .Spotlight-V100 17 | .TemporaryItems 18 | .Trashes 19 | .VolumeIcon.icns 20 | 21 | # Directories potentially created on remote AFP share 22 | .AppleDB 23 | .AppleDesktop 24 | Network Trash Folder 25 | Temporary Items 26 | .apdisk 27 | -------------------------------------------------------------------------------- /assets/bootstrap/js/npm.js: -------------------------------------------------------------------------------- 1 | // This file is autogenerated via the `commonjs` Grunt task. You can require() this file in a CommonJS environment. 2 | require('../../js/transition.js') 3 | require('../../js/alert.js') 4 | require('../../js/button.js') 5 | require('../../js/carousel.js') 6 | require('../../js/collapse.js') 7 | require('../../js/dropdown.js') 8 | require('../../js/modal.js') 9 | require('../../js/tooltip.js') 10 | require('../../js/popover.js') 11 | require('../../js/scrollspy.js') 12 | require('../../js/tab.js') 13 | require('../../js/affix.js') -------------------------------------------------------------------------------- /assets/css/switchery.min.css: -------------------------------------------------------------------------------- 1 | .switchery{background-color:#fff;border:1px solid #dfdfdf;border-radius:20px;cursor:pointer;display:inline-block;height:30px;position:relative;vertical-align:middle;width:50px;-moz-user-select:none;-khtml-user-select:none;-webkit-user-select:none;-ms-user-select:none;user-select:none;box-sizing:content-box;background-clip:content-box}.switchery>small{background:#fff;border-radius:100%;box-shadow:0 1px 3px rgba(0,0,0,0.4);height:30px;position:absolute;top:0;width:30px}.switchery-small{border-radius:20px;height:20px;width:33px}.switchery-small>small{height:20px;width:20px}.switchery-large{border-radius:40px;height:40px;width:66px}.switchery-large>small{height:40px;width:40px} -------------------------------------------------------------------------------- /init/error-log.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | // proxy pattern to override console.error and output to localStorage. 3 | // To display in a error log. 4 | var proxied = console.error; 5 | if(!localStorage['errorLog']){ localStorage.setItem("errorLog", JSON.stringify([])) } 6 | console.error = function() { 7 | var concatedArguments = ''; 8 | for(var i=0;i 2 | 3 | 4 | 5 | 6 | ProTip 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /RELEASE_NOTES.txt: -------------------------------------------------------------------------------- 1 | v.1.0.0.40 2 January 2016. Git Ref 9862179a5515dd51fc35abc75b6f016b1e8ca4ad 2 | Adding the ability to search dynamic loaded content for bitcoin addresses, first to be deployed to Staging. 3 | 4 | v1.0.0.39 29 December 2015. Git Ref 39b15d704fcfc8100e84c0b86ad7748ac68bd8ff 5 | BUG: Bitcoin address not being detected in a Span tag here: https://ploum.net/a-ceux-qui-sont-morts-pour-rien/ 6 | This is because the bitcoin address has a ',' after the bitcoin address '12uAH27PhWepyzZ8kzY9rMAZzwpfsWSxXt,' a bitcoin address needs either a space or 7 | end or beginning of a tag container. I have updated the regex to include ',', '.' and ';' 8 | 9 | v1.0.0.37 29 December 2015. Git Ref 9b5413ed6beac5a858c2cf37fe478298f2a778ee 10 | Bug: When Subscriptions and Weekly Browsing are empty ProTip sends the balance in the wallet to itself minus the miner fee. Fixed. 11 | 12 | v1.0.0.36 28 December 2015. Git Ref 33cb58775df31ccf71cf2eab04e202e5d08bf732 13 | Bug: Extension incorrectly displays balance amount as zero until user enters Full Screen mode. 14 | Fixed. 15 | https://twitter.com/dMnyc/status/678982723311136769 16 | 17 | v1.0.0.35, 27 November 2015. Git Ref following 6f22a604cc629c86b5297a397152ce4eb820711a. 18 | Fixing issue related to a failure to load external CSS and JS files on first load. 19 | Functions allowing the search of dynamically loaded content have been disable on 20 | this release because they are not yet tested and stable. -------------------------------------------------------------------------------- /js/ydn-db-schema.js: -------------------------------------------------------------------------------- 1 | var subscriptions = { 2 | name: 'subscriptions', 3 | keyPath: 'bitcoinAddress', 4 | autoIncrement: false, 5 | indexes: [ 6 | { 7 | name: 'amountFiat' 8 | }, { 9 | name: 'createdAt' 10 | }, { 11 | name: 'title' 12 | }, { 13 | name: 'url' 14 | } 15 | ] 16 | }; 17 | 18 | var sites = { 19 | name: 'sites', 20 | keyPath: 'url', 21 | autoIncrement: false, 22 | indexes: [ 23 | { 24 | name: 'bitcoinAddresses', 25 | multiEntry: true 26 | }, { 27 | name: 'createdAt' 28 | }, { 29 | name: 'timeOnPage' 30 | }, { 31 | name: 'title' 32 | }, { 33 | name: 'bitcoinAddress' 34 | } 35 | ] 36 | }; 37 | 38 | var blacklist = { 39 | name: 'blacklist', 40 | keyPath: 'url', 41 | autoIncrement: false, 42 | indexes: [ 43 | { 44 | name: 'url' 45 | } 46 | ] 47 | }; 48 | 49 | var blacklistedHostname = { 50 | name: 'blacklistedhostnames', 51 | keyPath: 'hostname', 52 | autoIncrement: false, 53 | indexes: [ 54 | { 55 | name: 'hostname' 56 | } 57 | ] 58 | }; 59 | 60 | var blacklistBitcoinAddresses = { 61 | name: 'blacklistbitcoinaddresses', 62 | keyPath: 'bitcoinAddress', 63 | autoIncrement: false, 64 | indexes: [ 65 | { 66 | name: 'bitcoinAddress' 67 | } 68 | ] 69 | }; 70 | 71 | var sponsors = { 72 | name: 'sponsors', 73 | keyPath: 'twitterhandle', 74 | autoIncrement: false, 75 | indexes: [ 76 | { 77 | name: 'twitterhandle' 78 | } 79 | ] 80 | }; 81 | 82 | var audit = { 83 | name: 'audit', 84 | keyPath: 'createdAt', 85 | autoIncrement: false, 86 | indexes: [ 87 | { 88 | name: 'createdAt' 89 | } 90 | ] 91 | }; 92 | 93 | var schema = { 94 | version: 1, 95 | autoSchema: false, // must be false when version is defined 96 | stores: [subscriptions, sites, blacklist, blacklistedHostname, blacklistBitcoinAddresses, audit, sponsors] 97 | } 98 | -------------------------------------------------------------------------------- /manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "background": { 3 | "scripts": [ 4 | "/lib/browser-polyfill.js", 5 | "/init/error-log.js", 6 | "/lib/jquery-2.1.3.min.js", 7 | "/lib/promise.min.js", 8 | "/lib/underscore-min.js", 9 | "/lib/date.js", 10 | "/lib/cryptojs.min.js", 11 | "/lib/bitcoinjs-lib.min.js", 12 | "/lib/preferences.js", 13 | "/lib/util.js", 14 | "/lib/currency-manager.js", 15 | "/lib/wallet.js", 16 | "/js/payment-manager.js", 17 | "/lib/ydn-db.min.js", 18 | "/js/ydn-db-schema.js", 19 | "/js/alarm-manager.js", 20 | "/js/utils-bitcoin.js", 21 | "/background.js" 22 | ] 23 | }, 24 | "web_accessible_resources": ["/assets/images/star.png"], 25 | "content_scripts": [ 26 | { 27 | "matches": ["http://*/*", "https://*/*"], 28 | "js": [ 29 | "/lib/browser-polyfill.js", 30 | "/init/error-log.js", 31 | "/lib/jquery-2.1.3.min.js", 32 | "/content.js", 33 | "/js/utils-bitcoin.js", 34 | "/lib/bitcoinjs-lib.min.js" 35 | ] 36 | } 37 | ], 38 | "icons": { "16": "/assets/images/heart16x16.png", 39 | "48": "/assets/images/heart48x48.png", 40 | "128": "/assets/images/heart128x128.png" }, 41 | "browser_action": { 42 | "default_icon": "/assets/images/heart48x48_passive.png", 43 | "default_popup": "/views/popup.html", 44 | "default_title": "ProTip" 45 | }, 46 | "description": "ProTip is peer to peer crowd funding. ProTip provides tipping, weekly subscriptions and content passes for premium work.", 47 | "icons": { 48 | "128": "/assets/images/heart128x128.png" 49 | }, 50 | "content_security_policy": "script-src 'self' 'unsafe-eval'; object-src 'self';", 51 | "permissions": [ 52 | "https://api.blockcypher.com/*", 53 | "http://blockchain.info/*", 54 | "https://blockchain.info/*", 55 | "wss://ws.blockchain.info:*", 56 | "https://apiv2.bitcoinaverage.com/indices/global/ticker/short" 57 | ], 58 | "incognito": "not_allowed", 59 | "manifest_version": 2, 60 | "name": "ProTip peer-to-peer crowd-funding", 61 | "short_name": "ProTip", 62 | "options_page": "/views/options.html", 63 | "permissions": [ "idle", "tabs", "storage", "alarms", "notifications" ], 64 | "update_url": "https://clients2.google.com/service/update2/crx", 65 | "applications": { 66 | "gecko": { 67 | "id": "protip@protip" 68 | } 69 | }, 70 | "version": "1.0.0.40" 71 | } 72 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # A Chrome Extension for peer-to-peer crowd funding. 2 | 3 |

4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 |

12 | 13 | Many thanks to the very talented Rock Barcellos for the creation and use of the above images. [https://twitter.com/rockbarcellos](https://twitter.com/rockbarcellos) 14 | 15 | ## Tipping & online donations for doing the work you love 16 | ProTip Release Candidate has the following features. 17 | 18 | - **Weekly subscriptions** for your loyal audience 19 | - **Platform independent** will work with your: YouTube, Soundcloud, WordPress/Blogger, Tumblr, Twitter, Deviant Art, Flickr, Patreon and self hosted websites and many more. 20 | 21 | For installation instructions go to this [handy guide](https://docs.google.com/presentation/d/1Uo37gP_EKJTfhqkV7cU0rKlUX14aXLLdVz9MjTb0VcE/pub?start=false&loop=false&delayms=3000). 22 | 23 | ## For Artists 24 | To make your website or social media site compatible with ProTip all you need is copy/paste a bitcoin address on your page… yes it’s really that simple. 25 | 26 | We also support the use of metatags in the HTML header for added security: 27 | 28 | ``` 29 | 30 | ``` 31 | 32 | ## Special Thanks 33 | Andrew Toth, for releasing his bitcoin wallet under the MIT license. His architecture and code greatly influenced the technical design of ProTip. Further, Toth's code introduced me to the Promise pattern which greatly simplified and improved the performance of ProTip. 34 | [https://github.com/andrewtoth/BitcoinWallet](https://github.com/andrewtoth/BitcoinWallet) 35 | 36 | ## Licence 37 | ProTip a Chrome extension for peer-to-peer crowd-funding. 38 | Copyright (C) 2015 Leo Campbell 39 | 40 | This program is free software: you can redistribute it and/or modify 41 | it under the terms of the GNU General Public License, version 3, as 42 | published by the Free Software Foundation. 43 | 44 | This program is distributed in the hope that it will be useful, 45 | but WITHOUT ANY WARRANTY; without even the implied warranty of 46 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 47 | GNU General Public License for more details. 48 | 49 | You should have received a copy of the GNU General Public License 50 | along with this program. If not, see . -------------------------------------------------------------------------------- /controllers/options.js: -------------------------------------------------------------------------------- 1 | $(function() { 2 | if(!localStorage['proTipInstalled']) { 3 | window.location.replace("install.html"); 4 | } 5 | 6 | if (!localStorage["fiatCurrencyCode"]) { 7 | localStorage["fiatCurrencyCode"] = "USD" 8 | } 9 | 10 | //initialize() 11 | // db = new ydn.db.Storage('protip', schema); 12 | // Going to use *localStore* for the options/preferences because 13 | // IndexedDB isn't built for a single record options array very 14 | // well. When wiping the IndexedDB must remember not to wipe the 15 | // Blacklists. 16 | db = new ydn.db.Storage('protip', schema); 17 | 18 | $('#fiat-currency-select').val(localStorage["fiatCurrencyCode"]); 19 | 20 | $('#fiat-currency-select').change(function() { 21 | $('#ajax-loader').show(); 22 | updateCurrency(this.value, localStorage["fiatCurrencyCode"]).then(function(response){ 23 | 24 | updateGlobalOptionsAmount(response.exchangeRateCoeff, response.newCurrencyCode); 25 | localStorage["fiatCurrencyCode"] = response.newCurrencyCode; 26 | 27 | updateFiatCurrencyCode(); // update any in page USD 28 | $('#ajax-loader').hide(); 29 | }, function(response){ 30 | // If all fails, reset to USD 31 | localStorage["fiatCurrencyCode"] = 'USD'; 32 | preferences.setCurrency(localStorage['fiatCurrencyCode']); 33 | $('#fiat-currency-select').val(localStorage["fiatCurrencyCode"]); 34 | updateFiatCurrencyCode(); 35 | $('#ajax-loader').hide(); 36 | }); 37 | }); 38 | 39 | $('#clear-log').click(function(){clearLog()}); 40 | //updateFiatCurrencyCode(); // update any in page USD 41 | 42 | // initFiatCurrency(); 43 | //updateFiatCurrencyCode(); 44 | initAvailableCurrenciesOptions(); 45 | initDefaultSubscriptionAmountFiat(); 46 | initErrorLog(); 47 | 48 | allowExternalLinks(); 49 | 50 | wallet.restoreAddress().then(function () { 51 | //$('#textAddress').text(wallet.getAddress()); 52 | setExampleMetaTag(); 53 | }); 54 | }); 55 | 56 | function setExampleMetaTag(){ 57 | $('#example-metatag').html( 58 | '' 59 | ); 60 | } 61 | 62 | function resetToDefaults(){ 63 | localStorage['defaultSubscriptionAmountFiat'] = "0.25" 64 | localStorage["fiatCurrencyCode"] = 'USD'; 65 | localStorage["incidentalTotalFiat"] = '1'; 66 | perferences.set({ 67 | currency: 'USD', 68 | 69 | }); 70 | 71 | } 72 | 73 | function initDefaultSubscriptionAmountFiat() { 74 | if (!localStorage['defaultSubscriptionAmountFiat']) { 75 | localStorage['defaultSubscriptionAmountFiat'] = "0.25" 76 | } 77 | 78 | $('#default-subscription-amount-fiat').val(localStorage['defaultSubscriptionAmountFiat']); 79 | 80 | $('#default-subscription-amount-fiat').change(function() { 81 | localStorage['defaultSubscriptionAmountFiat'] = this.value; 82 | }); 83 | } 84 | 85 | function initErrorLog() { 86 | var errors = JSON.parse(localStorage.getItem("errorLog")); 87 | errors.forEach(function(element) { 88 | $('#console-log').append(element + ' '); 89 | }); 90 | } 91 | 92 | function clearLog(){ 93 | localStorage.setItem("errorLog", JSON.stringify([])); 94 | $('#console-log').empty(); 95 | } 96 | 97 | 98 | 99 | -------------------------------------------------------------------------------- /js/weekly-budget-widget.js: -------------------------------------------------------------------------------- 1 | function totalSubscriptionsFiatAmount() { 2 | return new Promise(function(resolve, reject) { 3 | db.values('subscriptions').done(function(records) { 4 | var total = 0.0; 5 | for (var i in records) { 6 | total = total + parseFloat(records[i].amountFiat); 7 | } 8 | resolve(total); 9 | }); 10 | }) 11 | } 12 | 13 | function setBudgetWidget(availableBalanceFiat, bitcoinFeeFiat) { 14 | initFiatCurrency(); 15 | initIncidentalTotalFiat(); 16 | initDefaultSubscriptionAmountFiat(); 17 | localStorage['bitcoinFeeFiat'] = bitcoinFeeFiat; 18 | 19 | totalSubscriptionsFiatAmount().then(function(totalSubscriptionFiat) { 20 | return (function() { 21 | localStorage['subscriptionTotalFiat'] = totalSubscriptionFiat; 22 | $('#subscription-fiat-amount').html(parseFloat(totalSubscriptionFiat).formatMoney(2)); 23 | 24 | var incidentalTotalFiat = setIncidentalTotalFiat(availableBalanceFiat, bitcoinFeeFiat, totalSubscriptionFiat); 25 | //('#incidental-fiat-amount').val(availableIncidentalTotalFiat); 26 | //localStorage['incidentalTotalFiat'] = incidentalTotalFiat; 27 | $('#incidental-fiat-amount').val(localStorage['incidentalTotalFiat']); 28 | 29 | var weeklyTotalFiat = setWeeklyTotalFiat(localStorage['incidentalTotalFiat'], bitcoinFeeFiat, totalSubscriptionFiat); 30 | $('#total-fiat-amount').html(weeklyTotalFiat); // use standard money formattor 31 | currencyManager.amount(availableBalanceFiat).then(function(amountFiat) { 32 | if (weeklyTotalFiat > 0) { 33 | var balanceCoversXWeeks = (amountFiat - weeklyTotalFiat) / weeklyTotalFiat; 34 | if (balanceCoversXWeeks < 0) { 35 | balanceCoversXWeeks = 0 36 | } 37 | $('#balance-covers-weeks').html(balanceCoversXWeeks.toFixed(1)); 38 | } else { 39 | // bypass divide by zero error from wallet with 0 balance 40 | $('#balance-covers-weeks').html(0); 41 | } 42 | }); 43 | 44 | })(totalSubscriptionFiat, availableBalanceFiat, bitcoinFeeFiat) 45 | }); 46 | } 47 | 48 | function setIncidentalTotalFiat(availableBalanceFiat, bitcoinFeeFiat, totalSubscriptionsFiat) { 49 | //var total = parseFloat(localStorage['incidentalTotalFiat']) + parseFloat(bitcoinFeeFiat) + parseFloat(totalSubscriptionsFiat) 50 | var availableIncidentalTotalFiat = parseFloat(availableBalanceFiat) - (parseFloat(bitcoinFeeFiat) + parseFloat(totalSubscriptionsFiat)); 51 | if (availableIncidentalTotalFiat < 0) { 52 | availableIncidentalTotalFiat = 0 53 | } // Handle first time loading with empty wallet. 54 | 55 | //$('#incidental-fiat-amount').val(availableIncidentalTotalFiat); 56 | //$('#incidental-fiat-amount').val(localStorage['incidentalTotalFiat']); 57 | return parseFloat(availableIncidentalTotalFiat); 58 | } 59 | 60 | function setWeeklyTotalFiat(incidentalTotalFiat, bitcoinFeeFiat, totalSubscriptionsFiat) { 61 | var weeklyTotalFiat = parseFloat(bitcoinFeeFiat) + parseFloat(totalSubscriptionsFiat) + parseFloat(incidentalTotalFiat); 62 | weeklyTotalFiat = parseFloat(weeklyTotalFiat).toFixed(2); 63 | if (weeklyTotalFiat < 0) { 64 | weeklyTotalFiat = 0 65 | } // fix initializations problem. 66 | return weeklyTotalFiat; 67 | } 68 | 69 | function initFiatCurrency() { 70 | if (!localStorage["fiatCurrencyCode"]) { 71 | localStorage["fiatCurrencyCode"] = "USD"; 72 | } 73 | } 74 | 75 | function initDefaultSubscriptionAmountFiat() { 76 | if (!localStorage['defaultSubscriptionAmountFiat']) { 77 | localStorage['defaultSubscriptionAmountFiat'] = "0.25"; 78 | } 79 | } 80 | 81 | function initIncidentalTotalFiat(){ 82 | if (!localStorage['incidentalTotalFiat']) { 83 | localStorage['incidentalTotalFiat'] = 0; 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /lib/date.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Date Format 1.2.3 3 | * (c) 2007-2009 Steven Levithan 4 | * MIT license 5 | * 6 | * Includes enhancements by Scott Trenda 7 | * and Kris Kowal 8 | * 9 | * Accepts a date, a mask, or a date and a mask. 10 | * Returns a formatted version of the given date. 11 | * The date defaults to the current date/time. 12 | * The mask defaults to dateFormat.masks.default. 13 | */ 14 | 15 | var dateFormat = function () { 16 | var token = /d{1,4}|m{1,4}|yy(?:yy)?|([HhMsTt])\1?|[LloSZ]|"[^"]*"|'[^']*'/g, 17 | timezone = /\b(?:[PMCEA][SDP]T|(?:Pacific|Mountain|Central|Eastern|Atlantic) (?:Standard|Daylight|Prevailing) Time|(?:GMT|UTC)(?:[-+]\d{4})?)\b/g, 18 | timezoneClip = /[^-+\dA-Z]/g, 19 | pad = function (val, len) { 20 | val = String(val); 21 | len = len || 2; 22 | while (val.length < len) val = "0" + val; 23 | return val; 24 | }; 25 | 26 | // Regexes and supporting functions are cached through closure 27 | return function (date, mask, utc) { 28 | var dF = dateFormat; 29 | 30 | // You can't provide utc if you skip other args (use the "UTC:" mask prefix) 31 | if (arguments.length == 1 && Object.prototype.toString.call(date) == "[object String]" && !/\d/.test(date)) { 32 | mask = date; 33 | date = undefined; 34 | } 35 | 36 | // Passing date through Date applies Date.parse, if necessary 37 | date = date ? new Date(date) : new Date; 38 | if (isNaN(date)) throw SyntaxError("invalid date"); 39 | 40 | mask = String(dF.masks[mask] || mask || dF.masks["default"]); 41 | 42 | // Allow setting the utc argument via the mask 43 | if (mask.slice(0, 4) == "UTC:") { 44 | mask = mask.slice(4); 45 | utc = true; 46 | } 47 | 48 | var _ = utc ? "getUTC" : "get", 49 | d = date[_ + "Date"](), 50 | D = date[_ + "Day"](), 51 | m = date[_ + "Month"](), 52 | y = date[_ + "FullYear"](), 53 | H = date[_ + "Hours"](), 54 | M = date[_ + "Minutes"](), 55 | s = date[_ + "Seconds"](), 56 | L = date[_ + "Milliseconds"](), 57 | o = utc ? 0 : date.getTimezoneOffset(), 58 | flags = { 59 | d: d, 60 | dd: pad(d), 61 | ddd: dF.i18n.dayNames[D], 62 | dddd: dF.i18n.dayNames[D + 7], 63 | m: m + 1, 64 | mm: pad(m + 1), 65 | mmm: dF.i18n.monthNames[m], 66 | mmmm: dF.i18n.monthNames[m + 12], 67 | yy: String(y).slice(2), 68 | yyyy: y, 69 | h: H % 12 || 12, 70 | hh: pad(H % 12 || 12), 71 | H: H, 72 | HH: pad(H), 73 | M: M, 74 | MM: pad(M), 75 | s: s, 76 | ss: pad(s), 77 | l: pad(L, 3), 78 | L: pad(L > 99 ? Math.round(L / 10) : L), 79 | t: H < 12 ? "a" : "p", 80 | tt: H < 12 ? "am" : "pm", 81 | T: H < 12 ? "A" : "P", 82 | TT: H < 12 ? "AM" : "PM", 83 | Z: utc ? "UTC" : (String(date).match(timezone) || [""]).pop().replace(timezoneClip, ""), 84 | o: (o > 0 ? "-" : "+") + pad(Math.floor(Math.abs(o) / 60) * 100 + Math.abs(o) % 60, 4), 85 | S: ["th", "st", "nd", "rd"][d % 10 > 3 ? 0 : (d % 100 - d % 10 != 10) * d % 10] 86 | }; 87 | 88 | return mask.replace(token, function ($0) { 89 | return $0 in flags ? flags[$0] : $0.slice(1, $0.length - 1); 90 | }); 91 | }; 92 | }(); 93 | 94 | // Some common format strings 95 | dateFormat.masks = { 96 | "default": "ddd mmm dd yyyy HH:MM:ss", 97 | shortDate: "m/d/yy", 98 | mediumDate: "mmm d, yyyy", 99 | longDate: "mmmm d, yyyy", 100 | fullDate: "dddd, mmmm d, yyyy", 101 | shortTime: "h:MM TT", 102 | mediumTime: "h:MM:ss TT", 103 | longTime: "h:MM:ss TT Z", 104 | isoDate: "yyyy-mm-dd", 105 | isoTime: "HH:MM:ss", 106 | isoDateTime: "yyyy-mm-dd'T'HH:MM:ss", 107 | isoUtcDateTime: "UTC:yyyy-mm-dd'T'HH:MM:ss'Z'" 108 | }; 109 | 110 | // Internationalization strings 111 | dateFormat.i18n = { 112 | dayNames: [ 113 | "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", 114 | "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" 115 | ], 116 | monthNames: [ 117 | "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", 118 | "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December" 119 | ] 120 | }; 121 | 122 | // For convenience... 123 | Date.prototype.format = function (mask, utc) { 124 | return dateFormat(this, mask, utc); 125 | }; -------------------------------------------------------------------------------- /assets/bootstrap/css/bootstrap-non-responsive-override.css: -------------------------------------------------------------------------------- 1 | /* Template-specific stuff 2 | * 3 | * Customizations just for the template; these are not necessary for anything 4 | * with disabling the responsiveness. 5 | */ 6 | 7 | /* Account for fixed navbar */ 8 | body { 9 | padding-top: 50px; 10 | padding-bottom: 0px; 11 | } 12 | 13 | body, 14 | .navbar-fixed-top, 15 | .navbar-fixed-bottom { 16 | min-width: 600px; 17 | } 18 | 19 | /* Don't let the lead text change font-size. */ 20 | .lead { 21 | font-size: 16px; 22 | } 23 | 24 | /* Finesse the page header spacing */ 25 | .page-header { 26 | margin-bottom: 30px; 27 | } 28 | .page-header .lead { 29 | margin-bottom: 10px; 30 | } 31 | 32 | 33 | /* Non-responsive overrides 34 | * 35 | * Utilitze the following CSS to disable the responsive-ness of the container, 36 | * grid system, and navbar. 37 | */ 38 | 39 | /* Reset the container */ 40 | .container { 41 | width: 970px; 42 | max-width: none !important; 43 | } 44 | 45 | /* Demonstrate the grids */ 46 | .col-xs-4 { 47 | padding-top: 15px; 48 | padding-bottom: 15px; 49 | background-color: #eee; 50 | background-color: rgba(86,61,124,.15); 51 | border: 1px solid #ddd; 52 | border: 1px solid rgba(86,61,124,.2); 53 | } 54 | 55 | .container .navbar-header, 56 | .container .navbar-collapse { 57 | margin-right: 0; 58 | margin-left: 0; 59 | } 60 | 61 | /* Always float the navbar header */ 62 | .navbar-header { 63 | float: left; 64 | } 65 | 66 | /* Undo the collapsing navbar */ 67 | .navbar-collapse { 68 | display: block !important; 69 | height: auto !important; 70 | padding-bottom: 0; 71 | overflow: visible !important; 72 | visibility: visible !important; 73 | } 74 | 75 | .navbar-toggle { 76 | display: none; 77 | } 78 | .navbar-collapse { 79 | border-top: 0; 80 | } 81 | 82 | .navbar-brand { 83 | margin-left: -15px; 84 | } 85 | 86 | /* Always apply the floated nav */ 87 | .navbar-nav { 88 | float: left; 89 | margin: 0; 90 | } 91 | .navbar-nav > li { 92 | float: left; 93 | } 94 | .navbar-nav > li > a { 95 | padding: 15px; 96 | } 97 | 98 | /* Redeclare since we override the float above */ 99 | .navbar-nav.navbar-right { 100 | float: right; 101 | } 102 | 103 | /* Undo custom dropdowns */ 104 | .navbar .navbar-nav .open .dropdown-menu { 105 | position: absolute; 106 | float: left; 107 | background-color: #fff; 108 | border: 1px solid #ccc; 109 | border: 1px solid rgba(0, 0, 0, .15); 110 | border-width: 0 1px 1px; 111 | border-radius: 0 0 4px 4px; 112 | -webkit-box-shadow: 0 6px 12px rgba(0, 0, 0, .175); 113 | box-shadow: 0 6px 12px rgba(0, 0, 0, .175); 114 | } 115 | .navbar-default .navbar-nav .open .dropdown-menu > li > a { 116 | color: #333; 117 | } 118 | .navbar .navbar-nav .open .dropdown-menu > li > a:hover, 119 | .navbar .navbar-nav .open .dropdown-menu > li > a:focus, 120 | .navbar .navbar-nav .open .dropdown-menu > .active > a, 121 | .navbar .navbar-nav .open .dropdown-menu > .active > a:hover, 122 | .navbar .navbar-nav .open .dropdown-menu > .active > a:focus { 123 | color: #fff !important; 124 | background-color: #428bca !important; 125 | } 126 | .navbar .navbar-nav .open .dropdown-menu > .disabled > a, 127 | .navbar .navbar-nav .open .dropdown-menu > .disabled > a:hover, 128 | .navbar .navbar-nav .open .dropdown-menu > .disabled > a:focus { 129 | color: #999 !important; 130 | background-color: transparent !important; 131 | } 132 | 133 | /* Undo form expansion */ 134 | .navbar-form { 135 | float: left; 136 | width: auto; 137 | padding-top: 0; 138 | padding-bottom: 0; 139 | margin-right: 0; 140 | margin-left: 0; 141 | border: 0; 142 | -webkit-box-shadow: none; 143 | box-shadow: none; 144 | } 145 | 146 | /* Copy-pasted from forms.less since we mixin the .form-inline styles. */ 147 | .navbar-form .form-group { 148 | display: inline-block; 149 | margin-bottom: 0; 150 | vertical-align: middle; 151 | } 152 | 153 | .navbar-form .form-control { 154 | display: inline-block; 155 | width: auto; 156 | vertical-align: middle; 157 | } 158 | 159 | .navbar-form .form-control-static { 160 | display: inline-block; 161 | } 162 | 163 | .navbar-form .input-group { 164 | display: inline-table; 165 | vertical-align: middle; 166 | } 167 | 168 | .navbar-form .input-group .input-group-addon, 169 | .navbar-form .input-group .input-group-btn, 170 | .navbar-form .input-group .form-control { 171 | width: auto; 172 | } 173 | 174 | .navbar-form .input-group > .form-control { 175 | width: 100%; 176 | } 177 | 178 | .navbar-form .control-label { 179 | margin-bottom: 0; 180 | vertical-align: middle; 181 | } 182 | 183 | .navbar-form .radio, 184 | .navbar-form .checkbox { 185 | display: inline-block; 186 | margin-top: 0; 187 | margin-bottom: 0; 188 | vertical-align: middle; 189 | } 190 | 191 | .navbar-form .radio label, 192 | .navbar-form .checkbox label { 193 | padding-left: 0; 194 | } 195 | 196 | .navbar-form .radio input[type="radio"], 197 | .navbar-form .checkbox input[type="checkbox"] { 198 | position: relative; 199 | margin-left: 0; 200 | } 201 | 202 | .navbar-form .has-feedback .form-control-feedback { 203 | top: 0; 204 | } 205 | -------------------------------------------------------------------------------- /lib/util.js: -------------------------------------------------------------------------------- 1 | /** 2 | * util.js 3 | * Copyright (c) 2014 Andrew Toth 4 | * 5 | * This program is free software; you can redistribute it and/or modify 6 | * it under the terms of the MIT license. 7 | * 8 | * Utility methods 9 | */ 10 | 11 | (function (window) { 12 | 13 | var util = function () {}, 14 | // Promisified ajax request 15 | request = function (url, type, data) { 16 | return new Promise(function (resolve, reject) { 17 | var req = new XMLHttpRequest(); 18 | req.open((type ? type : 'GET'), url, true); 19 | req.onload = function () { 20 | if (req.status == 200) { 21 | resolve(req.response); 22 | } else { 23 | //$('#unknownErrorAlertLabel').text('Network Error: ' + req.responseURL + ' is unreachable. Please try again later.'); // Hack belongs in a UI layer 24 | //$('#unknownErrorAlert').slideDown(); // Hack belongs in a UI layer 25 | reject(Error(req.response)); 26 | } 27 | } 28 | req.onerror = function (error) { 29 | reject(Error('Error reaching: '+url)); 30 | } 31 | req.ontimeout = function (error) { 32 | reject(Error('Time Out Reading: '+url)); 33 | } 34 | if (type === 'POST') { 35 | req.setRequestHeader('Content-type', 'application/x-www-form-urlencoded'); 36 | } 37 | req.send(data); 38 | }); 39 | // .catch(function(error) { 40 | // Error('Network error'); 41 | // console.log("Failed!", error); 42 | // }); 43 | }; 44 | 45 | util.prototype = { 46 | getJSON: function (url) { 47 | if (typeof chrome !== 'undefined') { 48 | return request(url).then(JSON.parse); 49 | } else { 50 | return ret.message('getJSON', url); 51 | } 52 | }, 53 | 54 | get: function (url) { 55 | return request(url); 56 | }, 57 | 58 | post: function (url, data) { 59 | if (typeof chrome !== 'undefined') { 60 | return request(url, 'POST', data); 61 | } else { 62 | return ret.message('post', {url:url, content:data}); 63 | } 64 | }, 65 | 66 | // Used to send messages from content scripts to add-on scripts and return values to content scripts in Firefox add-on 67 | message: function (name, value) { 68 | return new Promise(function (resolve) { 69 | // 'self' can also be 'addon' depending on how script is injected 70 | var ref = (typeof addon === 'undefined' ? self : addon); 71 | ref.port.on(name, resolve); 72 | ref.port.emit(name, value); 73 | }); 74 | } 75 | }; 76 | 77 | var ret = new util(); 78 | 79 | // Different workarounds to inject content into iFrames for Chrome and Firefox 80 | util.prototype.iframe = function (src) { 81 | return new Promise(function (resolve) { 82 | var iframe = document.createElement('iframe'); 83 | document.body.appendChild(iframe); 84 | iframe.setAttribute('style', 'background-color: transparent; position: absolute; z-index: 2147483647; border: 0px;'); 85 | iframe.setAttribute('allowtransparency', 'true'); 86 | iframe.frameBorder = '0'; 87 | if (typeof chrome !== 'undefined') { 88 | // For Chrome get the HTML content with an ajax call and write it into the document 89 | iframe.src = 'about:blank'; 90 | var request = new XMLHttpRequest(); 91 | request.open('GET', browser.extension.getURL('data/' + src), false); 92 | request.send(null); 93 | var text = request.response; 94 | // Replace css relative locations with absolute locations since Chrome won't find relative 95 | text = text.replace(/css\//g, browser.extension.getURL('') + 'data/css/'); 96 | iframe.contentWindow.document.open('text/html', 'replace'); 97 | iframe.contentWindow.document.write(text); 98 | iframe.contentWindow.document.close(); 99 | resolve(iframe); 100 | } else { 101 | // For Firefox get the encoded HTML and set it to the iFrame's src 102 | ret.message('html', src).then(function (url) { 103 | iframe.src = url; 104 | // Only way to reliably know when the frame is ready in Firefox is by polling 105 | function pollReady() { 106 | if (!iframe.contentWindow.document.getElementById('progress')) { 107 | setTimeout(pollReady, 100); 108 | } else { 109 | resolve(iframe); 110 | } 111 | } 112 | pollReady(); 113 | }); 114 | } 115 | }); 116 | } 117 | 118 | window.util = ret; 119 | 120 | })(window); -------------------------------------------------------------------------------- /views/options.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | ProTip 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 106 | 107 | 110 |
111 | 114 | 115 |
116 | 117 | 118 | 121 |
122 | USD 123 | 124 |
125 | 126 | 132 | 133 | 136 |
137 | 140 |
141 | 142 | 145 |
146 | 147 | 148 |
149 | 150 | 151 | 152 | -------------------------------------------------------------------------------- /lib/promise.min.js: -------------------------------------------------------------------------------- 1 | // Copyright 2013 The Closure Library Authors. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS-IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | !function(){var a,b,c,d;!function(){var e={},f={};a=function(a,b,c){e[a]={deps:b,callback:c}},d=c=b=function(a){function c(b){if("."!==b.charAt(0))return b;for(var c=b.split("/"),d=a.split("/").slice(0,-1),e=0,f=c.length;f>e;e++){var g=c[e];if(".."===g)d.pop();else{if("."===g)continue;d.push(g)}}return d.join("/")}if(d._eak_seen=e,f[a])return f[a];if(f[a]={},!e[a])throw new Error("Could not find module "+a);for(var g,h=e[a],i=h.deps,j=h.callback,k=[],l=0,m=i.length;m>l;l++)"exports"===i[l]?k.push(g={}):k.push(b(c(i[l])));var n=j.apply(this,k);return f[a]=g||n}}(),a("promise/all",["./utils","exports"],function(a,b){"use strict";function c(a){var b=this;if(!d(a))throw new TypeError("You must pass an array to all.");return new b(function(b,c){function d(a){return function(b){f(a,b)}}function f(a,c){h[a]=c,0===--i&&b(h)}var g,h=[],i=a.length;0===i&&b([]);for(var j=0;j= Date.now())){ 26 | // localStorage['weeklyAlarmReminder'] = false; 27 | // callback(false) 28 | // } else { 29 | // // if alarms is for some reason empty [].length, conclude that it has expired. 30 | // localStorage['weeklyAlarmReminder'] = true; 31 | // callback(true) 32 | // } 33 | // }); 34 | // }, 35 | 36 | alarmExpired: function (alarmExpireDate, callback) { 37 | browser.alarms.getAll().then(function(alarms) { 38 | var alarmExpires = new Date(alarmExpireDate); 39 | var now = (new Date).getTime(); 40 | 41 | if(alarms.length > 0 && (alarmExpires > now)){ 42 | localStorage['weeklyAlarmReminder'] = false; 43 | callback(false) 44 | } else { 45 | // if alarms is for some reason empty [].length, conclude that it has expired. 46 | localStorage['weeklyAlarmReminder'] = true; 47 | callback(true) 48 | } 49 | }); 50 | }, 51 | 52 | setAlarm: function(delayInMinutes) { 53 | // is for testing only. 54 | this.cancelAlarm(); 55 | // Globals must be set, some the issues with this method 'not working' is 56 | // because people had ProTip set to manual Remind when testing automatic 57 | // donate. 58 | localStorage['automaticDonate'] = true; 59 | localStorage['manualRemind'] = false; 60 | 61 | var delayInMilliseconds = delayInMinutes * 60 * 1000; // convert to milliseconds 62 | var now = (new Date).getTime(); 63 | // the attribute periodInMinutes means that an expired alarm is replaced with a new alarm with 64 | // scheduledTime = expired_scheduledTime + periodInMinutes; 65 | // It is not possible to workout if the expired alarm ever existed. Therefore 66 | // not possible to determine if the inital alarm has expired and we need to display 67 | // the manual reminders. 68 | // There we are storing the initial expiry date. 69 | localStorage['alarmExpireDate'] = new Date(now + delayInMilliseconds); 70 | 71 | browser.alarms.create(alarmName, { 72 | delayInMinutes: delayInMinutes, periodInMinutes: null 73 | }); 74 | browser.alarms.getAll().then(function(objs){ 75 | var date = new Date(objs[0].scheduledTime); 76 | console.log( 77 | 'New alarm created for ' + date.format() 78 | ); 79 | }); 80 | }, 81 | 82 | listAlarms: function() { 83 | browser.alarms.getAll().then(function(objs){ 84 | for(var i=0;i 2 | 3 | 4 | 5 | ProTip 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 |

39 | 40 | Quick Start 41 |

42 | 43 | 44 |
45 | 48 | 49 |
50 | 51 | 52 | 53 |
54 |
55 |
56 |
57 |

58 | Bitcoin is like email for cash. A public address, like your email, where people can send bitcoins, and a private password to ensure only you can spend received bitcoins. You can trade in your bitcoins at an exchange for your local currency or send them to another address for goods and services. 59 |

60 |
61 |

62 | This is your private key, save it somewhere safe and private. 63 |

64 |

65 | 69 |

70 |
71 |
72 |
73 |
74 |
75 |

76 | Or import existing private key: 77 |

78 | 79 |

80 |

83 | 86 | 89 |

90 | 91 | 92 | 100 | 101 | 102 |

103 |

106 | 109 | 112 | 113 |
114 | 115 | 116 |
117 |

118 |
119 |
120 |
121 |
122 | 123 | 129 | 130 | 131 | 132 | -------------------------------------------------------------------------------- /views/popup.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | ProTip 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 49 | 50 | 51 | 52 |
53 |
54 | 55 | 61 |
62 | 63 | 68 | 72 | 76 | 77 | 101 | 102 | 103 | 108 | 109 | 110 | 111 | 112 | 116 | 119 | 122 | 125 | 128 | 129 | 130 | 131 | 132 |
113 | 114 | Subscribe 115 | 117 | 118 | 120 | USD 121 | 123 | # 124 | 126 | Ignore 127 |
133 | 134 |
135 | Weekly spend: 136 | ( 137 | Total Avail: ~ Loading... 138 | ) 139 |
140 | 141 | 142 | 143 | -------------------------------------------------------------------------------- /views/blacklist.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | ProTip 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 100 | 101 | 102 | 103 |
107 |
108 | 109 | 110 |
111 | 112 | 114 | 117 |
118 | 119 | 120 | 121 |
122 |
123 | 124 | 125 | 128 |
129 | 130 | 132 | 135 |
136 | 137 | 138 | 139 |
140 |
141 | 142 | 143 | 146 |
147 | 148 | 150 | 153 |
154 | 155 | 156 | 157 | 158 | -------------------------------------------------------------------------------- /controllers/blacklist.js: -------------------------------------------------------------------------------- 1 | // ------------------ 2 | // BLACKLISTED URLS CRUD 3 | // ------------------ 4 | function buildBlacklistSelector() { 5 | var select = document.getElementById("remove-from-blacklist"); 6 | 7 | db.values('blacklist').then(function(records) { 8 | select.options.length = 0; 9 | for (var i in records) { 10 | var option = document.createElement("option"); 11 | option.text = records[i].url; 12 | option.value = records[i].url; 13 | select.appendChild(option); 14 | } 15 | }); 16 | } 17 | 18 | function addToBlacklist() { 19 | var element = document.getElementById('add-to-blacklist'); 20 | var url = element.value; 21 | 22 | url = new URL(url); 23 | if (validateURL(url)) { 24 | db.put('blacklist', { 25 | url: url.href 26 | }).then(function() { 27 | element.value = ''; 28 | buildBlacklistSelector(); 29 | }); 30 | } 31 | } 32 | 33 | function removeFromBlacklist() { 34 | var select = document.getElementById("remove-from-blacklist"); 35 | 36 | for (var i = 0; i < select.children.length; i++) { 37 | var child = select.children[i]; 38 | if (child.selected == true) { 39 | db.remove('blacklist', child.value); 40 | select.removeChild(child); // remove from UI 41 | } 42 | } 43 | } 44 | 45 | 46 | // ------------------ 47 | // HOSTNAME CRUD 48 | // ------------------ 49 | function buildBlacklistedHostnameSelector() { 50 | var select = document.getElementById("remove-from-blacklisted-hostname"); 51 | 52 | db.values('blacklistedhostnames').then(function(records) { 53 | select.options.length = 0; 54 | for (var i in records) { 55 | var option = document.createElement("option"); 56 | option.text = records[i].hostname; 57 | option.value = records[i].hostname; 58 | select.appendChild(option); 59 | } 60 | }); 61 | } 62 | 63 | function addToBlacklistedHostname() { 64 | var input = document.getElementById("add-blacklisted-hostnames"); 65 | 66 | try { 67 | var hostname = new URL(input.value).hostname; 68 | } catch (e) { 69 | var hostname = input.value; 70 | } 71 | if (hostname) { //not empty string 72 | db.put('blacklistedhostnames', { 73 | hostname: hostname 74 | }).then(function() { 75 | input.value = ''; 76 | buildBlacklistedHostnameSelector(); 77 | }); 78 | } 79 | } 80 | 81 | function removeFromBlacklistedHostname() { 82 | var select = document.getElementById("remove-from-blacklisted-hostname"); 83 | 84 | for (var i = 0; i < select.children.length; i++) { 85 | var child = select.children[i]; 86 | if (child.selected == true) { 87 | db.remove('blacklistedhostnames', child.value); 88 | select.removeChild(child); // remove from UI 89 | } 90 | } 91 | } 92 | 93 | 94 | // ------------------ 95 | // BTC ADDRESSS CRUD 96 | // ------------------ 97 | function buildBlacklistedBitcoinAddressSelector() { 98 | var select = document.getElementById("remove-from-blacklisted-bitcoin-addresses"); 99 | db.values('blacklistbitcoinaddresses').then(function(records) { 100 | select.options.length = 0; 101 | for (var i in records) { 102 | var option = document.createElement("option"); 103 | option.text = records[i].bitcoinAddress; 104 | option.value = records[i].bitcoinAddress; 105 | select.appendChild(option); 106 | } 107 | }); 108 | } 109 | 110 | function addToBlacklistedBitcoinAddress() { 111 | var input = document.getElementById("add-blacklisted-bitcoin-address"); 112 | 113 | db.put('blacklistbitcoinaddresses', { 114 | bitcoinAddress: input.value 115 | }).then(function() { 116 | input.value = ''; 117 | buildBlacklistedBitcoinAddressSelector(); 118 | }); 119 | } 120 | 121 | function removeFromBlacklistedBitcoinAddress() { 122 | var select = document.getElementById("remove-from-blacklisted-bitcoin-addresses"); 123 | 124 | for (var i = 0; i < select.children.length; i++) { 125 | var child = select.children[i]; 126 | if (child.selected == true) { 127 | db.remove('blacklistbitcoinaddresses', child.value); 128 | select.removeChild(child); // remove from UI 129 | } 130 | } 131 | } 132 | 133 | function validateURL(textval) { 134 | var urlregex = new RegExp( 135 | "^(http|https|ftp)\://([a-zA-Z0-9\.\-]+(\:[a-zA-Z0-9\.&%\$\-]+)*@)*((25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[1-9])\.(25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[1-9]|0)\.(25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[1-9]|0)\.(25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[0-9])|([a-zA-Z0-9\-]+\.)*[a-zA-Z0-9\-]+\.(com|edu|gov|int|mil|net|org|biz|arpa|info|name|pro|aero|coop|museum|[a-zA-Z]{2}))(\:[0-9]+)*(/($|[a-zA-Z0-9\.\,\?\'\\\+&%\$#\=~_\-]+))*$"); 136 | return urlregex.test(textval); 137 | } 138 | 139 | function initialize() { 140 | 141 | db = new ydn.db.Storage('protip', schema); 142 | 143 | // --------------------- 144 | // Blacklisted URLs 145 | // --------------------- 146 | buildBlacklistSelector(); 147 | document.getElementById('remove-selected-btn') 148 | .addEventListener("click", function() { 149 | removeFromBlacklist() 150 | }); 151 | document.getElementById('add-selected-btn') 152 | .addEventListener("click", function() { 153 | addToBlacklist() 154 | }); 155 | 156 | // --------------------- 157 | // Blacklisted Hostnames 158 | // --------------------- 159 | buildBlacklistedHostnameSelector(); 160 | document.getElementById('remove-selected-blacklisted-hostnames-btn') 161 | .addEventListener("click", function() { 162 | removeFromBlacklistedHostname(); 163 | }); 164 | document.getElementById('add-selected-blacklisted-hostnames-btn') 165 | .addEventListener("click", function() { 166 | addToBlacklistedHostname(); 167 | }); 168 | 169 | // ------------------------------ 170 | // Blacklisted Bitcoin Addresses 171 | // ------------------------------ 172 | buildBlacklistedBitcoinAddressSelector(); 173 | document.getElementById('remove-selected-blacklisted-bitcoin-addresses-btn') 174 | .addEventListener("click", function() { 175 | removeFromBlacklistedBitcoinAddress(); 176 | }); 177 | document.getElementById('add-blacklisted-bitcoin-address-btn') 178 | .addEventListener("click", function() { 179 | addToBlacklistedBitcoinAddress(); 180 | }); 181 | 182 | allowExternalLinks(); 183 | 184 | } 185 | 186 | 187 | document.addEventListener("DOMContentLoaded", function() { 188 | if(!localStorage['proTipInstalled']) { 189 | window.location.replace("install.html"); 190 | } 191 | 192 | initialize(); 193 | }); -------------------------------------------------------------------------------- /controllers/subscriptions.js: -------------------------------------------------------------------------------- 1 | function subscriptionLabelCell(record) { 2 | var cell = document.createElement("td"); 3 | 4 | cell.appendChild(document.createElement('div').appendChild( 5 | document.createTextNode(record.title) 6 | )); 7 | var a = document.createElement('a'); 8 | a.style.display = 'block'; 9 | if (record.url) { // url is optional, for manual subscriptions 10 | var linkText = document.createTextNode(record.url.replace(/http(s?):\/\/(www.|)/, '').substring(0, 40)); 11 | } else { 12 | var linkText = document.createTextNode(''); 13 | } 14 | a.appendChild(linkText); 15 | a.className = 'external-link'; 16 | a.href = record.url; 17 | $(a).click(function() { 18 | // In a chrome popup 19 | // the links won't work without the below. Needs to be 20 | // re-ran on every table creation. 21 | browser.tabs.create({ 22 | url: $(this).attr('href') 23 | }); 24 | return false; 25 | }); 26 | cell.appendChild(a); 27 | return cell; 28 | } 29 | 30 | function subscriptionSwitchCellDefaultOn(record) { 31 | var cell = document.createElement("td"); 32 | cell.style.textAlign = 'center'; 33 | 34 | var input = document.createElement("input"); 35 | input.type = "checkbox"; 36 | input.checked = true; // This is the subscription table all rows are subscribed. 37 | input.id = record.bitcoinAddress; 38 | 39 | input.className = 'js-switch'; // Switchery.js 40 | cell.appendChild(input); // append to cell before init Switchery 41 | 42 | new Switchery(input, { 43 | size: 'small' 44 | }); 45 | input.addEventListener("change", (function(record, input) { 46 | return function() { 47 | var parentTr = input.parentElement.parentElement; 48 | if (input.checked != true) { 49 | db.remove('subscriptions', record.bitcoinAddress); 50 | parentTr.style.backgroundColor = '#eeeeee'; 51 | parentTr.style.color = '#aaa'; 52 | } else { 53 | // Allow the unsubscription to be reversed, no need to display confirmation warning 54 | // EG. "Do you really want to delete this subscription?" 55 | db.put('subscriptions', record); 56 | parentTr.style.backgroundColor = 'white'; 57 | parentTr.style.color = '#000'; 58 | } 59 | } 60 | })(record, input)); 61 | 62 | return cell; 63 | } 64 | 65 | function subscriptionEmptyRow(domId){ 66 | var tbody = $('#' + domId); 67 | var row = document.createElement("tr"); 68 | var cell = document.createElement("td"); 69 | cell.setAttribute("colspan",4); 70 | 71 | var text = document.createTextNode('Your subscriptions will be collected here.'); 72 | cell.appendChild(text); 73 | 74 | row.appendChild(cell); 75 | return row; 76 | } 77 | 78 | 79 | function buildRow(record) { 80 | var row = document.createElement("tr"); 81 | 82 | row.appendChild(subscriptionSwitchCellDefaultOn(record)); 83 | row.appendChild(subscriptionLabelCell(record)); 84 | row.appendChild(subscriptionAmountCell(record)); 85 | row.appendChild(subscriptionBitcoinAddressCell(record)); 86 | 87 | return row; 88 | } 89 | 90 | function buildTable(domId) { 91 | var tbody = $('#' + domId); 92 | tbody.empty(); 93 | 94 | db.values('subscriptions').done(function(records) { 95 | if(records.length > 0) { 96 | for (var i in records) { 97 | tbody.append(buildRow(records[i])); 98 | } 99 | } else { 100 | tbody.append(subscriptionEmptyRow()); 101 | } 102 | }); 103 | } 104 | 105 | function manualSubscription() { 106 | db.put('subscriptions', { 107 | amountFiat: $('#manual-amount-fiat').val(), 108 | bitcoinAddress: $('#manualBitcoinAddress').val(), 109 | title: $('#manual-label').val(), 110 | url: $('#manual-url').val() 111 | }).done(function(){ 112 | $('#manual-amount-fiat').val(''); 113 | $('#manualBitcoinAddress').val(''); 114 | $('#manual-label').val(''); 115 | $('#manual-url').val(''); 116 | }); 117 | } 118 | 119 | function validAddress(address){ 120 | // Bitcoinjs doesn't do mixed normal and multisig inputs 121 | // Proposed for version 2. 122 | // Some patches exist: 123 | // https://github.com/OutCast3k/bitcoin-multisig/issues/6 124 | // An explaination is here. 125 | // https://github.com/bitcoinjs/bitcoinjs-lib/issues/417 126 | // Multisig addresses are defined as follows: 127 | // base58(0x05 + [20-byte scripthash] + [4-byte checksum]) 128 | // For testnet, it's 0xC4 instead of 0x05, indeed. 129 | try { 130 | new bitcoin.address.fromBase58Check(address); 131 | } catch (e) { 132 | return false; 133 | } 134 | return true; 135 | } 136 | 137 | function proTipSubscription() { 138 | db.put('subscriptions', { 139 | amountFiat: $('#protip-amount-fiat').val(), 140 | bitcoinAddress: $('#protip-bitcoin-address').val(), 141 | title: $('#protip-label').val(), 142 | url: $('#protip-url').val() 143 | }); 144 | $('#protip-subscription-form').slideUp(); 145 | } 146 | 147 | $(function() { 148 | if(!localStorage['proTipInstalled']) { 149 | window.location.replace("install.html"); 150 | } 151 | 152 | db = new ydn.db.Storage('protip', schema); 153 | 154 | buildTable('subscription-tbody'); //('subscription-table'); 155 | 156 | 157 | 158 | subscriptionTotalFiatAmount().then(function(totalFiatAmount){ 159 | $('#subscription-total-amount').html(totalFiatAmount); 160 | }); 161 | 162 | $('#manual-amount-fiat').attr('placeholder', localStorage['defaultSubscriptionAmountFiat']); 163 | $('#protip-amount-fiat').val(localStorage['defaultSubscriptionAmountFiat']); 164 | 165 | $.validator.addMethod('validBitcoinAddress', function(value, element){return false;},'Invalid bitcoin address'); // A hack. 166 | $('#manualSubscriptionForm').validate({ 167 | rules: { 168 | manualBitcoinAddress: { 169 | validBitcoinAddress: { 170 | depends: function(element) { 171 | return !validAddress(element.value); 172 | } 173 | } 174 | }, 175 | }, 176 | submitHandler: function() { 177 | manualSubscription(); 178 | buildTable('subscription-tbody'); //('subscription-table'); 179 | } 180 | }); 181 | 182 | $.validator.setDefaults({}); 183 | 184 | if (typeof localStorage['showProTipSubscription'] === "undefined") { 185 | localStorage['showProTipSubscription'] = true; 186 | } 187 | 188 | $('#hideProTipSubscription').click(function() { 189 | localStorage['showProTipSubscription'] = false; 190 | $('#protip-subscription-form').fadeOut().slideUp(); 191 | }); 192 | 193 | if(localStorage['showProTipSubscription'] == 'true'){ 194 | $('#protip-subscription-form').show(); 195 | } 196 | 197 | $('#protip-subscribe-btn').click(function() { 198 | proTipSubscription(); 199 | localStorage['showProTipSubscription'] = false; 200 | //$('#protip-subscription-form').fadeOut().slideUp(); 201 | }); 202 | allowExternalLinks(); 203 | updateFiatCurrencyCode(); 204 | }); -------------------------------------------------------------------------------- /lib/preferences.js: -------------------------------------------------------------------------------- 1 | /** 2 | * preferences.js 3 | * Copyright (c) 2014 Andrew Toth 4 | * 5 | * This program is free software; you can redistribute it and/or modify 6 | * it under the terms of the MIT license. 7 | * 8 | * Preferences handles storing and retrieving saved values 9 | */ 10 | 11 | (function (window) { 12 | 13 | var ADDRESS = "wallet.address", 14 | PRIVATE_KEY = "wallet.private_key", 15 | IS_ENCRYPTED = "wallet.is_encrypted", 16 | LAST_BALANCE = "wallet.last_balance", 17 | EXCHANGE_RATE = 'wallet.exchange_rate', 18 | BTC_UNITS = 'wallet.btc_units', 19 | CURRENCY = 'wallet.currency', 20 | preferences = function() {}; 21 | 22 | // ************************************************************ 23 | 24 | function local() { 25 | return new Promise(function (resolve) { 26 | 27 | // Different APIs for Chrome and Firefox 28 | if (typeof chrome !== 'undefined') { 29 | var object = {}; 30 | object[ADDRESS] = ''; 31 | object[PRIVATE_KEY] = ''; 32 | object[IS_ENCRYPTED] = false; 33 | object[LAST_BALANCE] = 0; 34 | object[EXCHANGE_RATE] = 0; 35 | object[BTC_UNITS] = 'BTC'; 36 | object[CURRENCY] = 'USD'; 37 | browser.storage.local.get(object).then(resolve); 38 | } else { 39 | util.message('get').then(function (message) { 40 | if (typeof message[PRIVATE_KEY] === 'undefined') { 41 | message[ADDRESS] = ''; 42 | message[PRIVATE_KEY] = ''; 43 | message[IS_ENCRYPTED] = false; 44 | message[LAST_BALANCE] = 0; 45 | message[EXCHANGE_RATE] = 0; 46 | message[BTC_UNITS] = 'BTC'; 47 | message[CURRENCY] = 'USD'; 48 | return util.message('save', message); 49 | } else { 50 | return message; 51 | } 52 | }).then(function (message) { 53 | resolve(message); 54 | }); 55 | } 56 | }); 57 | } 58 | 59 | function get(pref) { 60 | return function () { 61 | return local().then(function (values) { 62 | return values[pref]; 63 | }); 64 | }; 65 | }; 66 | 67 | function set(key, value) { 68 | return new Promise(function (resolve) { 69 | var object = {}; 70 | object[key] = value; 71 | // Different APIs for Chrome and Firefox 72 | if (typeof chrome !== 'undefined') { 73 | browser.storage.local.set(object, resolve); 74 | } else { 75 | util.message('save', object).then(resolve); 76 | } 77 | }); 78 | }; 79 | 80 | 81 | //******************************************************************************* 82 | 83 | 84 | // function sync() { 85 | // return new Promise(function (resolve) { 86 | // // Different APIs for Chrome and Firefox 87 | // if (typeof chrome !== 'undefined') { 88 | // var object = {}; 89 | // object[ADDRESS] = ''; 90 | // object[PRIVATE_KEY] = ''; 91 | // object[IS_ENCRYPTED] = false; 92 | // object[LAST_BALANCE] = 0; 93 | // object[EXCHANGE_RATE] = 0; 94 | // object[BTC_UNITS] = 'BTC'; 95 | // object[CURRENCY] = 'USD'; 96 | // browser.storage.sync.get(object).then(resolve); 97 | // } else { 98 | // util.message('get').then(function (message) { 99 | // if (typeof message[PRIVATE_KEY] === 'undefined') { 100 | // message[ADDRESS] = ''; 101 | // message[PRIVATE_KEY] = ''; 102 | // message[IS_ENCRYPTED] = false; 103 | // message[LAST_BALANCE] = 0; 104 | // message[EXCHANGE_RATE] = 0; 105 | // message[BTC_UNITS] = 'BTC'; 106 | // message[CURRENCY] = 'USD'; 107 | // return util.message('save', message); 108 | // } else { 109 | // return message; 110 | // } 111 | // }).then(function (message) { 112 | // resolve(message); 113 | // }); 114 | // } 115 | // }); 116 | // } 117 | // 118 | // function get(pref) { 119 | // return function () { 120 | // return sync().then(function (values) { 121 | // return values[pref]; 122 | // }); 123 | // }; 124 | // }; 125 | // 126 | // function set(key, value) { 127 | // return new Promise(function (resolve) { 128 | // var object = {}; 129 | // object[key] = value; 130 | // // Different APIs for Chrome and Firefox 131 | // if (typeof chrome !== 'undefined') { 132 | // browser.storage.sync.set(object, resolve); 133 | // } else { 134 | // util.message('save', object).then(resolve); 135 | // } 136 | // }); 137 | // }; 138 | 139 | preferences.prototype = { 140 | 141 | convert: function (){ 142 | browser.storage.sync.get(["wallet.address"]).then(function(response){ 143 | if(Object.keys(response).length != 0){ 144 | var storedPrefs = [ 145 | "wallet.address", 146 | "wallet.private_key", 147 | "wallet.is_encrypted", 148 | "wallet.last_balance", 149 | "wallet.exchange_rate", 150 | "wallet.btc_units", 151 | "wallet.currency"]; 152 | browser.storage.sync.get(storedPrefs).then(function(proTipPreferences){ 153 | browser.storage.local.set(proTipPreferences).then(function(){ 154 | //browser.storage.sync.clear(); 155 | }); 156 | }); 157 | } 158 | }); 159 | }, 160 | 161 | getAddress: get(ADDRESS), 162 | setAddress: function (address) { 163 | return set(ADDRESS, address); 164 | }, 165 | 166 | getPrivateKey: get(PRIVATE_KEY), 167 | setPrivateKey: function (privateKey) { 168 | return set(PRIVATE_KEY, privateKey); 169 | }, 170 | 171 | getIsEncrypted: get(IS_ENCRYPTED), 172 | setIsEncrypted: function (isEncrypted) { 173 | return set(IS_ENCRYPTED, isEncrypted); 174 | }, 175 | 176 | getLastBalance: get(LAST_BALANCE), 177 | setLastBalance: function (lastBalance) { 178 | return set(LAST_BALANCE, lastBalance); 179 | }, 180 | 181 | getExchangeRate: get(EXCHANGE_RATE), 182 | setExchangeRate: function (exchangeRate) { 183 | return set(EXCHANGE_RATE, exchangeRate); 184 | }, 185 | 186 | getBTCUnits: get(BTC_UNITS), 187 | setBTCUnits: function (btcUnits) { 188 | return set(BTC_UNITS, btcUnits); 189 | }, 190 | 191 | getCurrency: get(CURRENCY), 192 | setCurrency: function (currency) { 193 | return set(CURRENCY, currency).then(function () { 194 | currencyManager.updateExchangeRate(); 195 | }); 196 | } 197 | }; 198 | 199 | window.preferences = new preferences(); 200 | 201 | })(window); 202 | -------------------------------------------------------------------------------- /assets/css/protip.css: -------------------------------------------------------------------------------- 1 | 2 | body { 3 | background-color: rgb(238, 238, 243); 4 | width:100%; 5 | min-width:580px; 6 | } 7 | 8 | .table { 9 | background-color: white; 10 | border-top: solid #eee 1px; 11 | border-bottom: solid #eee 1px; 12 | } 13 | 14 | .external-link { 15 | word-wrap: break-word; 16 | word-break: break-word; 17 | } 18 | 19 | .menu-label { 20 | font-size: 67%; 21 | font-family: Lucida Sans Unicode, Lucida Grande, sans-serif; 22 | text-align: center; 23 | } 24 | .menu-icon { 25 | font-size: 150%; 26 | text-align:center; 27 | } 28 | 29 | #menu-pass-icon { 30 | width: 27px; 31 | height: 19px; 32 | margin-left: 17px; 33 | margin-top: 3px; 34 | background-image: url('../images/pass_icon_default2.png'); 35 | } 36 | 37 | #menu-pass-icon-active { 38 | width: 27px; 39 | height: 19px; 40 | margin-left: 17px; 41 | margin-top: 3px; 42 | background-image: url('../images/pass_icon_active2.png'); 43 | } 44 | 45 | #menu-pass-icon:hover { 46 | width: 27px; 47 | height: 19px; 48 | margin-left: 17px; 49 | margin-top: 3px; 50 | background-image: url('../images/pass_icon_active2.png'); 51 | } 52 | 53 | 54 | 55 | .navbar-default .navbar-nav>li>a { 56 | padding: 8px 0 4px 0; 57 | } 58 | .navbar-nav > li { 59 | width: 64px; 60 | } 61 | .nav-non-responsive { 62 | padding-left: 0px; 63 | margin-left: 0px; 64 | } 65 | /*.alert { 66 | border-radius: 0px; 67 | border-style: none; 68 | margin-top:-1px; 69 | padding:5px; 70 | } 71 | */ 72 | 73 | #full-screen-link{ 74 | margin-top: 34px; 75 | display: inline-block; 76 | font-size: 10px; 77 | } 78 | .table>tbody>tr>td, .table>tbody>tr>th { 79 | padding: 5px; 80 | vertical-align: middle; 81 | } 82 | 83 | .ios-subheading { 84 | margin-left: 15px; 85 | margin-top: 20px; 86 | margin-bottom:4px; 87 | color: #8B8B8B; 88 | font-weight: normal; 89 | font-size: 84%; 90 | } 91 | 92 | .navbar-default .navbar-nav>.active>a, .navbar-default .navbar-nav>.active>a:focus, .navbar-default .navbar-nav>.active>a:hover { 93 | color: #007aff; 94 | background-color: transparent; 95 | } 96 | 97 | .navbar-default .navbar-nav>li>a:hover { 98 | color: #007aff; 99 | } 100 | 101 | .amount-fiat { 102 | width:65px; 103 | } 104 | 105 | .ajax-loader { 106 | display: none; 107 | margin-left:10px; 108 | } 109 | 110 | .ios-red { color: #ff3b30; } 111 | .ios-orange { color: #ff9500; } 112 | .ios-yellow { color: #fc0; } 113 | .ios-green { color: #4cd964; } 114 | .ios-teal { color: #34aadc; } 115 | .ios-blue { color: #007aff; } 116 | .ios-violet { color: #5856d6; } 117 | .ios-pink { color: #ff2d55; } 118 | .ios-mid-gray { color: #8e8e93; } 119 | .ios-light-gray { color: #c7c7cc; } 120 | 121 | .btn-remove { 122 | margin-right:5px; 123 | } 124 | 125 | /*.btn { 126 | background-color: rgba(247, 247, 247, .98); 127 | border: 1px solid #929292; 128 | border-radius: 2px; 129 | -webkit-transition: all; 130 | -moz-transition: all; 131 | transition: all; 132 | -webkit-transition-timing-function: linear; 133 | -moz-transition-timing-function: linear; 134 | transition-timing-function: linear; 135 | -webkit-transition-duration: .2s; 136 | -moz-transition-duration: .2s; 137 | transition-duration: .2s; 138 | } 139 | .btn:active, .btn.active { 140 | color: #fff; 141 | background-color: #929292; 142 | } 143 | .btn-warning { 144 | color: #fc0; 145 | border: solid 1px #fc0; 146 | } 147 | 148 | .btn-success { 149 | color: #4cd964; 150 | border: solid 1px #4cd964; 151 | } 152 | 153 | .btn-danger { 154 | color: #ff3b30; 155 | border-color: #ff3b30; 156 | } 157 | 158 | .btn-primary { 159 | color: #007aff; 160 | border: 1px solid #007aff; 161 | } 162 | .btn-primary:active, .btn-primary.active { 163 | background-color: #0062cc; 164 | border: 1px solid #0062cc; 165 | } 166 | 167 | .btn-positive { 168 | color: #fff; 169 | border: 1px solid #4cd964; 170 | } 171 | .btn-positive:active, .btn-positive.active { 172 | background-color: #2ac845; 173 | border: 1px solid #2ac845; 174 | } 175 | 176 | .btn-negative { 177 | color: #fff; 178 | border: 1px solid #dd524d; 179 | } 180 | .btn-negative:active, .btn-negative.active { 181 | background-color: #cf2d28; 182 | border: 1px solid #cf2d28; 183 | } 184 | 185 | .btn-outlined { 186 | background-color: transparent; 187 | } 188 | .btn-outlined.btn-primary { 189 | color: #007aff; 190 | } 191 | .btn-outlined.btn-positive { 192 | color: #4cd964; 193 | } 194 | .btn-outlined.btn-negative { 195 | color: #dd524d; 196 | } 197 | .btn-outlined.btn-primary:active, .btn-outlined.btn-positive:active, .btn-outlined.btn-negative:active { 198 | color: #fff; 199 | } 200 | 201 | .btn-link { 202 | color: #007aff; 203 | background-color: transparent; 204 | border: none; 205 | } 206 | .btn-link:active, .btn-link.active { 207 | color: #0062cc; 208 | background-color: transparent; 209 | } 210 | 211 | .btn .badge { 212 | background-color: rgba(0, 0, 0, .15); 213 | } 214 | .btn .badge.badge-inverted { 215 | background-color: transparent; 216 | } 217 | .btn:active .badge { 218 | color: #fff; 219 | } 220 | */ 221 | .remove-button-select { 222 | position: relative; 223 | top: -14px; 224 | } 225 | 226 | .ios-section { 227 | background-color:#fff; 228 | padding:10px; 229 | border-bottom: solid #ccc 1px; 230 | margin-bottom:10px; 231 | } 232 | 233 | .remove-icon { 234 | position: relative; 235 | top: 3px; 236 | padding-right: 5px; 237 | font-size: 140%; 238 | } 239 | 240 | .remove-icon:hover { 241 | cursor: pointer; 242 | } 243 | 244 | .option-subsection { 245 | border-bottom: 1px solid #ddd; 246 | padding:10px 5px 10px 5px; 247 | } 248 | 249 | #txtAddress{ 250 | width:300px; 251 | } 252 | 253 | #qr { 254 | cursor: pointer; 255 | } 256 | 257 | /*.progress { 258 | width: 24px; 259 | margin-bottom:3px; 260 | margin-top:3px; 261 | height: 17px; 262 | background-image: url('../images/coffee_cup_background2.png'); 263 | background-color:transparent; 264 | } 265 | 266 | .progress-bar { 267 | background-image: url('../images/coffee_cup_progress.png'); 268 | background-color:transparent; 269 | } 270 | 271 | .coffee-cup { 272 | height: 14px; 273 | } 274 | */ 275 | .subscription-input { 276 | padding: 2px 5px 2px 5px; 277 | } 278 | 279 | .error { 280 | color:red; 281 | } 282 | 283 | .ios-element-divider { 284 | border-bottom: solid #eee 1px; 285 | margin-bottom: 7px; 286 | padding-bottom: 7px; 287 | } 288 | 289 | label { 290 | font-weight: normal; 291 | } 292 | 293 | .blockchain-address { 294 | font-family: "Lucida Console", Monaco, monospace; 295 | } 296 | 297 | .percent-text { 298 | color: #737373; 299 | font-size: 80%; 300 | } 301 | 302 | .fiat-amount { 303 | text-align: center; 304 | } 305 | 306 | .tab-content { 307 | min-height: 200px; 308 | padding: 10px; 309 | border-right: 1px #ddd solid; 310 | border-bottom: 1px #ddd solid; 311 | border-left: 1px #ddd solid; 312 | } 313 | 314 | .new-password-box { 315 | border: 1px #ddd solid; 316 | display:none; 317 | } 318 | 319 | .ios-subheading-within-panel { 320 | position: relative; 321 | top: -10px; 322 | font-size: 140%; 323 | } 324 | 325 | #budget-table > tbody > tr > td > hr { 326 | padding: 1px; 327 | margin-top:0px; 328 | margin-bottom:0px; 329 | } 330 | 331 | #dismiss-manual-reminder-popover:hover { 332 | cursor: pointer; 333 | color: black; 334 | text-decoration: none; 335 | } 336 | 337 | .tooltip-inner { 338 | background-color: #faebcc; 339 | color:#333; 340 | } 341 | .tooltip.right .tooltip-arrow { 342 | border-right-color: #faebcc; 343 | } 344 | 345 | #automatic-donate-row:hover { 346 | background-color: red; 347 | } 348 | 349 | #manual-remind-row:hover { 350 | background-color: red; 351 | } 352 | 353 | .label { 354 | border-radius: 6px; 355 | } 356 | 357 | 358 | 359 | 360 | -------------------------------------------------------------------------------- /views/feedback.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | ProTip 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 110 | 124 | 125 | 126 |
127 |
128 | 129 |
130 | The Musical Mash 131 |
http://twitter.com/MashMash 132 |
133 |
134 | 135 |
136 | Please come to our awesome gig, will be debuting our latest album http://youtu.be/ykQLO4f2fI8 137 |
138 |
Cool, can I bring a friend?
139 | 140 |
141 | 142 |
143 | The Musical Mash 144 |
http://twitter.com/MashMash 145 |
146 |
147 | 148 |
149 | Of course you can bring a friend :). Would you like discount ticket for your friend? http://yo.yo/f2fI8 150 |
151 | 152 |
153 |
154 | 155 |
156 | New City Architecture 157 |
https://www.youtube.com/user/Aloon 158 |
159 |
160 | 161 |
Preview our new book, "From Arcologies to the Kowloon Walled City". PreOrder now. http://amzn.to/1eH9qG6
162 | 163 | 164 |
165 |
Send ($0.05)
166 |
167 |
168 |
169 |
170 |
171 | 172 | 173 | 174 | 175 | 176 | 177 | -------------------------------------------------------------------------------- /views/passes.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | ProTip 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 93 | 94 | 95 | 129 | 130 | 131 |
CREATE NEW PASS Planned Development
132 |
133 | 134 |
135 | 136 | 137 | 140 | 143 | 144 | 145 | 148 | 151 | 152 | 153 | 156 | 159 | 160 | 161 | 164 | 174 | 175 | 176 | 179 | 185 | 186 | 187 | 188 | 196 | 197 |
138 | 139 | 141 | 142 |
146 | 147 | 149 | 150 |
154 | 155 | 157 | 158 |
162 | 163 | 165 | 166 |
167 |
168 |
$
169 | 170 |
171 |
172 | 173 |
177 | 178 | 180 | 184 |
189 |

190 | Easy self hosting solutions are under development. For example, a Wordpress plugin. 191 |

192 |

193 | 194 |

195 |
198 | 199 | 200 | 201 |
202 | 203 | 204 |

Preview

205 |
206 | 207 |
208 | 209 |

Embed this code

210 | 214 | 215 | 216 |

Preview of Useage

217 |
218 | 219 |
220 | 221 |
222 | 223 | 224 | 225 | -------------------------------------------------------------------------------- /js/weekly-browsing-widget.js: -------------------------------------------------------------------------------- 1 | function browseLabelCell(record) { 2 | var cell = document.createElement("td"); 3 | cell.style.wordBreak = 'break-all'; 4 | 5 | if (record.title) { 6 | var title = document.createTextNode(record.title) 7 | } else { 8 | var title = document.createTextNode('') 9 | } 10 | 11 | cell.appendChild(document.createElement('div').appendChild(title)); 12 | var a = document.createElement('a'); 13 | a.style.display = 'block'; 14 | if (record.url) { // url is optional, for manual subscriptions 15 | var linkText = document.createTextNode(record.url.replace(/http(s?):\/\/(www.|)/, '').substring(0, 40)); 16 | } else { 17 | var linkText = document.createTextNode(''); 18 | } 19 | a.appendChild(linkText); 20 | a.className = 'external-link'; 21 | a.href = record.url; 22 | $(a).click(function() { 23 | // In a chrome popup 24 | // the links won't work without the below. Needs to be 25 | // re-ran on every table creation. 26 | browser.tabs.create({ 27 | url: $(this).attr('href') 28 | }); 29 | return false; 30 | }); 31 | cell.appendChild(a); 32 | return cell; 33 | } 34 | 35 | function addToBlacklist(url) { 36 | db.put('blacklist', { 37 | url: url 38 | }); 39 | db.remove('sites', url); 40 | } 41 | 42 | function subscriptionTotalFiatAmount(domIdOutput) { 43 | return new Promise(function (resolve, reject) { 44 | db.values('subscriptions').done(function(records) { 45 | var total = 0.0; 46 | for (var i in records) { 47 | total = total + parseFloat(records[i].amountFiat); 48 | }; 49 | localStorage['subscriptionTotalFiatAmount'] = total.toFixed(2); 50 | $('#' + domIdOutput).html(total.toFixed(2)); 51 | resolve(total.toFixed(2)); 52 | }); 53 | }); 54 | } 55 | 56 | function subscriptionAmountCell(record) { 57 | var input = document.createElement("input"); 58 | input.type = "number"; 59 | input.setAttribute('step', '0.01'); 60 | input.setAttribute('min', '0.01'); 61 | input.className = "amount-fiat subscription-input"; 62 | input.value = record.amountFiat; 63 | input.id = record.bitcoinAddress; 64 | 65 | input.addEventListener("change", function() { 66 | record.amountFiat = this.value; 67 | db.put('subscriptions', record).done(function(){ 68 | subscriptionTotalFiatAmount().then(function(totalFiatAmount){ 69 | $('#subscription-total-amount').html(totalFiatAmount); 70 | $('#subscription-total-amount-container').effect("highlight", {color: 'rgb(100, 189, 99)'}, 100); // cannot get this to work 'out of the box'. 71 | }); 72 | }); 73 | }); 74 | 75 | var cell = document.createElement("td"); 76 | cell.appendChild(input); 77 | return cell; 78 | } 79 | 80 | function subscriptionBitcoinAddressCell(record) { 81 | var cell = document.createElement("td"); 82 | cell.className = 'blockchain-address'; 83 | cell.appendChild( 84 | document.createTextNode( 85 | record.bitcoinAddress.substring(0, 7) + '...' 86 | ) 87 | ); 88 | return cell; 89 | } 90 | 91 | function subscriptionSwitchCell(record) { 92 | var cell = document.createElement("td"); 93 | cell.style.textAlign = 'center'; 94 | 95 | var input = document.createElement("input"); 96 | input.type = "checkbox"; 97 | input.checked = false; 98 | input.id = record.bitcoinAddress; 99 | 100 | input.className = 'js-switch'; // Switchery.js 101 | cell.appendChild(input); // append to cell before init Switchery 102 | 103 | new Switchery(input, { 104 | size: 'small' 105 | }); 106 | input.addEventListener("change", (function(record, input) { 107 | return function() { 108 | var parentTr = input.parentElement.parentElement; 109 | if (input.checked != true) { 110 | db.remove('subscriptions', record.bitcoinAddress); 111 | parentTr.style.backgroundColor = 'white'; 112 | parentTr.style.color = '#000'; 113 | } else { 114 | // Allow the unsubscription to be reversed, no need to display confirmation warning 115 | // EG. "Do you really want to delete this subscription?" 116 | record.bitcoinAddress = record.bitcoinAddress; 117 | record.amountFiat = localStorage['defaultSubscriptionAmountFiat']; 118 | db.put('subscriptions', record); 119 | parentTr.style.backgroundColor = '#eeeeee'; 120 | parentTr.style.color = '#aaa'; 121 | } 122 | } 123 | })(record, input)); 124 | 125 | return cell; 126 | } 127 | 128 | function browseIgnoreCell(record) { 129 | var cell = document.createElement("td"); 130 | 131 | var removeIcon = document.createElement('span') 132 | removeIcon.setAttribute('title', "Ignore"); 133 | removeIcon.className = 'glyphicon glyphicon-remove remove-icon ios-orange'; 134 | 135 | removeIcon.onclick = function() { 136 | addToBlacklist(record.url); 137 | $(this.parentElement.parentElement).remove() 138 | $('#blacklist').effect("highlight", { 139 | color: '#fc0;' 140 | }, 1000); 141 | } 142 | cell.style.textAlign = 'center'; 143 | cell.appendChild(removeIcon); 144 | return cell; 145 | } 146 | 147 | function browseAmountCell(record) { 148 | var incidentalAmount = parseFloat(localStorage["incidentalTotalFiat"]); 149 | 150 | var slice = (record.timeOnPage / localStorage['totalTime']).toFixed(2); 151 | var amountMoney = (slice * incidentalAmount).toFixed(2); 152 | if(!amountMoney){amountMoney = '0.00'} 153 | var text = document.createTextNode(amountMoney); 154 | 155 | var cell = document.createElement("td"); 156 | cell.className = 'fiat-amount'; 157 | cell.appendChild(text); 158 | return cell; 159 | } 160 | 161 | function timeAmountCell(record) { 162 | var min = parseInt(record.timeOnPage) / 60; 163 | var text = document.createTextNode(min.toFixed(1)); 164 | var cell = document.createElement("td"); 165 | cell.className = 'time-on-page'; 166 | cell.appendChild(text); 167 | cell.style.textAlign = 'center'; 168 | return cell; 169 | } 170 | 171 | function buildRow(record) { 172 | var row = document.createElement("tr"); 173 | 174 | row.appendChild(subscriptionSwitchCell(record)); 175 | row.appendChild(browseLabelCell(record)); 176 | row.appendChild(timeAmountCell(record)); 177 | row.appendChild(browseAmountCell(record)); 178 | row.appendChild(subscriptionBitcoinAddressCell(record)); 179 | row.appendChild(browseIgnoreCell(record)); 180 | 181 | return row; 182 | } 183 | 184 | function showEmptyTableNotice(domId){ 185 | var tbody = $('#' + domId); 186 | var row = document.createElement("tr"); 187 | var cell = document.createElement("td"); 188 | cell.setAttribute("colspan",5); 189 | 190 | var text = document.createTextNode('Sites containing Bitcoin addresses will be collected here.'); 191 | cell.appendChild(text); 192 | 193 | row.appendChild(cell); 194 | tbody.append(row); 195 | } 196 | 197 | function buildBrowsingTable(domId) { 198 | var tbody = $('#' + domId); 199 | tbody.empty(); 200 | 201 | // The Sites records should be filtered, 202 | // first, by removing the subscriptions 203 | // second, by tallying up the total amount 204 | // of time spent overall. 205 | 206 | // Remove subscriptions for daily browsing. 207 | db.values('subscriptions').done(function(records) { 208 | for (var i in records) { 209 | db.remove('sites', records[i].url); 210 | } 211 | }); 212 | 213 | // Update the totalTime, this global needs to be updated frequently to allow 214 | // the correct values for the percentages of each site. 215 | db.values('sites').done(function(records) { 216 | localStorage['totalTime'] = 0; 217 | for (var i in records) { 218 | if (records[i].timeOnPage) { // it is possible to get a new site record which doesn't yet have a timeOnPage. 219 | localStorage['totalTime'] = parseInt(localStorage['totalTime']) + parseInt(records[i].timeOnPage); 220 | } 221 | }; 222 | }); 223 | 224 | db.from('sites').order('timeOnPage').reverse().list(10).done(function(records) { 225 | if(records.length < 1){ 226 | showEmptyTableNotice('browsing-table'); 227 | } 228 | for (var i in records) { 229 | tbody.append(buildRow(records[i])); 230 | } 231 | }); 232 | } -------------------------------------------------------------------------------- /js/ui-utils.js: -------------------------------------------------------------------------------- 1 | function allowExternalLinks() { 2 | $('.external-link').each(function(i, obj) { 3 | // In a chrome popup page the links won't work without the below. 4 | $(obj).click(function() { 5 | browser.tabs.create({ 6 | url: obj.href 7 | }); 8 | }); 9 | }); 10 | } 11 | 12 | 13 | function initAvailableCurrenciesOptions() { 14 | if (!localStorage["fiatCurrencyCode"]) { 15 | localStorage["fiatCurrencyCode"] = "USD" 16 | } 17 | 18 | var currencies = currencyManager.getAvailableCurrencies(); 19 | for (var i in currencies) { 20 | var option = $('')[0]; 21 | $('#fiat-currency-select').append(option); 22 | } 23 | 24 | $('#fiat-currency-select').val(localStorage["fiatCurrencyCode"]); 25 | updateFiatCurrencyCode(); // update any in page USD 26 | 27 | // $('#fiat-currency-select').change(function() { 28 | // preferences.setCurrency($(this.selectedOptions).val()); 29 | // localStorage["fiatCurrencyCode"] = this.value; 30 | // updateFiatCurrencyCode(); 31 | // }); 32 | } 33 | 34 | 35 | function updateCurrency(newCurrencyCode, oldFiatCurrencyCode) { 36 | //var oldFiatCurrencyCode = localStorage["fiatCurrencyCode"]; 37 | var exchangeRateCoeff; 38 | 39 | //return preferences.setCurrency(oldFiatCurrencyCode).then(function(){ 40 | 41 | return preferences.setCurrency(newCurrencyCode).then(function(){ 42 | return currencyManager.updateExchangeRate(); 43 | }).then(function(exchangeToBTC){ 44 | return new Promise(function (resolve, reject) { 45 | if (oldFiatCurrencyCode == 'BTC' && newCurrencyCode == 'mBTC') { 46 | // from BTC to mBTC 47 | exchangeRateCoeff = 1000; 48 | resolve({exchangeRateCoeff: exchangeRateCoeff, newCurrencyCode: newCurrencyCode}); 49 | } else if (oldFiatCurrencyCode == 'mBTC' && newCurrencyCode == 'BTC') { 50 | // from mBTC to BTC 51 | exchangeRateCoeff = 1/1000; 52 | resolve({exchangeRateCoeff: exchangeRateCoeff, newCurrencyCode: newCurrencyCode}); 53 | } else if (newCurrencyCode == 'BTC') { 54 | // from fiat to BTC 55 | currencyManager.getExchangeRate(oldFiatCurrencyCode).then(function(rateObj) { 56 | exchangeRateCoeff = 1/rateObj[Object.keys(rateObj)[0]]['averages']['day']; 57 | resolve({exchangeRateCoeff: exchangeRateCoeff, newCurrencyCode: newCurrencyCode}); 58 | }); 59 | } else if (newCurrencyCode == 'mBTC'){ 60 | currencyManager.getExchangeRate(oldFiatCurrencyCode).then(function(rateObj) { 61 | // from fiat to mBTC 62 | exchangeRateCoeff = 1000/rateObj[Object.keys(rateObj)[0]]['averages']['day']; 63 | resolve({exchangeRateCoeff: exchangeRateCoeff, newCurrencyCode: newCurrencyCode}); 64 | }); 65 | } else if (oldFiatCurrencyCode == 'BTC') { 66 | currencyManager.getExchangeRate(newCurrencyCode).then(function(rateObj) { 67 | // from BTC to fiat 68 | exchangeRateCoeff = rateObj[Object.keys(rateObj)[0]]['averages']['day']; 69 | resolve({exchangeRateCoeff: exchangeRateCoeff, newCurrencyCode: newCurrencyCode}); 70 | }); 71 | } else if (oldFiatCurrencyCode == 'mBTC') { 72 | currencyManager.getExchangeRate(newCurrencyCode).then(function(rateObj) { 73 | // from mBTC to fiat 74 | exchangeRateCoeff = rateObj[Object.keys(rateObj)[0]]['averages']['day']/1000; 75 | resolve({exchangeRateCoeff: exchangeRateCoeff, newCurrencyCode: newCurrencyCode}); 76 | }); 77 | } else { // fiat to fiat 78 | resolve(util.getJSON('http://api.fixer.io/latest?symbols=' + newCurrencyCode + ',' + oldFiatCurrencyCode) 79 | .then(function(ratesData){ 80 | if(newCurrencyCode == 'EUR' || oldFiatCurrencyCode == 'EUR'){ 81 | if(oldFiatCurrencyCode == 'EUR'){ 82 | return {exchangeRateCoeff: ratesData.rates[Object.keys(ratesData.rates)[0]], newCurrencyCode: newCurrencyCode} 83 | } else { 84 | return {exchangeRateCoeff: 1/ratesData.rates[Object.keys(ratesData.rates)[0]], newCurrencyCode: newCurrencyCode} 85 | } 86 | } else { 87 | var exchangeRateCoeff = ratesData.rates[newCurrencyCode] / ratesData.rates[oldFiatCurrencyCode]; 88 | return {exchangeRateCoeff: exchangeRateCoeff, newCurrencyCode: newCurrencyCode}; 89 | } 90 | }, function(error){ 91 | //debugger; 92 | console.log(error); 93 | })); 94 | } 95 | }); 96 | }); 97 | } 98 | 99 | function exportSubscriptions(){ 100 | db.values('subscriptions').done(function(records) { 101 | var savedSubscriptions = []; 102 | for (var i in records) { 103 | savedSubscriptions.push(records[i]) 104 | } 105 | savedSubscriptions = JSON.stringify(savedSubscriptions); 106 | console.log(savedSubscriptions); 107 | }); 108 | } 109 | 110 | function importSubscriptions(subscriptionRecords){ 111 | //var subscriptionRecords = JSON.parse(subscriptionRecords); 112 | for(var i in subscriptionRecords){ 113 | db.put('subscriptions', subscriptionRecords[i]); 114 | } 115 | } 116 | 117 | function updateGlobalOptionsAmount(exchangeRateCoeff, newCurrencyCode){ 118 | currencyManager.getSymbol(newCurrencyCode).then(function(currencyDetails){ 119 | var roundingFactor = currencyDetails[2]; 120 | if(roundingFactor){ 121 | localStorage['defaultSubscriptionAmountFiat'] = parseFloat(exchangeRateCoeff * localStorage['defaultSubscriptionAmountFiat']).toFixed(roundingFactor); 122 | localStorage['incidentalTotalFiat'] = parseFloat(exchangeRateCoeff * localStorage['incidentalTotalFiat']).toFixed(roundingFactor); 123 | } else { 124 | localStorage['defaultSubscriptionAmountFiat'] = exchangeRateCoeff * localStorage['defaultSubscriptionAmountFiat']; 125 | localStorage['incidentalTotalFiat'] = exchangeRateCoeff * localStorage['incidentalTotalFiat']; 126 | } 127 | $('#default-subscription-amount-fiat').val(localStorage['defaultSubscriptionAmountFiat']); 128 | //localStorage['incidentalTotalFiat'] = exchangeRateCoeff * localStorage['incidentalTotalFiat']; 129 | db.values('subscriptions').done(function(records) { 130 | for (var i in records) { 131 | if(roundingFactor){ 132 | records[i].amountFiat = parseFloat(exchangeRateCoeff * records[i].amountFiat).toFixed(roundingFactor); 133 | if(records[i].amountFiat < 0.05){ records[i].amountFiat = 0.05 } 134 | } else { 135 | records[i].amountFiat = exchangeRateCoeff * records[i].amountFiat; 136 | } 137 | } 138 | db.put('subscriptions', records); 139 | }); 140 | }); 141 | } 142 | 143 | function updateFiatCurrencyCode() { 144 | currencyManager.getSymbol().then(function(symbol){ 145 | $.each($(".fiat-code"), function(key, element) { 146 | element.textContent = symbol[0]; 147 | }); 148 | }); 149 | } 150 | 151 | function createQRCodeCanvas(text) { 152 | var sizeMultiplier = 4; 153 | var typeNumber; 154 | var lengthCalculation = text.length * 8 + 12; 155 | if (lengthCalculation < 72) { 156 | typeNumber = 1; 157 | } else if (lengthCalculation < 128) { 158 | typeNumber = 2; 159 | } else if (lengthCalculation < 208) { 160 | typeNumber = 3; 161 | } else if (lengthCalculation < 288) { 162 | typeNumber = 4; 163 | } else if (lengthCalculation < 368) { 164 | typeNumber = 5; 165 | } else if (lengthCalculation < 480) { 166 | typeNumber = 6; 167 | } else if (lengthCalculation < 528) { 168 | typeNumber = 7; 169 | } else if (lengthCalculation < 688) { 170 | typeNumber = 8; 171 | } else if (lengthCalculation < 800) { 172 | typeNumber = 9; 173 | } else if (lengthCalculation < 976) { 174 | typeNumber = 10; 175 | } 176 | var qrcode = new QRCode(typeNumber, QRCode.ErrorCorrectLevel.H); 177 | qrcode.addData(text); 178 | qrcode.make(); 179 | var width = qrcode.getModuleCount() * sizeMultiplier; 180 | var height = qrcode.getModuleCount() * sizeMultiplier; 181 | // create canvas element 182 | var canvas = document.createElement('canvas'); 183 | var scale = 10.0; 184 | canvas.width = width * scale; 185 | canvas.height = height * scale; 186 | canvas.style.width = width + 'px'; 187 | canvas.style.height = height + 'px'; 188 | var ctx = canvas.getContext('2d'); 189 | ctx.scale(scale, scale); 190 | // compute tileW/tileH based on width/height 191 | var tileW = width / qrcode.getModuleCount(); 192 | var tileH = height / qrcode.getModuleCount(); 193 | // draw in the canvas 194 | for (var row = 0; row < qrcode.getModuleCount(); row++) { 195 | for (var col = 0; col < qrcode.getModuleCount(); col++) { 196 | ctx.fillStyle = qrcode.isDark(row, col) ? "#000000" : "#ffffff"; 197 | ctx.fillRect(col * tileW, row * tileH, tileW, tileH); 198 | } 199 | } 200 | return canvas; 201 | } 202 | -------------------------------------------------------------------------------- /controllers/install.js: -------------------------------------------------------------------------------- 1 | 2 | function initDefaultBlacklistedHostnames() { 3 | var hostnames = [{ 4 | hostname: "www.bitfinex.com" 5 | }, { 6 | hostname: "btc-e.com" 7 | }, { 8 | hostname: "www.bitstamp.net" 9 | }, { 10 | hostname: "blockexplorer.com" 11 | }, { 12 | hostname: "insight.bitpay.com" 13 | }, { 14 | hostname: "blockchain.info" 15 | }, { 16 | hostname: "google.com" 17 | }, { 18 | hostname: "google.co.uk" 19 | }] 20 | 21 | db.put('blacklistedhostnames', hostnames); 22 | } 23 | 24 | function initSponsors() { 25 | var sponsorTwitterHandles = [{ 26 | twitterhandle: "KiskaZilla" 27 | }, { 28 | twitterhandle: "mrchrisellis" 29 | }, { 30 | twitterhandle: "victoriavaneyk" 31 | }, { 32 | twitterhandle: "shop_rocket" 33 | }, { 34 | twitterhandle: "HardBTC" 35 | }, { 36 | twitterhandle: "M3metic" 37 | }, { 38 | twitterhandle: "brennannovak" 39 | }, { 40 | twitterhandle: "Bittylicious_" 41 | }, { 42 | twitterhandle: "agoodman1111" 43 | }, { 44 | twitterhandle: "Calem_Smith" 45 | }, { 46 | twitterhandle: "makevoid" 47 | }, { 48 | twitterhandle: "bitcoinpotato" 49 | }, { 50 | twitterhandle: "MadBitcoins" 51 | }, { 52 | twitterhandle: "LesleeFrost" 53 | }, { 54 | twitterhandle: "prestonjbyrne" 55 | }, { 56 | twitterhandle: "j32804" 57 | }, { 58 | twitterhandle: "NixiePixel" 59 | }, { 60 | twitterhandle: "OllyNewport" 61 | }, { 62 | twitterhandle: "mormo_music" 63 | }, { 64 | twitterhandle: "MinuteEarth" 65 | }, { 66 | twitterhandle: "rhian_is" 67 | }, { 68 | twitterhandle: "fliptopbox13" 69 | }, { 70 | twitterhandle: "TomerKantor" 71 | }, { 72 | twitterhandle: "louissschang" 73 | }, { 74 | twitterhandle: "ProofOfWork" 75 | }, { 76 | twitterhandle: "LaraCelenza" 77 | }]; 78 | 79 | db.put('sponsors', sponsorTwitterHandles); 80 | } 81 | 82 | function initFiatCurrency() { 83 | if (!localStorage["fiatCurrencyCode"]) { 84 | localStorage["fiatCurrencyCode"] = "USD" 85 | } 86 | 87 | var currencies = currencyManager.getAvailableCurrencies(); 88 | for (var i in currencies) { 89 | var option = $('')[0]; 90 | $('#fiat-currency-select').append(option); 91 | } 92 | 93 | $('#fiat-currency-select').val(localStorage["fiatCurrencyCode"]); 94 | updateFiatCurrencyCode(); // update any in page USD 95 | 96 | $('#fiat-currency-select').change(function() { 97 | $('#ajax-loader').show(); 98 | var old = 'f00'; 99 | updateCurrency(this.value, localStorage["fiatCurrencyCode"]).then(function(response){ 100 | 101 | updateGlobalOptionsAmount(response.exchangeRateCoeff, response.newCurrencyCode); 102 | localStorage["fiatCurrencyCode"] = response.newCurrencyCode; 103 | 104 | updateFiatCurrencyCode(); // update any in page USD 105 | $('#ajax-loader').hide(); 106 | }, function(response){ 107 | // If all fails, reset to USD 108 | localStorage["fiatCurrencyCode"] = 'USD'; 109 | preferences.setCurrency(localStorage['fiatCurrencyCode']); 110 | $('#fiat-currency-select').val(localStorage["fiatCurrencyCode"]); 111 | updateFiatCurrencyCode(); 112 | $('#ajax-loader').hide(); 113 | }); 114 | }); 115 | 116 | 117 | // $('#fiat-currency-select').change(function() { 118 | // $('#ajax-loader').show(); 119 | // currencyManager.getExchangeRateCoeff({ 120 | // newCurrencyCode: this.value, 121 | // oldFiatCurrencyCode: localStorage["fiatCurrencyCode"] 122 | // }).then(function(response){ 123 | // perferences.setCurrency(response.newCurrencyCode).then(function(){ 124 | // debugger; 125 | // updateGlobalOptionsAmount(response.exchangeRateCoeff, response.newCurrencyCode); 126 | // localStorage["fiatCurrencyCode"] = response.newCurrencyCode; 127 | // updateFiatCurrencyCode(); // update any in page USD 128 | // $('#ajax-loader').hide(); 129 | // }); 130 | // }); 131 | // // }, function(response){ 132 | // // // If all fails, reset to USD 133 | // // localStorage["fiatCurrencyCode"] = 'USD'; 134 | // // $('#fiat-currency-select').val(localStorage["fiatCurrencyCode"]); 135 | // // updateFiatCurrencyCode(); 136 | // // }); 137 | // }); 138 | } 139 | 140 | function updateFiatCurrencyCode() { 141 | currencyManager.getSymbol().then(function(symbol){ 142 | $.each($(".fiat-code"), function(key, element) { 143 | element.textContent = symbol[0]; 144 | }); 145 | }); 146 | } 147 | 148 | function setupWallet() { 149 | 150 | wallet.restoreAddress().then(setAddress(), 151 | function() { 152 | console.log('bazzz'); 153 | return wallet.generateAddress(); 154 | }).then(function(){ 155 | console.log('barr'); 156 | setAddress(); 157 | }, 158 | function() { 159 | alert('Failed to generate wallet. Refresh and try again.'); 160 | } 161 | ); 162 | 163 | 164 | function setAddress() { 165 | preferences.getPrivateKey().then(function(privateKey){ 166 | $('#private-key-input').val(privateKey); 167 | }); 168 | preferences.getAddress().then(function(address){ 169 | $('#textAddress').text(address()); 170 | }); 171 | } 172 | } 173 | 174 | function millisecondsToDays(milliseconds) { 175 | var seconds = Math.floor(milliseconds / 1000); 176 | var minutes = Math.floor(seconds / 60); 177 | var hours = Math.floor(minutes / 60); 178 | var days = Math.floor(hours / 24); 179 | return days; 180 | } 181 | 182 | function daysTillEndOWeek(endOfWeek) { 183 | var now = (new Date).getTime(); 184 | var milliseconds = endOfWeek - now; 185 | return millisecondsToDays(milliseconds) 186 | } 187 | 188 | function restartTheWeek() { 189 | var now = (new Date).getTime(); 190 | var milliSecondsInWeek = 604800000; 191 | var extraHour = 3600000; // add an hour to help the UI design. 192 | 193 | var alarm = now + milliSecondsInWeek + extraHour; 194 | 195 | var endOfWeek = new Date(alarm); 196 | 197 | var daysRemaining = daysTillEndOWeek(endOfWeek); 198 | 199 | localStorage['endOfWeek'] = alarm; 200 | } 201 | 202 | var db; 203 | $(document).ready(function() { 204 | db = new ydn.db.Storage('protip', schema); 205 | 206 | initFiatCurrency(); 207 | initDefaultBlacklistedHostnames(); 208 | initSponsors(); 209 | setupWallet(); 210 | 211 | if(!localStorage['availableBalanceFiat']){ 212 | localStorage['availableBalanceFiat'] = 0.00; 213 | } 214 | if(!localStorage['bitcoinFeeFiat']){ 215 | localStorage['bitcoinFeeFiat'] = 0.02; 216 | } 217 | if(!localStorage['subscriptionTotalFiat']){ 218 | localStorage['subscriptionTotalFiat'] = 0.00 219 | } 220 | if(!localStorage['incidentalTotalFiat']){ 221 | localStorage['incidentalTotalFiat'] = 0.00 222 | } 223 | 224 | allowExternalLinks(); 225 | 226 | $('#launch').click(function(obj){ 227 | localStorage['proTipInstalled'] = true; 228 | if(localStorage['protip-popup-install']){ 229 | browser.tabs.create({ 230 | url: "/views/home.html" // obj.href 231 | }); 232 | } else { 233 | window.location.href = "home.html"; 234 | } 235 | }); 236 | 237 | // Start the clock running. 238 | window.alarmManager.doToggleAlarm(); 239 | restartTheWeek(); 240 | 241 | /* 242 | * Import Private Key 243 | */ 244 | $('#importPrivateKey').click(function() { 245 | $('#importPrivateKeyPasswordIncorrect').hide(); 246 | $('#importPrivateKeyBadPrivateKey').hide(); 247 | if (wallet.isEncrypted()) { 248 | $('#importPrivateKeyPassword').val(null).show(); 249 | } else { 250 | $('#importPrivateKeyPassword').hide(); 251 | } 252 | $('#importPrivateKeyPrivateKey').val(null); 253 | $('#importPrivateKeyModal').modal().show(); 254 | }); 255 | 256 | $('#importPrivateKeyConfirm').click(function() { 257 | var privateKey = $('#importPrivateKeyPrivateKey').val(); 258 | try { 259 | new bitcoin.ECPair.fromWIF(privateKey); 260 | //new bitcoin.ECPair(privateKey).getExportedPrivateKey(); 261 | } catch (e) { 262 | $('#importPrivateKeyBadPrivateKey').slideDown(); 263 | return; 264 | } 265 | wallet.importAddress($('#importPrivateKeyPassword').val(), privateKey).then(function() { 266 | setupWallet(); 267 | $('#successAlertLabel').text('Private key imported successfully.'); 268 | $('#successAlertLabel').slideDown(); 269 | $('#private').slideUp(); 270 | $('#middle-aligned-media').slideUp(); 271 | }, function(e) { 272 | if (e.message === 'Incorrect password') { 273 | $('#importPrivateKeyBadPrivateKey').slideUp(); 274 | $('#importPrivateKeyPasswordIncorrect').slideDown(); 275 | } else { 276 | $('#importPrivateKeyPasswordIncorrect').slideUp(); 277 | $('#importPrivateKeyBadPrivateKey').slideDown(); 278 | } 279 | }); 280 | }); 281 | 282 | }); 283 | -------------------------------------------------------------------------------- /js/payment-manager.js: -------------------------------------------------------------------------------- 1 | (function(window) { 2 | var paymentManager = function() {}; 3 | 4 | var satoshis = 100000000; 5 | var fee = satoshis * .0001; 6 | 7 | paymentManager.prototype = { 8 | 9 | subscriptions: function(exchangeRateToSatoshi) { 10 | return new Promise(function(resolve, reject) { 11 | var subscriptions = []; 12 | db.values('subscriptions').done(function(records) { 13 | var txSatoshis; 14 | for (i = 0; i < records.length; i++) { 15 | txSatoshis = Math.floor(records[i].amountFiat / exchangeRateToSatoshi); 16 | subscriptions.push({ 17 | txDest: records[i].bitcoinAddress.trim(), 18 | txSatoshis: txSatoshis, 19 | paymentType: 'subscription' 20 | }); 21 | } 22 | resolve(subscriptions); 23 | }); 24 | }); 25 | }, 26 | 27 | browsing: function(incidentalTotalSatoshi) { 28 | return new Promise(function(resolve, reject) { 29 | var sites = []; 30 | db.values('sites').done(function(records) { 31 | 32 | records = _.filter(records, function(record){ return record.timeOnPage > 0; }); 33 | records = _.sortBy(records, 'timeOnPage').reverse().slice(0,9); 34 | 35 | var totalTime = 0; 36 | for (i = 0; i < records.length; i++) { 37 | if (records[i].timeOnPage) { 38 | totalTime += parseInt(records[i].timeOnPage); 39 | } 40 | }; 41 | var amountCoeff; 42 | var txSatoshis; 43 | for (i = 0; i < records.length; i++) { 44 | amountCoeff = (records[i].timeOnPage / totalTime); 45 | // Round down. Rounding errors may cause the sumation 46 | // of sites[x].txSatoshis to exceed incidentalTotalSatoshi 47 | txSatoshis = Math.floor(amountCoeff * incidentalTotalSatoshi); 48 | if (txSatoshis > 0) { 49 | sites.push({ 50 | txDest: records[i].bitcoinAddress.trim(), 51 | txSatoshis: txSatoshis, 52 | paymentType: 'browsing' 53 | }); 54 | } 55 | } 56 | resolve(sites); 57 | }); 58 | }); 59 | }, 60 | 61 | processPayments: function(paymentFiatData, totalWeeklyBudgetSatoshis, exchangeRate) { 62 | // Add the payments *upto* fiat budget. 63 | var paymentObjs = []; 64 | 65 | var txTotalSatoshis = 0; 66 | 67 | paymentFiatData = _.sortBy(paymentFiatData, 'amountFiat').reverse(); // sort descending 68 | // Most browsers do return properties in the same order as they were inserted, 69 | // but it is explicitly not guaranteed behaviour so you should not rely upon it. 70 | // In particular see section 12.6.4 of the ECMAScript specification: 71 | 72 | for (i = 0; i < paymentFiatData.length; i++) { 73 | var satoshisAsFloat = (parseFloat(paymentFiatData[i].amountFiat) / exchangeRate) * satoshis; 74 | paymentFiatData[i].txSatoshis = Math.floor(parseFloat(satoshisAsFloat)); 75 | paymentFiatData[i].exchangeRate = exchangeRate; 76 | txTotalSatoshis += paymentFiatData[i].txSatoshis; 77 | 78 | if (txTotalSatoshis > totalWeeklyBudgetSatoshis) { // if exceeded the budget, empty the wallet. 79 | paymentFiatData[i].txSatoshis = paymentFiatData[i].txSatoshis - (txTotalSatoshis - totalWeeklyBudgetSatoshis); 80 | paymentFiatData[i].amountFiat = ((paymentFiatData[i].txSatoshis / satoshis) * exchangeRate).toFixed(2); 81 | paymentFiatData[i].errors = 'Budget exceeded reducing the amount by' + (txTotalSatoshis - totalWeeklyBudgetSatoshis) + 'satoshis'; // for record keeping 82 | paymentObjs.push(paymentFiatData[i]); 83 | break; 84 | } 85 | paymentObjs.push(paymentFiatData[i]); 86 | } 87 | return paymentObjs; 88 | }, 89 | 90 | payAll: function(incidentalTotalFiat, subscriptionTotalFiat) { 91 | // Very important, many currency conversions cause rounding 92 | // errors, which can result in very small, but important 93 | // over run of the total weekly budget. 94 | // 95 | // This function is intended to produce an array of payment Objects 96 | // which reflect as accuately as possible the user's desired 97 | // distribution of funds, and not to exceed the weekly budgets 98 | // of browsing and subscriptions. 99 | // The actual balance of the wallet isn't considered here. It is 100 | // considered in the wallet.mulitpleOutputsSend. 101 | return new Promise(function(resolve, reject) { 102 | preferences.getExchangeRate().then(function(exchangeRateToBTC) { 103 | var exchangeRateToSatoshi = exchangeRateToBTC / satoshis; 104 | var incidentalTotalSatoshi = Math.floor(incidentalTotalFiat / exchangeRateToSatoshi); 105 | var subscriptionTotalSatoshi = Math.floor(subscriptionTotalFiat / exchangeRateToSatoshi); 106 | 107 | if(fee > wallet.getBalance()){ 108 | reject(Error('Balance must at least exceed minimum Bitcoin fee.')); 109 | } 110 | // If the weekly budget exceeds the total balance, then this function 111 | // just empties the wallet. 112 | return { 113 | exchangeRateToSatoshi: exchangeRateToSatoshi, 114 | incidentalTotalSatoshi: incidentalTotalSatoshi, 115 | subscriptionTotalSatoshi: subscriptionTotalSatoshi 116 | } 117 | }).then(function(resultObj){ 118 | return Promise.all([ 119 | ret.browsing(resultObj.incidentalTotalSatoshi), 120 | ret.subscriptions(resultObj.exchangeRateToSatoshi), 121 | new Promise(function (resolve) { resolve(resultObj.incidentalTotalSatoshi)}), 122 | new Promise(function (resolve) { resolve(resultObj.subscriptionTotalSatoshi)}) 123 | ]).then(function(results){ 124 | var browsingPaymentObjs = results[0]; 125 | var subscriptionsPaymentObjs = results[1]; 126 | var incidentalTotalSatoshi = results[2]; 127 | var subscriptionTotalSatoshi = results[3]; 128 | var availableSatoshi = incidentalTotalSatoshi + subscriptionTotalSatoshi; 129 | var selectedPayments = []; 130 | var runningTotal = 0; 131 | 132 | // The Subscriptions have priority over Browsing to the totalWeeklyBudgetSatoshi 133 | // do subscriptions first. 134 | for(var i=0;subscriptionsPaymentObjs.length > i;i++){ 135 | if(runningTotal >= availableSatoshi){ 136 | break; 137 | } 138 | runningTotal += subscriptionsPaymentObjs[i].txSatoshis; 139 | if (runningTotal - availableSatoshi > 0){ 140 | //empty the wallet 141 | subscriptionsPaymentObjs[i].txSatoshis -= runningTotal - availableSatoshi; 142 | runningTotal = availableSatoshi; 143 | } 144 | selectedPayments.push(subscriptionsPaymentObjs[i]); 145 | } 146 | // Give priority to the remainer of the funds to the largest payments. 147 | browsingPaymentObjs = _.sortBy(browsingPaymentObjs, 'txSatoshis').reverse(); 148 | for(var i=0;browsingPaymentObjs.length > i;i++){ 149 | if(runningTotal >= availableSatoshi){ 150 | break; 151 | } 152 | runningTotal += browsingPaymentObjs[i].txSatoshis; 153 | if (runningTotal - availableSatoshi > 0){ 154 | //empty the wallet 155 | browsingPaymentObjs[i].txSatoshis -= runningTotal - availableSatoshi; 156 | runningTotal = availableSatoshi; 157 | } 158 | selectedPayments.push(browsingPaymentObjs[i]); 159 | } 160 | if (selectedPayments.length == 0) { 161 | reject(Error('No browsing history or subscriptions.')); 162 | } 163 | var calculatedTotalAmountSatoshi = _.reduce(selectedPayments, function(memo, obj){ return obj.txSatoshis + memo; }, 0); 164 | if (availableSatoshi >= calculatedTotalAmountSatoshi) { 165 | wallet.mulitpleOutputsSend(selectedPayments, fee, '').then(function(response) { 166 | resolve(response); 167 | }, function(error){ 168 | reject(Error(error.message)); 169 | }); 170 | } else { 171 | return Error( 172 | 'Error: payment totals mismatch. No payments sent.' + 173 | 'Please report this error to https://github.com/Leo-ajc/ProTip.' 174 | ); 175 | } 176 | }); 177 | }); 178 | }); 179 | } 180 | } 181 | 182 | var ret = new paymentManager(); 183 | window.paymentManager = ret; 184 | 185 | })(window); -------------------------------------------------------------------------------- /views/subscriptions.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | ProTip 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 111 | 112 | 115 | 116 | Total x weekly 117 | 118 |
119 | 120 | 121 | 122 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 |
123 | 124 | LabelUSD#
133 |
134 | 135 | 136 |
137 |
138 | 139 | 140 | 143 | 146 | 147 | 148 | 151 | 154 | 155 | 156 | 159 | 162 | 163 | 164 | 167 | 170 | 171 | 172 | 173 | 176 | 177 |
141 | 142 | 144 | 145 |
149 | 150 | 152 | USD weekly 153 |
157 | 158 | 160 | 161 |
165 | 166 | 168 | 169 |
174 | 175 |
178 |
179 |
180 | 181 | 182 | 244 | 245 | 246 | 247 | 248 | 249 | -------------------------------------------------------------------------------- /lib/currency-manager.js: -------------------------------------------------------------------------------- 1 | /** 2 | * currency-manager.js 3 | * Copyright (c) 2014 Andrew Toth 4 | * 5 | * This program is free software; you can redistribute it and/or modify 6 | * it under the terms of the MIT license. 7 | * 8 | * Currency manager handles the exchange rate of the currency 9 | * and the proper formatting of the currency value 10 | */ 11 | 12 | (function (window) { 13 | var currencyManager = function () {}; 14 | currencyManager.prototype = { 15 | 16 | getExchangeRate: function (fiatCurrency) { 17 | return util.getJSON('https://apiv2.bitcoinaverage.com/indices/global/ticker/short?crypto=BTC&fiat=' + fiatCurrency) 18 | }, 19 | 20 | // getExchangeRateCoeff: function (obj) { 21 | // var newCurrencyCode = obj.newCurrencyCode || 'USD'; 22 | // var oldFiatCurrencyCode = obj.oldFiatCurrencyCode || 'USD'; 23 | // 24 | // return new Promise(function (resolve, reject) { 25 | // if (oldFiatCurrencyCode == 'BTC' && newCurrencyCode == 'mBTC') { 26 | // // from BTC to mBTC 27 | // exchangeRateCoeff = 1000; 28 | // resolve({exchangeRateCoeff: exchangeRateCoeff, newCurrencyCode: newCurrencyCode}); 29 | // } else if (oldFiatCurrencyCode == 'mBTC' && newCurrencyCode == 'BTC') { 30 | // // from mBTC to BTC 31 | // exchangeRateCoeff = 1/1000; 32 | // resolve({exchangeRateCoeff: exchangeRateCoeff, newCurrencyCode: newCurrencyCode}); 33 | // } else if (newCurrencyCode == 'BTC') { 34 | // // from fiat to BTC 35 | // currencyManager.getExchangeRate(oldFiatCurrencyCode).then(function(rateObj) { 36 | // exchangeRateCoeff = 1/rateObj[Object.keys(rateObj)[0]]['averages']['day']; 37 | // resolve({exchangeRateCoeff: exchangeRateCoeff, newCurrencyCode: newCurrencyCode}); 38 | // }); 39 | // } else if (newCurrencyCode == 'mBTC'){ 40 | // currencyManager.getExchangeRate(oldFiatCurrencyCode).then(function(rateObj) { 41 | // // from fiat to mBTC 42 | // exchangeRateCoeff = 1000/rateObj[Object.keys(rateObj)[0]]['averages']['day']; 43 | // resolve({exchangeRateCoeff: exchangeRateCoeff, newCurrencyCode: newCurrencyCode}); 44 | // }); 45 | // } else if (oldFiatCurrencyCode == 'BTC') { 46 | // currencyManager.getExchangeRate(newCurrencyCode).then(function(rateObj) { 47 | // // from BTC to fiat 48 | // exchangeRateCoeff = rateObj[Object.keys(rateObj)[0]]['averages']['day']; 49 | // resolve({exchangeRateCoeff: exchangeRateCoeff, newCurrencyCode: newCurrencyCode}); 50 | // }); 51 | // } else if (oldFiatCurrencyCode == 'mBTC') { 52 | // currencyManager.getExchangeRate(newCurrencyCode).then(function(rateObj) { 53 | // // from mBTC to fiat 54 | // exchangeRateCoeff = rateObj[Object.keys(rateObj)[0]]['averages']['day']/1000; 55 | // resolve({exchangeRateCoeff: exchangeRateCoeff, newCurrencyCode: newCurrencyCode}); 56 | // }); 57 | // } else { // fiat to fiat 58 | // util.getJSON('http://api.fixer.io/latest?symbols=' + newCurrencyCode + ',' + oldFiatCurrencyCode) 59 | // .then(function(ratesData){ 60 | // if(newCurrencyCode == 'EUR' || oldFiatCurrencyCode == 'EUR'){ 61 | // resolve({exchangeRateCoeff: ratesData.rates[Object.keys(ratesData.rates)[0]], newCurrencyCode: newCurrencyCode}); 62 | // } 63 | // exchangeRateCoeff = ratesData.rates[newCurrencyCode] / ratesData.rates[oldFiatCurrencyCode]; 64 | // resolve({exchangeRateCoeff: exchangeRateCoeff, newCurrencyCode: newCurrencyCode}); 65 | // }); 66 | // } 67 | // }); 68 | // }, 69 | 70 | updateExchangeRate: function () { 71 | preferences.getCurrency().then(function (currency) { 72 | switch (currency) { 73 | // for BTC and mBTC we don't need to get exchange rate 74 | // Bit of a hack for BTC. The wallet balance is always stored in BTC 75 | case 'BTC': 76 | return new Promise(function (resolve) { resolve({'dummy': {'averages': {'day': 1}}})}); // hack hack 77 | case 'mBTC': 78 | return new Promise(function (resolve) { resolve({'dummy': {'averages': {'day': 1000}}})}); // hack hack 79 | default: 80 | return util.getJSON('https://apiv2.bitcoinaverage.com/indices/global/ticker/short?crypto=BTC&fiat=' + currency); 81 | } 82 | }).then(function (response) { 83 | preferences.setExchangeRate(response[Object.keys(response)[0]]['averages']['day']); 84 | return response[Object.keys(response)[0]]['averages']['day']; 85 | }, function(err){ 86 | return Error(err); 87 | }); 88 | }, 89 | 90 | // updateExchangeRate: function () { 91 | // return preferences.getCurrency().then(function (currency) { 92 | // switch (currency) { 93 | // // for BTC and mBTC we don't need to get exchange rate 94 | // // Bit of a hack for BTC. The wallet balance is always stored in BTC 95 | // case 'BTC': 96 | // return new Promise(function (resolve) { resolve({'dummy': {'averages': {'day': 1}}})}); // hack hack 97 | // case 'mBTC': 98 | // return new Promise(function (resolve) { resolve({'dummy': {'averages': {'day': 1000}}})}); // hack hack 99 | // default: 100 | // return util.getJSON('https://apiv2.bitcoinaverage.com/indices/global/ticker/short?crypto=BTC&fiat=' + currency); 101 | // } 102 | // }).then(function (response) { 103 | // preferences.setExchangeRate(response[Object.keys(response)[0]]['averages']['day']); 104 | // return response[Object.keys(response)[0]]['averages']['day'] 105 | // }, function(err){ 106 | // debugger; 107 | // reject(err); 108 | // }); 109 | // }, 110 | 111 | getSymbol: function () { 112 | return preferences.getCurrency().then(function (currency) { 113 | switch (currency) { 114 | // ['Symbol', 'position of symbol before/after', rounding factor false/1-10] 115 | // case 'BTC': 116 | // return(['BTC', 'before', false]); // BTC doesn't really look good in the UI 117 | case 'mBTC': 118 | return(['mBTC', 'before', false]); 119 | case 'AUD': 120 | case 'CAD': 121 | case 'NZD': 122 | case 'SGD': 123 | case 'USD': 124 | return(['$', 'before', 2]); 125 | case 'BRL': 126 | return(['R$', 'before', 2]); 127 | case 'CHF': 128 | return([' Fr.', 'after', 2]); 129 | case 'CNY': 130 | return(['¥', 'before', 2]); 131 | case 'EUR': 132 | return(['€', 'before', 2]); 133 | case 'GBP': 134 | return(['£', 'before', 2]); 135 | case 'ILS': 136 | return(['₪', 'before', 2]); 137 | case 'NOK': 138 | case 'SEK': 139 | return([' kr', 'after', 2]); 140 | case 'PLN': 141 | return(['zł', 'after', 2]); 142 | case 'RUB': 143 | return([' RUB', 'after', 2]); 144 | case 'ZAR': 145 | return([' R', 'after', 2]); 146 | default: 147 | return(['$', 'before', 2]); 148 | } 149 | }); 150 | }, 151 | 152 | getAvailableCurrencies: function () { 153 | // 'EUR', 154 | return ['mBTC', 'AUD', 'BRL', 'CAD', 'CHF', 'CNY', 'EUR', 'GBP', 'ILS', 'NOK', 'NZD', 'PLN', 'RUB', 'SEK', 'SGD', 'USD', 'ZAR']; 155 | }, 156 | 157 | amount: function (valueSatoshi) { 158 | return Promise.all([preferences.getExchangeRate(), this.getSymbol()]).then(function (values) { 159 | var rate, 160 | SATOSHIS = 100000000; 161 | switch (values[1][0]) { 162 | case 'BTC': 163 | return valueSatoshi / SATOSHIS; 164 | case 'mBTC': 165 | return valueSatoshi / 100000; 166 | default: 167 | return (valueSatoshi / SATOSHIS * values[0]).formatMoney(2); 168 | } 169 | }); 170 | }, 171 | 172 | formatCurrency: function(value) { 173 | return Promise.all([this.amount(value), this.getSymbol()]).then(function (results) { 174 | var symbol = results[1][0], 175 | beforeOrAfter = results[1][1], 176 | amount = results[0]; 177 | switch (symbol) { 178 | // 'BTC' should not be rounded and not formatted like regular fiat. 179 | case 'BTC': 180 | return 'BTC ' + amount; 181 | case 'mBTC': 182 | return 'mBTC ' + amount; 183 | default: 184 | // Format fiat money 185 | if ( amount < 0.01 ) { amount = 0 } // We only want to do this Fiat currency. 186 | amount = parseFloat(amount); 187 | var text = parseFloat(amount.formatMoney(2)); 188 | if (beforeOrAfter === 'before') { 189 | text = symbol + text; 190 | } else { 191 | text += symbol; 192 | } 193 | return text; 194 | } 195 | }); 196 | }, 197 | 198 | formatAmount: function (value) { 199 | return Promise.all([preferences.getExchangeRate(), this.getSymbol()]).then(function (values) { 200 | var rate = values[0], 201 | symbol = values[1][0], 202 | beforeOrAfter = values[1][1], 203 | SATOSHIS = 100000000, 204 | amount = (value / SATOSHIS * rate); 205 | if ( amount < 0.01 ) { amount = 0 } 206 | var text = amount.formatMoney(2); 207 | if (beforeOrAfter === 'before') { 208 | text = symbol + text; 209 | } else { 210 | text += symbol; 211 | } 212 | return text; 213 | }); 214 | }, 215 | }; 216 | 217 | Number.prototype.formatMoney = function(c, d, t){ 218 | var n = this, 219 | c = isNaN(c = Math.abs(c)) ? 2 : c, 220 | d = d == undefined ? "." : d, 221 | t = t == undefined ? "," : t, 222 | s = n < 0 ? "-" : "", 223 | i = parseInt(n = Math.abs(+n || 0).toFixed(c)) + "", 224 | j = (j = i.length) > 3 ? j % 3 : 0; 225 | return s + (j ? i.substr(0, j) + t : "") + i.substr(j).replace(/(\d{3})(?=\d)/g, "$1" + t) + (c ? d + Math.abs(n - i).toFixed(c).slice(2) : ""); 226 | }; 227 | 228 | var ret = new currencyManager(); 229 | ret.updateExchangeRate(); 230 | window.currencyManager = ret; 231 | 232 | })(window); -------------------------------------------------------------------------------- /lib/cryptojs.min.js: -------------------------------------------------------------------------------- 1 | /* 2 | CryptoJS v3.1.2 3 | code.google.com/p/crypto-js 4 | (c) 2009-2013 by Jeff Mott. All rights reserved. 5 | code.google.com/p/crypto-js/wiki/License 6 | */ 7 | var CryptoJS=CryptoJS||function(u,p){var d={},l=d.lib={},s=function(){},t=l.Base={extend:function(a){s.prototype=this;var c=new s;a&&c.mixIn(a);c.hasOwnProperty("init")||(c.init=function(){c.$super.init.apply(this,arguments)});c.init.prototype=c;c.$super=this;return c},create:function(){var a=this.extend();a.init.apply(a,arguments);return a},init:function(){},mixIn:function(a){for(var c in a)a.hasOwnProperty(c)&&(this[c]=a[c]);a.hasOwnProperty("toString")&&(this.toString=a.toString)},clone:function(){return this.init.prototype.extend(this)}}, 8 | r=l.WordArray=t.extend({init:function(a,c){a=this.words=a||[];this.sigBytes=c!=p?c:4*a.length},toString:function(a){return(a||v).stringify(this)},concat:function(a){var c=this.words,e=a.words,j=this.sigBytes;a=a.sigBytes;this.clamp();if(j%4)for(var k=0;k>>2]|=(e[k>>>2]>>>24-8*(k%4)&255)<<24-8*((j+k)%4);else if(65535>>2]=e[k>>>2];else c.push.apply(c,e);this.sigBytes+=a;return this},clamp:function(){var a=this.words,c=this.sigBytes;a[c>>>2]&=4294967295<< 9 | 32-8*(c%4);a.length=u.ceil(c/4)},clone:function(){var a=t.clone.call(this);a.words=this.words.slice(0);return a},random:function(a){for(var c=[],e=0;e>>2]>>>24-8*(j%4)&255;e.push((k>>>4).toString(16));e.push((k&15).toString(16))}return e.join("")},parse:function(a){for(var c=a.length,e=[],j=0;j>>3]|=parseInt(a.substr(j, 10 | 2),16)<<24-4*(j%8);return new r.init(e,c/2)}},b=w.Latin1={stringify:function(a){var c=a.words;a=a.sigBytes;for(var e=[],j=0;j>>2]>>>24-8*(j%4)&255));return e.join("")},parse:function(a){for(var c=a.length,e=[],j=0;j>>2]|=(a.charCodeAt(j)&255)<<24-8*(j%4);return new r.init(e,c)}},x=w.Utf8={stringify:function(a){try{return decodeURIComponent(escape(b.stringify(a)))}catch(c){throw Error("Malformed UTF-8 data");}},parse:function(a){return b.parse(unescape(encodeURIComponent(a)))}}, 11 | q=l.BufferedBlockAlgorithm=t.extend({reset:function(){this._data=new r.init;this._nDataBytes=0},_append:function(a){"string"==typeof a&&(a=x.parse(a));this._data.concat(a);this._nDataBytes+=a.sigBytes},_process:function(a){var c=this._data,e=c.words,j=c.sigBytes,k=this.blockSize,b=j/(4*k),b=a?u.ceil(b):u.max((b|0)-this._minBufferSize,0);a=b*k;j=u.min(4*a,j);if(a){for(var q=0;q>>2]>>>24-8*(r%4)&255)<<16|(l[r+1>>>2]>>>24-8*((r+1)%4)&255)<<8|l[r+2>>>2]>>>24-8*((r+2)%4)&255,v=0;4>v&&r+0.75*v>>6*(3-v)&63));if(l=t.charAt(64))for(;d.length%4;)d.push(l);return d.join("")},parse:function(d){var l=d.length,s=this._map,t=s.charAt(64);t&&(t=d.indexOf(t),-1!=t&&(l=t));for(var t=[],r=0,w=0;w< 15 | l;w++)if(w%4){var v=s.indexOf(d.charAt(w-1))<<2*(w%4),b=s.indexOf(d.charAt(w))>>>6-2*(w%4);t[r>>>2]|=(v|b)<<24-8*(r%4);r++}return p.create(t,r)},_map:"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="}})(); 16 | (function(u){function p(b,n,a,c,e,j,k){b=b+(n&a|~n&c)+e+k;return(b<>>32-j)+n}function d(b,n,a,c,e,j,k){b=b+(n&c|a&~c)+e+k;return(b<>>32-j)+n}function l(b,n,a,c,e,j,k){b=b+(n^a^c)+e+k;return(b<>>32-j)+n}function s(b,n,a,c,e,j,k){b=b+(a^(n|~c))+e+k;return(b<>>32-j)+n}for(var t=CryptoJS,r=t.lib,w=r.WordArray,v=r.Hasher,r=t.algo,b=[],x=0;64>x;x++)b[x]=4294967296*u.abs(u.sin(x+1))|0;r=r.MD5=v.extend({_doReset:function(){this._hash=new w.init([1732584193,4023233417,2562383102,271733878])}, 17 | _doProcessBlock:function(q,n){for(var a=0;16>a;a++){var c=n+a,e=q[c];q[c]=(e<<8|e>>>24)&16711935|(e<<24|e>>>8)&4278255360}var a=this._hash.words,c=q[n+0],e=q[n+1],j=q[n+2],k=q[n+3],z=q[n+4],r=q[n+5],t=q[n+6],w=q[n+7],v=q[n+8],A=q[n+9],B=q[n+10],C=q[n+11],u=q[n+12],D=q[n+13],E=q[n+14],x=q[n+15],f=a[0],m=a[1],g=a[2],h=a[3],f=p(f,m,g,h,c,7,b[0]),h=p(h,f,m,g,e,12,b[1]),g=p(g,h,f,m,j,17,b[2]),m=p(m,g,h,f,k,22,b[3]),f=p(f,m,g,h,z,7,b[4]),h=p(h,f,m,g,r,12,b[5]),g=p(g,h,f,m,t,17,b[6]),m=p(m,g,h,f,w,22,b[7]), 18 | f=p(f,m,g,h,v,7,b[8]),h=p(h,f,m,g,A,12,b[9]),g=p(g,h,f,m,B,17,b[10]),m=p(m,g,h,f,C,22,b[11]),f=p(f,m,g,h,u,7,b[12]),h=p(h,f,m,g,D,12,b[13]),g=p(g,h,f,m,E,17,b[14]),m=p(m,g,h,f,x,22,b[15]),f=d(f,m,g,h,e,5,b[16]),h=d(h,f,m,g,t,9,b[17]),g=d(g,h,f,m,C,14,b[18]),m=d(m,g,h,f,c,20,b[19]),f=d(f,m,g,h,r,5,b[20]),h=d(h,f,m,g,B,9,b[21]),g=d(g,h,f,m,x,14,b[22]),m=d(m,g,h,f,z,20,b[23]),f=d(f,m,g,h,A,5,b[24]),h=d(h,f,m,g,E,9,b[25]),g=d(g,h,f,m,k,14,b[26]),m=d(m,g,h,f,v,20,b[27]),f=d(f,m,g,h,D,5,b[28]),h=d(h,f, 19 | m,g,j,9,b[29]),g=d(g,h,f,m,w,14,b[30]),m=d(m,g,h,f,u,20,b[31]),f=l(f,m,g,h,r,4,b[32]),h=l(h,f,m,g,v,11,b[33]),g=l(g,h,f,m,C,16,b[34]),m=l(m,g,h,f,E,23,b[35]),f=l(f,m,g,h,e,4,b[36]),h=l(h,f,m,g,z,11,b[37]),g=l(g,h,f,m,w,16,b[38]),m=l(m,g,h,f,B,23,b[39]),f=l(f,m,g,h,D,4,b[40]),h=l(h,f,m,g,c,11,b[41]),g=l(g,h,f,m,k,16,b[42]),m=l(m,g,h,f,t,23,b[43]),f=l(f,m,g,h,A,4,b[44]),h=l(h,f,m,g,u,11,b[45]),g=l(g,h,f,m,x,16,b[46]),m=l(m,g,h,f,j,23,b[47]),f=s(f,m,g,h,c,6,b[48]),h=s(h,f,m,g,w,10,b[49]),g=s(g,h,f,m, 20 | E,15,b[50]),m=s(m,g,h,f,r,21,b[51]),f=s(f,m,g,h,u,6,b[52]),h=s(h,f,m,g,k,10,b[53]),g=s(g,h,f,m,B,15,b[54]),m=s(m,g,h,f,e,21,b[55]),f=s(f,m,g,h,v,6,b[56]),h=s(h,f,m,g,x,10,b[57]),g=s(g,h,f,m,t,15,b[58]),m=s(m,g,h,f,D,21,b[59]),f=s(f,m,g,h,z,6,b[60]),h=s(h,f,m,g,C,10,b[61]),g=s(g,h,f,m,j,15,b[62]),m=s(m,g,h,f,A,21,b[63]);a[0]=a[0]+f|0;a[1]=a[1]+m|0;a[2]=a[2]+g|0;a[3]=a[3]+h|0},_doFinalize:function(){var b=this._data,n=b.words,a=8*this._nDataBytes,c=8*b.sigBytes;n[c>>>5]|=128<<24-c%32;var e=u.floor(a/ 21 | 4294967296);n[(c+64>>>9<<4)+15]=(e<<8|e>>>24)&16711935|(e<<24|e>>>8)&4278255360;n[(c+64>>>9<<4)+14]=(a<<8|a>>>24)&16711935|(a<<24|a>>>8)&4278255360;b.sigBytes=4*(n.length+1);this._process();b=this._hash;n=b.words;for(a=0;4>a;a++)c=n[a],n[a]=(c<<8|c>>>24)&16711935|(c<<24|c>>>8)&4278255360;return b},clone:function(){var b=v.clone.call(this);b._hash=this._hash.clone();return b}});t.MD5=v._createHelper(r);t.HmacMD5=v._createHmacHelper(r)})(Math); 22 | (function(){var u=CryptoJS,p=u.lib,d=p.Base,l=p.WordArray,p=u.algo,s=p.EvpKDF=d.extend({cfg:d.extend({keySize:4,hasher:p.MD5,iterations:1}),init:function(d){this.cfg=this.cfg.extend(d)},compute:function(d,r){for(var p=this.cfg,s=p.hasher.create(),b=l.create(),u=b.words,q=p.keySize,p=p.iterations;u.length>>2]&255}};d.BlockCipher=v.extend({cfg:v.cfg.extend({mode:b,padding:q}),reset:function(){v.reset.call(this);var a=this.cfg,b=a.iv,a=a.mode;if(this._xformMode==this._ENC_XFORM_MODE)var c=a.createEncryptor;else c=a.createDecryptor,this._minBufferSize=1;this._mode=c.call(a, 28 | this,b&&b.words)},_doProcessBlock:function(a,b){this._mode.processBlock(a,b)},_doFinalize:function(){var a=this.cfg.padding;if(this._xformMode==this._ENC_XFORM_MODE){a.pad(this._data,this.blockSize);var b=this._process(!0)}else b=this._process(!0),a.unpad(b);return b},blockSize:4});var n=d.CipherParams=l.extend({init:function(a){this.mixIn(a)},toString:function(a){return(a||this.formatter).stringify(this)}}),b=(p.format={}).OpenSSL={stringify:function(a){var b=a.ciphertext;a=a.salt;return(a?s.create([1398893684, 29 | 1701076831]).concat(a).concat(b):b).toString(r)},parse:function(a){a=r.parse(a);var b=a.words;if(1398893684==b[0]&&1701076831==b[1]){var c=s.create(b.slice(2,4));b.splice(0,4);a.sigBytes-=16}return n.create({ciphertext:a,salt:c})}},a=d.SerializableCipher=l.extend({cfg:l.extend({format:b}),encrypt:function(a,b,c,d){d=this.cfg.extend(d);var l=a.createEncryptor(c,d);b=l.finalize(b);l=l.cfg;return n.create({ciphertext:b,key:c,iv:l.iv,algorithm:a,mode:l.mode,padding:l.padding,blockSize:a.blockSize,formatter:d.format})}, 30 | decrypt:function(a,b,c,d){d=this.cfg.extend(d);b=this._parse(b,d.format);return a.createDecryptor(c,d).finalize(b.ciphertext)},_parse:function(a,b){return"string"==typeof a?b.parse(a,this):a}}),p=(p.kdf={}).OpenSSL={execute:function(a,b,c,d){d||(d=s.random(8));a=w.create({keySize:b+c}).compute(a,d);c=s.create(a.words.slice(b),4*c);a.sigBytes=4*b;return n.create({key:a,iv:c,salt:d})}},c=d.PasswordBasedCipher=a.extend({cfg:a.cfg.extend({kdf:p}),encrypt:function(b,c,d,l){l=this.cfg.extend(l);d=l.kdf.execute(d, 31 | b.keySize,b.ivSize);l.iv=d.iv;b=a.encrypt.call(this,b,c,d.key,l);b.mixIn(d);return b},decrypt:function(b,c,d,l){l=this.cfg.extend(l);c=this._parse(c,l.format);d=l.kdf.execute(d,b.keySize,b.ivSize,c.salt);l.iv=d.iv;return a.decrypt.call(this,b,c,d.key,l)}})}(); 32 | (function(){for(var u=CryptoJS,p=u.lib.BlockCipher,d=u.algo,l=[],s=[],t=[],r=[],w=[],v=[],b=[],x=[],q=[],n=[],a=[],c=0;256>c;c++)a[c]=128>c?c<<1:c<<1^283;for(var e=0,j=0,c=0;256>c;c++){var k=j^j<<1^j<<2^j<<3^j<<4,k=k>>>8^k&255^99;l[e]=k;s[k]=e;var z=a[e],F=a[z],G=a[F],y=257*a[k]^16843008*k;t[e]=y<<24|y>>>8;r[e]=y<<16|y>>>16;w[e]=y<<8|y>>>24;v[e]=y;y=16843009*G^65537*F^257*z^16843008*e;b[k]=y<<24|y>>>8;x[k]=y<<16|y>>>16;q[k]=y<<8|y>>>24;n[k]=y;e?(e=z^a[a[a[G^z]]],j^=a[a[j]]):e=j=1}var H=[0,1,2,4,8, 33 | 16,32,64,128,27,54],d=d.AES=p.extend({_doReset:function(){for(var a=this._key,c=a.words,d=a.sigBytes/4,a=4*((this._nRounds=d+6)+1),e=this._keySchedule=[],j=0;j>>24]<<24|l[k>>>16&255]<<16|l[k>>>8&255]<<8|l[k&255]):(k=k<<8|k>>>24,k=l[k>>>24]<<24|l[k>>>16&255]<<16|l[k>>>8&255]<<8|l[k&255],k^=H[j/d|0]<<24);e[j]=e[j-d]^k}c=this._invKeySchedule=[];for(d=0;dd||4>=j?k:b[l[k>>>24]]^x[l[k>>>16&255]]^q[l[k>>> 34 | 8&255]]^n[l[k&255]]},encryptBlock:function(a,b){this._doCryptBlock(a,b,this._keySchedule,t,r,w,v,l)},decryptBlock:function(a,c){var d=a[c+1];a[c+1]=a[c+3];a[c+3]=d;this._doCryptBlock(a,c,this._invKeySchedule,b,x,q,n,s);d=a[c+1];a[c+1]=a[c+3];a[c+3]=d},_doCryptBlock:function(a,b,c,d,e,j,l,f){for(var m=this._nRounds,g=a[b]^c[0],h=a[b+1]^c[1],k=a[b+2]^c[2],n=a[b+3]^c[3],p=4,r=1;r>>24]^e[h>>>16&255]^j[k>>>8&255]^l[n&255]^c[p++],s=d[h>>>24]^e[k>>>16&255]^j[n>>>8&255]^l[g&255]^c[p++],t= 35 | d[k>>>24]^e[n>>>16&255]^j[g>>>8&255]^l[h&255]^c[p++],n=d[n>>>24]^e[g>>>16&255]^j[h>>>8&255]^l[k&255]^c[p++],g=q,h=s,k=t;q=(f[g>>>24]<<24|f[h>>>16&255]<<16|f[k>>>8&255]<<8|f[n&255])^c[p++];s=(f[h>>>24]<<24|f[k>>>16&255]<<16|f[n>>>8&255]<<8|f[g&255])^c[p++];t=(f[k>>>24]<<24|f[n>>>16&255]<<16|f[g>>>8&255]<<8|f[h&255])^c[p++];n=(f[n>>>24]<<24|f[g>>>16&255]<<16|f[h>>>8&255]<<8|f[k&255])^c[p++];a[b]=q;a[b+1]=s;a[b+2]=t;a[b+3]=n},keySize:8});u.AES=p._createHelper(d)})(); -------------------------------------------------------------------------------- /controllers/home.js: -------------------------------------------------------------------------------- 1 | function initAlarmDisplay() { 2 | alarmManager.alarmExpired(localStorage['alarmExpireDate'], function(expired){ 3 | if(expired){ 4 | var now = (new Date).getTime(); 5 | var weekInTheFuture = new Date(now+(60 * 60 * 24 * 7)); // One week in the future. 6 | $('#date-end-of-week').html('1 week from now, ' + weekInTheFuture.format("dddd, mmmm dS, yyyy, h:MM:ss TT")); 7 | $('#days-till-end-of-week').html('0'); 8 | } else { 9 | browser.alarms.getAll().then(function(objs){ 10 | var date = new Date(objs[0].scheduledTime); 11 | $('#date-end-of-week').html('on ' + date.format("dddd, mmmm dS, yyyy, h:MM:ss TT")); 12 | var daysRemaining = daysTillEndOfWeek(date); 13 | $('#days-till-end-of-week').html(daysRemaining); 14 | }); 15 | } 16 | }); 17 | } 18 | 19 | function setupWallet() { 20 | wallet.restoreAddress().then(function(){ 21 | setQRCodes(); 22 | updateBalance(wallet.getAddress()); 23 | }, 24 | function() { 25 | return wallet.generateAddress(); 26 | }).then(function(address){ 27 | setQRCodes; 28 | updateBalance(wallet.getAddress()); 29 | }, 30 | function() { 31 | alert('Failed to generate wallet. Refresh and try again.'); 32 | } 33 | ); 34 | 35 | function setQRCodes() { 36 | $('#qrcode').html(createQRCodeCanvas(wallet.getAddress())); 37 | $('#textAddress').text(wallet.getAddress()); 38 | var blockchainURL = 'https://blockchain.info/address/' + wallet.getAddress(); 39 | $('#payment-history-link').attr('href', blockchainURL); 40 | } 41 | 42 | } 43 | 44 | function updateBalance(address) { 45 | var host = 'https://api.blockcypher.com/v1/btc/main/addrs/'; 46 | util.getJSON(host + address + '?unspentOnly=true&limit=50').then(function (response) { // This API call is an unnesscesary duplicate of a earlier call in wallest.restoreAddress. Intergrate there. 47 | 48 | if(response.txrefs){ 49 | response.balance = _.reduce(response.txrefs, function(memo, obj){ return obj.value + memo; }, 0); 50 | } else { 51 | response.balance = 0; 52 | } 53 | 54 | if(!response.unconfirmed_txrefs && !response.txrefs){ 55 | // The wallet is empty 56 | response.balance = 0; 57 | } else if(response.unconfirmed_txrefs){ 58 | // The attribute 'unconfirmed_balance' from Blockcypher does not 59 | // indicate the total of the unconfirmed unspent outputs. 60 | // Even from http://dev.blockcypher.com/#address I cannot workout 61 | // what this number really represents. 62 | // 63 | var pendingConfirmation = _.reduce(response.unconfirmed_txrefs, function(memo, obj){ return obj.value + memo; }, 0); 64 | $('#balance-pending-confirmation-container').show(); 65 | currencyManager.formatCurrency(pendingConfirmation).then(function(balancePendingConfirmation){ 66 | $('#balance-pending-confirmation').html(balancePendingConfirmation); 67 | }); 68 | } 69 | currencyManager.amount(response.balance).then(function(moneyWithoutSymbol) { 70 | localStorage['availableBalanceFiat'] = moneyWithoutSymbol; 71 | currencyManager.amount(FEE).then(function(bitcoinFeeFiat) { 72 | $('#bitcoin-fee').text(bitcoinFeeFiat); 73 | setBudgetWidget(localStorage['availableBalanceFiat'], bitcoinFeeFiat); 74 | }); 75 | browser.browserAction.setBadgeText({text: moneyWithoutSymbol}); // May as well use this API call to also update this value. 76 | }); 77 | //$('#head-line-balance').text('BTC ' + response.balance); 78 | currencyManager.formatCurrency(response.balance).then(function(formattedMoney) { 79 | for(i=0;i < $('.btc-balance-to-fiat').length; i++){ 80 | $('.btc-balance-to-fiat')[i].textContent = formattedMoney; 81 | } 82 | }); 83 | }); 84 | } 85 | 86 | function setMinIncidentalFiatAmounts(incidentalTotalFiat){ 87 | if(parseFloat(incidentalTotalFiat) >= 0.00) { 88 | // If the Tx is less than <= 0.01 it takes many many hours to confirm, and your change is locked up. 89 | // Making 0.03 the min. 90 | return parseFloat(incidentalTotalFiat); 91 | } else { 92 | return 0.00; 93 | } 94 | } 95 | 96 | function restartCountDown(){ 97 | window.alarmManager.doToggleAlarm(); 98 | initAlarmDisplay(); 99 | $('#date-end-of-week').effect("highlight", { 100 | color: 'rgb(100, 189, 99)' 101 | }, 1000); 102 | } 103 | 104 | var db; 105 | $(function() { 106 | if(!localStorage['proTipInstalled']) { 107 | window.location.replace("install.html"); 108 | } 109 | window.FEE = 10000; 110 | 111 | db = new ydn.db.Storage('protip', schema); 112 | 113 | updateFiatCurrencyCode(); 114 | allowExternalLinks(); 115 | 116 | initAlarmDisplay(); 117 | 118 | setupWallet(); 119 | buildBrowsingTable('browsing-table'); 120 | 121 | var availableBalanceFiat = parseFloat(localStorage['availableBalanceFiat']); 122 | var bitcoinFeeFiat = parseFloat(localStorage['bitcoinFeeFiat']); 123 | var totalSubscriptionsFiat = parseFloat(localStorage['subscriptionTotalFiat']); 124 | var incidentalTotalFiat = parseFloat(localStorage['incidentalTotalFiat']); 125 | var weeklyTotalFiat = bitcoinFeeFiat + totalSubscriptionsFiat + incidentalTotalFiat; 126 | $('#weekly-spend-manual-pay-reminder-btn').html(parseFloat(weeklyTotalFiat).toFixed(2)); 127 | 128 | $('#confirm-donate-now').click(function() { 129 | $('#donate-now').button('Sending...'); 130 | $('#notice-dialogue').slideUp().fadeOut(); 131 | //localStorage['weeklyAlarmReminder'] = false; 132 | browser.browserAction.setBadgeText({ 133 | text: '' 134 | }); 135 | 136 | paymentManager.payAll(localStorage['incidentalTotalFiat'], localStorage['subscriptionTotalFiat']).then(function(response){ 137 | localStorage['weeklyAlarmReminder'] = false; 138 | window.alarmManager.doToggleAlarm(); 139 | restartCountDown(); 140 | $('#payment-history').effect("highlight", { 141 | color: 'rgb(100, 189, 99)' 142 | }, 4000); 143 | //$('#notice').html(response); 144 | db.clear('sites'); 145 | $('#notice').html('Transaction Submitted'); 146 | $('#notice-dialogue').fadeIn().slideDown(); 147 | $('#donate-now').button('reset'); 148 | $('#confirm-donate-now-dialogue').slideUp().fadeOut(); 149 | updateBalance(wallet.getAddress()); 150 | 151 | }, function(response){ 152 | // blockCypher is returning a error code when the transaction was successfull? 153 | //$('#notice').html(response); 154 | localStorage['weeklyAlarmReminder'] = false; 155 | window.alarmManager.doToggleAlarm(); 156 | restartCountDown(); 157 | $('#payment-history').effect("highlight", { 158 | color: 'rgb(100, 189, 99)' 159 | }, 4000); 160 | db.clear('sites'); 161 | updateBalance(wallet.getAddress()); 162 | $('#notice').html('Transaction Submitted'); 163 | $('#notice-dialogue').fadeIn().slideDown(); 164 | $('#donate-now').button('reset'); 165 | $('#confirm-donate-now-dialogue').slideUp().fadeOut(); 166 | }); 167 | }); 168 | 169 | $('#dismiss-manual-reminder-popover').click(function(){ 170 | $('#donate-now-reminder').fadeOut('fast'); 171 | }); 172 | 173 | $( "#slider" ).slider({ 174 | range: "max", 175 | min: 0.03, 176 | max: 10, 177 | value: parseFloat(localStorage['incidentalTotalFiat']), 178 | slide: function( event, ui ) { 179 | $( "#incidental-fiat-amount" ).val( parseFloat(ui.value)); 180 | $('#incidental-fiat-amount').trigger('change'); 181 | } 182 | }); 183 | 184 | $("input[name=remind-me]:radio").change(function(value) { 185 | if (this.value == 'automaticDonate') { 186 | localStorage['automaticDonate'] = true; 187 | localStorage['manualRemind'] = false; 188 | alarmManager.alarmExpired(localStorage['alarmExpireDate'], function(expired){ 189 | if (expired) { 190 | restartTheWeek(); 191 | window.alarmManager.doToggleAlarm(); 192 | restartCountDown(); 193 | } 194 | }); 195 | } else { 196 | localStorage['automaticDonate'] = false; 197 | localStorage['manualRemind'] = true; 198 | } 199 | //restartCountDown(); 200 | }); 201 | 202 | $('#incidental-fiat-amount').change(function() { 203 | localStorage['incidentalTotalFiat'] = setMinIncidentalFiatAmounts($(this).val()); 204 | $(this).val(setMinIncidentalFiatAmounts($(this).val())); 205 | 206 | var availableBalanceFiat = parseFloat(localStorage['availableBalanceFiat']); 207 | var bitcoinFeeFiat = parseFloat(localStorage['bitcoinFeeFiat']); 208 | var totalSubscriptionsFiat = parseFloat(localStorage['subscriptionTotalFiat']); 209 | var incidentalTotalFiat = parseFloat($(this).val()); 210 | 211 | var weeklyTotalFiat = bitcoinFeeFiat + totalSubscriptionsFiat + incidentalTotalFiat; 212 | // if (availableBalanceFiat > 0 && weeklyTotalFiat > availableBalanceFiat - bitcoinFeeFiat) { 213 | // weeklyTotalFiat = availableBalanceFiat - bitcoinFeeFiat; 214 | // $(this).attr('max', incidentalTotalFiat); 215 | // } 216 | 217 | var balanceCoversXWeeks = (availableBalanceFiat - weeklyTotalFiat) / weeklyTotalFiat; 218 | 219 | if (balanceCoversXWeeks < 0) { 220 | balanceCoversXWeeks = 0 221 | } // initalization with empty wallet. 222 | 223 | $('#balance-covers-weeks').html(balanceCoversXWeeks.toFixed(1)); 224 | $('#balance-covers-weeks').effect("highlight", { 225 | color: 'rgb(100, 189, 99)' 226 | }, 400); 227 | 228 | $('#total-fiat-amount').html(parseFloat(weeklyTotalFiat).toFixed(2)); // use standard money formattor 229 | $('#weekly-spend-manual-pay-reminder-btn').html(parseFloat(weeklyTotalFiat).toFixed(2)); // use standard money formattor 230 | $( "#slider" ).slider({value: $(this).val() }); 231 | }); 232 | 233 | $('#donate-now').click(function() { 234 | $('#insufficient-funds-dialogue').slideUp().fadeOut(); 235 | $('#notice-dialogue').slideUp().fadeOut(); 236 | var totalFiatAmount = parseFloat($('#total-fiat-amount').html()); 237 | var currentBalance = parseFloat(localStorage['availableBalanceFiat']); 238 | if(parseFloat(localStorage['incidentalTotalFiat']) + parseFloat(localStorage['subscriptionTotalFiat']) <= 0){ 239 | $('#notice').html('No funds allocated to weekly subscriptions or browsing.'); 240 | $('#notice-dialogue').slideDown().fadeIn(); 241 | } else if (totalFiatAmount > currentBalance) { 242 | $('#insufficient-funds-dialogue').slideDown().fadeIn(); 243 | } else { 244 | $('#confirm-donate-now-dialogue').slideDown().fadeIn(); 245 | } 246 | }); 247 | 248 | $('#confirm-donate-cancel').click(function() { 249 | $('#confirm-donate-now-dialogue').slideUp().fadeOut(); 250 | }); 251 | 252 | if (typeof localStorage['automaticDonate'] === "undefined" && typeof localStorage['manualRemind'] === "undefined") { 253 | localStorage['manualRemind'] = true; 254 | } 255 | 256 | if (localStorage['weeklyAlarmReminder'] == "true" && localStorage['manualRemind'] == "true") { 257 | $('#donate-now-reminder').show(); 258 | } 259 | 260 | if (localStorage['automaticDonate'] == 'true') { 261 | $('#automaticDonate').prop('checked', true); 262 | $('#automatic-donate-container').addClass('list-group-item-success'); 263 | } else { 264 | $('#manualRemind').prop('checked', true); 265 | $('#manual-remind-container').toggleClass('list-group-item-success'); 266 | } 267 | 268 | if (localStorage['automaticDonate'] == "true") { 269 | $('#automaticDonate').prop('checked', true) 270 | } 271 | 272 | if (typeof localStorage['showBitcoinArtists'] === "undefined") { 273 | localStorage['showBitcoinArtists'] = true; 274 | } 275 | 276 | if (localStorage['showBitcoinArtists'] == 'true') { 277 | $('#show-bitcoin-artists').show(); 278 | } 279 | 280 | $('#hideBitcoinArtists').click(function() { 281 | localStorage['showBitcoinArtists'] = false; 282 | $('#show-bitcoin-artists').fadeOut(); 283 | }); 284 | 285 | $('#toggle-alarm').click(function() { 286 | window.alarmManager.doToggleAlarm(); 287 | restartCountDown(); 288 | }); 289 | 290 | $("#clear-data").click(function() { 291 | $('#browsing-table').fadeOut(); 292 | $('#browsing-table').empty(); 293 | db.clear('sites'); 294 | }); 295 | }); 296 | --------------------------------------------------------------------------------