Initial work to get working in Firefox

This commit is contained in:
Nathaniel van Diepen
2016-12-14 13:55:43 -07:00
parent 628d6eb2f1
commit 9823acc03f
81 changed files with 5501 additions and 5496 deletions

View File

@@ -1,3 +1,3 @@
{
"directory": "vendor/bower_components"
{
"directory": "vendor/bower_components"
}

68
.gitignore vendored
View File

@@ -1,34 +1,34 @@
# Logs
logs
*.log
# Runtime data
pids
*.pid
*.seed
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Compiled binary addons (http://nodejs.org/api/addons.html)
build/Release
# Dependency directory
# Deployed apps should consider commenting this line out:
# see https://npmjs.org/doc/faq.html#Should-I-check-my-node_modules-folder-into-git
node_modules
.DS_Store
vendor/
.idea
# Generated chrome extension
public/
# Logs
logs
*.log
# Runtime data
pids
*.pid
*.seed
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Compiled binary addons (http://nodejs.org/api/addons.html)
build/Release
# Dependency directory
# Deployed apps should consider commenting this line out:
# see https://npmjs.org/doc/faq.html#Should-I-check-my-node_modules-folder-into-git
node_modules
.DS_Store
vendor/
.idea
# Generated chrome extension
public/

View File

@@ -1,5 +1,5 @@
coverage/
node_modules/
public/
vendor/
coverage/
node_modules/
public/
vendor/
tests/

View File

@@ -1,10 +1,10 @@
{
"node": true,
"curly": true,
"latedef": true,
"quotmark": true,
"undef": true,
"unused": true,
"trailing": true,
"predef": [ "chrome" ]
}
{
"node": true,
"curly": true,
"latedef": true,
"quotmark": true,
"undef": true,
"unused": true,
"trailing": true,
"predef": [ "chrome" ]
}

View File

@@ -1,6 +1,6 @@
{
"scripts": {
"lint": "jshint ."
},
"pre-commit": ["lint", "validate", "test"]
}
{
"scripts": {
"lint": "jshint ."
},
"pre-commit": ["lint", "validate", "test"]
}

28
AUTHORS
View File

@@ -1,14 +1,14 @@
WakaTime is written and maintained by Alan Hamlett and
various contributors:
Development Lead
----------------
- Alan Hamlett <alan.hamlett@gmail.com>
- Mario Bašić <mario.basic@outlook.com>
Patches and Suggestions
-----------------------
WakaTime is written and maintained by Alan Hamlett and
various contributors:
Development Lead
----------------
- Alan Hamlett <alan.hamlett@gmail.com>
- Mario Bašić <mario.basic@outlook.com>
Patches and Suggestions
-----------------------

View File

@@ -1,24 +1,24 @@
History
-------
1.0.2 (2016-06-30)
++++++++++++++++++
- Fix bug preventing options from saving. #42
1.0.1 (2016-06-29)
++++++++++++++++++
- Fix blacklist setting.
- Misc bug fixes related to icon popup.
- Send plugin name and version with heartbeat data. #41
1.0.0 (2016-06-29)
++++++++++++++++++
- Birth
History
-------
1.0.2 (2016-06-30)
++++++++++++++++++
- Fix bug preventing options from saving. #42
1.0.1 (2016-06-29)
++++++++++++++++++
- Fix blacklist setting.
- Misc bug fixes related to icon popup.
- Send plugin name and version with heartbeat data. #41
1.0.0 (2016-06-29)
++++++++++++++++++
- Birth

62
LICENSE
View File

@@ -1,31 +1,31 @@
BSD 3-Clause License
Copyright (c) 2014 by the respective authors (see AUTHORS file).
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided
with the distribution.
* Neither the names of WakaTime, nor the names of its
contributors may be used to endorse or promote products derived
from this software without specific prior written permission.
THIS SOFTWARE AND DOCUMENTATION IS PROVIDED BY THE COPYRIGHT HOLDERS AND
CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT
NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER
OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
BSD 3-Clause License
Copyright (c) 2014 by the respective authors (see AUTHORS file).
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided
with the distribution.
* Neither the names of WakaTime, nor the names of its
contributors may be used to endorse or promote products derived
from this software without specific prior written permission.
THIS SOFTWARE AND DOCUMENTATION IS PROVIDED BY THE COPYRIGHT HOLDERS AND
CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT
NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER
OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

150
README.md
View File

@@ -1,75 +1,75 @@
chrome-wakatime
===============
Automatic time tracking for stats about your website debugging, research, documentation, etc.
##Installation
1. Install the extension:
[![Chrome Web Store](https://wakatime.com/static/img/chrome-web-store.png)](https://chrome.google.com/webstore/detail/wakatime/jnbbnacmeggbgdjgaoojpmhdlkkpblgi)
2. Login to [WakaTime](https://wakatime.com/).
3. Use Chrome like you normally do and your time will be tracked for you automatically.
4. Visit https://wakatime.com to see your logged time.
5. Use in conjunction with [other WakaTime plugins](https://wakatime.com/plugins).
## Screenshots
![SC open](./screenshots/sc_6-green.png)
![SC open](./screenshots/sc_6-open.png)
![Options SC](./screenshots/sc_8-options.png)
## Development instructions
> For development purposes only.
To get started, install NPM and Bower dependencies, and do an initial build with Gulp:
```
npm start
```
To build the extension once:
```
npm run gulp
```
To monitor changes:
```
npm run watch
```
Run tests:
```
npm test
```
Lint code *(Both JS and JSX)*:
```
jsxhint --jsx-only .
```
### Automatic code linting
There is a precommit hook that lints the code before commiting the changes.
### Load unpacked in Chrome
1. Clone repository to disk
2. Go to `Settings` -> `Extensions`
3. Enable `Developer mode`
4. Click `Load unpacked extension...`
5. Select repository directory
chrome-wakatime
===============
Automatic time tracking for stats about your website debugging, research, documentation, etc.
##Installation
1. Install the extension:
[![Chrome Web Store](https://wakatime.com/static/img/chrome-web-store.png)](https://chrome.google.com/webstore/detail/wakatime/jnbbnacmeggbgdjgaoojpmhdlkkpblgi)
2. Login to [WakaTime](https://wakatime.com/).
3. Use Chrome like you normally do and your time will be tracked for you automatically.
4. Visit https://wakatime.com to see your logged time.
5. Use in conjunction with [other WakaTime plugins](https://wakatime.com/plugins).
## Screenshots
![SC open](./screenshots/sc_6-green.png)
![SC open](./screenshots/sc_6-open.png)
![Options SC](./screenshots/sc_8-options.png)
## Development instructions
> For development purposes only.
To get started, install NPM and Bower dependencies, and do an initial build with Gulp:
```
npm start
```
To build the extension once:
```
npm run gulp
```
To monitor changes:
```
npm run watch
```
Run tests:
```
npm test
```
Lint code *(Both JS and JSX)*:
```
jsxhint --jsx-only .
```
### Automatic code linting
There is a precommit hook that lints the code before commiting the changes.
### Load unpacked in Chrome
1. Clone repository to disk
2. Go to `Settings` -> `Extensions`
3. Enable `Developer mode`
4. Click `Load unpacked extension...`
5. Select repository directory

View File

@@ -1,15 +1,15 @@
/* This is a fix for Bootstrap requiring jQuery */
global.jQuery = require('jquery');
require('bootstrap');
var React = require('react');
var ReactDOM = require('react-dom');
// React components
var WakaTime = require('./components/WakaTime.jsx');
ReactDOM.render(
<WakaTime />,
document.getElementById('wakatime')
);
/* This is a fix for Bootstrap requiring jQuery */
global.jQuery = require('jquery');
require('bootstrap');
var React = require('react');
var ReactDOM = require('react-dom');
// React components
var WakaTime = require('./components/WakaTime.jsx');
ReactDOM.render(
<WakaTime />,
document.getElementById('wakatime')
);

View File

@@ -1,19 +1,19 @@
var React = require('react');
var classNames = require('classnames');
var Alert = React.createClass({
propTypes: {
type: React.PropTypes.string.isRequired,
text: React.PropTypes.string.isRequired
},
render: function() {
return(
<div className={classNames('alert', 'alert-' + this.props.type)}>{this.props.text}</div>
);
}
});
module.exports = Alert;
var React = require('react');
var classNames = require('classnames');
var Alert = React.createClass({
propTypes: {
type: React.PropTypes.string.isRequired,
text: React.PropTypes.string.isRequired
},
render: function() {
return(
<div className={classNames('alert', 'alert-' + this.props.type)}>{this.props.text}</div>
);
}
});
module.exports = Alert;

View File

@@ -1,106 +1,106 @@
/* global chrome */
var React = require('react');
var MainList = React.createClass({
_openOptionsPage: function() {
if (chrome.runtime.openOptionsPage) {
// New way to open options pages, if supported (Chrome 42+).
chrome.runtime.openOptionsPage();
} else {
// Reasonable fallback.
window.open(chrome.runtime.getURL('options.html'));
}
},
render: function() {
var that = this;
var loginLogoutButton = function() {
if (that.props.loggedIn === true) {
return (
<div>
<a href="#" className="list-group-item" onClick={that.props.logoutUser}>
<i className="fa fa-fw fa-sign-out"></i>
Logout
</a>
</div>
);
}
return (
<a target="_blank" href="https://wakatime.com/login" className="list-group-item">
<i className="fa fa-fw fa-sign-in"></i>
Login
</a>
);
};
// If logging is enabled, display that info to user
var loggingStatus = function() {
if(that.props.loggingEnabled === true && that.props.loggedIn === true)
{
return (
<div className="row">
<div className="col-xs-12">
<p>
<a href="#" onClick={that.props.disableLogging} className="btn btn-danger btn-block">Disable logging</a>
</p>
</div>
</div>
);
}
else if(that.props.loggingEnabled === false && that.props.loggedIn === true)
{
return (
<div className="row">
<div className="col-xs-12">
<p>
<a href="#" onClick={that.props.enableLogging} className="btn btn-success btn-block">Enable logging</a>
</p>
</div>
</div>
);
}
};
var totalTimeLoggedToday = function() {
if (that.props.loggedIn === true) {
return (
<div className="row">
<div className="col-xs-12">
<blockquote>
<p>{that.props.totalTimeLoggedToday}</p>
<small><cite>TOTAL TIME LOGGED TODAY</cite></small>
</blockquote>
</div>
</div>
);
}
};
return (
<div>
{totalTimeLoggedToday()}
{loggingStatus()}
<div className="list-group">
<a href="#" className="list-group-item" onClick={this._openOptionsPage}>
<i className="fa fa-fw fa-cogs"></i>
Options
</a>
{loginLogoutButton()}
</div>
</div>
);
}
});
module.exports = MainList;
/* global browser */
var React = require('react');
var MainList = React.createClass({
_openOptionsPage: function() {
if (browser.runtime.openOptionsPage) {
// New way to open options pages, if supported (Chrome 42+).
browser.runtime.openOptionsPage();
} else {
// Reasonable fallback.
window.open(browser.runtime.getURL('options.html'));
}
},
render: function() {
var that = this;
var loginLogoutButton = function() {
if (that.props.loggedIn === true) {
return (
<div>
<a href="#" className="list-group-item" onClick={that.props.logoutUser}>
<i className="fa fa-fw fa-sign-out"></i>
Logout
</a>
</div>
);
}
return (
<a target="_blank" href="https://wakatime.com/login" className="list-group-item">
<i className="fa fa-fw fa-sign-in"></i>
Login
</a>
);
};
// If logging is enabled, display that info to user
var loggingStatus = function() {
if(that.props.loggingEnabled === true && that.props.loggedIn === true)
{
return (
<div className="row">
<div className="col-xs-12">
<p>
<a href="#" onClick={that.props.disableLogging} className="btn btn-danger btn-block">Disable logging</a>
</p>
</div>
</div>
);
}
else if(that.props.loggingEnabled === false && that.props.loggedIn === true)
{
return (
<div className="row">
<div className="col-xs-12">
<p>
<a href="#" onClick={that.props.enableLogging} className="btn btn-success btn-block">Enable logging</a>
</p>
</div>
</div>
);
}
};
var totalTimeLoggedToday = function() {
if (that.props.loggedIn === true) {
return (
<div className="row">
<div className="col-xs-12">
<blockquote>
<p>{that.props.totalTimeLoggedToday}</p>
<small><cite>TOTAL TIME LOGGED TODAY</cite></small>
</blockquote>
</div>
</div>
);
}
};
return (
<div>
{totalTimeLoggedToday()}
{loggingStatus()}
<div className="list-group">
<a href="#" className="list-group-item" onClick={this._openOptionsPage}>
<i className="fa fa-fw fa-cogs"></i>
Options
</a>
{loginLogoutButton()}
</div>
</div>
);
}
});
module.exports = MainList;

View File

@@ -1,89 +1,89 @@
var React = require('react');
var NavBar = React.createClass({
render: function() {
var that = this;
var signedInAs = function() {
if (that.props.loggedIn === true) {
return (
<p className="navbar-text">Signed in as <b>{that.props.user.full_name}</b></p>
);
}
};
var dashboard = function() {
if (that.props.loggedIn === true) {
return (
<li>
<a target="_blank" href="https://wakatime.com/dashboard">
<i className="fa fa-fw fa-tachometer"></i>
Dashboard
</a>
</li>
);
}
};
var customRules = function() {
if (that.props.loggedIn === true) {
return (
<li>
<a target="_blank" href="https://wakatime.com/settings/rules">
<i className="fa fa-fw fa-filter"></i>
Custom Rules
</a>
</li>
);
}
};
return (
<nav className="navbar navbar-default" role="navigation">
<div className="container-fluid">
<div className="navbar-header">
<button type="button" className="navbar-toggle collapsed" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1">
<span className="sr-only">Toggle navigation</span>
<i className="fa fa-fw fa-cogs"></i>
</button>
<a target="_blank" className="navbar-brand" href="https://wakatime.com">
WakaTime
<img src="graphics/wakatime-logo-48.png" />
</a>
</div>
<div className="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
{signedInAs()}
<ul className="nav navbar-nav">
{customRules()}
{dashboard()}
<li className="dropdown">
<a href="#" className="dropdown-toggle" data-toggle="dropdown" role="button" aria-expanded="false">
<i className="fa fa-fw fa-info"></i>
About
<span className="caret"></span>
</a>
<ul className="dropdown-menu" role="menu">
<li>
<a target="_blank" href="https://github.com/wakatime/chrome-wakatime/issues">
<i className="fa fa-fw fa-bug"></i>
Report an Issue</a>
</li>
<li>
<a target="_blank" href="https://github.com/wakatime/chrome-wakatime">
<i className="fa fa-fw fa-github"></i>
View on GitHub</a>
</li>
</ul>
</li>
</ul>
</div>
</div>
</nav>
);
}
});
module.exports = NavBar;
var React = require('react');
var NavBar = React.createClass({
render: function() {
var that = this;
var signedInAs = function() {
if (that.props.loggedIn === true) {
return (
<p className="navbar-text">Signed in as <b>{that.props.user.full_name}</b></p>
);
}
};
var dashboard = function() {
if (that.props.loggedIn === true) {
return (
<li>
<a target="_blank" href="https://wakatime.com/dashboard">
<i className="fa fa-fw fa-tachometer"></i>
Dashboard
</a>
</li>
);
}
};
var customRules = function() {
if (that.props.loggedIn === true) {
return (
<li>
<a target="_blank" href="https://wakatime.com/settings/rules">
<i className="fa fa-fw fa-filter"></i>
Custom Rules
</a>
</li>
);
}
};
return (
<nav className="navbar navbar-default" role="navigation">
<div className="container-fluid">
<div className="navbar-header">
<button type="button" className="navbar-toggle collapsed" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1">
<span className="sr-only">Toggle navigation</span>
<i className="fa fa-fw fa-cogs"></i>
</button>
<a target="_blank" className="navbar-brand" href="https://wakatime.com">
WakaTime
<img src="graphics/wakatime-logo-48.png" />
</a>
</div>
<div className="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
{signedInAs()}
<ul className="nav navbar-nav">
{customRules()}
{dashboard()}
<li className="dropdown">
<a href="#" className="dropdown-toggle" data-toggle="dropdown" role="button" aria-expanded="false">
<i className="fa fa-fw fa-info"></i>
About
<span className="caret"></span>
</a>
<ul className="dropdown-menu" role="menu">
<li>
<a target="_blank" href="https://github.com/wakatime/chrome-wakatime/issues">
<i className="fa fa-fw fa-bug"></i>
Report an Issue</a>
</li>
<li>
<a target="_blank" href="https://github.com/wakatime/chrome-wakatime">
<i className="fa fa-fw fa-github"></i>
View on GitHub</a>
</li>
</ul>
</li>
</ul>
</div>
</div>
</nav>
);
}
});
module.exports = NavBar;

View File

@@ -1,214 +1,214 @@
/* global chrome */
var React = require('react');
var ReactCSSTransitionGroup = require('react-addons-css-transition-group');
var config = require('../config');
// React components
var Alert = require('./Alert.jsx');
var SitesList = require('./SitesList.jsx');
/**
* One thing to keep in mind is that you cannot use this.refs.blacklist if
* the blacklist select box is not being rendered on the form.
*
* @type {*|Function}
*/
var Options = React.createClass({
getInitialState: function () {
return {
theme: config.theme,
blacklist: '',
whitelist: '',
loggingType: config.loggingType,
loggingStyle: config.loggingStyle,
displayAlert: false,
alertType: config.alert.success.type,
alertText: config.alert.success.text
};
},
componentDidMount: function () {
this.restoreSettings();
},
restoreSettings: function () {
var that = this;
chrome.storage.sync.get({
theme: config.theme,
blacklist: '',
whitelist: '',
loggingType: config.loggingType,
loggingStyle: config.loggingStyle
}, function (items) {
that.setState({
theme: items.theme,
blacklist: items.blacklist,
whitelist: items.whitelist,
loggingType: items.loggingType,
loggingStyle: items.loggingStyle
});
that.refs.theme.value = items.theme;
that.refs.loggingType.value = items.loggingType;
that.refs.loggingStyle.value = items.loggingStyle;
});
},
_handleSubmit: function (e) {
e.preventDefault();
this.saveSettings();
},
saveSettings: function () {
var that = this;
var theme = this.refs.theme.value.trim();
var loggingType = this.refs.loggingType.value.trim();
var loggingStyle = this.refs.loggingStyle.value.trim();
// Trimming blacklist and whitelist removes blank lines and spaces.
var blacklist = that.state.blacklist.trim();
var whitelist = that.state.whitelist.trim();
// Sync options with google storage.
chrome.storage.sync.set({
theme: theme,
blacklist: blacklist,
whitelist: whitelist,
loggingType: loggingType,
loggingStyle: loggingStyle
}, function () {
// Set state to be newly entered values.
that.setState({
theme: theme,
blacklist: blacklist,
whitelist: whitelist,
loggingType: loggingType,
loggingStyle: loggingStyle,
displayAlert: true
});
});
},
_displayBlackOrWhiteList: function () {
var loggingStyle = this.refs.loggingStyle.value.trim();
this.setState({loggingStyle: loggingStyle});
},
_updateBlacklistState: function(sites){
this.setState({
blacklist: sites
});
},
_updateWhitelistState: function(sites){
this.setState({
whitelist: sites
});
},
render: function () {
var that = this;
var alert = function() {
if(that.state.displayAlert === true){
setTimeout(function () {
that.setState({displayAlert:false});
}, 2000);
return(
<Alert key={that.state.alertText} type={that.state.alertType} text={that.state.alertText} />
);
}
};
var loggingStyle = function () {
if (that.state.loggingStyle == 'blacklist') {
return (
<SitesList
handleChange={that._updateBlacklistState}
label="Blacklist"
sites={that.state.blacklist}
helpText="Sites that you don't want to show in your reports." />
);
}
return (
<SitesList
handleChange={that._updateWhitelistState}
label="Whitelist"
sites={that.state.whitelist}
helpText="Sites that you want to show in your reports." />
);
};
return (
<div className="container">
<div className="row">
<div className="col-md-12">
<ReactCSSTransitionGroup transitionName="alert" transitionEnterTimeout={500} transitionLeaveTimeout={300}>
{alert()}
</ReactCSSTransitionGroup>
<form className="form-horizontal" onSubmit={this._handleSubmit}>
<div className="form-group">
<label className="col-lg-2 control-label">Logging style</label>
<div className="col-lg-10">
<select className="form-control" ref="loggingStyle" defaultValue="blacklist" onChange={this._displayBlackOrWhiteList}>
<option value="blacklist">All except blacklisted sites</option>
<option value="whitelist">Only whitelisted sites</option>
</select>
</div>
</div>
{loggingStyle()}
<div className="form-group">
<label className="col-lg-2 control-label">Logging type</label>
<div className="col-lg-10">
<select className="form-control" ref="loggingType" defaultValue="domain">
<option value="domain">Only the domain</option>
<option value="url">Entire URL</option>
</select>
</div>
</div>
<div className="form-group">
<label htmlFor="theme" className="col-lg-2 control-label">Theme</label>
<div className="col-lg-10">
<select className="form-control" ref="theme" defaultValue="light">
<option value="light">Light</option>
<option value="dark">Dark</option>
</select>
</div>
</div>
<div className="form-group">
<div className="col-lg-10 col-lg-offset-2">
<button type="submit" className="btn btn-primary">Save</button>
</div>
</div>
</form>
</div>
</div>
</div>
);
}
});
module.exports = Options;
/* global browser */
var React = require('react');
var ReactCSSTransitionGroup = require('react-addons-css-transition-group');
var config = require('../config');
// React components
var Alert = require('./Alert.jsx');
var SitesList = require('./SitesList.jsx');
/**
* One thing to keep in mind is that you cannot use this.refs.blacklist if
* the blacklist select box is not being rendered on the form.
*
* @type {*|Function}
*/
var Options = React.createClass({
getInitialState: function () {
return {
theme: config.theme,
blacklist: '',
whitelist: '',
loggingType: config.loggingType,
loggingStyle: config.loggingStyle,
displayAlert: false,
alertType: config.alert.success.type,
alertText: config.alert.success.text
};
},
componentDidMount: function () {
this.restoreSettings();
},
restoreSettings: function () {
var that = this;
browser.storage.sync.get({
theme: config.theme,
blacklist: '',
whitelist: '',
loggingType: config.loggingType,
loggingStyle: config.loggingStyle
}).then(function (items) {
that.setState({
theme: items.theme,
blacklist: items.blacklist,
whitelist: items.whitelist,
loggingType: items.loggingType,
loggingStyle: items.loggingStyle
});
that.refs.theme.value = items.theme;
that.refs.loggingType.value = items.loggingType;
that.refs.loggingStyle.value = items.loggingStyle;
});
},
_handleSubmit: function (e) {
e.preventDefault();
this.saveSettings();
},
saveSettings: function () {
var that = this;
var theme = this.refs.theme.value.trim();
var loggingType = this.refs.loggingType.value.trim();
var loggingStyle = this.refs.loggingStyle.value.trim();
// Trimming blacklist and whitelist removes blank lines and spaces.
var blacklist = that.state.blacklist.trim();
var whitelist = that.state.whitelist.trim();
// Sync options with google storage.
browser.storage.sync.set({
theme: theme,
blacklist: blacklist,
whitelist: whitelist,
loggingType: loggingType,
loggingStyle: loggingStyle
}).then(function () {
// Set state to be newly entered values.
that.setState({
theme: theme,
blacklist: blacklist,
whitelist: whitelist,
loggingType: loggingType,
loggingStyle: loggingStyle,
displayAlert: true
});
});
},
_displayBlackOrWhiteList: function () {
var loggingStyle = this.refs.loggingStyle.value.trim();
this.setState({loggingStyle: loggingStyle});
},
_updateBlacklistState: function(sites){
this.setState({
blacklist: sites
});
},
_updateWhitelistState: function(sites){
this.setState({
whitelist: sites
});
},
render: function () {
var that = this;
var alert = function() {
if(that.state.displayAlert === true){
setTimeout(function () {
that.setState({displayAlert:false});
}, 2000);
return(
<Alert key={that.state.alertText} type={that.state.alertType} text={that.state.alertText} />
);
}
};
var loggingStyle = function () {
if (that.state.loggingStyle == 'blacklist') {
return (
<SitesList
handleChange={that._updateBlacklistState}
label="Blacklist"
sites={that.state.blacklist}
helpText="Sites that you don't want to show in your reports." />
);
}
return (
<SitesList
handleChange={that._updateWhitelistState}
label="Whitelist"
sites={that.state.whitelist}
helpText="Sites that you want to show in your reports." />
);
};
return (
<div className="container">
<div className="row">
<div className="col-md-12">
<ReactCSSTransitionGroup transitionName="alert" transitionEnterTimeout={500} transitionLeaveTimeout={300}>
{alert()}
</ReactCSSTransitionGroup>
<form className="form-horizontal" onSubmit={this._handleSubmit}>
<div className="form-group">
<label className="col-lg-2 control-label">Logging style</label>
<div className="col-lg-10">
<select className="form-control" ref="loggingStyle" defaultValue="blacklist" onChange={this._displayBlackOrWhiteList}>
<option value="blacklist">All except blacklisted sites</option>
<option value="whitelist">Only whitelisted sites</option>
</select>
</div>
</div>
{loggingStyle()}
<div className="form-group">
<label className="col-lg-2 control-label">Logging type</label>
<div className="col-lg-10">
<select className="form-control" ref="loggingType" defaultValue="domain">
<option value="domain">Only the domain</option>
<option value="url">Entire URL</option>
</select>
</div>
</div>
<div className="form-group">
<label htmlFor="theme" className="col-lg-2 control-label">Theme</label>
<div className="col-lg-10">
<select className="form-control" ref="theme" defaultValue="light">
<option value="light">Light</option>
<option value="dark">Dark</option>
</select>
</div>
</div>
<div className="form-group">
<div className="col-lg-10 col-lg-offset-2">
<button type="submit" className="btn btn-primary">Save</button>
</div>
</div>
</form>
</div>
</div>
</div>
);
}
});
module.exports = Options;

View File

@@ -1,35 +1,35 @@
var React = require('react');
var SitesList = React.createClass({
getDefaultProps: function () {
return {
placeholder: 'http://google.com'
};
},
_handleChange: function (event) {
var sites = event.target.value;
this.props.handleChange(sites);
},
render: function () {
return (
<div className="form-group">
<label htmlFor="sites" className="col-lg-2 control-label">{this.props.label}</label>
<div className="col-lg-10">
<textarea className="form-control" rows="3" ref="sites" onChange={this._handleChange}
placeholder={this.props.placeholder} value={this.props.sites}></textarea>
<span className="help-block">{this.props.helpText}
<br/>
One line per site.</span>
</div>
</div>
);
}
});
module.exports = SitesList;
var React = require('react');
var SitesList = React.createClass({
getDefaultProps: function () {
return {
placeholder: 'http://google.com'
};
},
_handleChange: function (event) {
var sites = event.target.value;
this.props.handleChange(sites);
},
render: function () {
return (
<div className="form-group">
<label htmlFor="sites" className="col-lg-2 control-label">{this.props.label}</label>
<div className="col-lg-10">
<textarea className="form-control" rows="3" ref="sites" onChange={this._handleChange}
placeholder={this.props.placeholder} value={this.props.sites}></textarea>
<span className="help-block">{this.props.helpText}
<br/>
One line per site.</span>
</div>
</div>
);
}
});
module.exports = SitesList;

View File

@@ -1,173 +1,173 @@
/* global chrome */
var React = require("react");
var $ = require('jquery');
var config = require('../config');
// React components
var NavBar = require('./NavBar.jsx');
var MainList = require('./MainList.jsx');
// Core
var WakaTimeCore = require('../core/WakaTimeCore').default;
// Helpers
var changeExtensionState = require('../helpers/changeExtensionState');
var Wakatime = React.createClass({
getInitialState: function() {
return {
user: {
full_name: null,
email: null,
photo: null
},
loggedIn: false,
loggingEnabled: config.loggingEnabled,
totalTimeLoggedToday: '0 minutes'
};
},
componentDidMount: function() {
var wakatime = new WakaTimeCore();
var that = this;
wakatime.checkAuth().done(function(data) {
if (data !== false) {
chrome.storage.sync.get({
loggingEnabled: config.loggingEnabled
}, function(items) {
that.setState({loggingEnabled: items.loggingEnabled});
if (items.loggingEnabled === true) {
changeExtensionState('allGood');
}
else {
changeExtensionState('notLogging');
}
});
that.setState({
user: {
full_name: data.full_name,
email: data.email,
photo: data.photo
},
loggedIn: true
});
wakatime.getTotalTimeLoggedToday().done(function(grand_total) {
that.setState({
totalTimeLoggedToday: grand_total.text
});
});
}
else {
changeExtensionState('notSignedIn');
}
});
},
logoutUser: function() {
var deferredObject = $.Deferred();
var that = this;
$.ajax({
url: config.logoutUserUrl,
method: 'GET',
success: function() {
deferredObject.resolve(that);
},
error: function(xhr, status, err) {
console.error(config.logoutUserUrl, status, err.toString());
deferredObject.resolve(that);
}
});
return deferredObject.promise();
},
_logoutUser: function() {
var that = this;
this.logoutUser().done(function(){
that.setState({
user: {
full_name: null,
email: null,
photo: null
},
loggedIn: false,
loggingEnabled: false
});
changeExtensionState('notSignedIn');
});
},
_disableLogging: function() {
this.setState({
loggingEnabled: false
});
changeExtensionState('notLogging');
chrome.storage.sync.set({
loggingEnabled: false
});
},
_enableLogging: function() {
this.setState({
loggingEnabled: true
});
changeExtensionState('allGood');
chrome.storage.sync.set({
loggingEnabled: true
});
},
render: function() {
return (
<div>
<NavBar
user={this.state.user}
loggedIn={this.state.loggedIn} />
<div className="container">
<div className="row">
<div className="col-md-12">
<MainList
disableLogging={this._disableLogging}
enableLogging={this._enableLogging}
loggingEnabled={this.state.loggingEnabled}
user={this.state.user}
totalTimeLoggedToday={this.state.totalTimeLoggedToday}
logoutUser={this._logoutUser}
loggedIn={this.state.loggedIn} />
</div>
</div>
</div>
</div>
);
}
});
module.exports = Wakatime;
/* global browser */
var React = require("react");
var $ = require('jquery');
var config = require('../config');
// React components
var NavBar = require('./NavBar.jsx');
var MainList = require('./MainList.jsx');
// Core
var WakaTimeCore = require('../core/WakaTimeCore').default;
// Helpers
var changeExtensionState = require('../helpers/changeExtensionState');
var Wakatime = React.createClass({
getInitialState: function() {
return {
user: {
full_name: null,
email: null,
photo: null
},
loggedIn: false,
loggingEnabled: config.loggingEnabled,
totalTimeLoggedToday: '0 minutes'
};
},
componentDidMount: function() {
var wakatime = new WakaTimeCore();
var that = this;
wakatime.checkAuth().done(function(data) {
if (data !== false) {
browser.storage.sync.get({
loggingEnabled: config.loggingEnabled
}).then(function(items) {
that.setState({loggingEnabled: items.loggingEnabled});
if (items.loggingEnabled === true) {
changeExtensionState('allGood');
}
else {
changeExtensionState('notLogging');
}
});
that.setState({
user: {
full_name: data.full_name,
email: data.email,
photo: data.photo
},
loggedIn: true
});
wakatime.getTotalTimeLoggedToday().done(function(grand_total) {
that.setState({
totalTimeLoggedToday: grand_total.text
});
});
}
else {
changeExtensionState('notSignedIn');
}
});
},
logoutUser: function() {
var deferredObject = $.Deferred();
var that = this;
$.ajax({
url: config.logoutUserUrl,
method: 'GET',
success: function() {
deferredObject.resolve(that);
},
error: function(xhr, status, err) {
console.error(config.logoutUserUrl, status, err.toString());
deferredObject.resolve(that);
}
});
return deferredObject.promise();
},
_logoutUser: function() {
var that = this;
this.logoutUser().done(function(){
that.setState({
user: {
full_name: null,
email: null,
photo: null
},
loggedIn: false,
loggingEnabled: false
});
changeExtensionState('notSignedIn');
});
},
_disableLogging: function() {
this.setState({
loggingEnabled: false
});
changeExtensionState('notLogging');
browser.storage.sync.set({
loggingEnabled: false
});
},
_enableLogging: function() {
this.setState({
loggingEnabled: true
});
changeExtensionState('allGood');
browser.storage.sync.set({
loggingEnabled: true
});
},
render: function() {
return (
<div>
<NavBar
user={this.state.user}
loggedIn={this.state.loggedIn} />
<div className="container">
<div className="row">
<div className="col-md-12">
<MainList
disableLogging={this._disableLogging}
enableLogging={this._enableLogging}
loggingEnabled={this.state.loggingEnabled}
user={this.state.user}
totalTimeLoggedToday={this.state.totalTimeLoggedToday}
logoutUser={this._logoutUser}
loggedIn={this.state.loggedIn} />
</div>
</div>
</div>
</div>
);
}
});
module.exports = Wakatime;

View File

@@ -1,67 +1,67 @@
/* global chrome */
//jshint esnext:true
var config = {
// Extension name
name: 'WakaTime',
// Extension version
version: chrome.app.getDetails().version,
// Time for idle state of the browser
// The user is considered idle if there was
// no activity in the browser for x seconds
detectionIntervalInSeconds: 60,
// Default logging style
// Log all except blacklisted sites
// or log only the white listed sites.
loggingStyle: 'blacklist',
// Default logging type
loggingType: 'domain',
// By default logging is enabled
loggingEnabled: true,
// Url to which to send the heartbeat
heartbeatApiUrl: 'https://api.wakatime.com/api/v1/users/current/heartbeats',
// Url from which to detect if the user is logged in
currentUserApiUrl: 'https://api.wakatime.com/api/v1/users/current',
// The url to logout the user from wakatime
logoutUserUrl: 'https://wakatime.com/logout',
// Gets stats from the WakaTime API
summariesApiUrl: 'https://api.wakatime.com/api/v1/users/current/summaries',
// Different colors for different states of the extension
colors: {
allGood: '',
notLogging: 'gray',
notSignedIn: 'red',
lightTheme: 'white'
},
// Tooltips for each of the extension states
tooltips: {
allGood: '',
notLogging: 'Not logging',
notSignedIn: 'Not signed In',
blacklisted: 'This URL is blacklisted',
whitelisted: 'This URL is not on your whitelist'
},
// Default theme
theme: 'light',
// Valid extension states
states: [
'allGood',
'notLogging',
'notSignedIn',
'blacklisted',
'whitelisted'
],
// Predefined alert type and text for success and failure.
alert: {
success: {
type: 'success',
text: 'Options have been saved!'
},
failure: {
type: 'danger',
text: 'There was an error while saving the options!'
}
}
};
module.exports = config;
/* global browser */
//jshint esnext:true
var config = {
// Extension name
name: 'WakaTime',
// Extension version
version: browser.runtime.getManifest().version,
// Time for idle state of the browser
// The user is considered idle if there was
// no activity in the browser for x seconds
detectionIntervalInSeconds: 60,
// Default logging style
// Log all except blacklisted sites
// or log only the white listed sites.
loggingStyle: 'blacklist',
// Default logging type
loggingType: 'domain',
// By default logging is enabled
loggingEnabled: true,
// Url to which to send the heartbeat
heartbeatApiUrl: 'https://api.wakatime.com/api/v1/users/current/heartbeats',
// Url from which to detect if the user is logged in
currentUserApiUrl: 'https://api.wakatime.com/api/v1/users/current',
// The url to logout the user from wakatime
logoutUserUrl: 'https://wakatime.com/logout',
// Gets stats from the WakaTime API
summariesApiUrl: 'https://api.wakatime.com/api/v1/users/current/summaries',
// Different colors for different states of the extension
colors: {
allGood: '',
notLogging: 'gray',
notSignedIn: 'red',
lightTheme: 'white'
},
// Tooltips for each of the extension states
tooltips: {
allGood: '',
notLogging: 'Not logging',
notSignedIn: 'Not signed In',
blacklisted: 'This URL is blacklisted',
whitelisted: 'This URL is not on your whitelist'
},
// Default theme
theme: 'light',
// Valid extension states
states: [
'allGood',
'notLogging',
'notSignedIn',
'blacklisted',
'whitelisted'
],
// Predefined alert type and text for success and failure.
alert: {
success: {
type: 'success',
text: 'Options have been saved!'
},
failure: {
type: 'danger',
text: 'There was an error while saving the options!'
}
}
};
module.exports = config;

View File

@@ -1,256 +1,256 @@
/* global chrome */
//jshint esnext:true
var $ = require('jquery');
var moment = require('moment');
var config = require('./../config');
// Helpers
var getDomainFromUrl = require('./../helpers/getDomainFromUrl');
var changeExtensionState = require('../helpers/changeExtensionState');
var in_array = require('./../helpers/in_array');
var contains = require('./../helpers/contains');
class WakaTimeCore {
constructor() {
this.tabsWithDevtoolsOpen = [];
}
/**
* Settter for tabsWithDevtoolsOpen
*
* @param tabs
*/
setTabsWithDevtoolsOpen(tabs) {
this.tabsWithDevtoolsOpen = tabs;
}
getTotalTimeLoggedToday() {
var deferredObject = $.Deferred();
var today = moment().format('YYYY-MM-DD');
$.ajax({
url: config.summariesApiUrl + '?start=' + today + '&end=' + today,
dataType: 'json',
success: (data) => {
deferredObject.resolve(data.data[0].grand_total);
},
error: (xhr, status, err) => {
console.error(config.summariesApiUrl, status, err.toString());
deferredObject.resolve(false);
}
});
return deferredObject.promise();
}
/**
* Checks if the user is logged in.
*
* @returns {*}
*/
checkAuth() {
var deferredObject = $.Deferred();
$.ajax({
url: config.currentUserApiUrl,
dataType: 'json',
success: (data) => {
deferredObject.resolve(data.data);
},
error: (xhr, status, err) => {
console.error(config.currentUserApiUrl, status, err.toString());
deferredObject.resolve(false);
}
});
return deferredObject.promise();
}
/**
* Depending on various factors detects the current active tab URL or domain,
* and sends it to WakaTime for logging.
*/
recordHeartbeat() {
chrome.storage.sync.get({
loggingEnabled: config.loggingEnabled,
loggingStyle: config.loggingStyle,
blacklist: '',
whitelist: ''
}, (items) => {
if (items.loggingEnabled === true) {
changeExtensionState('allGood');
chrome.idle.queryState(config.detectionIntervalInSeconds, (newState) => {
if (newState === 'active') {
// Get current tab URL.
chrome.tabs.query({active: true}, (tabs) => {
var currentActiveTab = tabs[0];
var debug = false;
// If the current active tab has devtools open
if (in_array(currentActiveTab.id, this.tabsWithDevtoolsOpen)) debug = true;
if (items.loggingStyle == 'blacklist') {
if (! contains(currentActiveTab.url, items.blacklist)) {
this.sendHeartbeat(currentActiveTab.url, debug);
}
else {
changeExtensionState('blacklisted');
console.log(currentActiveTab.url + ' is on a blacklist.');
}
}
if (items.loggingStyle == 'whitelist') {
if (contains(currentActiveTab.url, items.whitelist)) {
this.sendHeartbeat(currentActiveTab.url, debug);
}
else {
changeExtensionState('whitelisted');
console.log(currentActiveTab.url + ' is not on a whitelist.');
}
}
});
}
});
}
else {
changeExtensionState('notLogging');
}
});
}
/**
* Creates payload for the heartbeat and returns it as JSON.
*
* @param entity
* @param type
* @param debug
* @returns {*}
* @private
*/
_preparePayload(entity, type, debug = false) {
return JSON.stringify({
entity: entity,
type: type,
time: moment().format('X'),
project: '<<LAST_PROJECT>>',
is_debugging: debug,
plugin: 'chrome-wakatime/' + config.version
});
}
/**
* Returns a promise with logging type variable.
*
* @returns {*}
* @private
*/
_getLoggingType() {
var deferredObject = $.Deferred();
chrome.storage.sync.get({
loggingType: config.loggingType
}, function (items) {
deferredObject.resolve(items.loggingType);
});
return deferredObject.promise();
}
/**
* Given the entity and logging type it creates a payload and
* sends an ajax post request to the API.
*
* @param entity
* @param debug
*/
sendHeartbeat(entity, debug) {
var payload = null;
this._getLoggingType().done((loggingType) => {
// Get only the domain from the entity.
// And send that in heartbeat
if (loggingType == 'domain') {
var domain = getDomainFromUrl(entity);
payload = this._preparePayload(domain, 'domain', debug);
console.log(payload);
this.sendAjaxRequestToApi(payload);
}
// Send entity in heartbeat
else if (loggingType == 'url') {
payload = this._preparePayload(entity, 'url', debug);
console.log(payload);
this.sendAjaxRequestToApi(payload);
}
});
}
/**
* Sends AJAX request with payload to the heartbeat API as JSON.
*
* @param payload
* @param method
* @returns {*}
*/
sendAjaxRequestToApi(payload, method = 'POST') {
var deferredObject = $.Deferred();
$.ajax({
url: config.heartbeatApiUrl,
dataType: 'json',
contentType: 'application/json',
method: method,
data: payload,
statusCode: {
401: function () {
changeExtensionState('notSignedIn');
},
201: function () {
// nothing to do here
}
},
success: (response) => {
deferredObject.resolve(this);
},
error: (xhr, status, err) => {
console.error(config.heartbeatApiUrl, status, err.toString());
deferredObject.resolve(this);
}
});
return deferredObject.promise();
}
}
export default WakaTimeCore;
/* global browser */
//jshint esnext:true
var $ = require('jquery');
var moment = require('moment');
var config = require('./../config');
// Helpers
var getDomainFromUrl = require('./../helpers/getDomainFromUrl');
var changeExtensionState = require('../helpers/changeExtensionState');
var in_array = require('./../helpers/in_array');
var contains = require('./../helpers/contains');
class WakaTimeCore {
constructor() {
this.tabsWithDevtoolsOpen = [];
}
/**
* Settter for tabsWithDevtoolsOpen
*
* @param tabs
*/
setTabsWithDevtoolsOpen(tabs) {
this.tabsWithDevtoolsOpen = tabs;
}
getTotalTimeLoggedToday() {
var deferredObject = $.Deferred();
var today = moment().format('YYYY-MM-DD');
$.ajax({
url: config.summariesApiUrl + '?start=' + today + '&end=' + today,
dataType: 'json',
success: (data) => {
deferredObject.resolve(data.data[0].grand_total);
},
error: (xhr, status, err) => {
console.error(config.summariesApiUrl, status, err.toString());
deferredObject.resolve(false);
}
});
return deferredObject.promise();
}
/**
* Checks if the user is logged in.
*
* @returns {*}
*/
checkAuth() {
var deferredObject = $.Deferred();
$.ajax({
url: config.currentUserApiUrl,
dataType: 'json',
success: (data) => {
deferredObject.resolve(data.data);
},
error: (xhr, status, err) => {
console.error(config.currentUserApiUrl, status, err.toString());
deferredObject.resolve(false);
}
});
return deferredObject.promise();
}
/**
* Depending on various factors detects the current active tab URL or domain,
* and sends it to WakaTime for logging.
*/
recordHeartbeat() {
browser.storage.sync.get({
loggingEnabled: config.loggingEnabled,
loggingStyle: config.loggingStyle,
blacklist: '',
whitelist: ''
}).then((items) => {
if (items.loggingEnabled === true) {
changeExtensionState('allGood');
browser.idle.queryState(config.detectionIntervalInSeconds).then((newState) => {
if (newState === 'active') {
// Get current tab URL.
browser.tabs.query({active: true}).then((tabs) => {
var currentActiveTab = tabs[0];
var debug = false;
// If the current active tab has devtools open
if (in_array(currentActiveTab.id, this.tabsWithDevtoolsOpen)) debug = true;
if (items.loggingStyle == 'blacklist') {
if (! contains(currentActiveTab.url, items.blacklist)) {
this.sendHeartbeat(currentActiveTab.url, debug);
}
else {
changeExtensionState('blacklisted');
console.log(currentActiveTab.url + ' is on a blacklist.');
}
}
if (items.loggingStyle == 'whitelist') {
if (contains(currentActiveTab.url, items.whitelist)) {
this.sendHeartbeat(currentActiveTab.url, debug);
}
else {
changeExtensionState('whitelisted');
console.log(currentActiveTab.url + ' is not on a whitelist.');
}
}
});
}
});
}
else {
changeExtensionState('notLogging');
}
});
}
/**
* Creates payload for the heartbeat and returns it as JSON.
*
* @param entity
* @param type
* @param debug
* @returns {*}
* @private
*/
_preparePayload(entity, type, debug = false) {
return JSON.stringify({
entity: entity,
type: type,
time: moment().format('X'),
project: '<<LAST_PROJECT>>',
is_debugging: debug,
plugin: 'browser-wakatime/' + config.version
});
}
/**
* Returns a promise with logging type variable.
*
* @returns {*}
* @private
*/
_getLoggingType() {
var deferredObject = $.Deferred();
browser.storage.sync.get({
loggingType: config.loggingType
}).then(function (items) {
deferredObject.resolve(items.loggingType);
});
return deferredObject.promise();
}
/**
* Given the entity and logging type it creates a payload and
* sends an ajax post request to the API.
*
* @param entity
* @param debug
*/
sendHeartbeat(entity, debug) {
var payload = null;
this._getLoggingType().done((loggingType) => {
// Get only the domain from the entity.
// And send that in heartbeat
if (loggingType == 'domain') {
var domain = getDomainFromUrl(entity);
payload = this._preparePayload(domain, 'domain', debug);
console.log(payload);
this.sendAjaxRequestToApi(payload);
}
// Send entity in heartbeat
else if (loggingType == 'url') {
payload = this._preparePayload(entity, 'url', debug);
console.log(payload);
this.sendAjaxRequestToApi(payload);
}
});
}
/**
* Sends AJAX request with payload to the heartbeat API as JSON.
*
* @param payload
* @param method
* @returns {*}
*/
sendAjaxRequestToApi(payload, method = 'POST') {
var deferredObject = $.Deferred();
$.ajax({
url: config.heartbeatApiUrl,
dataType: 'json',
contentType: 'application/json',
method: method,
data: payload,
statusCode: {
401: function () {
changeExtensionState('notSignedIn');
},
201: function () {
// nothing to do here
}
},
success: (response) => {
deferredObject.resolve(this);
},
error: (xhr, status, err) => {
console.error(config.heartbeatApiUrl, status, err.toString());
deferredObject.resolve(this);
}
});
return deferredObject.promise();
}
}
export default WakaTimeCore;

View File

@@ -1,12 +1,12 @@
/* global chrome */
// Create a connection to the background page
var backgroundPageConnection = chrome.runtime.connect({
name: "devtools-page"
});
// Send a message to background page with the current active tabId
backgroundPageConnection.postMessage({
name: 'init',
tabId: chrome.devtools.inspectedWindow.tabId
});
/* global browser */
// Create a connection to the background page
var backgroundPageConnection = browser.runtime.connect({
name: "devtools-page"
});
// Send a message to background page with the current active tabId
backgroundPageConnection.postMessage({
name: 'init',
tabId: browser.devtools.inspectedWindow.tabId
});

View File

@@ -1,100 +1,100 @@
/* global chrome */
// Core
var WakaTimeCore = require("./core/WakaTimeCore").default;
// initialize class
var wakatime = new WakaTimeCore();
// Holds currently open connections (ports) with devtools
// Uses tabId as index key.
var connections = {};
// Add a listener to resolve alarms
chrome.alarms.onAlarm.addListener(function (alarm) {
// |alarm| can be undefined because onAlarm also gets called from
// window.setTimeout on old chrome versions.
if (alarm && alarm.name == 'heartbeatAlarm') {
console.log('recording a heartbeat - alarm triggered');
wakatime.recordHeartbeat();
}
});
// Create a new alarm for heartbeats.
chrome.alarms.create('heartbeatAlarm', {periodInMinutes: 2});
/**
* Whenever a active tab is changed it records a heartbeat with that tab url.
*/
chrome.tabs.onActivated.addListener(function (activeInfo) {
chrome.tabs.get(activeInfo.tabId, function (tab) {
console.log('recording a heartbeat - active tab changed');
wakatime.recordHeartbeat();
});
});
/**
* Whenever any tab is updated it checks if the updated tab is the tab that is
* currently active and if it is, then it records a heartbeat.
*/
chrome.tabs.onUpdated.addListener(function (tabId, changeInfo, tab) {
if (changeInfo.status === 'complete') {
// Get current tab URL.
chrome.tabs.query({active: true}, function(tabs) {
// If tab updated is the same as active tab
if (tabId == tabs[0].id) {
console.log('recording a heartbeat - tab updated');
wakatime.recordHeartbeat();
}
});
}
});
/**
* This is in charge of detecting if devtools are opened or closed
* and sending a heartbeat depending on that.
*/
chrome.runtime.onConnect.addListener(function (port) {
if (port.name == "devtools-page") {
// Listen to messages sent from the DevTools page
port.onMessage.addListener(function (message, sender, sendResponse) {
if (message.name == "init") {
connections[message.tabId] = port;
wakatime.setTabsWithDevtoolsOpen(Object.keys(connections));
wakatime.recordHeartbeat();
}
});
port.onDisconnect.addListener(function (port) {
var tabs = Object.keys(connections);
for (var i = 0, len = tabs.length; i < len; i ++) {
if (connections[tabs[i]] == port) {
delete connections[tabs[i]];
break;
}
}
wakatime.setTabsWithDevtoolsOpen(Object.keys(connections));
wakatime.recordHeartbeat();
});
}
});
/* global browser */
// Core
var WakaTimeCore = require("./core/WakaTimeCore").default;
// initialize class
var wakatime = new WakaTimeCore();
// Holds currently open connections (ports) with devtools
// Uses tabId as index key.
var connections = {};
// Add a listener to resolve alarms
browser.alarms.onAlarm.addListener(function (alarm) {
// |alarm| can be undefined because onAlarm also gets called from
// window.setTimeout on old chrome versions.
if (alarm && alarm.name == 'heartbeatAlarm') {
console.log('recording a heartbeat - alarm triggered');
wakatime.recordHeartbeat();
}
});
// Create a new alarm for heartbeats.
browser.alarms.create('heartbeatAlarm', {periodInMinutes: 2});
/**
* Whenever a active tab is changed it records a heartbeat with that tab url.
*/
browser.tabs.onActivated.addListener(function (activeInfo) {
browser.tabs.get(activeInfo.tabId).then(function (tab) {
console.log('recording a heartbeat - active tab changed');
wakatime.recordHeartbeat();
});
});
/**
* Whenever any tab is updated it checks if the updated tab is the tab that is
* currently active and if it is, then it records a heartbeat.
*/
browser.tabs.onUpdated.addListener(function (tabId, changeInfo, tab) {
if (changeInfo.status === 'complete') {
// Get current tab URL.
browser.tabs.query({active: true}).then(function(tabs) {
// If tab updated is the same as active tab
if (tabId == tabs[0].id) {
console.log('recording a heartbeat - tab updated');
wakatime.recordHeartbeat();
}
});
}
});
/**
* This is in charge of detecting if devtools are opened or closed
* and sending a heartbeat depending on that.
*/
browser.runtime.onConnect.addListener(function (port) {
if (port.name == "devtools-page") {
// Listen to messages sent from the DevTools page
port.onMessage.addListener(function (message, sender, sendResponse) {
if (message.name == "init") {
connections[message.tabId] = port;
wakatime.setTabsWithDevtoolsOpen(Object.keys(connections));
wakatime.recordHeartbeat();
}
});
port.onDisconnect.addListener(function (port) {
var tabs = Object.keys(connections);
for (var i = 0, len = tabs.length; i < len; i ++) {
if (connections[tabs[i]] == port) {
delete connections[tabs[i]];
break;
}
}
wakatime.setTabsWithDevtoolsOpen(Object.keys(connections));
wakatime.recordHeartbeat();
});
}
});

View File

@@ -1,50 +1,50 @@
/* global chrome */
var config = require('../config');
/**
* It changes the extension icon color.
* Supported values are: 'red', 'white', 'gray' and ''.
*
* @param color
*/
function changeExtensionIcon(color) {
color = color ? color : '';
var path = null;
if (color !== '') {
color = '-' + color;
path = './graphics/wakatime-logo-38' + color + '.png';
chrome.browserAction.setIcon({
path: path
});
}
if (color === '') {
chrome.storage.sync.get({
theme: config.theme
}, function (items) {
if (items.theme == config.theme) {
path = './graphics/wakatime-logo-38.png';
chrome.browserAction.setIcon({
path: path
});
}
else {
path = './graphics/wakatime-logo-38-white.png';
chrome.browserAction.setIcon({
path: path
});
}
});
}
}
module.exports = changeExtensionIcon;
/* global browser */
var config = require('../config');
/**
* It changes the extension icon color.
* Supported values are: 'red', 'white', 'gray' and ''.
*
* @param color
*/
function changeExtensionIcon(color) {
color = color ? color : '';
var path = null;
if (color !== '') {
color = '-' + color;
path = './graphics/wakatime-logo-38' + color + '.png';
browser.browserAction.setIcon({
path: path
});
}
if (color === '') {
browser.storage.sync.get({
theme: config.theme
}).then(function (items) {
if (items.theme == config.theme) {
path = './graphics/wakatime-logo-38.png';
browser.browserAction.setIcon({
path: path
});
}
else {
path = './graphics/wakatime-logo-38-white.png';
browser.browserAction.setIcon({
path: path
});
}
});
}
}
module.exports = changeExtensionIcon;

View File

@@ -1,42 +1,42 @@
var config = require('../config');
// Helpers
var changeExtensionIcon = require('./changeExtensionIcon');
var changeExtensionTooltip = require('./changeExtensionTooltip');
var in_array = require('./in_array');
/**
* Sets the current state of the extension.
*
* @param state
*/
function changeExtensionState(state) {
if (! in_array(state, config.states)) {
throw new Error('Not a valid state!');
}
switch (state) {
case 'allGood':
changeExtensionIcon(config.colors.allGood);
changeExtensionTooltip(config.tooltips.allGood);
break;
case 'notLogging':
changeExtensionIcon(config.colors.notLogging);
changeExtensionTooltip(config.tooltips.notLogging);
break;
case 'notSignedIn':
changeExtensionIcon(config.colors.notSignedIn);
changeExtensionTooltip(config.tooltips.notSignedIn);
break;
case 'blacklisted':
changeExtensionIcon(config.colors.notLogging);
changeExtensionTooltip(config.tooltips.blacklisted);
break;
case 'whitelisted':
changeExtensionIcon(config.colors.notLogging);
changeExtensionTooltip(config.tooltips.whitelisted);
break;
}
}
var config = require('../config');
// Helpers
var changeExtensionIcon = require('./changeExtensionIcon');
var changeExtensionTooltip = require('./changeExtensionTooltip');
var in_array = require('./in_array');
/**
* Sets the current state of the extension.
*
* @param state
*/
function changeExtensionState(state) {
if (! in_array(state, config.states)) {
throw new Error('Not a valid state!');
}
switch (state) {
case 'allGood':
changeExtensionIcon(config.colors.allGood);
changeExtensionTooltip(config.tooltips.allGood);
break;
case 'notLogging':
changeExtensionIcon(config.colors.notLogging);
changeExtensionTooltip(config.tooltips.notLogging);
break;
case 'notSignedIn':
changeExtensionIcon(config.colors.notSignedIn);
changeExtensionTooltip(config.tooltips.notSignedIn);
break;
case 'blacklisted':
changeExtensionIcon(config.colors.notLogging);
changeExtensionTooltip(config.tooltips.blacklisted);
break;
case 'whitelisted':
changeExtensionIcon(config.colors.notLogging);
changeExtensionTooltip(config.tooltips.whitelisted);
break;
}
}
module.exports = changeExtensionState;

View File

@@ -1,22 +1,22 @@
/* global chrome */
var config = require('../config');
/**
* It changes the extension title
*
* @param text
*/
function changeExtensionTooltip(text) {
if (text === '') {
text = config.name;
}
else {
text = config.name + ' - ' + text;
}
chrome.browserAction.setTitle({title: text});
}
/* global browser */
var config = require('../config');
/**
* It changes the extension title
*
* @param text
*/
function changeExtensionTooltip(text) {
if (text === '') {
text = config.name;
}
else {
text = config.name + ' - ' + text;
}
browser.browserAction.setTitle({title: text});
}
module.exports = changeExtensionTooltip;

View File

@@ -1,29 +1,29 @@
/**
* Creates an array from list using \n as delimiter
* and checks if any element in list is contained in the url.
*
* @param url
* @param list
* @returns {boolean}
*/
function contains(url, list) {
var lines = list.split('\n');
for (var i = 0; i < lines.length; i ++) {
// Trim all lines from the list one by one
var cleanLine = lines[i].trim();
// If by any chance one line in the list is empty, ignore it
if(cleanLine === '') continue;
// If url contains the current line return true
if (url.indexOf(cleanLine) > -1) {
return true;
}
}
return false;
}
module.exports = contains;
/**
* Creates an array from list using \n as delimiter
* and checks if any element in list is contained in the url.
*
* @param url
* @param list
* @returns {boolean}
*/
function contains(url, list) {
var lines = list.split('\n');
for (var i = 0; i < lines.length; i ++) {
// Trim all lines from the list one by one
var cleanLine = lines[i].trim();
// If by any chance one line in the list is empty, ignore it
if(cleanLine === '') continue;
// If url contains the current line return true
if (url.indexOf(cleanLine) > -1) {
return true;
}
}
return false;
}
module.exports = contains;

View File

@@ -1,13 +1,13 @@
/**
* Returns domain from given URL.
*
* @param url
* @returns {string}
*/
function getDomainFromUrl(url) {
var parts = url.split('/');
return parts[0] + "//" + parts[2];
}
/**
* Returns domain from given URL.
*
* @param url
* @returns {string}
*/
function getDomainFromUrl(url) {
var parts = url.split('/');
return parts[0] + "//" + parts[2];
}
module.exports = getDomainFromUrl;

View File

@@ -1,18 +1,18 @@
/**
* Returns boolean if needle is found in haystack or not.
*
* @param needle
* @param haystack
* @returns {boolean}
*/
function in_array(needle, haystack) {
for (var i = 0; i < haystack.length; i ++) {
if (needle == haystack[i]) {
return true;
}
}
return false;
}
/**
* Returns boolean if needle is found in haystack or not.
*
* @param needle
* @param haystack
* @returns {boolean}
*/
function in_array(needle, haystack) {
for (var i = 0; i < haystack.length; i ++) {
if (needle == haystack[i]) {
return true;
}
}
return false;
}
module.exports = in_array;

View File

@@ -1,16 +1,16 @@
/* global chrome */
/* This is a fix for Bootstrap requiring jQuery */
global.jQuery = require('jquery');
require('bootstrap');
var React = require('react');
var ReactDOM = require('react-dom');
// React components
var Options = require('./components/Options.jsx');
ReactDOM.render(
<Options />,
document.getElementById('wakatime-options')
);
/* global browser */
/* This is a fix for Bootstrap requiring jQuery */
global.jQuery = require('jquery');
require('bootstrap');
var React = require('react');
var ReactDOM = require('react-dom');
// React components
var Options = require('./components/Options.jsx');
ReactDOM.render(
<Options />,
document.getElementById('wakatime-options')
);

View File

@@ -1,30 +1,30 @@
@import "bootstrap/bootstrap";
@import "font-awesome/font-awesome";
@import "bootswatch/paper/bootswatch";
@import "bootswatch/paper/variables";
@import "variables";
@import "partials/_animations";
body {
min-width: 357px;
}
a.navbar-brand {
img {
margin-top: -12px;
float: left;
margin-right: 7px;
}
}
div.container {
margin-top: 20px;
}
canvas#icon {
display: none;
}
div#status {
display: none;
}
@import "bootstrap/bootstrap";
@import "font-awesome/font-awesome";
@import "bootswatch/paper/bootswatch";
@import "bootswatch/paper/variables";
@import "variables";
@import "partials/_animations";
body {
min-width: 357px;
}
a.navbar-brand {
img {
margin-top: -12px;
float: left;
margin-right: 7px;
}
}
div.container {
margin-top: 20px;
}
canvas#icon {
display: none;
}
div#status {
display: none;
}

View File

@@ -1,304 +1,304 @@
{
"always-semicolon": true,
"block-indent": 2,
"color-case": "lower",
"color-shorthand": true,
"element-case": "lower",
"eof-newline": true,
"leading-zero": false,
"remove-empty-rulesets": true,
"space-after-colon": 1,
"space-after-combinator": 1,
"space-before-selector-delimiter": 0,
"space-between-declarations": "\n",
"space-after-opening-brace": "\n",
"space-before-closing-brace": "\n",
"space-before-colon": 0,
"space-before-combinator": 1,
"space-before-opening-brace": 1,
"strip-spaces": true,
"unitless-zero": true,
"vendor-prefix-align": true,
"sort-order": [
[
"position",
"top",
"right",
"bottom",
"left",
"z-index",
"display",
"float",
"width",
"min-width",
"max-width",
"height",
"min-height",
"max-height",
"-webkit-box-sizing",
"-moz-box-sizing",
"box-sizing",
"-webkit-appearance",
"padding",
"padding-top",
"padding-right",
"padding-bottom",
"padding-left",
"margin",
"margin-top",
"margin-right",
"margin-bottom",
"margin-left",
"overflow",
"overflow-x",
"overflow-y",
"-webkit-overflow-scrolling",
"-ms-overflow-x",
"-ms-overflow-y",
"-ms-overflow-style",
"clip",
"clear",
"font",
"font-family",
"font-size",
"font-style",
"font-weight",
"font-variant",
"font-size-adjust",
"font-stretch",
"font-effect",
"font-emphasize",
"font-emphasize-position",
"font-emphasize-style",
"font-smooth",
"-webkit-hyphens",
"-moz-hyphens",
"hyphens",
"line-height",
"color",
"text-align",
"-webkit-text-align-last",
"-moz-text-align-last",
"-ms-text-align-last",
"text-align-last",
"text-emphasis",
"text-emphasis-color",
"text-emphasis-style",
"text-emphasis-position",
"text-decoration",
"text-indent",
"text-justify",
"text-outline",
"-ms-text-overflow",
"text-overflow",
"text-overflow-ellipsis",
"text-overflow-mode",
"text-shadow",
"text-transform",
"text-wrap",
"-webkit-text-size-adjust",
"-ms-text-size-adjust",
"letter-spacing",
"-ms-word-break",
"word-break",
"word-spacing",
"-ms-word-wrap",
"word-wrap",
"-moz-tab-size",
"-o-tab-size",
"tab-size",
"white-space",
"vertical-align",
"list-style",
"list-style-position",
"list-style-type",
"list-style-image",
"pointer-events",
"-ms-touch-action",
"touch-action",
"cursor",
"visibility",
"zoom",
"flex-direction",
"flex-order",
"flex-pack",
"flex-align",
"table-layout",
"empty-cells",
"caption-side",
"border-spacing",
"border-collapse",
"content",
"quotes",
"counter-reset",
"counter-increment",
"resize",
"-webkit-user-select",
"-moz-user-select",
"-ms-user-select",
"-o-user-select",
"user-select",
"nav-index",
"nav-up",
"nav-right",
"nav-down",
"nav-left",
"background",
"background-color",
"background-image",
"-ms-filter:\\'progid:DXImageTransform.Microsoft.gradient",
"filter:progid:DXImageTransform.Microsoft.gradient",
"filter:progid:DXImageTransform.Microsoft.AlphaImageLoader",
"filter",
"background-repeat",
"background-attachment",
"background-position",
"background-position-x",
"background-position-y",
"-webkit-background-clip",
"-moz-background-clip",
"background-clip",
"background-origin",
"-webkit-background-size",
"-moz-background-size",
"-o-background-size",
"background-size",
"border",
"border-color",
"border-style",
"border-width",
"border-top",
"border-top-color",
"border-top-style",
"border-top-width",
"border-right",
"border-right-color",
"border-right-style",
"border-right-width",
"border-bottom",
"border-bottom-color",
"border-bottom-style",
"border-bottom-width",
"border-left",
"border-left-color",
"border-left-style",
"border-left-width",
"border-radius",
"border-top-left-radius",
"border-top-right-radius",
"border-bottom-right-radius",
"border-bottom-left-radius",
"-webkit-border-image",
"-moz-border-image",
"-o-border-image",
"border-image",
"-webkit-border-image-source",
"-moz-border-image-source",
"-o-border-image-source",
"border-image-source",
"-webkit-border-image-slice",
"-moz-border-image-slice",
"-o-border-image-slice",
"border-image-slice",
"-webkit-border-image-width",
"-moz-border-image-width",
"-o-border-image-width",
"border-image-width",
"-webkit-border-image-outset",
"-moz-border-image-outset",
"-o-border-image-outset",
"border-image-outset",
"-webkit-border-image-repeat",
"-moz-border-image-repeat",
"-o-border-image-repeat",
"border-image-repeat",
"outline",
"outline-width",
"outline-style",
"outline-color",
"outline-offset",
"-webkit-box-shadow",
"-moz-box-shadow",
"box-shadow",
"filter:progid:DXImageTransform.Microsoft.Alpha(Opacity",
"-ms-filter:\\'progid:DXImageTransform.Microsoft.Alpha",
"opacity",
"-ms-interpolation-mode",
"-webkit-transition",
"-moz-transition",
"-ms-transition",
"-o-transition",
"transition",
"-webkit-transition-delay",
"-moz-transition-delay",
"-ms-transition-delay",
"-o-transition-delay",
"transition-delay",
"-webkit-transition-timing-function",
"-moz-transition-timing-function",
"-ms-transition-timing-function",
"-o-transition-timing-function",
"transition-timing-function",
"-webkit-transition-duration",
"-moz-transition-duration",
"-ms-transition-duration",
"-o-transition-duration",
"transition-duration",
"-webkit-transition-property",
"-moz-transition-property",
"-ms-transition-property",
"-o-transition-property",
"transition-property",
"-webkit-transform",
"-moz-transform",
"-ms-transform",
"-o-transform",
"transform",
"-webkit-transform-origin",
"-moz-transform-origin",
"-ms-transform-origin",
"-o-transform-origin",
"transform-origin",
"-webkit-animation",
"-moz-animation",
"-ms-animation",
"-o-animation",
"animation",
"-webkit-animation-name",
"-moz-animation-name",
"-ms-animation-name",
"-o-animation-name",
"animation-name",
"-webkit-animation-duration",
"-moz-animation-duration",
"-ms-animation-duration",
"-o-animation-duration",
"animation-duration",
"-webkit-animation-play-state",
"-moz-animation-play-state",
"-ms-animation-play-state",
"-o-animation-play-state",
"animation-play-state",
"-webkit-animation-timing-function",
"-moz-animation-timing-function",
"-ms-animation-timing-function",
"-o-animation-timing-function",
"animation-timing-function",
"-webkit-animation-delay",
"-moz-animation-delay",
"-ms-animation-delay",
"-o-animation-delay",
"animation-delay",
"-webkit-animation-iteration-count",
"-moz-animation-iteration-count",
"-ms-animation-iteration-count",
"-o-animation-iteration-count",
"animation-iteration-count",
"-webkit-animation-direction",
"-moz-animation-direction",
"-ms-animation-direction",
"-o-animation-direction",
"animation-direction"
]
]
}
{
"always-semicolon": true,
"block-indent": 2,
"color-case": "lower",
"color-shorthand": true,
"element-case": "lower",
"eof-newline": true,
"leading-zero": false,
"remove-empty-rulesets": true,
"space-after-colon": 1,
"space-after-combinator": 1,
"space-before-selector-delimiter": 0,
"space-between-declarations": "\n",
"space-after-opening-brace": "\n",
"space-before-closing-brace": "\n",
"space-before-colon": 0,
"space-before-combinator": 1,
"space-before-opening-brace": 1,
"strip-spaces": true,
"unitless-zero": true,
"vendor-prefix-align": true,
"sort-order": [
[
"position",
"top",
"right",
"bottom",
"left",
"z-index",
"display",
"float",
"width",
"min-width",
"max-width",
"height",
"min-height",
"max-height",
"-webkit-box-sizing",
"-moz-box-sizing",
"box-sizing",
"-webkit-appearance",
"padding",
"padding-top",
"padding-right",
"padding-bottom",
"padding-left",
"margin",
"margin-top",
"margin-right",
"margin-bottom",
"margin-left",
"overflow",
"overflow-x",
"overflow-y",
"-webkit-overflow-scrolling",
"-ms-overflow-x",
"-ms-overflow-y",
"-ms-overflow-style",
"clip",
"clear",
"font",
"font-family",
"font-size",
"font-style",
"font-weight",
"font-variant",
"font-size-adjust",
"font-stretch",
"font-effect",
"font-emphasize",
"font-emphasize-position",
"font-emphasize-style",
"font-smooth",
"-webkit-hyphens",
"-moz-hyphens",
"hyphens",
"line-height",
"color",
"text-align",
"-webkit-text-align-last",
"-moz-text-align-last",
"-ms-text-align-last",
"text-align-last",
"text-emphasis",
"text-emphasis-color",
"text-emphasis-style",
"text-emphasis-position",
"text-decoration",
"text-indent",
"text-justify",
"text-outline",
"-ms-text-overflow",
"text-overflow",
"text-overflow-ellipsis",
"text-overflow-mode",
"text-shadow",
"text-transform",
"text-wrap",
"-webkit-text-size-adjust",
"-ms-text-size-adjust",
"letter-spacing",
"-ms-word-break",
"word-break",
"word-spacing",
"-ms-word-wrap",
"word-wrap",
"-moz-tab-size",
"-o-tab-size",
"tab-size",
"white-space",
"vertical-align",
"list-style",
"list-style-position",
"list-style-type",
"list-style-image",
"pointer-events",
"-ms-touch-action",
"touch-action",
"cursor",
"visibility",
"zoom",
"flex-direction",
"flex-order",
"flex-pack",
"flex-align",
"table-layout",
"empty-cells",
"caption-side",
"border-spacing",
"border-collapse",
"content",
"quotes",
"counter-reset",
"counter-increment",
"resize",
"-webkit-user-select",
"-moz-user-select",
"-ms-user-select",
"-o-user-select",
"user-select",
"nav-index",
"nav-up",
"nav-right",
"nav-down",
"nav-left",
"background",
"background-color",
"background-image",
"-ms-filter:\\'progid:DXImageTransform.Microsoft.gradient",
"filter:progid:DXImageTransform.Microsoft.gradient",
"filter:progid:DXImageTransform.Microsoft.AlphaImageLoader",
"filter",
"background-repeat",
"background-attachment",
"background-position",
"background-position-x",
"background-position-y",
"-webkit-background-clip",
"-moz-background-clip",
"background-clip",
"background-origin",
"-webkit-background-size",
"-moz-background-size",
"-o-background-size",
"background-size",
"border",
"border-color",
"border-style",
"border-width",
"border-top",
"border-top-color",
"border-top-style",
"border-top-width",
"border-right",
"border-right-color",
"border-right-style",
"border-right-width",
"border-bottom",
"border-bottom-color",
"border-bottom-style",
"border-bottom-width",
"border-left",
"border-left-color",
"border-left-style",
"border-left-width",
"border-radius",
"border-top-left-radius",
"border-top-right-radius",
"border-bottom-right-radius",
"border-bottom-left-radius",
"-webkit-border-image",
"-moz-border-image",
"-o-border-image",
"border-image",
"-webkit-border-image-source",
"-moz-border-image-source",
"-o-border-image-source",
"border-image-source",
"-webkit-border-image-slice",
"-moz-border-image-slice",
"-o-border-image-slice",
"border-image-slice",
"-webkit-border-image-width",
"-moz-border-image-width",
"-o-border-image-width",
"border-image-width",
"-webkit-border-image-outset",
"-moz-border-image-outset",
"-o-border-image-outset",
"border-image-outset",
"-webkit-border-image-repeat",
"-moz-border-image-repeat",
"-o-border-image-repeat",
"border-image-repeat",
"outline",
"outline-width",
"outline-style",
"outline-color",
"outline-offset",
"-webkit-box-shadow",
"-moz-box-shadow",
"box-shadow",
"filter:progid:DXImageTransform.Microsoft.Alpha(Opacity",
"-ms-filter:\\'progid:DXImageTransform.Microsoft.Alpha",
"opacity",
"-ms-interpolation-mode",
"-webkit-transition",
"-moz-transition",
"-ms-transition",
"-o-transition",
"transition",
"-webkit-transition-delay",
"-moz-transition-delay",
"-ms-transition-delay",
"-o-transition-delay",
"transition-delay",
"-webkit-transition-timing-function",
"-moz-transition-timing-function",
"-ms-transition-timing-function",
"-o-transition-timing-function",
"transition-timing-function",
"-webkit-transition-duration",
"-moz-transition-duration",
"-ms-transition-duration",
"-o-transition-duration",
"transition-duration",
"-webkit-transition-property",
"-moz-transition-property",
"-ms-transition-property",
"-o-transition-property",
"transition-property",
"-webkit-transform",
"-moz-transform",
"-ms-transform",
"-o-transform",
"transform",
"-webkit-transform-origin",
"-moz-transform-origin",
"-ms-transform-origin",
"-o-transform-origin",
"transform-origin",
"-webkit-animation",
"-moz-animation",
"-ms-animation",
"-o-animation",
"animation",
"-webkit-animation-name",
"-moz-animation-name",
"-ms-animation-name",
"-o-animation-name",
"animation-name",
"-webkit-animation-duration",
"-moz-animation-duration",
"-ms-animation-duration",
"-o-animation-duration",
"animation-duration",
"-webkit-animation-play-state",
"-moz-animation-play-state",
"-ms-animation-play-state",
"-o-animation-play-state",
"animation-play-state",
"-webkit-animation-timing-function",
"-moz-animation-timing-function",
"-ms-animation-timing-function",
"-o-animation-timing-function",
"animation-timing-function",
"-webkit-animation-delay",
"-moz-animation-delay",
"-ms-animation-delay",
"-o-animation-delay",
"animation-delay",
"-webkit-animation-iteration-count",
"-moz-animation-iteration-count",
"-ms-animation-iteration-count",
"-o-animation-iteration-count",
"animation-iteration-count",
"-webkit-animation-direction",
"-moz-animation-direction",
"-ms-animation-direction",
"-o-animation-direction",
"animation-direction"
]
]
}

View File

@@ -1,19 +1,19 @@
{
"adjoining-classes": false,
"box-sizing": false,
"box-model": false,
"compatible-vendor-prefixes": false,
"floats": false,
"font-sizes": false,
"gradients": false,
"important": false,
"known-properties": false,
"outline-none": false,
"qualified-headings": false,
"regex-selectors": false,
"shorthand": false,
"text-indent": false,
"unique-headings": false,
"universal-selector": false,
"unqualified-attributes": false
}
{
"adjoining-classes": false,
"box-sizing": false,
"box-model": false,
"compatible-vendor-prefixes": false,
"floats": false,
"font-sizes": false,
"gradients": false,
"important": false,
"known-properties": false,
"outline-none": false,
"qualified-headings": false,
"regex-selectors": false,
"shorthand": false,
"text-indent": false,
"unique-headings": false,
"universal-selector": false,
"unqualified-attributes": false
}

View File

@@ -1,6 +1,6 @@
/*!
* Bootstrap v3.3.6 (http://getbootstrap.com)
* Copyright 2011-2015 Twitter, Inc.
* Bootstrap v3.3.7 (http://getbootstrap.com)
* Copyright 2011-2016 Twitter, Inc.
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
*/

View File

@@ -59,7 +59,7 @@
.border-right-radius(0);
}
}
// Need .dropdown-toggle since :last-child doesn't apply given a .dropdown-menu immediately after it
// Need .dropdown-toggle since :last-child doesn't apply, given that a .dropdown-menu is used immediately after it
.btn-group > .btn:last-child:not(:first-child),
.btn-group > .dropdown-toggle:not(:first-child) {
.border-left-radius(0);

View File

@@ -181,7 +181,7 @@ input[type="search"] {
// set a pixel line-height that matches the given height of the input, but only
// for Safari. See https://bugs.webkit.org/show_bug.cgi?id=139848
//
// Note that as of 8.3, iOS doesn't support `datetime` or `week`.
// Note that as of 9.3, iOS doesn't support `week`.
@media screen and (-webkit-min-device-pixel-ratio: 0) {
input[type="date"],

View File

@@ -29,7 +29,7 @@
width: 100%;
margin-bottom: 0;
&:focus {
z-index: 3;
}

View File

@@ -1,9 +1,9 @@
// WebKit-style focus
.tab-focus() {
// Default
outline: thin dotted;
// WebKit
// WebKit-specific. Other browsers will keep their default outline style.
// (Initially tried to also force default via `outline: initial`,
// but that seems to erroneously remove the outline in Firefox altogether.)
outline: 5px auto -webkit-focus-ring-color;
outline-offset: -2px;
}

View File

@@ -214,7 +214,7 @@
}
// Collapsable panels (aka, accordion)
// Collapsible panels (aka, accordion)
//
// Wrap a series of panels in `.panel-group` to turn them into an accordion with
// the help of our collapse JavaScript plugin.

View File

@@ -120,7 +120,7 @@ hr {
// Only display content to screen readers
//
// See: http://a11yproject.com/posts/how-to-hide-content/
// See: http://a11yproject.com/posts/how-to-hide-content
.sr-only {
position: absolute;

View File

@@ -1,6 +1,6 @@
/*!
* Bootstrap v3.3.6 (http://getbootstrap.com)
* Copyright 2011-2015 Twitter, Inc.
* Bootstrap v3.3.7 (http://getbootstrap.com)
* Copyright 2011-2016 Twitter, Inc.
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
*/

View File

@@ -111,7 +111,7 @@
//** Global background color for active items (e.g., navs or dropdowns).
@component-active-bg: @brand-primary;
//** Width of the `border` for generating carets that indicator dropdowns.
//** Width of the `border` for generating carets that indicate dropdowns.
@caret-width-base: 4px;
//** Carets increase slightly in size for larger components.
@caret-width-large: 5px;

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,34 +1,34 @@
// Animated Icons
// --------------------------
.@{fa-css-prefix}-spin {
-webkit-animation: fa-spin 2s infinite linear;
animation: fa-spin 2s infinite linear;
}
.@{fa-css-prefix}-pulse {
-webkit-animation: fa-spin 1s infinite steps(8);
animation: fa-spin 1s infinite steps(8);
}
@-webkit-keyframes fa-spin {
0% {
-webkit-transform: rotate(0deg);
transform: rotate(0deg);
}
100% {
-webkit-transform: rotate(359deg);
transform: rotate(359deg);
}
}
@keyframes fa-spin {
0% {
-webkit-transform: rotate(0deg);
transform: rotate(0deg);
}
100% {
-webkit-transform: rotate(359deg);
transform: rotate(359deg);
}
}
// Animated Icons
// --------------------------
.@{fa-css-prefix}-spin {
-webkit-animation: fa-spin 2s infinite linear;
animation: fa-spin 2s infinite linear;
}
.@{fa-css-prefix}-pulse {
-webkit-animation: fa-spin 1s infinite steps(8);
animation: fa-spin 1s infinite steps(8);
}
@-webkit-keyframes fa-spin {
0% {
-webkit-transform: rotate(0deg);
transform: rotate(0deg);
}
100% {
-webkit-transform: rotate(359deg);
transform: rotate(359deg);
}
}
@keyframes fa-spin {
0% {
-webkit-transform: rotate(0deg);
transform: rotate(0deg);
}
100% {
-webkit-transform: rotate(359deg);
transform: rotate(359deg);
}
}

View File

@@ -1,25 +1,25 @@
// Bordered & Pulled
// -------------------------
.@{fa-css-prefix}-border {
padding: .2em .25em .15em;
border: solid .08em @fa-border-color;
border-radius: .1em;
}
.@{fa-css-prefix}-pull-left { float: left; }
.@{fa-css-prefix}-pull-right { float: right; }
.@{fa-css-prefix} {
&.@{fa-css-prefix}-pull-left { margin-right: .3em; }
&.@{fa-css-prefix}-pull-right { margin-left: .3em; }
}
/* Deprecated as of 4.4.0 */
.pull-right { float: right; }
.pull-left { float: left; }
.@{fa-css-prefix} {
&.pull-left { margin-right: .3em; }
&.pull-right { margin-left: .3em; }
}
// Bordered & Pulled
// -------------------------
.@{fa-css-prefix}-border {
padding: .2em .25em .15em;
border: solid .08em @fa-border-color;
border-radius: .1em;
}
.@{fa-css-prefix}-pull-left { float: left; }
.@{fa-css-prefix}-pull-right { float: right; }
.@{fa-css-prefix} {
&.@{fa-css-prefix}-pull-left { margin-right: .3em; }
&.@{fa-css-prefix}-pull-right { margin-left: .3em; }
}
/* Deprecated as of 4.4.0 */
.pull-right { float: right; }
.pull-left { float: left; }
.@{fa-css-prefix} {
&.pull-left { margin-right: .3em; }
&.pull-right { margin-left: .3em; }
}

View File

@@ -1,12 +1,12 @@
// Base Class Definition
// -------------------------
.@{fa-css-prefix} {
display: inline-block;
font: normal normal normal @fa-font-size-base/@fa-line-height-base FontAwesome; // shortening font declaration
font-size: inherit; // can't have font-size inherit on line above, so need to override
text-rendering: auto; // optimizelegibility throws things off #1094
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
// Base Class Definition
// -------------------------
.@{fa-css-prefix} {
display: inline-block;
font: normal normal normal @fa-font-size-base/@fa-line-height-base FontAwesome; // shortening font declaration
font-size: inherit; // can't have font-size inherit on line above, so need to override
text-rendering: auto; // optimizelegibility throws things off #1094
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}

View File

@@ -1,6 +1,6 @@
// Fixed Width Icons
// -------------------------
.@{fa-css-prefix}-fw {
width: (18em / 14);
text-align: center;
}
// Fixed Width Icons
// -------------------------
.@{fa-css-prefix}-fw {
width: (18em / 14);
text-align: center;
}

View File

@@ -1,18 +1,18 @@
/*!
* Font Awesome 4.6.3 by @davegandy - http://fontawesome.io - @fontawesome
* License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License)
*/
@import "variables.less";
@import "mixins.less";
@import "path.less";
@import "core.less";
@import "larger.less";
@import "fixed-width.less";
@import "list.less";
@import "bordered-pulled.less";
@import "animated.less";
@import "rotated-flipped.less";
@import "stacked.less";
@import "icons.less";
@import "screen-reader.less";
/*!
* Font Awesome 4.6.3 by @davegandy - http://fontawesome.io - @fontawesome
* License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License)
*/
@import "variables.less";
@import "mixins.less";
@import "path.less";
@import "core.less";
@import "larger.less";
@import "fixed-width.less";
@import "list.less";
@import "bordered-pulled.less";
@import "animated.less";
@import "rotated-flipped.less";
@import "stacked.less";
@import "icons.less";
@import "screen-reader.less";

File diff suppressed because it is too large Load Diff

View File

@@ -1,13 +1,13 @@
// Icon Sizes
// -------------------------
/* makes the font 33% larger relative to the icon container */
.@{fa-css-prefix}-lg {
font-size: (4em / 3);
line-height: (3em / 4);
vertical-align: -15%;
}
.@{fa-css-prefix}-2x { font-size: 2em; }
.@{fa-css-prefix}-3x { font-size: 3em; }
.@{fa-css-prefix}-4x { font-size: 4em; }
.@{fa-css-prefix}-5x { font-size: 5em; }
// Icon Sizes
// -------------------------
/* makes the font 33% larger relative to the icon container */
.@{fa-css-prefix}-lg {
font-size: (4em / 3);
line-height: (3em / 4);
vertical-align: -15%;
}
.@{fa-css-prefix}-2x { font-size: 2em; }
.@{fa-css-prefix}-3x { font-size: 3em; }
.@{fa-css-prefix}-4x { font-size: 4em; }
.@{fa-css-prefix}-5x { font-size: 5em; }

View File

@@ -1,19 +1,19 @@
// List Icons
// -------------------------
.@{fa-css-prefix}-ul {
padding-left: 0;
margin-left: @fa-li-width;
list-style-type: none;
> li { position: relative; }
}
.@{fa-css-prefix}-li {
position: absolute;
left: -@fa-li-width;
width: @fa-li-width;
top: (2em / 14);
text-align: center;
&.@{fa-css-prefix}-lg {
left: (-@fa-li-width + (4em / 14));
}
}
// List Icons
// -------------------------
.@{fa-css-prefix}-ul {
padding-left: 0;
margin-left: @fa-li-width;
list-style-type: none;
> li { position: relative; }
}
.@{fa-css-prefix}-li {
position: absolute;
left: -@fa-li-width;
width: @fa-li-width;
top: (2em / 14);
text-align: center;
&.@{fa-css-prefix}-lg {
left: (-@fa-li-width + (4em / 14));
}
}

View File

@@ -1,60 +1,60 @@
// Mixins
// --------------------------
.fa-icon() {
display: inline-block;
font: normal normal normal @fa-font-size-base/@fa-line-height-base FontAwesome; // shortening font declaration
font-size: inherit; // can't have font-size inherit on line above, so need to override
text-rendering: auto; // optimizelegibility throws things off #1094
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.fa-icon-rotate(@degrees, @rotation) {
-ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=@{rotation})";
-webkit-transform: rotate(@degrees);
-ms-transform: rotate(@degrees);
transform: rotate(@degrees);
}
.fa-icon-flip(@horiz, @vert, @rotation) {
-ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=@{rotation}, mirror=1)";
-webkit-transform: scale(@horiz, @vert);
-ms-transform: scale(@horiz, @vert);
transform: scale(@horiz, @vert);
}
// Only display content to screen readers. A la Bootstrap 4.
//
// See: http://a11yproject.com/posts/how-to-hide-content/
.sr-only() {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0,0,0,0);
border: 0;
}
// Use in conjunction with .sr-only to only display content when it's focused.
//
// Useful for "Skip to main content" links; see http://www.w3.org/TR/2013/NOTE-WCAG20-TECHS-20130905/G1
//
// Credit: HTML5 Boilerplate
.sr-only-focusable() {
&:active,
&:focus {
position: static;
width: auto;
height: auto;
margin: 0;
overflow: visible;
clip: auto;
}
}
// Mixins
// --------------------------
.fa-icon() {
display: inline-block;
font: normal normal normal @fa-font-size-base/@fa-line-height-base FontAwesome; // shortening font declaration
font-size: inherit; // can't have font-size inherit on line above, so need to override
text-rendering: auto; // optimizelegibility throws things off #1094
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.fa-icon-rotate(@degrees, @rotation) {
-ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=@{rotation})";
-webkit-transform: rotate(@degrees);
-ms-transform: rotate(@degrees);
transform: rotate(@degrees);
}
.fa-icon-flip(@horiz, @vert, @rotation) {
-ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=@{rotation}, mirror=1)";
-webkit-transform: scale(@horiz, @vert);
-ms-transform: scale(@horiz, @vert);
transform: scale(@horiz, @vert);
}
// Only display content to screen readers. A la Bootstrap 4.
//
// See: http://a11yproject.com/posts/how-to-hide-content/
.sr-only() {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0,0,0,0);
border: 0;
}
// Use in conjunction with .sr-only to only display content when it's focused.
//
// Useful for "Skip to main content" links; see http://www.w3.org/TR/2013/NOTE-WCAG20-TECHS-20130905/G1
//
// Credit: HTML5 Boilerplate
.sr-only-focusable() {
&:active,
&:focus {
position: static;
width: auto;
height: auto;
margin: 0;
overflow: visible;
clip: auto;
}
}

View File

@@ -1,15 +1,15 @@
/* FONT PATH
* -------------------------- */
@font-face {
font-family: 'FontAwesome';
src: url('@{fa-font-path}/fontawesome-webfont.eot?v=@{fa-version}');
src: url('@{fa-font-path}/fontawesome-webfont.eot?#iefix&v=@{fa-version}') format('embedded-opentype'),
url('@{fa-font-path}/fontawesome-webfont.woff2?v=@{fa-version}') format('woff2'),
url('@{fa-font-path}/fontawesome-webfont.woff?v=@{fa-version}') format('woff'),
url('@{fa-font-path}/fontawesome-webfont.ttf?v=@{fa-version}') format('truetype'),
url('@{fa-font-path}/fontawesome-webfont.svg?v=@{fa-version}#fontawesomeregular') format('svg');
// src: url('@{fa-font-path}/FontAwesome.otf') format('opentype'); // used when developing fonts
font-weight: normal;
font-style: normal;
}
/* FONT PATH
* -------------------------- */
@font-face {
font-family: 'FontAwesome';
src: url('@{fa-font-path}/fontawesome-webfont.eot?v=@{fa-version}');
src: url('@{fa-font-path}/fontawesome-webfont.eot?#iefix&v=@{fa-version}') format('embedded-opentype'),
url('@{fa-font-path}/fontawesome-webfont.woff2?v=@{fa-version}') format('woff2'),
url('@{fa-font-path}/fontawesome-webfont.woff?v=@{fa-version}') format('woff'),
url('@{fa-font-path}/fontawesome-webfont.ttf?v=@{fa-version}') format('truetype'),
url('@{fa-font-path}/fontawesome-webfont.svg?v=@{fa-version}#fontawesomeregular') format('svg');
// src: url('@{fa-font-path}/FontAwesome.otf') format('opentype'); // used when developing fonts
font-weight: normal;
font-style: normal;
}

View File

@@ -1,20 +1,20 @@
// Rotated & Flipped Icons
// -------------------------
.@{fa-css-prefix}-rotate-90 { .fa-icon-rotate(90deg, 1); }
.@{fa-css-prefix}-rotate-180 { .fa-icon-rotate(180deg, 2); }
.@{fa-css-prefix}-rotate-270 { .fa-icon-rotate(270deg, 3); }
.@{fa-css-prefix}-flip-horizontal { .fa-icon-flip(-1, 1, 0); }
.@{fa-css-prefix}-flip-vertical { .fa-icon-flip(1, -1, 2); }
// Hook for IE8-9
// -------------------------
:root .@{fa-css-prefix}-rotate-90,
:root .@{fa-css-prefix}-rotate-180,
:root .@{fa-css-prefix}-rotate-270,
:root .@{fa-css-prefix}-flip-horizontal,
:root .@{fa-css-prefix}-flip-vertical {
filter: none;
}
// Rotated & Flipped Icons
// -------------------------
.@{fa-css-prefix}-rotate-90 { .fa-icon-rotate(90deg, 1); }
.@{fa-css-prefix}-rotate-180 { .fa-icon-rotate(180deg, 2); }
.@{fa-css-prefix}-rotate-270 { .fa-icon-rotate(270deg, 3); }
.@{fa-css-prefix}-flip-horizontal { .fa-icon-flip(-1, 1, 0); }
.@{fa-css-prefix}-flip-vertical { .fa-icon-flip(1, -1, 2); }
// Hook for IE8-9
// -------------------------
:root .@{fa-css-prefix}-rotate-90,
:root .@{fa-css-prefix}-rotate-180,
:root .@{fa-css-prefix}-rotate-270,
:root .@{fa-css-prefix}-flip-horizontal,
:root .@{fa-css-prefix}-flip-vertical {
filter: none;
}

View File

@@ -1,5 +1,5 @@
// Screen Readers
// -------------------------
.sr-only { .sr-only(); }
.sr-only-focusable { .sr-only-focusable(); }
// Screen Readers
// -------------------------
.sr-only { .sr-only(); }
.sr-only-focusable { .sr-only-focusable(); }

View File

@@ -1,20 +1,20 @@
// Stacked Icons
// -------------------------
.@{fa-css-prefix}-stack {
position: relative;
display: inline-block;
width: 2em;
height: 2em;
line-height: 2em;
vertical-align: middle;
}
.@{fa-css-prefix}-stack-1x, .@{fa-css-prefix}-stack-2x {
position: absolute;
left: 0;
width: 100%;
text-align: center;
}
.@{fa-css-prefix}-stack-1x { line-height: inherit; }
.@{fa-css-prefix}-stack-2x { font-size: 2em; }
.@{fa-css-prefix}-inverse { color: @fa-inverse; }
// Stacked Icons
// -------------------------
.@{fa-css-prefix}-stack {
position: relative;
display: inline-block;
width: 2em;
height: 2em;
line-height: 2em;
vertical-align: middle;
}
.@{fa-css-prefix}-stack-1x, .@{fa-css-prefix}-stack-2x {
position: absolute;
left: 0;
width: 100%;
text-align: center;
}
.@{fa-css-prefix}-stack-1x { line-height: inherit; }
.@{fa-css-prefix}-stack-2x { font-size: 2em; }
.@{fa-css-prefix}-inverse { color: @fa-inverse; }

File diff suppressed because it is too large Load Diff

View File

@@ -1,17 +1,17 @@
.alert-enter {
opacity: 0.01;
transition: opacity 1s ease-in;
}
.alert-enter.alert-enter-active {
opacity: 1;
}
.alert-leave {
opacity: 1;
transition: opacity 1s ease-in;
}
.alert-leave.alert-leave-active {
opacity: 0.01;
.alert-enter {
opacity: 0.01;
transition: opacity 1s ease-in;
}
.alert-enter.alert-enter-active {
opacity: 1;
}
.alert-leave {
opacity: 1;
transition: opacity 1s ease-in;
}
.alert-leave.alert-leave-active {
opacity: 0.01;
}

View File

@@ -1,14 +1,14 @@
::-webkit-scrollbar {
height: 12px;
width: 12px;
background: @body-bg;
}
::-webkit-scrollbar-thumb {
background: @brand-primary;
-webkit-border-radius: 0;
}
::-webkit-scrollbar-corner {
background: #000;
}
::-webkit-scrollbar {
height: 12px;
width: 12px;
background: @body-bg;
}
::-webkit-scrollbar-thumb {
background: @brand-primary;
-webkit-border-radius: 0;
}
::-webkit-scrollbar-corner {
background: #000;
}

View File

@@ -1,2 +1,2 @@
@navbar-margin-bottom: 0px;
@navbar-border-radius: 0px;
@navbar-margin-bottom: 0px;
@navbar-border-radius: 0px;

View File

@@ -1,14 +1,14 @@
{
"name": "WakaTime",
"ignore": [
"**/.*",
"node_modules",
"bower_components",
"test",
"tests"
],
"dependencies": {
"font-awesome": "~4.6.3",
"bootstrap": "~3.3.4"
}
}
{
"name": "WakaTime",
"ignore": [
"**/.*",
"node_modules",
"bower_components",
"test",
"tests"
],
"dependencies": {
"font-awesome": "~4.6.3",
"bootstrap": "~3.3.4"
}
}

View File

@@ -1,2 +1,2 @@
<!DOCTYPE html>
<script src="public/js/devtools.js"></script>
<!DOCTYPE html>
<script src="public/js/devtools.js"></script>

View File

@@ -1,43 +1,43 @@
var del = require('del');
var gulp = require('gulp');
var elixir = require('laravel-elixir');
/*
|--------------------------------------------------------------------------
| Pre-defined Gulp Tasks
|--------------------------------------------------------------------------
|
| Tasks outside the scope of Elixir can be predefined before setting it up.
|
*/
gulp.task('postinstall', function (cb) {
// .pem files cause Chrome to show a bunch of warnings
//so we remove them on postinstall
del('node_modules/**/*.pem', cb);
});
/*
|--------------------------------------------------------------------------
| Elixir Asset Management
|--------------------------------------------------------------------------
|
| Elixir provides a clean, fluent API for defining some basic Gulp tasks
| for your Laravel application. By default, we are compiling the Less
| file for our application, as well as publishing vendor resources.
|
*/
elixir.config.assetsPath = 'assets/';
elixir(function (mix) {
mix.copy('vendor/bower_components/bootstrap/less', 'assets/less/bootstrap');
mix.copy('vendor/bower_components/bootstrap/fonts', 'public/fonts');
mix.copy('vendor/bower_components/font-awesome/less', 'assets/less/font-awesome');
mix.copy('vendor/bower_components/font-awesome/fonts', 'public/fonts');
mix.less('app.less');
mix.browserify('app.jsx', 'public/js/app.js', 'assets/js');
mix.browserify('events.js', 'public/js/events.js', 'assets/js');
mix.browserify('options.jsx', 'public/js/options.js', 'assets/js');
mix.browserify('devtools.js', 'public/js/devtools.js', 'assets/js');
});
var del = require('del');
var gulp = require('gulp');
var elixir = require('laravel-elixir');
/*
|--------------------------------------------------------------------------
| Pre-defined Gulp Tasks
|--------------------------------------------------------------------------
|
| Tasks outside the scope of Elixir can be predefined before setting it up.
|
*/
gulp.task('postinstall', function (cb) {
// .pem files cause Chrome to show a bunch of warnings
//so we remove them on postinstall
del('node_modules/**/*.pem', cb);
});
/*
|--------------------------------------------------------------------------
| Elixir Asset Management
|--------------------------------------------------------------------------
|
| Elixir provides a clean, fluent API for defining some basic Gulp tasks
| for your Laravel application. By default, we are compiling the Less
| file for our application, as well as publishing vendor resources.
|
*/
elixir.config.assetsPath = 'assets/';
elixir(function (mix) {
mix.copy('vendor/bower_components/bootstrap/less', 'assets/less/bootstrap');
mix.copy('vendor/bower_components/bootstrap/fonts', 'public/fonts');
mix.copy('vendor/bower_components/font-awesome/less', 'assets/less/font-awesome');
mix.copy('vendor/bower_components/font-awesome/fonts', 'public/fonts');
mix.less('app.less');
mix.browserify('app.jsx', 'public/js/app.js', 'assets/js');
mix.browserify('events.js', 'public/js/events.js', 'assets/js');
mix.browserify('options.jsx', 'public/js/options.js', 'assets/js');
mix.browserify('devtools.js', 'public/js/devtools.js', 'assets/js');
});

View File

@@ -1,39 +1,44 @@
{
"manifest_version": 2,
"name": "WakaTime",
"version": "1.0.2",
"description": "Automatic time tracking for Chrome.",
"homepage_url": "https://wakatime.com",
"devtools_page": "devtools.html",
"icons": {
"16": "graphics/wakatime-logo-16.png",
"48": "graphics/wakatime-logo-48.png",
"128": "graphics/wakatime-logo-128.png"
},
"permissions": [
"https://api.wakatime.com/*",
"https://wakatime.com/*",
"alarms",
"tabs",
"storage",
"idle"
],
"background": {
"scripts": [
"public/js/events.js"
],
"persistent": false
},
"browser_action": {
"default_icon": {
"19": "graphics/wakatime-logo-19.png",
"38": "graphics/wakatime-logo-38.png"
},
"default_title": "WakaTime",
"default_popup": "popup.html"
},
"options_ui": {
"page": "options.html",
"chrome_style": false
}
}
{
"manifest_version": 2,
"name": "WakaTime",
"version": "1.0.2",
"description": "Automatic time tracking for Chrome.",
"homepage_url": "https://wakatime.com",
"devtools_page": "devtools.html",
"icons": {
"16": "graphics/wakatime-logo-16.png",
"48": "graphics/wakatime-logo-48.png",
"128": "graphics/wakatime-logo-128.png"
},
"permissions": [
"https://api.wakatime.com/*",
"https://wakatime.com/*",
"alarms",
"tabs",
"storage",
"idle"
],
"background": {
"scripts": [
"public/js/events.js"
],
"persistent": false
},
"browser_action": {
"default_icon": {
"19": "graphics/wakatime-logo-19.png",
"38": "graphics/wakatime-logo-38.png"
},
"default_title": "WakaTime",
"default_popup": "popup.html"
},
"options_ui": {
"page": "options.html",
"chrome_style": false
},
"applicaitons": {
"gecko": {
"id": "addon@wakatime.com"
}
}
}

View File

@@ -1,17 +1,17 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>WakaTime options</title>
<link href="public/css/app.css" rel="stylesheet">
</head>
<body>
<div id="wakatime-options"></div>
<script src="public/js/options.js"></script>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>WakaTime options</title>
<link href="public/css/app.css" rel="stylesheet">
</head>
<body>
<div id="wakatime-options"></div>
<script src="public/js/options.js"></script>
</body>
</html>

View File

@@ -1,67 +1,67 @@
{
"scripts": {
"test": "jest --verbose --coverage && mocha --compilers js:mocha-traceur tests/**/*.spec.js",
"test-react": "jest --verbose --coverage",
"test-js": "node_modules/.bin/phantomjs tests/run.js",
"start": "npm install && bower install && gulp",
"gulp": "gulp",
"watch": "gulp watch",
"lint": "jsxhint --jsx-only .",
"postinstall": "gulp postinstall",
"validate": "npm ls"
},
"pre-commit": [
"lint"
],
"jest": {
"testFileExtensions": [
"jest.js"
],
"scriptPreprocessor": "<rootDir>/node_modules/babel-jest",
"testDirectoryName": "tests",
"unmockedModulePathPatterns": [
"<rootDir>/node_modules/react"
]
},
"private": true,
"devDependencies": {
"babel-jest": "^13.0.0",
"bower": "^1.7.9",
"chai": "^3.5.0",
"del": "^2.2.1",
"gulp": "^3.9.1",
"jest-cli": "^13.0.0",
"jshint": "^2.9.2",
"jsxhint": "^0.15.1",
"laravel-elixir": "^5.0.0",
"mocha": "^2.5.3",
"mocha-sinon": "^1.1.5",
"mocha-traceur": "^2.1.0",
"precommit-hook": "^3.0.0",
"sinon": "^1.17.4",
"sinon-chai": "^2.8.0",
"sinon-chrome": "^1.1.2",
"traceur": "^0.0.111"
},
"dependencies": {
"bootstrap": "^3.3.6",
"classnames": "^2.2.5",
"jquery": "^3.0.0",
"moment": "^2.13.0",
"react": "^15.1.0",
"react-addons-css-transition-group": "^15.1.0",
"react-dom": "^15.1.0"
},
"jshintConfig": {
"asi": false,
"browser": true,
"curly": false,
"expr": true,
"indent": 4,
"loopfunc": true,
"node": true,
"trailing": true,
"undef": true,
"white": true
}
}
{
"scripts": {
"test": "jest --verbose --coverage && mocha --compilers js:mocha-traceur tests/**/*.spec.js",
"test-react": "jest --verbose --coverage",
"test-js": "node_modules/.bin/phantomjs tests/run.js",
"start": "npm install && bower install && gulp",
"gulp": "gulp",
"watch": "gulp watch",
"lint": "jsxhint --jsx-only .",
"postinstall": "gulp postinstall",
"validate": "npm ls"
},
"pre-commit": [
"lint"
],
"jest": {
"testFileExtensions": [
"jest.js"
],
"scriptPreprocessor": "<rootDir>/node_modules/babel-jest",
"testDirectoryName": "tests",
"unmockedModulePathPatterns": [
"<rootDir>/node_modules/react"
]
},
"private": true,
"devDependencies": {
"babel-jest": "^13.0.0",
"bower": "^1.7.9",
"chai": "^3.5.0",
"del": "^2.2.1",
"gulp": "^3.9.1",
"jest-cli": "^13.0.0",
"jshint": "^2.9.2",
"jsxhint": "^0.15.1",
"laravel-elixir": "^5.0.0",
"mocha": "^2.5.3",
"mocha-sinon": "^1.1.5",
"mocha-traceur": "^2.1.0",
"precommit-hook": "^3.0.0",
"sinon": "^1.17.4",
"sinon-chai": "^2.8.0",
"sinon-chrome": "^1.1.2",
"traceur": "^0.0.111"
},
"dependencies": {
"bootstrap": "^3.3.6",
"classnames": "^2.2.5",
"jquery": "^3.0.0",
"moment": "^2.13.0",
"react": "^15.1.0",
"react-addons-css-transition-group": "^15.1.0",
"react-dom": "^15.1.0"
},
"jshintConfig": {
"asi": false,
"browser": true,
"curly": false,
"expr": true,
"indent": 4,
"loopfunc": true,
"node": true,
"trailing": true,
"undef": true,
"white": true
}
}

View File

@@ -1,17 +1,17 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>WakaTime</title>
<link href="public/css/app.css" rel="stylesheet">
</head>
<body>
<div id="wakatime"></div>
<script src="public/js/app.js"></script>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>WakaTime</title>
<link href="public/css/app.css" rel="stylesheet">
</head>
<body>
<div id="wakatime"></div>
<script src="public/js/app.js"></script>
</body>
</html>

View File

@@ -1,40 +1,40 @@
var fs = require('fs');
var page;
var beforeLoadFn;
beforeEach(function() {
page = require('webpage').create();
page.onConsoleMessage = function(msg) { console.log(msg); };
page.onError = function(msg, trace) {
var msgStack = [msg];
if (trace && trace.length) {
msgStack.push('TRACE:');
trace.forEach(function(t) {
msgStack.push(' -> ' + t.file + ': ' + t.line + (t.function ? ' (in function "' + t.function +'")' : ''));
});
}
// we need try..catch here as mocha throws error that catched by phantom.onError
try {
mocha.throwError(msgStack.join('\n'));
} catch(e) { }
};
page.onInitialized = function() {
page.injectJs(node_modules + 'chai/chai.js');
page.injectJs(node_modules + 'sinon/pkg/sinon.js');
page.injectJs(node_modules + 'sinon-chrome/chrome.js');
page.injectJs(node_modules + 'sinon-chrome/src/phantom-tweaks.js');
page.injectJs(node_modules + 'require-stub/index.js');
// call additional function defined in tests
if (beforeLoadFn) {
beforeLoadFn();
}
};
});
afterEach(function() {
page.close();
beforeLoadFn = null;
var fs = require('fs');
var page;
var beforeLoadFn;
beforeEach(function() {
page = require('webpage').create();
page.onConsoleMessage = function(msg) { console.log(msg); };
page.onError = function(msg, trace) {
var msgStack = [msg];
if (trace && trace.length) {
msgStack.push('TRACE:');
trace.forEach(function(t) {
msgStack.push(' -> ' + t.file + ': ' + t.line + (t.function ? ' (in function "' + t.function +'")' : ''));
});
}
// we need try..catch here as mocha throws error that catched by phantom.onError
try {
mocha.throwError(msgStack.join('\n'));
} catch(e) { }
};
page.onInitialized = function() {
page.injectJs(node_modules + 'chai/chai.js');
page.injectJs(node_modules + 'sinon/pkg/sinon.js');
page.injectJs(node_modules + 'sinon-chrome/chrome.js');
page.injectJs(node_modules + 'sinon-chrome/src/phantom-tweaks.js');
page.injectJs(node_modules + 'require-stub/index.js');
// call additional function defined in tests
if (beforeLoadFn) {
beforeLoadFn();
}
};
});
afterEach(function() {
page.close();
beforeLoadFn = null;
});

View File

@@ -1,17 +1,17 @@
jest.dontMock('../../assets/js/components/Alert.jsx');
describe('Alert', function() {
var React, Alert, TestUtils, Component;
beforeEach(function() {
// Setup our tools
React = require('react/addons');
Alert = require('../../assets/js/components/Alert.jsx');
TestUtils = React.addons.TestUtils;
// Create the React component here using TestUtils and store into Component
});
it('should work', function() {
expect(2 + 2).toEqual(4);
});
jest.dontMock('../../assets/js/components/Alert.jsx');
describe('Alert', function() {
var React, Alert, TestUtils, Component;
beforeEach(function() {
// Setup our tools
React = require('react/addons');
Alert = require('../../assets/js/components/Alert.jsx');
TestUtils = React.addons.TestUtils;
// Create the React component here using TestUtils and store into Component
});
it('should work', function() {
expect(2 + 2).toEqual(4);
});
});

View File

@@ -1,17 +1,17 @@
jest.dontMock('../../assets/js/components/MainList.jsx');
describe('MainList', function() {
var React, MainList, TestUtils, Component;
beforeEach(function() {
// Setup our tools
React = require('react/addons');
MainList = require('../../assets/js/components/MainList.jsx');
TestUtils = React.addons.TestUtils;
// Create the React component here using TestUtils and store into Component
});
it('should work', function() {
expect(2 + 2).toEqual(4);
});
jest.dontMock('../../assets/js/components/MainList.jsx');
describe('MainList', function() {
var React, MainList, TestUtils, Component;
beforeEach(function() {
// Setup our tools
React = require('react/addons');
MainList = require('../../assets/js/components/MainList.jsx');
TestUtils = React.addons.TestUtils;
// Create the React component here using TestUtils and store into Component
});
it('should work', function() {
expect(2 + 2).toEqual(4);
});
});

View File

@@ -1,17 +1,17 @@
jest.dontMock('../../assets/js/components/Navbar.jsx');
describe('Navbar', function() {
var React, Navbar, TestUtils, Component;
beforeEach(function() {
// Setup our tools
React = require('react/addons');
Navbar = require('../../assets/js/components/Navbar.jsx');
TestUtils = React.addons.TestUtils;
// Create the React component here using TestUtils and store into Component
});
it('should work', function() {
expect(2 + 2).toEqual(4);
});
jest.dontMock('../../assets/js/components/Navbar.jsx');
describe('Navbar', function() {
var React, Navbar, TestUtils, Component;
beforeEach(function() {
// Setup our tools
React = require('react/addons');
Navbar = require('../../assets/js/components/Navbar.jsx');
TestUtils = React.addons.TestUtils;
// Create the React component here using TestUtils and store into Component
});
it('should work', function() {
expect(2 + 2).toEqual(4);
});
});

View File

@@ -1,17 +1,17 @@
jest.dontMock('../../assets/js/components/Options.jsx');
describe('Options', function() {
var React, Options, TestUtils, Component;
beforeEach(function() {
// Setup our tools
React = require('react/addons');
Options = require('../../assets/js/components/Options.jsx');
TestUtils = React.addons.TestUtils;
// Create the React component here using TestUtils and store into Component
});
it('should work', function() {
expect(2 + 2).toEqual(4);
});
jest.dontMock('../../assets/js/components/Options.jsx');
describe('Options', function() {
var React, Options, TestUtils, Component;
beforeEach(function() {
// Setup our tools
React = require('react/addons');
Options = require('../../assets/js/components/Options.jsx');
TestUtils = React.addons.TestUtils;
// Create the React component here using TestUtils and store into Component
});
it('should work', function() {
expect(2 + 2).toEqual(4);
});
});

View File

@@ -1,17 +1,17 @@
jest.dontMock('../../assets/js/components/SitesList.jsx');
describe('SitesList', function() {
var React, SitesList, TestUtils, Component;
beforeEach(function() {
// Setup our tools
React = require('react/addons');
SitesList = require('../../assets/js/components/SitesList.jsx');
TestUtils = React.addons.TestUtils;
// Create the React component here using TestUtils and store into Component
});
it('should work', function() {
expect(2 + 2).toEqual(4);
});
jest.dontMock('../../assets/js/components/SitesList.jsx');
describe('SitesList', function() {
var React, SitesList, TestUtils, Component;
beforeEach(function() {
// Setup our tools
React = require('react/addons');
SitesList = require('../../assets/js/components/SitesList.jsx');
TestUtils = React.addons.TestUtils;
// Create the React component here using TestUtils and store into Component
});
it('should work', function() {
expect(2 + 2).toEqual(4);
});
});

View File

@@ -1,17 +1,17 @@
jest.dontMock('../../assets/js/components/Wakatime.jsx');
describe('Wakatime', function() {
var React, Wakatime, TestUtils, Component;
beforeEach(function() {
// Setup our tools
React = require('react/addons');
Wakatime = require('../../assets/js/components/Wakatime.jsx');
TestUtils = React.addons.TestUtils;
// Create the React component here using TestUtils and store into Component
});
it('should work', function() {
expect(2 + 2).toEqual(4);
});
jest.dontMock('../../assets/js/components/Wakatime.jsx');
describe('Wakatime', function() {
var React, Wakatime, TestUtils, Component;
beforeEach(function() {
// Setup our tools
React = require('react/addons');
Wakatime = require('../../assets/js/components/Wakatime.jsx');
TestUtils = React.addons.TestUtils;
// Create the React component here using TestUtils and store into Component
});
it('should work', function() {
expect(2 + 2).toEqual(4);
});
});

View File

@@ -1,10 +1,10 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title></title>
</head>
<body>
</body>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title></title>
</head>
<body>
</body>
</html>

View File

@@ -1,11 +1,11 @@
var chai = require('chai');
var sinon = require('sinon');
var chrome = require('sinon-chrome');
var expect = chai.expect;
describe('Chrome Dev Tools', function() {
it('should work', function() {
chrome.browserAction.setTitle({title: 'hello'});
sinon.assert.calledOnce(chrome.browserAction.setTitle);
});
});
var chai = require('chai');
var sinon = require('sinon');
var chrome = require('sinon-chrome');
var expect = chai.expect;
describe('Chrome Dev Tools', function() {
it('should work', function() {
chrome.browserAction.setTitle({title: 'hello'});
sinon.assert.calledOnce(chrome.browserAction.setTitle);
});
});

View File

@@ -1,10 +1,10 @@
var chai = require('chai');
var expect = chai.expect;
import changeExtensionIcon from '../../assets/js/helpers/changeExtensionIcon';
describe('changeExtensionIcon', function() {
it('should be a function', function() {
expect(changeExtensionIcon).to.be.a('function');
});
var chai = require('chai');
var expect = chai.expect;
import changeExtensionIcon from '../../assets/js/helpers/changeExtensionIcon';
describe('changeExtensionIcon', function() {
it('should be a function', function() {
expect(changeExtensionIcon).to.be.a('function');
});
});

View File

@@ -1,10 +1,10 @@
var chai = require('chai');
var expect = chai.expect;
import changeExtensionState from '../../assets/js/helpers/changeExtensionState';
describe('changeExtensionState', function() {
it('should be a function', function() {
expect(changeExtensionState).to.be.a('function');
});
var chai = require('chai');
var expect = chai.expect;
import changeExtensionState from '../../assets/js/helpers/changeExtensionState';
describe('changeExtensionState', function() {
it('should be a function', function() {
expect(changeExtensionState).to.be.a('function');
});
});

View File

@@ -1,18 +1,18 @@
var chai = require('chai');
var sinon = require('sinon-chai');
var chrome = require('sinon-chrome');
var expect = chai.expect;
import changeExtensionTooltip from '../../assets/js/helpers/changeExtensionTooltip';
describe('changeExtensionTooltip', function() {
it('should be a function', function() {
expect(changeExtensionTooltip).to.be.a('function');
});
// it('should change the extension tooltip', function() {
// changeExtensionTooltip('WakaTime');
// expect(chrome.browserAction.setTitle).toHaveBeenCalledWith({title: 'Wakatime'});
// sinon.assert.calledWithMatch(chrome.browserAction.setTitle, {title: 'WakaTime'});
// });
});
var chai = require('chai');
var sinon = require('sinon-chai');
var chrome = require('sinon-chrome');
var expect = chai.expect;
import changeExtensionTooltip from '../../assets/js/helpers/changeExtensionTooltip';
describe('changeExtensionTooltip', function() {
it('should be a function', function() {
expect(changeExtensionTooltip).to.be.a('function');
});
// it('should change the extension tooltip', function() {
// changeExtensionTooltip('WakaTime');
// expect(chrome.browserAction.setTitle).toHaveBeenCalledWith({title: 'Wakatime'});
// sinon.assert.calledWithMatch(chrome.browserAction.setTitle, {title: 'WakaTime'});
// });
});

View File

@@ -1,26 +1,26 @@
var chai = require('chai');
var expect = chai.expect;
import contains from '../../assets/js/helpers/contains';
describe('contains', function() {
it('should be a function', function() {
expect(contains).to.be.a('function');
});
it('should match url against blacklist and return true', function() {
var list = "localhost\ntest.com";
var url = 'http://localhost/fooapp';
expect(contains(url, list)).to.equal(true);
});
it('should not match url against blacklist and return false', function() {
var list = "localhost2\ntest.com";
var url = 'http://localhost/fooapp';
expect(contains(url, list)).to.equal(false);
});
});
var chai = require('chai');
var expect = chai.expect;
import contains from '../../assets/js/helpers/contains';
describe('contains', function() {
it('should be a function', function() {
expect(contains).to.be.a('function');
});
it('should match url against blacklist and return true', function() {
var list = "localhost\ntest.com";
var url = 'http://localhost/fooapp';
expect(contains(url, list)).to.equal(true);
});
it('should not match url against blacklist and return false', function() {
var list = "localhost2\ntest.com";
var url = 'http://localhost/fooapp';
expect(contains(url, list)).to.equal(false);
});
});

View File

@@ -1,19 +1,19 @@
var chai = require('chai');
var expect = chai.expect;
import getDomainFromUrl from '../../assets/js/helpers/getDomainFromUrl';
describe('getDomainFromUrl', function() {
it('should be a function', function() {
expect(getDomainFromUrl).to.be.a('function');
});
it('should return the domain', function() {
expect(getDomainFromUrl('http://google.com/something/very/secret')).to.equal('http://google.com');
expect(getDomainFromUrl('http://www.google.com/something/very/secret')).to.equal('http://www.google.com');
// This is not how it was imaged to work, but let's leave it here as a warning.
expect(getDomainFromUrl('google.com/something/very/secret')).to.equal('google.com//very');
});
var chai = require('chai');
var expect = chai.expect;
import getDomainFromUrl from '../../assets/js/helpers/getDomainFromUrl';
describe('getDomainFromUrl', function() {
it('should be a function', function() {
expect(getDomainFromUrl).to.be.a('function');
});
it('should return the domain', function() {
expect(getDomainFromUrl('http://google.com/something/very/secret')).to.equal('http://google.com');
expect(getDomainFromUrl('http://www.google.com/something/very/secret')).to.equal('http://www.google.com');
// This is not how it was imaged to work, but let's leave it here as a warning.
expect(getDomainFromUrl('google.com/something/very/secret')).to.equal('google.com//very');
});
});

View File

@@ -1,18 +1,18 @@
var chai = require('chai');
var expect = chai.expect;
import in_array from '../../assets/js/helpers/in_array';
describe('in_array', function() {
it('should be a function', function() {
expect(in_array).to.be.a('function');
});
it('should find the needle and return true', function() {
expect(in_array('4', ['4', '3', '2', '1'])).to.equal(true);
});
it('should not find the needle and it should return false', function() {
expect(in_array('5', ['4', '3', '2', '1'])).to.equal(false);
});
var chai = require('chai');
var expect = chai.expect;
import in_array from '../../assets/js/helpers/in_array';
describe('in_array', function() {
it('should be a function', function() {
expect(in_array).to.be.a('function');
});
it('should find the needle and return true', function() {
expect(in_array('4', ['4', '3', '2', '1'])).to.equal(true);
});
it('should not find the needle and it should return false', function() {
expect(in_array('5', ['4', '3', '2', '1'])).to.equal(false);
});
});

View File

@@ -1,24 +1,24 @@
/**
* Test Runner for Mocha tests
* Using phantomjs to render page and execute scripts
*/
var node_modules = '../node_modules/';
phantom.injectJs(node_modules + 'mocha/mocha.js');
phantom.injectJs(node_modules + 'sinon-chrome/src/phantom-tweaks.js');
mocha.setup({ui: 'bdd', reporter: 'spec'});
// Setup
phantom.injectJs('beforeeach.js');
// Tests
phantom.injectJs('helpers/changeExtensionTooltip.spec.js');
// Execute
mocha.run(function(failures) {
// setTimeout is needed to supress "Unsafe JavaScript attempt to access..."
// see https://github.com/ariya/phantomjs/issues/12697
setTimeout(function() {
phantom.exit(failures);
}, 0);
/**
* Test Runner for Mocha tests
* Using phantomjs to render page and execute scripts
*/
var node_modules = '../node_modules/';
phantom.injectJs(node_modules + 'mocha/mocha.js');
phantom.injectJs(node_modules + 'sinon-chrome/src/phantom-tweaks.js');
mocha.setup({ui: 'bdd', reporter: 'spec'});
// Setup
phantom.injectJs('beforeeach.js');
// Tests
phantom.injectJs('helpers/changeExtensionTooltip.spec.js');
// Execute
mocha.run(function(failures) {
// setTimeout is needed to supress "Unsafe JavaScript attempt to access..."
// see https://github.com/ariya/phantomjs/issues/12697
setTimeout(function() {
phantom.exit(failures);
}, 0);
});