<commit_before>'use strict';

/**
* @ngdoc object
*
* @name stormpath.STORMPATH_CONFIG:STORMPATH_CONFIG
*
* @description
*
* This constant allows you to configure the internal settings of the module,
* such as authentication endpoints and the names of events. These properties
* must be modified within a config block.
*
* **Example:**
*

*     angular.module('myapp')
*       .config(function(STORMPATH_CONFIG){
*           STORMPATH_CONFIG.ENDPOINT_PREFIX = 'http://api.mydomain.com';
*       });
*

*/

angular.module('stormpath.CONFIG',[])
.constant('STORMPATH_CONFIG',(function stormpathConfigBuilder(){
  var c={
    /**
    * @ngdoc property
    *
    * @name AUTHENTICATION_SUCCESS_EVENT_NAME
    *
    * @propertyOf stormpath.STORMPATH_CONFIG:STORMPATH_CONFIG
    *
    * @description
    *
    * Default: `$authenticated`
    *
    * The name of the event that is fired when a user logs in, after
    * successfully submitting the login form.
    *
    */
    AUTHENTICATION_SUCCESS_EVENT_NAME: '$authenticated',

    /**
    * @ngdoc property
    *
    * @name AUTHENTICATION_FAILURE_EVENT_NAME
    *
    * @propertyOf stormpath.STORMPATH_CONFIG:STORMPATH_CONFIG
    *
    * @description
    *
    * Default: `$authenticationFailure`
    *
    * The name of the event that is fired when the user posts
    * invalid login credentials to the login form.
    */
    AUTHENTICATION_FAILURE_EVENT_NAME: '$authenticationFailure',

    /**
    * @ngdoc property
    *
    * @name AUTH_SERVICE_NAME
    *
    * @propertyOf stormpath.STORMPATH_CONFIG:STORMPATH_CONFIG
    *
    * @description
    *
    * Default: `$auth`
    *
    * The name of the authentication service, this changes the
    * service name that you inject.
    */
    AUTH_SERVICE_NAME: '$auth',

    /**
     * @ngdoc property
     *
     * @name AUTO_AUTHORIZED_URIS
     *
     * @propertyOf stormpath.STORMPATH_CONFIG:STORMPATH_CONFIG
     *
     * @description
     *
     * Default: `['/me$']`
     *
     * The list of URLs to match when making requests.  If an OAuth strategy is
     * being used and the request matches this URL, this library will automatically
     * add the `Authorization: Bearer <token>` header to the request.
    */
    AUTO_AUTHORIZED_URIS: ['/me$'],

    /**
    * @ngdoc property
    *
    * @name SOCIAL_LOGIN_SERVICE_NAME
    *
    * @propertyOf stormpath.STORMPATH_CONFIG:STORMPATH_CONFIG
    *
    * @description
    *
    * Default: `$socialLogin`
    *
    * The name of the social login service, this changes the
    * service name that you inject.
    */
    SOCIAL_LOGIN_SERVICE_NAME: '$socialLogin',

    /**
    * @ngdoc property
    *
    * @name SOCIAL_LOGIN_RESPONSE_TYPE
    *
    * The response type requested from the Stormpath Social Login v2 API.
    * Determines the type of token that will be returned for OAuth authentication
    * against the Stormpath OAuth API, when making social login attempts.
    */
    SOCIAL_LOGIN_RESPONSE_TYPE: 'stormpath_token',

    /**
    * @ngdoc property
    *
    * @name SOCIAL_LOGIN_OPTIONS
    *
    * Options that are set for a given social authentication provider when
    * making a social authentication request. These are appended to the request
    * as query parameters, and will override any default options, or options
    * set in the Stormpath admin console.
    *
    * The settings are mapped by providerId of a directory (e.g. `google`, `facebook`).
    *
    * Additional providers may be added to the object, but must all be in lowercase.
    */
    SOCIAL_LOGIN_OPTIONS: {
      google: {},
      facebook: {},
      twitter: {},
      linkedin: {}
    },

    /**
    * @ngdoc property
    *
    * @name SOCIAL_LOGIN_REDIRECT_URI
    *
    * The URI that the social login flow will redirect to after a login attempt.
    * This URI is relative to the base application URI.
    */
    SOCIAL_LOGIN_REDIRECT_URI: '',

    /**
    * @ngdoc property
    *
    * @name SOCIAL_LOGIN_AUTHORIZE_URI
    *
    * The relative URI of the endpoint used for social auth.
    * Should not be changed if Client API is used.
    */
    SOCIAL_LOGIN_AUTHORIZE_ENDPOINT: '/authorize',

    /**
    * @ngdoc property
    *
    * @name AUTHENTICATION_ENDPOINT
    *
    * @propertyOf stormpath.STORMPATH_CONFIG:STORMPATH_CONFIG
    *
    * @description
    *
    * Default: `/login`
    *
    * The URI that the login form will post to.  The endpoint MUST accept data
    * in the following format:
    *
    * ```
    * {
    *     username: '',
    *     password: ''
    * }
    * ```
    */
    AUTHENTICATION_ENDPOINT: '/login',

    /**
    * @ngdoc property
    *
    * @name CURRENT_USER_URI
    *
    * @propertyOf stormpath.STORMPATH_CONFIG:STORMPATH_CONFIG
    *
    * @description
    *
    * Default: `/me`
    *
    * The URI that is used to fetch the account object of
    * the currently logged in user.  This endpoint MUST:
    *  * Respond with a JSON object that is the Stormpath account object,
    *  if the user has an active session.
    *  * Respond with `401 Unauthorized` if the user has no session.
    */
    CURRENT_USER_URI: '/me',

    /**
    * @ngdoc property
    *
    * @name DESTROY_SESSION_ENDPOINT
    *
    * @propertyOf stormpath.STORMPATH_CONFIG:STORMPATH_CONFIG
    *
    * @description
    *
    * Default: `/logout`
    *
    * The URL that the {@link stormpath.spLogout:spLogout spLogout} directive
    * will make a GET request to, this endpoint MUST delete the access token
    * cookie, XSRF token cookie, and any other cookies that relate to the user
    * session.
    */
    DESTROY_SESSION_ENDPOINT: '/logout',

    /**
    * @ngdoc property
    *
    * @name EMAIL_VERIFICATION_ENDPOINT
    *
    * @propertyOf stormpath.STORMPATH_CONFIG:STORMPATH_CONFIG
    *
    * @description
    *
    * Default: `/verify`
    *
    * The endpoint that is used for verifying an account that requires email
    * verification.  Used by
    * {@link stormpath.userService.$user#methods_verify $user.verify()} to POST
    * the `sptoken` that was delivered to the user by email.
    *
    * This endpoint MUST accept a POST request with the following format and
    * use Stormpath to verify the token:
    * ```
    * {
    *   sptoken: '<token from email sent to user>'
    * }
    * ```
    *
    */
    EMAIL_VERIFICATION_ENDPOINT: '/verify',

    /**
    * @ngdoc property
    *
    * @name ENDPOINT_PREFIX
    *
    * @propertyOf stormpath.STORMPATH_CONFIG:STORMPATH_CONFIG
    *
    * @description
    *
    * Default: *none*
    *
    * A prefix, e.g. "base URL" to add to all endpoints that are used by this SDK.
    * Use this if your backend API is running on a different port or domain than
    * your Angular application.  Omit the trailing forward slash.
    *
    * **NOTE:** This may trigger
    * [CORS](https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS)
    * behaviour in the browser, and your server
    * will need to respond to requests accordingly.  If you are using our
    * Express SDK see
    * [allowedOrigins](https://github.com/stormpath/stormpath-sdk-express#allowedOrigins)
    *
    * **Example:**
    *

    *   ENDPOINT_PREFIX = 'http://api.mydomain.com'
    *

    */
    ENDPOINT_PREFIX: '',

    /**
    * @ngdoc property
    *
    * @name GET_USER_EVENT
    *
    * @propertyOf stormpath.STORMPATH_CONFIG:STORMPATH_CONFIG
    *
    * @description
    *
    * Default: `$currentUser`
    *
    * The name of the event that is fired when
    * {@link stormpath.userService.$user#methods_get $user.get()}
    * is resolved with a user object.
    */
    GET_USER_EVENT: '$currentUser',

    /**
    * @ngdoc property
    *
    * @name NOT_LOGGED_IN_EVENT
    *
    * @propertyOf stormpath.STORMPATH_CONFIG:STORMPATH_CONFIG
    *
    * @description
    *
    * Default: `$notLoggedin`
    *
    * The name of the event that is fired when
    * {@link stormpath.userService.$user#methods_get $user.get()}
    * is rejected without a user.
    */
    NOT_LOGGED_IN_EVENT: '$notLoggedin',

    /**
    * @ngdoc property
    *
    * @name FORGOT_PASSWORD_ENDPOINT
    *
    * @propertyOf stormpath.STORMPATH_CONFIG:STORMPATH_CONFIG
    *
    * @description
    *
    * Default: `/forgot`
    *
    * The endpoint that is used by
    * {@link stormpath.userService.$user#methods_passwordResetRequest $user.passwordResetRequest()}
    * to create password reset tokens.
    */
    FORGOT_PASSWORD_ENDPOINT: '/forgot',

    /**
    * @ngdoc property
    *
    * @name CHANGE_PASSWORD_ENDPOINT
    *
    * @propertyOf stormpath.STORMPATH_CONFIG:STORMPATH_CONFIG
    *
    * @description
    *
    * Default: `/change`
    *
    * The endpoint that is used by
    * {@link stormpath.userService.$user#methods_verifyPasswordResetToken $user.verifyPasswordResetToken()} and
    * {@link stormpath.userService.$user#methods_resetPassword $user.resetPassword()}
    * to verify and consume password reset tokens (change a user's password with the token).
    */
    CHANGE_PASSWORD_ENDPOINT: '/change',

    /**
    * @ngdoc property
    *
    * @name SESSION_END_EVENT
    *
    * @propertyOf stormpath.STORMPATH_CONFIG:STORMPATH_CONFIG
    *
    * @description
    *
    * Default: `$sessionEnd`
    *
    * The name of the event that is fired when the user logs out via the
    * {@link stormpath.spLogout:spLogout spLogout}
    * directive
    */
    SESSION_END_EVENT: '$sessionEnd',

    SESSION_END_ERROR_EVENT: '$sessionEndError',

    /**
    * @ngdoc property
    *
    * @name STATE_CHANGE_UNAUTHENTICATED
    *
    * @propertyOf stormpath.STORMPATH_CONFIG:STORMPATH_CONFIG
    *
    * @description
    *
    * Default: `$stateChangeUnauthenticated`
    *
    * The name of the event that is fired when the user attempts to visit a
    * UI Router state that requires authentication, but the user is not
    * authenticated.
    */
    STATE_CHANGE_UNAUTHENTICATED: '$stateChangeUnauthenticated',

    /**
    * @ngdoc property
    *
    * @name STATE_CHANGE_UNAUTHORIZED
    *
    * @propertyOf stormpath.STORMPATH_CONFIG:STORMPATH_CONFIG
    *
    * @description
    *
    * Default: `$stateChangeUnauthorized`
    *
    * The name of the event that is fired when the user attempts to visit a
    * UI Router state that has an access control rule which the user does not
    * meet (such as not being in a specified group)
    */
    STATE_CHANGE_UNAUTHORIZED: '$stateChangeUnauthorized',

    /**
    * @ngdoc property
    *
    * @name ROUTE_CHANGE_UNAUTHENTICATED
    *
    * @propertyOf stormpath.STORMPATH_CONFIG:STORMPATH_CONFIG
    *
    * @description
    *
    * Default: `$routeChangeUnauthenticated`
    *
    * The name of the event that is fired when the user attempts to visit a
    * route that requires authentication, but the user is not
    * authenticated.
    */
    ROUTE_CHANGE_UNAUTHENTICATED: '$routeChangeUnauthenticated',

    /**
    * @ngdoc property
    *
    * @name ROUTE_CHANGE_UNAUTHORIZED
    *
    * @propertyOf stormpath.STORMPATH_CONFIG:STORMPATH_CONFIG
    *
    * @description
    *
    * Default: `$routeChangeUnauthorized`
    *
    * The name of the event that is fired when the user attempts to visit a
    * route that has an access control rule which the user does not
    * meet (such as not being in a specified group)
    */
    ROUTE_CHANGE_UNAUTHORIZED: '$routeChangeUnauthorized',

    /**
    * @ngdoc property
    *
    * @name OAUTH_REQUEST_ERROR
    *
    * @propertyOf stormpath.STORMPATH_CONFIG:STORMPATH_CONFIG
    *
    * @description
    *
    * Default: `$oAuthRequestError`
    *
    * The name of the event that is fired when the user attempts OAuth-based
    * authentication, and fails due to an OAuth issue.
    */
    OAUTH_REQUEST_ERROR: '$oAuthRequestError',

    /**
    * @ngdoc property
    *
    * @name OAUTH_AUTHENTICATION_ENDPOINT
    *
    * @propertyOf stormpath.STORMPATH_CONFIG:STORMPATH_CONFIG
    *
    * @description
    *
    * Default: `/oauth/token`
    *
    * The endpoint that is used to authenticate and refresh using OAuth tokens.
    * This endpoint MUST support password and refresh_token grant authentication
    * flows.
    */
    OAUTH_AUTHENTICATION_ENDPOINT: '/oauth/token',

    /**
    * @ngdoc property
    * @name OAUTH_REVOKE_ENDPOINT
    *
    * @propertyOf stormpath.STORMPATH_CONFIG:STORMPATH_CONFIG
    *
    * @description
    *
    * Default: `/oauth/revoke`
    *
    * The endpoint that is used to revoke OAuth tokens.
    */
    OAUTH_REVOKE_ENDPOINT: '/oauth/revoke',

    /**
    * @ngdoc property
    * @name OAUTH_REVOKE_ENDPOINT
    *
    * @propertyOf stormpath.STORMPATH_CONFIG:STORMPATH_CONFIG
    *
    * @description
    *
    * The name under which tokens are stored in the token storage mechanism.
    * Might not be relevant if the underlying storage mechanism is not key-value
    * based.
    *
    * See {@link stormpath.tokenStore.TokenStoreManager TokenStoreManager} for more detail.
    */
    OAUTH_TOKEN_STORAGE_NAME: 'stormpath:token',

    /**
    * @ngdoc property
    *
    * @name REGISTER_URI
    *
    * @propertyOf stormpath.STORMPATH_CONFIG:STORMPATH_CONFIG
    *
    * @description
    *
    * Default: `/register`
    *
    * The endpoint that is used by
    * {@link stormpath.userService.$user#methods_create $user.create()}
    * to POST new users.  This endpoint MUST accept a stormpath account object
    * and use Stormpath to create the new user.
    */
    REGISTER_URI: '/register',

    /**
    * @ngdoc property
    *
    * @name REGISTERED_EVENT_NAME
    *
    * @propertyOf stormpath.STORMPATH_CONFIG:STORMPATH_CONFIG
    *
    * @description
    *
    * Default: `$registered`
    *
    * The name of the event that is fired when
    * {@link stormpath.userService.$user#methods_create $user.create()}
    * is resolved with an account that was successfully created
    */
    REGISTERED_EVENT_NAME: '$registered',

    /**
    * @ngdoc property
    *
    * @name OAUTH_DEFAULT_TOKEN_STORE_TYPE
    *
    * @propertyOf stormpath.STORMPATH_CONFIG:STORMPATH_CONFIG
    *
    * @description
    *
    * Default: `localStorage`
    *
    * The default type of local storage used by
    * {@link stormpath.oauth.StormpathOAuthTokenProvider}.
    */
    OAUTH_DEFAULT_TOKEN_STORE_TYPE: 'localStorage'

  };

  c.getUrl = function(key) {
    return this.ENDPOINT_PREFIX + this[key];
  };

  c.getSocialLoginConfiguration = function(key) {
    var canonicalKey = key ? key.toLowerCase() : '';
    return this.SOCIAL_LOGIN_OPTIONS[canonicalKey] || {};
  };
  return c;
})());
<commit_msg>Fixing ngdocs issues so that ngdocs can build<commit_after>'use strict';

/**
* @ngdoc object
*
* @name stormpath.STORMPATH_CONFIG:STORMPATH_CONFIG
*
* @description
*
* This constant allows you to configure the internal settings of the module,
* such as authentication endpoints and the names of events. These properties
* must be modified within a config block.
*
* **Example:**
*

*     angular.module('myapp')
*       .config(function(STORMPATH_CONFIG){
*           STORMPATH_CONFIG.ENDPOINT_PREFIX = 'http://api.mydomain.com';
*       });
*

*/

angular.module('stormpath.CONFIG',[])
.constant('STORMPATH_CONFIG',(function stormpathConfigBuilder(){
  var c={
    /**
    * @ngdoc property
    *
    * @name AUTHENTICATION_SUCCESS_EVENT_NAME
    *
    * @propertyOf stormpath.STORMPATH_CONFIG:STORMPATH_CONFIG
    *
    * @description
    *
    * Default: `$authenticated`
    *
    * The name of the event that is fired when a user logs in, after
    * successfully submitting the login form.
    *
    */
    AUTHENTICATION_SUCCESS_EVENT_NAME: '$authenticated',

    /**
    * @ngdoc property
    *
    * @name AUTHENTICATION_FAILURE_EVENT_NAME
    *
    * @propertyOf stormpath.STORMPATH_CONFIG:STORMPATH_CONFIG
    *
    * @description
    *
    * Default: `$authenticationFailure`
    *
    * The name of the event that is fired when the user posts
    * invalid login credentials to the login form.
    */
    AUTHENTICATION_FAILURE_EVENT_NAME: '$authenticationFailure',

    /**
     * @ngdoc property
     *
     * @name AUTH_SERVICE_NAME
     *
     * @propertyOf stormpath.STORMPATH_CONFIG:STORMPATH_CONFIG
     *
     * @description
     *
     * Default: `$auth`
     *
     * The name of the authentication service, this changes the
     * service name that you inject.
    */
    AUTH_SERVICE_NAME: '$auth',

    /**
     * @ngdoc property
     *
     * @name AUTO_AUTHORIZED_URIS
     *
     * @propertyOf stormpath.STORMPATH_CONFIG:STORMPATH_CONFIG
     *
     * @description
     *
     * Default: `['/me$']`
     *
     * The list of URLs to match when making requests.  If an OAuth strategy is
     * being used and the request matches this URL, this library will automatically
     * add the `Authorization: Bearer <token>` header to the request.
    */
    AUTO_AUTHORIZED_URIS: ['/me$'],

    /**
     * @ngdoc property
     *
     * @name SOCIAL_LOGIN_SERVICE_NAME
     *
     * @propertyOf stormpath.STORMPATH_CONFIG:STORMPATH_CONFIG
     *
     * @description
     *
     * Default: `$socialLogin`
     *
     * The name of the social login service, this changes the
     * service name that you inject.
    */
    SOCIAL_LOGIN_SERVICE_NAME: '$socialLogin',

    /**
     * @ngdoc property
     *
     * @name SOCIAL_LOGIN_RESPONSE_TYPE
     *
     * @propertyOf stormpath.STORMPATH_CONFIG:STORMPATH_CONFIG
     *
     * @description
     *
     * The response type requested from the Stormpath Social Login v2 API.
     * Determines the type of token that will be returned for OAuth authentication
     * against the Stormpath OAuth API, when making social login attempts.
    */
    SOCIAL_LOGIN_RESPONSE_TYPE: 'stormpath_token',

    /**
     * @ngdoc property
     *
     * @name SOCIAL_LOGIN_OPTIONS
     *
     * @propertyOf stormpath.STORMPATH_CONFIG:STORMPATH_CONFIG
     *
     * @description
     *
     * Options that are set for a given social authentication provider when
     * making a social authentication request. These are appended to the request
     * as query parameters, and will override any default options, or options
     * set in the Stormpath admin console.
     *
     * The settings are mapped by providerId of a directory (e.g. `google`, `facebook`).
     *
     * Additional providers may be added to the object, but must all be in lowercase.
    */
    SOCIAL_LOGIN_OPTIONS: {
      google: {},
      facebook: {},
      twitter: {},
      linkedin: {}
    },

    /**
     * @ngdoc property
     *
     * @name SOCIAL_LOGIN_REDIRECT_URI
     *
     * @propertyOf stormpath.STORMPATH_CONFIG:STORMPATH_CONFIG
     *
     * @description
     *
     * The URI that the social login flow will redirect to after a login attempt.
     * This URI is relative to the base application URI.
    */
    SOCIAL_LOGIN_REDIRECT_URI: '',

    /**
     * @ngdoc property
     *
     * @name SOCIAL_LOGIN_AUTHORIZE_URI
     *
     * @propertyOf stormpath.STORMPATH_CONFIG:STORMPATH_CONFIG
     *
     * @description
     *
     * The relative URI of the endpoint used for social auth.
     * Should not be changed if Client API is used.
    */
    SOCIAL_LOGIN_AUTHORIZE_ENDPOINT: '/authorize',

    /**
    * @ngdoc property
    *
    * @name AUTHENTICATION_ENDPOINT
    *
    * @propertyOf stormpath.STORMPATH_CONFIG:STORMPATH_CONFIG
    *
    * @description
    *
    * Default: `/login`
    *
    * The URI that the login form will post to.  The endpoint MUST accept data
    * in the following format:
    *
    * ```
    * {
    *     username: '',
    *     password: ''
    * }
    * ```
    */
    AUTHENTICATION_ENDPOINT: '/login',

    /**
    * @ngdoc property
    *
    * @name CURRENT_USER_URI
    *
    * @propertyOf stormpath.STORMPATH_CONFIG:STORMPATH_CONFIG
    *
    * @description
    *
    * Default: `/me`
    *
    * The URI that is used to fetch the account object of
    * the currently logged in user.  This endpoint MUST:
    *  * Respond with a JSON object that is the Stormpath account object,
    *  if the user has an active session.
    *  * Respond with `401 Unauthorized` if the user has no session.
    */
    CURRENT_USER_URI: '/me',

    /**
    * @ngdoc property
    *
    * @name DESTROY_SESSION_ENDPOINT
    *
    * @propertyOf stormpath.STORMPATH_CONFIG:STORMPATH_CONFIG
    *
    * @description
    *
    * Default: `/logout`
    *
    * The URL that the {@link stormpath.spLogout:spLogout spLogout} directive
    * will make a GET request to, this endpoint MUST delete the access token
    * cookie, XSRF token cookie, and any other cookies that relate to the user
    * session.
    */
    DESTROY_SESSION_ENDPOINT: '/logout',

    /**
    * @ngdoc property
    *
    * @name EMAIL_VERIFICATION_ENDPOINT
    *
    * @propertyOf stormpath.STORMPATH_CONFIG:STORMPATH_CONFIG
    *
    * @description
    *
    * Default: `/verify`
    *
    * The endpoint that is used for verifying an account that requires email
    * verification.  Used by
    * {@link stormpath.userService.$user#methods_verify $user.verify()} to POST
    * the `sptoken` that was delivered to the user by email.
    *
    * This endpoint MUST accept a POST request with the following format and
    * use Stormpath to verify the token:
    * ```
    * {
    *   sptoken: '<token from email sent to user>'
    * }
    * ```
    *
    */
    EMAIL_VERIFICATION_ENDPOINT: '/verify',

    /**
    * @ngdoc property
    *
    * @name ENDPOINT_PREFIX
    *
    * @propertyOf stormpath.STORMPATH_CONFIG:STORMPATH_CONFIG
    *
    * @description
    *
    * Default: *none*
    *
    * A prefix, e.g. "base URL" to add to all endpoints that are used by this SDK.
    * Use this if your backend API is running on a different port or domain than
    * your Angular application.  Omit the trailing forward slash.
    *
    * **NOTE:** This may trigger
    * [CORS](https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS)
    * behaviour in the browser, and your server
    * will need to respond to requests accordingly.  If you are using our
    * Express SDK see
    * [allowedOrigins](https://github.com/stormpath/stormpath-sdk-express#allowedOrigins)
    *
    * **Example:**
    *

    *   ENDPOINT_PREFIX = 'http://api.mydomain.com'
    *

    */
    ENDPOINT_PREFIX: '',

    /**
    * @ngdoc property
    *
    * @name GET_USER_EVENT
    *
    * @propertyOf stormpath.STORMPATH_CONFIG:STORMPATH_CONFIG
    *
    * @description
    *
    * Default: `$currentUser`
    *
    * The name of the event that is fired when
    * {@link stormpath.userService.$user#methods_get $user.get()}
    * is resolved with a user object.
    */
    GET_USER_EVENT: '$currentUser',

    /**
    * @ngdoc property
    *
    * @name NOT_LOGGED_IN_EVENT
    *
    * @propertyOf stormpath.STORMPATH_CONFIG:STORMPATH_CONFIG
    *
    * @description
    *
    * Default: `$notLoggedin`
    *
    * The name of the event that is fired when
    * {@link stormpath.userService.$user#methods_get $user.get()}
    * is rejected without a user.
    */
    NOT_LOGGED_IN_EVENT: '$notLoggedin',

    /**
    * @ngdoc property
    *
    * @name FORGOT_PASSWORD_ENDPOINT
    *
    * @propertyOf stormpath.STORMPATH_CONFIG:STORMPATH_CONFIG
    *
    * @description
    *
    * Default: `/forgot`
    *
    * The endpoint that is used by
    * {@link stormpath.userService.$user#methods_passwordResetRequest $user.passwordResetRequest()}
    * to create password reset tokens.
    */
    FORGOT_PASSWORD_ENDPOINT: '/forgot',

    /**
    * @ngdoc property
    *
    * @name CHANGE_PASSWORD_ENDPOINT
    *
    * @propertyOf stormpath.STORMPATH_CONFIG:STORMPATH_CONFIG
    *
    * @description
    *
    * Default: `/change`
    *
    * The endpoint that is used by
    * {@link stormpath.userService.$user#methods_verifyPasswordResetToken $user.verifyPasswordResetToken()} and
    * {@link stormpath.userService.$user#methods_resetPassword $user.resetPassword()}
    * to verify and consume password reset tokens (change a user's password with the token).
    */
    CHANGE_PASSWORD_ENDPOINT: '/change',

    /**
    * @ngdoc property
    *
    * @name SESSION_END_EVENT
    *
    * @propertyOf stormpath.STORMPATH_CONFIG:STORMPATH_CONFIG
    *
    * @description
    *
    * Default: `$sessionEnd`
    *
    * The name of the event that is fired when the user logs out via the
    * {@link stormpath.spLogout:spLogout spLogout}
    * directive
    */
    SESSION_END_EVENT: '$sessionEnd',

    SESSION_END_ERROR_EVENT: '$sessionEndError',

    /**
    * @ngdoc property
    *
    * @name STATE_CHANGE_UNAUTHENTICATED
    *
    * @propertyOf stormpath.STORMPATH_CONFIG:STORMPATH_CONFIG
    *
    * @description
    *
    * Default: `$stateChangeUnauthenticated`
    *
    * The name of the event that is fired when the user attempts to visit a
    * UI Router state that requires authentication, but the user is not
    * authenticated.
    */
    STATE_CHANGE_UNAUTHENTICATED: '$stateChangeUnauthenticated',

    /**
    * @ngdoc property
    *
    * @name STATE_CHANGE_UNAUTHORIZED
    *
    * @propertyOf stormpath.STORMPATH_CONFIG:STORMPATH_CONFIG
    *
    * @description
    *
    * Default: `$stateChangeUnauthorized`
    *
    * The name of the event that is fired when the user attempts to visit a
    * UI Router state that has an access control rule which the user does not
    * meet (such as not being in a specified group)
    */
    STATE_CHANGE_UNAUTHORIZED: '$stateChangeUnauthorized',

    /**
    * @ngdoc property
    *
    * @name ROUTE_CHANGE_UNAUTHENTICATED
    *
    * @propertyOf stormpath.STORMPATH_CONFIG:STORMPATH_CONFIG
    *
    * @description
    *
    * Default: `$routeChangeUnauthenticated`
    *
    * The name of the event that is fired when the user attempts to visit a
    * route that requires authentication, but the user is not
    * authenticated.
    */
    ROUTE_CHANGE_UNAUTHENTICATED: '$routeChangeUnauthenticated',

    /**
    * @ngdoc property
    *
    * @name ROUTE_CHANGE_UNAUTHORIZED
    *
    * @propertyOf stormpath.STORMPATH_CONFIG:STORMPATH_CONFIG
    *
    * @description
    *
    * Default: `$routeChangeUnauthorized`
    *
    * The name of the event that is fired when the user attempts to visit a
    * route that has an access control rule which the user does not
    * meet (such as not being in a specified group)
    */
    ROUTE_CHANGE_UNAUTHORIZED: '$routeChangeUnauthorized',

    /**
    * @ngdoc property
    *
    * @name OAUTH_REQUEST_ERROR
    *
    * @propertyOf stormpath.STORMPATH_CONFIG:STORMPATH_CONFIG
    *
    * @description
    *
    * Default: `$oAuthRequestError`
    *
    * The name of the event that is fired when the user attempts OAuth-based
    * authentication, and fails due to an OAuth issue.
    */
    OAUTH_REQUEST_ERROR: '$oAuthRequestError',

    /**
    * @ngdoc property
    *
    * @name OAUTH_AUTHENTICATION_ENDPOINT
    *
    * @propertyOf stormpath.STORMPATH_CONFIG:STORMPATH_CONFIG
    *
    * @description
    *
    * Default: `/oauth/token`
    *
    * The endpoint that is used to authenticate and refresh using OAuth tokens.
    * This endpoint MUST support password and refresh_token grant authentication
    * flows.
    */
    OAUTH_AUTHENTICATION_ENDPOINT: '/oauth/token',

    /**
    * @ngdoc property
    * @name OAUTH_REVOKE_ENDPOINT
    *
    * @propertyOf stormpath.STORMPATH_CONFIG:STORMPATH_CONFIG
    *
    * @description
    *
    * Default: `/oauth/revoke`
    *
    * The endpoint that is used to revoke OAuth tokens.
    */
    OAUTH_REVOKE_ENDPOINT: '/oauth/revoke',

    /**
    * @ngdoc property
    * @name OAUTH_REVOKE_ENDPOINT
    *
    * @propertyOf stormpath.STORMPATH_CONFIG:STORMPATH_CONFIG
    *
    * @description
    *
    * The name under which tokens are stored in the token storage mechanism.
    * Might not be relevant if the underlying storage mechanism is not key-value
    * based.
    *
    * See {@link stormpath.tokenStore.TokenStoreManager TokenStoreManager} for more detail.
    */
    OAUTH_TOKEN_STORAGE_NAME: 'stormpath:token',

    /**
    * @ngdoc property
    *
    * @name REGISTER_URI
    *
    * @propertyOf stormpath.STORMPATH_CONFIG:STORMPATH_CONFIG
    *
    * @description
    *
    * Default: `/register`
    *
    * The endpoint that is used by
    * {@link stormpath.userService.$user#methods_create $user.create()}
    * to POST new users.  This endpoint MUST accept a stormpath account object
    * and use Stormpath to create the new user.
    */
    REGISTER_URI: '/register',

    /**
    * @ngdoc property
    *
    * @name REGISTERED_EVENT_NAME
    *
    * @propertyOf stormpath.STORMPATH_CONFIG:STORMPATH_CONFIG
    *
    * @description
    *
    * Default: `$registered`
    *
    * The name of the event that is fired when
    * {@link stormpath.userService.$user#methods_create $user.create()}
    * is resolved with an account that was successfully created
    */
    REGISTERED_EVENT_NAME: '$registered',

    /**
    * @ngdoc property
    *
    * @name OAUTH_DEFAULT_TOKEN_STORE_TYPE
    *
    * @propertyOf stormpath.STORMPATH_CONFIG:STORMPATH_CONFIG
    *
    * @description
    *
    * Default: `localStorage`
    *
    * The default type of local storage used by
    * {@link stormpath.oauth.StormpathOAuthTokenProvider}.
    */
    OAUTH_DEFAULT_TOKEN_STORE_TYPE: 'localStorage'

  };

  c.getUrl = function(key) {
    return this.ENDPOINT_PREFIX + this[key];
  };

  c.getSocialLoginConfiguration = function(key) {
    var canonicalKey = key ? key.toLowerCase() : '';
    return this.SOCIAL_LOGIN_OPTIONS[canonicalKey] || {};
  };
  return c;
})());
<|endoftext|>


<commit_before>const { MessageEmbed } = require('discord.js');
const Star = require('../models/stars');

class Starboard {
	constructor(guild) {
		this.client = guild.client;
		this.guild = guild;

		this.stars = new Map();

		Star.findAll({ where: { guildID: this.guild.id } }).then(stars => {
			for (const star of stars) {
				this.stars.set(star.messageID, star);
			}
		});
	}

	get channel() {
		const channelId = this.client.settings.get(this.guild, 'starboardChannelID');
		return this.guild.channels.get(channelId);
	}

	async add(message, starredBy) {
		if (message.author.id === starredBy.id) {
			throw new Error('You can\'t star your own messages.');
		}

		if (!this.stars.has(message.id)) {
			const starboardMessage = await this.channel.send({ embed: Starboard.buildStarboardEmbed(message) });

			const star = await Star.create({
				messageID: message.id,
				authorID: message.author.id,
				channelID: message.channel.id,
				guildID: this.guild.id,
				starboardMessageID: starboardMessage.id,
				starredBy: [starredBy.id]
			});

			this.stars.set(message.id, star);
		} else {
			const star = this.stars.get(message.id);

			if (star.starredBy.includes(starredBy.id)) {
				throw new Error('You have already starred this message before; You can\'t star it again.');
			} else {
				const newStarredBy = star.starredBy.concat([starredBy.id]);

				const starboardMessage = await this.channel.fetchMessage(star.starboardMessageID);
				await starboardMessage.edit({ embed: Starboard.buildStarboardEmbed(message, newStarredBy.length) });

				const newStar = await star.update({
					starCount: newStarredBy.length,
					starredBy: newStarredBy
				});

				this.stars.set(message.id, newStar);
			}
		}
	}

	async remove(message, unstarBy) {
		const star = this.stars.get(message.id);

		if (!star.starredBy.includes(unstarBy.id)) {
			throw new Error('You can\'t remove any star from this message because you never gave it one in the first place.');
		}

		if (message.reactions.get('⭐').users.has(unstarBy.id)) {
			await message.reactions.get('⭐').remove(unstarBy);
		}

		const newStarredBy = star.starredBy.filter(id => id !== unstarBy.id);

		const starboardMessage = await this.channel.fetchMessage(star.starboardMessageID);
		if (newStarredBy.length) {
			await starboardMessage.edit({ embed: Starboard.buildStarboardEmbed(message, newStarredBy.length) });

			const newStar = await star.update({
				starCount: newStarredBy.length,
				starredBy: newStarredBy
			});

			this.stars.set(message.id, newStar);
		} else {
			await starboardMessage.delete();
			await star.destroy();
			this.stars.delete(message.id);
		}
	}

	static buildStarboardEmbed(message, starCount = 1) {
		const embed = new MessageEmbed()
			.setColor(0xFFAC33)
			.addField('Author', message.author, true)
			.addField('Channel', message.channel, true)
			.setThumbnail(message.author.displayAvatarURL())
			.setTimestamp(message.createdAt)
			.setFooter(`⭐ ${starCount}`);

		if (message.content) {
			embed.addField('Message', message.content);
		}

		const attachment = message.attachments.find(a => a.height);
		if (attachment) {
			embed.setImage(attachment.url);
		}

		return embed;
	}
}

module.exports = Starboard;
<commit_msg>Improve attachment finding<commit_after>const { MessageEmbed } = require('discord.js');
const path = require('path');
const Star = require('../models/stars');

class Starboard {
	constructor(guild) {
		this.client = guild.client;
		this.guild = guild;

		this.stars = new Map();

		Star.findAll({ where: { guildID: this.guild.id } }).then(stars => {
			for (const star of stars) {
				this.stars.set(star.messageID, star);
			}
		});
	}

	get channel() {
		const channelId = this.client.settings.get(this.guild, 'starboardChannelID');
		return this.guild.channels.get(channelId);
	}

	async add(message, starredBy) {
		if (message.author.id === starredBy.id) {
			throw new Error('You can\'t star your own messages.');
		}

		if (!this.stars.has(message.id)) {
			const starboardMessage = await this.channel.send({ embed: Starboard.buildStarboardEmbed(message) });

			const star = await Star.create({
				messageID: message.id,
				authorID: message.author.id,
				channelID: message.channel.id,
				guildID: this.guild.id,
				starboardMessageID: starboardMessage.id,
				starredBy: [starredBy.id]
			});

			this.stars.set(message.id, star);
		} else {
			const star = this.stars.get(message.id);

			if (star.starredBy.includes(starredBy.id)) {
				throw new Error('You have already starred this message before; You can\'t star it again.');
			} else {
				const newStarredBy = star.starredBy.concat([starredBy.id]);

				const starboardMessage = await this.channel.fetchMessage(star.starboardMessageID);
				await starboardMessage.edit({ embed: Starboard.buildStarboardEmbed(message, newStarredBy.length) });

				const newStar = await star.update({
					starCount: newStarredBy.length,
					starredBy: newStarredBy
				});

				this.stars.set(message.id, newStar);
			}
		}
	}

	async remove(message, unstarBy) {
		const star = this.stars.get(message.id);

		if (!star.starredBy.includes(unstarBy.id)) {
			throw new Error('You can\'t remove any star from this message because you never gave it one in the first place.');
		}

		if (message.reactions.get('⭐').users.has(unstarBy.id)) {
			await message.reactions.get('⭐').remove(unstarBy);
		}

		const newStarredBy = star.starredBy.filter(id => id !== unstarBy.id);

		const starboardMessage = await this.channel.fetchMessage(star.starboardMessageID);
		if (newStarredBy.length) {
			await starboardMessage.edit({ embed: Starboard.buildStarboardEmbed(message, newStarredBy.length) });

			const newStar = await star.update({
				starCount: newStarredBy.length,
				starredBy: newStarredBy
			});

			this.stars.set(message.id, newStar);
		} else {
			await starboardMessage.delete();
			await star.destroy();
			this.stars.delete(message.id);
		}
	}

	static findAttachment(message) {
		let attachmentImage;
		const extensions = ['.png', '.jpg', '.jpeg', '.gif', '.webp'];
		const linkRegex = /https?:\/\/(?:\w+\.)?[\w-]+\.[\w]{2,3}(?:\/[\w-_.]+)+\.(?:png|jpg|jpeg|gif|webp)/;

		if (message.attachments.some(attachment => extensions.includes(path.extname(attachment.url)))) {
			attachmentImage = message.attachments.first().url;
		}

		if (!attachmentImage) {
			const linkMatch = message.content.match(linkRegex);
			if (linkMatch && extensions.includes(path.extname(linkMatch[0]))) {
				attachmentImage = linkMatch[0];
			}
		}

		return attachmentImage;
	}

	static buildStarboardEmbed(message, starCount = 1) {
		const embed = new MessageEmbed()
			.setColor(0xFFAC33)
			.addField('Author', message.author, true)
			.addField('Channel', message.channel, true)
			.setThumbnail(message.author.displayAvatarURL())
			.setTimestamp(message.createdAt)
			.setFooter(`⭐ ${starCount}`);

		if (message.content) {
			embed.addField('Message', message.content);
		}

		const attachment = this.findAttachment(message);
		if (attachment) {
			embed.setImage(attachment);
		}

		return embed;
	}
}

module.exports = Starboard;
<|endoftext|>


<commit_before>var app = angular.module('app', ['ngAccordian', 'ui.router']);

app.config(['$stateProvider', '$urlRouterProvider', '$locationProvider', function ($stateProvider, $urlRouterProvider, $locationProvider) {

	$locationProvider.html5Mode({
		enabled: true
	});

	$stateProvider
		.state('home', {
			url: '/',
			controller: 'gettingStartedCtrl',
			templateUrl: 'view/getting-started.html'
		})
		.state('basic', {
			url: '/examples/basic',
			controller: 'basicCtrl',
			templateUrl: 'view/examples/basic.html'
		})
		.state('configuration', {
			url: '/examples/configuration',
			controller: 'configurationCtrl',
			templateUrl: 'view/examples/configuration.html'
		})
		.state('attributes', {
			url: '/attributes',
			controller: 'attributesCtrl',
			templateUrl: 'view/attributes.html'
		})
		.state('icons', {
			url: '/icons',
			controller: 'iconsCtrl',
			templateUrl: 'view/icons.html'
		});
}]);

app.controller('gettingStartedCtrl', ['$scope', function ($scope) {
	$scope.intro = 'getting started';
}]);

app.controller('basicCtrl', ['$scope', function ($scope) {

	$scope.basic = [
		{
			title: 'Markup',
			url: 'template/accordian/basic/markup.html'
		}, {
			title: 'JavaScript',
			url: 'template/accordian/basic/javascript.html'
		}
	];
}]);

app.controller('configurationCtrl', ['$scope', function ($scope) {

	$scope.configuration = [
		{
			title: 'Markup',
			url: 'template/accordian/configuration/markup.html'
		}, {
			title: 'JavaScript',
			url: 'template/accordian/configuration/javascript.html'
		}
	];
}]);

app.controller('attributesCtrl', ['$scope', function ($scope) {

}]);

app.controller('iconsCtrl', ['$scope', function ($scope) {

}]);

app.directive('tabs', [function () {
	return {
		restrict: 'E',
		templateUrl: 'template/tabs.html',
		scope: {
			tabs: '=',
			selected: '@'
		},
		link: function (scope, elem, attrs) {

			scope.currentTab = scope.tabs[scope.selected].url;

			scope.onClickTab = function (tab) {
				scope.currentTab = tab.url;
			}

			scope.isActiveTab = function (tabUrl) {
				return tabUrl === scope.currentTab;
			}
		}
	}
}]);

app.directive('prism', [function () {
	return {
		restrict: 'A',
		link: function (scope, element, attrs) {
			element.ready(function () {
				Prism.highlightElement(element[0]);
			});
		}
	}
}]);
<commit_msg>spaces<commit_after>var app = angular.module('app', ['ngAccordian', 'ui.router']);

app.config(['$stateProvider', '$urlRouterProvider', '$locationProvider', function ($stateProvider, $urlRouterProvider, $locationProvider) {

	$locationProvider.html5Mode({
		enabled: true
	});

	$stateProvider
		.state('home', {
			url: '/',
			controller: 'gettingStartedCtrl',
			templateUrl: 'view/getting-started.html'
		})
		.state('basic', {
			url: '/examples/basic',
			controller: 'basicCtrl',
			templateUrl: 'view/examples/basic.html'
		})
		.state('configuration', {
			url: '/examples/configuration',
			controller: 'configurationCtrl',
			templateUrl: 'view/examples/configuration.html'
		})
		.state('attributes', {
			url: '/attributes',
			controller: 'attributesCtrl',
			templateUrl: 'view/attributes.html'
		})
		.state('icons', {
			url: '/icons',
			controller: 'iconsCtrl',
			templateUrl: 'view/icons.html'
		});
}]);

app.controller('gettingStartedCtrl', ['$scope', function ($scope) {
	$scope.intro = 'getting started';
}]);

app.controller('basicCtrl', ['$scope', function ($scope) {

	$scope.basic = [
		{
			title: 'Markup',
			url: 'template/accordian/basic/markup.html'
		}, {
			title: 'JavaScript',
			url: 'template/accordian/basic/javascript.html'
		}
	];
}]);

app.controller('configurationCtrl', ['$scope', function ($scope) {

	$scope.configuration = [
		{
			title: 'Markup',
			url: 'template/accordian/configuration/markup.html'
		}, {
			title: 'JavaScript',
			url: 'template/accordian/configuration/javascript.html'
		}
	];
}]);

app.controller('attributesCtrl', ['$scope', function ($scope) {
}]);

app.controller('iconsCtrl', ['$scope', function ($scope) {
}]);

app.directive('tabs', [function () {
	return {
		restrict: 'E',
		templateUrl: 'template/tabs.html',
		scope: {
			tabs: '=',
			selected: '@'
		},
		link: function (scope, elem, attrs) {

			scope.currentTab = scope.tabs[scope.selected].url;

			scope.onClickTab = function (tab) {
				scope.currentTab = tab.url;
			}

			scope.isActiveTab = function (tabUrl) {
				return tabUrl === scope.currentTab;
			}
		}
	}
}]);

app.directive('prism', [function () {
	return {
		restrict: 'A',
		link: function (scope, element, attrs) {
			element.ready(function () {
				Prism.highlightElement(element[0]);
			});
		}
	}
}]);
<|endoftext|>


<commit_before>/* syntax.js
 * apply simple syntax highlighting to Tetra code
 */

keywords = ["def", "if", "elif", "else", "while", "for", "in", "parallel"];

function applySyntax() {
    /* find all the
 blocks */
    var snippets = document.querySelectorAll('pre code');

    snippets[0].style.backgroundColor = "red";

    /* highlight each one */
    for (i = 0; i < snippets.length; i++) {
        var text = snippets[i].textContent;
        snippets[i].textContent = "Heyyo";
        alert(text);
        //highlighted = text.replace(/def/g, "heyoo");
        //snippet.innerHTML = highlighted;
        //alert(highlighted);
    }
}

<commit_msg>work on site<commit_after>/* syntax.js
 * apply simple syntax highlighting to Tetra code
 */

keywords = ["def", "if", "elif", "else", "while", "for", "in", "parallel"];

function applySyntax() {
    /* find all the
 blocks */
    var snippets = document.querySelectorAll('pre code');

    /* highlight each one */
    for (i = 0; i < snippets.length; i++) {
        /* get existing text */
        var text = snippets[i].textContent;

        /* apply the replacements */
        highlighted = text.replace(/def/g, "heyoo");

        /* write it back */
        snippets[i].textContent = highlighted;
    }
}

<|endoftext|>


<commit_before>var Stremio = require("stremio-addons");
var http = require("http");
var _ = require("lodash");
var async = require("async");
var sift = require("sift");
var bagpipe = require("bagpipe");
var events = require("events");

var cfg = require("../lib/cfg");
var db = require("../lib/db");
var utils = require("../lib/utils");

// Keeping meta collection up to date; Algo here is
// async.queue with concurrency = 1 / bagpipe(1) ; we push update requests and collectMeta() calls to it
// updateMeta() function, called on idxbuild, debounced at half a second (or 300ms?)
// sample test: 79.1mb / 74.8mb / 75.1mb RAM with 1000 lean meta ; without: 72.0mb, 74.4, 72.3 NO DIFF in memory usage
var LID = cfg.LID || cfg.dbId.slice(0, 10);
var metaQueryProps = ["imdb_id", "type", "name", "year", "genre", "director", "dvdRelease", "imdbRating", "poster", "popularities."+LID];

var CINEMETA_URL = process.env.CINEMETA || cfg.cinemeta || "http://stremio-cinemeta.herokuapp.com";
var addons = new Stremio.Client();
addons.add(CINEMETA_URL);

var meta = { col: [], updated: 0, have: { } }, getPopularities;
var metaPipe = new bagpipe(1);

function updateMeta(ready) {
    getPopularities({ }, function(err, res) {
        var popSort = function(x) { return -res.popularities[x.imdb_id || x] };
        var constructMeta = function(x) {
            x.imdbRating = parseFloat(x.imdbRating);
            x.popularities = { }; // reset that - don't know if it brings any benefits
            x.popularities[LID] = res.popularities[x.imdb_id] || 0;
            // figure out year? since for series it's like "2011-2015" we can sort by the first field, but we can't replace the value
            meta.have[x.imdb_id] = 1;
        };

        var toGet = _.chain(res.popularities).omit(_.keys(meta.have)).keys().sortBy(popSort).value();
        /* Kind of useful for debugging
        console.log(_.chain(res.popularities).keys().sortBy(popSort).slice(0,10).each(function(x) {
            var hashes = db.lookup({imdb_id:x},1); db.get(hashes[0].id,function(err,res){console.log(res[0].value.popularity)})
        }).value());*/

        if (toGet.length == 0) return process.nextTick(ready);

        addons.meta.find({ query: { imdb_id: { $in: toGet } }, limit: toGet.length }, function(err, res) {
            process.nextTick(ready); // ensure we don't dead-end (deadlock is not a right term, block is not the right term, terms have to figured out for async code)

            if (err) console.error(err);
            meta.col = _.chain(meta.col).concat(res || []).sortBy(popSort).uniq("imdb_id").each(constructMeta).value();
        });
    });
};

db.evs.on("idxbuild", _.debounce(function() { metaPipe.push(updateMeta) }, 1000));
db.evs.on("idxready", function() { metaPipe.push(updateMeta) });

// Basic validation of args
function validate(args) {
    var meta = args.query;
    if (! (args.query || args.infoHash)) return { code: 0, message: "query/infoHash required" };
    if (meta && !meta.imdb_id) return { code: 1, message: "imdb_id required" };
    if (meta && (meta.type == "series" && !(meta.hasOwnProperty("episode") && meta.hasOwnProperty("season"))))
        return { code: 2, message: "season and episode required for series type" };
    return false;
};

function query(args, callback) {
    var start = Date.now();

    (function(next) {
        if (args.infoHash) return db.get(args.infoHash, function(err, res) { next(err, res && res[0] && res[0].value) });
        if (! args.query) return callback(new Error("must specify query or infoHash"));

        //var preferred = _.uniq(PREFERRED.concat(args.preferred || []), function(x) { return x.tag });
        var preferred = args.preferred || [];
        var prio = function(resolution) {
            return preferred.map(function(pref) {
                return utils.getAvailForTorrent(resolution.torrent) >= pref.min_avail && resolution.file.tag.indexOf(pref.tag)!=-1
            }).reduce(function(a,b) { return a+b }, 0);
        };

        var resolution = null;
        var matches = db.lookup(args.query, 3);
        async.whilst(
            function() { return matches.length && (!resolution || prio(resolution) < preferred.length) },
            function(callback) {
                var hash = matches.shift().id;
                db.get(hash, function(err, res) {
                    if (err) return callback({ err: err });

                    var tor = res[0] && res[0].value;
                    if (! tor) return callback({ err: "hash not found "+hash });

                    var file = _.find(tor.files, function(f) {
                        return f.imdb_id == args.query.imdb_id &&
                            (args.query.season ? (f.season == args.query.season) : true) &&
                            (args.query.episode ? ((f.episode || []).indexOf(args.query.episode) != -1) : true)
                    });

                    var res = { torrent: tor, file: file };
                    if (!resolution || prio(res) > prio(resolution)) resolution = res;

                    callback();
                });
            },
            function() { resolution ? next(resolution.err, resolution.torrent, resolution.file) : next() }
        );
    })(function(err, torrent, file) {
        // Output according to Stremio Addon API for stream.get
        // http://strem.io/addons-api
        callback(err, torrent ? _.extend({
            infoHash: torrent.infoHash.toLowerCase(),
            uploaders: utils.getMaxPopularity(torrent), // optional
            downloaders: Math.max.apply(Math, _.values(torrent.popularity).map(function(x) { return x[1] }).concat(0)), // optional
            //map: torrent.files,
            //pieceLength: torrent.pieceLength,
            availability: utils.getAvailForTorrent(torrent),
            sources: utils.getSourcesForTorrent(torrent), // optional but preferred
            runtime: Date.now()-start // optional
        }, file ? {
            mapIdx: file.idx,
            tag: file.tag, filename: file.name,
        } : { }) : null);
    });
};

var manifest = _.merge({
    // this should be always overridable by stremio-manifest
    stremio_LID: LID,
    // set filter so that we intercept meta.find from cinemeta
    // WARNING: this won't work because mpath doesn't support going into keys that contain dots, since it always thinks of it as a delimiter
    filter: _.object([ "sort.popularities."+LID,"query.popularities."+LID ], [{ "$exists": true },{ "$exists": true }])
}, require("./stremio-manifest"), _.pick(require("../package"), "version"), cfg.stremioManifest || {});

var service = new Stremio.Server({
    "stream.get": function(args, callback, user) { // OBSOLETE
        service.events.emit("stream.get", args, callback);

        var error = validate(args);
        if (error) return callback(error);
        query(args, callback);
    },
    "stream.find": function(args, callback, user) {
        service.events.emit("stream.find", args, callback);

        if ( args.items && Array.isArray(args.items)) {
            // OLD FORMAT; TODO: OBSOLETE
            var error = null;
            args.items.forEach(function(x) { error = error || validate(x) });
            if (error) return callback(error);

            async.map(args.items, query, function(err, items) {
                callback(err, items ? { items: items.map(function(x) {
                    return x ? { availability: x.availability, tag: x.tag } : null // TODO: send back number of candidates under _candidates
                }) } : null);
            });
        } else if (args.query) {
            // New format ; same as stream.get, even returns the full result; no point to slim it down, takes same time
            var error = validate(args);
            if (error) return callback(error);
            async.map([ _.extend({ preferred: [{ tag: "hd", min_avail: 2 }] }, args), args ], query, function(err, res) {
                callback(err, res ? _.chain(res).filter(function(x) { return x }).uniq(function(x) { return x.infoHash }).value() : undefined);
            });
        } else return callback({code: 10, message: "unsupported arguments"});
    },
    "stream.popularities": getPopularities = function(args, callback, user) { // OBSOLETE
        service.events.emit("stream.popularities", args, callback);

        var popularities = { };
        db.indexes.meta.executeOnEveryNode(function(n) {
            // value is equivalent to utils.getMaxPopularity
            if (n.key) popularities[n.key.split(" ")[0]] = Math.max.apply(null, n.data.map(function(k) { return db.indexes.seeders.get(k) })) || 0;
        });
        callback(null, { popularities: popularities });
    },
    "meta.find": function(args, callback, user) {
        service.events.emit("meta.find", args, callback);

        // Call this to wait for meta to be collected
        if (args.projection && ( args.projection=="full" ) ) return callback(new Error("full projection not supported by mp"));

        if (! meta.col.length) metaPipe.push(updateMeta);
        metaPipe.push(function(ready) {
            process.nextTick(ready); // ensure we don't lock

            args.query = _.pick.apply(null, [args.query || { }].concat(metaQueryProps));
            args.sort = _.pick.apply(null, [args.sort || { }].concat(metaQueryProps));
            //if (! _.keys(args.sort).length) args.sort["popularities."+LID] = -1; // no need as this is our default sort order

            var proj, projFn;
            if (args.projection && typeof(args.projection) == "object") {
                proj = _.keys(args.projection);
                projFn = _.values(args.projection)[0] ? _.pick : _.omit;
            }

            var res = _.chain(meta.col)
                .filter(args.query ? sift(args.query) : _.constant(true))
                .sortByOrder(_.keys(args.sort), _.values(args.sort).map(function(x) { return x>0 ? "asc" : "desc" }))
                .slice(args.skip || 0, Math.min(400, args.limit))
                .map(function(x) { return projFn ? projFn(x, proj) : x })
                .value();
            callback(null, res);
        });
    },
    "stats.get": function(args, callback, user) {
        service.events.emit("stats.get", args, callback);

        var c = db.indexes.seeders.size;
        var items = 0, episodes = 0, movies = 0;
        db.indexes.meta.executeOnEveryNode(function(n) {
            if (n.key.indexOf(" ") != -1) episodes++; else movies++;
            items++;
        });
        callback(null, { statsNum: items+" movies and episodes", stats: [
            { name: "number of items - "+items, count: items, colour: items > 100 ? "green" : (items > 50 ? "yellow" : "red") },
            { name: "number of movies - "+movies, count: movies, colour: movies > 100 ? "green" : (movies > 50 ? "yellow" : "red") },
            { name: "number of episodes - "+episodes, count: episodes, colour: episodes > 100 ? "green" : (episodes > 50 ? "yellow" : "red") },
            { name: "number of torrents - "+c, count: c, colour: c > 1000 ? "green" : (c > 500 ? "yellow" : "red") }
        ] });
    },
}, { stremioget: true, allow: [cfg.stremioCentral,"http://api8.herokuapp.com","http://api9.strem.io"], secret: cfg.stremioSecret }, manifest);

// Event emitter in case we want to intercept/plug-in to this
service.events = new events.EventEmitter();

function listen(port, ip) {
    var server = http.createServer(function (req, res) {
	req.on("error", function(e) { console.error(e) });
        service.middleware(req, res, function() { res.end() });
    })
    .on("error", function(e) { console.error("mp server", e) })
    .on("listening", function()
    {
        console.log("Multipass Stremio Addon listening on "+server.address().port);
    });
    return server.listen(port, ip);
}

if (module.parent) { module.exports = listen; module.exports.service = service; }
else listen(process.env.PORT || 7000);
<commit_msg>Update cinemeta endpoint<commit_after>var Stremio = require("stremio-addons");
var http = require("http");
var _ = require("lodash");
var async = require("async");
var sift = require("sift");
var bagpipe = require("bagpipe");
var events = require("events");

var cfg = require("../lib/cfg");
var db = require("../lib/db");
var utils = require("../lib/utils");

// Keeping meta collection up to date; Algo here is
// async.queue with concurrency = 1 / bagpipe(1) ; we push update requests and collectMeta() calls to it
// updateMeta() function, called on idxbuild, debounced at half a second (or 300ms?)
// sample test: 79.1mb / 74.8mb / 75.1mb RAM with 1000 lean meta ; without: 72.0mb, 74.4, 72.3 NO DIFF in memory usage
var LID = cfg.LID || cfg.dbId.slice(0, 10);
var metaQueryProps = ["imdb_id", "type", "name", "year", "genre", "director", "dvdRelease", "imdbRating", "poster", "popularities."+LID];

var CINEMETA_URL = process.env.CINEMETA || cfg.cinemeta || "http://cinemeta.strem.io/stremioget";
var addons = new Stremio.Client();
addons.add(CINEMETA_URL);

var meta = { col: [], updated: 0, have: { } }, getPopularities;
var metaPipe = new bagpipe(1);

function updateMeta(ready) {
    getPopularities({ }, function(err, res) {
        var popSort = function(x) { return -res.popularities[x.imdb_id || x] };
        var constructMeta = function(x) {
            x.imdbRating = parseFloat(x.imdbRating);
            x.popularities = { }; // reset that - don't know if it brings any benefits
            x.popularities[LID] = res.popularities[x.imdb_id] || 0;
            // figure out year? since for series it's like "2011-2015" we can sort by the first field, but we can't replace the value
            meta.have[x.imdb_id] = 1;
        };

        var toGet = _.chain(res.popularities).omit(_.keys(meta.have)).keys().sortBy(popSort).value();
        /* Kind of useful for debugging
        console.log(_.chain(res.popularities).keys().sortBy(popSort).slice(0,10).each(function(x) {
            var hashes = db.lookup({imdb_id:x},1); db.get(hashes[0].id,function(err,res){console.log(res[0].value.popularity)})
        }).value());*/

        if (toGet.length == 0) return process.nextTick(ready);

        addons.meta.find({ query: { imdb_id: { $in: toGet } }, limit: toGet.length }, function(err, res) {
            process.nextTick(ready); // ensure we don't dead-end (deadlock is not a right term, block is not the right term, terms have to figured out for async code)

            if (err) console.error(err);
            meta.col = _.chain(meta.col).concat(res || []).sortBy(popSort).uniq("imdb_id").each(constructMeta).value();
        });
    });
};

db.evs.on("idxbuild", _.debounce(function() { metaPipe.push(updateMeta) }, 1000));
db.evs.on("idxready", function() { metaPipe.push(updateMeta) });

// Basic validation of args
function validate(args) {
    var meta = args.query;
    if (! (args.query || args.infoHash)) return { code: 0, message: "query/infoHash required" };
    if (meta && !meta.imdb_id) return { code: 1, message: "imdb_id required" };
    if (meta && (meta.type == "series" && !(meta.hasOwnProperty("episode") && meta.hasOwnProperty("season"))))
        return { code: 2, message: "season and episode required for series type" };
    return false;
};

function query(args, callback) {
    var start = Date.now();

    (function(next) {
        if (args.infoHash) return db.get(args.infoHash, function(err, res) { next(err, res && res[0] && res[0].value) });
        if (! args.query) return callback(new Error("must specify query or infoHash"));

        //var preferred = _.uniq(PREFERRED.concat(args.preferred || []), function(x) { return x.tag });
        var preferred = args.preferred || [];
        var prio = function(resolution) {
            return preferred.map(function(pref) {
                return utils.getAvailForTorrent(resolution.torrent) >= pref.min_avail && resolution.file.tag.indexOf(pref.tag)!=-1
            }).reduce(function(a,b) { return a+b }, 0);
        };

        var resolution = null;
        var matches = db.lookup(args.query, 3);
        async.whilst(
            function() { return matches.length && (!resolution || prio(resolution) < preferred.length) },
            function(callback) {
                var hash = matches.shift().id;
                db.get(hash, function(err, res) {
                    if (err) return callback({ err: err });

                    var tor = res[0] && res[0].value;
                    if (! tor) return callback({ err: "hash not found "+hash });

                    var file = _.find(tor.files, function(f) {
                        return f.imdb_id == args.query.imdb_id &&
                            (args.query.season ? (f.season == args.query.season) : true) &&
                            (args.query.episode ? ((f.episode || []).indexOf(args.query.episode) != -1) : true)
                    });

                    var res = { torrent: tor, file: file };
                    if (!resolution || prio(res) > prio(resolution)) resolution = res;

                    callback();
                });
            },
            function() { resolution ? next(resolution.err, resolution.torrent, resolution.file) : next() }
        );
    })(function(err, torrent, file) {
        // Output according to Stremio Addon API for stream.get
        // http://strem.io/addons-api
        callback(err, torrent ? _.extend({
            infoHash: torrent.infoHash.toLowerCase(),
            uploaders: utils.getMaxPopularity(torrent), // optional
            downloaders: Math.max.apply(Math, _.values(torrent.popularity).map(function(x) { return x[1] }).concat(0)), // optional
            //map: torrent.files,
            //pieceLength: torrent.pieceLength,
            availability: utils.getAvailForTorrent(torrent),
            sources: utils.getSourcesForTorrent(torrent), // optional but preferred
            runtime: Date.now()-start // optional
        }, file ? {
            mapIdx: file.idx,
            tag: file.tag, filename: file.name,
        } : { }) : null);
    });
};

var manifest = _.merge({
    // this should be always overridable by stremio-manifest
    stremio_LID: LID,
    // set filter so that we intercept meta.find from cinemeta
    // WARNING: this won't work because mpath doesn't support going into keys that contain dots, since it always thinks of it as a delimiter
    filter: _.object([ "sort.popularities."+LID,"query.popularities."+LID ], [{ "$exists": true },{ "$exists": true }])
}, require("./stremio-manifest"), _.pick(require("../package"), "version"), cfg.stremioManifest || {});

var service = new Stremio.Server({
    "stream.get": function(args, callback, user) { // OBSOLETE
        service.events.emit("stream.get", args, callback);

        var error = validate(args);
        if (error) return callback(error);
        query(args, callback);
    },
    "stream.find": function(args, callback, user) {
        service.events.emit("stream.find", args, callback);

        if ( args.items && Array.isArray(args.items)) {
            // OLD FORMAT; TODO: OBSOLETE
            var error = null;
            args.items.forEach(function(x) { error = error || validate(x) });
            if (error) return callback(error);

            async.map(args.items, query, function(err, items) {
                callback(err, items ? { items: items.map(function(x) {
                    return x ? { availability: x.availability, tag: x.tag } : null // TODO: send back number of candidates under _candidates
                }) } : null);
            });
        } else if (args.query) {
            // New format ; same as stream.get, even returns the full result; no point to slim it down, takes same time
            var error = validate(args);
            if (error) return callback(error);
            async.map([ _.extend({ preferred: [{ tag: "hd", min_avail: 2 }] }, args), args ], query, function(err, res) {
                callback(err, res ? _.chain(res).filter(function(x) { return x }).uniq(function(x) { return x.infoHash }).value() : undefined);
            });
        } else return callback({code: 10, message: "unsupported arguments"});
    },
    "stream.popularities": getPopularities = function(args, callback, user) { // OBSOLETE
        service.events.emit("stream.popularities", args, callback);

        var popularities = { };
        db.indexes.meta.executeOnEveryNode(function(n) {
            // value is equivalent to utils.getMaxPopularity
            if (n.key) popularities[n.key.split(" ")[0]] = Math.max.apply(null, n.data.map(function(k) { return db.indexes.seeders.get(k) })) || 0;
        });
        callback(null, { popularities: popularities });
    },
    "meta.find": function(args, callback, user) {
        service.events.emit("meta.find", args, callback);

        // Call this to wait for meta to be collected
        if (args.projection && ( args.projection=="full" ) ) return callback(new Error("full projection not supported by mp"));

        if (! meta.col.length) metaPipe.push(updateMeta);
        metaPipe.push(function(ready) {
            process.nextTick(ready); // ensure we don't lock

            args.query = _.pick.apply(null, [args.query || { }].concat(metaQueryProps));
            args.sort = _.pick.apply(null, [args.sort || { }].concat(metaQueryProps));
            //if (! _.keys(args.sort).length) args.sort["popularities."+LID] = -1; // no need as this is our default sort order

            var proj, projFn;
            if (args.projection && typeof(args.projection) == "object") {
                proj = _.keys(args.projection);
                projFn = _.values(args.projection)[0] ? _.pick : _.omit;
            }

            var res = _.chain(meta.col)
                .filter(args.query ? sift(args.query) : _.constant(true))
                .sortByOrder(_.keys(args.sort), _.values(args.sort).map(function(x) { return x>0 ? "asc" : "desc" }))
                .slice(args.skip || 0, Math.min(400, args.limit))
                .map(function(x) { return projFn ? projFn(x, proj) : x })
                .value();
            callback(null, res);
        });
    },
    "stats.get": function(args, callback, user) {
        service.events.emit("stats.get", args, callback);

        var c = db.indexes.seeders.size;
        var items = 0, episodes = 0, movies = 0;
        db.indexes.meta.executeOnEveryNode(function(n) {
            if (n.key.indexOf(" ") != -1) episodes++; else movies++;
            items++;
        });
        callback(null, { statsNum: items+" movies and episodes", stats: [
            { name: "number of items - "+items, count: items, colour: items > 100 ? "green" : (items > 50 ? "yellow" : "red") },
            { name: "number of movies - "+movies, count: movies, colour: movies > 100 ? "green" : (movies > 50 ? "yellow" : "red") },
            { name: "number of episodes - "+episodes, count: episodes, colour: episodes > 100 ? "green" : (episodes > 50 ? "yellow" : "red") },
            { name: "number of torrents - "+c, count: c, colour: c > 1000 ? "green" : (c > 500 ? "yellow" : "red") }
        ] });
    },
}, { stremioget: true, allow: [cfg.stremioCentral,"http://api8.herokuapp.com","http://api9.strem.io"], secret: cfg.stremioSecret }, manifest);

// Event emitter in case we want to intercept/plug-in to this
service.events = new events.EventEmitter();

function listen(port, ip) {
    var server = http.createServer(function (req, res) {
	req.on("error", function(e) { console.error(e) });
        service.middleware(req, res, function() { res.end() });
    })
    .on("error", function(e) { console.error("mp server", e) })
    .on("listening", function()
    {
        console.log("Multipass Stremio Addon listening on "+server.address().port);
    });
    return server.listen(port, ip);
}

if (module.parent) { module.exports = listen; module.exports.service = service; }
else listen(process.env.PORT || 7000);
<|endoftext|>


<commit_before><commit_msg>Fix isOrg<commit_after><|endoftext|>


<commit_before>/*!
 * jQuery TimeDurationPicker Plugin v1.0.4
 *
 * https://github.com/digaev/jQuery-timeDurationPicker
 *
 * Copyright (c) 2015 Nikolay Digaev
 * Released under the MIT license
 */

(function($) {
  $.timeDurationPicker = function(options) {
    $.timeDurationPicker.defaults = $.extend({}, $.timeDurationPicker.defaults, options);
  }

  $.timeDurationPicker.defaults = {
    lang: 'en',
    seconds: false,
    minutes: true,
    hours: true,
    days: true,
    months: true,
    years: true
  }
})(jQuery);

(function($) {
  var instances = [];

  $(document).focusin(function(e) {
    for (var i = 0, c = instances.length; i < c; ++i) {
      var inst = instances[i];
      for (var j = 0, l = inst.element.length; j < l; ++j) {
        if (inst.element[j] == e.target) {
          var offset = $(e.target).offset();
          offset.top += $(e.target).outerHeight();
          offset.top -= $(window).scrollTop();
          offset.left -= $(window).scrollLeft();
          inst._content.div.css(offset).fadeIn();
        }
      }
    }
  }).focusout(function(e) {
    // FIXME
    setTimeout(function() {
      var el = document.activeElement;
      if ($(el).parents('.time-duration-picker-content').length == 0) {
        for (var i = 0, c = instances.length; i < c; ++i) {
          var hide = true;
          var inst = instances[i];
          for (var j = 0, l = inst.element.length; j < l; ++j) {
            if (inst.element[j] == el) {
              hide = false;
              break;
            }
          }
          if (hide) {
            inst._content.div.fadeOut();
          }
        }
      }
    }, 10);
  });

  var YEAR = 12 * 30 * 24 * 60 * 60;
  var MONTH = 30 * 24 * 60 * 60;
  var DAY = 24 * 60 * 60;
  var HOUR = 60 * 60;
  var MINUTE = 60;

  $.widget('custom.timeDurationPicker', {
    options: {
    },
    _langs: {
      en: {
        seconds: 'Seconds',
        minutes: 'Minutes',
        hours: 'Hours',
        days: 'Days',
        months: 'Months',
        years: 'Years',
        human_years: 'years',
        human_months: 'months',
        human_days: 'days',
        human_hours: 'hours',
        human_minutes: 'minutes',
        human_seconds: 'seconds',
        and: 'and',
        button_ok: 'Done'
      },
      ru: {
        seconds: 'Секунды',
        minutes: 'Минуты',
        hours: 'Часы',
        days: 'Дни',
        months: 'Месяцы',
        years: 'Годы',
        human_years: 'лет',
        human_months: 'месяцев',
        human_days: 'дней',
        human_hours: 'часов',
        human_minutes: 'минут',
        human_seconds: 'секунд',
        and: 'и',
        button_ok: 'Выбрать'
      },
      ru_short: {
        seconds: 'Секунды',
        minutes: 'Минуты',
        hours: 'Часы',
        days: 'Дни',
        months: 'Месяцы',
        years: 'Годы',
        human_years: 'г.',
        human_months: 'мес.',
        human_days: 'д.',
        human_hours: 'час.',
        human_minutes: 'мин.',
        human_seconds: 'сек.',
        and: 'и',
        button_ok: 'Выбрать'
      }
    },
    _create: function() {
        var self = this;

        this.options = $.extend({}, $.timeDurationPicker.defaults, this.options);

        this._content = {};
        this._content.div = $('
');
        this._content.div.addClass('ui-widget ui-widget-content ui-helper-clearfix ui-corner-all');
        this._content.div.addClass('time-duration-picker-content');
        this._content.div.css({
            display: 'none',
            position: 'fixed',
            "z-index": 401
        });
        this._content.div.appendTo(document.body);

        this._content.table = $('

').appendTo(this._content.div);
        this._content.tableBody = $('tbody', this._content.table);

        this._content.button = $('<input type="button" />').val(this._tr('button_ok'));
        this._content.button.addClass('ui-button ui-widget ui-state-default ui-corner-all');
        this._content.button.css({
          display: 'block',
          margin: '0.5em auto',
          padding: '0.5em 1em'
        });
        this._content.button.hover(function() {
          $(this).addClass('ui-state-hover');
        }, function() {
          $(this).removeClass('ui-state-hover');
        });
        this._content.button.on('click', function(e) {
          self._content.div.fadeOut();
          if (self.options.onselect) {
            self.options.onselect(self.element, self.getDuration(), self.getHumanDuration());
          }
        });
        this._content.button.appendTo(this._content.div);

        this._initUnits();

        instances.push(this);
    },
    _initUnits: function() {
        var i;
        if (this.options.seconds) {
          this._content.seconds = this._createSelectWithOptions(0, 59);
          this._appendRow(this._tr('seconds'), this._content.seconds);
        }
        if (this.options.minutes) {
          this._content.minutes = this._createSelectWithOptions(0, 59);
          this._appendRow(this._tr('minutes'), this._content.minutes);
        }
        if (this.options.hours) {
          this._content.hours = this._createSelectWithOptions(0, 23);
          this._appendRow(this._tr('hours'), this._content.hours);
        }
        if (this.options.days) {
          this._content.days = this._createSelectWithOptions(0, 29);
          this._appendRow(this._tr('days'), this._content.days);
        }
        if (this.options.months) {
          this._content.months = this._createSelectWithOptions(0, 11);
          this._appendRow(this._tr('months'), this._content.months);
        }
        if (this.options.years) {
          this._content.years = this._createSelectWithOptions(0, 10);
          this._appendRow(this._tr('years'), this._content.years);
        }
        if (this.options.defaultValue) {
          this.setDuration(this.options.defaultValue);
        }
    },
    _createSelectWithOptions: function(min, max) {
        var select = $('<select />')
          .addClass('ui-widget ui-state-default ui-corner-all')

        select.hover(function() {
          $(this).addClass('ui-state-hover');
        }, function() {
          $(this).removeClass('ui-state-hover');
        });

        this._createOptionsForSelect(select, min, max);
        return select;
    },
    _createOptionsForSelect: function(select, min, max) {
      for (var i = min; i <= max; ++i) {
        select.append($('<option />').val(i).text(i < 10 ? ('0' + i) : i));
      }
    },
    _appendRow: function(text, el) {
      var row = $('
').appendTo(this._content.tableBody);
      $('').css({
        width: '50%',
        padding: '.5em 1em',
        "text-align": 'right',
        "vertical-align": 'middle'
      }).append($('')
        .text(text))
        .appendTo(row);
      $('').css({
        width: '50%',
        padding: '.5em 1em',
        "text-align": 'right',
        "vertical-align": 'middle'
      }).append(el)
        .appendTo(row);
    },
    _tr: function(key) {
      return this._langs[this.options.lang][key];
    },
    getSeconds: function() {
      return parseInt(this._content.seconds.val());
    },
    getMinutes: function() {
      return parseInt(this._content.minutes.val());
    },
    getHours: function() {
      return parseInt(this._content.hours.val());
    },
    getDays: function() {
      return parseInt(this._content.days.val());
    },
    getMonths: function() {
      return parseInt(this._content.months.val());
    },
    getYears: function() {
      return parseInt(this._content.years.val());
    },
    setDuration: function(value) {
      value = parseInt(value);
      if (isNaN(value)) {
        return false;
      }

      var i;
      if (this.options.years) {
        i = Math.floor(value / YEAR);
        value -= i * YEAR;
        this._content.years.val(i);
      }
      if (this.options.months) {
        i = Math.floor(value / MONTH);
        if (i >= 12) {
          i = 0;
        }
        value -= i * MONTH;
        this._content.months.val(i);
      }
      if (this.options.days) {
        i = Math.floor(value / DAY);
        if (i >= 30) {
          i = 0;
        }
        value -= i * DAY;
        this._content.days.val(i);
      }
      if (this.options.hours) {
        i = Math.floor(value / HOUR);
        if (i >= 24) {
          i = 0;
        }
        value -= i * HOUR;
        this._content.hours.val(i);
      }
      if (this.options.minutes) {
        i = Math.floor(value / MINUTE);
        if (i >= 60) {
          i = 0;
        }
        value -= i * MINUTE;
        this._content.minutes.val(i);
      }
      if (this.options.seconds) {
        i = Math.floor(value);
        if (i >= 60) {
          i = 0;
        }
        this._content.seconds.val(i);
      }
      return value;
    },
    getDuration: function() {
      var seconds = 0;
      if (this.options.seconds) {
        seconds += this.getSeconds();
      }
      if (this.options.minutes) {
        seconds += this.getMinutes() * MINUTE;
      }
      if (this.options.hours) {
        seconds += this.getHours() * HOUR;
      }
      if (this.options.days) {
        seconds += this.getDays() * DAY;
      }
      if (this.options.months) {
        seconds += this.getMonths() * MONTH;
      }
      if (this.options.years) {
        seconds += this.getYears() * YEAR;
      }
      return seconds;
    },
    getHumanDuration: function() {
      var units = [];
      var duration = '';

      if (this.options.years && this.getYears() > 0) {
        units.push({value: this.getYears(), name: this._tr('human_years')});
      }
      if (this.options.months && this.getMonths() > 0) {
        units.push({value: this.getMonths(), name: this._tr('human_months')});
      }
      if (this.options.days && this.getDays() > 0) {
        units.push({value: this.getDays(), name: this._tr('human_days')});
      }
      if (this.options.hours && this.getHours() > 0) {
        units.push({value: this.getHours(), name: this._tr('human_hours')});
      }
      if (this.options.minutes && this.getMinutes() > 0) {
        units.push({value: this.getMinutes(), name: this._tr('human_minutes')});
      }
      if (this.options.seconds && this.getSeconds() > 0) {
        units.push({value: this.getSeconds(), name: this._tr('human_seconds')});
      }

      for (var i = 0, l = units.length; i < l; ++i) {
        var unit = units[i];
        var separator = i == l - 1 ? '' : (i == l - 2 ? ' ' + this._tr('and') + ' ' : ', ');

        duration += unit.value + ' ' + unit.name + separator;
      }
      return duration;
    }
  });
})(jQuery);
<commit_msg>Add `position` option (github #1)<commit_after>/*!
 * jQuery TimeDurationPicker Plugin v1.0.4
 *
 * https://github.com/digaev/jQuery-timeDurationPicker
 *
 * Copyright (c) 2015 Nikolay Digaev
 * Released under the MIT license
 */

(function($) {
  $.timeDurationPicker = function(options) {
    $.timeDurationPicker.defaults = $.extend({}, $.timeDurationPicker.defaults, options);
  }

  $.timeDurationPicker.defaults = {
    lang: 'en',
    position: 'fixed', // https://github.com/digaev/jQuery-timeDurationPicker/issues/1
    seconds: false,
    minutes: true,
    hours: true,
    days: true,
    months: true,
    years: true
  }
})(jQuery);

(function($) {
  var instances = [];

  $(document).focusin(function(e) {
    for (var i = 0, c = instances.length; i < c; ++i) {
      var inst = instances[i];
      for (var j = 0, l = inst.element.length; j < l; ++j) {
        if (inst.element[j] == e.target) {
          var offset = $(e.target).offset();
          offset.top += $(e.target).outerHeight();
          offset.top -= $(window).scrollTop();
          offset.left -= $(window).scrollLeft();
          inst._content.div.css(offset).fadeIn();
        }
      }
    }
  }).focusout(function(e) {
    // FIXME
    setTimeout(function() {
      var el = document.activeElement;
      if ($(el).parents('.time-duration-picker-content').length == 0) {
        for (var i = 0, c = instances.length; i < c; ++i) {
          var hide = true;
          var inst = instances[i];
          for (var j = 0, l = inst.element.length; j < l; ++j) {
            if (inst.element[j] == el) {
              hide = false;
              break;
            }
          }
          if (hide) {
            inst._content.div.fadeOut();
          }
        }
      }
    }, 10);
  });

  var YEAR = 12 * 30 * 24 * 60 * 60;
  var MONTH = 30 * 24 * 60 * 60;
  var DAY = 24 * 60 * 60;
  var HOUR = 60 * 60;
  var MINUTE = 60;

  $.widget('custom.timeDurationPicker', {
    options: {
    },
    _langs: {
      en: {
        seconds: 'Seconds',
        minutes: 'Minutes',
        hours: 'Hours',
        days: 'Days',
        months: 'Months',
        years: 'Years',
        human_years: 'years',
        human_months: 'months',
        human_days: 'days',
        human_hours: 'hours',
        human_minutes: 'minutes',
        human_seconds: 'seconds',
        and: 'and',
        button_ok: 'Done'
      },
      ru: {
        seconds: 'Секунды',
        minutes: 'Минуты',
        hours: 'Часы',
        days: 'Дни',
        months: 'Месяцы',
        years: 'Годы',
        human_years: 'лет',
        human_months: 'месяцев',
        human_days: 'дней',
        human_hours: 'часов',
        human_minutes: 'минут',
        human_seconds: 'секунд',
        and: 'и',
        button_ok: 'Выбрать'
      },
      ru_short: {
        seconds: 'Секунды',
        minutes: 'Минуты',
        hours: 'Часы',
        days: 'Дни',
        months: 'Месяцы',
        years: 'Годы',
        human_years: 'г.',
        human_months: 'мес.',
        human_days: 'д.',
        human_hours: 'час.',
        human_minutes: 'мин.',
        human_seconds: 'сек.',
        and: 'и',
        button_ok: 'Выбрать'
      }
    },
    _create: function() {
        var self = this;

        this.options = $.extend({}, $.timeDurationPicker.defaults, this.options);

        this._content = {};
        this._content.div = $('
');
        this._content.div.addClass('ui-widget ui-widget-content ui-helper-clearfix ui-corner-all');
        this._content.div.addClass('time-duration-picker-content');
        this._content.div.css({
            display: 'none',
            position: this.options.position,
            "z-index": 401
        });
        this._content.div.appendTo(document.body);

        this._content.table = $('

').appendTo(this._content.div);
        this._content.tableBody = $('tbody', this._content.table);

        this._content.button = $('<input type="button" />').val(this._tr('button_ok'));
        this._content.button.addClass('ui-button ui-widget ui-state-default ui-corner-all');
        this._content.button.css({
          display: 'block',
          margin: '0.5em auto',
          padding: '0.5em 1em'
        });
        this._content.button.hover(function() {
          $(this).addClass('ui-state-hover');
        }, function() {
          $(this).removeClass('ui-state-hover');
        });
        this._content.button.on('click', function(e) {
          self._content.div.fadeOut();
          if (self.options.onselect) {
            self.options.onselect(self.element, self.getDuration(), self.getHumanDuration());
          }
        });
        this._content.button.appendTo(this._content.div);

        this._initUnits();

        instances.push(this);
    },
    _initUnits: function() {
        var i;
        if (this.options.seconds) {
          this._content.seconds = this._createSelectWithOptions(0, 59);
          this._appendRow(this._tr('seconds'), this._content.seconds);
        }
        if (this.options.minutes) {
          this._content.minutes = this._createSelectWithOptions(0, 59);
          this._appendRow(this._tr('minutes'), this._content.minutes);
        }
        if (this.options.hours) {
          this._content.hours = this._createSelectWithOptions(0, 23);
          this._appendRow(this._tr('hours'), this._content.hours);
        }
        if (this.options.days) {
          this._content.days = this._createSelectWithOptions(0, 29);
          this._appendRow(this._tr('days'), this._content.days);
        }
        if (this.options.months) {
          this._content.months = this._createSelectWithOptions(0, 11);
          this._appendRow(this._tr('months'), this._content.months);
        }
        if (this.options.years) {
          this._content.years = this._createSelectWithOptions(0, 10);
          this._appendRow(this._tr('years'), this._content.years);
        }
        if (this.options.defaultValue) {
          this.setDuration(this.options.defaultValue);
        }
    },
    _createSelectWithOptions: function(min, max) {
        var select = $('<select />')
          .addClass('ui-widget ui-state-default ui-corner-all')

        select.hover(function() {
          $(this).addClass('ui-state-hover');
        }, function() {
          $(this).removeClass('ui-state-hover');
        });

        this._createOptionsForSelect(select, min, max);
        return select;
    },
    _createOptionsForSelect: function(select, min, max) {
      for (var i = min; i <= max; ++i) {
        select.append($('<option />').val(i).text(i < 10 ? ('0' + i) : i));
      }
    },
    _appendRow: function(text, el) {
      var row = $('
').appendTo(this._content.tableBody);
      $('').css({
        width: '50%',
        padding: '.5em 1em',
        "text-align": 'right',
        "vertical-align": 'middle'
      }).append($('')
        .text(text))
        .appendTo(row);
      $('').css({
        width: '50%',
        padding: '.5em 1em',
        "text-align": 'right',
        "vertical-align": 'middle'
      }).append(el)
        .appendTo(row);
    },
    _tr: function(key) {
      return this._langs[this.options.lang][key];
    },
    getSeconds: function() {
      return parseInt(this._content.seconds.val());
    },
    getMinutes: function() {
      return parseInt(this._content.minutes.val());
    },
    getHours: function() {
      return parseInt(this._content.hours.val());
    },
    getDays: function() {
      return parseInt(this._content.days.val());
    },
    getMonths: function() {
      return parseInt(this._content.months.val());
    },
    getYears: function() {
      return parseInt(this._content.years.val());
    },
    setDuration: function(value) {
      value = parseInt(value);
      if (isNaN(value)) {
        return false;
      }

      var i;
      if (this.options.years) {
        i = Math.floor(value / YEAR);
        value -= i * YEAR;
        this._content.years.val(i);
      }
      if (this.options.months) {
        i = Math.floor(value / MONTH);
        if (i >= 12) {
          i = 0;
        }
        value -= i * MONTH;
        this._content.months.val(i);
      }
      if (this.options.days) {
        i = Math.floor(value / DAY);
        if (i >= 30) {
          i = 0;
        }
        value -= i * DAY;
        this._content.days.val(i);
      }
      if (this.options.hours) {
        i = Math.floor(value / HOUR);
        if (i >= 24) {
          i = 0;
        }
        value -= i * HOUR;
        this._content.hours.val(i);
      }
      if (this.options.minutes) {
        i = Math.floor(value / MINUTE);
        if (i >= 60) {
          i = 0;
        }
        value -= i * MINUTE;
        this._content.minutes.val(i);
      }
      if (this.options.seconds) {
        i = Math.floor(value);
        if (i >= 60) {
          i = 0;
        }
        this._content.seconds.val(i);
      }
      return value;
    },
    getDuration: function() {
      var seconds = 0;
      if (this.options.seconds) {
        seconds += this.getSeconds();
      }
      if (this.options.minutes) {
        seconds += this.getMinutes() * MINUTE;
      }
      if (this.options.hours) {
        seconds += this.getHours() * HOUR;
      }
      if (this.options.days) {
        seconds += this.getDays() * DAY;
      }
      if (this.options.months) {
        seconds += this.getMonths() * MONTH;
      }
      if (this.options.years) {
        seconds += this.getYears() * YEAR;
      }
      return seconds;
    },
    getHumanDuration: function() {
      var units = [];
      var duration = '';

      if (this.options.years && this.getYears() > 0) {
        units.push({value: this.getYears(), name: this._tr('human_years')});
      }
      if (this.options.months && this.getMonths() > 0) {
        units.push({value: this.getMonths(), name: this._tr('human_months')});
      }
      if (this.options.days && this.getDays() > 0) {
        units.push({value: this.getDays(), name: this._tr('human_days')});
      }
      if (this.options.hours && this.getHours() > 0) {
        units.push({value: this.getHours(), name: this._tr('human_hours')});
      }
      if (this.options.minutes && this.getMinutes() > 0) {
        units.push({value: this.getMinutes(), name: this._tr('human_minutes')});
      }
      if (this.options.seconds && this.getSeconds() > 0) {
        units.push({value: this.getSeconds(), name: this._tr('human_seconds')});
      }

      for (var i = 0, l = units.length; i < l; ++i) {
        var unit = units[i];
        var separator = i == l - 1 ? '' : (i == l - 2 ? ' ' + this._tr('and') + ' ' : ', ');

        duration += unit.value + ' ' + unit.name + separator;
      }
      return duration;
    }
  });
})(jQuery);
<|endoftext|>