├── .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 |
8 | - Fixed Share broken for locked posts Issue# 71.
10 | - Feature Added Research Gate as a new share Issue# 70
12 | - Feature Added Scoop.it as a new share
13 |
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 | 
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 |
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 |
52 |
53 |
54 | - What share icons to show?
This will not autoshare.
55 | -
56 |
57 |
58 |
59 |
60 |
61 |
62 |
84 |
85 |
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 |
--------------------------------------------------------------------------------