├── .gitignore ├── img ├── app.png ├── digg.png ├── google.png ├── hyves.png ├── icon16.png ├── icon32.png ├── icon48.png ├── mailto.png ├── pingfm.png ├── reddit.png ├── share.png ├── tumblr.png ├── yahoo.png ├── addthis.png ├── blogger.png ├── facebook.png ├── icon128.png ├── identica.png ├── linkedin.png ├── netvibes.png ├── scoopit.png ├── twitter.png ├── livejournal.png ├── pinterest.png ├── popup_close.png ├── posterous.png ├── stumbleupon.png ├── technorati.png └── researchgate.png ├── screenshot ├── marquee.png ├── large-tile.png ├── small-tile.png ├── presentation .png └── small-tile-2.png ├── js ├── updates.js ├── functions.js ├── settings.js ├── options.js ├── extended_shares.js ├── background_controller.js └── extended_injection.js ├── AUTHORS.md ├── _locales ├── de │ └── messages.json └── en │ └── messages.json ├── css ├── update.css ├── options_custom.css ├── extended_injection.css ├── options.css └── common.css ├── updates.html ├── README.md ├── manifest.json └── options.html /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | -------------------------------------------------------------------------------- /img/app.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/altryne/extended-share-extension/master/img/app.png -------------------------------------------------------------------------------- /img/digg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/altryne/extended-share-extension/master/img/digg.png -------------------------------------------------------------------------------- /img/google.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/altryne/extended-share-extension/master/img/google.png -------------------------------------------------------------------------------- /img/hyves.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/altryne/extended-share-extension/master/img/hyves.png -------------------------------------------------------------------------------- /img/icon16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/altryne/extended-share-extension/master/img/icon16.png -------------------------------------------------------------------------------- /img/icon32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/altryne/extended-share-extension/master/img/icon32.png -------------------------------------------------------------------------------- /img/icon48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/altryne/extended-share-extension/master/img/icon48.png -------------------------------------------------------------------------------- /img/mailto.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/altryne/extended-share-extension/master/img/mailto.png -------------------------------------------------------------------------------- /img/pingfm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/altryne/extended-share-extension/master/img/pingfm.png -------------------------------------------------------------------------------- /img/reddit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/altryne/extended-share-extension/master/img/reddit.png -------------------------------------------------------------------------------- /img/share.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/altryne/extended-share-extension/master/img/share.png -------------------------------------------------------------------------------- /img/tumblr.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/altryne/extended-share-extension/master/img/tumblr.png -------------------------------------------------------------------------------- /img/yahoo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/altryne/extended-share-extension/master/img/yahoo.png -------------------------------------------------------------------------------- /img/addthis.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/altryne/extended-share-extension/master/img/addthis.png -------------------------------------------------------------------------------- /img/blogger.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/altryne/extended-share-extension/master/img/blogger.png -------------------------------------------------------------------------------- /img/facebook.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/altryne/extended-share-extension/master/img/facebook.png -------------------------------------------------------------------------------- /img/icon128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/altryne/extended-share-extension/master/img/icon128.png -------------------------------------------------------------------------------- /img/identica.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/altryne/extended-share-extension/master/img/identica.png -------------------------------------------------------------------------------- /img/linkedin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/altryne/extended-share-extension/master/img/linkedin.png -------------------------------------------------------------------------------- /img/netvibes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/altryne/extended-share-extension/master/img/netvibes.png -------------------------------------------------------------------------------- /img/scoopit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/altryne/extended-share-extension/master/img/scoopit.png -------------------------------------------------------------------------------- /img/twitter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/altryne/extended-share-extension/master/img/twitter.png -------------------------------------------------------------------------------- /img/livejournal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/altryne/extended-share-extension/master/img/livejournal.png -------------------------------------------------------------------------------- /img/pinterest.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/altryne/extended-share-extension/master/img/pinterest.png -------------------------------------------------------------------------------- /img/popup_close.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/altryne/extended-share-extension/master/img/popup_close.png -------------------------------------------------------------------------------- /img/posterous.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/altryne/extended-share-extension/master/img/posterous.png -------------------------------------------------------------------------------- /img/stumbleupon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/altryne/extended-share-extension/master/img/stumbleupon.png -------------------------------------------------------------------------------- /img/technorati.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/altryne/extended-share-extension/master/img/technorati.png -------------------------------------------------------------------------------- /img/researchgate.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/altryne/extended-share-extension/master/img/researchgate.png -------------------------------------------------------------------------------- /screenshot/marquee.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/altryne/extended-share-extension/master/screenshot/marquee.png -------------------------------------------------------------------------------- /screenshot/large-tile.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/altryne/extended-share-extension/master/screenshot/large-tile.png -------------------------------------------------------------------------------- /screenshot/small-tile.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/altryne/extended-share-extension/master/screenshot/small-tile.png -------------------------------------------------------------------------------- /js/updates.js: -------------------------------------------------------------------------------- 1 | document.getElementById('version').innerHTML = ' v' + chrome.extension.getBackgroundPage().settings.version ; 2 | -------------------------------------------------------------------------------- /screenshot/presentation .png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/altryne/extended-share-extension/master/screenshot/presentation .png -------------------------------------------------------------------------------- /screenshot/small-tile-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/altryne/extended-share-extension/master/screenshot/small-tile-2.png -------------------------------------------------------------------------------- /AUTHORS.md: -------------------------------------------------------------------------------- 1 | Extended Share awesome list of contributors 2 | 3 | Maintainer 4 | ------------ 5 | Mohamed Mansour (https://github.com/mohamedmansour) 6 | 7 | Contributors 8 | ------------ 9 | * Mohamed Mansour (https://github.com/mohamedmansour) 10 | * Yuxuan Wang (https://github.com/fishy) 11 | * Maxime Thirouin (https://github.com/MoOx) 12 | * Christian Jung (https://github.com/campino2k) 13 | * Jim Tittsler (https://github.com/jimt) 14 | * Huy Zing (https://github.com/huyz) 15 | * Bruno Leonardo Michels (https://github.com/brunolm) 16 | * Matt Senter (https://github.com/kungfuters) 17 | -------------------------------------------------------------------------------- /_locales/de/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "extName": { "message": "Extended Share for Google Plus"}, 3 | "extDescription": { "message": "Extends Google+ to share to Facebook, LinkedIn, Twitter, and many more."}, 4 | "injectionNoShareLinks": { "message": "No share links enabled, visit options to add a couple!"}, 5 | "injectionError": { "message": "Cannot find URL, please file bug to developer. mhm@chromium.org"}, 6 | "injectionCannotShareSincePublic": { "message": "You cannot share this post because it is not public."}, 7 | "injectionShareOnNetwork": { "message": "Share on" }, 8 | "injectionPublicLabel": { "message": "Öffentlich" } 9 | } -------------------------------------------------------------------------------- /_locales/en/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "extName": { "message": "Extended Share for Google Plus"}, 3 | "extDescription": { "message": "Extends Google+ to share to Facebook, LinkedIn, Twitter, and many more."}, 4 | "injectionNoShareLinks": { "message": "No share links enabled, visit options to add a couple!"}, 5 | "injectionError": { "message": "Cannot find URL, please file bug to developer. mhm@chromium.org"}, 6 | "injectionCannotShareSincePublic": { "message": "You cannot share this post because it is not public."}, 7 | "injectionShareOnNetwork": { "message": "Share on" }, 8 | "injectionPublicLabel": { "message": "Public" } 9 | } 10 | -------------------------------------------------------------------------------- /js/functions.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Global functions. 3 | * 4 | * @author Mohamed Mansour 2011 (http://mohamedmansour.com) 5 | */ 6 | 7 | /** 8 | * Short form for getting elements by id. 9 | * @param {string} id The id. 10 | */ 11 | function $(id) { 12 | return document.getElementById(id); 13 | } 14 | 15 | /** 16 | * Asynchronously load the file to the current DOM. 17 | * 18 | * @parm {HTMLElement} parent The DOM to append to. 19 | * @parma {string} file The file to inject. 20 | */ 21 | function loadScript(parent, file) { 22 | var script = document.createElement('script'); 23 | script.src = chrome.extension.getURL(file); 24 | parent.appendChild(script); 25 | } 26 | -------------------------------------------------------------------------------- /css/update.css: -------------------------------------------------------------------------------- 1 | body { 2 | color: white; 3 | } 4 | 5 | h1 { 6 | text-align: center; 7 | } 8 | 9 | a { 10 | color: #ccc; 11 | text-decoration: none; 12 | } 13 | a:hover { 14 | background-color: #eee; 15 | color: #333; 16 | } 17 | 18 | ul { 19 | list-style-type: none; 20 | margin: 0; 21 | padding: 5px; 22 | } 23 | 24 | li { 25 | margin: 20px 0; 26 | } 27 | .issue { 28 | border: 1px dotted white; 29 | padding: 4px; 30 | } 31 | .status { 32 | border-radius: 5px; 33 | padding: 4px; 34 | } 35 | .fixed { 36 | background-color: #cc0000; 37 | } 38 | .feature { 39 | background-color: green; 40 | } 41 | .enhancement { 42 | background-color: orange; 43 | } -------------------------------------------------------------------------------- /updates.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 |

Just updated to !

7 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /css/options_custom.css: -------------------------------------------------------------------------------- 1 | .shareIcon { 2 | text-align: center; 3 | border: 1px solid transparent; 4 | cursor: pointer; 5 | padding: 0; 6 | margin: 5px; 7 | width: 75px; 8 | height: 72px; 9 | float: left; 10 | border-radius: 10px; 11 | -webkit-transition: background-color .25s linear; 12 | } 13 | .shareIcon input, .shareIcon img, .shareIcon p { 14 | display: block; 15 | padding: 0; 16 | margin: 0; 17 | } 18 | .shareIcon input { 19 | width: 75px; 20 | display: none; 21 | } 22 | .shareIcon img { 23 | margin: 0 auto; 24 | width: 32px; 25 | height: 32px; 26 | padding-top: 10px; 27 | } 28 | .shareIcon p { 29 | height: 40px; 30 | font-size: 0.9em; 31 | } 32 | 33 | .shareIcon:hover, .shareIcon.checked { 34 | border-color: #ccc; 35 | background-color: #fff; 36 | } 37 | 38 | .shareIcon.checked , .shareIcon.checked:hover { 39 | background-color: #333; 40 | color: #eee; 41 | } -------------------------------------------------------------------------------- /css/extended_injection.css: -------------------------------------------------------------------------------- 1 | .gp-crx-settings { 2 | cursor: pointer; 3 | position: absolute; 4 | right: 0; 5 | bottom: 0; 6 | padding-right: 5px; 7 | font-size: 10px; 8 | color: #aaa; 9 | } 10 | 11 | .gp-crx-close { 12 | cursor: pointer; 13 | background: url('chrome-extension://__MSG_@@extension_id__/img/popup_close.png') no-repeat; 14 | border: 1px solid transparent; 15 | height: 21px; 16 | opacity: .4; 17 | outline: 0; 18 | position: absolute; 19 | right: 2px; 20 | top: 2px; 21 | width: 21px; 22 | } 23 | 24 | .gp-crx-shelf { 25 | border-left: 1px solid #ccc; 26 | border-right: 1px solid #ccc; 27 | background-color: #F2F2F2; 28 | border-bottom: 1px solid #CCC; 29 | box-shadow: 1px 1px 10px #ccc; 30 | color: #999; 31 | cursor: pointer; 32 | font-weight: bold; 33 | overflow: hidden; 34 | white-space: nowrap; 35 | vertical-align: top; 36 | height: 1px; 37 | padding: 10px; 38 | position: relative; 39 | left: 0; 40 | right: 0; 41 | z-index: 900; 42 | -webkit-transition: height 0.3s linear; 43 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Extended Share for Google+ Chrome Extension 2 | ===================================== 3 | 4 | This Google Chrome extension will inject a link to your Google Plus profile so 5 | that you can share your post to many social networks. 6 | 7 | Contribute! 8 | ----------------- 9 | Please submit your pull requests if you have anything interesting! Let me know 10 | prior implementation :) The people who contributed to this project are mentioned 11 | here in [AUTHORS](https://github.com/mohamedmansour/extended-share-extension/raw/master/AUTHORS.md) 12 | 13 | Follow me on [Google+](https://plus.google.com/116805285176805120365/about) 14 | 15 | Icons 16 | ----------------- 17 | Social Network Icon Pack by [Komodo Media, Rogie King](http://www.komodomedia.com/) is licensed under a [Creative Commons Attribution-Share Alike 3.0 Unported License](http://creativecommons.org/licenses/by-sa/3.0/). 18 | Based on a work at [www.komodomedia.com](http://www.komodomedia.com/download/#social-network-icon-pack). 19 | Vector Icon by [+G. Hussain Chinoy](https://plus.google.com/u/0/114930558215776389910). 20 | 21 | Screenshots 22 | ----------------- 23 | ![Screenshot of the Chrome Extension](https://github.com/mohamedmansour/extended-share-extension/raw/master/screenshot/marquee.png) 24 | 25 | 26 | -------------------------------------------------------------------------------- /js/settings.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Global Settings. 3 | * 4 | * @author Mohamed Mansour 2011 (http://mohamedmansour.com) 5 | */ 6 | settings = { 7 | get version() { 8 | return localStorage['version']; 9 | }, 10 | set version(val) { 11 | localStorage['version'] = val; 12 | }, 13 | get opt_out() { 14 | var key = localStorage['opt_out']; 15 | return (typeof key == 'undefined') ? false : key === 'true'; 16 | }, 17 | set opt_out(val) { 18 | localStorage['opt_out'] = val; 19 | }, 20 | get shares() { 21 | var key = localStorage['shares']; 22 | return (typeof key == 'undefined') ? ['facebook', 'twitter'] : (key == '' ? [] : key.split(', ')); 23 | }, 24 | set shares(val) { 25 | if (typeof val == 'object') { 26 | localStorage['shares'] = val.sort().join(', '); 27 | } 28 | }, 29 | get open_as_popup() { 30 | var key = localStorage['open_as_popup']; 31 | return (typeof key == 'undefined') ? true : key === 'true'; 32 | }, 33 | set open_as_popup(val) { 34 | localStorage['open_as_popup'] = val; 35 | }, 36 | get auto_close_shelf() { 37 | var key = localStorage['auto_close_shelf']; 38 | return (typeof key == 'undefined') ? false : key === 'true'; 39 | }, 40 | set auto_close_shelf(val) { 41 | localStorage['auto_close_shelf'] = val; 42 | }, 43 | get use_link() { 44 | var key = localStorage['use_link']; 45 | return (typeof key == 'undefined') ? true : key === 'true'; 46 | }, 47 | set use_link(val) { 48 | localStorage['use_link'] = val; 49 | } 50 | }; 51 | -------------------------------------------------------------------------------- /manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "__MSG_extName__", 3 | "version": "4.0.3", 4 | "manifest_version": 2, 5 | "description": "__MSG_extDescription__", 6 | "default_locale": "en", 7 | "icons": { 8 | "16": "img/icon16.png", 9 | "32": "img/icon32.png", 10 | "48": "img/icon48.png", 11 | "128": "img/icon128.png" 12 | }, 13 | "permissions": ["tabs", "https://plus.google.com/*"], 14 | "background": { 15 | "scripts": [ "js/settings.js", "js/extended_shares.js", "js/background_controller.js" ] 16 | }, 17 | "options_page": "options.html", 18 | "content_scripts": [{ 19 | "matches": ["https://plus.google.com/*"], 20 | "js": ["js/extended_shares.js", "js/extended_injection.js"], 21 | "css": ["css/extended_injection.css"], 22 | "run_at": "document_end", 23 | "all_frames": true 24 | }], 25 | "web_accessible_resources": [ 26 | "img/app.png", 27 | "img/addthis.png", 28 | "img/blogger.png", 29 | "img/digg.png", 30 | "img/facebook.png", 31 | "img/google.png", 32 | "img/hyves.png", 33 | "img/identica.png", 34 | "img/linkedin.png", 35 | "img/livejournal.png", 36 | "img/mailto.png", 37 | "img/netvibes.png", 38 | "img/pingfm.png", 39 | "img/pinterest.png", 40 | "img/popup_close.png", 41 | "img/posterous.png", 42 | "img/researchgate.png", 43 | "img/reddit.png", 44 | "img/scoopit.png", 45 | "img/share.png", 46 | "img/stumbleupon.png", 47 | "img/technorati.png", 48 | "img/tumblr.png", 49 | "img/twitter.png", 50 | "img/yahoo.png" 51 | ], 52 | "content_security_policy": "script-src 'self' https://apis.google.com; object-src 'self'" 53 | } 54 | -------------------------------------------------------------------------------- /css/options.css: -------------------------------------------------------------------------------- 1 | /* Extension Options Default Style */ 2 | 3 | body { 4 | min-width: 800px; 5 | } 6 | 7 | #header h1 { 8 | background: url("/img/icon48.png") 0px 30px no-repeat; 9 | display: inline; 10 | margin: 0; 11 | padding-bottom: 43px; 12 | padding-left: 55px; 13 | padding-top: 35px; 14 | font-size: 150%; 15 | font-weight: bold; 16 | } 17 | 18 | em { 19 | font-size: 50%; 20 | font-weight: normal; 21 | } 22 | 23 | dt { 24 | width: 15em; 25 | float: left; 26 | padding: .4em; 27 | } 28 | 29 | dd { 30 | margin-left: 16em; 31 | padding: .4em; 32 | } 33 | 34 | a { 35 | color: blue; 36 | font-size: 100%; 37 | } 38 | 39 | p { 40 | margin: 0; 41 | padding: 0; 42 | } 43 | 44 | .extension-template { 45 | border-bottom: 1px solid #cdcdcd; 46 | margin: 10px 0; 47 | } 48 | 49 | .extension-header { 50 | background: #ebeff9; 51 | border-top: 1px solid #b5c7de; 52 | padding: 3px 5px; 53 | font-weight: bold; 54 | } 55 | 56 | .extension-header span { 57 | font-size: 0.7em; 58 | color: #333; 59 | } 60 | 61 | .extension-options { 62 | background: #f4f6fc; 63 | border-bottom: 1px solid #edeff5; 64 | font-size: 85%; 65 | padding: 0.8em 10px; 66 | } 67 | 68 | .extension-options li { 69 | margin: 2px 0; 70 | } 71 | .note { 72 | background-color: #ccc; 73 | border-radius: 5px; 74 | padding: 3px 5px; 75 | margin-right: 30px; 76 | color: #333; 77 | font-size: 0.9em; 78 | } 79 | 80 | #buttons { 81 | float: right; 82 | text-align: right; 83 | padding: 10px 0px; 84 | position: absolute; 85 | right: 5px; 86 | bottom: 0px; 87 | } 88 | 89 | #header { 90 | margin-bottom: 1.05em; 91 | overflow: hidden; 92 | padding-left: 1.5em; 93 | padding-top: 1.5em; 94 | position: fixed; 95 | width: 100%; 96 | top: 0; 97 | left: 0; 98 | background-color: white; 99 | min-width: 960px; 100 | } 101 | 102 | #header p { 103 | padding-left: 55px; 104 | margin-bottom: 5px; 105 | } 106 | 107 | #footer { 108 | position: fixed; 109 | width: 100%; 110 | box-shadow: -5px -5px 5px #ebeff9; 111 | background-image: -webkit-gradient( 112 | linear, 113 | left bottom, 114 | left top, 115 | color-stop(0.03, #bebebe), 116 | color-stop(0.52, #bcbcbc), 117 | color-stop(0.76, #ccc) 118 | ); 119 | bottom: 0; 120 | left: 0; 121 | float: right; 122 | height: 50px; 123 | } 124 | 125 | #main { 126 | margin-top: 85px; 127 | margin-bottom: 50px; 128 | overflow: hidden; 129 | height: 100%; 130 | } 131 | 132 | #info-message { 133 | -webkit-transition-duration: 1s; 134 | display: none; 135 | background:#FDFFBD none repeat scroll 0 0; 136 | border-radius: 5px; 137 | padding: 7px 10px; 138 | margin-right: 30px; 139 | } 140 | 141 | #credits { 142 | margin-left: 10px; 143 | font-size: 85%; 144 | float: left; 145 | line-height: 50px; 146 | } 147 | 148 | #button-save { 149 | background: -webkit-linear-gradient(#ff0000, #cc0000 40%, #ff0000); 150 | color: #fff; 151 | } 152 | 153 | .widget { 154 | float: right; 155 | width: 250px; 156 | } 157 | 158 | #updatecontainer, #updateframe { 159 | display: block; 160 | position: fixed; 161 | top: 0; 162 | left: 0; 163 | right: 0; 164 | bottom: 0; 165 | margin: auto auto; 166 | background: -webkit-radial-gradient(rgba(127, 127, 127, 0.5), rgba(127, 127, 127, 0.5) 35%, rgba(0, 0, 0, 0.7)); 167 | } 168 | #updatecontainer div { 169 | top: 0; 170 | bottom: auto; 171 | margin: 0 auto; 172 | background-color: green; 173 | border-bottom-right-radius: 10px; 174 | border-bottom-left-radius: 10px; 175 | text-align: center; 176 | box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.8); 177 | color: white; 178 | padding: 10px; 179 | width: 100px; 180 | cursor: pointer; 181 | } 182 | #updatecontainer div:hover { 183 | background-color: red; 184 | } 185 | #updateframe { 186 | width: 570px; 187 | min-height: 330px; 188 | border: 0; 189 | background-color: rgba(0, 0, 0, 0.8); 190 | box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.8); 191 | } -------------------------------------------------------------------------------- /options.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Extended Share for Google Plus 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 | 30 |
31 |
32 |
Support my open source development!
33 |
34 |
35 |
My Code for Charity fundraiser!
36 |
37 | Help the cause and 38 | 39 | It will really make a difference and will give me energy and motivation to continue coding :) 40 |
41 |
Support future open source projects
42 |
43 | Donate to my 44 | 45 | so I can use the funds to develop (server costs, hardware) more projects. 46 |
47 |
48 |
49 |
50 |
51 |
Shares
52 |
53 |
54 |
What share icons to show?
This will not autoshare.
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
Customizable Options
64 |
65 |
66 |
Open share as a popup
67 |
68 | 69 | If checked, this will create a popup window for the share. 70 |
71 |
Auto close shelf box
72 |
73 | 74 | If checked, the share shelf will automatically close when you click on any share item or option. 75 |
76 |
Use post's link as share URL
77 |
78 | 79 | If a post has a link, then that link is shared and not the Google+ post URL. 80 |
81 |
82 |
83 |
84 |
85 |
Misc
86 |
87 |

If you would like to be notified of major upgrades to this extension, 88 | please make sure the box below is unchecked. You wont be spammed.

89 |
90 |
Opt-Out of future notifications:
91 |
92 | 93 |
94 |
95 |
96 |
97 |
98 | 107 |
108 | 109 | 110 | -------------------------------------------------------------------------------- /js/options.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Options controller. 3 | * 4 | * @author Mohamed Mansour 2011 (http://mohamedmansour.com) 5 | */ 6 | 7 | // Extensions pages can all have access to the bacground page. 8 | var bkg = chrome.extension.getBackgroundPage(); 9 | 10 | // When the DOM is loaded, make sure all the saved info is restored. 11 | window.addEventListener('load', onLoad, false); 12 | 13 | /** 14 | * When the options window has been loaded. 15 | */ 16 | function onLoad() { 17 | onRestore(); 18 | onRenderGooglePlus(); 19 | onRenderUpdate(); 20 | $('button-close').addEventListener('click', onClose, false); 21 | $('donate').addEventListener('click', onDonate, false); 22 | $('charity').addEventListener('click', onCharity, false); 23 | } 24 | 25 | /** 26 | * When the options window is closed; 27 | */ 28 | function onClose() { 29 | window.close(); 30 | } 31 | 32 | function onDonate() { 33 | chrome.tabs.create({url: 'http://mohamedmansour.com/donate'}); 34 | } 35 | 36 | function onCharity() { 37 | chrome.tabs.create({url: 'http://www.crowdrise.com/code-for-charity'}); 38 | } 39 | 40 | function onRenderGooglePlus() { 41 | var script = document.createElement('script'); 42 | script.src = 'https://apis.google.com/js/plusone.js'; 43 | script.innerText = '{lang: "en"}'; 44 | document.body.appendChild(script); 45 | } 46 | 47 | function onRenderUpdate() { 48 | if (location.hash === '#updated') { 49 | var iframeContainer = document.createElement('div'); 50 | iframeContainer.id = 'updatecontainer'; 51 | 52 | var closeContainer = document.createElement('div'); 53 | closeContainer.innerText = 'close dialog'; 54 | iframeContainer.appendChild(closeContainer); 55 | iframeContainer.onclick = function(e) { 56 | iframeContainer.parentNode.removeChild(iframeContainer); 57 | }; 58 | 59 | var iframe = document.createElement('iframe'); 60 | iframe.id = 'updateframe'; 61 | iframe.src = chrome.extension.getURL('updates.html'); 62 | iframeContainer.appendChild(iframe); 63 | document.body.appendChild(iframeContainer); 64 | } 65 | } 66 | 67 | function shareRendered(shareElement) { 68 | var labelElement = shareElement.parentNode; 69 | var classList = labelElement.classList; 70 | if (shareElement.checked) { 71 | classList.add('checked'); 72 | } 73 | else { 74 | classList.remove('checked'); 75 | } 76 | } 77 | 78 | /** 79 | * Saves options to localStorage. 80 | */ 81 | function shareUpdated() { 82 | var shares = []; 83 | var shareNodes = document.querySelectorAll("input[name='shares']:checked"); 84 | for (var i = 0; i < shareNodes.length; i++) { 85 | shares.push(shareNodes[i].id); 86 | } 87 | bkg.settings.shares = shares; 88 | bkg.controller.updateSettings(); 89 | } 90 | 91 | /** 92 | * Restore all options. 93 | */ 94 | function onRestore() { 95 | // Restore settings. 96 | $('version').innerHTML = ' (v' + bkg.settings.version + ')'; 97 | 98 | addCheckboxOption('opt_out'); 99 | addCheckboxOption('open_as_popup'); 100 | addCheckboxOption('auto_close_shelf'); 101 | addCheckboxOption('use_link'); 102 | 103 | var container_shares = $('container-shares'); 104 | for (var share in Shares) { 105 | if (Shares.hasOwnProperty(share)) { 106 | container_shares.appendChild(createSharesItem(share)); 107 | } 108 | } 109 | 110 | if (bkg.settings.shares) { 111 | var shares = bkg.settings.shares; 112 | for (var share in shares) { 113 | var shareDOM = $(shares[share]); 114 | if (shareDOM) { 115 | shareDOM.checked = true; 116 | shareRendered(shareDOM); 117 | } 118 | } 119 | } 120 | } 121 | 122 | function addCheckboxOption(shareName) { 123 | var elt = $(shareName); 124 | elt.addEventListener('click', function(e) { 125 | bkg.settings[shareName] = elt.checked; 126 | bkg.controller.updateSettings(); 127 | }); 128 | elt.checked = bkg.settings[shareName]; 129 | } 130 | 131 | /** 132 | * Creates the share item for each social feed. 133 | * 134 | * @param {object} shareItem The item that is being shared for social. 135 | * @return the DOM to create. 136 | */ 137 | function createSharesItem(share) { 138 | var shareItem = Shares[share]; 139 | 140 | // Render label. 141 | var label = document.createElement('label'); 142 | label.setAttribute('class', 'shareIcon'); 143 | label.setAttribute('for', share); 144 | 145 | // Render input. 146 | var input = document.createElement('input'); 147 | input.setAttribute('type', 'checkbox'); 148 | input.setAttribute('name', 'shares'); 149 | input.setAttribute('id', share); 150 | label.appendChild(input); 151 | 152 | // Render icon. 153 | var icon = document.createElement('image'); 154 | icon.src = shareItem.icon; 155 | icon.title = shareItem.name; 156 | label.appendChild(icon); 157 | 158 | // Render name. 159 | var name = document.createElement('p'); 160 | name.innerText = shareItem.name; 161 | label.appendChild(name); 162 | 163 | // Persist event. 164 | input.addEventListener('click', function(e) { 165 | shareRendered(e.target); 166 | shareUpdated(); 167 | }); 168 | 169 | return label; 170 | } 171 | -------------------------------------------------------------------------------- /js/extended_shares.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Shares List. 3 | * 4 | * The format to share would be adding a JSON object that has the keys: 5 | * - name (The name of the extension that appears on the hover) 6 | * - icon (relative path of the icon) 7 | * - url (contains some attributes that will be replaced) 8 | * - trim (true to trim upto 100 chars) 9 | * - media (true if the post contains any images) 10 | * 11 | * The URL has the following keys that will be replaced: 12 | * - ${link} The link to share. 13 | * - ${text} The text to place. 14 | * - ${title} The title of the post. 15 | * - ${media} The URL to the media resource. 16 | * 17 | * @author Mohamed Mansour 2011 (http://mohamedmansour.com) 18 | */ 19 | Shares = { 20 | app: { 21 | name: 'App.net', 22 | icon: '/img/app.png', 23 | url: 'https://alpha.app.net/intent/post?text=${text}%20${link}', 24 | trim: true 25 | }, 26 | twitter: { 27 | name: 'Twitter', 28 | icon: '/img/twitter.png', 29 | url: 'http://twitter.com/share?url=${link}&text=${text}', 30 | trim: true 31 | }, 32 | facebook: { 33 | name: 'Facebook', 34 | icon: '/img/facebook.png', 35 | url: 'http://www.facebook.com/sharer.php?u=${link}&t=${text}', 36 | trim: false 37 | }, 38 | pinterest: { 39 | name: 'Pinterest', 40 | media: true, 41 | icon: '/img/pinterest.png', 42 | url: 'http://pinterest.com/pin/create/bookmarklet/?media=${media}&url=${link}&description=${text}', 43 | trim: false // should be 500 44 | }, 45 | linkedin: { 46 | name: 'Linkedin', 47 | icon: '/img/linkedin.png', 48 | url: 'http://www.linkedin.com/shareArticle?mini=true&url=${link}&title=${title}&summary=${text}', 49 | trim: false 50 | }, 51 | tumblr: { 52 | name: 'Tumblr', 53 | icon: '/img/tumblr.png', 54 | url: 'http://www.tumblr.com/share?v=3&u=${link}&t=${text}', 55 | trim: false 56 | }, 57 | identica: { 58 | name: 'Identica', 59 | icon: '/img/identica.png', 60 | url: 'http://identi.ca/notice/new?status_textarea=${text} ${link}', 61 | trim: false 62 | }, 63 | posterous: { 64 | name: 'Posterous', 65 | icon: '/img/posterous.png', 66 | url: 'http://posterous.com/share?linkto=${link}&title=${title}&selection=${text}', 67 | trim: false 68 | }, 69 | reddit: { 70 | name: 'Reddit', 71 | icon: '/img/reddit.png', 72 | url: 'http://www.reddit.com/submit?url=${link}&title=${title}', 73 | trim: false 74 | }, 75 | pingfm: { 76 | name: 'ping.fm', 77 | icon: '/img/pingfm.png', 78 | url: 'http://ping.fm/ref/?link=${link}&title=${title}', 79 | trim: false 80 | }, 81 | hyves: { 82 | name: 'hyves', 83 | icon: '/img/hyves.png', 84 | url: 'http://www.hyves-share.nl/button/tip/?tipcategoryid=12&rating=5&title=${title}&body=${text}[url=${link}]${link}[/url]', 85 | trim: false 86 | }, 87 | netvibes: { 88 | name: 'Netvibes', 89 | icon: '/img/netvibes.png', 90 | url: 'http://www.addtoany.com/add_to/netvibes_share?linkurl=${link}&linkname=${title}', 91 | trim: false 92 | }, 93 | technorati: { 94 | name: 'Technorati', 95 | icon: '/img/technorati.png', 96 | url: 'http://technorati.com/faves?sub=addfavbtn&add=${link}', 97 | trim: false 98 | }, 99 | stumbleupon: { 100 | name: 'StumbleUpon', 101 | icon: '/img/stumbleupon.png', 102 | url: 'http://www.stumbleupon.com/submit?url=${link}&title=${title}', 103 | trim: false 104 | }, 105 | yahoo: { 106 | name: 'Yahoo! Bookmarks', 107 | icon: '/img/yahoo.png', 108 | url: 'http://bookmarks.yahoo.com/toolbar/savebm?u=${link}&t=${title}&d=${text}', 109 | trim: false 110 | }, 111 | blogger: { 112 | name: 'Google Blogger', 113 | icon: '/img/blogger.png', 114 | url: 'http://www.blogger.com/blog-this.g?t&u=${link}&title=${title}&n=${text}&pli=1', 115 | trim: false 116 | }, 117 | digg: { 118 | name: 'Digg', 119 | icon: '/img/digg.png', 120 | url: 'http://digg.com/submit?phase=2&url=${link}&title=${title}&summary=${text}', 121 | trim: false 122 | }, 123 | google: { 124 | name: 'Google Bookmarks', 125 | icon: '/img/google.png', 126 | url: 'https://www.google.com/bookmarks/mark?op=edit&bkmk=${link}&title=${title}', 127 | trim: false 128 | }, 129 | addthis: { 130 | name: 'Add This', 131 | icon: '/img/addthis.png', 132 | url: 'http://www.addthis.com/bookmark.php?url=${link}&title=${text}', 133 | trim: false 134 | }, 135 | livejournal: { 136 | name: 'Live Journal', 137 | icon: '/img/livejournal.png', 138 | url: 'http://www.livejournal.com/update.bml/?event=${text}${title}&subject=${title}', 139 | trim: false 140 | }, 141 | mailto: { 142 | name: 'Email', 143 | icon: '/img/mailto.png', 144 | url: 'mailto:?subject=${title}&body=${text}+${link}', 145 | trim: false 146 | }, 147 | researchgate: { 148 | name: 'ResearchGate', 149 | icon: '/img/researchgate.png', 150 | url: 'https://www.researchgate.net/go.Share.html?url=${link}&title=${text}', 151 | trim: false 152 | }, 153 | scoopit: { 154 | name: 'Scoop.it!', 155 | icon: '/img/scoopit.png', 156 | url: 'http://www.scoop.it/bookmarklet?url=${link}', 157 | trim: false 158 | } 159 | }; 160 | -------------------------------------------------------------------------------- /js/background_controller.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Manages a single instance of the entire application. 3 | * 4 | * @author Mohamed Mansour 2011 (http://mohamedmansour.com) 5 | * @constructor 6 | */ 7 | BackgroundController = function() { 8 | this.onExtensionLoaded(); 9 | }; 10 | 11 | /** 12 | * Triggered when the extension just loaded. Should be the first thing 13 | * that happens when chrome loads the extension. 14 | */ 15 | BackgroundController.prototype.onExtensionLoaded = function() { 16 | var currVersion = chrome.app.getDetails().version; 17 | var prevVersion = settings.version; 18 | if (currVersion != prevVersion) { 19 | // Check if we just installed this extension. 20 | if (typeof prevVersion == 'undefined') { 21 | this.onInstall(); 22 | } else { 23 | this.onUpdate(prevVersion, currVersion); 24 | } 25 | settings.version = currVersion; 26 | } 27 | }; 28 | 29 | /** 30 | * Triggered when the extension just installed. 31 | */ 32 | BackgroundController.prototype.onInstall = function() { 33 | this.doWorkTabs(function(tab) { 34 | chrome.tabs.executeScript(tab.id, { file: 'js/extended_shares.js', 35 | allFrames: true }, function() { 36 | chrome.tabs.executeScript(tab.id, { file: 'js/extended_injection.js', 37 | allFrames: true }, function() { 38 | // This is needed because all the DOM is already inserted, no events 39 | // would have been fired. This will force the events to fire after 40 | // initial injection. 41 | chrome.tabs.sendRequest(tab.id, { 42 | method: 'InitialInjection' 43 | }); 44 | }); 45 | }); 46 | }); 47 | chrome.tabs.create({url: 'options.html'}); 48 | }; 49 | 50 | /** 51 | * Do some work on all tabs that are on Google Plus. 52 | * 53 | * @param {Function} callback The callback with the tab results. 54 | */ 55 | BackgroundController.prototype.doWorkTabs = function(callback) { 56 | self = this; 57 | chrome.windows.getAll({ populate: true }, function(windows) { 58 | for (var w = 0; w < windows.length; w++) { 59 | var tabs = windows[w].tabs; 60 | for (var t = 0; t < tabs.length; t++) { 61 | var tab = tabs[t]; 62 | if (self.isValidURL(tab.url)) { 63 | callback(tab); 64 | } 65 | } 66 | } 67 | }); 68 | }; 69 | 70 | /** 71 | * Inform all Content Scripts that new settings are available. 72 | */ 73 | BackgroundController.prototype.updateSettings = function() { 74 | self = this; 75 | this.doWorkTabs(function(tab) { 76 | chrome.tabs.sendRequest(tab.id, { 77 | method: 'SettingsUpdated', 78 | data: settings 79 | }); 80 | }); 81 | }; 82 | 83 | /** 84 | * Check if the URL is part of plus websites. 85 | 86 | * @param {string} url The URL to check if valid. 87 | */ 88 | BackgroundController.prototype.isValidURL = function(url) { 89 | return (url.indexOf('https://plus.google.com') == 0 || 90 | url.indexOf('http://plus.google.com') == 0); 91 | }; 92 | 93 | /** 94 | * Triggered when the extension just uploaded to a new version. DB Migrations 95 | * notifications, etc should go here. 96 | * 97 | * @param {string} previous The previous version. 98 | * @param {string} current The new version updating to. 99 | */ 100 | BackgroundController.prototype.onUpdate = function(previous, current) { 101 | if (!settings.opt_out) { 102 | chrome.tabs.create({url: 'options.html#updated'}); 103 | } 104 | }; 105 | 106 | /** 107 | * Initialize the main Background Controller 108 | */ 109 | BackgroundController.prototype.init = function() { 110 | // Listens on new tab updates. Google+ uses new sophisticated HTML5 history 111 | // push API, so content scripts don't get recognized always. We inject 112 | // the content script once, and listen for URL changes. 113 | chrome.tabs.onUpdated.addListener(this.tabUpdated.bind(this)); 114 | chrome.extension.onRequest.addListener(this.onExternalRequest.bind(this)); 115 | }; 116 | 117 | /** 118 | * Listens on new tab URL updates. We use this make sure we capture history 119 | * push API for asynchronous page reloads. 120 | * 121 | * @param {number} tabId Tab identifier that changed. 122 | * @param {object} changeInfo lists the changes of the states. 123 | * @param {object} tab The state of the tab that was updated. 124 | */ 125 | BackgroundController.prototype.tabUpdated = function(tabId, changeInfo, tab) { 126 | if (changeInfo.status == 'complete') { 127 | chrome.tabs.sendRequest(tabId, { method: 'RenderShares' }); 128 | } 129 | }; 130 | 131 | /** 132 | * Listen on requests coming from content scripts. 133 | * 134 | * @param {object} request The request object to match data. 135 | * @param {object} sender The sender object to know what the source it. 136 | * @param {Function} sendResponse The response callback. 137 | */ 138 | BackgroundController.prototype.onExternalRequest = function(request, sender, sendResponse) { 139 | if (request.method == 'GetSettings') { 140 | sendResponse({ 141 | data: settings 142 | }); 143 | } 144 | else if (request.method == 'OpenURL') { 145 | if (settings.open_as_popup) { 146 | chrome.windows.create({url: request.data, type: 'popup', width: 700, height: 400}); 147 | } 148 | else { 149 | chrome.tabs.create({url: request.data}); 150 | } 151 | sendResponse({}); 152 | } 153 | else { 154 | sendResponse({}); 155 | } 156 | }; 157 | 158 | var controller = new BackgroundController(); 159 | controller.init(); 160 | -------------------------------------------------------------------------------- /css/common.css: -------------------------------------------------------------------------------- 1 | /* Common Chrome Style */ 2 | body { 3 | font-size: 87%; 4 | margin: 10px; 5 | font-family: Arial, Helvetica, 'Helvetica Neue', Verdana, sans-serif; 6 | padding: 0; 7 | } 8 | .clear { 9 | clear: both; 10 | } 11 | 12 | /* Default state **************************************************************/ 13 | 14 | button:not(.custom-appearance):not(.link-button), 15 | input[type='button']:not(.custom-appearance):not(.link-button), 16 | input[type='submit']:not(.custom-appearance):not(.link-button), 17 | select, 18 | input[type='checkbox'], 19 | input[type='radio'] { 20 | -webkit-appearance: none; 21 | -webkit-user-select: none; 22 | background-image: -webkit-linear-gradient(#ededed, #ededed 38%, #dedede); 23 | border: 1px solid rgba(0, 0, 0, 0.25); 24 | border-radius: 2px; 25 | box-shadow: 0 1px 0 rgba(0, 0, 0, 0.08), 26 | inset 0 1px 2px rgba(255, 255, 255, 0.75); 27 | color: #444; 28 | font: inherit; 29 | margin: 0 1px 0 0; 30 | text-shadow: 0 1px 0 rgb(240, 240, 240); 31 | } 32 | 33 | button:not(.custom-appearance):not(.link-button), 34 | input[type='button']:not(.custom-appearance):not(.link-button), 35 | input[type='submit']:not(.custom-appearance):not(.link-button), 36 | select { 37 | min-height: 2em; 38 | min-width: 4em; 39 | 40 | /* The following platform-specific rule is necessary to get adjacent 41 | * buttons, text inputs, and so forth to align on their borders while also 42 | * aligning on the text's baselines. */ 43 | padding-bottom: 1px; 44 | } 45 | 46 | button:not(.custom-appearance):not(.link-button), 47 | input[type='button']:not(.custom-appearance):not(.link-button), 48 | input[type='submit']:not(.custom-appearance):not(.link-button) { 49 | -webkit-padding-end: 10px; 50 | -webkit-padding-start: 10px; 51 | } 52 | 53 | select { 54 | -webkit-appearance: none; 55 | -webkit-padding-end: 20px; 56 | -webkit-padding-start: 6px; 57 | /* OVERRIDE */ 58 | background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABMAAAAICAYAAAAbQcSUAAAAWklEQVQokWNgoAOIAuI0PDiKaJMSgYCZmfkbkPkfHYPEQfJEG/b//3+FBQsWLGRjY/uJbBCIDxIHyRNtGDYDyTYI3UA+Pr4vFBmEbODbt2+bKDYIyUBWYtQBAIRzRP/XKJ//AAAAAElFTkSuQmCC"), 59 | -webkit-linear-gradient(#ededed, #ededed 38%, #dedede); 60 | background-position: right center; 61 | background-repeat: no-repeat; 62 | } 63 | 64 | html[dir='rtl'] select { 65 | background-position: center left; 66 | } 67 | 68 | input[type='checkbox'] { 69 | bottom: 2px; 70 | height: 13px; 71 | position: relative; 72 | vertical-align: middle; 73 | width: 13px; 74 | } 75 | 76 | input[type='radio'] { 77 | /* OVERRIDE */ 78 | border-radius: 100%; 79 | bottom: 3px; 80 | height: 15px; 81 | position: relative; 82 | vertical-align: middle; 83 | width: 15px; 84 | } 85 | 86 | /* TODO(estade): add more types here? */ 87 | input[type='password'], 88 | input[type='search'], 89 | input[type='text'], 90 | input[type='url'], 91 | input:not([type]) { 92 | border: 1px solid #bfbfbf; 93 | border-radius: 2px; 94 | box-sizing: border-box; 95 | color: #444; 96 | font: inherit; 97 | height: 2em; 98 | margin: 0; 99 | padding: 3px; 100 | /* For better alignment between adjacent buttons and inputs. */ 101 | padding-bottom: 4px; 102 | 103 | } 104 | 105 | input[type='search'] { 106 | -webkit-appearance: textfield; 107 | /* NOTE: Keep a relatively high min-width for this so we don't obscure the end 108 | * of the default text in relatively spacious languages (i.e. German). */ 109 | min-width: 160px; 110 | } 111 | 112 | /* Checked ********************************************************************/ 113 | 114 | input[type='checkbox']:checked::before { 115 | -webkit-user-select: none; 116 | background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAsAAAALCAYAAACprHcmAAAAAXNSR0IArs4c6QAAAAZiS0dEAP8A/wD/oL2nkwAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB9wDBhYcG79aGIsAAACbSURBVBjTjdFBCkFhFAXgj4fp24PBy0SZ2ICRXRgYGb2xlKzBSEo2YgsiKWVoZgFKMjD5X/2Ux6lb99bpnNO5lKMR5i8MsEQHkhJiEzlS9HCqfiFWMUIt3AfsC3KKLCL30Qr7HfM4Ro4h6rhiEqmusIMKuphGqo+ogSPGcbYLzh91vdkXSHDDBk+0gxussS3rNcMCs+D6E18/9gLPPhbDshfzLgAAAABJRU5ErkJggg=="); 117 | background-size: 100% 100%; 118 | content: ''; 119 | display: block; 120 | height: 100%; 121 | width: 100%; 122 | } 123 | 124 | html[dir='rtl'] input[type='checkbox']:checked::before { 125 | -webkit-transform: scaleX(-1); 126 | } 127 | 128 | input[type='radio']:checked::before { 129 | background-color: #666; 130 | border-radius: 100%; 131 | bottom: 25%; 132 | content: ''; 133 | display: block; 134 | left: 25%; 135 | position: absolute; 136 | right: 25%; 137 | top: 25%; 138 | } 139 | 140 | /* Hover **********************************************************************/ 141 | 142 | button:not(.custom-appearance):not(.link-button):enabled:hover, 143 | input[type='button']:not(.custom-appearance):not(.link-button):enabled:hover, 144 | input[type='submit']:not(.custom-appearance):not(.link-button):enabled:hover, 145 | select:enabled:hover, 146 | input[type='checkbox']:enabled:hover, 147 | input[type='radio']:enabled:hover { 148 | background-image: -webkit-linear-gradient(#f0f0f0, #f0f0f0 38%, #e0e0e0); 149 | border-color: rgba(0, 0, 0, 0.3); 150 | box-shadow: 0 1px 0 rgba(0, 0, 0, 0.12), 151 | inset 0 1px 2px rgba(255, 255, 255, 0.95); 152 | color: black; 153 | } 154 | 155 | select:enabled:hover { 156 | /* OVERRIDE */ 157 | background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABMAAAAICAYAAAAbQcSUAAAAWklEQVQokWNgoAOIAuI0PDiKaJMSgYCZmfkbkPkfHYPEQfJEG/b//3+FBQsWLGRjY/uJbBCIDxIHyRNtGDYDyTYI3UA+Pr4vFBmEbODbt2+bKDYIyUBWYtQBAIRzRP/XKJ//AAAAAElFTkSuQmCC"), 158 | -webkit-linear-gradient(#f0f0f0, #f0f0f0 38%, #e0e0e0); 159 | } 160 | 161 | /* Active *********************************************************************/ 162 | 163 | button:not(.custom-appearance):not(.link-button):enabled:active, 164 | input[type='button']:not(.custom-appearance):not(.link-button):enabled:active, 165 | input[type='submit']:not(.custom-appearance):not(.link-button):enabled:active, 166 | select:enabled:active, 167 | input[type='checkbox']:enabled:active, 168 | input[type='radio']:enabled:active { 169 | background-image: -webkit-linear-gradient(#e7e7e7, #e7e7e7 38%, #d7d7d7); 170 | box-shadow: none; 171 | text-shadow: none; 172 | } 173 | 174 | select:enabled:active { 175 | /* OVERRIDE */ 176 | background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABMAAAAICAYAAAAbQcSUAAAAWklEQVQokWNgoAOIAuI0PDiKaJMSgYCZmfkbkPkfHYPEQfJEG/b//3+FBQsWLGRjY/uJbBCIDxIHyRNtGDYDyTYI3UA+Pr4vFBmEbODbt2+bKDYIyUBWYtQBAIRzRP/XKJ//AAAAAElFTkSuQmCC"), 177 | -webkit-linear-gradient(#e7e7e7, #e7e7e7 38%, #d7d7d7); 178 | } 179 | 180 | /* Disabled *******************************************************************/ 181 | 182 | button:not(.custom-appearance):not(.link-button):disabled, 183 | input[type='button']:not(.custom-appearance):not(.link-button):disabled, 184 | input[type='submit']:not(.custom-appearance):not(.link-button):disabled, 185 | select:disabled { 186 | background-image: -webkit-linear-gradient(#f1f1f1, #f1f1f1 38%, #e6e6e6); 187 | border-color: rgba(80, 80, 80, 0.2); 188 | box-shadow: 0 1px 0 rgba(80, 80, 80, 0.08), 189 | inset 0 1px 2px rgba(255, 255, 255, 0.75); 190 | color: #aaa; 191 | } 192 | 193 | select:disabled { 194 | background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABMAAAAICAYAAAAbQcSUAAAAAXNSR0IArs4c6QAAAAd0SU1FB9sLAxYEBKriBmwAAAAGYktHRAD/AP8A/6C9p5MAAAAJcEhZcwAACxMAAAsTAQCanBgAAABLSURBVCiRY2CgA4gC4jQ8OIpokxKBoKGh4T8uDJIn2rD///8rLFiwYCE2g0DiIHkSfIndQLIMwmYgRQYhG/j27dsmig1CMpCVGHUAo8FcsHfxfXQAAAAASUVORK5CYII="), 195 | -webkit-linear-gradient(#f1f1f1, #f1f1f1 38%, #e6e6e6); 196 | } 197 | 198 | input[type='checkbox']:disabled, 199 | input[type='radio']:disabled { 200 | opacity: .75; 201 | } 202 | 203 | input[type='password']:disabled, 204 | input[type='search']:disabled, 205 | input[type='text']:disabled, 206 | input[type='url']:disabled, 207 | input:not([type]):disabled { 208 | color: #999; 209 | } 210 | 211 | /* Focus **********************************************************************/ 212 | 213 | button:not(.custom-appearance):not(.link-button):enabled:focus, 214 | input[type='button']:not(.custom-appearance):enabled:focus, 215 | input[type='checkbox']:enabled:focus, 216 | input[type='password']:enabled:focus, 217 | input[type='radio']:enabled:focus, 218 | input[type='search']:enabled:focus, 219 | input[type='submit']:not(.custom-appearance):enabled:focus, 220 | input[type='text']:enabled:focus, 221 | input[type='url']:enabled:focus, 222 | input:not([type]):enabled:focus, 223 | select:enabled:focus { 224 | /* OVERRIDE */ 225 | -webkit-transition: border-color 200ms; 226 | /* We use border color because it follows the border radius (unlike outline). 227 | * This is particularly noticeable on mac. */ 228 | border-color: rgb(77, 144, 254); 229 | outline: none; 230 | } 231 | 232 | /* Link buttons ***************************************************************/ 233 | 234 | .link-button { 235 | -webkit-box-shadow: none; 236 | background: transparent none; 237 | border: none; 238 | color: rgb(17, 85, 204); 239 | cursor: pointer; 240 | /* Input elements have -webkit-small-control which can override the body font. 241 | * Resolve this by using 'inherit'. */ 242 | font: inherit; 243 | margin: 0; 244 | padding: 0 4px; 245 | } 246 | 247 | .link-button:hover { 248 | text-decoration: underline; 249 | } 250 | 251 | .link-button:active { 252 | color: rgb(5, 37, 119); 253 | text-decoration: underline; 254 | } 255 | 256 | .link-button[disabled] { 257 | color: #999; 258 | cursor: default; 259 | text-decoration: none; 260 | } 261 | 262 | .checkbox, 263 | .radio { 264 | margin: 0.65em 0; 265 | } 266 | 267 | .checkbox label, 268 | .radio label { 269 | /* Don't expand horizontally: . */ 270 | display: -webkit-inline-box; 271 | } 272 | 273 | .checkbox label input ~ span, 274 | .radio label input ~ span { 275 | -webkit-margin-start: 0.6em; 276 | /* Make sure long spans wrap at the same horizontal position they start. */ 277 | display: block; 278 | } 279 | 280 | .checkbox label:hover, 281 | .radio label:hover { 282 | color: black; 283 | } 284 | 285 | label > input[type=checkbox]:disabled ~ span, 286 | label > input[type=radio]:disabled ~ span { 287 | color: #999; 288 | } -------------------------------------------------------------------------------- /js/extended_injection.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Injection Content Script. 3 | * 4 | * @author Mohamed Mansour 2011 (http://mohamedmansour.com) 5 | * @constructor 6 | */ 7 | Injection = function() { 8 | this.auto_close_shelf = false; 9 | this.use_link = true; 10 | this.availableShares = []; 11 | this.closeIcon = this.createCloseIcon(); 12 | this.settingsIcon = this.createSettingsIcon(); 13 | this.currentlyOpenedShelf = null; 14 | this.windowPressedListener = this.onWindowPressed.bind(this); 15 | }; 16 | 17 | Injection.Translations = { 18 | NO_SHARE_LINKS: chrome.i18n.getMessage('injectionNoShareLinks'), 19 | ERROR: chrome.i18n.getMessage('injectionError'), 20 | CANNOT_SHARE_NOT_PUBLIC: chrome.i18n.getMessage('injectionCannotShareSincePublic'), 21 | SHARE_ON: chrome.i18n.getMessage('injectionShareOnNetwork'), 22 | PUBLIC: chrome.i18n.getMessage('injectionPublicLabel'), 23 | }; 24 | 25 | Injection.CONTENT_PANE_ID = '#contentPane'; 26 | Injection.SHARE_BUTTON_SELECTOR = 'div[role="button"]:nth-of-type(2)'; 27 | Injection.STREAM_UPDATE_SELECTOR = 'div[id^="update"]'; 28 | Injection.STREAM_POST_LINK = 'a[target="_blank"]'; 29 | Injection.STREAM_CONTENTS_SELECTOR = 'div > div > div:nth-of-type(3)'; 30 | Injection.STREAM_CONTENTS_MAIN_SELECTOR = Injection.STREAM_CONTENTS_SELECTOR + ' > div:nth-of-type(1)'; 31 | Injection.STREAM_CONTENTS_EMBED_SELECTOR = Injection.STREAM_CONTENTS_SELECTOR + ' > div:last-of-type'; 32 | Injection.STREAM_SHARING_DETAILS = 'header > span > span:last-of-type'; 33 | Injection.STREAM_ACTION_BAR_SELECTOR = Injection.STREAM_UPDATE_SELECTOR + '> div > div:nth-of-type(1) > div:last-child'; 34 | Injection.STREAM_AUTHOR_SELECTOR = 'header > h3'; 35 | Injection.STREAM_IMAGE_SELECTOR = 'img:not([oid])'; 36 | Injection.BUBBLE_CONTAINER_ID = 'gp-crx-bubble'; 37 | Injection.BUBBLE_SHARE_CONTENT_ID = '.gp-crx-shares'; 38 | Injection.BUBBLE_CLOSE_ID = '.gp-crx-close'; 39 | 40 | /** 41 | * Initialize the events that will be listening within this DOM. 42 | */ 43 | Injection.prototype.init = function() { 44 | // Listen when the subtree is modified for new posts. 45 | var googlePlusContentPane = document.querySelector(Injection.CONTENT_PANE_ID); 46 | if (googlePlusContentPane) { 47 | chrome.extension.sendRequest({method: 'GetSettings'}, this.onSettingsReceived.bind(this)); 48 | googlePlusContentPane.addEventListener('DOMNodeInserted', 49 | this.onGooglePlusContentModified.bind(this), false); 50 | chrome.extension.onRequest.addListener(this.onExternalRequest.bind(this)); 51 | } 52 | }; 53 | 54 | /** 55 | * This does an array comparison to see if two arrays are the same. This assumes the arrays are 56 | * already sorted. 57 | * 58 | * @param {Array} a sorted array. 59 | * @param {Array} b sorted array. 60 | * @return true if arrays are the same otherwise false. 61 | */ 62 | Injection.prototype.compareArrays = function(a, b) { 63 | if (a.length !== b.length) { 64 | return false; 65 | } 66 | for (var i = 0; i < a.length; i++) { 67 | if (a[i] !== b[i]) { 68 | return false; 69 | } 70 | } 71 | return true; 72 | }; 73 | 74 | /** 75 | * Prepares the share data by prefetching the icon and display name. 76 | * 77 | * @param {data} List of shares we are modifying. 78 | */ 79 | Injection.prototype.prepareShareData = function(data) { 80 | var shareName = 'Share on '; 81 | var shareIcon = null; 82 | var shareHeight = null; 83 | var shareTop = null; 84 | var shareWidth = null; 85 | var shareZoom = null; 86 | var shareMarginLeft = null; 87 | if (data.length === 1) { 88 | var shareItem = Shares[data[0]]; 89 | shareName += shareItem.name; 90 | shareIcon = chrome.extension.getURL(shareItem.icon); 91 | shareTop = '-3px'; 92 | shareHeight = '32px'; 93 | shareWidth = '32px'; 94 | shareZoom = 0.6; 95 | shareMarginLeft = '7px'; 96 | } 97 | else { 98 | shareName += '...'; 99 | shareIcon = chrome.extension.getURL('/img/share.png'); 100 | shareTop = '2px'; 101 | shareHeight = '14px'; 102 | shareWidth = '10px'; 103 | shareZoom = 1; 104 | shareMarginLeft = '0px'; 105 | } 106 | return { 107 | name: shareName, 108 | icon: shareIcon, 109 | top: shareTop, 110 | height: shareHeight, 111 | width: shareWidth, 112 | zoom: shareZoom, 113 | marginLeft: shareMarginLeft 114 | } 115 | }; 116 | 117 | /** 118 | * Decorates the exisiting share based on a specific state. 119 | * 120 | * @param {Element} shareNode the share to decorate. 121 | * @param {Object} shareData the properties for the decoration. 122 | */ 123 | Injection.prototype.decorateShare = function(shareNode, shareData) { 124 | shareNode.setAttribute('data-tooltip', shareData.name); 125 | shareNode.style.marginLeft = shareData.marginLeft; 126 | shareNode.style.width = shareData.width; 127 | var shareIcon = shareNode.childNodes[0]; 128 | shareIcon.style.background = 'no-repeat url(' + shareData.icon + ')'; 129 | shareIcon.style.top = shareData.top; 130 | shareIcon.style.height = shareData.height; 131 | shareIcon.style.zoom = shareData.zoom; 132 | shareIcon.style.width = shareData.width; 133 | }; 134 | 135 | /** 136 | * Settings received, update content script. 137 | */ 138 | Injection.prototype.onSettingsReceived = function(response) { 139 | this.auto_close_shelf = response.data.auto_close_shelf; 140 | this.use_link = response.data.use_link; 141 | var shares = response.data.shares; 142 | 143 | // If only a single share is enabled, just rename all the links to that share name. 144 | if (!this.compareArrays(this.availableShares, shares)) { 145 | // Destroy all the shares since it is easier for it to re-render it. 146 | this.destroyShelf(); 147 | 148 | // Query all the existing shares on the page. 149 | var existingShares = document.querySelectorAll('.external-share'); 150 | 151 | var shareData = this.prepareShareData(shares); 152 | for (var s = 0; s < existingShares.length; s++) { 153 | var existingShare = existingShares[s]; 154 | this.decorateShare(existingShare, shareData); 155 | } 156 | } 157 | this.availableShares = shares; 158 | }; 159 | 160 | /** 161 | * Figures out where the direct link URL is for the post within the |dom|. 162 | * This might change in the future since we are scraping it. 163 | * 164 | * @param {Object} dom The parent DOM source for the item. 165 | */ 166 | Injection.prototype.parseURL = function(parent) { 167 | var link = parent.querySelector(Injection.STREAM_POST_LINK); 168 | var image = parent.querySelector(Injection.STREAM_IMAGE_SELECTOR); 169 | var title = parent.querySelector(Injection.STREAM_AUTHOR_SELECTOR); 170 | var sharingDetails = parent.querySelector(Injection.STREAM_SHARING_DETAILS); 171 | 172 | var isPublic = sharingDetails.innerText === Injection.Translations.PUBLIC; 173 | var text = ''; 174 | 175 | if (title) { 176 | title = title.innerText + ' @ Google+'; 177 | } 178 | 179 | if (image) { 180 | image = image.src; 181 | } 182 | 183 | if (link) { 184 | // Smartly find out the contents of that div. 185 | link = link.href.replace(/plus\.google\.com\/u\/(\d*)/, 'plus.google.com'); 186 | var textDOM = null; 187 | var postContent = parent.querySelector(Injection.STREAM_CONTENTS_MAIN_SELECTOR); 188 | var postEmbed = parent.querySelector(Injection.STREAM_CONTENTS_EMBED_SELECTOR); 189 | if (postContent.innerText.trim()) { 190 | textDOM = postContent; 191 | } 192 | else { 193 | textDOM = postEmbed; 194 | } 195 | 196 | if (textDOM) { 197 | text = textDOM.innerText.trim().substring(0, 800); 198 | 199 | // Use link instead of post link. We put preference in links for the embedding 200 | // content if exists. 201 | if (this.use_link) { 202 | var linkDOM = (postEmbed && postEmbed.querySelector('a')) || textDOM.querySelector('a'); 203 | if (linkDOM) { 204 | link = linkDOM.href; 205 | } 206 | } 207 | } 208 | else { 209 | text = ''; // Empty for now till we figure out what to do. 210 | } 211 | } 212 | 213 | return { 214 | status: link ? true : false, 215 | link: link, 216 | text: text, 217 | title: title, 218 | media: image, 219 | isPublic: isPublic 220 | }; 221 | }; 222 | 223 | /** 224 | * Removes the bubble from the DOM. Same functionality as the share button. 225 | * 226 | * @param {Object} event The mouse event. 227 | */ 228 | Injection.prototype.destroyShelf = function(event) { 229 | if (!this.currentlyOpenedShelf) { 230 | return; 231 | } 232 | this.currentlyOpenedShelf.style.height = '1px'; 233 | setTimeout(function() { 234 | this.currentlyOpenedShelf.parentNode.removeChild(this.currentlyOpenedShelf); 235 | this.currentlyOpenedShelf = null; 236 | window.removeEventListener('keyup', this.windowPressedListener, false); 237 | }.bind(this), 300); 238 | }; 239 | 240 | /** 241 | * Visits the options page. 242 | * 243 | * @param {Object} event The mouse event. 244 | */ 245 | Injection.prototype.visitOptions = function(event) { 246 | if (this.auto_close_shelf) { 247 | this.destroyShelf(); 248 | } 249 | window.open(chrome.extension.getURL('options.html')); 250 | }; 251 | 252 | /** 253 | * Computes the 254 | * 255 | * @param {string} share The social share object defined in Shares array. 256 | * @param {string} result The URL detail request that contains the parsed data. 257 | */ 258 | Injection.prototype.createSocialLink = function(share, result) { 259 | var image = share.icon; 260 | var name = share.name; 261 | var url = share.url; 262 | var limit = share.trim; 263 | var text = limit ? result.text.substring(0, 100) : result.text; 264 | if (share.media) { 265 | url = url.replace('\${media}', encodeURIComponent(result.media)); 266 | } 267 | url = url.replace('\${link}', encodeURIComponent(result.link)); 268 | url = url.replace('\${text}', encodeURIComponent(text.trim())); 269 | url = url.replace('\${title}', encodeURIComponent(result.title)); 270 | return url; 271 | }; 272 | 273 | /** 274 | * Creates the DOM for the close button. 275 | */ 276 | Injection.prototype.createCloseIcon = function() { 277 | var closeIcon = document.createElement('div'); 278 | closeIcon.setAttribute('class', 'gp-crx-close'); 279 | return closeIcon; 280 | }; 281 | 282 | /** 283 | * Creates the DOM for the settings button. 284 | */ 285 | Injection.prototype.createSettingsIcon = function() { 286 | var settingsButton = document.createElement('span'); 287 | settingsButton.setAttribute('class', 'gp-crx-settings'); 288 | settingsButton.innerText = 'options'; 289 | return settingsButton; 290 | }; 291 | 292 | /** 293 | * Creates the social hyperlink image. 294 | * 295 | * @param {string} icon The image for the share name. 296 | * @param {string} name The social name 297 | * @param {string} url The URL to share. 298 | */ 299 | Injection.prototype.createSocialIcon = function(icon, name, url) { 300 | var a = document.createElement('a'); 301 | a.setAttribute('href', '#'); 302 | a.setAttribute('style', 'margin: 0 .4em'); 303 | a.onclick = function(e) { 304 | e.preventDefault(); 305 | chrome.extension.sendRequest({method: 'OpenURL', data: url}); 306 | if (this.auto_close_shelf) { 307 | this.destroyShelf(); 308 | } 309 | return false; 310 | }.bind(this); 311 | 312 | var img = document.createElement('img'); 313 | img.setAttribute('src', chrome.extension.getURL(icon)); 314 | img.setAttribute('data-tooltip', Injection.Translations.SHARE_ON + ' ' + name); 315 | img.setAttribute('style', 'vertical-align: middle'); 316 | 317 | a.appendChild(img); 318 | return a; 319 | }; 320 | 321 | /** 322 | * Creates the DOM for the shelf. 323 | */ 324 | Injection.prototype.createShelf = function(itemDOM) { 325 | // Only allow a singleton instance of the bubble opened at all times. 326 | if (this.currentlyOpenedShelf) { 327 | this.destroyShelf(); 328 | return; 329 | } 330 | 331 | var nodeToFill = document.createElement('div'); 332 | nodeToFill.setAttribute('class', 'gp-crx-shelf'); 333 | 334 | var result = this.parseURL(itemDOM); 335 | /* // Disable this cause it causes a internationalization bug 336 | if (!result.isPublic && !this.share_limited) { 337 | nodeToFill.appendChild(document.createTextNode(Injection.Translations.CANNOT_SHARE_NOT_PUBLIC)); 338 | } 339 | else*/ 340 | if (result.status) { 341 | if (this.availableShares.length > 1) { // User has some shares, display them. 342 | for (var i in this.availableShares) { 343 | var share = Shares[this.availableShares[i]]; 344 | if (!share.media || result.media) { 345 | var url = this.createSocialLink(share, result); 346 | nodeToFill.appendChild(this.createSocialIcon(share.icon, share.name, url)); 347 | } 348 | } 349 | } 350 | else if (this.availableShares.length === 1) { // Single share, auto link it directly. 351 | var url = this.createSocialLink(Shares[this.availableShares[0]], result); 352 | // Pass the URL to the background page so we can open it. This is needed 353 | // to overcome the block that Google+ is putting to redirect links. 354 | chrome.extension.sendRequest({method: 'OpenURL', data: url}); 355 | return; // TODO(mohamed): Figure out a better way. 356 | } 357 | else { // Nothing setup. 358 | nodeToFill.appendChild(document.createTextNode(Injection.Translations.NO_SHARE_LINKS)); 359 | } 360 | } 361 | else { 362 | nodeToFill.appendChild(document.createTextNode(Injection.Translations.ERROR)); 363 | } 364 | 365 | // Add the shelf node to the existing DOM. 366 | itemDOM.parentNode.insertBefore(nodeToFill, itemDOM.nextSibling); 367 | 368 | // Add close icon. 369 | var closeIcon = this.closeIcon.cloneNode(true); 370 | closeIcon.onclick = this.destroyShelf.bind(this); 371 | nodeToFill.appendChild(closeIcon); 372 | 373 | // Add settings button. 374 | var settingsIcon = this.settingsIcon.cloneNode(true); 375 | settingsIcon.onclick = this.visitOptions.bind(this); 376 | nodeToFill.appendChild(settingsIcon); 377 | 378 | // Close the share bubble when the user hits escape. 379 | window.addEventListener('keyup', this.windowPressedListener, false); 380 | 381 | // Save the current shelf so we can refer back to it later on. 382 | this.currentlyOpenedShelf = nodeToFill; 383 | 384 | // Animate it by fading in. 385 | setTimeout(function() { 386 | this.currentlyOpenedShelf.style.height = '32px'; 387 | }.bind(this)); 388 | }; 389 | 390 | /** 391 | * Listens on key presses while the share bubble is active. 392 | */ 393 | Injection.prototype.onWindowPressed = function(e) { 394 | if (e.keyCode === 27) { // ESCAPE. 395 | this.destroyShelf(); 396 | } 397 | }; 398 | 399 | /** 400 | * On Click event for when sending the link. 401 | * 402 | * @param {Object} event The mouse event. 403 | */ 404 | Injection.prototype.onSendClick = function(event) { 405 | // discover update parent. 406 | var cardDOM = event.srcElement; 407 | while (cardDOM.id.indexOf('update-') !== 0) { 408 | cardDOM = cardDOM.parentNode; 409 | } 410 | 411 | var mainContentCardDOM = cardDOM.childNodes[0].childNodes[0]; 412 | if (mainContentCardDOM) { 413 | this.createShelf(mainContentCardDOM); 414 | } 415 | }; 416 | 417 | /** 418 | * Render the "Share on ..." Link on each post. 419 | * 420 | * @param {Object} event modified event. 421 | */ 422 | Injection.prototype.renderItem = function(itemDOM) { 423 | if (!itemDOM || itemDOM.classList.contains('gpi-crx')) { 424 | return; 425 | } 426 | 427 | // Check if there is a share button, if not, then we cannot share this post. 428 | // Basically, the shelf contains exactly two components, the left share buttons 429 | // and the right share information. If the left bar has only one item then we know 430 | // they cannot share since the minimal case is just a plus widget. 431 | if (itemDOM.childNodes.length < 3) { 432 | return; 433 | } 434 | 435 | 436 | var originalShareNode = itemDOM.querySelector(Injection.SHARE_BUTTON_SELECTOR); 437 | // This means you cannot share this post, because it is locked. 438 | if (!originalShareNode) { 439 | return; 440 | } 441 | var shareNode = originalShareNode.cloneNode(true); 442 | 443 | // Remove the last class (I believe that is the trigger class from inspector). 444 | var lastClassNameItem = originalShareNode.classList[originalShareNode.classList.length - 1]; 445 | shareNode.classList.remove(lastClassNameItem); 446 | shareNode.classList.add('external-share'); 447 | 448 | // This post is not shareable, for now just remove it. 449 | if (shareNode.childNodes.length == 0) { 450 | return; 451 | } 452 | 453 | var shareData = this.prepareShareData(this.availableShares); 454 | shareNode.setAttribute('aria-label', shareData.name); 455 | this.decorateShare(shareNode, shareData); 456 | shareNode.onclick = this.onSendClick.bind(this); 457 | 458 | originalShareNode.parentNode.insertBefore(shareNode, originalShareNode.nextSibling ); 459 | itemDOM.classList.add('gpi-crx'); 460 | }; 461 | 462 | // TODO: Everything under here should be converted to a Mutation event instead. 463 | //////////////////////////////////////////////////////////////////////////////// 464 | 465 | /** 466 | * Render all the items in the current page. 467 | */ 468 | Injection.prototype.resetAndRenderAll = function() { 469 | var googlePlusContentPane = document.querySelector(Injection.CONTENT_PANE_ID); 470 | if (googlePlusContentPane) { 471 | googlePlusContentPane.removeEventListener('DOMNodeInserted', 472 | this.onGooglePlusContentModified.bind(this), false); 473 | googlePlusContentPane.addEventListener('DOMNodeInserted', 474 | this.onGooglePlusContentModified.bind(this), false); 475 | } 476 | this.renderAllItems(); 477 | }; 478 | 479 | /** 480 | * Render the "Share on ..." Link on each post. 481 | */ 482 | Injection.prototype.onGooglePlusContentModified = function(e) { 483 | // This happens when a new stream is selected 484 | if (e.relatedNode && e.relatedNode.parentNode && e.relatedNode.parentNode.id === 'contentPane') { 485 | // We're only interested in the insertion of entire content pane 486 | this.renderAllItems(e.target); 487 | } else if (e.target.nodeType === Node.ELEMENT_NODE && e.target.id.indexOf('update') === 0) { 488 | var actionBar = e.target.querySelector(Injection.STREAM_ACTION_BAR_SELECTOR); 489 | this.renderItem(actionBar); 490 | } 491 | }; 492 | 493 | /** 494 | * Render on all the items of the documents, or within the specified subtree 495 | * if applicable 496 | */ 497 | Injection.prototype.renderAllItems = function(subtreeDOM) { 498 | var actionBars = typeof subtreeDOM === 'undefined' ? 499 | document.querySelectorAll(Injection.STREAM_ACTION_BAR_SELECTOR) : subtreeDOM.querySelectorAll(Injection.STREAM_ACTION_BAR_SELECTOR); 500 | for (var i = 0; i < actionBars.length; i++) { 501 | this.renderItem(actionBars[i]); 502 | } 503 | }; 504 | 505 | /** 506 | * API to handle when clicking on different HTML5 push API. This somehow doesn't 507 | * play well with DOMSubtreeModified 508 | */ 509 | Injection.prototype.onExternalRequest = function(request, sender, sendResponse) { 510 | if (request.method === 'RenderShares' || request.method === 'InitialInjection') { 511 | this.resetAndRenderAll(); 512 | } 513 | else if (request.method === 'SettingsUpdated') { 514 | this.onSettingsReceived(request); 515 | } 516 | sendResponse({}); 517 | }; 518 | 519 | // Main 520 | var injection = new Injection(); 521 | injection.init(); 522 | --------------------------------------------------------------------------------