├── .babelrc
├── .editorconfig
├── .eslintrc
├── .gitignore
├── LICENSE
├── README.md
├── app
├── BaseComponent.js
├── components
│ ├── Button.css
│ ├── Button.js
│ ├── ColorPicker.css
│ ├── ColorPicker.js
│ ├── Header.css
│ ├── Header.js
│ ├── Icon.css
│ ├── Icon.js
│ ├── Link.css
│ ├── Link.js
│ ├── NotificationBar.css
│ ├── NotificationBar.js
│ ├── Options
│ │ ├── Config.js
│ │ ├── Options.css
│ │ └── Options.js
│ ├── Play.css
│ ├── Play.js
│ ├── PlayContainer.js
│ ├── Reset.js
│ ├── ResetBar.css
│ ├── ResetBar.js
│ ├── Times.js
│ ├── Toggle.css
│ ├── ToggleDisplay.js
│ └── Variants.js
└── containers
│ ├── App.css
│ ├── App.js
│ └── Root.js
├── chrome
├── assets
│ ├── fonts
│ │ └── chessglyph-regular.woff
│ └── img
│ │ ├── arrow-down-medium.png
│ │ ├── arrow-down-small.png
│ │ ├── home.png
│ │ ├── icon-128.png
│ │ ├── icon-16.png
│ │ ├── icon-48.png
│ │ ├── interface-icon.png
│ │ ├── logo-black.svg
│ │ ├── logo.png
│ │ ├── logo.svg
│ │ └── sprite.png
├── extension
│ ├── background.js
│ ├── handleLiveChanges.js
│ ├── popup.css
│ └── popup.js
├── getCurrentUser.js
├── injectScriptWithWindowAccess.js
├── manifest.build.json
├── manifest.buildFirefox.json
├── manifest.dev.json
├── manifest.devFirefox.json
└── views
│ ├── background.jade
│ └── popup.jade
├── circle.yml
├── package.json
├── scripts
├── .eslintrc
├── build.js
├── buildFirefox.js
├── compress.js
├── dev.js
├── devFirefox.js
└── tasks.js
├── webpack
├── customPublicPath.js
├── dev.config.base.js
├── dev.config.js
├── devFirefox.config.js
├── prod.config.base.js
├── prod.config.js
├── prodFirefox.config.js
├── replace
│ ├── JsonpMainTemplate.runtime.js
│ └── log-apply-result.js
└── test.config.js
└── yarn.lock
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["es2015", "stage-0", "react"],
3 | "plugins": ["add-module-exports", "transform-decorators-legacy", "transform-runtime"],
4 | "env": {
5 | "test": {
6 | "plugins": [
7 | ["webpack-loaders", { "config": "webpack/test.config.js", "verbose": false }]
8 | ]
9 | }
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # editorconfig.org
2 |
3 | root = true
4 |
5 | [*]
6 | charset = utf-8
7 | end_of_line = lf
8 | indent_size = 2
9 | indent_style = space
10 | insert_final_newline = true
11 | trim_trailing_whitespace = true
12 |
13 | [*.md]
14 | trim_trailing_whitespace = false
15 |
16 | [*.py]
17 | indent_size = 4
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "parser": "babel-eslint",
3 | "extends": "airbnb",
4 | "globals": {
5 | "chrome": true
6 | },
7 | "env": {
8 | "browser": true,
9 | "node": true
10 | },
11 | "rules": {
12 | "react/prefer-stateless-function": 0,
13 | "react/jsx-filename-extension": 0,
14 | "consistent-return": 0,
15 | "comma-dangle": 0,
16 | "spaced-comment": 0,
17 | "global-require": 0,
18 | "no-param-reassign": ["error", {"props": false}]
19 | },
20 | "plugins": [
21 | "react"
22 | ]
23 | }
24 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | npm-debug.log
3 | .DS_Store
4 | .idea
5 | *.swp
6 |
7 | build/
8 | dev/
9 | buildFirefox/
10 | devFirefox/
11 |
12 | *.zip
13 | *.crx
14 | *.pem
15 | update.xml
16 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | COMMON DEVELOPMENT AND DISTRIBUTION LICENSE (CDDL) Version 1.0
2 | 1. Definitions.
3 | 1.1. "Contributor" means each individual or entity that
4 | creates or contributes to the creation of Modifications.
5 | 1.2. "Contributor Version" means the combination of the
6 | Original Software, prior Modifications used by a
7 | Contributor (if any), and the Modifications made by that
8 | particular Contributor.
9 | 1.3. "Covered Software" means (a) the Original Software, or
10 | (b) Modifications, or (c) the combination of files
11 | containing Original Software with files containing
12 | Modifications, in each case including portions thereof.
13 | 1.4. "Executable" means the Covered Software in any form
14 | other than Source Code.
15 | 1.5. "Initial Developer" means the individual or entity
16 | that first makes Original Software available under this
17 | License.
18 | 1.6. "Larger Work" means a work which combines Covered
19 | Software or portions thereof with code not governed by the
20 | terms of this License.
21 | 1.7. "License" means this document.
22 | 1.8. "Licensable" means having the right to grant, to the
23 | maximum extent possible, whether at the time of the initial
24 | grant or subsequently acquired, any and all of the rights
25 | conveyed herein.
26 | 1.9. "Modifications" means the Source Code and Executable
27 | form of any of the following:
28 | A. Any file that results from an addition to,
29 | deletion from or modification of the contents of a
30 | file containing Original Software or previous
31 | Modifications;
32 | B. Any new file that contains any part of the
33 | Original Software or previous Modification; or
34 | C. Any new file that is contributed or otherwise made
35 | available under the terms of this License.
36 | 1.10. "Original Software" means the Source Code and
37 | Executable form of computer software code that is
38 | originally released under this License.
39 | 1.11. "Patent Claims" means any patent claim(s), now owned
40 | or hereafter acquired, including without limitation,
41 | method, process, and apparatus claims, in any patent
42 | Licensable by grantor.
43 | 1.12. "Source Code" means (a) the common form of computer
44 | software code in which modifications are made and (b)
45 | associated documentation included in or with such code.
46 | 1.13. "You" (or "Your") means an individual or a legal
47 | entity exercising rights under, and complying with all of
48 | the terms of, this License. For legal entities, "You"
49 | includes any entity which controls, is controlled by, or is
50 | under common control with You. For purposes of this
51 | definition, "control" means (a) the power, direct or
52 | indirect, to cause the direction or management of such
53 | entity, whether by contract or otherwise, or (b) ownership
54 | of more than fifty percent (50%) of the outstanding shares
55 | or beneficial ownership of such entity.
56 | 2. License Grants.
57 | 2.1. The Initial Developer Grant.
58 | Conditioned upon Your compliance with Section 3.1 below and
59 | subject to third party intellectual property claims, the
60 | Initial Developer hereby grants You a world-wide,
61 | royalty-free, non-exclusive license:
62 | (a) under intellectual property rights (other than
63 | patent or trademark) Licensable by Initial Developer,
64 | to use, reproduce, modify, display, perform,
65 | sublicense and distribute the Original Software (or
66 | portions thereof), with or without Modifications,
67 | and/or as part of a Larger Work; and
68 | (b) under Patent Claims infringed by the making,
69 | using or selling of Original Software, to make, have
70 | made, use, practice, sell, and offer for sale, and/or
71 | otherwise dispose of the Original Software (or
72 | portions thereof).
73 | (c) The licenses granted in Sections 2.1(a) and (b)
74 | are effective on the date Initial Developer first
75 | distributes or otherwise makes the Original Software
76 | available to a third party under the terms of this
77 | License.
78 | (d) Notwithstanding Section 2.1(b) above, no patent
79 | license is granted: (1) for code that You delete from
80 | the Original Software, or (2) for infringements
81 | caused by: (i) the modification of the Original
82 | Software, or (ii) the combination of the Original
83 | Software with other software or devices.
84 | 2.2. Contributor Grant.
85 | Conditioned upon Your compliance with Section 3.1 below and
86 | subject to third party intellectual property claims, each
87 | Contributor hereby grants You a world-wide, royalty-free,
88 | non-exclusive license:
89 | (a) under intellectual property rights (other than
90 | patent or trademark) Licensable by Contributor to
91 | use, reproduce, modify, display, perform, sublicense
92 | and distribute the Modifications created by such
93 | Contributor (or portions thereof), either on an
94 | unmodified basis, with other Modifications, as
95 | Covered Software and/or as part of a Larger Work; and
96 | (b) under Patent Claims infringed by the making,
97 | using, or selling of Modifications made by that
98 | Contributor either alone and/or in combination with
99 | its Contributor Version (or portions of such
100 | combination), to make, use, sell, offer for sale,
101 | have made, and/or otherwise dispose of: (1)
102 | Modifications made by that Contributor (or portions
103 | thereof); and (2) the combination of Modifications
104 | made by that Contributor with its Contributor Version
105 | (or portions of such combination).
106 | (c) The licenses granted in Sections 2.2(a) and
107 | 2.2(b) are effective on the date Contributor first
108 | distributes or otherwise makes the Modifications
109 | available to a third party.
110 | (d) Notwithstanding Section 2.2(b) above, no patent
111 | license is granted: (1) for any code that Contributor
112 | has deleted from the Contributor Version; (2) for
113 | infringements caused by: (i) third party
114 | modifications of Contributor Version, or (ii) the
115 | combination of Modifications made by that Contributor
116 | with other software (except as part of the
117 | Contributor Version) or other devices; or (3) under
118 | Patent Claims infringed by Covered Software in the
119 | absence of Modifications made by that Contributor.
120 | 3. Distribution Obligations.
121 | 3.1. Availability of Source Code.
122 | Any Covered Software that You distribute or otherwise make
123 | available in Executable form must also be made available in
124 | Source Code form and that Source Code form must be
125 | distributed only under the terms of this License. You must
126 | include a copy of this License with every copy of the
127 | Source Code form of the Covered Software You distribute or
128 | otherwise make available. You must inform recipients of any
129 | such Covered Software in Executable form as to how they can
130 | obtain such Covered Software in Source Code form in a
131 | reasonable manner on or through a medium customarily used
132 | for software exchange.
133 | 3.2. Modifications.
134 | The Modifications that You create or to which You
135 | contribute are governed by the terms of this License. You
136 | represent that You believe Your Modifications are Your
137 | original creation(s) and/or You have sufficient rights to
138 | grant the rights conveyed by this License.
139 | 3.3. Required Notices.
140 | You must include a notice in each of Your Modifications
141 | that identifies You as the Contributor of the Modification.
142 | You may not remove or alter any copyright, patent or
143 | trademark notices contained within the Covered Software, or
144 | any notices of licensing or any descriptive text giving
145 | attribution to any Contributor or the Initial Developer.
146 | 3.4. Application of Additional Terms.
147 | You may not offer or impose any terms on any Covered
148 | Software in Source Code form that alters or restricts the
149 | applicable version of this License or the recipients'
150 | rights hereunder. You may choose to offer, and to charge a
151 | fee for, warranty, support, indemnity or liability
152 | obligations to one or more recipients of Covered Software.
153 | However, you may do so only on Your own behalf, and not on
154 | behalf of the Initial Developer or any Contributor. You
155 | must make it absolutely clear that any such warranty,
156 | support, indemnity or liability obligation is offered by
157 | You alone, and You hereby agree to indemnify the Initial
158 | Developer and every Contributor for any liability incurred
159 | by the Initial Developer or such Contributor as a result of
160 | warranty, support, indemnity or liability terms You offer.
161 | 3.5. Distribution of Executable Versions.
162 | You may distribute the Executable form of the Covered
163 | Software under the terms of this License or under the terms
164 | of a license of Your choice, which may contain terms
165 | different from this License, provided that You are in
166 | compliance with the terms of this License and that the
167 | license for the Executable form does not attempt to limit
168 | or alter the recipient's rights in the Source Code form
169 | from the rights set forth in this License. If You
170 | distribute the Covered Software in Executable form under a
171 | different license, You must make it absolutely clear that
172 | any terms which differ from this License are offered by You
173 | alone, not by the Initial Developer or Contributor. You
174 | hereby agree to indemnify the Initial Developer and every
175 | Contributor for any liability incurred by the Initial
176 | Developer or such Contributor as a result of any such terms
177 | You offer.
178 | 3.6. Larger Works.
179 | You may create a Larger Work by combining Covered Software
180 | with other code not governed by the terms of this License
181 | and distribute the Larger Work as a single product. In such
182 | a case, You must make sure the requirements of this License
183 | are fulfilled for the Covered Software.
184 | 4. Versions of the License.
185 | 4.1. New Versions.
186 | Sun Microsystems, Inc. is the initial license steward and
187 | may publish revised and/or new versions of this License
188 | from time to time. Each version will be given a
189 | distinguishing version number. Except as provided in
190 | Section 4.3, no one other than the license steward has the
191 | right to modify this License.
192 | 4.2. Effect of New Versions.
193 | You may always continue to use, distribute or otherwise
194 | make the Covered Software available under the terms of the
195 | version of the License under which You originally received
196 | the Covered Software. If the Initial Developer includes a
197 | notice in the Original Software prohibiting it from being
198 | distributed or otherwise made available under any
199 | subsequent version of the License, You must distribute and
200 | make the Covered Software available under the terms of the
201 | version of the License under which You originally received
202 | the Covered Software. Otherwise, You may also choose to
203 | use, distribute or otherwise make the Covered Software
204 | available under the terms of any subsequent version of the
205 | License published by the license steward.
206 | 4.3. Modified Versions.
207 | When You are an Initial Developer and You want to create a
208 | new license for Your Original Software, You may create and
209 | use a modified version of this License if You: (a) rename
210 | the license and remove any references to the name of the
211 | license steward (except to note that the license differs
212 | from this License); and (b) otherwise make it clear that
213 | the license contains terms which differ from this License.
214 | 5. DISCLAIMER OF WARRANTY.
215 | COVERED SOFTWARE IS PROVIDED UNDER THIS LICENSE ON AN "AS IS"
216 | BASIS, WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED,
217 | INCLUDING, WITHOUT LIMITATION, WARRANTIES THAT THE COVERED
218 | SOFTWARE IS FREE OF DEFECTS, MERCHANTABLE, FIT FOR A PARTICULAR
219 | PURPOSE OR NON-INFRINGING. THE ENTIRE RISK AS TO THE QUALITY AND
220 | PERFORMANCE OF THE COVERED SOFTWARE IS WITH YOU. SHOULD ANY
221 | COVERED SOFTWARE PROVE DEFECTIVE IN ANY RESPECT, YOU (NOT THE
222 | INITIAL DEVELOPER OR ANY OTHER CONTRIBUTOR) ASSUME THE COST OF
223 | ANY NECESSARY SERVICING, REPAIR OR CORRECTION. THIS DISCLAIMER OF
224 | WARRANTY CONSTITUTES AN ESSENTIAL PART OF THIS LICENSE. NO USE OF
225 | ANY COVERED SOFTWARE IS AUTHORIZED HEREUNDER EXCEPT UNDER THIS
226 | DISCLAIMER.
227 | 6. TERMINATION.
228 | 6.1. This License and the rights granted hereunder will
229 | terminate automatically if You fail to comply with terms
230 | herein and fail to cure such breach within 30 days of
231 | becoming aware of the breach. Provisions which, by their
232 | nature, must remain in effect beyond the termination of
233 | this License shall survive.
234 | 6.2. If You assert a patent infringement claim (excluding
235 | declaratory judgment actions) against Initial Developer or
236 | a Contributor (the Initial Developer or Contributor against
237 | whom You assert such claim is referred to as "Participant")
238 | alleging that the Participant Software (meaning the
239 | Contributor Version where the Participant is a Contributor
240 | or the Original Software where the Participant is the
241 | Initial Developer) directly or indirectly infringes any
242 | patent, then any and all rights granted directly or
243 | indirectly to You by such Participant, the Initial
244 | Developer (if the Initial Developer is not the Participant)
245 | and all Contributors under Sections 2.1 and/or 2.2 of this
246 | License shall, upon 60 days notice from Participant
247 | terminate prospectively and automatically at the expiration
248 | of such 60 day notice period, unless if within such 60 day
249 | period You withdraw Your claim with respect to the
250 | Participant Software against such Participant either
251 | unilaterally or pursuant to a written agreement with
252 | Participant.
253 | 6.3. In the event of termination under Sections 6.1 or 6.2
254 | above, all end user licenses that have been validly granted
255 | by You or any distributor hereunder prior to termination
256 | (excluding licenses granted to You by any distributor)
257 | shall survive termination.
258 | 7. LIMITATION OF LIABILITY.
259 | UNDER NO CIRCUMSTANCES AND UNDER NO LEGAL THEORY, WHETHER TORT
260 | (INCLUDING NEGLIGENCE), CONTRACT, OR OTHERWISE, SHALL YOU, THE
261 | INITIAL DEVELOPER, ANY OTHER CONTRIBUTOR, OR ANY DISTRIBUTOR OF
262 | COVERED SOFTWARE, OR ANY SUPPLIER OF ANY OF SUCH PARTIES, BE
263 | LIABLE TO ANY PERSON FOR ANY INDIRECT, SPECIAL, INCIDENTAL, OR
264 | CONSEQUENTIAL DAMAGES OF ANY CHARACTER INCLUDING, WITHOUT
265 | LIMITATION, DAMAGES FOR LOST PROFITS, LOSS OF GOODWILL, WORK
266 | STOPPAGE, COMPUTER FAILURE OR MALFUNCTION, OR ANY AND ALL OTHER
267 | COMMERCIAL DAMAGES OR LOSSES, EVEN IF SUCH PARTY SHALL HAVE BEEN
268 | INFORMED OF THE POSSIBILITY OF SUCH DAMAGES. THIS LIMITATION OF
269 | LIABILITY SHALL NOT APPLY TO LIABILITY FOR DEATH OR PERSONAL
270 | INJURY RESULTING FROM SUCH PARTY'S NEGLIGENCE TO THE EXTENT
271 | APPLICABLE LAW PROHIBITS SUCH LIMITATION. SOME JURISDICTIONS DO
272 | NOT ALLOW THE EXCLUSION OR LIMITATION OF INCIDENTAL OR
273 | CONSEQUENTIAL DAMAGES, SO THIS EXCLUSION AND LIMITATION MAY NOT
274 | APPLY TO YOU.
275 | 8. U.S. GOVERNMENT END USERS.
276 | The Covered Software is a "commercial item," as that term is
277 | defined in 48 C.F.R. 2.101 (Oct. 1995), consisting of "commercial
278 | computer software" (as that term is defined at 48 C.F.R. ¤
279 | 252.227-7014(a)(1)) and "commercial computer software
280 | documentation" as such terms are used in 48 C.F.R. 12.212 (Sept.
281 | 1995). Consistent with 48 C.F.R. 12.212 and 48 C.F.R. 227.7202-1
282 | through 227.7202-4 (June 1995), all U.S. Government End Users
283 | acquire Covered Software with only those rights set forth herein.
284 | This U.S. Government Rights clause is in lieu of, and supersedes,
285 | any other FAR, DFAR, or other clause or provision that addresses
286 | Government rights in computer software under this License.
287 | 9. MISCELLANEOUS.
288 | This License represents the complete agreement concerning subject
289 | matter hereof. If any provision of this License is held to be
290 | unenforceable, such provision shall be reformed only to the
291 | extent necessary to make it enforceable. This License shall be
292 | governed by the law of the jurisdiction specified in a notice
293 | contained within the Original Software (except to the extent
294 | applicable law, if any, provides otherwise), excluding such
295 | jurisdiction's conflict-of-law provisions. Any litigation
296 | relating to this License shall be subject to the jurisdiction of
297 | the courts located in the jurisdiction and venue specified in a
298 | notice contained within the Original Software, with the losing
299 | party responsible for costs, including, without limitation, court
300 | costs and reasonable attorneys' fees and expenses. The
301 | application of the United Nations Convention on Contracts for the
302 | International Sale of Goods is expressly excluded. Any law or
303 | regulation which provides that the language of a contract shall
304 | be construed against the drafter shall not apply to this License.
305 | You agree that You alone are responsible for compliance with the
306 | United States export administration regulations (and the export
307 | control laws and regulation of any other countries) when You use,
308 | distribute or otherwise make available any Covered Software.
309 | 10. RESPONSIBILITY FOR CLAIMS.
310 | As between Initial Developer and the Contributors, each party is
311 | responsible for claims and damages arising, directly or
312 | indirectly, out of its utilization of rights under this License
313 | and You agree to work with Initial Developer and Contributors to
314 | distribute such responsibility on an equitable basis. Nothing
315 | herein is intended or shall be deemed to constitute any admission
316 | of liability.
317 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | 
2 |
3 | # Chess.com Browser Extension
4 |
5 | Do chess your way! Even though Chess.com is working hard to provide the best online experience for playing, learning, and sharing chess, we know everyone has different preferences and ideas. We want to encourage everyone to customize their online chess experience as much as they want!
6 |
7 | ## Download the extension for...
8 | - [Chrome](https://chrome.google.com/webstore/detail/chess-browser-extension/fcfojodfingmafbdmlekaaoogcfpjegg)
9 | - [Firefox](https://addons.mozilla.org/en-US/firefox/addon/chess-com-browser-extension)
10 | - Safari: (TBA)
11 | - Opera: (TBA)
12 |
13 | ---
14 |
15 | ### Developing
16 |
17 | Once you've cloned the repo, install the NPM modules:
18 |
19 | ```
20 | yarn install
21 | ```
22 |
23 | Then run the developer build:
24 |
25 | #### For Chrome
26 |
27 | ```
28 | yarn run dev
29 | ```
30 |
31 | Load the extension via [Chrome Apps & Extensions Developer Tool](https://chrome.google.com/webstore/detail/chrome-apps-extensions-de/ohmmkhmmmpcnpikjeljgnaoabkaalbgc?hl=en). Be sure to select the `dev` folder from the "Load unpacked..." step.
32 |
33 | #### For Firefox
34 |
35 | ```
36 | yarn run devFirefox
37 | ```
38 |
39 | Load the extension [doing these steps](https://github.com/ChessCom/browser-extension/pull/48#issuecomment-264218199).
40 |
41 | ### Contribute
42 | If you would like to contribute code, please submit a pull request. Meaningful contributors will get a free Diamond membership.
43 |
44 | If you would just like to submit an idea, please go here: [http://goo.gl/forms/AVaggClVWuIyP87k1](http://goo.gl/forms/AVaggClVWuIyP87k1)
45 |
46 | #### All contributions should:
47 | - Respect the community (nothing negative or trolling)
48 | - Respect the service (don’t burden servers, hack around premium access, link to other sites)
49 | - Pass our ES6 lint standards, which you can check with this command:
50 |
51 | ```
52 | yarn run lint
53 | ```
54 |
55 |
56 | ### Issues
57 | Check out our current outstanding issues [here](https://github.com/ChessCom/browser-extension/issues)
58 |
59 | ### Special Thanks
60 | Huge thanks to all of these people and all of this software:
61 |
62 | [Rish](https://github.com/rish)
63 | [Martyn Chamberlin](https://github.com/martynchamberlin)
64 | [Jhen-Jie Hong](https://github.com/jhen0409/react-chrome-extension-boilerplate)
65 | [Winner Crespo](https://github.com/wistcc)
66 |
--------------------------------------------------------------------------------
/app/BaseComponent.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | function shallowComp(A, B) {
4 | if (A === B) {
5 | // They are the same object
6 | return true;
7 | }
8 |
9 | if (typeof A !== 'object' || A === null ||
10 | typeof B !== 'object' || B === null) {
11 | // This is only meant for comparing objects
12 | // If they are not the function could have unintended behaviour
13 | return false;
14 | }
15 |
16 | // Check that keys are the same
17 | const keysA = Object.keys(A);
18 | const keysB = Object.keys(B);
19 | if (keysA.length !== keysB.length) {
20 | return false;
21 | }
22 | return keysA.every(key => {
23 | if (!Object.hasOwnProperty.call(B, key)) {
24 | return false;
25 | }
26 | return A[key] === B[key];
27 | });
28 | }
29 |
30 | export default class BaseComponent extends React.Component {
31 | constructor(props, context) {
32 | super(props, context);
33 | if (this.constructor === BaseComponent) {
34 | throw new TypeError('BaseComponent is abstract');
35 | }
36 | }
37 |
38 | shouldComponentUpdate = (nextProps, nextState) => (
39 | !shallowComp(this.props, nextProps) ||
40 | !shallowComp(this.state, nextState)
41 | );
42 | }
43 |
--------------------------------------------------------------------------------
/app/components/Button.css:
--------------------------------------------------------------------------------
1 | .button {
2 | cursor: pointer;
3 | margin: 0 4px 3px 0;
4 | height: 35px;
5 | border-bottom: 1px solid #a7a6a2;
6 | border-radius: 3px;
7 | color: #666463;
8 | background: #dbd9d7;
9 | font-size: 13px;
10 | }
11 |
12 | .selected {
13 | background: #bfbeba;
14 | }
15 |
16 | .small {
17 | width: 64px;
18 | min-width: 64px;
19 | }
20 |
21 | .panel {
22 | width: 100%;
23 | border-radius: 0;
24 | border: none;
25 | text-align: left;
26 | padding: 12px 15px 12px 45px;
27 | height: auto;
28 | margin: 0;
29 | position: relative;
30 | }
31 |
32 | .panelButtonIcon {
33 | position: absolute;
34 | top: 1px;
35 | left: 15px;
36 | }
37 |
38 | .huge {
39 | height: 50px;
40 | font-size: 17px;
41 | width: 48%;
42 | line-height: 1;
43 | float: left;
44 | position: relative;
45 | }
46 |
47 | .huge:after {
48 | position: absolute;
49 | font-family: Chess;
50 | font-weight: 400;
51 | font-style: normal;
52 | speak: none;
53 | color: #8c8a88;
54 | -webkit-font-smoothing: antialiased;
55 | -moz-osx-font-smoothing: grayscale;
56 | content: '\003F';
57 | right: 15px;
58 | top: 16px;
59 | padding-left: 15px;
60 | }
61 |
62 | .huge:nth-child(2) {
63 | margin-right: 0;
64 | float: right;
65 | }
66 |
67 | .hugeButtonIcon {
68 | line-height: .8;
69 | }
70 |
71 | .btn.btn-arrow:after {
72 | position: relative;
73 | left: 5px;
74 | padding-left: 5px;
75 | font-family: Chess;
76 | font-weight: 400;
77 | font-style: normal;
78 | speak: none;
79 | color: #8c8a88;
80 | -webkit-font-smoothing: antialiased;
81 | -moz-osx-font-smoothing: grayscale;
82 | content: '\003F';
83 | }
84 |
85 | .button:nth-child(4n+5) {
86 | margin-right: 0;
87 | }
88 |
89 | .button:hover {
90 | background: #bfbeba;
91 | color: #312e2b;
92 | }
93 |
--------------------------------------------------------------------------------
/app/components/Button.js:
--------------------------------------------------------------------------------
1 | import React, { PropTypes } from 'react';
2 | import classNames from 'classnames/bind';
3 | import style from '../components/Button.css';
4 | import BaseComponent from '../BaseComponent';
5 |
6 | const cx = classNames.bind(style);
7 |
8 | export default class Button extends BaseComponent {
9 |
10 | static propTypes = {
11 | onClick: PropTypes.func.isRequired,
12 | concern: PropTypes.object,
13 | children: PropTypes.any,
14 | className: PropTypes.any
15 | };
16 |
17 | constructor(props) {
18 | super(props);
19 | this.onClick = this.onClick.bind(this);
20 | }
21 |
22 | onClick() {
23 | this.props.onClick(this.props.concern);
24 | }
25 |
26 | render() {
27 | const className = cx({
28 | [this.props.className]: true,
29 | button: true
30 | });
31 | return (
32 |
36 | { this.props.children }
37 |
38 | );
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/app/components/ColorPicker.css:
--------------------------------------------------------------------------------
1 | .row {
2 | padding-bottom: 10px;
3 | }
4 |
5 | label {
6 | font-size: 0.8rem;
7 | line-height: 1.8rem;
8 | font-weight: 500;
9 | }
10 |
11 | .arrow {
12 | display: block;
13 | float: right;
14 | position: absolute;
15 | top: 40%;
16 | right: 10%;
17 | }
18 |
19 | .resetButton {
20 | cursor: pointer;
21 | float: right;
22 | margin-right: 5px;
23 | padding-top: 3px;
24 | }
25 |
26 | .row:hover .resetButton i {
27 | color: #8c8a88 !important;
28 | }
29 |
--------------------------------------------------------------------------------
/app/components/ColorPicker.js:
--------------------------------------------------------------------------------
1 | /* eslint quote-props: 0 */
2 | import React, { PropTypes } from 'react';
3 | import { ChromePicker } from 'react-color';
4 | import reactCSS from 'reactcss';
5 | import style from './ColorPicker.css';
6 | import BaseComponent from '../BaseComponent';
7 | import Reset from './Reset';
8 |
9 | export default class ColorPicker extends BaseComponent {
10 |
11 | static propTypes = {
12 | name: PropTypes.string.isRequired,
13 | selector: PropTypes.string.isRequired,
14 | property: PropTypes.string.isRequired
15 | };
16 |
17 | constructor(props, context) {
18 | super(props, context);
19 |
20 | this.state = {
21 | update: 'style',
22 | selector: '',
23 | property: '',
24 | color: {},
25 | displayColorPicker: false
26 | };
27 | }
28 |
29 | componentDidMount() {
30 | this.addStorageListener();
31 | }
32 |
33 | setDefaultState = () => {
34 | this.setState({
35 | color: {
36 | r: '255',
37 | g: '255',
38 | b: '255',
39 | a: '1'
40 | }
41 | });
42 | }
43 |
44 | setDefaultStateAndSave = () => {
45 | chrome.storage.local.set({ style: {} });
46 | this.setDefaultState();
47 | }
48 |
49 | isDefaultColor = color => Object.keys(color).length === 0 ||
50 | (color.r === '255' && color.g === '255' && color.b === '255' && color.a === '1');
51 |
52 | checkIfStorageAlreadyExists = (name) => {
53 | chrome.storage.local.get('style', result => {
54 | if (!{}.hasOwnProperty.call(result, 'style')) {
55 | return this.setDefaultStateAndSave();
56 | }
57 |
58 | if ({}.hasOwnProperty.call(result.style, name)) {
59 | const storedColor = result.style[name].color;
60 | this.setState({ color: storedColor });
61 | }
62 | });
63 | }
64 |
65 | addStorageListener = () => {
66 | this.checkIfStorageAlreadyExists(this.props.name);
67 |
68 | // Reset color picker when reset button is hit
69 | chrome.storage.onChanged.addListener(changes => {
70 | try {
71 | const newValue = changes.style.newValue;
72 |
73 | if (Object.keys(newValue).length === 0 && newValue.constructor === Object) {
74 | this.setDefaultState();
75 | }
76 | } catch (e) {
77 | this.checkIfStorageAlreadyExists();
78 | }
79 | });
80 | }
81 |
82 | handleClick = () => {
83 | this.setState({ displayColorPicker: !this.state.displayColorPicker });
84 | };
85 |
86 | handleClose = () => {
87 | this.setState({ displayColorPicker: false });
88 | };
89 |
90 | handleChange = (color) => {
91 | chrome.tabs.query({ active: true, currentWindow: true }, tabs => {
92 | chrome.tabs.sendMessage(tabs[0].id, {
93 | update: 'style',
94 | color: color.rgb,
95 | selector: this.props.selector,
96 | property: this.props.property
97 | });
98 | });
99 | };
100 |
101 | handleChangeComplete = (color) => {
102 | this.setState({
103 | color: color.rgb,
104 | selector: this.props.selector,
105 | property: this.props.property
106 | });
107 | const state = JSON.parse(JSON.stringify(this.state));
108 | const name = this.props.name;
109 |
110 | chrome.storage.local.get('style', result => {
111 | if (Object.keys(result).length === 0 && result.constructor === Object) {
112 | chrome.storage.local.set({ style: {} });
113 | result.style = {};
114 | }
115 | result.style[name] = state;
116 | delete result.style[name].displayColorPicker;
117 | chrome.storage.local.set(result);
118 | });
119 | };
120 |
121 | render() {
122 | const colorpicker = this.props;
123 | const inputId = `${this.props.name}_input`;
124 | const color = this.state.color;
125 | const styles = reactCSS({
126 | 'default': {
127 | color: {
128 | width: '18px',
129 | height: '18px',
130 | borderRadius: '2px',
131 | background: `rgba(${color.r}, ${color.g}, ${color.b}, ${color.a})`,
132 | float: 'left',
133 | marginRight: '30px'
134 | },
135 | swatch: {
136 | padding: '5px',
137 | background: '#fff',
138 | borderRadius: '1px',
139 | boxShadow: '0 0 0 1px rgba(0,0,0,.1)',
140 | display: 'inline-block',
141 | cursor: 'pointer',
142 | position: 'relative',
143 | 'float': 'right'
144 | },
145 | popover: {
146 | position: 'absolute',
147 | zIndex: '2',
148 | right: '20px'
149 | },
150 | picker: {
151 | position: 'relative',
152 | },
153 | cover: {
154 | position: 'fixed',
155 | top: '0px',
156 | right: '0px',
157 | bottom: '0px',
158 | left: '0px',
159 | }
160 | }
161 | });
162 |
163 | return (
164 |
165 |
{colorpicker.title}
166 |
174 | {!this.isDefaultColor(color) ?
175 |
187 | : null}
188 | {this.state.displayColorPicker ?
189 |
190 |
191 |
192 |
197 |
198 |
199 | : null}
200 |
201 | );
202 | }
203 | }
204 |
--------------------------------------------------------------------------------
/app/components/Header.css:
--------------------------------------------------------------------------------
1 | .header {
2 | padding: 15px;
3 | height: 55px;
4 | font-size: 13px;
5 | overflow: hidden;
6 | }
7 |
8 | .header img {
9 | float: left;
10 | }
11 |
12 | .userInfo {
13 | float: right;
14 | color: #fff;
15 | }
16 |
17 | .btn {
18 | cursor: pointer;
19 | float: right;
20 | background: #e4902d;
21 | color: white;
22 | padding: 0 13px;
23 | border-radius: 3px;
24 | height: 25px;
25 | }
26 |
27 | .userInfo img {
28 | margin: 0 0 0 15px;
29 | float: right;
30 | }
31 |
32 | .username {
33 | margin: 4px 0 -4px;
34 | max-width: 140px;
35 | overflow: hidden;
36 | display: inline-block;
37 | text-overflow: ellipsis;
38 | opacity: .8;
39 | }
40 |
41 | .home {
42 | display: inline-block;
43 | margin: 0.5rem;
44 | }
45 |
46 | .home:hover {
47 | cursor: pointer;
48 | }
49 |
--------------------------------------------------------------------------------
/app/components/Header.js:
--------------------------------------------------------------------------------
1 | import React, { PropTypes } from 'react';
2 | import style from './Header.css';
3 | import Link from './Link';
4 | import BaseComponent from '../BaseComponent';
5 |
6 | export default class Header extends BaseComponent {
7 |
8 | static propTypes = {
9 | user: PropTypes.object.isRequired,
10 | };
11 |
12 | render() {
13 | let userInfo = (
);
14 | if (!this.props.user.loading) {
15 | if (this.props.user.loggedIn) {
16 | userInfo = (
17 |
18 |
19 |
{this.props.user.username}
20 |
21 |
22 |
23 | );
24 | } else {
25 | userInfo = (
26 |
27 | Login
28 |
29 | );
30 | }
31 | }
32 |
33 | return (
34 |
35 |
36 |
37 |
38 | { userInfo }
39 |
40 | );
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/app/components/Icon.css:
--------------------------------------------------------------------------------
1 | @font-face {
2 | font-family: 'Chess';
3 | src: url('../../chrome/assets/fonts/chessglyph-regular.woff');
4 | }
5 | .icon {
6 | font-family: 'Chess';
7 | font-weight: normal;
8 | font-style: normal;
9 | speak: none;
10 | text-align: center;
11 | color: #8c8a88;
12 | display: inline-block;
13 | -webkit-font-smoothing: antialiased;
14 | }
15 |
16 | .icon:hover {
17 | text-decoration: none
18 | }
19 |
20 | .icon-magnifying-glass {
21 | padding-left: 1px;
22 | margin-left: -1px
23 | }
24 |
25 | .icon-chess:before {
26 | content: '\0069'
27 | }
28 |
29 | .icon-960:before {
30 | content: '\010C'
31 | }
32 |
33 | .icon-chess960:before {
34 | content: '\010C'
35 | }
36 |
37 | .icon-threecheck:before {
38 | content: '\00CB'
39 | }
40 |
41 | .icon-kingofthehill:before {
42 | content: '\012A'
43 | }
44 |
45 | .icon-losers:before {
46 | content: '\012B'
47 | }
48 |
49 | .icon-crazyhouse:before {
50 | content: '\010E'
51 | }
52 |
53 | .icon-bughouse:before {
54 | content: '\011A'
55 | }
56 |
57 | .icon-nav-horizontal:before {
58 | content: '\0052'
59 | }
60 |
61 | .icon-nav-vertical:before {
62 | content: '\00E7'
63 | }
64 |
65 | .icon-nav-expanded:before {
66 | content: '\03E1'
67 | }
68 |
69 | .icon-nav-collapsed:before {
70 | content: '\03E0'
71 | }
72 |
73 | .icon-bug:before {
74 | content: '\00F5'
75 | }
76 |
77 | .icon-hourglass:before {
78 | content: '\03FA'
79 | }
80 |
81 | .icon-camera:before {
82 | content: '\0048'
83 | }
84 |
85 | .icon-camera-plus:before {
86 | content: '\03DF'
87 | }
88 |
89 | .icon-globe:before {
90 | content: '\004D'
91 | }
92 |
93 | .icon-order:before {
94 | content: '\004F'
95 | }
96 |
97 | .icon-flag:before {
98 | content: '\0059'
99 | }
100 |
101 | .icon-calendar:before {
102 | content: '\0061'
103 | }
104 |
105 | .icon-calendar-alt:before {
106 | content: '\0032'
107 | }
108 |
109 | .icon-daily:before {
110 | content: '\0032'
111 | }
112 |
113 | .icon-chat:before {
114 | content: '\0063'
115 | }
116 |
117 | .icon-chat-alt:before {
118 | content: '\007A'
119 | }
120 |
121 | .icon-chat-x:before {
122 | content: '\2044'
123 | }
124 |
125 | .icon-chess-book:before {
126 | content: '\006F'
127 | }
128 |
129 | .icon-filter:before {
130 | content: '\203A'
131 | }
132 |
133 | .icon-lock:before {
134 | content: '\0064'
135 | }
136 |
137 | .icon-inbox:before {
138 | content: '\0065'
139 | }
140 |
141 | .icon-lightbulb:before {
142 | content: '\0067'
143 | }
144 |
145 | .icon-nametag:before {
146 | content: '\0068'
147 | }
148 |
149 | .icon-mail:before {
150 | content: '\0075'
151 | }
152 |
153 | .icon-mail-alt:before {
154 | content: '\0079'
155 | }
156 |
157 | .icon-mail-plus:before {
158 | content: '\006B'
159 | }
160 |
161 | .icon-mail-exclaimation:before {
162 | content: '\03BB'
163 | }
164 |
165 | .icon-book:before {
166 | content: '\006F'
167 | }
168 |
169 | .icon-book-alt:before {
170 | content: '\00A4'
171 | }
172 |
173 | .icon-book-open:before {
174 | content: '\00B4'
175 | }
176 |
177 | .icon-files:before {
178 | content: '\03C3'
179 | }
180 |
181 | .icon-popup:before {
182 | content: '\0070'
183 | }
184 |
185 | .icon-bell:before {
186 | content: '\0071'
187 | }
188 |
189 | .icon-menu:before {
190 | content: '\0074'
191 | }
192 |
193 | .icon-home:before {
194 | content: '\0077'
195 | }
196 |
197 | .icon-home-alt:before {
198 | content: '\03DB'
199 | }
200 |
201 | .icon-lightning:before {
202 | content: '\0034'
203 | }
204 |
205 | .icon-blitz:before {
206 | content: '\0034'
207 | }
208 |
209 | .icon-bullet:before {
210 | content: '\0035'
211 | }
212 |
213 | .icon-chess960:before {
214 | content: '\0036'
215 | }
216 |
217 | .icon-chess-board-puzzle:before {
218 | content: '\0037'
219 | }
220 |
221 | .icon-chess-board-puzzle-reversed:before {
222 | content: '\03DE'
223 | }
224 |
225 | .icon-fire-puzzle:before {
226 | content: '\0371'
227 | }
228 |
229 | .icon-computer:before {
230 | content: '\0039'
231 | }
232 |
233 | .icon-computer-search:before {
234 | content: '\03BE'
235 | }
236 |
237 | .icon-tag:before {
238 | content: '\00BD'
239 | }
240 |
241 | .icon-toolbox:before {
242 | content: '\00BC'
243 | }
244 |
245 | .icon-cake:before {
246 | content: '\00BE'
247 | }
248 |
249 | .icon-asterisk:before {
250 | content: '\002A'
251 | }
252 |
253 | .icon-trash:before {
254 | content: '\2022'
255 | }
256 |
257 | .icon-download:before {
258 | content: '\0022'
259 | }
260 |
261 | .icon-display-grid:before {
262 | content: '\00AB'
263 | }
264 |
265 | .icon-display-slider:before {
266 | content: '\2039'
267 | }
268 |
269 | .icon-display-list:before {
270 | content: '\203A'
271 | }
272 |
273 | .icon-pushpin:before {
274 | content: '\00BB'
275 | }
276 |
277 | .icon-key:before {
278 | content: '\00A2'
279 | }
280 |
281 | .icon-page:before {
282 | content: '\00A3'
283 | }
284 |
285 | .icon-page-alt:before {
286 | content: '\20AC'
287 | }
288 |
289 | .icon-page-pencil:before {
290 | content: '\0078'
291 | }
292 |
293 | .icon-news:before {
294 | content: '\0045'
295 | }
296 |
297 | .icon-equal:before {
298 | content: '\003D'
299 | }
300 |
301 | .icon-stats:before {
302 | content: '\003B'
303 | }
304 |
305 | .icon-stats-arrow-up:before {
306 | content: '\010D'
307 | }
308 |
309 | .icon-stats-x:before {
310 | content: '\00F0'
311 | }
312 |
313 | .icon-graphs:before {
314 | content: '\03C5'
315 | }
316 |
317 | .icon-binoculars:before {
318 | content: '\2014'
319 | }
320 |
321 | .icon-magnifying-glass:before {
322 | content: '\2013'
323 | }
324 |
325 | .icon-present:before {
326 | content: '\00BE'
327 | }
328 |
329 | .icon-exit:before {
330 | content: '\00D7'
331 | }
332 |
333 | .icon-handshake:before {
334 | content: '\002B'
335 | }
336 |
337 | .icon-cup:before {
338 | content: '\2021'
339 | }
340 |
341 | .icon-paper-pencil:before {
342 | content: '\03A3'
343 | }
344 |
345 | .icon-todo-list:before {
346 | content: '\010F'
347 | }
348 |
349 | .icon-trophy-plus:before {
350 | content: '\03A5'
351 | }
352 |
353 | .icon-trophy-minus:before {
354 | content: '\03BA'
355 | }
356 |
357 | .icon-trophy-podium:before {
358 | content: '\03B5'
359 | }
360 |
361 | .icon-privacy:before {
362 | content: '\03B2'
363 | }
364 |
365 | .icon-survey:before {
366 | content: '\03C1'
367 | }
368 |
369 | .icon-crossed-swords:before {
370 | content: '\03C4'
371 | }
372 |
373 | .icon-checkmark-box:before {
374 | content: '\03A8'
375 | }
376 |
377 | .icon-checkmark-box-plus:before {
378 | content: '\03A6'
379 | }
380 |
381 | .icon-tracked-content:before {
382 | content: '\00D5'
383 | }
384 |
385 | .icon-queen-wreath:before {
386 | content: '\00F1'
387 | }
388 |
389 | .icon-crosshair:before {
390 | content: '\0111'
391 | }
392 |
393 | .icon-shield:before {
394 | content: '\014B'
395 | }
396 |
397 | .icon-chip:before {
398 | content: '\00F6'
399 | }
400 |
401 | .icon-smiley:before {
402 | content: '\03C2'
403 | }
404 |
405 | .icon-eye:before {
406 | content: '\0057'
407 | }
408 |
409 | .icon-select:before {
410 | content: '\03AD'
411 | }
412 |
413 | .icon-undo:before {
414 | content: '\004C'
415 | }
416 |
417 | .icon-link:before {
418 | content: '\0041'
419 | }
420 |
421 | .icon-x:before {
422 | content: '\0042'
423 | }
424 |
425 | .icon-reply:before {
426 | content: '\0043'
427 | }
428 |
429 | .icon-checkmark:before {
430 | content: '\0047'
431 | }
432 |
433 | .icon-redo:before {
434 | content: '\003A'
435 | }
436 |
437 | .icon-plus:before {
438 | content: '\0056'
439 | }
440 |
441 | .icon-list:before {
442 | content: '\0072'
443 | }
444 |
445 | .icon-embed:before {
446 | content: '\221E'
447 | }
448 |
449 | .icon-image-plus:before {
450 | content: '\03B6'
451 | }
452 |
453 | .icon-follow:before {
454 | content: '\03CE'
455 | }
456 |
457 | .icon-unfollow:before {
458 | content: '\03CD'
459 | }
460 |
461 | .icon-quote:before {
462 | content: '\00D6'
463 | }
464 |
465 | .icon-circle:before {
466 | content: '\0054'
467 | }
468 |
469 | .icon-circle-dashboard:before {
470 | content: '\004E'
471 | }
472 |
473 | .icon-circle-x:before {
474 | content: '\0051'
475 | }
476 |
477 | .icon-circle-3-dots:before {
478 | content: '\038F'
479 | }
480 |
481 | .icon-circle-timer:before {
482 | content: '\0033'
483 | }
484 |
485 | .icon-standard:before {
486 | content: '\0033'
487 | }
488 |
489 | .icon-circle-gearwheel:before {
490 | content: '\00B7'
491 | }
492 |
493 | .icon-circle-clock:before {
494 | content: '\0027'
495 | }
496 |
497 | .icon-circle-clock-alt:before {
498 | content: '\00B0'
499 | }
500 |
501 | .icon-circle-question:before {
502 | content: '\0028'
503 | }
504 |
505 | .icon-circle-info:before {
506 | content: '\0029'
507 | }
508 |
509 | .icon-circle-arrow:before {
510 | content: '\00F7'
511 | }
512 |
513 | .icon-circle-block:before {
514 | content: '\222B'
515 | }
516 |
517 | .icon-circle-stop:before {
518 | content: '\0026'
519 | }
520 |
521 | .icon-circle-danger:before {
522 | content: '\2020'
523 | }
524 |
525 | .icon-circle-checkmark:before {
526 | content: '\03C7'
527 | }
528 |
529 | .icon-square-reply:before {
530 | content: '\0058'
531 | }
532 |
533 | .icon-square-pencil:before {
534 | content: '\005A'
535 | }
536 |
537 | .icon-square-brush:before {
538 | content: '\006C'
539 | }
540 |
541 | .icon-square-in:before {
542 | content: '\00A1'
543 | }
544 |
545 | .icon-square-out:before {
546 | content: '\00BF'
547 | }
548 |
549 | .icon-square-bottom-in:before {
550 | content: '\039E'
551 | }
552 |
553 | .icon-square-x:before {
554 | content: '\00FC'
555 | }
556 |
557 | .icon-square-checkmark:before {
558 | content: '\03BF'
559 | }
560 |
561 | .icon-square-four:before {
562 | content: '\03F8'
563 | }
564 |
565 | .icon-caret-up:before {
566 | content: '\007C'
567 | }
568 |
569 | .icon-caret-down:before {
570 | content: '\003F'
571 | }
572 |
573 | .icon-caret-left:before {
574 | content: '\002F'
575 | }
576 |
577 | .icon-caret-right:before {
578 | content: '\005C'
579 | }
580 |
581 | .icon-chevron-up:before {
582 | content: '\003E'
583 | }
584 |
585 | .icon-chevron-bottom:before {
586 | content: '\003C'
587 | }
588 |
589 | .icon-chevron-left:before {
590 | content: '\002C'
591 | }
592 |
593 | .icon-chevron-right:before {
594 | content: '\2026'
595 | }
596 |
597 | .icon-double-chevron-left:before {
598 | content: '\0021'
599 | }
600 |
601 | .icon-double-chevron-right:before {
602 | content: '\03B1'
603 | }
604 |
605 | .icon-chevron-previous:before {
606 | content: '\0023'
607 | }
608 |
609 | .icon-chevron-next:before {
610 | content: '\0040'
611 | }
612 |
613 | .icon-chevron-down:before {
614 | content: '\0030'
615 | }
616 |
617 | .icon-arrow-return:before {
618 | content: '\005F'
619 | }
620 |
621 | .icon-arrow-up:before {
622 | content: '\007D'
623 | }
624 |
625 | .icon-arrow-down:before {
626 | content: '\007B'
627 | }
628 |
629 | .icon-arrow-left:before {
630 | content: '\005B'
631 | }
632 |
633 | .icon-arrow-right:before {
634 | content: '\005D'
635 | }
636 |
637 | .icon-round-arrow-return:before {
638 | content: '\03BC'
639 | }
640 |
641 | .icon-bold-arrow-right:before {
642 | content: '\0386'
643 | }
644 |
645 | .icon-arrow-cross:before {
646 | content: '\0110'
647 | }
648 |
649 | .icon-user:before {
650 | content: '\0062'
651 | }
652 |
653 | .icon-users:before {
654 | content: '\006D'
655 | }
656 |
657 | .icon-users-alt:before {
658 | content: '\006E'
659 | }
660 |
661 | .icon-user-info:before {
662 | content: '\0076'
663 | }
664 |
665 | .icon-user-question:before {
666 | content: '\002D'
667 | }
668 |
669 | .icon-user-search:before {
670 | content: '\0024'
671 | }
672 |
673 | .icon-user-search-alt:before {
674 | content: '\014a'
675 | }
676 |
677 | .icon-user-feed:before {
678 | content: '\0025'
679 | }
680 |
681 | .icon-user-block:before {
682 | content: '\00A6'
683 | }
684 |
685 | .icon-user-plus:before {
686 | content: '\00B6'
687 | }
688 |
689 | .icon-users-plus:before {
690 | content: '\00FF'
691 | }
692 |
693 | .icon-user-x:before {
694 | content: '\00A7'
695 | }
696 |
697 | .icon-user-chain:before {
698 | content: '\0159'
699 | }
700 |
701 | .icon-user-broken-chain:before {
702 | content: '\0158'
703 | }
704 |
705 | .icon-user-shield:before {
706 | content: '\03DA'
707 | }
708 |
709 | .icon-user-shield-plus:before {
710 | content: '\0373'
711 | }
712 |
713 | .icon-chess-board:before {
714 | content: '\0069'
715 | }
716 |
717 | .icon-chess-board-alt:before {
718 | content: '\2019'
719 | }
720 |
721 | .icon-chess-board-search:before {
722 | content: '\0394'
723 | }
724 |
725 | .icon-chess-board-search-alt:before {
726 | content: '\03A9'
727 | }
728 |
729 | .icon-question:before {
730 | content: '\00A9'
731 | }
732 |
733 | .icon-chess-board-plus:before {
734 | content: '\02C6'
735 | }
736 |
737 | .icon-chess-board-arrow:before {
738 | content: '\0038'
739 | }
740 |
741 | .icon-chess-board-circle:before {
742 | content: '\00AB'
743 | }
744 |
745 | .icon-chess-board-gear:before {
746 | content: '\03F7'
747 | }
748 |
749 | .icon-chess-crown:before {
750 | content: '\03FB'
751 | }
752 |
753 | .icon-chess-pawn:before {
754 | content: '\0031'
755 | }
756 |
757 | .icon-chess-pawn-left-half-rook:before {
758 | content: '\0112'
759 | }
760 |
761 | .icon-chess-pawn-right-half-rook:before {
762 | content: '\0113'
763 | }
764 |
765 | .icon-chess-pawn-rook:before {
766 | content: '\0073'
767 | }
768 |
769 | .icon-chess-pawn-square:before {
770 | content: '\03B7'
771 | }
772 |
773 | .icon-chess-pawns:before {
774 | content: '\0073'
775 | }
776 |
777 | .icon-chess-move:before {
778 | content: '\0030'
779 | }
780 |
781 | .icon-chess-move-alt:before {
782 | content: '\006A'
783 | }
784 |
785 | .icon-chess-board-folder:before {
786 | content: '\0398'
787 | }
788 |
789 | .icon-chess-board-paper:before {
790 | content: '\03A5'
791 | }
792 |
793 | .icon-chess-board-arrow-down:before {
794 | content: '\03CC'
795 | }
796 |
797 | .icon-chess-board-arrow-right:before {
798 | content: '\03CC'
799 | }
800 |
801 | .icon-chess-board-arrow-right {
802 | position: relative;
803 | top: 2px;
804 | left: -2px;
805 | transform: rotate(-90deg);
806 | }
807 |
808 | .icon-chess-board-arrow-left:before {
809 | content: '\03CC'
810 | }
811 |
812 | .icon-chess-board-arrow-left {
813 | position: relative;
814 | top: 2px;
815 | left: 2px;
816 | transform: rotate(90deg);
817 | }
818 |
819 | .icon-checkbox:before {
820 | content: '\03A8'
821 | }
822 |
823 | .icon-checkbox-plus:before {
824 | content: '\03A6'
825 | }
826 |
827 | .icon-printer:before {
828 | content: '\00E5'
829 | }
830 |
831 | .icon-play:before {
832 | content: '\004A'
833 | }
834 |
835 | .icon-pause:before {
836 | content: '\004B'
837 | }
838 |
839 | .icon-sound-off:before {
840 | content: '\0050'
841 | }
842 |
843 | .icon-sound-on:before {
844 | content: '\0055'
845 | }
846 |
847 | .icon-repeat:before {
848 | content: '\0066'
849 | }
850 |
851 | .icon-shuffle:before {
852 | content: '\0049'
853 | }
854 |
855 | .icon-resize:before {
856 | content: '\03C0'
857 | }
858 |
859 | .icon-favorites:before {
860 | content: '\03AE'
861 | }
862 |
863 | .icon-facebook:before {
864 | content: '\0053'
865 | }
866 |
867 | .icon-facebook-alt:before {
868 | content: '\0044'
869 | }
870 |
871 | .icon-twitter:before {
872 | content: '\0046'
873 | }
874 |
875 | .icon-linkedin:before {
876 | content: '\00C5'
877 | }
878 |
879 | .icon-tumblr:before {
880 | content: '\00EB'
881 | }
882 |
883 | .icon-stumbleupon:before {
884 | content: '\00DF'
885 | }
886 |
887 | .icon-reddit:before {
888 | content: '\00B1'
889 | }
890 |
891 | .icon-google-plus:before {
892 | content: '\00AE'
893 | }
894 |
895 | .icon-youtube:before {
896 | content: '\0060'
897 | }
898 |
899 | .icon-share:before {
900 | content: '\00A5'
901 | }
902 |
903 | .icon-twitch:before {
904 | content: '\0393'
905 | }
906 |
907 | .icon-android:before {
908 | content: '\00FE'
909 | }
910 |
911 | .icon-apple:before {
912 | content: '\25CA'
913 | }
914 |
915 | .icon-win-phone:before {
916 | content: '\03A0'
917 | }
918 |
919 | .icon-thumbs-down:before {
920 | content: '\2265'
921 | }
922 |
923 | .icon-thumbs-up:before {
924 | content: '\2264'
925 | }
926 |
927 | .icon-card:before {
928 | content: '\03DC'
929 | }
930 |
931 | .icon-paypal:before {
932 | content: '\0377'
933 | }
934 |
935 | .icon-membership-diamond:before {
936 | content: '\0370'
937 | }
938 |
939 | .icon-membership-platinum:before {
940 | content: '\0376'
941 | }
942 |
943 | .icon-membership-staff:before {
944 | content: '\0372'
945 | }
946 |
947 | .icon-membership-mod:before {
948 | content: '\0372'
949 | }
950 |
951 | .icon-membership-gold:before {
952 | content: '\03AE'
953 | }
954 |
955 | .icon-circle-hollow:before {
956 | content: '\03BD'
957 | }
958 |
959 | .icon-binoculars-crossed:before {
960 | content: '\00DE'
961 | }
962 |
963 | .icon-border-resize:before {
964 | content: '\03AC'
965 | }
966 |
967 | .icon-maximize:before {
968 | content: '\03DD'
969 | }
970 |
971 | .icon-minimize:before {
972 | content: '\03D8'
973 | }
974 |
975 | .icon-live360:before {
976 | content: '\00D1'
977 | }
978 |
979 | .icon-signal:before {
980 | content: '\03AF'
981 | }
982 |
983 | .icon-captured-bishop:before {
984 | content: '\0062'
985 | }
986 |
987 | .icon-captured-bishop-2:before {
988 | content: '\0042'
989 | }
990 |
991 | .icon-captured-knight:before {
992 | content: '\006E'
993 | }
994 |
995 | .icon-captured-knight-2:before {
996 | content: '\004E'
997 | }
998 |
999 | .icon-captured-rook:before {
1000 | content: '\0072'
1001 | }
1002 |
1003 | .icon-captured-rook-2:before {
1004 | content: '\0052'
1005 | }
1006 |
1007 | .icon-captured-king:before {
1008 | content: '\006B'
1009 | }
1010 |
1011 | .icon-captured-queen:before {
1012 | content: '\0071'
1013 | }
1014 |
1015 | .icon-captured-pawn:before {
1016 | content: '\0031'
1017 | }
1018 |
1019 | .icon-captured-pawn-2:before {
1020 | content: '\0032'
1021 | }
1022 |
1023 | .icon-captured-pawn-3:before {
1024 | content: '\0033'
1025 | }
1026 |
1027 | .icon-captured-pawn-4:before {
1028 | content: '\0034'
1029 | }
1030 |
1031 | .icon-captured-pawn-5:before {
1032 | content: '\0035'
1033 | }
1034 |
1035 | .icon-captured-pawn-6:before {
1036 | content: '\0036'
1037 | }
1038 |
1039 | .icon-captured-pawn-7:before {
1040 | content: '\0037'
1041 | }
1042 |
1043 | .icon-captured-pawn-8:before {
1044 | content: '\0038'
1045 | }
1046 |
--------------------------------------------------------------------------------
/app/components/Icon.js:
--------------------------------------------------------------------------------
1 | /* eslint quote-props: 0 */
2 | import React, { PropTypes } from 'react';
3 | import classNames from 'classnames/bind';
4 | import icon from './Icon.css';
5 | import BaseComponent from '../BaseComponent';
6 |
7 | const cx = classNames.bind(icon);
8 |
9 | export default class Icon extends BaseComponent {
10 |
11 | static propTypes = {
12 | name: PropTypes.string.isRequired,
13 | size: PropTypes.string,
14 | color: PropTypes.string,
15 | className: PropTypes.any
16 | };
17 |
18 | render() {
19 | const iconName = `icon-${this.props.name}`;
20 | const iconClassName = icon[iconName];
21 |
22 | const iconClass = cx({
23 | 'icon': true,
24 | [iconClassName]: true,
25 | [this.props.className]: true
26 | });
27 |
28 | const iconStyle = {
29 | fontSize: parseInt(this.props.size, 10),
30 | color: `rgba(${this.props.color})`
31 | };
32 |
33 | return (
34 |
35 | );
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/app/components/Link.css:
--------------------------------------------------------------------------------
1 | .link:hover {
2 | cursor: pointer;
3 | }
4 |
--------------------------------------------------------------------------------
/app/components/Link.js:
--------------------------------------------------------------------------------
1 | import React, { PropTypes } from 'react';
2 | import style from './Link.css';
3 | import BaseComponent from '../BaseComponent';
4 |
5 | export default class Link extends BaseComponent {
6 |
7 | static propTypes = {
8 | slug: PropTypes.string.isRequired,
9 | children: PropTypes.element.isRequired
10 | };
11 |
12 | constructor(props) {
13 | super(props);
14 | this.goTo = this.goTo.bind(this);
15 | }
16 |
17 | // We need to a routing function to handle actions
18 | // that send the user to various url targets on site
19 | goTo() {
20 | chrome.tabs.create({ url: `https://www.chess.com/${this.props.slug}` });
21 | window.close();
22 | }
23 |
24 | render() {
25 | return (
26 |
27 | { this.props.children }
28 |
29 | );
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/app/components/NotificationBar.css:
--------------------------------------------------------------------------------
1 | .notificationBar {
2 | padding: 10px 0px 10px 0px;
3 | }
4 | .notificationBar div {
5 | position:relative;
6 | display:inline-block;
7 | margin-left: 20%;
8 | }
9 | .notificationBar div span span {
10 | position: absolute;
11 | right:-20px;
12 | top:10px;
13 | background: #b43430;
14 | color: white;
15 | text-align: center;
16 | padding: 0px 5px 0px 5px;
17 | font-size:12px;
18 | font-weight: bold;
19 | border-radius: 30px 30px 30px 30px;
20 | }
--------------------------------------------------------------------------------
/app/components/NotificationBar.js:
--------------------------------------------------------------------------------
1 | import React, { PropTypes } from 'react';
2 | import style from './NotificationBar.css';
3 | import Icon from './Icon.js';
4 | import Link from './Link.js';
5 | import BaseComponent from '../BaseComponent';
6 |
7 | export default class NotificationBar extends BaseComponent {
8 |
9 | static propTypes = {
10 | notifications: PropTypes.object.isRequired,
11 | };
12 |
13 | render() {
14 | let games = ( );
15 | let messages = ( );
16 | let alerts = ( );
17 |
18 | if (!this.props.notifications.loading) {
19 | games = ({this.props.notifications.games} );
20 | messages = ({this.props.notifications.messages} );
21 | alerts = ({this.props.notifications.alerts} );
22 | }
23 |
24 | return (
25 |
26 |
27 |
28 |
29 | {games}
30 |
31 |
32 |
33 |
34 |
35 | {messages}
36 |
37 |
38 |
39 |
40 |
41 | {alerts}
42 |
43 |
44 |
45 | );
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/app/components/Options/Config.js:
--------------------------------------------------------------------------------
1 | export default {
2 | groups: [
3 | {
4 | id: 'backgrounds',
5 | title: 'Backgrounds',
6 | icon: 'page-alt',
7 | options: [
8 | {
9 | type: 'ColorPicker',
10 | name: 'content',
11 | title: 'Content Background Color',
12 | property: 'backgroundColor',
13 | selector: [
14 | '.index #content > section',
15 | '.user-home #content section',
16 | '#content > section, #content .forum-category-header',
17 | '#content .forum-table-full tbody>tr:hover',
18 | '#content .table .row-highlighted>td',
19 | '#content .table.with-row-highlight>tbody>tr:hover',
20 | '#content .switchers a.iconized.active'
21 | ]
22 | },
23 | {
24 | type: 'ColorPicker',
25 | name: 'header-sections',
26 | title: 'Header Sections Color',
27 | property: 'backgroundColor',
28 | selector: [
29 | 'main #sidebar .section-header',
30 | '#content #load-more-container',
31 | '#sidebar .section-header',
32 | '.user-home #sidebar .section-header',
33 | '#content .section-header'
34 | ]
35 | },
36 | {
37 | type: 'ColorPicker',
38 | name: 'sidebar',
39 | title: 'Sidebar Background Color',
40 | property: 'backgroundColor',
41 | selector: [
42 | '#sidebar section:not(#chess-board-sidebar)',
43 | '#sidebar section:not(#chess-board-sidebar) .section-wrapper',
44 | 'ul.stats-list li',
45 | '.daily-chess #sidebar section:not(#chess-board-sidebar)'
46 | ]
47 | }
48 | ]
49 | },
50 | {
51 | id: 'text',
52 | title: 'Text',
53 | icon: 'paper-pencil',
54 | options: [
55 | {
56 | type: 'ColorPicker',
57 | name: 'text',
58 | title: 'Text Color',
59 | property: 'color',
60 | selector: [
61 | '#content section.section-wrapper:not(.dismissible-banner):not(.intro)',
62 | '#content article',
63 | '#content .content-body',
64 | '#content .content-body h1',
65 | '#content h1, .index p',
66 | '.view-thread .comments li .user-content > p',
67 | '#comments .comments .comment-body'
68 | ]
69 | },
70 | {
71 | type: 'ColorPicker',
72 | name: 'sidebar-text',
73 | title: 'Sidebar Text Color',
74 | property: 'color',
75 | selector: [
76 | 'sidebar .member-item [class^=\'icon-\']',
77 | '#sidebar .members-stats .member-item .number',
78 | '#sidebar .members-stats .member-item .stat, #sidebar .place-number',
79 | '#sidebar section:not(#chess-board-sidebar) .user-rating',
80 | '#sidebar .survey-container .survey-item label',
81 | '#sidebar section:not(#chess-board-sidebar) a',
82 | '#sidebar section .section-clickable h3',
83 | '.white-header h3, #sidebar ul.rating-list',
84 | '#sidebar ul.stats-list li',
85 | '#sidebar ul.stats-list aside',
86 | '#sidebar ul.stats-list aside span',
87 | '#sidebar .new-game-time-header'
88 | ]
89 | },
90 | {
91 | type: 'ColorPicker',
92 | name: 'link',
93 | title: 'Link Color',
94 | property: 'color',
95 | selector: [
96 | '.index article.content a',
97 | '.user-home #content .section-wrapper a',
98 | '#content section a:not(.btn)',
99 | '#content a.username',
100 | '#content span.username'
101 | ]
102 | },
103 | {
104 | type: 'ColorPicker',
105 | name: 'header-text',
106 | title: 'Section Header Text Color',
107 | property: 'color',
108 | selector: [
109 | 'content #load-more-container a',
110 | '#content #load-more-container span',
111 | '#content .header-clickable h3.section-clickable a',
112 | '#content .section-header h3',
113 | '#content .header-clickable h3',
114 | '#content .header-clickable h3 a',
115 | '#sidebar .section-header h3',
116 | '#sidebar .header-clickable h3.section-clickable a',
117 | '.section-header a',
118 | 'main #content.content-container .section-header .header-count',
119 | 'main #sidebar .section-header .header-count',
120 | '.view-thread > h1'
121 | ]
122 | },
123 | {
124 | type: 'ColorPicker',
125 | name: 'meta-color',
126 | title: 'Meta Information Color',
127 | property: 'color',
128 | selector: [
129 | 'content ul.content-stats',
130 | '#content ul.content-stats>li [class^=icon-]',
131 | '.content-stats time, #content .user-chess-title',
132 | '.user-home ul.content-stats>li [class^=icon-]',
133 | '.user-home .content-stats time, .user-home .content-stats .user-chess-title',
134 | '.user-home ul.content-stats',
135 | '.user-home .content-container ul.content-list>.list-short .amount',
136 | '.user-home ul.content-list>li',
137 | '.forum-thread time',
138 | '#content ul.forum-thread>li .cell-time-data>a i',
139 | '.view-thread .comments .comment-header time',
140 | '.view-thread .pagination ul li',
141 | '.view-thread .pagination [class^=\'icon-\']',
142 | '.navigation .pagination ul li',
143 | '.view-thread .social-and-follow label',
144 | '#content .club-info',
145 | '#content .more-from h2',
146 | '#comments .comments .comment-header time',
147 | '#content .user-rating'
148 | ]
149 | },
150 | {
151 | type: 'ColorPicker',
152 | name: 'icon-color',
153 | title: 'Icon Color',
154 | property: 'color',
155 | selector: [
156 | '.section-header [class^=\'icon-\']',
157 | '.section-header [class*=\' icon-\']',
158 | 'main .header-clickable h3.section-clickable::before',
159 | '#sidebar .section-clickable [class^=\'icon-\']',
160 | '#sidebar section .iconized>i',
161 | '.new-game-time [class^=\'icon-\']',
162 | '#content section .iconized>i',
163 | '#content section.section-header .iconized>i',
164 | '#content section.section-row .iconized>i',
165 | '#content .section-wrapper [class^=\'icon-\']'
166 | ]
167 | },
168 | {
169 | type: 'FontFamily',
170 | optionFonts: [
171 | {
172 | title: 'Default',
173 | value: ''
174 | },
175 | {
176 | title: 'Trebuchet (V2)',
177 | value: 'Trebuchet MS'
178 | },
179 | {
180 | title: 'System Default',
181 | value: 'system,-apple-system,BlinkMacSystemFont,Segoe UI,' +
182 | 'Helvetica Neue,Helvetica,Lucida Grande'
183 | },
184 | {
185 | title: 'Helvetica',
186 | value: 'Helvetica'
187 | },
188 | {
189 | title: 'Arial',
190 | value: 'Arial'
191 | }
192 | ]
193 | }
194 | ]
195 | },
196 | {
197 | id: 'buttons',
198 | title: 'Buttons',
199 | icon: 'square-checkmark',
200 | options: [
201 | {
202 | type: 'ColorPicker',
203 | name: 'button-primary-color',
204 | title: 'Button Primary Color',
205 | property: 'backgroundColor',
206 | selector: [
207 | '#sidebar .btn.btn-primary'
208 | ]
209 | },
210 | {
211 | type: 'ColorPicker',
212 | name: 'button-primary-text-color',
213 | title: 'Button Primary Text Color',
214 | property: 'color',
215 | selector: [
216 | '#sidebar .btn.btn-primary'
217 | ]
218 | },
219 | {
220 | type: 'ColorPicker',
221 | name: 'button-secondary-color',
222 | title: 'Button Secondary Color',
223 | property: 'backgroundColor',
224 | selector: [
225 | 'sidebar .btn.btn-arrow',
226 | '.game-controls .btn',
227 | '.quick-game-controls-row .btn',
228 | '.new-game-time .btn',
229 | '.view-thread .comments .comment-number'
230 | ]
231 | },
232 | {
233 | type: 'ColorPicker',
234 | name: 'button-secondary-text-color',
235 | title: 'Button Secondary Text Color',
236 | property: 'color',
237 | selector: [
238 | 'sidebar .btn.btn-arrow',
239 | '#sidebar .btn.btn-arrow::after',
240 | '#sidebar .btn.btn-arrow .format-icon',
241 | '#sidebar .game-controls .control-group .btn.btn-icon i',
242 | '#sidebar .quick-game-controls-row .btn',
243 | '.new-game-time .btn'
244 | ]
245 | }
246 | ]
247 | },
248 | {
249 | id: 'hide',
250 | title: 'Hide',
251 | icon: 'circle-stop',
252 | options: [
253 | {
254 | type: 'ToggleDisplay',
255 | name: 'activity',
256 | title: 'Hide Activity',
257 | selector: [
258 | '.user-home #content > div > recent-content > section', //https://www.chess.com/home
259 | '.social-share-present #content > div:nth-child(7) > recent-content > section', //https://www.chess.com/member/wistcc
260 | '.social-share-present #content > div:nth-child(7)' //https://www.chess.com/member/wistcc?view=trophies
261 | ]
262 | },
263 | {
264 | type: 'ToggleDisplay',
265 | name: 'new-game',
266 | title: 'Hide New Game',
267 | selector: [
268 | '#sidebar > section.new-game-container.with-friends-list.recent-opponents', //https://www.chess.com/daily
269 | '.user-home #sidebar > section.new-game-container.recent-opponents' //https://www.chess.com/home
270 | ]
271 | },
272 | {
273 | type: 'ToggleDisplay',
274 | name: 'friends',
275 | title: 'Hide Friends',
276 | selector: [
277 | '.social-share-present #sidebar > section:nth-child(2)', //https://www.chess.com/member/wistcc
278 | '#sidebar > section.new-game-container.with-friends-list.recent-opponents > div.anim-panel.shown > ul.section-wrapper.users-grid', //https://www.chess.com/daily
279 | '.user-home #sidebar > section:nth-child(4)', //https://www.chess.com/home
280 | '#sidebar > section:nth-child(2)', //https://www.chess.com/members
281 | ],
282 | helpers: [
283 | {
284 | type: 'hide',
285 | selector: '.user-home #sidebar section:nth-child(3)',
286 | relation: 'clubs'
287 | }
288 | ]
289 | },
290 | {
291 | type: 'ToggleDisplay',
292 | name: 'clubs',
293 | title: 'Hide Clubs',
294 | selector: [
295 | '.clubs-index #sidebar > section:nth-child(1)' //https://www.chess.com/clubs
296 | ],
297 | helpers: [
298 | {
299 | type: 'hide',
300 | selector: '.user-home #sidebar section:nth-child(3)',
301 | relation: 'friends'
302 | }
303 | ]
304 | },
305 | {
306 | type: 'ToggleDisplay',
307 | name: 'stats',
308 | title: 'Hide Stats',
309 | selector: [
310 | '.user-home #user-rating-sidebar', //https://www.chess.com/home
311 | '.stats #sidebar > section:nth-child(1)', //https://www.chess.com/stats/daily?type=chess
312 | '.social-share-present #user-rating-sidebar', //https://www.chess.com/member/wistcc
313 | '#sidebar > section.rating-sidebar' //https://www.chess.com/daily
314 | ]
315 | },
316 | {
317 | type: 'ToggleDisplay',
318 | name: 'trophies',
319 | title: 'Hide Trophies',
320 | selector: [
321 | '.social-share-present #sidebar-user-trophy-showcase', //https://www.chess.com/member/wistcc
322 | '.user-home #sidebar-user-trophy-showcase', //https://www.chess.com/home
323 | ]
324 | }
325 | ]
326 | }
327 | ]
328 | };
329 |
--------------------------------------------------------------------------------
/app/components/Options/Options.css:
--------------------------------------------------------------------------------
1 | .options {
2 | background-color: #fff;
3 | }
4 |
5 | .options li {
6 | list-style-type: none;
7 | padding: 0;
8 | }
9 |
10 | .options > li {
11 | border-bottom: 1px solid #e8e7e6;
12 | }
13 |
14 | .options ul li {
15 | padding: 0 15px;
16 | }
17 |
18 | .sectionHeading {
19 | color: rgb(43, 43, 43);
20 | font-size: 14px;
21 | font-weight: 600;
22 | line-height: 20px;
23 | display: block;
24 | padding: 1rem;
25 | -webkit-user-select: none;
26 | }
27 |
28 | .sectionHeading:hover {
29 | cursor: pointer;
30 | }
31 |
32 | .sectionHeadingIcon {
33 | float: left;
34 | margin-right: 10px;
35 | position: relative;
36 | top: -3px;
37 | }
38 |
39 | .toggleIcon {
40 | float: right;
41 | }
42 |
43 | .active, .inactive {
44 | transition-duration: 0.3s;
45 | transition-property: transform;
46 | }
47 |
48 | .active {
49 | transform: rotate(90deg);
50 | }
51 |
52 | .inactive {
53 | transform: rotate(0deg);
54 | }
55 |
56 | select {
57 | -webkit-appearance: none;
58 | -moz-appearance: none;
59 | text-indent: 1px;
60 | padding: 5px;
61 | display: inline-block;
62 | cursor: pointer;
63 | position: relative;
64 | width: 50%;
65 | float: right;
66 | background: no-repeat #ffffff;
67 | background-position: right;
68 | background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABkAAAAeCAYAAADZ7LXbAAAACXBIWXMAAAsTAAALEwEAmpwYAAAKT2lDQ1BQaG90b3Nob3AgSUNDIHByb2ZpbGUAAHjanVNnVFPpFj333vRCS4iAlEtvUhUIIFJCi4AUkSYqIQkQSoghodkVUcERRUUEG8igiAOOjoCMFVEsDIoK2AfkIaKOg6OIisr74Xuja9a89+bN/rXXPues852zzwfACAyWSDNRNYAMqUIeEeCDx8TG4eQuQIEKJHAAEAizZCFz/SMBAPh+PDwrIsAHvgABeNMLCADATZvAMByH/w/qQplcAYCEAcB0kThLCIAUAEB6jkKmAEBGAYCdmCZTAKAEAGDLY2LjAFAtAGAnf+bTAICd+Jl7AQBblCEVAaCRACATZYhEAGg7AKzPVopFAFgwABRmS8Q5ANgtADBJV2ZIALC3AMDOEAuyAAgMADBRiIUpAAR7AGDIIyN4AISZABRG8lc88SuuEOcqAAB4mbI8uSQ5RYFbCC1xB1dXLh4ozkkXKxQ2YQJhmkAuwnmZGTKBNA/g88wAAKCRFRHgg/P9eM4Ors7ONo62Dl8t6r8G/yJiYuP+5c+rcEAAAOF0ftH+LC+zGoA7BoBt/qIl7gRoXgugdfeLZrIPQLUAoOnaV/Nw+H48PEWhkLnZ2eXk5NhKxEJbYcpXff5nwl/AV/1s+X48/Pf14L7iJIEyXYFHBPjgwsz0TKUcz5IJhGLc5o9H/LcL//wd0yLESWK5WCoU41EScY5EmozzMqUiiUKSKcUl0v9k4t8s+wM+3zUAsGo+AXuRLahdYwP2SycQWHTA4vcAAPK7b8HUKAgDgGiD4c93/+8//UegJQCAZkmScQAAXkQkLlTKsz/HCAAARKCBKrBBG/TBGCzABhzBBdzBC/xgNoRCJMTCQhBCCmSAHHJgKayCQiiGzbAdKmAv1EAdNMBRaIaTcA4uwlW4Dj1wD/phCJ7BKLyBCQRByAgTYSHaiAFiilgjjggXmYX4IcFIBBKLJCDJiBRRIkuRNUgxUopUIFVIHfI9cgI5h1xGupE7yAAygvyGvEcxlIGyUT3UDLVDuag3GoRGogvQZHQxmo8WoJvQcrQaPYw2oefQq2gP2o8+Q8cwwOgYBzPEbDAuxsNCsTgsCZNjy7EirAyrxhqwVqwDu4n1Y8+xdwQSgUXACTYEd0IgYR5BSFhMWE7YSKggHCQ0EdoJNwkDhFHCJyKTqEu0JroR+cQYYjIxh1hILCPWEo8TLxB7iEPENyQSiUMyJ7mQAkmxpFTSEtJG0m5SI+ksqZs0SBojk8naZGuyBzmULCAryIXkneTD5DPkG+Qh8lsKnWJAcaT4U+IoUspqShnlEOU05QZlmDJBVaOaUt2ooVQRNY9aQq2htlKvUYeoEzR1mjnNgxZJS6WtopXTGmgXaPdpr+h0uhHdlR5Ol9BX0svpR+iX6AP0dwwNhhWDx4hnKBmbGAcYZxl3GK+YTKYZ04sZx1QwNzHrmOeZD5lvVVgqtip8FZHKCpVKlSaVGyovVKmqpqreqgtV81XLVI+pXlN9rkZVM1PjqQnUlqtVqp1Q61MbU2epO6iHqmeob1Q/pH5Z/YkGWcNMw09DpFGgsV/jvMYgC2MZs3gsIWsNq4Z1gTXEJrHN2Xx2KruY/R27iz2qqaE5QzNKM1ezUvOUZj8H45hx+Jx0TgnnKKeX836K3hTvKeIpG6Y0TLkxZVxrqpaXllirSKtRq0frvTau7aedpr1Fu1n7gQ5Bx0onXCdHZ4/OBZ3nU9lT3acKpxZNPTr1ri6qa6UbobtEd79up+6Ynr5egJ5Mb6feeb3n+hx9L/1U/W36p/VHDFgGswwkBtsMzhg8xTVxbzwdL8fb8VFDXcNAQ6VhlWGX4YSRudE8o9VGjUYPjGnGXOMk423GbcajJgYmISZLTepN7ppSTbmmKaY7TDtMx83MzaLN1pk1mz0x1zLnm+eb15vft2BaeFostqi2uGVJsuRaplnutrxuhVo5WaVYVVpds0atna0l1rutu6cRp7lOk06rntZnw7Dxtsm2qbcZsOXYBtuutm22fWFnYhdnt8Wuw+6TvZN9un2N/T0HDYfZDqsdWh1+c7RyFDpWOt6azpzuP33F9JbpL2dYzxDP2DPjthPLKcRpnVOb00dnF2e5c4PziIuJS4LLLpc+Lpsbxt3IveRKdPVxXeF60vWdm7Obwu2o26/uNu5p7ofcn8w0nymeWTNz0MPIQ+BR5dE/C5+VMGvfrH5PQ0+BZ7XnIy9jL5FXrdewt6V3qvdh7xc+9j5yn+M+4zw33jLeWV/MN8C3yLfLT8Nvnl+F30N/I/9k/3r/0QCngCUBZwOJgUGBWwL7+Hp8Ib+OPzrbZfay2e1BjKC5QRVBj4KtguXBrSFoyOyQrSH355jOkc5pDoVQfujW0Adh5mGLw34MJ4WHhVeGP45wiFga0TGXNXfR3ENz30T6RJZE3ptnMU85ry1KNSo+qi5qPNo3ujS6P8YuZlnM1VidWElsSxw5LiquNm5svt/87fOH4p3iC+N7F5gvyF1weaHOwvSFpxapLhIsOpZATIhOOJTwQRAqqBaMJfITdyWOCnnCHcJnIi/RNtGI2ENcKh5O8kgqTXqS7JG8NXkkxTOlLOW5hCepkLxMDUzdmzqeFpp2IG0yPTq9MYOSkZBxQqohTZO2Z+pn5mZ2y6xlhbL+xW6Lty8elQfJa7OQrAVZLQq2QqboVFoo1yoHsmdlV2a/zYnKOZarnivN7cyzytuQN5zvn//tEsIS4ZK2pYZLVy0dWOa9rGo5sjxxedsK4xUFK4ZWBqw8uIq2Km3VT6vtV5eufr0mek1rgV7ByoLBtQFr6wtVCuWFfevc1+1dT1gvWd+1YfqGnRs+FYmKrhTbF5cVf9go3HjlG4dvyr+Z3JS0qavEuWTPZtJm6ebeLZ5bDpaql+aXDm4N2dq0Dd9WtO319kXbL5fNKNu7g7ZDuaO/PLi8ZafJzs07P1SkVPRU+lQ27tLdtWHX+G7R7ht7vPY07NXbW7z3/T7JvttVAVVN1WbVZftJ+7P3P66Jqun4lvttXa1ObXHtxwPSA/0HIw6217nU1R3SPVRSj9Yr60cOxx++/p3vdy0NNg1VjZzG4iNwRHnk6fcJ3/ceDTradox7rOEH0x92HWcdL2pCmvKaRptTmvtbYlu6T8w+0dbq3nr8R9sfD5w0PFl5SvNUyWna6YLTk2fyz4ydlZ19fi753GDborZ752PO32oPb++6EHTh0kX/i+c7vDvOXPK4dPKy2+UTV7hXmq86X23qdOo8/pPTT8e7nLuarrlca7nuer21e2b36RueN87d9L158Rb/1tWeOT3dvfN6b/fF9/XfFt1+cif9zsu72Xcn7q28T7xf9EDtQdlD3YfVP1v+3Njv3H9qwHeg89HcR/cGhYPP/pH1jw9DBY+Zj8uGDYbrnjg+OTniP3L96fynQ89kzyaeF/6i/suuFxYvfvjV69fO0ZjRoZfyl5O/bXyl/erA6xmv28bCxh6+yXgzMV70VvvtwXfcdx3vo98PT+R8IH8o/2j5sfVT0Kf7kxmTk/8EA5jz/GMzLdsAAAAgY0hSTQAAeiUAAICDAAD5/wAAgOkAAHUwAADqYAAAOpgAABdvkl/FRgAAAKRJREFUeNrs1TEKwkAQheEvIoI2nsk7qFdIq1hoJ3gCC5sUVpY23sDKXnvrYOUBbGITG0kQjQriPlgYhmF/3ryFjbIs82nVfEEBEiAB8k+Q+q1IkqSDNVq4lMy3scIkjuP0FSdbjNHMLys6OwyQVlnXEsOS2QP6OL8jkzlmd70jus86eBT8FIu8PqGXg6oFX6ARGthgX+V1ReFnDJAACZAfhFwHAJI7HF2lZGQaAAAAAElFTkSuQmCC);
69 | }
--------------------------------------------------------------------------------
/app/components/Options/Options.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Config from './Config';
3 | import Icon from '../Icon';
4 | import ColorPicker from '../ColorPicker';
5 | import ToggleDisplay from '../ToggleDisplay';
6 | import style from './Options.css';
7 | import BaseComponent from '../../BaseComponent.js';
8 |
9 | export default class Options extends BaseComponent {
10 |
11 | constructor() {
12 | super();
13 | this.state = { fontFamily: '', visible: {} };
14 |
15 | Config.groups.forEach(group => {
16 | this.state.visible[group.id] = false;
17 | });
18 |
19 | chrome.storage.local.get(result => {
20 | this.setState({ fontFamily: result.fontFamily,
21 | visible: this.state.visible
22 | });
23 | });
24 |
25 | // Reset dropdown list when reset button is hit
26 | chrome.storage.onChanged.addListener(changes => {
27 | try {
28 | const newValue = changes.fontFamily.newValue;
29 |
30 | if (Object.keys(newValue).length === 0) {
31 | this.setState({
32 | fontFamily: changes.fontFamily.newValue,
33 | visible: this.state.visible
34 | });
35 | }
36 | } catch (e) {
37 | // empty
38 | }
39 | });
40 | }
41 |
42 | handleToggle = (e) => {
43 | const id = e.target.id;
44 | this.setState({ fontFamily: this.state.fontFamily,
45 | visible: {
46 | [id]: !this.state.visible[id]
47 | }
48 | });
49 | }
50 |
51 | createFontOptions = (fonts) =>
52 | fonts.map((font, i) =>
53 | {font.title} )
54 |
55 | handleOnChange = (e) => {
56 | const font = e.target.value;
57 | chrome.storage.local.set({ fontFamily: font });
58 | this.sendMessageToDOM(font);
59 | this.setState({ fontFamily: font,
60 | visible: this.state.visible
61 | });
62 | }
63 |
64 | sendMessageToDOM = (font) => {
65 | chrome.tabs.query({ active: true, currentWindow: true }, tabs => {
66 | chrome.tabs.sendMessage(tabs[0].id, {
67 | update: 'fontFamily',
68 | font
69 | });
70 | });
71 | }
72 |
73 | render() {
74 | const groups = Config.groups;
75 |
76 | return (
77 |
78 | {groups.map((group) =>
79 |
80 |
85 |
86 |
91 |
92 | {group.title}
93 |
94 |
100 |
101 |
102 | {this.state.visible[group.id] ?
103 |
137 | : null}
138 |
139 | )}
140 |
141 | );
142 | }
143 | }
144 |
--------------------------------------------------------------------------------
/app/components/Play.css:
--------------------------------------------------------------------------------
1 | .play {
2 | background: #f1f1f1;
3 | padding: 15px;
4 | }
5 |
6 | .choices {
7 | padding: 0 0 10px;
8 | overflow: hidden;
9 | }
10 |
11 | .choicePallete {
12 | padding: 0px 0 15px;
13 | }
14 |
15 | .timeHeader {
16 | margin: 10px 0 5px;
17 | }
18 |
19 | .btn {
20 | background: #e4902d;
21 | display: block;
22 | text-align: center;
23 | color: white;
24 | border-radius: 0.2rem;
25 | padding: 1rem 0;
26 | font-size: 1.1rem;
27 | box-shadow: 0rem 0.07rem 0rem rgba(172,109,35,1);
28 | }
29 | .btn:hover {
30 | cursor: pointer;
31 | background: #d37e19;
32 | }
33 |
34 |
--------------------------------------------------------------------------------
/app/components/Play.js:
--------------------------------------------------------------------------------
1 | import React, { PropTypes } from 'react';
2 | import Variants from './Variants';
3 | import Times from './Times';
4 | import Button from './Button';
5 | import Link from './Link';
6 | import Icon from '../components/Icon';
7 | import style from '../components/Play.css';
8 | import buttonStyle from '../components/Button.css';
9 | import BaseComponent from '../BaseComponent';
10 |
11 | export default class Play extends BaseComponent {
12 |
13 | static propTypes = {
14 | api: PropTypes.object.isRequired
15 | };
16 |
17 | render() {
18 | return (
19 |
20 |
21 |
25 |
30 |
31 |
35 | {this.props.api.selectedTime.label}
36 |
37 |
38 | {this.props.api.showVariantsBox ?
39 |
45 | : null}
46 | {this.props.api.showTimesBox ?
47 |
53 | : null}
54 |
Play
55 |
56 | );
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/app/components/PlayContainer.js:
--------------------------------------------------------------------------------
1 | import React, { PropTypes } from 'react';
2 | import lodash from 'lodash';
3 | import Play from './Play';
4 | import BaseComponent from '../BaseComponent';
5 |
6 | export default class PlayContainer extends BaseComponent {
7 |
8 | static propTypes = {
9 | user: PropTypes.object.isRequired
10 | };
11 |
12 | constructor(props) {
13 | super(props);
14 | this.state = {
15 | variants: [
16 | {
17 | label: 'Standard',
18 | value: 'chess',
19 | icon: 'chess-board',
20 | daily: true,
21 | live: true
22 | },
23 | {
24 | label: 'Chess960',
25 | value: 'chess960',
26 | icon: '960',
27 | daily: true,
28 | live: true
29 | },
30 | {
31 | label: '3 Check',
32 | value: 'threecheck',
33 | icon: 'threecheck',
34 | live: true
35 | },
36 | {
37 | label: 'King of the Hill',
38 | value: 'kingofthehill',
39 | icon: 'kingofthehill',
40 | live: true
41 | },
42 | {
43 | label: 'Crazyhouse',
44 | value: 'crazyhouse',
45 | icon: 'crazyhouse',
46 | live: true
47 | }
48 | ],
49 | dailyTimes: [
50 | { label: '1 day', days: 1 },
51 | { label: '2 days', days: 2 },
52 | { label: '3 days', days: 3 },
53 | { label: '5 days', days: 5 },
54 | { label: '7 days', days: 7 },
55 | { label: '10 days', days: 10 },
56 | { label: '14 days', days: 14 },
57 | ],
58 | liveTimes: [
59 | { label: '1 min', minutes: 1, seconds: 0 },
60 | { label: '2 | 1', minutes: 2, seconds: 1 },
61 | { label: '3 min', minutes: 3, seconds: 0 },
62 | { label: '3 | 2', minutes: 3, seconds: 2 },
63 | { label: '5 min', minutes: 5, seconds: 0 },
64 | { label: '5 | 5', minutes: 5, seconds: 5 },
65 | { label: '10 min', minutes: 10, seconds: 0 },
66 | { label: '15 | 10', minutes: 15, seconds: 10 },
67 | { label: '30 min', minutes: 30, seconds: 0 },
68 | { label: '45 | 45', minutes: 45, seconds: 45 }
69 | ],
70 | showVariantsBox: false,
71 | showTimesBox: false,
72 | selectedType: 'daily'
73 | };
74 | this.state.selectedVariant = this.state.variants[0];
75 | this.state.selectedTime = this.state.dailyTimes[0];
76 | this.toggleVariantsBox = this.toggleVariantsBox.bind(this);
77 | this.toggleTimesBox = this.toggleTimesBox.bind(this);
78 | this.changeVariant = this.changeVariant.bind(this);
79 | this.changeTime = this.changeTime.bind(this);
80 | this.isSelectedTime = this.isSelectedTime.bind(this);
81 | this.isSelectedVariant = this.isSelectedVariant.bind(this);
82 | this.playUrl = this.playUrl.bind(this);
83 | }
84 |
85 | playUrl() {
86 | let slug;
87 | if (this.state.selectedType === 'live') {
88 | slug = `live/?#s=${this.state.selectedTime.minutes}m${this.state.selectedTime.seconds}s`;
89 | } else {
90 | slug = `home/#d=${this.state.selectedTime.days}`;
91 | }
92 | if (this.state.selectedVariant.value !== 'chess') {
93 | slug += `|${this.state.selectedVariant.value}`;
94 | }
95 | return slug;
96 | }
97 |
98 | toggleVariantsBox() {
99 | this.setState({ showVariantsBox: !this.state.showVariantsBox, showTimesBox: false });
100 | }
101 |
102 | toggleTimesBox() {
103 | this.setState({ showTimesBox: !this.state.showTimesBox, showVariantsBox: false });
104 | }
105 |
106 | changeVariant(variant) {
107 | this.setState({ selectedVariant: variant });
108 | this.closeBoxes();
109 | }
110 |
111 | closeBoxes() {
112 | this.setState({
113 | showVariantsBox: false,
114 | showTimesBox: false
115 | });
116 | }
117 |
118 | isSelectedVariant(variant) {
119 | return lodash.isEqual(this.state.selectedVariant, variant);
120 | }
121 |
122 | isSelectedTime(time) {
123 | return lodash.isEqual(this.state.selectedTime, time);
124 | }
125 |
126 | changeTime(time) {
127 | const update = {};
128 | if (lodash.find(this.state.dailyTimes, time)) {
129 | update.selectedType = 'daily';
130 | } else {
131 | update.selectedType = 'live';
132 | }
133 | // If the use has a variant incompatible with daily, revert to
134 | // standard
135 | if (time.days && !this.state.selectedVariant.daily) {
136 | this.setState({ selectedVariant: this.state.variants[0] });
137 | }
138 | // Close the boxes
139 | this.closeBoxes();
140 | update.selectedTime = time;
141 | this.setState(update);
142 | }
143 |
144 | render() {
145 | if (this.props.user.loggedIn) {
146 | return (
147 | React.createElement(Play, {
148 | api: Object.assign({}, this.state, {
149 | handlePlay: this.handlePlay,
150 | toggleVariantsBox: this.toggleVariantsBox,
151 | toggleTimesBox: this.toggleTimesBox,
152 | changeVariant: this.changeVariant,
153 | changeTime: this.changeTime,
154 | isSelectedVariant: this.isSelectedVariant,
155 | isSelectedTime: this.isSelectedTime,
156 | playUrl: this.playUrl
157 | })
158 | }));
159 | }
160 | return null;
161 | }
162 | }
163 |
--------------------------------------------------------------------------------
/app/components/Reset.js:
--------------------------------------------------------------------------------
1 | import React, { PropTypes } from 'react';
2 | import Icon from './Icon';
3 | import BaseComponent from '../BaseComponent';
4 |
5 | export default class Reset extends BaseComponent {
6 |
7 | static propTypes = {
8 | type: PropTypes.oneOf(['all', 'color']).isRequired,
9 | className: PropTypes.string,
10 | iconProps: PropTypes.object.isRequired,
11 | selector: PropTypes.string.isRequired,
12 | colorName: (props, propName, componentName) => {
13 | // This is the name given in the Config file and used to
14 | // store the styles of this kind of node in the local storage
15 | if (props.type === 'color' &&
16 | ((typeof props.colorName) !== 'string' ||
17 | !props.colorName)) {
18 | return new Error(`Invalid prop '${propName}' supplied to \
19 | '${componentName}'. Validation failed`);
20 | }
21 | },
22 | colorProperty: (props, propName, componentName) => {
23 | // This is the name of the property in the inline-styling
24 | // that should be reset
25 | if (props.type === 'color' &&
26 | ((typeof props.colorProperty) !== 'string' ||
27 | !props.colorProperty)) {
28 | return new Error(`Invalid prop '${propName}' supplied to \
29 | '${componentName}'. Validation failed`);
30 | }
31 | },
32 | };
33 |
34 | handleClick = () => {
35 | if (this.props.type === 'all') {
36 | chrome.storage.local.set({
37 | style: {},
38 | display: {},
39 | fontFamily: ''
40 | });
41 | chrome.tabs.query({ active: true, currentWindow: true }, tabs => {
42 | chrome.tabs.sendMessage(tabs[0].id, {
43 | update: 'reset',
44 | selector: this.props.selector,
45 | });
46 | });
47 | } else if (this.props.type === 'color') {
48 | const name = this.props.colorName;
49 | chrome.storage.local.get('style', result => {
50 | delete result.style[name];
51 | chrome.storage.local.set(result);
52 | });
53 | chrome.tabs.query({ active: true, currentWindow: true }, tabs => {
54 | chrome.tabs.sendMessage(tabs[0].id, {
55 | update: 'style',
56 | selector: this.props.selector,
57 | property: this.props.colorProperty,
58 | });
59 | });
60 | }
61 | }
62 |
63 | render() {
64 | return (
65 |
66 |
67 | { this.props.type === 'all' ?
68 | Reset All
69 | : null }
70 |
71 | );
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/app/components/ResetBar.css:
--------------------------------------------------------------------------------
1 | .resetBar {
2 | background: #f1f1f1;
3 | }
4 |
5 | .resetBar div {
6 | display:inline-block;
7 | position:relative;
8 | margin-bottom: 3px;
9 | }
10 |
11 | .resetIcon {
12 | float: left;
13 | margin-right: 5px;
14 | }
15 |
16 | .resetButton {
17 | left: 5px;
18 | padding: 5px;
19 | width: 70%;
20 | }
21 |
22 | .resetButton > div {
23 | font-weight: bold;
24 | font-size: 12px;
25 | line-height: -20px;
26 | color: #8c8a88;
27 | }
28 |
29 | .resetButton > div i {
30 | position: relative;
31 | margin-right: 5px;
32 | top: 3px;
33 | }
34 |
35 | .resetButton > div:hover {
36 | cursor: pointer;
37 | color: rgb(80, 80, 80);
38 | }
39 |
40 | .suggestionsIcon {
41 | position: relative;
42 | top: -2px;
43 | float: left;
44 | margin-right: 3px;
45 | }
46 |
47 | .suggestions {
48 | color: #8c8a88;
49 | text-align: center;
50 | font-weight: bold;
51 | font-size: 12px;
52 | text-decoration: none;
53 | right: 5%;
54 | }
55 |
56 | .suggestions:hover {
57 | color: rgb(80, 80, 80);
58 | cursor: pointer;
59 | }
60 |
61 | .suggestions:focus {
62 | outline: none;
63 | }
64 |
--------------------------------------------------------------------------------
/app/components/ResetBar.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import style from './ResetBar.css';
3 | import Icon from './Icon.js';
4 | import Config from './Options/Config';
5 | import Reset from '../components/Reset';
6 | import BaseComponent from '../BaseComponent';
7 |
8 | export default class ResetBar extends BaseComponent {
9 |
10 | handleSuggestionsClick = () => {
11 | const suggestions = 'http://goo.gl/forms/AVaggClVWuIyP87k1';
12 | chrome.tabs.create({ url: suggestions });
13 | }
14 |
15 | render() {
16 | let resetAllSelector = '';
17 | Config.groups.forEach(group => {
18 | group.options.forEach(option => {
19 | if (!Object.hasOwnProperty.call(option, 'selector')) {
20 | return;
21 | }
22 | const localSelectorArray = option.selector;
23 | if (resetAllSelector) {
24 | // Only do this if it's not the first time
25 | resetAllSelector += ',';
26 | }
27 | resetAllSelector += localSelectorArray.join(',');
28 | });
29 | });
30 |
31 | return (
32 |
33 |
34 |
39 |
40 |
41 |
42 |
43 |
44 | Suggestions
45 |
46 |
47 | );
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/app/components/Times.js:
--------------------------------------------------------------------------------
1 | import React, { PropTypes } from 'react';
2 | import classNames from 'classnames/bind';
3 | import Button from './Button';
4 | import Icon from '../components/Icon';
5 | import buttonStyle from '../components/Button.css';
6 | import style from '../components/Play.css';
7 | import BaseComponent from '../BaseComponent';
8 |
9 | const cx = classNames.bind(style);
10 |
11 | export default class Times extends BaseComponent {
12 |
13 | static propTypes = {
14 | liveTimes: PropTypes.array.isRequired,
15 | dailyTimes: PropTypes.array.isRequired,
16 | onClick: PropTypes.func.isRequired,
17 | isSelectedTime: PropTypes.func.isRequired
18 | };
19 |
20 | render() {
21 | const liveTimes = this.props.liveTimes.map((time, i) => {
22 | const className = cx({
23 | [buttonStyle.selected]: this.props.isSelectedTime(time),
24 | [buttonStyle.small]: true
25 | });
26 |
27 | return ({time.label} );
33 | });
34 |
35 | const dailyTimes = this.props.dailyTimes.map((time, i) => {
36 | const className = cx({
37 | [buttonStyle.selected]: this.props.isSelectedTime(time),
38 | [buttonStyle.small]: true
39 | });
40 | return ({time.label} );
46 | });
47 |
48 | return (
49 |
50 |
51 |
52 | Live
53 |
54 | { liveTimes }
55 |
56 |
57 |
58 | Daily
59 |
60 | { dailyTimes }
61 |
62 |
63 | );
64 | }
65 | }
66 |
67 |
--------------------------------------------------------------------------------
/app/components/Toggle.css:
--------------------------------------------------------------------------------
1 | .row {
2 | padding: 0.2rem 0;
3 | }
4 |
5 | .toggle {
6 | margin-top: 2px;
7 | width: 44px;
8 | height: 22px;
9 | background: #bfbeba;
10 | border: 1px solid #e8e7e6;
11 | position: relative;
12 | cursor: pointer;
13 | border-radius: 13px;
14 | float: right;
15 | }
16 |
17 | .active {
18 | background: #67A032;
19 | }
20 |
21 | .button {
22 | border-radius: 100%;
23 | width: 24px;
24 | height: 24px;
25 | margin: -2px;
26 | background: #e8e7e6;
27 | border: 1px solid #dbd9d7;
28 | position: absolute;
29 | transition: all .2s .2s ease-out;
30 | transition-delay: 0s;
31 | left: 0;
32 | }
33 |
34 | .active .button {
35 | left: 21px;
36 | }
37 |
--------------------------------------------------------------------------------
/app/components/ToggleDisplay.js:
--------------------------------------------------------------------------------
1 | import React, { PropTypes } from 'react';
2 | import classNames from 'classnames/bind';
3 | import style from './Toggle.css';
4 | import BaseComponent from '../BaseComponent';
5 |
6 | const cx = classNames.bind(style);
7 |
8 | export default class ToggleDisplay extends BaseComponent {
9 |
10 | static propTypes = {
11 | name: PropTypes.string.isRequired,
12 | title: PropTypes.string.isRequired,
13 | selector: PropTypes.string.isRequired,
14 | helpers: PropTypes.array
15 | };
16 |
17 | constructor(props, context) {
18 | super(props, context);
19 | this.state = {
20 | update: 'display',
21 | name: this.props.name,
22 | selector: this.props.selector,
23 | visible: true,
24 | helpers: this.props.helpers
25 | };
26 | this.checkIfStorageAlreadyExists();
27 | }
28 |
29 | componentDidUpdate = () => {
30 | const name = this.props.name;
31 | const state = JSON.parse(JSON.stringify(this.state));
32 |
33 | this.save(name, state);
34 |
35 | this.runHelpers();
36 | this.sendMessageToDOM();
37 | }
38 |
39 | save = (name, state) => {
40 | chrome.storage.local.get('display', result => {
41 | const obj = result;
42 | if (Object.keys(result).length === 0 && obj.constructor === Object) {
43 | obj.display = {};
44 | }
45 | obj.display[name] = state;
46 | chrome.storage.local.set(obj);
47 | });
48 | }
49 |
50 | checkIfStorageAlreadyExists() {
51 | const name = this.props.name;
52 | chrome.storage.local.get(result => {
53 | if (!{}.hasOwnProperty.call(result, 'display')) {
54 | return chrome.storage.local.set({ display: {} });
55 | }
56 |
57 | if ({}.hasOwnProperty.call(result.display, name)) {
58 | this.setState({ visible: result.display[name].visible });
59 | }
60 | });
61 | }
62 |
63 | runHelpers = () => {
64 | if (this.state.helpers) {
65 | chrome.storage.local.get('display', result => {
66 | this.state.helpers.forEach(helper => {
67 | if (helper.type === 'hide') {
68 | if (!{}.hasOwnProperty.call(result.display, helper.relation)) {
69 | return;
70 | }
71 | const relation = result.display[helper.relation].visible;
72 | helper.display = (this.state.visible + relation) !== 0;
73 | this.sendHelperToDOM(helper);
74 | }
75 | });
76 | });
77 | }
78 | }
79 |
80 | handleClick = () => {
81 | const props = this.props;
82 | this.setState({
83 | visible: !this.state.visible,
84 | selector: props.selector
85 | });
86 | };
87 |
88 | sendMessageToDOM = () => {
89 | chrome.tabs.query({ active: true, currentWindow: true }, tabs => {
90 | chrome.tabs.sendMessage(tabs[0].id, {
91 | update: 'display',
92 | selector: this.props.selector,
93 | display: this.state.visible
94 | });
95 | });
96 | }
97 |
98 | sendHelperToDOM = (helper) => {
99 | const target = [this.state.name, helper.relation].sort();
100 | const obj = {
101 | update: 'display',
102 | name: `helper-${helper.type}-${target[0]}-${target[1]}`,
103 | selector: helper.selector,
104 | visible: helper.display
105 |
106 | };
107 |
108 | chrome.tabs.query({ active: true, currentWindow: true }, tabs => {
109 | chrome.tabs.sendMessage(tabs[0].id, {
110 | update: obj.update,
111 | selector: obj.selector,
112 | display: obj.visible
113 | });
114 | });
115 |
116 | if (typeof obj.visible !== 'undefined') {
117 | this.save(obj.name, obj);
118 | }
119 | }
120 |
121 | render() {
122 | const toggle = cx({
123 | toggle: true,
124 | active: !this.state.visible
125 | });
126 |
127 | const inputId = `${this.props.name}_input`;
128 |
129 | return (
130 |
131 |
{this.props.title}
132 |
139 |
140 | );
141 | }
142 | }
143 |
--------------------------------------------------------------------------------
/app/components/Variants.js:
--------------------------------------------------------------------------------
1 | import React, { PropTypes } from 'react';
2 | import classNames from 'classnames/bind';
3 | import Button from './Button';
4 | import Icon from '../components/Icon';
5 | import style from '../components/Play.css';
6 | import buttonStyle from '../components/Button.css';
7 | import BaseComponent from '../BaseComponent';
8 |
9 | const cx = classNames.bind(style);
10 |
11 | export default class Variants extends BaseComponent {
12 |
13 | static propTypes = {
14 | variants: PropTypes.array.isRequired,
15 | selectedType: PropTypes.string.isRequired,
16 | onClick: PropTypes.func.isRequired,
17 | isSelectedVariant: PropTypes.func.isRequired
18 | };
19 |
20 | render() {
21 | const variants = this.props.variants.map((type, i) => {
22 | const className = cx({
23 | [buttonStyle.selected]: this.props.isSelectedVariant(type),
24 | [buttonStyle.panel]: true
25 | });
26 |
27 | if (type[this.props.selectedType] === true) {
28 | return (
29 |
35 |
40 | {type.label}
41 |
42 | );
43 | }
44 | return false;
45 | });
46 |
47 | return ({ variants }
);
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/app/containers/App.css:
--------------------------------------------------------------------------------
1 | .app-base {
2 | background: #1d1c1a;
3 | }
4 |
5 | .app-bulk {
6 | /* Max height of extension is 600px, if we don't add our
7 | * our own max-height, a second scrollbar will appear when
8 | * the extension tries to exceed 600px. Notification bar is 45px
9 | * and Header is 55 so 600-45-55 = 500.
10 | */
11 | max-height: 500px;
12 | overflow-y: auto;
13 | }
14 |
15 | .not-mac-os .app-bulk {
16 | /* If not MacOS we make vertical scroll bar always show
17 | * so as to avoid the extension being jumpy when overflow
18 | * sets in on expanding an option.
19 | */
20 | overflow-y: scroll;
21 | }
22 |
--------------------------------------------------------------------------------
/app/containers/App.js:
--------------------------------------------------------------------------------
1 | import React, { PropTypes } from 'react';
2 | import Header from '../components/Header';
3 | import NotificationBar from '../components/NotificationBar';
4 | import ResetBar from '../components/ResetBar';
5 | import PlayContainer from '../components/PlayContainer';
6 | import Options from '../components/Options/Options';
7 | import style from './App.css';
8 | import BaseComponent from '../BaseComponent';
9 |
10 | export default class App extends BaseComponent {
11 |
12 | static propTypes = {
13 | user: PropTypes.object.isRequired,
14 | notifications: PropTypes.object.isRequired
15 | };
16 |
17 | render() {
18 | let notificationBar = (
);
19 |
20 | if (!this.props.user.loading) {
21 | if (this.props.user.loggedIn) {
22 | notificationBar = (
23 |
24 | );
25 | }
26 | }
27 |
28 | let appClassName = style['app-base'];
29 | if (this.props.os !== 'mac') {
30 | appClassName += ` ${style['not-mac-os']}`;
31 | }
32 |
33 | return (
34 |
35 |
36 |
41 | { notificationBar }
42 |
43 | );
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/app/containers/Root.js:
--------------------------------------------------------------------------------
1 | import xhr from 'xhr';
2 | import React from 'react';
3 | import App from './App';
4 | import BaseComponent from '../BaseComponent';
5 |
6 | export default class Root extends BaseComponent {
7 |
8 | constructor() {
9 | super();
10 | this.state = {
11 | user: {
12 | loading: true,
13 | onChessCom: null,
14 | loggedIn: null
15 | },
16 | notifications: {
17 | loading: true,
18 | games: '',
19 | messages: '',
20 | alerts: ''
21 | }
22 | };
23 | }
24 |
25 | componentDidMount() {
26 | chrome.storage.local.get('notifications', result => {
27 | const data = Object.assign({}, result.notifications);
28 | data.loading = false;
29 | this.setState({ notifications: data });
30 | });
31 |
32 | const user = this.state.user;
33 |
34 | this.calcLoggedIn(user).then((user1) => {
35 | if (user1.loggedIn) {
36 | if (!user1.avatarUrl) {
37 | this.setAvatar(user1).then((user2) => {
38 | this.resolveUser(user2);
39 | // Save the avatar to localStorage so we can cache it
40 | chrome.storage.local.set({ user: user2 });
41 | });
42 | } else {
43 | this.resolveUser(user1);
44 | }
45 | } else {
46 | this.calcOnChessCom(user1).then((user2) => {
47 | this.resolveUser(user2);
48 | });
49 | }
50 | });
51 | }
52 |
53 | /**
54 | * This should only be called if we know for a fact that the
55 | * user is logged in and their avatar has not yet been cached
56 | *
57 | * @return Promise
58 | */
59 | setAvatar(user) {
60 | return new Promise(resolve =>
61 | xhr.get(`https://www.chess.com/callback/user/popup/${user.username}`,
62 | { json: true },
63 | (err, resp) => {
64 | if (resp.statusCode === 200) {
65 | return resolve(Object.assign({}, user, { avatarUrl: resp.body.avatarUrl }));
66 | }
67 | resolve(user);
68 | })
69 | );
70 | }
71 |
72 | /**
73 | * @return Promise
74 | */
75 | calcLoggedIn(user) {
76 | return new Promise(resolve =>
77 | chrome.storage.local.get('user', (result) => {
78 | if (result.user) {
79 | // Add payload and loggedIn property to user
80 | resolve(Object.assign({}, user, result.user, { loggedIn: true }));
81 | } else {
82 | resolve(Object.assign({}, user, { loggedIn: false }));
83 | }
84 | }));
85 | }
86 |
87 | /**
88 | * Earmark the user as currently on Chess.com or not
89 | *
90 | * @return Promise
91 | */
92 | calcOnChessCom(user) {
93 | return new Promise(resolve =>
94 | chrome.tabs.query({ active: true, currentWindow: true }, tabs => {
95 | if (tabs[0].url.indexOf('chess.com') >= 0) {
96 | resolve(Object.assign({}, user, { onChessCom: true }));
97 | } else {
98 | resolve(Object.assign({}, user, { onChessCom: false }));
99 | }
100 | }));
101 | }
102 |
103 | /**
104 | * Once a promise is fulfilled we should call this function. This is
105 | * the only place where we set user.loading to false.
106 | *
107 | * @return promise
108 | */
109 | resolveUser(user) {
110 | const userInfoComplete = user.avatarUrl;
111 | if (userInfoComplete || user.onChessCom === false ||
112 | (user.onChessCom !== null && user.loggedIn !== null)) {
113 | const userToSave = Object.assign({}, user, { loading: false });
114 | this.setState({ user: userToSave });
115 | }
116 | }
117 |
118 | render() {
119 | return (
120 |
125 | );
126 | }
127 | }
128 |
--------------------------------------------------------------------------------
/chrome/assets/fonts/chessglyph-regular.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ChessCom/browser-extension/552e791d3f15b2dcbb80974cd582f06d922fc07e/chrome/assets/fonts/chessglyph-regular.woff
--------------------------------------------------------------------------------
/chrome/assets/img/arrow-down-medium.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ChessCom/browser-extension/552e791d3f15b2dcbb80974cd582f06d922fc07e/chrome/assets/img/arrow-down-medium.png
--------------------------------------------------------------------------------
/chrome/assets/img/arrow-down-small.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ChessCom/browser-extension/552e791d3f15b2dcbb80974cd582f06d922fc07e/chrome/assets/img/arrow-down-small.png
--------------------------------------------------------------------------------
/chrome/assets/img/home.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ChessCom/browser-extension/552e791d3f15b2dcbb80974cd582f06d922fc07e/chrome/assets/img/home.png
--------------------------------------------------------------------------------
/chrome/assets/img/icon-128.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ChessCom/browser-extension/552e791d3f15b2dcbb80974cd582f06d922fc07e/chrome/assets/img/icon-128.png
--------------------------------------------------------------------------------
/chrome/assets/img/icon-16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ChessCom/browser-extension/552e791d3f15b2dcbb80974cd582f06d922fc07e/chrome/assets/img/icon-16.png
--------------------------------------------------------------------------------
/chrome/assets/img/icon-48.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ChessCom/browser-extension/552e791d3f15b2dcbb80974cd582f06d922fc07e/chrome/assets/img/icon-48.png
--------------------------------------------------------------------------------
/chrome/assets/img/interface-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ChessCom/browser-extension/552e791d3f15b2dcbb80974cd582f06d922fc07e/chrome/assets/img/interface-icon.png
--------------------------------------------------------------------------------
/chrome/assets/img/logo-black.svg:
--------------------------------------------------------------------------------
1 | Slice 1
--------------------------------------------------------------------------------
/chrome/assets/img/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ChessCom/browser-extension/552e791d3f15b2dcbb80974cd582f06d922fc07e/chrome/assets/img/logo.png
--------------------------------------------------------------------------------
/chrome/assets/img/logo.svg:
--------------------------------------------------------------------------------
1 | Slice 1
--------------------------------------------------------------------------------
/chrome/assets/img/sprite.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ChessCom/browser-extension/552e791d3f15b2dcbb80974cd582f06d922fc07e/chrome/assets/img/sprite.png
--------------------------------------------------------------------------------
/chrome/extension/background.js:
--------------------------------------------------------------------------------
1 | function loadScript(name, tabId, cb) {
2 | if (process.env.NODE_ENV === 'production') {
3 | chrome.tabs.executeScript(tabId, {
4 | file: `/js/${name}.bundle.js`,
5 | runAt: 'document_start'
6 | },
7 | cb);
8 | } else {
9 | // dev: async fetch bundle
10 | fetch(`https://localhost:3000/js/${name}.bundle.js`)
11 | .then(res => res.text())
12 | .then(fetchRes => {
13 | chrome.tabs.executeScript(tabId, { code: fetchRes, runAt: 'document_end' }, cb);
14 | });
15 | }
16 | }
17 |
18 | function jsNameToCssName(name) {
19 | return name.replace(/([A-Z])/g, '-$1').toLowerCase();
20 | }
21 |
22 | function injectCSS(tabId, style) {
23 | Object.keys(style).forEach(key => {
24 | const prop = style[key];
25 | const color = prop.color;
26 | const property = jsNameToCssName(prop.property);
27 | const rgba = `rgba(${color.r},${color.g},${color.b},${color.a})`;
28 | let selector = prop.selector;
29 | if (selector) {
30 | selector = selector.split(',').map(singleSelector => (
31 | `body.removable-initial-styles ${singleSelector}`
32 | )).join(',');
33 | }
34 | const css = `${selector} { ${property}: ${rgba} }`;
35 | chrome.tabs.insertCSS(tabId, {
36 | code: css,
37 | runAt: 'document_start'
38 | });
39 | });
40 | }
41 |
42 | function injectDisplay(tabId, display) {
43 | Object.keys(display).forEach(key => {
44 | const prop = display[key];
45 | const visible = prop.visible ? 'block' : 'none';
46 | let selector = prop.selector;
47 | if (selector) {
48 | selector = selector.split(',').map(singleSelector => (
49 | `body.removable-initial-styles ${singleSelector}`
50 | )).join(',');
51 | }
52 | const css = `${selector} { display: ${visible} }`;
53 | chrome.tabs.insertCSS(tabId, {
54 | code: css,
55 | runAt: 'document_start'
56 | });
57 | });
58 | }
59 |
60 | function injectFontFamily(tabId, fontFamily) {
61 | const code = `body.removable-initial-styles { font-family: ${fontFamily} !important }`;
62 | chrome.tabs.insertCSS(tabId, {
63 | code,
64 | runAt: 'document_start'
65 | });
66 | }
67 |
68 | function injectTempStylesClass(tabId) {
69 | // We use a mutation observer to be able to modify the DOM
70 | // as soon as the body element is created but before anything is
71 | // rendered so as to avoid FOUC
72 | const code = (`
73 | const observer = new MutationObserver(function(mutations) {
74 | // Use .some instead of .forEach as .forEach can't be cancelled and
75 | // .some cancels as soon as a truthy return value is given
76 | mutations.some(function(mutation) {
77 | if (mutation.target.nodeName === 'HTML' && mutation.addedNodes &&
78 | mutation.addedNodes.length && mutation.addedNodes[0].nodeName === 'BODY') {
79 | // The body element was created in the dom
80 | const body = mutation.addedNodes[0];
81 | body.classList.add('removable-initial-styles');
82 | // Stop observer listening
83 | observer.disconnect();
84 | // Stop the loop by returning true to .some
85 | return true;
86 | }
87 | });
88 | });
89 |
90 | const config = {childList: true, subtree: true};
91 | observer.observe(document, config);
92 | `);
93 |
94 | chrome.tabs.executeScript(tabId, { code, runAt: 'document_start' });
95 | }
96 |
97 | function updateBadge() {
98 | chrome.runtime.onMessage.addListener((request) => {
99 | if (request.badge || request.badge === 0) {
100 | const total = request.badge.toString();
101 | // Use red background for now
102 | chrome.browserAction.setBadgeBackgroundColor({ color: [179, 52, 48, 50] });
103 | if (total >= 1) {
104 | chrome.browserAction.setBadgeText({ text: total });
105 | } else {
106 | chrome.browserAction.setBadgeText({ text: '' });
107 | }
108 | }
109 | });
110 | }
111 |
112 | function onLoading(tabId) {
113 | // Get cached styles to inject before the DOM loads on each page load
114 | chrome.storage.local.get(storage => {
115 | injectCSS(tabId, storage.style || {});
116 | injectDisplay(tabId, storage.display || {});
117 | injectFontFamily(tabId, storage.fontFamily || '');
118 | });
119 | // Insert the temp styles class in body so initial css shows
120 | injectTempStylesClass(tabId);
121 |
122 | updateBadge();
123 |
124 | if (chrome.runtime.lastError) return;
125 |
126 | // Loads content script to manipulate the dom in real time
127 | loadScript('handleLiveChanges', tabId);
128 | }
129 |
130 | const arrowURLs = ['^https://www\\.chess\\.com'];
131 |
132 | chrome.tabs.onUpdated.addListener(async (tabId, changeInfo, tab) => {
133 | if (tab.url.match(arrowURLs.join('|'))) {
134 | if (changeInfo.status === 'loading') {
135 | onLoading(tabId);
136 | }
137 | }
138 | });
139 |
--------------------------------------------------------------------------------
/chrome/extension/handleLiveChanges.js:
--------------------------------------------------------------------------------
1 | import _ from 'lodash';
2 |
3 | function jsNameToCssName(name) {
4 | return name.replace(/([A-Z])/g, '-$1').toLowerCase();
5 | }
6 |
7 | function updateStyles() {
8 | // Handles live update from color picker
9 | chrome.runtime.onMessage.addListener(
10 | (request, sender) => {
11 | if (!sender.tab) {
12 | if (request.update === 'style') {
13 | const { property, selector, color } = request;
14 | const elementsArray = document.querySelectorAll(selector);
15 |
16 | let rgba = '';
17 | if (color) {
18 | rgba = `rgba(${color.r},${color.g},${color.b},${color.a})`;
19 | }
20 |
21 | elementsArray.forEach(element => {
22 | element.style[property] = rgba;
23 | });
24 | }
25 | }
26 | }
27 | );
28 | }
29 |
30 | function updateDisplay() {
31 | chrome.runtime.onMessage.addListener(
32 | (request, sender) => {
33 | if (!sender.tab) {
34 | if (request.update === 'display') {
35 | const elementsArray = document.querySelectorAll(request.selector);
36 | try {
37 | elementsArray.forEach(element => {
38 | if (!request.display) {
39 | element.style.display = 'none';
40 | } else {
41 | element.style.display = 'block';
42 | }
43 | });
44 | } catch (e) {
45 | throw e;
46 | }
47 | }
48 | }
49 | }
50 | );
51 | }
52 |
53 | function updateFontFamily() {
54 | chrome.runtime.onMessage.addListener(
55 | (request, sender) => {
56 | if (!sender.tab) {
57 | if (request.update === 'fontFamily') {
58 | try {
59 | document.body.style.fontFamily = `${request.font} !important`;
60 | } catch (e) {
61 | throw e;
62 | }
63 | }
64 | }
65 | }
66 | );
67 | }
68 |
69 | function reset() {
70 | // Resets all styles on all elements given in selector
71 | chrome.runtime.onMessage.addListener(
72 | (request, sender) => {
73 | if (!sender.tab) {
74 | if (request.update === 'reset') {
75 | const elementsArray = document.querySelectorAll(request.selector);
76 | elementsArray.forEach(element => {
77 | element.style = '';
78 | });
79 | }
80 | }
81 | }
82 | );
83 | }
84 |
85 | function sendNotification(total, cb) {
86 | return chrome.runtime.sendMessage({
87 | badge: total
88 | }, cb);
89 | }
90 |
91 | function getNotifications() {
92 | // Set a delay for getting notifcations
93 | setTimeout(() => {
94 | const elementsArray = document.querySelectorAll('span[data-notifications]');
95 | const nodes = [...elementsArray].splice(0, 3);
96 | let total = 0;
97 |
98 | const notifications = {
99 | games: '',
100 | messages: '',
101 | alerts: ''
102 | };
103 | const notificationKeys = Object.keys(notifications);
104 |
105 | nodes.map((target, index) => {
106 | const value = parseInt(target.dataset.notifications, 10);
107 | total += value;
108 | if (value !== 0) {
109 | notifications[notificationKeys[index]] = parseInt(target.dataset.notifications, 10);
110 | }
111 | return total;
112 | });
113 |
114 | chrome.storage.local.set({ notifications });
115 | return sendNotification(total, getNotifications);
116 | }, 60000);
117 | }
118 |
119 | function onLoadComplete() {
120 | // We make all the injected styles inline for easier
121 | // manipulation and then remove the removable-initial-styles class from body
122 | chrome.storage.local.get(storage => {
123 | const { style, display, fontFamily } = storage;
124 | // Browsers should batch all these DOM updates as they are all consecutive
125 | // First insert inline-styles
126 | if (style) {
127 | _.forEach(style, updateObject => {
128 | const { color, selector } = updateObject;
129 | const property = jsNameToCssName(updateObject.property);
130 | const rgba = `rgba(${color.r},${color.g},${color.b},${color.a})`;
131 | const elementsArray = document.querySelectorAll(selector);
132 | elementsArray.forEach(element => {
133 | element.style[property] = rgba;
134 | });
135 | });
136 | }
137 | // Then we insert inline-display
138 | if (display) {
139 | _.forEach(display, updateObject => {
140 | const visible = updateObject.visible ? 'block' : 'none';
141 | const elementsArray = document.querySelectorAll(updateObject.selector);
142 | elementsArray.forEach(element => {
143 | element.style.display = visible;
144 | });
145 | });
146 | }
147 | // Finally insert inline-fontFamily
148 | if (fontFamily) {
149 | document.body.style.fontFamily = `${fontFamily} !important`;
150 | }
151 |
152 | // When all inline-styles are applied we can remove the class from the body
153 | document.body.classList.remove('removable-initial-styles');
154 | });
155 | }
156 |
157 | window.addEventListener('load', () => {
158 | updateStyles();
159 | updateDisplay();
160 | updateFontFamily();
161 | reset();
162 | onLoadComplete();
163 |
164 | /**
165 | * Set a delay whilst we wait for current page to compute all DOM
166 | * elements that have notification attributes bound to them
167 | */
168 | setTimeout(getNotifications, 1000);
169 | });
170 |
171 | // This listener catches the message from chrome/getCurrentUser.js
172 | // which contains the current logged in user and then stores it locally
173 | // so our extension knows we are logged in
174 | window.addEventListener('message', (event) => {
175 | // We only accept messages from ourselves
176 | if (event.source !== window) {
177 | return;
178 | }
179 |
180 | if (event.data.username) {
181 | chrome.storage.local.set({ user: event.data });
182 | }
183 | }, false);
184 |
--------------------------------------------------------------------------------
/chrome/extension/popup.css:
--------------------------------------------------------------------------------
1 | *,
2 | html,
3 | body {
4 | margin: 0;
5 | padding: 0;
6 | }
7 |
8 | button {
9 | margin: 0;
10 | padding: 0;
11 | border: 0;
12 | background: none;
13 | font-size: 100%;
14 | vertical-align: baseline;
15 | font-family: inherit;
16 | font-weight: inherit;
17 | color: inherit;
18 | appearance: none;
19 | font-smoothing: antialiased;
20 | }
21 |
22 | body {
23 | font: 14px 'Open Sans', 'Helvetica Neue', Helvetica, Arial, sans-serif;
24 | line-height: 1.4em;
25 | background: #f5f5f5;
26 | color: #4d4d4d;
27 | width: 320px;
28 | margin: 0 auto;
29 | }
30 |
31 | /* This is more specific and put after
32 | * the body so should override with specifics
33 | * for OSX styling
34 | */
35 | body.is-mac-os {
36 | width: 300px;
37 | }
38 |
39 | button,
40 | input[type="checkbox"] {
41 | outline: none;
42 | }
43 |
44 | * {
45 | -webkit-box-sizing: border-box;
46 | -moz-box-sizing: border-box;
47 | box-sizing: border-box;
48 | }
49 |
--------------------------------------------------------------------------------
/chrome/extension/popup.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import Root from '../../app/containers/Root';
4 | import styles from './popup.css';
5 |
6 | // Get platformInfo before rendering Root
7 | chrome.runtime.getPlatformInfo(platformInfo => {
8 | if (platformInfo.os === 'mac') {
9 | document.body.classList.add(styles['is-mac-os']);
10 | }
11 | ReactDOM.render(
12 | ,
13 | document.querySelector('#root')
14 | );
15 | });
16 |
--------------------------------------------------------------------------------
/chrome/getCurrentUser.js:
--------------------------------------------------------------------------------
1 | if (window.context) {
2 | // Chess.com sets the logged in user data on window.context
3 | // which we fetch from inside the actual website and send to
4 | // the extension through postMessage
5 | window.postMessage(window.context.user, '*');
6 | // This lets Chess.com know that this user is using the extension
7 | // which can help with troubleshooting / data handling etc.
8 | window.context.chessBrowserExtension = true;
9 | }
10 |
--------------------------------------------------------------------------------
/chrome/injectScriptWithWindowAccess.js:
--------------------------------------------------------------------------------
1 | /**
2 | * injectScript - Inject internal script to available access to the `window`
3 | *
4 | * @param {type} file_path Local path of the internal script.
5 | * @param {type} tag The tag as string, where the script will be append (default: 'body').
6 | * @see {@link http://stackoverflow.com/questions/20499994/access-window-variable-from-content-script}
7 | */
8 | function injectScript(filePath, tag) {
9 | const node = document.getElementsByTagName(tag)[0];
10 | const script = document.createElement('script');
11 | script.setAttribute('type', 'text/javascript');
12 | script.setAttribute('src', filePath);
13 | node.appendChild(script);
14 | }
15 | injectScript(chrome.extension.getURL('getCurrentUser.js'), 'body');
16 |
--------------------------------------------------------------------------------
/chrome/manifest.build.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "1.0.0",
3 | "name": "Chess Browser Extension",
4 | "manifest_version": 2,
5 | "description": "Customize your Chess.com Experience",
6 | "browser_action": {
7 | "default_title": "Chess.com Browser Extension",
8 | "default_popup": "popup.html"
9 | },
10 | "icons": {
11 | "16": "img/icon-16.png",
12 | "48": "img/icon-48.png",
13 | "128": "img/icon-128.png"
14 | },
15 | "web_accessible_resources": [
16 | "getCurrentUser.js"
17 | ],
18 | "content_scripts": [
19 | {
20 | "matches": ["https://www.chess.com/*"],
21 | "js": ["injectScriptWithWindowAccess.js"],
22 | "all_frames": true
23 | }
24 | ],
25 | "background": {
26 | "page": "background.html"
27 | },
28 | "permissions": [ "contextMenus", "tabs", "storage", "https://www.chess.com/*" ],
29 | "content_security_policy": "default-src 'self'; script-src 'self'; connect-src https://www.chess.com; style-src * 'unsafe-inline'; img-src 'self' https://images.chesscomfiles.com data:; font-src 'self' data:;"
30 | }
31 |
--------------------------------------------------------------------------------
/chrome/manifest.buildFirefox.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "1.0.0",
3 | "name": "Chess Browser Extension",
4 | "manifest_version": 2,
5 | "description": "Customize your Chess.com Experience",
6 | "browser_action": {
7 | "default_title": "Chess.com Browser Extension",
8 | "default_popup": "popup.html",
9 | "default_icon": {
10 | "48": "img/icon-48.png"
11 | }
12 | },
13 | "icons": {
14 | "16": "img/icon-16.png",
15 | "48": "img/icon-48.png",
16 | "128": "img/icon-128.png"
17 | },
18 | "applications": {
19 | "gecko": {
20 | "id": "chesscom@gmail.com"
21 | }
22 | },
23 | "web_accessible_resources": [
24 | "getCurrentUser.js"
25 | ],
26 | "content_scripts": [
27 | {
28 | "matches": ["https://www.chess.com/*"],
29 | "js": ["injectScriptWithWindowAccess.js"],
30 | "all_frames": true
31 | }
32 | ],
33 | "background": {
34 | "page": "background.html"
35 | },
36 | "permissions": [ "contextMenus", "tabs", "storage", "https://www.chess.com/*" ],
37 | "content_security_policy": "default-src 'self'; script-src 'self'; connect-src https://www.chess.com; style-src * 'unsafe-inline'; img-src 'self' https://images.chesscomfiles.com data:; font-src 'self' data:;"
38 | }
39 |
--------------------------------------------------------------------------------
/chrome/manifest.dev.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "1.0.0",
3 | "name": "Chess Browser Extension",
4 | "manifest_version": 2,
5 | "description": "Customize your Chess.com Experience",
6 | "browser_action": {
7 | "default_title": "Chess.com Browser Extension",
8 | "default_popup": "popup.html"
9 | },
10 | "icons": {
11 | "128": "img/icon-128.png"
12 | },
13 | "web_accessible_resources": [
14 | "img/*",
15 | "fonts/*",
16 | "getCurrentUser.js"
17 | ],
18 | "content_scripts": [
19 | {
20 | "matches": [
21 | "https://www.chess.com/*"
22 | ],
23 | "js": [
24 | "injectScriptWithWindowAccess.js"
25 | ],
26 | "all_frames": true
27 | }
28 | ],
29 | "background": {
30 | "page": "background.html"
31 | },
32 | "permissions": [
33 | "contextMenus",
34 | "management",
35 | "tabs",
36 | "storage",
37 | "https://www.chess.com/*"
38 | ],
39 | "content_security_policy": "default-src 'self'; script-src 'self' https://localhost:3000 'unsafe-eval'; connect-src https://localhost:3000 https://www.chess.com; style-src * 'unsafe-inline' 'self' blob:; img-src 'self' https://images.chesscomfiles.com data:; font-src 'self' data:;"
40 | }
41 |
--------------------------------------------------------------------------------
/chrome/manifest.devFirefox.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "1.0.0",
3 | "name": "Chess Browser Extension",
4 | "manifest_version": 2,
5 | "description": "Customize your Chess.com Experience",
6 | "browser_action": {
7 | "default_title": "Chess.com Browser Extension",
8 | "default_popup": "popup.html",
9 | "default_icon": {
10 | "48": "img/icon-48.png"
11 | }
12 | },
13 | "icons": {
14 | "128": "img/icon-128.png"
15 | },
16 | "applications": {
17 | "gecko": {
18 | "id": "chesscom@gmail.com"
19 | }
20 | },
21 | "web_accessible_resources": [
22 | "img/*",
23 | "fonts/*",
24 | "getCurrentUser.js"
25 | ],
26 | "content_scripts": [
27 | {
28 | "matches": [
29 | "https://www.chess.com/*"
30 | ],
31 | "js": [
32 | "injectScriptWithWindowAccess.js"
33 | ],
34 | "all_frames": true
35 | }
36 | ],
37 | "background": {
38 | "page": "background.html"
39 | },
40 | "permissions": [
41 | "contextMenus",
42 | "management",
43 | "tabs",
44 | "storage",
45 | "https://www.chess.com/*"
46 | ],
47 | "content_security_policy": "default-src 'self'; script-src 'self' https://localhost:3000 'unsafe-eval'; object-src 'self'; connect-src https://localhost:3000 https://www.chess.com; style-src 'self' blob:; img-src 'self' https://images.chesscomfiles.com data:; font-src 'self' data:;"
48 | }
49 |
--------------------------------------------------------------------------------
/chrome/views/background.jade:
--------------------------------------------------------------------------------
1 | doctype html
2 |
3 | html
4 | head
5 | script(src=env == 'prod' ? '/js/background.bundle.js' : 'https://localhost:3000/js/background.bundle.js')
6 |
--------------------------------------------------------------------------------
/chrome/views/popup.jade:
--------------------------------------------------------------------------------
1 | doctype html
2 |
3 | html
4 | head
5 | meta(charset='UTF-8')
6 | title Chess.com Browser Extension
7 |
8 | body
9 | #root
10 | script(src=env == 'prod' ? '/js/popup.bundle.js' : 'https://localhost:3000/js/popup.bundle.js')
11 |
--------------------------------------------------------------------------------
/circle.yml:
--------------------------------------------------------------------------------
1 | machine:
2 | environment:
3 | PATH: "${PATH}:${HOME}/${CIRCLE_PROJECT_REPONAME}/node_modules/.bin"
4 |
5 | dependencies:
6 | override:
7 | - yarn
8 | cache_directories:
9 | - ~/.cache/yarn
10 |
11 | test:
12 | override:
13 | - echo "Overriding tests"
14 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "chess-browser-extension",
3 | "version": "1.0.0",
4 | "description": "Chess.com Browser Extension",
5 | "scripts": {
6 | "dev": "node scripts/dev",
7 | "build": "node scripts/build",
8 | "devFirefox": "node scripts/devFirefox",
9 | "buildFirefox": "node scripts/buildFirefox",
10 | "compress": "node scripts/compress",
11 | "compress-keygen": "crx keygen",
12 | "clean": "rimraf build/ dev/ *.zip *.crx",
13 | "lint": "eslint app chrome test scripts webpack/*.js",
14 | "test-e2e": "cross-env NODE_ENV=test mocha test/e2e",
15 | "test": "cross-env NODE_ENV=test mocha -r ./test/setup-app test/app"
16 | },
17 | "repository": {
18 | "type": "git",
19 | "url": "https://github.com/ChessCom/browser-extension.git"
20 | },
21 | "keywords": [
22 | "chrome",
23 | "browser",
24 | "extension",
25 | "chess"
26 | ],
27 | "author": "Chess.com",
28 | "license": "MIT",
29 | "devDependencies": {
30 | "babel-core": "^6.3.15",
31 | "babel-eslint": "^6.0.0",
32 | "babel-loader": "^6.2.0",
33 | "babel-plugin-add-module-exports": "^0.2.1",
34 | "babel-plugin-transform-decorators-legacy": "^1.2.0",
35 | "babel-plugin-transform-runtime": "^6.5.2",
36 | "babel-plugin-webpack-loaders": "^0.7.0",
37 | "babel-preset-es2015": "^6.3.13",
38 | "babel-preset-react": "^6.3.13",
39 | "babel-preset-react-hmre": "^1.0.0",
40 | "babel-preset-stage-0": "^6.3.13",
41 | "babel-runtime": "^6.3.19",
42 | "chai": "^3.2.0",
43 | "chromedriver": "^2.19.0",
44 | "co-mocha": "^1.1.2",
45 | "cross-env": "^1.0.7",
46 | "crx": "^3.0.3",
47 | "css-loader": "^0.23.1",
48 | "eslint": "^3.4.0",
49 | "eslint-config-airbnb": "^10.0.1",
50 | "eslint-plugin-import": "^1.6.1",
51 | "eslint-plugin-jsx-a11y": "^2.2.0",
52 | "eslint-plugin-react": "^6.2.0",
53 | "extract-text-webpack-plugin": "^1.0.1",
54 | "file-loader": "^0.9.0",
55 | "jade": "^1.11.0",
56 | "jsdom": "^9.2.1",
57 | "minimist": "^1.2.0",
58 | "mocha": "^2.4.5",
59 | "postcss-loader": "^0.9.1",
60 | "react-addons-test-utils": "^15.0.2",
61 | "rimraf": "^2.4.3",
62 | "selenium-webdriver": "^2.47.0",
63 | "shelljs": "^0.7.0",
64 | "sinon": "^1.17.1",
65 | "style-loader": "^0.13.1",
66 | "url-loader": "^0.5.7",
67 | "webpack": "^1.13.0",
68 | "webpack-hot-middleware": "^2.10.0",
69 | "webpack-httpolyglot-server": "^0.2.0"
70 | },
71 | "dependencies": {
72 | "classnames": "^2.2.5",
73 | "lodash": "^4.15.0",
74 | "react": "^15.0.2",
75 | "react-color": "^2.2.0",
76 | "react-dock": "^0.2.3",
77 | "react-dom": "^15.0.2",
78 | "reactcss": "^1.0.6",
79 | "xhr": "^2.2.2"
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/scripts/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "shelljs": true
4 | },
5 | "rules": {
6 | "no-console": 0
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/scripts/build.js:
--------------------------------------------------------------------------------
1 | const tasks = require('./tasks');
2 |
3 | tasks.replaceWebpack();
4 | console.log('[Copy assets]');
5 | console.log('-'.repeat(80));
6 | tasks.copyAssets('build');
7 |
8 | console.log('[Webpack Build]');
9 | console.log('-'.repeat(80));
10 | exec('webpack --config webpack/prod.config.js --progress --profile --colors');
11 |
--------------------------------------------------------------------------------
/scripts/buildFirefox.js:
--------------------------------------------------------------------------------
1 | const tasks = require('./tasks');
2 |
3 | tasks.replaceWebpack();
4 | console.log('[Copy assets]');
5 | console.log('-'.repeat(80));
6 | tasks.copyAssets('buildFirefox');
7 |
8 | console.log('[Webpack Build]');
9 | console.log('-'.repeat(80));
10 | exec('webpack --config webpack/prodFirefox.config.js --progress --profile --colors');
11 |
--------------------------------------------------------------------------------
/scripts/compress.js:
--------------------------------------------------------------------------------
1 | /* eslint import/no-extraneous-dependencies: 0 */
2 | const fs = require('fs');
3 | const ChromeExtension = require('crx');
4 | /* eslint import/no-unresolved: 0 */
5 | const name = require('../build/manifest.json').name;
6 | const argv = require('minimist')(process.argv.slice(2));
7 |
8 | const keyPath = argv.key || 'key.pem';
9 | const existsKey = fs.existsSync(keyPath);
10 | const crx = new ChromeExtension({
11 | appId: argv['app-id'],
12 | codebase: argv.codebase,
13 | privateKey: existsKey ? fs.readFileSync(keyPath) : null
14 | });
15 |
16 | crx.load('build')
17 | .then(() => crx.loadContents())
18 | .then(archiveBuffer => {
19 | fs.writeFile(`${name}.zip`, archiveBuffer);
20 |
21 | if (!argv.codebase || !existsKey) return;
22 | crx.pack(archiveBuffer).then(crxBuffer => {
23 | const updateXML = crx.generateUpdateXML();
24 |
25 | fs.writeFile('update.xml', updateXML);
26 | fs.writeFile(`${name}.crx`, crxBuffer);
27 | });
28 | });
29 |
--------------------------------------------------------------------------------
/scripts/dev.js:
--------------------------------------------------------------------------------
1 | /* eslint import/no-extraneous-dependencies: 0 */
2 | const tasks = require('./tasks');
3 | const createWebpackServer = require('webpack-httpolyglot-server');
4 | const devConfig = require('../webpack/dev.config');
5 |
6 | tasks.replaceWebpack();
7 | console.log('[Copy assets]');
8 | console.log('-'.repeat(80));
9 | tasks.copyAssets('dev');
10 |
11 | console.log('[Webpack Dev]');
12 | console.log('-'.repeat(80));
13 | console.log('In order to allow access to inject.js');
14 | console.log('please allow `https://localhost:3000` connections in Google Chrome,');
15 | console.log('which can be done by enabling #allow-insecure-localhost at chrome://flags');
16 | console.log('and load unpacked extensions with `./dev` folder. (see https://developer.chrome.com/extensions/getstarted#unpacked)\n');
17 | createWebpackServer(devConfig, {
18 | host: 'localhost',
19 | port: 3000,
20 | protocol: 'https'
21 | });
22 |
--------------------------------------------------------------------------------
/scripts/devFirefox.js:
--------------------------------------------------------------------------------
1 | /* eslint import/no-extraneous-dependencies: 0 */
2 | const tasks = require('./tasks');
3 | const createWebpackServer = require('webpack-httpolyglot-server');
4 | const devConfigFirefox = require('../webpack/devFirefox.config');
5 |
6 | tasks.replaceWebpack();
7 | console.log('[Copy assets]');
8 | console.log('-'.repeat(80));
9 | tasks.copyAssets('devFirefox');
10 |
11 | console.log('[Webpack Dev]');
12 | console.log('-'.repeat(80));
13 | console.log('If you\'re developing Inject page,');
14 | console.log('please allow `https://localhost:3000` connections in Firefox,');
15 | console.log('and load unpacked extensions with `./devFirefox` folder. (see https://developer.mozilla.org/en-US/Add-ons/WebExtensions/Your_first_WebExtension)\n');
16 | createWebpackServer(devConfigFirefox, {
17 | host: 'localhost',
18 | port: 3000,
19 | protocol: 'https'
20 | });
21 |
--------------------------------------------------------------------------------
/scripts/tasks.js:
--------------------------------------------------------------------------------
1 | /* eslint import/no-extraneous-dependencies: 0 */
2 | require('shelljs/global');
3 |
4 | exports.replaceWebpack = () => {
5 | const replaceTasks = [{
6 | from: 'webpack/replace/JsonpMainTemplate.runtime.js',
7 | to: 'node_modules/webpack/lib/JsonpMainTemplate.runtime.js'
8 | }, {
9 | from: 'webpack/replace/log-apply-result.js',
10 | to: 'node_modules/webpack/hot/log-apply-result.js'
11 | }];
12 |
13 | replaceTasks.forEach(task => cp(task.from, task.to));
14 | };
15 |
16 | exports.copyAssets = type => {
17 | let env = type;
18 |
19 | if (type.indexOf('build') >= 0) {
20 | env = 'prod';
21 | }
22 |
23 | rm('-rf', type);
24 | mkdir(type);
25 | cp(`chrome/manifest.${type}.json`, `${type}/manifest.json`);
26 | cp('-R', 'chrome/assets/*', type);
27 | cp('-R', 'chrome/*.js', type);
28 | exec(`jade -O "{ env: '${env}' }" -o ${type} chrome/views/`);
29 | };
30 |
--------------------------------------------------------------------------------
/webpack/customPublicPath.js:
--------------------------------------------------------------------------------
1 | /* global __webpack_public_path__ __HOST__ __PORT__ */
2 | /* eslint no-native-reassign: 0, camelcase: 0, no-global-assign: 0 */
3 |
4 | if (process.env.NODE_ENV === 'production') {
5 | __webpack_public_path__ = chrome.extension.getURL('/js');
6 | } else {
7 | // In development mode,
8 | // the iframe of injectpage cannot get correct path,
9 | // it need to get parent page protocol.
10 | const path = `//${__HOST__}:${__PORT__}/js`;
11 | if (location.protocol === 'https:' || location.search.indexOf('protocol=https') !== -1) {
12 | __webpack_public_path__ = `https:${path}`;
13 | } else {
14 | __webpack_public_path__ = `https:${path}`;
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/webpack/dev.config.base.js:
--------------------------------------------------------------------------------
1 | /* eslint import/no-extraneous-dependencies: 0 */
2 | const path = require('path');
3 | const webpack = require('webpack');
4 |
5 | const host = 'localhost';
6 | const port = 3000;
7 | const customPath = path.join(__dirname, './customPublicPath');
8 | const hotScript = 'webpack-hot-middleware/client?path=/__webpack_hmr&dynamicPublicPath=true';
9 |
10 | const createDevConfig = (platform) => {
11 | const config = {};
12 | if (platform === 'firefox') {
13 | config.outputDir = '../devFirefox/js';
14 | } else {
15 | config.outputDir = '../dev/js';
16 | }
17 | const baseDevConfig = () => ({
18 | devtool: 'eval-source-map',
19 | entry: {
20 | popup: [customPath, hotScript, path.join(__dirname, '../chrome/extension/popup')],
21 | background: [customPath, hotScript, path.join(__dirname, '../chrome/extension/background')],
22 | },
23 | devMiddleware: {
24 | publicPath: `https://${host}:${port}/js`,
25 | stats: {
26 | colors: true
27 | },
28 | noInfo: true
29 | },
30 | hotMiddleware: {
31 | path: '/js/__webpack_hmr'
32 | },
33 | output: {
34 | path: path.join(__dirname, config.outputDir),
35 | filename: '[name].bundle.js',
36 | chunkFilename: '[id].chunk.js'
37 | },
38 | plugins: [
39 | new webpack.HotModuleReplacementPlugin(),
40 | new webpack.NoErrorsPlugin(),
41 | new webpack.IgnorePlugin(/[^/]+\/[\S]+.prod$/),
42 | new webpack.DefinePlugin({
43 | __HOST__: `'${host}'`,
44 | __PORT__: port,
45 | 'process.env': {
46 | NODE_ENV: JSON.stringify('development')
47 | }
48 | })
49 | ],
50 | resolve: {
51 | extensions: ['', '.js']
52 | },
53 | module: {
54 | loaders: [{
55 | test: /\.js$/,
56 | loader: 'babel',
57 | exclude: /node_modules/,
58 | query: {
59 | presets: ['react-hmre']
60 | }
61 | }, {
62 | test: /\.css$/,
63 | loaders: [
64 | 'style',
65 | 'css?modules&sourceMap&importLoaders=1&localIdentName=[name]__[local]___[hash:base64:5]',
66 | 'postcss'
67 | ]
68 | }, {
69 | test: /\.(png|woff|woff2|eot|ttf|svg)$/,
70 | loader: 'url-loader?limit=100000'
71 | }]
72 | }
73 | });
74 |
75 | const handleLiveChangesConfig = baseDevConfig();
76 | handleLiveChangesConfig.entry = [
77 | customPath,
78 | path.join(__dirname, '../chrome/extension/handleLiveChanges')
79 | ];
80 | delete handleLiveChangesConfig.hotMiddleware;
81 | delete handleLiveChangesConfig.module.loaders[0].query;
82 | handleLiveChangesConfig.plugins.shift(); // remove HotModuleReplacementPlugin
83 | handleLiveChangesConfig.output = {
84 | path: path.join(__dirname, config.outputDir),
85 | filename: 'handleLiveChanges.bundle.js',
86 | };
87 | const appConfig = baseDevConfig();
88 |
89 | return [
90 | handleLiveChangesConfig,
91 | appConfig
92 | ];
93 | };
94 |
95 | module.exports = createDevConfig;
96 |
97 |
--------------------------------------------------------------------------------
/webpack/dev.config.js:
--------------------------------------------------------------------------------
1 | const createDevConfig = require('./dev.config.base');
2 |
3 | module.exports = createDevConfig('chrome');
4 |
--------------------------------------------------------------------------------
/webpack/devFirefox.config.js:
--------------------------------------------------------------------------------
1 | const createDevConfig = require('./dev.config.base');
2 |
3 | module.exports = createDevConfig('firefox');
4 |
--------------------------------------------------------------------------------
/webpack/prod.config.base.js:
--------------------------------------------------------------------------------
1 | /* eslint import/no-extraneous-dependencies: 0 */
2 | const path = require('path');
3 | const webpack = require('webpack');
4 |
5 | const customPath = path.join(__dirname, './customPublicPath');
6 |
7 | module.exports = (platform) => {
8 | const config = {};
9 | if (platform === 'firefox') {
10 | config.outputDir = '../buildFirefox/js';
11 | } else {
12 | config.outputDir = '../build/js';
13 | }
14 |
15 | return {
16 | entry: {
17 | popup: [customPath, path.join(__dirname, '../chrome/extension/popup')],
18 | background: [customPath, path.join(__dirname, '../chrome/extension/background')],
19 | handleLiveChanges: [customPath, path.join(__dirname, '../chrome/extension/handleLiveChanges')]
20 | },
21 | output: {
22 | path: path.join(__dirname, config.outputDir),
23 | filename: '[name].bundle.js',
24 | chunkFilename: '[id].chunk.js'
25 | },
26 | plugins: [
27 | new webpack.optimize.OccurenceOrderPlugin(),
28 | new webpack.IgnorePlugin(/[^/]+\/[\S]+.dev$/),
29 | new webpack.optimize.DedupePlugin(),
30 | new webpack.optimize.UglifyJsPlugin({
31 | comments: false,
32 | compressor: {
33 | warnings: false
34 | }
35 | }),
36 | new webpack.DefinePlugin({
37 | 'process.env': {
38 | NODE_ENV: JSON.stringify('production')
39 | }
40 | })
41 | ],
42 | resolve: {
43 | extensions: ['', '.js']
44 | },
45 | module: {
46 | loaders: [{
47 | test: /\.js$/,
48 | loader: 'babel',
49 | exclude: /node_modules/
50 | }, {
51 | test: /\.css$/,
52 | loaders: [
53 | 'style',
54 | 'css?modules&importLoaders=1&localIdentName=[name]__[local]___[hash:base64:5]',
55 | 'postcss'
56 | ]
57 | }, {
58 | test: /\.(png|woff|woff2|eot|ttf|svg)$/,
59 | loader: 'url-loader?limit=100000'
60 | }]
61 | }
62 | };
63 | };
64 |
65 |
--------------------------------------------------------------------------------
/webpack/prod.config.js:
--------------------------------------------------------------------------------
1 | const createDevConfig = require('./prod.config.base');
2 |
3 | module.exports = createDevConfig('chrome');
4 |
--------------------------------------------------------------------------------
/webpack/prodFirefox.config.js:
--------------------------------------------------------------------------------
1 | const createDevConfig = require('./prod.config.base');
2 |
3 | module.exports = createDevConfig('firefox');
4 |
--------------------------------------------------------------------------------
/webpack/replace/JsonpMainTemplate.runtime.js:
--------------------------------------------------------------------------------
1 | /*
2 | MIT License http://www.opensource.org/licenses/mit-license.php
3 | Author Tobias Koppers @sokra
4 | */
5 | /*globals hotAddUpdateChunk parentHotUpdateCallback document XMLHttpRequest $require$ $hotChunkFilename$ $hotMainFilename$ */
6 | module.exports = function() {
7 | function webpackHotUpdateCallback(chunkId, moreModules) { // eslint-disable-line no-unused-vars
8 | hotAddUpdateChunk(chunkId, moreModules);
9 | if(parentHotUpdateCallback) parentHotUpdateCallback(chunkId, moreModules);
10 | }
11 |
12 | var context = this;
13 | function evalCode(code, context) {
14 | return (function() { return eval(code); }).call(context);
15 | }
16 |
17 | context.hotDownloadUpdateChunk = function (chunkId) { // eslint-disable-line no-unused-vars
18 | var src = __webpack_require__.p + "" + chunkId + "." + hotCurrentHash + ".hot-update.js";
19 | var request = new XMLHttpRequest();
20 |
21 | request.onload = function() {
22 | evalCode(this.responseText, context);
23 | };
24 | request.open("get", src, true);
25 | request.send();
26 | }
27 |
28 | function hotDownloadManifest(callback) { // eslint-disable-line no-unused-vars
29 | if(typeof XMLHttpRequest === "undefined")
30 | return callback(new Error("No browser support"));
31 | try {
32 | var request = new XMLHttpRequest();
33 | var requestPath = $require$.p + $hotMainFilename$;
34 | request.open("GET", requestPath, true);
35 | request.timeout = 10000;
36 | request.send(null);
37 | } catch(err) {
38 | return callback(err);
39 | }
40 | request.onreadystatechange = function() {
41 | if(request.readyState !== 4) return;
42 | if(request.status === 0) {
43 | // timeout
44 | callback(new Error("Manifest request to " + requestPath + " timed out."));
45 | } else if(request.status === 404) {
46 | // no update available
47 | callback();
48 | } else if(request.status !== 200 && request.status !== 304) {
49 | // other failure
50 | callback(new Error("Manifest request to " + requestPath + " failed."));
51 | } else {
52 | // success
53 | try {
54 | var update = JSON.parse(request.responseText);
55 | } catch(e) {
56 | callback(e);
57 | return;
58 | }
59 | callback(null, update);
60 | }
61 | };
62 | }
63 | };
64 |
--------------------------------------------------------------------------------
/webpack/replace/log-apply-result.js:
--------------------------------------------------------------------------------
1 | /*
2 | MIT License http://www.opensource.org/licenses/mit-license.php
3 | Author Tobias Koppers @sokra
4 | */
5 | module.exports = function(updatedModules, renewedModules) {
6 | var unacceptedModules = updatedModules.filter(function(moduleId) {
7 | return renewedModules && renewedModules.indexOf(moduleId) < 0;
8 | });
9 |
10 | if(unacceptedModules.length > 0) {
11 | console.warn("[HMR] The following modules couldn't be hot updated: (They would need a full reload!)");
12 | unacceptedModules.forEach(function(moduleId) {
13 | console.warn("[HMR] - " + moduleId);
14 | });
15 |
16 | if(chrome && chrome.runtime && chrome.runtime.reload) {
17 | console.warn("[HMR] extension reload");
18 | chrome.runtime.reload();
19 | } else {
20 | console.warn("[HMR] Can't extension reload. not found chrome.runtime.reload.");
21 | }
22 | }
23 |
24 | if(!renewedModules || renewedModules.length === 0) {
25 | console.log("[HMR] Nothing hot updated.");
26 | } else {
27 | console.log("[HMR] Updated modules:");
28 | renewedModules.forEach(function(moduleId) {
29 | console.log("[HMR] - " + moduleId);
30 | });
31 | }
32 | };
33 |
--------------------------------------------------------------------------------
/webpack/test.config.js:
--------------------------------------------------------------------------------
1 | // for babel-plugin-webpack-loaders
2 | const config = require('./prod.config');
3 |
4 | module.exports = {
5 | output: {
6 | libraryTarget: 'commonjs2'
7 | },
8 | module: {
9 | loaders: config.module.loaders.slice(1) // remove babel-loader
10 | }
11 | };
12 |
--------------------------------------------------------------------------------