<commit_before>(function (angular) {
    'use strict';

    var module = angular.module('externalLinkInterceptor', [
        'ngRoute',
        'ui.bootstrap'
    ]);

    module.config([
        '$routeProvider',
        function ($routeProvider) {
            $routeProvider
                .when('/external-link/', {
                    templateUrl: 'templates/external_link/page.html',
                    controller: 'ExternalLinkCtrl'
                });
        }
    ]);

    module.run([
        '$templateCache',
        function ($templateCache) {
            $templateCache.put('templates/external_link/message.html',
                '
You are now leaving this website.
' +
                '
' +
                    'Continue' +
                    'Cancel' +
                '
'
            );

            $templateCache.put('templates/external_link/page.html',
                '

'
            );
        }
    ]);

    module.controller('ExternalLinkCtrl', ['$scope', '$location', function ($scope, $location) {
        $scope.externalUrl = $location.$$search.next;

        $scope.cancel = function () {
            if ($location.$$search.prev) {
                $location.url($location.$$search.prev);
            } else {
                $location.url('/');
            }
        };
    }]);

    module.directive('a', ['$filter', '$location', '$modal', function ($filter, $location, $modal) {
        var externalLinkRE = new RegExp(/^([^:\/?#]+:)?(?:\/\/([^\/?#]*))?([^?#]+)?(\?[^#]*)?(#.*)?/);

        return {
            restrict: 'E',
            link: function (scope, element, attrs) {
                // External url testing function from http://stackoverflow.com/a/6238456
                function isExternal(url) {
                    var match = url.match(externalLinkRE);
                    if (typeof match[1] === 'string' && match[1].length > 0 && match[1] === 'mailto:') { return false; }
                    if (typeof match[1] === 'string' && match[1].length > 0 && match[1].toLowerCase() !== location.protocol) { return true; }
                    if (typeof match[2] === 'string' && match[2].length > 0 && match[2].replace(new RegExp(':(' + {'http:' : 80, 'https:' : 443}[location.protocol] + ')?$'), '') !== location.host) { return true; }
                    return false;
                }

                function externalModal(e) {
                    e.preventDefault();

                    // Open a bootstrap-ui modal.
                    $modal.open({
                        templateUrl: 'templates/external_link/message.html',
                        resolve: {
                            externalUrl: function () {
                                return attrs.href;
                            }
                        },
                        controller: ['$scope', '$modalInstance', 'externalUrl', function ($scope, $modalInstance, externalUrl) {
                            $scope.externalUrl = externalUrl;

                            $scope.cancel = function () {
                                $modalInstance.dismiss('cancel');
                            };
                        }]
                    });
                }

                // If the link does not have an attribute to allow it to by-pass the warning.
                if (!attrs.allowExternal) {
                    attrs.$observe('href', function (href) {
                        // The click event may have been bound based on a
                        // previous href value.
                        element.unbind('click', externalModal);
                        if (href) {
                            // If the link is external.
                            if (isExternal(href)) {
                                // Rewrite the url to go to the external link route.
                                // Adds a `next` parameter containing the external link and
                                // a `prev` parameter containing the current path, so that
                                // we can navigate back to where the user came from.
                                element.attr('href', $filter('reverseUrl')('ExternalLinkCtrl') + '?next=' + encodeURI(attrs.href) + '&prev=' + encodeURI($location.path()));

                                element.bind('click', externalModal);
                            }
                        }
                    });
                }
            }
        };
    }]);

}(window.angular));
<commit_msg>Added tel: phone prefix to be ignored alongside mailto<commit_after>(function (angular) {
    'use strict';

    var module = angular.module('externalLinkInterceptor', [
        'ngRoute',
        'ui.bootstrap'
    ]);

    module.config([
        '$routeProvider',
        function ($routeProvider) {
            $routeProvider
                .when('/external-link/', {
                    templateUrl: 'templates/external_link/page.html',
                    controller: 'ExternalLinkCtrl'
                });
        }
    ]);

    module.run([
        '$templateCache',
        function ($templateCache) {
            $templateCache.put('templates/external_link/message.html',
                '
You are now leaving this website.
' +
                '
' +
                    'Continue' +
                    'Cancel' +
                '
'
            );

            $templateCache.put('templates/external_link/page.html',
                '

'
            );
        }
    ]);

    module.controller('ExternalLinkCtrl', ['$scope', '$location', function ($scope, $location) {
        $scope.externalUrl = $location.$$search.next;

        $scope.cancel = function () {
            if ($location.$$search.prev) {
                $location.url($location.$$search.prev);
            } else {
                $location.url('/');
            }
        };
    }]);

    module.directive('a', ['$filter', '$location', '$modal', function ($filter, $location, $modal) {
        var externalLinkRE = new RegExp(/^([^:\/?#]+:)?(?:\/\/([^\/?#]*))?([^?#]+)?(\?[^#]*)?(#.*)?/);

        return {
            restrict: 'E',
            link: function (scope, element, attrs) {
                // External url testing function from http://stackoverflow.com/a/6238456
                function isExternal(url) {
                    var match = url.match(externalLinkRE);

                    if (typeof match[1] === 'string' && match[1].length > 0) {
                        if (match[1] === 'mailto:' || match[1] === 'tel:'){
                           return false;
                        }

                        if (match[1].toLowerCase() !== location.protocol) {
                           return true;
                        }
                    }

                    if (typeof match[2] === 'string' && match[2].length > 0 && match[2].replace(new RegExp(':(' + {'http:' : 80, 'https:' : 443}[location.protocol] + ')?$'), '') !== location.host) { return true; }
                    return false;
                }

                function externalModal(e) {
                    e.preventDefault();

                    // Open a bootstrap-ui modal.
                    $modal.open({
                        templateUrl: 'templates/external_link/message.html',
                        resolve: {
                            externalUrl: function () {
                                return attrs.href;
                            }
                        },
                        controller: ['$scope', '$modalInstance', 'externalUrl', function ($scope, $modalInstance, externalUrl) {
                            $scope.externalUrl = externalUrl;

                            $scope.cancel = function () {
                                $modalInstance.dismiss('cancel');
                            };
                        }]
                    });
                }

                // If the link does not have an attribute to allow it to by-pass the warning.
                if (!attrs.allowExternal) {
                    attrs.$observe('href', function (href) {
                        // The click event may have been bound based on a
                        // previous href value.
                        element.unbind('click', externalModal);
                        if (href) {
                            // If the link is external.
                            if (isExternal(href)) {
                                // Rewrite the url to go to the external link route.
                                // Adds a `next` parameter containing the external link and
                                // a `prev` parameter containing the current path, so that
                                // we can navigate back to where the user came from.
                                element.attr('href', $filter('reverseUrl')('ExternalLinkCtrl') + '?next=' + encodeURI(attrs.href) + '&prev=' + encodeURI($location.path()));

                                element.bind('click', externalModal);
                            }
                        }
                    });
                }
            }
        };
    }]);

}(window.angular));
<|endoftext|>


<commit_before>import React, { Component, PropTypes } from 'react';
import Checkbox from '../checkbox';
import Translation from '../../../behaviours/translation';
import { pull } from 'lodash/array';

/**
 * SelectCheckbox component.
 */
@Translation
class SelectCheckbox extends Component {

    /**
     * Display name.
     */
    static displayName = 'SelectCheckbox';

    /**
     * PropTypes.
     */
    static propTypes = {
        values: PropTypes.array,
        value: PropTypes.array,
        valueKey: PropTypes.string,
        labelKey: PropTypes.string,
        onChange: PropTypes.func
    };

    /**
     * Default props.
     */
    static defaultProps = {
        values: [], // all values
        value: [], // selected values list
        valueKey: 'value', // key for the displayed value
        labelKey: 'label', // key for the displayed label
        onChange: undefined
    };

    /**
     * Initial state.
     */
    state = {
        selectedValues: this.props.value
    };

    /**
     * React componentWillReceiveProps hook.
     * @param {object} newProps new props.
     */
    componentWillReceiveProps(newProps) {
        if (newProps) {
            this.setState({ selectedValues: newProps.value });
        }
    }

    /**
    * Get the value from the select in the DOM.
    * @return {string} value
    */
    getValue() {
        return this.state.selectedValues;
    }

    /**
     * Handle a change of value.
     * @param  {[type]} key       the key
     * @param  {[type]} newStatus the new status
     */
    _handleCheckboxChange(key, newStatus) {
        if (this.props.onChange) {
            this.props.onChange(key, newStatus);
            return;
        }
        const selectedValues = this.state.selectedValues;
        if (newStatus) {
            selectedValues.push(key);
        } else {
            pull(selectedValues, key);
        }
        this.setState({ value: selectedValues });
    }

    /**
     * Closure to capture key and checbox status.
     * @param  {[type]} key the key of checkbox
     * @return {[type]} status closure
     */
    _getCheckboxChangeHandler(key) {
        return (status) => {
            this._handleCheckboxChange(key, status);
        };
    }

    /**
     * Render all selection checkbox.
     * @return {ReactDOMNode} list of ReactDomNode
     */
    renderCheckboxes() {
        return this.props.values.map((val, idx) => {
            const value = val[this.props.valueKey];
            const label = val[this.props.labelKey];
            const isChecked = 0 <= this.state.selectedValues.indexOf(value);
            return (
                <Checkbox key={idx} label={this.i18n(label)} onChange={this._getCheckboxChangeHandler(value)} value={isChecked} />
            );
        });
    }

    /**
     * Render.
     * @return {ReactElement} markup.
     */
    render() {
        return (

                {this.renderCheckboxes()}

        );
    }
}

export default SelectCheckbox;
<commit_msg>Change select-checkbox onChange behavior to return new array of selected values<commit_after>import React, { Component, PropTypes } from 'react';
import Checkbox from '../checkbox';
import Translation from '../../../behaviours/translation';
import { pull } from 'lodash/array';

/**
 * SelectCheckbox component.
 */
@Translation
class SelectCheckbox extends Component {

    /**
     * Display name.
     */
    static displayName = 'SelectCheckbox';

    /**
     * PropTypes.
     */
    static propTypes = {
        values: PropTypes.array,
        value: PropTypes.array,
        valueKey: PropTypes.string,
        labelKey: PropTypes.string,
        onChange: PropTypes.func
    };

    /**
     * Default props.
     */
    static defaultProps = {
        values: [], // all values
        value: [], // selected values list
        valueKey: 'value', // key for the displayed value
        labelKey: 'label', // key for the displayed label
        onChange: undefined
    };

    /**
     * Initial state.
     */
    state = {
        selectedValues: this.props.value
    };

    /**
     * React componentWillReceiveProps hook.
     * @param {object} newProps new props.
     */
    componentWillReceiveProps(newProps) {
        if (newProps) {
            this.setState({ selectedValues: newProps.value });
        }
    }

    /**
    * Get the value from the select in the DOM.
    * @return {string} value
    */
    getValue() {
        return this.state.selectedValues;
    }

    /**
     * Handle a change of value.
     * @param  {[type]} key       the key
     * @param  {[type]} newStatus the new status
     */
    _handleCheckboxChange(key, newStatus) {
        const selectedValues = this.state.selectedValues;
        if (newStatus) {
            selectedValues.push(key);
        } else {
            pull(selectedValues, key);
        }

        if (this.props.onChange) {
            this.props.onChange(selectedValues);
            return;
        }

        this.setState({ value: selectedValues });
    }

    /**
     * Closure to capture key and checbox status.
     * @param  {[type]} key the key of checkbox
     * @return {[type]} status closure
     */
    _getCheckboxChangeHandler(key) {
        return (status) => {
            this._handleCheckboxChange(key, status);
        };
    }

    /**
     * Render all selection checkbox.
     * @return {ReactDOMNode} list of ReactDomNode
     */
    renderCheckboxes() {
        return this.props.values.map((val, idx) => {
            const value = val[this.props.valueKey];
            const label = val[this.props.labelKey];
            const isChecked = 0 <= this.state.selectedValues.indexOf(value);
            return (
                <Checkbox key={idx} label={this.i18n(label)} onChange={this._getCheckboxChangeHandler(value)} value={isChecked} />
            );
        });
    }

    /**
     * Render.
     * @return {ReactElement} markup.
     */
    render() {
        return (

                {this.renderCheckboxes()}

        );
    }
}

export default SelectCheckbox;
<|endoftext|>


<commit_before>/**
 * Created by Zafer on 9.6.2015.
 */
angular.module('starter.controllers', [])

    .controller('DashCtrl', function ($scope) {
        $scope.info =
        {
            games : [
                {link:'./#', img : "img/cover.jpg" , name:'1'},
                {link:'./#', img : "img/cover.jpg" , name:'2'},
                {link:'./#', img : "img/cover.jpg" , name:'3'},
                {link:'./#', img : "img/cover.jpg" , name:'4'},
                {link:'./#', img : "img/cover.jpg" , name:'5img/cover.jpg'},
                {link:'./#', img : "img/cover.jpg" , name:'6'},
                {link:'./#', img : "img/cover.jpg" , name:'7'},
                {link:'./#', img : "img/cover.jpg" , name:'8'},
                {link:'./#', img : "img/cover.jpg" , name:'9'},
                {link:'./#', img : "img/cover.jpg" , name:'10'}
            ]
        };
        $scope.chunker = function chunk(arr, size) {
            var newArr = [];
            for (var i=0; i<arr.length; i+=size) {
                newArr.push(arr.slice(i, i+size));
            }
            return newArr;
        };

        $scope.chunkedData = chunker(games, 4);
    })

    .controller('GameCtrl', function ($scope) {
        $scope.info =
        {
            gameName: 'Zafer Oyunda'
        };
    })

    .controller('LoginCtrl', function ($scope, $ionicModal, $timeout, $ionicLoading, $ionicPopup, $state, Auth) {
        // Form data for the login modal
        $scope.loginData = {};

        $scope.doLogin = function () {
            $ionicLoading.show({
                template: 'Loading...'
            });
            var lData = $scope.loginData;
            Auth.login(lData.email, lData.password, function (err) {
                if (err) {
                    $ionicLoading.hide();
                    console.log(JSON.stringify(err));
                    var alertPopup = $ionicPopup.alert({
                        title: "Login Error",
                        template: err.message || err
                    });
                    alertPopup.then(function (res) {
                        console.log('Thank you for not eating my delicious ice cream cone');
                    });

                    return;
                }
                $scope.loginData = {};
                $state.go('dash');
                $ionicLoading.hide();

                /*   setTimeout(function () {

                 }, 1000);*/

            });
        };
    });<commit_msg>Fix controller<commit_after>/**
 * Created by Zafer on 9.6.2015.
 */
angular.module('starter.controllers', [])

    .controller('DashCtrl', function ($scope) {

        var games = [
            {link: './#', img: "img/cover.jpg", name: '1'},
            {link: './#', img: "img/cover.jpg", name: '2'},
            {link: './#', img: "img/cover.jpg", name: '3'},
            {link: './#', img: "img/cover.jpg", name: '4'},
            {link: './#', img: "img/cover.jpg", name: '5'},
            {link: './#', img: "img/cover.jpg", name: '6'},
            {link: './#', img: "img/cover.jpg", name: '7'},
            {link: './#', img: "img/cover.jpg", name: '8'},
            {link: './#', img: "img/cover.jpg", name: '9'}
        ];

        function chunk(arr, size) {
            var newArr = [];
            for (var i = 0; i < arr.length; i += size) {
                newArr.push(arr.slice(i, i + size));
            }

            return newArr;
        }

        $scope.chunkedData = chunk(games, 4);
    })

    .controller('GameCtrl', function ($scope) {
        $scope.info =
        {
            gameName: 'Zafer Oyunda'
        };
    })

    .controller('LoginCtrl', function ($scope, $ionicModal, $timeout, $ionicLoading, $ionicPopup, $state, Auth) {
        // Form data for the login modal
        $scope.loginData = {};

        $scope.doLogin = function () {
            $ionicLoading.show({
                template: 'Loading...'
            });
            var lData = $scope.loginData;
            Auth.login(lData.email, lData.password, function (err) {
                if (err) {
                    $ionicLoading.hide();
                    console.log(JSON.stringify(err));
                    var alertPopup = $ionicPopup.alert({
                        title: "Login Error",
                        template: err.message || err
                    });
                    alertPopup.then(function (res) {
                        console.log('Thank you for not eating my delicious ice cream cone');
                    });

                    return;
                }
                $scope.loginData = {};
                $state.go('dash');
                $ionicLoading.hide();

                /*   setTimeout(function () {

                 }, 1000);*/

            });
        };
    });<|endoftext|>


<commit_before>'use strict';

const mongo = require('../../lib/mongo');
const parse = require('../../lib/parse');
const settings = require('../../lib/settings');
const emailSender = require('../../lib/email');
const ObjectID = require('mongodb').ObjectID;
const jwt = require('jsonwebtoken');
const cache = require('lru-cache')({
  max: 10000,
  maxAge: 1000 * 60 * 60 * 24 // 24h
});
const BLACKLIST_CACHE_KEY = 'blacklist';

class SecurityTokensService {
  constructor() {}

  getTokens(params = {}) {
    let filter = {
      is_revoked: false
    };
    const id = parse.getObjectIDIfValid(params.id);
    if (id) {
      filter._id = new ObjectID(id);
    }

    const email = parse.getString(params.email);
    if (email && email.length > 0) {
      filter.email = email;
    }

    return mongo.db.collection('tokens').find(filter).toArray().then(items => items.map(item => this.renameDocumentFields(item)))
  }

  getTokensBlacklist() {
    const blacklistFromCache = cache.get(BLACKLIST_CACHE_KEY);

    if (blacklistFromCache) {
      return Promise.resolve(blacklistFromCache);
    } else {
      return mongo.db.collection('tokens').find({
        is_revoked: true
      }, {_id: 1}).toArray().then(items => {
        const blacklistFromDB = items.map(item => item._id.toString());
        cache.set(BLACKLIST_CACHE_KEY, blacklistFromDB);
        return blacklistFromDB;
      })
    }
  }

  getSingleToken(id) {
    if (!ObjectID.isValid(id)) {
      return Promise.reject('Invalid identifier');
    }
    return this.getTokens({id: id}).then(items => {
      return items.length > 0
        ? items[0]
        : null;
    })
  }

  getSingleTokenByEmail(email) {
    return this.getTokens({email}).then(items => {
      return items.length > 0
        ? items[0]
        : null;
    })
  }

  addToken(data) {
    return this.getDocumentForInsert(data).then(tokenData => mongo.db.collection('tokens').insertMany([tokenData])).then(res => this.getSingleToken(res.ops[0]._id.toString())).then(token => this.getSignedToken(token).then(signedToken => {
      token.token = signedToken;
      return token;
    }))
  }

  updateToken(id, data) {
    if (!ObjectID.isValid(id)) {
      return Promise.reject('Invalid identifier');
    }
    const tokenObjectID = new ObjectID(id);
    const token = this.getDocumentForUpdate(id, data);

    return mongo.db.collection('tokens').updateOne({
      _id: tokenObjectID
    }, {$set: token}).then(res => this.getSingleToken(id));
  }

  deleteToken(id) {
    if (!ObjectID.isValid(id)) {
      return Promise.reject('Invalid identifier');
    }
    const tokenObjectID = new ObjectID(id);
    return mongo.db.collection('tokens').updateOne({
      _id: tokenObjectID
    }, {
      $set: {
        is_revoked: true,
        date_created: new Date()
      }
    }).then(res => {
      cache.del(BLACKLIST_CACHE_KEY);
    });
  }

  checkTokenEmailUnique(email) {
    return mongo.db.collection('tokens').count({email: email, is_revoked: false}).then(count => count === 0
      ? email
      : Promise.reject('Token email must be unique'));
  }

  getDocumentForInsert(data) {
    const email = parse.getString(data.email);
    return this.checkTokenEmailUnique(email).then(email => {
      let token = {
        is_revoked: false,
        date_created: new Date()
      }

      token.name = parse.getString(data.name);
      token.email = email;
      token.scopes = parse.getArrayIfValid(data.scopes);
      token.expiration = parse.getNumberIfPositive(data.expiration);

      return token;
    })
  }

  getDocumentForUpdate(id, data) {
    if (Object.keys(data).length === 0) {
      return new Error('Required fields are missing');
    }

    let token = {
      'date_updated': new Date()
    }

    if (data.name !== undefined) {
      token.name = parse.getString(data.name);
    }

    return token;
  }

  renameDocumentFields(item) {
    if (item) {
      item.id = item._id.toString();
      delete item._id;
      delete item.is_revoked;
    }

    return item;
  }

  getSignedToken(token) {
    return new Promise((resolve, reject) => {
      const jwtOptions = {};

      let payload = {
        email: token.email,
        scopes: token.scopes,
        jti: token.id
      }

      if (token.expiration) {
        // convert hour to sec
        jwtOptions.expiresIn = token.expiration * 60 * 60;
      }

      jwt.sign(payload, settings.security.jwtSecret, jwtOptions, (err, token) => {
        if (err) {
          reject(err)
        } else {
          resolve(token)
        }
      });
    })
  }

  getDashboardSigninUrl(email) {
    return this.getSingleTokenByEmail(email).then(token => {
      if(token) {
        return this.getSignedToken(token).then(signedToken => {
          return `${settings.adminLoginUrl}?token=${signedToken}`;
        })
      } else {
        return null;
      }
    })
  }

  sendDashboardSigninUrl(email) {
    return this.getDashboardSigninUrl(email).then(linkUrl => {
      if(linkUrl) {
        const message = {
          to: email,
          subject: `Sign-in URL`,
          html: `<p><a href='${linkUrl}'>Sign-in</a></p>`
        };
        return emailSender.send(message).then(info => {return { sent: true, error: null }}).catch(err => {return { sent: false, error: err }});
      } else {
        return { sent: false, error: 'Access Denied' };
      }
    });
  }
}

module.exports = new SecurityTokensService();
<commit_msg>Allow change token expiration<commit_after>'use strict';

const mongo = require('../../lib/mongo');
const parse = require('../../lib/parse');
const settings = require('../../lib/settings');
const emailSender = require('../../lib/email');
const ObjectID = require('mongodb').ObjectID;
const jwt = require('jsonwebtoken');
const cache = require('lru-cache')({
  max: 10000,
  maxAge: 1000 * 60 * 60 * 24 // 24h
});
const BLACKLIST_CACHE_KEY = 'blacklist';

class SecurityTokensService {
  constructor() {}

  getTokens(params = {}) {
    let filter = {
      is_revoked: false
    };
    const id = parse.getObjectIDIfValid(params.id);
    if (id) {
      filter._id = new ObjectID(id);
    }

    const email = parse.getString(params.email);
    if (email && email.length > 0) {
      filter.email = email;
    }

    return mongo.db.collection('tokens').find(filter).toArray().then(items => items.map(item => this.renameDocumentFields(item)))
  }

  getTokensBlacklist() {
    const blacklistFromCache = cache.get(BLACKLIST_CACHE_KEY);

    if (blacklistFromCache) {
      return Promise.resolve(blacklistFromCache);
    } else {
      return mongo.db.collection('tokens').find({
        is_revoked: true
      }, {_id: 1}).toArray().then(items => {
        const blacklistFromDB = items.map(item => item._id.toString());
        cache.set(BLACKLIST_CACHE_KEY, blacklistFromDB);
        return blacklistFromDB;
      })
    }
  }

  getSingleToken(id) {
    if (!ObjectID.isValid(id)) {
      return Promise.reject('Invalid identifier');
    }
    return this.getTokens({id: id}).then(items => {
      return items.length > 0
        ? items[0]
        : null;
    })
  }

  getSingleTokenByEmail(email) {
    return this.getTokens({email}).then(items => {
      return items.length > 0
        ? items[0]
        : null;
    })
  }

  addToken(data) {
    return this.getDocumentForInsert(data).then(tokenData => mongo.db.collection('tokens').insertMany([tokenData])).then(res => this.getSingleToken(res.ops[0]._id.toString())).then(token => this.getSignedToken(token).then(signedToken => {
      token.token = signedToken;
      return token;
    }))
  }

  updateToken(id, data) {
    if (!ObjectID.isValid(id)) {
      return Promise.reject('Invalid identifier');
    }
    const tokenObjectID = new ObjectID(id);
    const token = this.getDocumentForUpdate(id, data);

    return mongo.db.collection('tokens').updateOne({
      _id: tokenObjectID
    }, {$set: token}).then(res => this.getSingleToken(id));
  }

  deleteToken(id) {
    if (!ObjectID.isValid(id)) {
      return Promise.reject('Invalid identifier');
    }
    const tokenObjectID = new ObjectID(id);
    return mongo.db.collection('tokens').updateOne({
      _id: tokenObjectID
    }, {
      $set: {
        is_revoked: true,
        date_created: new Date()
      }
    }).then(res => {
      cache.del(BLACKLIST_CACHE_KEY);
    });
  }

  checkTokenEmailUnique(email) {
    return mongo.db.collection('tokens').count({email: email, is_revoked: false}).then(count => count === 0
      ? email
      : Promise.reject('Token email must be unique'));
  }

  getDocumentForInsert(data) {
    const email = parse.getString(data.email);
    return this.checkTokenEmailUnique(email).then(email => {
      let token = {
        is_revoked: false,
        date_created: new Date()
      }

      token.name = parse.getString(data.name);
      token.email = email;
      token.scopes = parse.getArrayIfValid(data.scopes);
      token.expiration = parse.getNumberIfPositive(data.expiration);

      return token;
    })
  }

  getDocumentForUpdate(id, data) {
    if (Object.keys(data).length === 0) {
      return new Error('Required fields are missing');
    }

    let token = {
      'date_updated': new Date()
    }

    if (data.name !== undefined) {
      token.name = parse.getString(data.name);
    }

    if (data.expiration !== undefined) {
      token.expiration = parse.getNumberIfPositive(data.expiration);
    }

    return token;
  }

  renameDocumentFields(item) {
    if (item) {
      item.id = item._id.toString();
      delete item._id;
      delete item.is_revoked;
    }

    return item;
  }

  getSignedToken(token) {
    return new Promise((resolve, reject) => {
      const jwtOptions = {};

      let payload = {
        email: token.email,
        scopes: token.scopes,
        jti: token.id
      }

      if (token.expiration) {
        // convert hour to sec
        jwtOptions.expiresIn = token.expiration * 60 * 60;
      }

      jwt.sign(payload, settings.security.jwtSecret, jwtOptions, (err, token) => {
        if (err) {
          reject(err)
        } else {
          resolve(token)
        }
      });
    })
  }

  getDashboardSigninUrl(email) {
    return this.getSingleTokenByEmail(email).then(token => {
      if(token) {
        return this.getSignedToken(token).then(signedToken => {
          return `${settings.adminLoginUrl}?token=${signedToken}`;
        })
      } else {
        return null;
      }
    })
  }

  sendDashboardSigninUrl(email) {
    return this.getDashboardSigninUrl(email).then(linkUrl => {
      if(linkUrl) {
        const message = {
          to: email,
          subject: `Sign-in URL`,
          html: `<p><a href='${linkUrl}'>Sign-in</a></p>`
        };
        return emailSender.send(message).then(info => {return { sent: true, error: null }}).catch(err => {return { sent: false, error: err }});
      } else {
        return { sent: false, error: 'Access Denied' };
      }
    });
  }
}

module.exports = new SecurityTokensService();
<|endoftext|>


<commit_before>require('jquery');

module.exports = function(hash) {
  if ($(hash).length === 1) {
    if ($(hash).css('top') === 'auto' || '0') {
      $(window).scrollTop($(hash).offset().top - $('#global-header').height());
    }
  }
};
<commit_msg>CATO-2939: Fixed problem with 'contentNudge' module trying to scroll to any element from 'hash' even though some selectors might not be correct.<commit_after>require('jquery');

module.exports = function(hash) {
  var targetElement;
  try {
    targetElement = $(hash);
  }
  catch (ex) {
    /* This can only happen if selector from 'hash' is not valid. */
    return;
  }

  if (targetElement.length === 1) {
    if (targetElement.css('top') === 'auto' || '0') {
      $(window).scrollTop(targetElement.offset().top - $('#global-header').height());
    }
  }
};
<|endoftext|>


<commit_before>angular.module("angular-simple-table",[])
    .directive('simpleTable',['$filter','$compile','filterFilter', function($filter,$compile,filterFilter) {
        'use strict';
        window.TableSettings = function (passedData){
            var data = passedData || null;
            var reverseSort = false;
            var currentPage = 1;
            var filteredData = null;
            var rows = 25;
            var orderField = null;
            var searchFields = {};

            //ReverseSort
            this.setReverseSort = function(param){ reverseSort = param; };
            this.getReverseSort = function(){ return reverseSort; };

            //OrderField
            this.setOrderField = function(param){ orderField = param; };
            this.getOrderField = function(){ return orderField; };

            //Data
            this.setData = function(param){ data = param; };
            this.getData = function(){ return data; };

            //Rows per page
            this.setRows = function(param){ this.setPage(1); rows = parseInt(param,10); };
            this.getRows = function(){ return rows; };

            //Page nr
            this.getPage = function(){return currentPage;};
            this.setPage = function(nr){currentPage = parseInt(nr,10);};

            /**
             * getTotalPagesArray
             *
             * @return {Array} - has length = total pages
             */
            this.getTotalPagesArray = function(){
                if(filteredData){
                    return new Array(Math.ceil(filteredData.length/rows))};
            };

            /**
             *  getTotalPages
             *
             * @returns {integer}
             */
            this.getTotalPages = function(){
                if(filteredData){
                    return Math.ceil(filteredData.length/rows)
                }
            };

            /**
             * getFilteredData
             *
             * @returns {Array}  filtered data by search,rows per page and ordered
             */
            this.getFilteredData = function(){
                //convert to array
                if(!(data instanceof Array) && typeof data === 'object' && data !== null){
                    var keys = Object.keys(data);
                    filteredData = data;
                    filteredData = keys.map(function (key) {
                        return filteredData[key]; });
                }else{
                    filteredData = data;
                }

                //searchBy
                if(Object.keys(searchFields).length){
                    var result = [];
                    for (var property in searchFields) {
                        if (searchFields.hasOwnProperty(property)) {
                            //search for each field
                            if(property.indexOf('.') === -1)
                            {
                                var filtered = simpleSearch(searchFields[property],property,filteredData);

                            }else{
                                var filtered = subArraySearch(searchFields[property],property,filteredData);
                            }
                            filtered.forEach(function(elem,index,arr){
                                result.push(elem);
                            });

                        }
                    };
                    filteredData = remove_duplicates(result);
                }
                //orderBy
                if(typeof orderField == 'string'){
                    filteredData = $filter('orderBy')(filteredData, orderField.toLowerCase(), reverseSort);
                }
                if(filteredData && data ){
                    //filter Current Page and Number of Items Per Page
                    var startIndex = (currentPage - 1) * rows;
                    //avoid non-existent page
                    if(startIndex > filteredData.length){
                        this.setPage(1);
                    }
                    return filteredData.slice(startIndex,startIndex + rows);
                }else{
                    filteredData = data;
                }

            };

            function simpleSearch(text,field,data){
                var searched = [];
                var index = field || '$';
                searched[index] = text;
                var result = $filter('filter')(data, searched, false);
                return result;
            };
            function subArraySearch(text,field,data){
                var properties = field.split(".");
                var searchedField = [];
                searchedField[properties[1]] = text ;
                var result = [];
                data.forEach(function(elem,index,arr){
                    var filtered = $filter('filter')(elem[properties[0]],searchedField, false);
                    if(filtered.length > 0){
                        result.push(filteredData[index])
                    }
                });
                return result;
            };
            function remove_duplicates(objectsArray) {
                var usedObjects = {};

                for (var i=objectsArray.length - 1;i>=0;i--) {
                    var so = JSON.stringify(objectsArray[i]);

                    if (usedObjects[so]) {
                        objectsArray.splice(i, 1);

                    } else {
                        usedObjects[so] = true;
                    }
                }

                return objectsArray;

            }

            /**
             *  search
             *
             *  Searches through the data by one or more fields
             *
             * @param text | string  Search String
             * @param field... | string  Name of the field to search by. From the second argument, every argument is a field to search by
             */
            this.search = function(text,field){
                currentPage = 1;
                orderField = null;
                searchFields = {};
                //search by one field
                if(arguments.length<2){
                    var field = arguments[1] || '$';
                    searchFields[field] = arguments[0];
                }else{
                    //search by multiple fields
                    for (var i = 1; i < arguments.length; i++) {
                        var field = arguments[i];
                        searchFields[field] = arguments[0];
                    }
                }
            };
            /**
             *
             *    Order By
             *
             * @param field | string The name of the field to order by
             * @param keepReverseState  | boolean  If true - reverse state remains the same.
             */
            this.orderBy = function(field,keepReverseState){
                orderField = field;
                if(!keepReverseState){
                    reverseSort = !reverseSort;
                }
            };
        };

        var link = function(scope, iElement, iAttrs){
            $compile(iElement);
            //assign the table to directive's isolated scope
            var table = document.querySelectorAll('[simple-table]');
            var simpleTable = iAttrs['simpleTable'];
            for(var item in table){
                if(table.hasOwnProperty(item)){
                    var currentItem = table[item];
                    if(iAttrs['simpleTable'] === currentItem.getAttribute("simple-table") && currentItem.tagName === "TABLE"){
                        scope.table = table[item];
                    };
                }
            }
            //add thead
            iElement.prepend(document.createElement("tHead"));

            //watch for data change
            scope.$watch(
                function(){
                    var row = scope.table.children[1].children[0];
                    return row;
                },
                function(newValue,oldValue){
                    //if table rows appear for the first time
                    if(typeof newValue !== 'undefined'){
                        var row = newValue;

                        //select tr from thead
                        var headRow = scope.table.children[0];
                        headRow.innerHTML='';
                        var allTds = row.children;
                        var tr = document.createElement("tr");
                        //add th to thead
                        for(var item in allTds){
                            if(allTds.hasOwnProperty(item)){
                                var th = document.createElement("th");

                                var dataSort = allTds[item].getAttribute("data-sort-by");
                                if(dataSort){
                                    var title = allTds[item].getAttribute("title") || '';
                                    th.innerHTML = ''+
                                        title + '  ▲▼' ;
                                }else{
                                    th.innerHTML = allTds[item].getAttribute("title");
                                }

                                tr.appendChild(th);
                            }
                        }
                        //compiled the data to the parent scope
                        var compiled = $compile(tr)(scope.$parent);
                        headRow.appendChild(compiled[0]);
                    }
                });
        };

        return {
            scope:{
                simpleTable: "&"
            },
            restrict: 'EA',
            template: '',
            link: link,
        };
    }]);
<commit_msg>Update angular-simple-table.js<commit_after>/**
* Name:  Angular Simple Table
*
* Version: 1.0.0
*
* Author: Andrei Pericleanu
*		  andrei_pericleanu@yahoo.com
*
*
*/

angular.module("angular-simple-table",[])
    .directive('simpleTable',['$filter','$compile','filterFilter', function($filter,$compile,filterFilter) {
        'use strict';
        window.TableSettings = function (passedData){
            var data = passedData || null;
            var reverseSort = false;
            var currentPage = 1;
            var filteredData = null;
            var rows = 25;
            var orderField = null;
            var searchFields = {};

            //ReverseSort
            this.setReverseSort = function(param){ reverseSort = param; };
            this.getReverseSort = function(){ return reverseSort; };

            //OrderField
            this.setOrderField = function(param){ orderField = param; };
            this.getOrderField = function(){ return orderField; };

            //Data
            this.setData = function(param){ data = param; };
            this.getData = function(){ return data; };

            //Rows per page
            this.setRows = function(param){ this.setPage(1); rows = parseInt(param,10); };
            this.getRows = function(){ return rows; };

            //Page nr
            this.getPage = function(){return currentPage;};
            this.setPage = function(nr){currentPage = parseInt(nr,10);};

            /**
             * getTotalPagesArray
             *
             * @return {Array} - has length = total pages
             */
            this.getTotalPagesArray = function(){
                if(filteredData){
                    return new Array(Math.ceil(filteredData.length/rows))};
            };

            /**
             *  getTotalPages
             *
             * @returns {integer}
             */
            this.getTotalPages = function(){
                if(filteredData){
                    return Math.ceil(filteredData.length/rows)
                }
            };

            /**
             * getFilteredData
             *
             * @returns {Array}  filtered data by search,rows per page and ordered
             */
            this.getFilteredData = function(){
                //convert to array
                if(!(data instanceof Array) && typeof data === 'object' && data !== null){
                    var keys = Object.keys(data);
                    filteredData = data;
                    filteredData = keys.map(function (key) {
                        return filteredData[key]; });
                }else{
                    filteredData = data;
                }

                //searchBy
                if(Object.keys(searchFields).length){
                    var result = [];
                    for (var property in searchFields) {
                        if (searchFields.hasOwnProperty(property)) {
                            //search for each field
                            if(property.indexOf('.') === -1)
                            {
                                var filtered = simpleSearch(searchFields[property],property,filteredData);

                            }else{
                                var filtered = subArraySearch(searchFields[property],property,filteredData);
                            }
                            filtered.forEach(function(elem,index,arr){
                                result.push(elem);
                            });

                        }
                    };
                    filteredData = remove_duplicates(result);
                }
                //orderBy
                if(typeof orderField == 'string'){
                    filteredData = $filter('orderBy')(filteredData, orderField.toLowerCase(), reverseSort);
                }
                if(filteredData && data ){
                    //filter Current Page and Number of Items Per Page
                    var startIndex = (currentPage - 1) * rows;
                    //avoid non-existent page
                    if(startIndex > filteredData.length){
                        this.setPage(1);
                    }
                    return filteredData.slice(startIndex,startIndex + rows);
                }else{
                    filteredData = data;
                }

            };

            function simpleSearch(text,field,data){
                var searched = [];
                var index = field || '$';
                searched[index] = text;
                var result = $filter('filter')(data, searched, false);
                return result;
            };
            function subArraySearch(text,field,data){
                var properties = field.split(".");
                var searchedField = [];
                searchedField[properties[1]] = text ;
                var result = [];
                data.forEach(function(elem,index,arr){
                    var filtered = $filter('filter')(elem[properties[0]],searchedField, false);
                    if(filtered.length > 0){
                        result.push(filteredData[index])
                    }
                });
                return result;
            };
            function remove_duplicates(objectsArray) {
                var usedObjects = {};

                for (var i=objectsArray.length - 1;i>=0;i--) {
                    var so = JSON.stringify(objectsArray[i]);

                    if (usedObjects[so]) {
                        objectsArray.splice(i, 1);

                    } else {
                        usedObjects[so] = true;
                    }
                }

                return objectsArray;

            }

            /**
             *  search
             *
             *  Searches through the data by one or more fields
             *
             * @param text | string  Search String
             * @param field... | string  Name of the field to search by. From the second argument, every argument is a field to search by
             */
            this.search = function(text,field){
                currentPage = 1;
                orderField = null;
                searchFields = {};
                //search by one field
                if(arguments.length<2){
                    var field = arguments[1] || '$';
                    searchFields[field] = arguments[0];
                }else{
                    //search by multiple fields
                    for (var i = 1; i < arguments.length; i++) {
                        var field = arguments[i];
                        searchFields[field] = arguments[0];
                    }
                }
            };
            /**
             *
             *    Order By
             *
             * @param field | string The name of the field to order by
             * @param keepReverseState  | boolean  If true - reverse state remains the same.
             */
            this.orderBy = function(field,keepReverseState){
                orderField = field;
                if(!keepReverseState){
                    reverseSort = !reverseSort;
                }
            };
        };

        var link = function(scope, iElement, iAttrs){
            $compile(iElement);
            //assign the table to directive's isolated scope
            var table = document.querySelectorAll('[simple-table]');
            var simpleTable = iAttrs['simpleTable'];
            for(var item in table){
                if(table.hasOwnProperty(item)){
                    var currentItem = table[item];
                    if(iAttrs['simpleTable'] === currentItem.getAttribute("simple-table") && currentItem.tagName === "TABLE"){
                        scope.table = table[item];
                    };
                }
            }
            //add thead
            iElement.prepend(document.createElement("tHead"));

            //watch for data change
            scope.$watch(
                function(){
                    var row = scope.table.children[1].children[0];
                    return row;
                },
                function(newValue,oldValue){
                    //if table rows appear for the first time
                    if(typeof newValue !== 'undefined'){
                        var row = newValue;

                        //select tr from thead
                        var headRow = scope.table.children[0];
                        headRow.innerHTML='';
                        var allTds = row.children;
                        var tr = document.createElement("tr");
                        //add th to thead
                        for(var item in allTds){
                            if(allTds.hasOwnProperty(item)){
                                var th = document.createElement("th");

                                var dataSort = allTds[item].getAttribute("data-sort-by");
                                if(dataSort){
                                    var title = allTds[item].getAttribute("title") || '';
                                    th.innerHTML = ''+
                                        title + '  ▲▼' ;
                                }else{
                                    th.innerHTML = allTds[item].getAttribute("title");
                                }

                                tr.appendChild(th);
                            }
                        }
                        //compiled the data to the parent scope
                        var compiled = $compile(tr)(scope.$parent);
                        headRow.appendChild(compiled[0]);
                    }
                });
        };

        return {
            scope:{
                simpleTable: "&"
            },
            restrict: 'EA',
            template: '',
            link: link,
        };
    }]);
<|endoftext|>


<commit_before>import { expect } from 'chai';
import ClientAPI from '../client_api';

const { describe, it } = global;

class StoryStore {
  constructor() {
    this.stories = [];
  }

  addStory(kind, story, fn) {
    this.stories.push({ kind, story, fn });
  }

  getStoryKinds() {
    return this.stories.reduce((kinds, info) => {
      if (kinds.indexOf(info.kind) === -1) {
        kinds.push(info.kind);
      }
      return kinds;
    }, []);
  }

  getStories(kind) {
    return this.stories.reduce((stories, info) => {
      if (info.kind === kind) {
        stories.push(info.story);
      }
      return stories;
    }, []);
  }

  getStory(kind, name) {
    return this.stories.reduce((fn, info) => {
      if (!fn && info.kind === kind && info.story === name) {
        return info.fn;
      }
      return fn;
    }, null);
  }
}

describe('preview.client_api', () => {
  describe('setAddon', () => {
    it('should register addons', () => {
      const api = new ClientAPI({});
      let data;

      api.setAddon({
        aa() {
          data = 'foo';
        },
      });

      api.storiesOf('none').aa();
      expect(data).to.be.equal('foo');
    });

    it('should not remove previous addons', () => {
      const api = new ClientAPI({});
      const data = [];

      api.setAddon({
        aa() {
          data.push('foo');
        },
      });

      api.setAddon({
        bb() {
          data.push('bar');
        },
      });

      api.storiesOf('none').aa().bb();
      expect(data).to.deep.equal(['foo', 'bar']);
    });

    it('should call with the api context', () => {
      const api = new ClientAPI({});
      let data;

      api.setAddon({
        aa() {
          data = typeof this.add;
        },
      });

      api.storiesOf('none').aa();
      expect(data).to.be.equal('function');
    });

    it('should be able to access addons added previously', () => {
      const api = new ClientAPI({});
      let data;

      api.setAddon({
        aa() {
          data = 'foo';
        },
      });

      api.setAddon({
        bb() {
          this.aa();
        },
      });

      api.storiesOf('none').bb();
      expect(data).to.be.equal('foo');
    });

    it('should be able to access the current kind', () => {
      const api = new ClientAPI({});
      const kind = 'dfdwf3e3';
      let data;

      api.setAddon({
        aa() {
          data = this.kind;
        },
      });

      api.storiesOf(kind).aa();
      expect(data).to.be.equal(kind);
    });
  });

  describe('addDecorator', () => {
    it('should add local decorators', () => {
      const storyStore = new StoryStore();
      const api = new ClientAPI({ storyStore });
      const localApi = api.storiesOf('none');
      localApi.addDecorator(function (fn) {
        return `aa-${fn()}`;
      });

      localApi.add('storyName', () => ('Hello'));
      expect(storyStore.stories[0].fn()).to.be.equal('aa-Hello');
    });

    it('should add global decorators', () => {
      const storyStore = new StoryStore();
      const api = new ClientAPI({ storyStore });
      api.addDecorator(function (fn) {
        return `bb-${fn()}`;
      });
      const localApi = api.storiesOf('none');

      localApi.add('storyName', () => ('Hello'));
      expect(storyStore.stories[0].fn()).to.be.equal('bb-Hello');
    });

    it('should utilize both decorators at once', () => {
      const storyStore = new StoryStore();
      const api = new ClientAPI({ storyStore });
      const localApi = api.storiesOf('none');

      api.addDecorator(function (fn) {
        return `aa-${fn()}`;
      });
      localApi.addDecorator(function (fn) {
        return `bb-${fn()}`;
      });

      localApi.add('storyName', () => ('Hello'));
      expect(storyStore.stories[0].fn()).to.be.equal('aa-bb-Hello');
    });

    it('should pass the context', () => {
      const storyStore = new StoryStore();
      const api = new ClientAPI({ storyStore });
      const localApi = api.storiesOf('none');
      localApi.addDecorator(function (fn) {
        return `aa-${fn()}`;
      });

      localApi.add('storyName', ({ kind, story }) => (`${kind}-${story}`));

      const kind = 'dfdfd';
      const story = 'ef349ff';

      const result = storyStore.stories[0].fn({ kind, story });
      expect(result).to.be.equal(`aa-${kind}-${story}`);
    });

    it('should have access to the context', () => {
      const storyStore = new StoryStore();
      const api = new ClientAPI({ storyStore });
      const localApi = api.storiesOf('none');
      localApi.addDecorator(function (fn, { kind, story }) {
        return `${kind}-${story}-${fn()}`;
      });

      localApi.add('storyName', () => ('Hello'));

      const kind = 'dfdfd';
      const story = 'ef349ff';

      const result = storyStore.stories[0].fn({ kind, story });
      expect(result).to.be.equal(`${kind}-${story}-Hello`);
    });
  });

  describe('clearDecorators', () => {
    it('should remove all global decorators', () => {
      const api = new ClientAPI({});
      api._globalDecorators = 1234;
      api.clearDecorators();
      expect(api._globalDecorators).to.deep.equal([]);
    });
  });

  describe('getStorybook', () => {
    it('should return storybook when empty', () => {
      const storyStore = new StoryStore();
      const api = new ClientAPI({ storyStore });
      const book = api.getStorybook();
      expect(book).to.deep.equal([]);
    });

    it('should return storybook with stories', () => {
      const storyStore = new StoryStore();
      const api = new ClientAPI({ storyStore });
      const functions = {
        'story-1.1': () => 'story-1.1',
        'story-1.2': () => 'story-1.2',
        'story-2.1': () => 'story-2.1',
        'story-2.2': () => 'story-2.2',
      };
      const kind1 = api.storiesOf('kind-1');
      kind1.add('story-1.1', functions['story-1.1']);
      kind1.add('story-1.2', functions['story-1.2']);
      const kind2 = api.storiesOf('kind-2');
      kind2.add('story-2.1', functions['story-2.1']);
      kind2.add('story-2.2', functions['story-2.2']);
      const book = api.getStorybook();
      expect(book).to.deep.equal([
        {
          kind: 'kind-1',
          stories: [
            { name: 'story-1.1', render: functions['story-1.1'] },
            { name: 'story-1.2', render: functions['story-1.2'] },
          ],
        },
        {
          kind: 'kind-2',
          stories: [
            { name: 'story-2.1', render: functions['story-2.1'] },
            { name: 'story-2.2', render: functions['story-2.2'] },
          ],
        },
      ]);
    });
  });
});
<commit_msg>add hasStory method to StoryStore class in tests<commit_after>import { expect } from 'chai';
import ClientAPI from '../client_api';

const { describe, it } = global;

class StoryStore {
  constructor() {
    this.stories = [];
  }

  addStory(kind, story, fn) {
    this.stories.push({ kind, story, fn });
  }

  getStoryKinds() {
    return this.stories.reduce((kinds, info) => {
      if (kinds.indexOf(info.kind) === -1) {
        kinds.push(info.kind);
      }
      return kinds;
    }, []);
  }

  getStories(kind) {
    return this.stories.reduce((stories, info) => {
      if (info.kind === kind) {
        stories.push(info.story);
      }
      return stories;
    }, []);
  }

  getStory(kind, name) {
    return this.stories.reduce((fn, info) => {
      if (!fn && info.kind === kind && info.story === name) {
        return info.fn;
      }
      return fn;
    }, null);
  }

  hasStory(kind, name) {
    return Boolean(this.getStory(kind, name));
  }
}

describe('preview.client_api', () => {
  describe('setAddon', () => {
    it('should register addons', () => {
      const api = new ClientAPI({});
      let data;

      api.setAddon({
        aa() {
          data = 'foo';
        },
      });

      api.storiesOf('none').aa();
      expect(data).to.be.equal('foo');
    });

    it('should not remove previous addons', () => {
      const api = new ClientAPI({});
      const data = [];

      api.setAddon({
        aa() {
          data.push('foo');
        },
      });

      api.setAddon({
        bb() {
          data.push('bar');
        },
      });

      api.storiesOf('none').aa().bb();
      expect(data).to.deep.equal(['foo', 'bar']);
    });

    it('should call with the api context', () => {
      const api = new ClientAPI({});
      let data;

      api.setAddon({
        aa() {
          data = typeof this.add;
        },
      });

      api.storiesOf('none').aa();
      expect(data).to.be.equal('function');
    });

    it('should be able to access addons added previously', () => {
      const api = new ClientAPI({});
      let data;

      api.setAddon({
        aa() {
          data = 'foo';
        },
      });

      api.setAddon({
        bb() {
          this.aa();
        },
      });

      api.storiesOf('none').bb();
      expect(data).to.be.equal('foo');
    });

    it('should be able to access the current kind', () => {
      const api = new ClientAPI({});
      const kind = 'dfdwf3e3';
      let data;

      api.setAddon({
        aa() {
          data = this.kind;
        },
      });

      api.storiesOf(kind).aa();
      expect(data).to.be.equal(kind);
    });
  });

  describe('addDecorator', () => {
    it('should add local decorators', () => {
      const storyStore = new StoryStore();
      const api = new ClientAPI({ storyStore });
      const localApi = api.storiesOf('none');
      localApi.addDecorator(function (fn) {
        return `aa-${fn()}`;
      });

      localApi.add('storyName', () => ('Hello'));
      expect(storyStore.stories[0].fn()).to.be.equal('aa-Hello');
    });

    it('should add global decorators', () => {
      const storyStore = new StoryStore();
      const api = new ClientAPI({ storyStore });
      api.addDecorator(function (fn) {
        return `bb-${fn()}`;
      });
      const localApi = api.storiesOf('none');

      localApi.add('storyName', () => ('Hello'));
      expect(storyStore.stories[0].fn()).to.be.equal('bb-Hello');
    });

    it('should utilize both decorators at once', () => {
      const storyStore = new StoryStore();
      const api = new ClientAPI({ storyStore });
      const localApi = api.storiesOf('none');

      api.addDecorator(function (fn) {
        return `aa-${fn()}`;
      });
      localApi.addDecorator(function (fn) {
        return `bb-${fn()}`;
      });

      localApi.add('storyName', () => ('Hello'));
      expect(storyStore.stories[0].fn()).to.be.equal('aa-bb-Hello');
    });

    it('should pass the context', () => {
      const storyStore = new StoryStore();
      const api = new ClientAPI({ storyStore });
      const localApi = api.storiesOf('none');
      localApi.addDecorator(function (fn) {
        return `aa-${fn()}`;
      });

      localApi.add('storyName', ({ kind, story }) => (`${kind}-${story}`));

      const kind = 'dfdfd';
      const story = 'ef349ff';

      const result = storyStore.stories[0].fn({ kind, story });
      expect(result).to.be.equal(`aa-${kind}-${story}`);
    });

    it('should have access to the context', () => {
      const storyStore = new StoryStore();
      const api = new ClientAPI({ storyStore });
      const localApi = api.storiesOf('none');
      localApi.addDecorator(function (fn, { kind, story }) {
        return `${kind}-${story}-${fn()}`;
      });

      localApi.add('storyName', () => ('Hello'));

      const kind = 'dfdfd';
      const story = 'ef349ff';

      const result = storyStore.stories[0].fn({ kind, story });
      expect(result).to.be.equal(`${kind}-${story}-Hello`);
    });
  });

  describe('clearDecorators', () => {
    it('should remove all global decorators', () => {
      const api = new ClientAPI({});
      api._globalDecorators = 1234;
      api.clearDecorators();
      expect(api._globalDecorators).to.deep.equal([]);
    });
  });

  describe('getStorybook', () => {
    it('should return storybook when empty', () => {
      const storyStore = new StoryStore();
      const api = new ClientAPI({ storyStore });
      const book = api.getStorybook();
      expect(book).to.deep.equal([]);
    });

    it('should return storybook with stories', () => {
      const storyStore = new StoryStore();
      const api = new ClientAPI({ storyStore });
      const functions = {
        'story-1.1': () => 'story-1.1',
        'story-1.2': () => 'story-1.2',
        'story-2.1': () => 'story-2.1',
        'story-2.2': () => 'story-2.2',
      };
      const kind1 = api.storiesOf('kind-1');
      kind1.add('story-1.1', functions['story-1.1']);
      kind1.add('story-1.2', functions['story-1.2']);
      const kind2 = api.storiesOf('kind-2');
      kind2.add('story-2.1', functions['story-2.1']);
      kind2.add('story-2.2', functions['story-2.2']);
      const book = api.getStorybook();
      expect(book).to.deep.equal([
        {
          kind: 'kind-1',
          stories: [
            { name: 'story-1.1', render: functions['story-1.1'] },
            { name: 'story-1.2', render: functions['story-1.2'] },
          ],
        },
        {
          kind: 'kind-2',
          stories: [
            { name: 'story-2.1', render: functions['story-2.1'] },
            { name: 'story-2.2', render: functions['story-2.2'] },
          ],
        },
      ]);
    });
  });
});
<|endoftext|>


<commit_before>var mediaInfo;
var user;
var notifyLogger = true;
var debug = true;
var showhiddenchat = false;
var autojoin = false;
var mentionlist = new Array();
//var staffchecker = false;
//var stafflist;
var autojoinAmount = undefined;

//$("head").append('');

//$('#chat-messages').append('

						' +message.un+ '			' +message.timestamp+ '

' +message.message+'

');

function showInChat(message){
	$('#chat-messages').append('

' +message.un+ '[Notificationlog] ' +message.timestamp+ '

' +message.message+'

');
};

function log(message){
	console.log(message);
}

function debuglogger(message){
	if(debug == true){
		console.log(message);
	}
}

function getMediaInfo(){
		mediaInfo = API.getMedia()
}

function chatLog(msg){
	API.chatLog(msg);
}

function getNigtcore331StyleSheet(){
	cssdocs = document.styleSheets;
	for(var i = 0; i< cssdocs.length;i++){
		if(cssdocs[i].ownerNode.baseURI === "https://plug.dj/nightcore-331"){
			return cssdocs[i];
		}

	}
}

function getCSSRULE(styleSheet,ruleName, deleteFlag){
	var ii = 0;
	var cssRule = false
    do {                                             			// For each rule in stylesheet
        if (styleSheet.cssRules) {                    			// Browser uses cssRules?
            cssRule = styleSheet.cssRules[ii];        			// Yes --Mozilla Style
        } else {                                      			// Browser usses rules?
            cssRule = styleSheet.rules[ii];            			// Yes IE style.
        }                                             			// End IE check.
        if (cssRule)  {                               			// If we found a rule...
            if (cssRule.selectorText.toLowerCase()==ruleName){ 	//  match ruleName?
                if (deleteFlag) {             					// Yes.  Are we deleteing?
                    if (styleSheet.cssRules) {         			// Yes, deleting...
                       styleSheet.deleteRule(ii + 1);        	// Delete rule, Moz Style
                    }else {                             		// Still deleting.
                        styleSheet.removeRule(ii + 1);        	// Delete rule IE style.
                    }                                    		// End IE check.
                    return true;                         		// return true, class deleted.
                } else {                                		// found and not deleting.
                    return cssRule;                      		// return the style object.
                }                                       		// End delete Check
            }                                          			// End found rule name
        }                                             			// end found cssRule
        ii++;                                         			// Increment sub-counter
	} while (cssRule)                                			// end While loop
}

function autojoinfunction(){
	console.log("autojoin function entered")
	var pos = API.getWaitListPosition();
	var check = API.djJoin();
	debuglogger(pos);
	if(check == 0){
		if(pos == -1){
			var pos = API.getWaitListPosition();
			if(pos != -1){
				API.chatLog("succesfully joined");
				if(autojoinAmount != undefined){autojoin = autojoin-1;}
			}
		};
	};
};

API.on(API.CHAT, function(message){
	debuglogger(message);

	//not sure if working yet
	//removing annoying color Nue Houjou
	if(message.uid === "3927729"){
		$(".cm[data-cid^="3927729"] .msg .from .un").css('color': '#7854a9');
	}

	if(showhiddenchat){
		if(message.message.charAt(0) === "!"){
			API.chatLog(message.un + ": " + message.message);
		}
	}

	//probably never going to happen
	/*if(staffchecker){
		for(var i =0; i< stafflist.length;i++){
			if(message.un === stafflist[i].name){

			}
		}
	}*/

	if(notifyLogger){
		if(message.type === "mention" && message.un != undefined){
			debuglogger(message);
			debuglogger("added a mention");
			debuglogger(message.un + ": " + message.message);
			debuglogger("---------------");
			mentionlist.push(message)
		}
	}
});

//autojoin
API.on(API.WAIT_LIST_UPDATE, function(details){
	debuglogger(details);
	if(autojoinAmount == 0 && autojoinAmount != undefined){
		autojoin = false;
		API.chatLog("autojoin now inactive: joined selected amount of times");
	}
	if(autojoin && details.length <= 49){
		console.log("autojoinfuntion started");
		autojoinfunction();
	}
});

//not using this anymore
API.on(API.ADVANCE,function(details){
	if(autojoin){
		autojoinfunction();
	}
});

API.on(API.CHAT_COMMAND, function(value){
    debuglogger(value + ' typed as chat command');
	var words = value.split(" ");
	var value1 = words[0].substring(1);

	debuglogger(words);
	debuglogger(value1);

	if(value1 === "autojoin" || value1 === "aj"){
		if(words[1] == undefined){autojoin = !autojoin;}
		if(autojoin){
			autojoinAmount = words[1];
			if (autojoinAmount == 0){
				API.chatLog("cannot autjoin 0 times")
				API.chatLog("autojoin disabled")
				autojoin = false;
			}
			else{
				if(autojoinAmount !=undefined){API.chatLog("autojoin now active for: " + words[1] +"-plays");}
				else{API.chatLog("autojoin now active");}
				autojoinfunction();
			}

		}
		else{API.chatLog("autojoin now inactive");}
	}

	else if(value1 === "getmedia" || value1 === "getVidInfo" || value1 === "gi"){
		getMediaInfo();
		window.open("http://polsy.org.uk/stuff/ytrestrict.cgi?ytid=" + mediaInfo.cid);
	}

	else if(value1 === "download" || value1 === "dl"){
		getMediaInfo();
		var uri = "https://youtube.com/watch?v=" + mediaInfo.cid;
		window.open('https://www.youtubeinmp3.com/fetch/?video=' + uri);
	}

	else if(value1 === "hiddenchat"){
		hiddenchat = !hiddenchat;
		if(hiddenchat){API.chatLog("hiddenchat enabled");}
		else{API.chatLog("hiddenchat disabled");}
	}

	else if(value1 === "urban"){
		if(words[1] != null || words[1] != undefined){
			var uri = " http://www.urbandictionary.com/define.php?term=" + words[1];
			window.open(uri);
		}
	}

	else if(value1 === "debugtog"){
		debug = !debug;
		chatLog("debug is now " + debug);
	}

	else if(value1 === "Nreset"){
		mentionlist = new Array();
	}

	else if(value1 === "Nlogger"){
		notifyLogger = !notifyLogger;
		chatLog("notifyLogger is now " + notifyLogger);

	}

	else if(value1 === "Nlist"){
		//mentionlist.foreach(arrayChatLogger);
		debuglogger(mentionlist);
		for(var i = 0;i<mentionlist.length;i++){
			debuglogger(mentionlist[i]);
			showInChat(mentionlist[i]);
		};
	}

	//not needed anymore toaster has the !staff command for resdj (nightcore-331)
	/*else if(value1 ==="getStaff"){
			stafflist = API.getStaff();
			console.log(stafflist);
		}*/

});

function arrayChatLogger(item,index){
	chatLog(item);
}

function getUser(){
	var temp = API.getUser();
	user = temp.username;
	console.log("@"+user);
};

if(debug){
	API.chatLog("API testprogram loadedV5");
	chatLog("debug on");
}else{
	API.chatLog("API program loaded");
}

getUser();

/*WIP
getCSSRULE(getNigtcore331StyleSheet,".cm.id-3927729 .msg .from .un, #user-lists .list.room .user.role-manager.id-3927729 .name {",true)
*/
<commit_msg> added Nue's chat name color<commit_after>var mediaInfo;
var user;
var notifyLogger = true;
var debug = false;
var showhiddenchat = false;
var autojoin = false;
var mentionlist = new Array();
//var staffchecker = false;
//var stafflist;
var autojoinAmount = undefined;

//$("head").append('');

//$('#chat-messages').append('

						' +message.un+ '			' +message.timestamp+ '

' +message.message+'

');
 /*message.classes +*/
function showInChat(message){
	$('#chat-messages').append('

' +message.un+ '[Notificationlog] ' +message.timestamp+ '

' +message.message+'

Hide

');
};

function log(message){
	console.log(message);
}

function debuglogger(message){
	if(debug == true){
		console.log(message);
	}
}

function getMediaInfo(){
		mediaInfo = API.getMedia()
}

function chatLog(msg){
	API.chatLog(msg);
}

function getNigtcore331StyleSheet(){
	cssdocs = document.styleSheets;
	for(var i = 0; i< cssdocs.length;i++){
		if(cssdocs[i].ownerNode.baseURI === "https://plug.dj/nightcore-331"){
			console.log(cssdocs[i]);
			return cssdocs[i];
		}

	}
	return undefined;
}

function getCSSRULE(styleSheet,ruleName, deleteFlag){
	if(styleSheet == undefined){

	}else{
		var ii = 0;
		var cssRule = false
		do {                                             			// For each rule in stylesheet
			if (styleSheet.cssRules) {                    			// Browser uses cssRules?
				cssRule = styleSheet.cssRules[ii];        			// Yes --Mozilla Style
			} else {                                      			// Browser usses rules?
				cssRule = styleSheet.rules[ii];            			// Yes IE style.
			}                                             			// End IE check.
			if (cssRule)  {                               			// If we found a rule...
				if ( (cssRule.selectorText.toLowerCase()).includes(ruleName)){ 	//  match ruleName?
					if (deleteFlag) {             					// Yes.  Are we deleteing?
						if (styleSheet.cssRules) {         			// Yes, deleting...
						   styleSheet.deleteRule(ii + 1);        	// Delete rule, Moz Style
						}else {                             		// Still deleting.
							styleSheet.removeRule(ii + 1);        	// Delete rule IE style.
						}                                    		// End IE check.
						return true;                         		// return true, class deleted.
					} else {                                		// found and not deleting.
						return cssRule;                      		// return the style object.
					}                                       		// End delete Check
				}                                          			// End found rule name
			}                                             			// end found cssRule
			ii++;                                         			// Increment sub-counter
		} while (cssRule)                                			// end While loop
	}
}

function autojoinfunction(){
	//console.log("autojoin function entered")
	var pos = API.getWaitListPosition();
	var check = API.djJoin();
	debuglogger(pos);
	if(check == 0){
		if(pos == -1){
			var pos = API.getWaitListPosition();
			if(pos != -1){
				API.chatLog("succesfully joined");
				if(autojoinAmount != undefined){
					autojoin = autojoin-1;
					if(autojoinAmount == 0 /*&& autojoinAmount != undefined*/){
						autojoin = false;
						API.chatLog("autojoin now inactive: joined selected amount of times");
					}
				}
			}
		};
	};
};

API.on(API.CHAT, function(message){
	debuglogger(message);

	//not sure if working yet
	//removing annoying color Nue Houjou
	//console.log(message.uid);
	if(message.uid === 3927729){
		console.log("Nue found");
		//$(".cm.id-3927729 .msg .from .un").css('color', '#7854a9 !important');
		//$(".id-3927729 span.un").attr('color','#7854a9' ,'important');
		$(".id-3927729 span.un").attr('style','color:#7854a9 !important');
	}

	if(showhiddenchat){
		if(message.message.charAt(0) === "!"){
			//API.chatLog(message.un + ": " + message.message);
			showInChat(message);
		}
	}

	//probably never going to happen
	/*if(staffchecker){
		for(var i =0; i< stafflist.length;i++){
			if(message.un === stafflist[i].name){

			}
		}
	}*/

	if(notifyLogger){
		if(message.type === "mention" && message.un != undefined){
			debuglogger(message);
			debuglogger("added a mention");
			debuglogger(message.un + ": " + message.message);
			debuglogger("---------------");
			mentionlist.push(message)
		}
	}
});

//autojoin
API.on(API.WAIT_LIST_UPDATE, function(details){
	debuglogger(details);
	if(autojoin && details.length <= 49){
		console.log("autojoinfuntion started");
		autojoinfunction();
	}
});

//not using this anymore
API.on(API.ADVANCE,function(details){
	if(autojoin){
		autojoinfunction();
	}
});

API.on(API.CHAT_COMMAND, function(value){
    debuglogger(value + ' typed as chat command');
	var words = value.split(" ");
	var value1 = words[0].substring(1);

	debuglogger(words);
	debuglogger(value1);

	if(value1 === "autojoin" || value1 === "aj"){
		if(words[1] == undefined){autojoin = !autojoin;}
		if(autojoin){
			autojoinAmount = words[1];
			if (autojoinAmount == 0){
				API.chatLog("cannot autojoin 0 times")
				API.chatLog("autojoin disabled")
				autojoin = false;
			}
			else{
				if(autojoinAmount !=undefined){API.chatLog("autojoin now active for: " + words[1] +"-plays");}
				else{API.chatLog("autojoin now active");}
				autojoinfunction();
			}

		}
		else{API.chatLog("autojoin now inactive");}
	}

	else if(value1 === "getmedia" || value1 === "getVidInfo" || value1 === "gi"){
		getMediaInfo();
		window.open("http://polsy.org.uk/stuff/ytrestrict.cgi?ytid=" + mediaInfo.cid);
	}

	else if(value1 === "download" || value1 === "dl"){
		getMediaInfo();
		var uri = "https://youtube.com/watch?v=" + mediaInfo.cid;
		window.open('https://www.youtubeinmp3.com/fetch/?video=' + uri);
	}

	else if(value1 === "hiddenchat"){
		hiddenchat = !hiddenchat;
		if(hiddenchat){API.chatLog("hiddenchat enabled");}
		else{API.chatLog("hiddenchat disabled");}
	}

	else if(value1 === "urban"){
		if(words[1] != null || words[1] != undefined){
			var uri = " http://www.urbandictionary.com/define.php?term=" + words[1];
			window.open(uri);
		}
	}

	else if(value1 === "debugtog"){
		debug = !debug;
		chatLog("debug is now " + debug);
	}

	else if(value1 === "Nreset"){
		mentionlist = new Array();
	}

	else if(value1 === "Nlogger"){
		notifyLogger = !notifyLogger;
		chatLog("notifyLogger is now " + notifyLogger);

	}

	else if(value1 === "Nlist"){
		//mentionlist.foreach(arrayChatLogger);
		debuglogger(mentionlist);
		for(var i = 0;i<mentionlist.length;i++){
			debuglogger(mentionlist[i]);
			showInChat(mentionlist[i]);
		};
	}
	else if(value1 === "getplaylists"){
		getplaylistinfo();
	}
	else if(value1 === "logplaylists"){
		if(playlistarray[0] != null || playlistarray[0] != undefined){
			playlistarray.sort(function(a, b) {
			  var nameA = a.name.toLowerCase(); // ignore upper and lowercase
			  var nameB = b.name.toLowerCase(); // ignore upper and lowercase
			  if (nameA < nameB) {
				return -1;
			  }
			  if (nameA > nameB) {
				return 1;
			  }

			  // names must be equal
			  return 0;
			});
			console.log(playlistarray);
		}
	}

	//not needed anymore toaster has the !staff command for resdj (nightcore-331)
	/*else if(value1 ==="getStaff"){
			stafflist = API.getStaff();
			console.log(stafflist);
		}*/

});

function getplaylistinfo(){
	 $.ajax({
        url: 'https://plug.dj/_/playlists',
        dataType: 'json',
        success: function(json) {
            //console.log(json.data);
			//var everything = new Array();
			for(var i = 0; i<json.data.length; i++){
				getsongs(json.data[i]);
				/*var id = json.data[i].id;
				var currentlistname = json.data[i].name;
				console.log(json.data[i].name);
			*/
			}
			//console.log(everything);
        }
    });
}

var playlistarray =  new Array();

function getsongs(data){
	$.ajax({
			url: 'https://plug.dj/_/playlists/'+data.id+'/media	',
			dataType: 'json',
			success: function(songs){
				//console.log(songs.data);
				//for(var i = 0; i<songs.data.length;i++)
				var playlist = { name: data.name, songs: songs.data }
				//console.log(playlist);
				playlistarray.push(playlist);
			}
	});
}

function arrayChatLogger(item,index){
	chatLog(item);
}

function getUser(){
	var temp = API.getUser();
	user = temp.username;
	console.log("@"+user);
};

if(debug){
	API.chatLog("API testprogram loadedV5");
	chatLog("debug on");
}else{
	API.chatLog("API program loaded");
}

getUser();

//WIP
var sentence = String(".msg .from .un, #user-lists .list.room .user.role-manager.id-3927729 .name {");
getCSSRULE(getNigtcore331StyleSheet,sentence,true)

<|endoftext|>


<commit_before>'use strict';

import React, {
  Image,
  Platform,
  StyleSheet,
  Text,
  TouchableOpacity,
  View,
} from 'react-native';

import autobind from 'autobind-decorator';
import invariant from 'invariant';
import cloneReferencedElement from 'react-native-clone-referenced-element';

import ExNavigatorStyles from './ExNavigatorStyles';
import ExSceneConfigs from './ExSceneConfigs';
import Layout from './Layout';

import { BackIcon } from './ExNavigatorIcons';

import type { Navigator } from 'react-native';
import type * as ExNavigator from './ExNavigator';
import type * as ExRoute from './ExRoute';

type BarStyles = {
  titleStyle?: any;
  barButtonTextStyle?: any;
  barButtonIconStyle?: any;
};

class NavigationBarRouteMapper {
  constructor(navigator: ExNavigator, styles: BarStyles) {
    this._navigator = navigator;
    this._titleStyle = styles.titleStyle;
    this._barButtonTextStyle = styles.barButtonTextStyle;
    this._barButtonIconStyle = styles.barButtonIconStyle;
  }

  Title(
    route: ExRoute,
    navigator: Navigator,
    index: number,
    state: Object
  ): ?React.Component {
    if (route.renderTitle) {
      return route.renderTitle(this._navigator, index, state);
    }

    if (!route.getTitle) {
      return null;
    }

    return (
      <Text style={[ExNavigatorStyles.barTitleText, this._titleStyle]}>
        {shortenTitle(route.getTitle(this._navigator, index, state))}
      </Text>
    );
  }

  LeftButton(
    route: ExRoute,
    navigator: Navigator,
    index: number,
    state: Object
  ): ?React.Component {

    if (route.renderLeftButton) {
      return route.renderLeftButton(this._navigator, index, state);
    }

    if (index === 0) {
      return null;
    }

    let previousIndex = index - 1;
    let previousRoute = state.routeStack[previousIndex];
    return this._renderBackButton(previousRoute, this._navigator, previousIndex, state);
  }

  _renderBackButton(
    previousRoute: ExRoute,
    navigator: Navigator,
    previousIndex: number,
    state: Object
  ): ?React.Component {
    if (previousRoute.renderBackButton) {
      return previousRoute.renderBackButton(this._navigator, previousIndex, state);
    }

    let title;
    if (previousRoute.getTitle) {
      title = previousRoute.getTitle(this._navigator, previousIndex, state);
    }

    if (title) {
      var buttonText =
        <Text style={[
          ExNavigatorStyles.barButtonText,
          ExNavigatorStyles.barBackButtonText,
          this._barButtonTextStyle,
        ]}>
          {title}
        </Text>;
    }

    return (
      <TouchableOpacity
        touchRetentionOffset={ExNavigatorStyles.barButtonTouchRetentionOffset}
        onPress={() => this._navigator.pop()}
        style={[ExNavigatorStyles.barBackButton, styles.backButtonStyle]}>
        <BackIcon
          style={[
            ExNavigatorStyles.barButtonIcon,
            this._barButtonIconStyle,
          ]}
        />
        {buttonText}
      </TouchableOpacity>
    );
  }

  RightButton(
    route: ExRoute,
    navigator: Navigator,
    index: number,
    state: Object
  ): ?React.Component {
    if (route.renderRightButton) {
      return route.renderRightButton(this._navigator, index, state);
    }
  }
};

export default class ExRouteRenderer {
  constructor(navigator: ExNavigator, styles: BarStyles) {
    this._previousRoute = null;
    this.navigationBarRouteMapper = new NavigationBarRouteMapper(
      navigator,
      styles,
    );
  }

  @autobind
  configureScene(route: ExRoute): Object {
    if (route.configureScene) {
      let sceneConfig = route.configureScene();
      if (sceneConfig) {
        return sceneConfig;
      }
    }

    if (Platform.OS === 'android') {
      return ExSceneConfigs.Fade;
    } else {
      return ExSceneConfigs.PushFromRight;
    }
  }

  @autobind
  renderScene(route: ExRoute, navigator: ExNavigator): React.Component {
    if (route.renderScene) {
      let scene = route.renderScene(navigator);
      if (!scene) {
        return scene;
      }
      return cloneReferencedElement(scene, {
        ref: component => { route.scene = component; },
      });
    }

    invariant(
      route.getSceneClass,
      'The route must implement renderScene or getSceneClass',
    );
    let Component = route.getSceneClass();
    return (
      <Component
        ref={component => { route.scene = component; }}
        navigator={navigator}
      />
    );
  }

  @autobind
  onWillFocus(event) {
    let { data: { route } } = event;
    if (route.onWillFocus) {
      route.onWillFocus(event);
    }
    // The component isn't mounted yet if this is the first time it's rendered
    if (route.scene && route.scene.componentWillFocus) {
      route.scene.componentWillFocus(event);
    }

    if (this._previousRoute && this._previousRoute.onWillBlur) {
      this._previousRoute.onWillBlur(event);
    }
  }

  @autobind
  onDidFocus(event) {
    let { data: { route } } = event;
    if (route.onDidFocus) {
      route.onDidFocus(event);
    }
    if (route.scene.componentDidFocus) {
      route.scene.componentDidFocus(event);
    }

    if (this._previousRoute && this._previousRoute.onDidBlur) {
      this._previousRoute.onDidBlur(event);
    }
    this._previousRoute = route;
  }
};

// Long titles will run into the left and right button text or overflow even
// further and just generally look gross so we try to limit the damage by
// shortening the title text.
//
// iOS does this by moving the title to take up the available space (to the
// left or right if the buttons leave space), and then ellipsising as necessary
// by measuring the actual text, etc. We can eventually but for now, we'll just
// limit titles to at most 18 characters.
function shortenTitle(title) {
  if (title.length > 18) {
    return title.substr(0, 18) + '…';
  } else {
    return title;
  }
}

let styles = StyleSheet.create({
  backButton: {
    flexDirection: 'row',
    alignItems: 'center',
  },
});

export default ExRouteRenderer;
<commit_msg>Add componentWillBlur and componentDidBlur to previous route scene<commit_after>'use strict';

import React, {
  Image,
  Platform,
  StyleSheet,
  Text,
  TouchableOpacity,
  View,
} from 'react-native';

import autobind from 'autobind-decorator';
import invariant from 'invariant';
import cloneReferencedElement from 'react-native-clone-referenced-element';

import ExNavigatorStyles from './ExNavigatorStyles';
import ExSceneConfigs from './ExSceneConfigs';
import Layout from './Layout';

import { BackIcon } from './ExNavigatorIcons';

import type { Navigator } from 'react-native';
import type * as ExNavigator from './ExNavigator';
import type * as ExRoute from './ExRoute';

type BarStyles = {
  titleStyle?: any;
  barButtonTextStyle?: any;
  barButtonIconStyle?: any;
};

class NavigationBarRouteMapper {
  constructor(navigator: ExNavigator, styles: BarStyles) {
    this._navigator = navigator;
    this._titleStyle = styles.titleStyle;
    this._barButtonTextStyle = styles.barButtonTextStyle;
    this._barButtonIconStyle = styles.barButtonIconStyle;
  }

  Title(
    route: ExRoute,
    navigator: Navigator,
    index: number,
    state: Object
  ): ?React.Component {
    if (route.renderTitle) {
      return route.renderTitle(this._navigator, index, state);
    }

    if (!route.getTitle) {
      return null;
    }

    return (
      <Text style={[ExNavigatorStyles.barTitleText, this._titleStyle]}>
        {shortenTitle(route.getTitle(this._navigator, index, state))}
      </Text>
    );
  }

  LeftButton(
    route: ExRoute,
    navigator: Navigator,
    index: number,
    state: Object
  ): ?React.Component {

    if (route.renderLeftButton) {
      return route.renderLeftButton(this._navigator, index, state);
    }

    if (index === 0) {
      return null;
    }

    let previousIndex = index - 1;
    let previousRoute = state.routeStack[previousIndex];
    return this._renderBackButton(previousRoute, this._navigator, previousIndex, state);
  }

  _renderBackButton(
    previousRoute: ExRoute,
    navigator: Navigator,
    previousIndex: number,
    state: Object
  ): ?React.Component {
    if (previousRoute.renderBackButton) {
      return previousRoute.renderBackButton(this._navigator, previousIndex, state);
    }

    let title;
    if (previousRoute.getTitle) {
      title = previousRoute.getTitle(this._navigator, previousIndex, state);
    }

    if (title) {
      var buttonText =
        <Text style={[
          ExNavigatorStyles.barButtonText,
          ExNavigatorStyles.barBackButtonText,
          this._barButtonTextStyle,
        ]}>
          {title}
        </Text>;
    }

    return (
      <TouchableOpacity
        touchRetentionOffset={ExNavigatorStyles.barButtonTouchRetentionOffset}
        onPress={() => this._navigator.pop()}
        style={[ExNavigatorStyles.barBackButton, styles.backButtonStyle]}>
        <BackIcon
          style={[
            ExNavigatorStyles.barButtonIcon,
            this._barButtonIconStyle,
          ]}
        />
        {buttonText}
      </TouchableOpacity>
    );
  }

  RightButton(
    route: ExRoute,
    navigator: Navigator,
    index: number,
    state: Object
  ): ?React.Component {
    if (route.renderRightButton) {
      return route.renderRightButton(this._navigator, index, state);
    }
  }
};

export default class ExRouteRenderer {
  constructor(navigator: ExNavigator, styles: BarStyles) {
    this._previousRoute = null;
    this.navigationBarRouteMapper = new NavigationBarRouteMapper(
      navigator,
      styles,
    );
  }

  @autobind
  configureScene(route: ExRoute): Object {
    if (route.configureScene) {
      let sceneConfig = route.configureScene();
      if (sceneConfig) {
        return sceneConfig;
      }
    }

    if (Platform.OS === 'android') {
      return ExSceneConfigs.Fade;
    } else {
      return ExSceneConfigs.PushFromRight;
    }
  }

  @autobind
  renderScene(route: ExRoute, navigator: ExNavigator): React.Component {
    if (route.renderScene) {
      let scene = route.renderScene(navigator);
      if (!scene) {
        return scene;
      }
      return cloneReferencedElement(scene, {
        ref: component => { route.scene = component; },
      });
    }

    invariant(
      route.getSceneClass,
      'The route must implement renderScene or getSceneClass',
    );
    let Component = route.getSceneClass();
    return (
      <Component
        ref={component => { route.scene = component; }}
        navigator={navigator}
      />
    );
  }

  @autobind
  onWillFocus(event) {
    let { data: { route } } = event;
    if (route.onWillFocus) {
      route.onWillFocus(event);
    }
    // The component isn't mounted yet if this is the first time it's rendered
    if (route.scene && route.scene.componentWillFocus) {
      route.scene.componentWillFocus(event);
    }

    if (this._previousRoute && this._previousRoute.onWillBlur) {
      this._previousRoute.onWillBlur(event);
    }

    if (this._previousRoute && this._previousRoute.scene && this._previousRoute.scene.componentWillBlur) {
      this._previousRoute.scene.componentWillBlur(event);
    }
  }

  @autobind
  onDidFocus(event) {
    let { data: { route } } = event;
    if (route.onDidFocus) {
      route.onDidFocus(event);
    }
    if (route.scene.componentDidFocus) {
      route.scene.componentDidFocus(event);
    }

    if (this._previousRoute && this._previousRoute.onDidBlur) {
      this._previousRoute.onDidBlur(event);
    }
    if (this._previousRoute && this._previousRoute.scene && this._previousRoute.scene.componentDidBlur) {
      this._previousRoute.scene.componentDidBlur(event);
    }

    this._previousRoute = route;
  }
};

// Long titles will run into the left and right button text or overflow even
// further and just generally look gross so we try to limit the damage by
// shortening the title text.
//
// iOS does this by moving the title to take up the available space (to the
// left or right if the buttons leave space), and then ellipsising as necessary
// by measuring the actual text, etc. We can eventually but for now, we'll just
// limit titles to at most 18 characters.
function shortenTitle(title) {
  if (title.length > 18) {
    return title.substr(0, 18) + '…';
  } else {
    return title;
  }
}

let styles = StyleSheet.create({
  backButton: {
    flexDirection: 'row',
    alignItems: 'center',
  },
});

export default ExRouteRenderer;
<|endoftext|>