<commit_before>import _ from 'lodash';

import React from 'react';
import { Router, Route, Link } from 'react-router';
import { createHashHistory } from 'history';
import Griddle from 'griddle-react';
import Loader from 'react-loader';
import { octokat, octokatHelper } from '../factory';
import * as helper from '../helper';
import Auth from '../auth';
import { dispatcher } from '../dispatcher';

import {
  Alert,
  Button,
  CollapsibleNav,
  Input,
  MenuItem,
  Modal,
  Nav,
  Navbar,
  NavBrand,
  NavDropdown,
  NavItem
} from 'react-bootstrap';

var history = createHashHistory();

class AppRoute extends React.Component {

  constructor(props) {
    super(props);
    this.state = {
      error: null,
      user: null,
      authed: false,
      githubAccessToken: null,
      rateLimit: {},
      reset: new Date(),
      repos: [
        'oauth-io/oauth-js',
        'isaacs/github'
      ],
      showingAuthModal: false
    };

    dispatcher.register(payload => {
      if(payload.actionType === dispatcher.actionTypes.ERROR) {
        var error = payload.data;
        this.setState({error});
      }
    });
  }

  componentDidMount() {
    this.checkAuth();

    var checkRateLimit = () => {
      if (!octokat()) {
        setTimeout(checkRateLimit, 2000);
        return;
      }
      octokat().rateLimit.fetch().then(
        (data) => {
          var rateLimit = data.resources.core;
          var reset = data.resources.core.reset;
          var date = new Date(reset*1000);
          var state = {rateLimit, reset: date};
          this.setState(state);
          setTimeout(checkRateLimit, 2000);
        },
        () => {
          console.error(arguments);
        }
      );
    };
    checkRateLimit();
  }

  onClickSignIn() {
    this.showAuthModal();
  }

  onClickAuth() {
    this.showAuthModal();
  }

  showAuthModal() {
    this.setState({showingAuthModal: true});
  }

  signIn() {
    Auth.signIn().then(this.onSignedIn.bind(this));
  }

  onClickSignOut() {
    console.log('onClickSignOut');
    Auth.signOut().
      then(d => {
        this.setState({authed: false, user: null})
      });
  }

  checkAuth(options) {
    Auth.check(options).
      then(d => {
        console.log('authed', d);
        this.setState({githubAccessToken: d.githubAccessToken})
        this.onSignedIn();
      });
  }

  onSignedIn() {
    this.setState({authed: true});

    octokat().user.fetch().then(user => {
      this.setState({user});
    });
  }

  signInFromAlert() {
    this.dismissAlert();
    this.signIn();
  }

  dismissAlert() {
    this.setState({error: null});
  }

  closeAuthModal() {
    this.setState({showingAuthModal: false});
  }

  onChangeGitHubAccessToken(e) {
    var githubAccessToken = e.target.value;
    this.setState({githubAccessToken});
    Auth.setToken(githubAccessToken).
      then(d => {
        this.onSignedIn();
      });
  }

  render() {
    var children = this.props.children;

    if (! children) {
      children = (

          {this.state.repos.map(r => {
            return (

                <Link to={'/' + r}>{r}</Link>

            );
          })}

      );
    }

    var errorCmp;
    var error = this.state.error;
    if (this.state.error) {
      var message = error.message || error;
      errorCmp =
        <Alert bsStyle="danger" onDismiss={this.dismissAlert.bind(this)}>

Oh snap! You got an error!

{message}

            <Button onClick={this.onClickSignIn.bind(this)} bsStyle="success">Sign In</Button>
             or
            <Button onClick={this.dismissAlert.bind(this)}>Hide Alert</Button>

        </Alert>;
    }

    var authModalSignInCmp;
    if (! this.state.user) {
      authModalSignInCmp = (
        <Button
          bsStyle="success"
          onClick={this.signIn.bind(this)}
        >
          Sign In to GitHub via OAuth.io
        </Button>
      );
    }
    else {
      authModalSignInCmp = (

              @{this.state.user.login}

            <Button
              bsStyle="success"
              onClick={this.onClickSignOut.bind(this)}
            >
               Sign Out
            </Button>

      );
    }

    var authBarCmp;
    if (! this.state.user) {
      /*
       * https://github.com/react-bootstrap/react-bootstrap/issues/1404
       */
      authBarCmp = (
        <button type="button"
          className="btn btn-success navbar-btn navbar-right"
          onClick={this.onClickSignIn.bind(this)}
        >
          Sign In
        </button>
      );
    }
    else {
      var avatar = (

      );
      authBarCmp = (

          <NavDropdown eventKey={2}
            title={avatar}
            id="ghir-navbar-user-dropdown"
          >
            <MenuItem eventKey={5}
              onSelect={this.onClickAuth.bind(this)}
            >
               Authentication
            </MenuItem>
            <MenuItem eventKey={5}
              onSelect={this.onClickSignOut.bind(this)}
            >
               Sign Out
            </MenuItem>
          </NavDropdown>

      );
    }

    return (

        <Navbar toggleNavKey={0}>
          <NavBrand>
            <Link to="/">GitHub Issue Rank</Link>
          </NavBrand>
          <CollapsibleNav eventKey={0}>

              <NavDropdown eventKey={3}
                title={}
                id="ghir-navbar-more-dropdown"
              >
                <MenuItem eventKey={1}>
                   Settings
                </MenuItem>
                <MenuItem divider />
                <MenuItem eventKey={4}
                  href="https://github.com/AndersDJohnson/github-issue-rank"
                  target="_blank"
                >
                   Source Code
                </MenuItem>
              </NavDropdown>

            {authBarCmp}

          </CollapsibleNav>
        </Navbar>

        <Modal show={this.state.showingAuthModal} onHide={this.closeAuthModal.bind(this)}>
          <Modal.Header closeButton>
            <Modal.Title>Authentication</Modal.Title>
          </Modal.Header>
          <Modal.Body>

            {authModalSignInCmp}

Or provide an access token:

            <Input
              type="text"
              value={this.state.githubAccessToken}
              onChange={this.onChangeGitHubAccessToken.bind(this)}
            />

          </Modal.Body>
          <Modal.Footer>
            <Button
              onClick={this.closeAuthModal.bind(this)}
            >
              Close
            </Button>
          </Modal.Footer>
        </Modal>

        {errorCmp}

          <progress id="gh-api-limit"
            title="API Limit"
            value={this.state.rateLimit.remaining}
            max={this.state.rateLimit.limit} />

            API Limit {this.state.rateLimit.remaining} / {this.state.rateLimit.limit}
             (resets {this.state.reset.toString()})

        {children}

    )
  }
};

class NoRoute extends React.Component {
  componentDidMount() {
    console.log(this.props.params);
  }

  render() {
    return (

404

    )
  }
};

class LinkComponent extends React.Component {
  render() {
    var data = this.data();
    var {owner, repo, number} = this.props.rowData;
    var href = '#/' + owner + '/' + repo + '/' + number;
    return (

        {data}

    );
  }
  data() {
    return this.props.data;
  }
};

class IssueNumberComponent extends LinkComponent {
  data() {
    var data = this.props.data;
    return data ? '#' + data : '';
  }
};

class RepoRoute extends React.Component {
  constructor(props) {
    super(props);

    var columnMetadata = [
      {
        columnName: 'number',
        displayName: '#',
        customComponent: IssueNumberComponent,
        cssClassName: 'griddle-column-number'
      },
      {
        columnName: 'title',
        displayName: 'Title',
        customComponent: LinkComponent,
        cssClassName: 'griddle-column-title'
      },
      {
        columnName: 'voteCount',
        displayName: '# Votes',
        customComponent: LinkComponent,
        cssClassName: 'griddle-column-voteCount'
      },
      {
        columnName: 'owner',
        visible: false
      },
      {
        columnName: 'repo',
        visible: false
      },
      {
        columnName: 'htmlUrl',
        visible: false
      }
    ];

    columnMetadata = _.each(columnMetadata, (md, i) => { md.order = i; })

    var columns =_.chain(columnMetadata)
      .sortBy('order')
      .filter((md) => {
        return md.visible == null ? true : false;
      })
      .pluck('columnName')
      .value();

    this.state = {
      owner: null,
      repo: null,
      rows: [],
      loaded: false,
      anyLoaded: false,
      columnMetadata,
      columns,
      showingIssueModal: false,
      commentsWithVotes: [],
      issue: {},
      progress: {
        value:0,
        max: 0
      }
    };
  }

  componentDidUpdate() {
    this.unmounting = false;
    this.showRepo();
  }

  componentDidMount() {
    this.unmounting = false;
    this.showRepo();
  }

  componentWillUnmount () {
    // allows us to ignore an inflight request in scenario 4
    this.unmounting = true;
    this.owner = null;
    this.repo = null;
  }

  sameState(owner, repo) {
    return (! this.unmounting) && (owner && (owner === this.state.owner)) && (repo && (repo === this.state.repo));
  }

  showRepo() {
    var {owner, repo} = this.props.params;

    if (this.sameState(owner, repo)) return;

    this.setState({
      loaded: false,
      owner: owner,
      repo: repo,
      rows: []
    });

    Auth.wait().then(() => {
      helper.showRepo(owner, repo,
        (err, rows, cancel, progress) => {
          if (err) {
            return dispatcher.error(err);
          }
          if ( ! this.sameState(owner, repo)) return cancel();
          this.showRows(err, rows);
          this.setState({
            anyLoaded: true,
            progress
          });
        },
        (err, rows, cancel, progress) => {
          // console.log('progress', progress);
          if (err) {
            this.setState({loaded: true, anyLoaded: true})
            return dispatcher.error(err);
          }
          if ( ! this.sameState(owner, repo)) return cancel();
          this.showRows(err, rows);
          this.setState({
            loaded: true,
            progress
          });
        }
      );
    });

  }

  showRows(err, rows) {
    if (! this.unmounting) {
      this.setState({
        rows
      });
    }
  }

  render() {
    var children = this.props.children;

    var {owner, repo} = this.props.params;

    return (

          <Link to={'/' + owner + '/' + repo}>
            {owner}/{repo}
          </Link>

        <progress
          value={this.state.progress.value}
          max={this.state.progress.max}
        ></progress>

        <Loader loaded={this.state.loaded}></Loader>

        <Loader loaded={this.state.anyLoaded}>

# issues: {this.state.rows.length}

          <Griddle
            results={this.state.rows}
            columnMetadata={this.state.columnMetadata}
            columns={this.state.columns}
            resultsPerPage={10}
            showSettings={true}
            showFilter={true}
          />
        </Loader>

        {children}

    )
  }
};

class IssueRoute extends React.Component {

  constructor(props) {
    super(props);
    this.state = {
      owner: null,
      repo: null,
      number: null,
      issue: {},
      comments: [],
      commentsWithVotes: [],
      loaded: false
    };
  }

  componentDidMount() {
    this.unmounting = false;
    this.showComments();
  }

  showComments() {
    var {owner, repo, number} = this.props.params;

    Auth.wait().then(() => {
      octokat()
        .repos(owner, repo)
        .issues(number)
        .fetch()
        .then(issue => {
          // console.log('issue', issue);
          this.setState({issue});
        }, (err) => {
          if (err) throw err;
        });

      octokatHelper().getComments(
        owner, repo, number,
        (err, comments, cancel) => {
          if (err) {
            return dispatcher.error(err);
          }

          // console.log('each', err, comments, cancel);
          comments = helper.mapCommentsHaveVotes(comments);
          this.setState({comments});
          var commentsWithVotes = comments.filter(c => {
            return c.hasVote;
          });
          this.setState({commentsWithVotes});
        },
        (err, comments) => {
          if (err) {
            return dispatcher.error(err);
          }
          // console.log('done', comments);
        }
      );
    });
  }

  closeIssueModal() {
    var {owner, repo} = this.props.params;
    history.pushState(null, '/' + owner + '/' + repo);
  }

  render() {
    var {owner, repo, number} = this.props.params;
    return (

        <Modal show={true} onHide={this.closeIssueModal.bind(this)}>
          <Modal.Header closeButton>
            <Modal.Title>

              <Link to={'/' + owner + '/' + repo}>
                {owner}/{repo}
              </Link>

              <Link to={'/' + owner + '/' + repo + '/' + number}>
                #{number}
              </Link>

            </Modal.Title>
          </Modal.Header>
          <Modal.Body>

              {this.state.issue.title}

              {this.state.commentsWithVotes.map(c => {
                return (

                );
              })}

          </Modal.Body>
          <Modal.Footer>
            <Button
              onClick={this.closeIssueModal.bind(this)}
            >
              Close
            </Button>
          </Modal.Footer>
        </Modal>

    );
  }
};

export class RouterComponent extends React.Component {
  render() {
    return (
      <Router history={history}>
        <Route path="/" component={AppRoute}>
          <Route path=":owner/:repo" component={RepoRoute}>
            <Route path=":number" component={IssueRoute}/>
          </Route>
          <Route path="*" component={NoRoute}/>
        </Route>
      </Router>
    );
  }
};
<commit_msg>Add vote count to issue modal.<commit_after>import _ from 'lodash';

import React from 'react';
import { Router, Route, Link } from 'react-router';
import { createHashHistory } from 'history';
import Griddle from 'griddle-react';
import Loader from 'react-loader';
import { octokat, octokatHelper } from '../factory';
import * as helper from '../helper';
import Auth from '../auth';
import { dispatcher } from '../dispatcher';

import {
  Alert,
  Button,
  CollapsibleNav,
  Input,
  MenuItem,
  Modal,
  Nav,
  Navbar,
  NavBrand,
  NavDropdown,
  NavItem
} from 'react-bootstrap';

var history = createHashHistory();

class AppRoute extends React.Component {

  constructor(props) {
    super(props);
    this.state = {
      error: null,
      user: null,
      authed: false,
      githubAccessToken: null,
      rateLimit: {},
      reset: new Date(),
      repos: [
        'oauth-io/oauth-js',
        'isaacs/github'
      ],
      showingAuthModal: false
    };

    dispatcher.register(payload => {
      if(payload.actionType === dispatcher.actionTypes.ERROR) {
        var error = payload.data;
        this.setState({error});
      }
    });
  }

  componentDidMount() {
    this.checkAuth();

    var checkRateLimit = () => {
      if (!octokat()) {
        setTimeout(checkRateLimit, 2000);
        return;
      }
      octokat().rateLimit.fetch().then(
        (data) => {
          var rateLimit = data.resources.core;
          var reset = data.resources.core.reset;
          var date = new Date(reset*1000);
          var state = {rateLimit, reset: date};
          this.setState(state);
          setTimeout(checkRateLimit, 2000);
        },
        () => {
          console.error(arguments);
        }
      );
    };
    checkRateLimit();
  }

  onClickSignIn() {
    this.showAuthModal();
  }

  onClickAuth() {
    this.showAuthModal();
  }

  showAuthModal() {
    this.setState({showingAuthModal: true});
  }

  signIn() {
    Auth.signIn().then(this.onSignedIn.bind(this));
  }

  onClickSignOut() {
    console.log('onClickSignOut');
    Auth.signOut().
      then(d => {
        this.setState({authed: false, user: null})
      });
  }

  checkAuth(options) {
    Auth.check(options).
      then(d => {
        console.log('authed', d);
        this.setState({githubAccessToken: d.githubAccessToken})
        this.onSignedIn();
      });
  }

  onSignedIn() {
    this.setState({authed: true});

    octokat().user.fetch().then(user => {
      this.setState({user});
    });
  }

  signInFromAlert() {
    this.dismissAlert();
    this.signIn();
  }

  dismissAlert() {
    this.setState({error: null});
  }

  closeAuthModal() {
    this.setState({showingAuthModal: false});
  }

  onChangeGitHubAccessToken(e) {
    var githubAccessToken = e.target.value;
    this.setState({githubAccessToken});
    Auth.setToken(githubAccessToken).
      then(d => {
        this.onSignedIn();
      });
  }

  render() {
    var children = this.props.children;

    if (! children) {
      children = (

          {this.state.repos.map(r => {
            return (

                <Link to={'/' + r}>{r}</Link>

            );
          })}

      );
    }

    var errorCmp;
    var error = this.state.error;
    if (this.state.error) {
      var message = error.message || error;
      errorCmp =
        <Alert bsStyle="danger" onDismiss={this.dismissAlert.bind(this)}>

Oh snap! You got an error!

{message}

            <Button onClick={this.onClickSignIn.bind(this)} bsStyle="success">Sign In</Button>
             or
            <Button onClick={this.dismissAlert.bind(this)}>Hide Alert</Button>

        </Alert>;
    }

    var authModalSignInCmp;
    if (! this.state.user) {
      authModalSignInCmp = (
        <Button
          bsStyle="success"
          onClick={this.signIn.bind(this)}
        >
          Sign In to GitHub via OAuth.io
        </Button>
      );
    }
    else {
      authModalSignInCmp = (

              @{this.state.user.login}

            <Button
              bsStyle="success"
              onClick={this.onClickSignOut.bind(this)}
            >
               Sign Out
            </Button>

      );
    }

    var authBarCmp;
    if (! this.state.user) {
      /*
       * https://github.com/react-bootstrap/react-bootstrap/issues/1404
       */
      authBarCmp = (
        <button type="button"
          className="btn btn-success navbar-btn navbar-right"
          onClick={this.onClickSignIn.bind(this)}
        >
          Sign In
        </button>
      );
    }
    else {
      var avatar = (

      );
      authBarCmp = (

          <NavDropdown eventKey={2}
            title={avatar}
            id="ghir-navbar-user-dropdown"
          >
            <MenuItem eventKey={5}
              onSelect={this.onClickAuth.bind(this)}
            >
               Authentication
            </MenuItem>
            <MenuItem eventKey={5}
              onSelect={this.onClickSignOut.bind(this)}
            >
               Sign Out
            </MenuItem>
          </NavDropdown>

      );
    }

    return (

        <Navbar toggleNavKey={0}>
          <NavBrand>
            <Link to="/">GitHub Issue Rank</Link>
          </NavBrand>
          <CollapsibleNav eventKey={0}>

              <NavDropdown eventKey={3}
                title={}
                id="ghir-navbar-more-dropdown"
              >
                <MenuItem eventKey={1}>
                   Settings
                </MenuItem>
                <MenuItem divider />
                <MenuItem eventKey={4}
                  href="https://github.com/AndersDJohnson/github-issue-rank"
                  target="_blank"
                >
                   Source Code
                </MenuItem>
              </NavDropdown>

            {authBarCmp}

          </CollapsibleNav>
        </Navbar>

        <Modal show={this.state.showingAuthModal} onHide={this.closeAuthModal.bind(this)}>
          <Modal.Header closeButton>
            <Modal.Title>Authentication</Modal.Title>
          </Modal.Header>
          <Modal.Body>

            {authModalSignInCmp}

Or provide an access token:

            <Input
              type="text"
              value={this.state.githubAccessToken}
              onChange={this.onChangeGitHubAccessToken.bind(this)}
            />

          </Modal.Body>
          <Modal.Footer>
            <Button
              onClick={this.closeAuthModal.bind(this)}
            >
              Close
            </Button>
          </Modal.Footer>
        </Modal>

        {errorCmp}

          <progress id="gh-api-limit"
            title="API Limit"
            value={this.state.rateLimit.remaining}
            max={this.state.rateLimit.limit} />

            API Limit {this.state.rateLimit.remaining} / {this.state.rateLimit.limit}
             (resets {this.state.reset.toString()})

        {children}

    )
  }
};

class NoRoute extends React.Component {
  componentDidMount() {
    console.log(this.props.params);
  }

  render() {
    return (

404

    )
  }
};

class LinkComponent extends React.Component {
  render() {
    var data = this.data();
    var {owner, repo, number} = this.props.rowData;
    var href = '#/' + owner + '/' + repo + '/' + number;
    return (

        {data}

    );
  }
  data() {
    return this.props.data;
  }
};

class IssueNumberComponent extends LinkComponent {
  data() {
    var data = this.props.data;
    return data ? '#' + data : '';
  }
};

class RepoRoute extends React.Component {
  constructor(props) {
    super(props);

    var columnMetadata = [
      {
        columnName: 'number',
        displayName: '#',
        customComponent: IssueNumberComponent,
        cssClassName: 'griddle-column-number'
      },
      {
        columnName: 'title',
        displayName: 'Title',
        customComponent: LinkComponent,
        cssClassName: 'griddle-column-title'
      },
      {
        columnName: 'voteCount',
        displayName: '# Votes',
        customComponent: LinkComponent,
        cssClassName: 'griddle-column-voteCount'
      },
      {
        columnName: 'owner',
        visible: false
      },
      {
        columnName: 'repo',
        visible: false
      },
      {
        columnName: 'htmlUrl',
        visible: false
      }
    ];

    columnMetadata = _.each(columnMetadata, (md, i) => { md.order = i; })

    var columns =_.chain(columnMetadata)
      .sortBy('order')
      .filter((md) => {
        return md.visible == null ? true : false;
      })
      .pluck('columnName')
      .value();

    this.state = {
      owner: null,
      repo: null,
      rows: [],
      loaded: false,
      anyLoaded: false,
      columnMetadata,
      columns,
      showingIssueModal: false,
      commentsWithVotes: [],
      issue: {},
      progress: {
        value:0,
        max: 0
      }
    };
  }

  componentDidUpdate() {
    this.unmounting = false;
    this.showRepo();
  }

  componentDidMount() {
    this.unmounting = false;
    this.showRepo();
  }

  componentWillUnmount () {
    // allows us to ignore an inflight request in scenario 4
    this.unmounting = true;
    this.owner = null;
    this.repo = null;
  }

  sameState(owner, repo) {
    return (! this.unmounting) && (owner && (owner === this.state.owner)) && (repo && (repo === this.state.repo));
  }

  showRepo() {
    var {owner, repo} = this.props.params;

    if (this.sameState(owner, repo)) return;

    this.setState({
      loaded: false,
      owner: owner,
      repo: repo,
      rows: []
    });

    Auth.wait().then(() => {
      helper.showRepo(owner, repo,
        (err, rows, cancel, progress) => {
          if (err) {
            return dispatcher.error(err);
          }
          if ( ! this.sameState(owner, repo)) return cancel();
          this.showRows(err, rows);
          this.setState({
            anyLoaded: true,
            progress
          });
        },
        (err, rows, cancel, progress) => {
          // console.log('progress', progress);
          if (err) {
            this.setState({loaded: true, anyLoaded: true})
            return dispatcher.error(err);
          }
          if ( ! this.sameState(owner, repo)) return cancel();
          this.showRows(err, rows);
          this.setState({
            loaded: true,
            progress
          });
        }
      );
    });

  }

  showRows(err, rows) {
    if (! this.unmounting) {
      this.setState({
        rows
      });
    }
  }

  render() {
    var children = this.props.children;

    var {owner, repo} = this.props.params;

    return (

          <Link to={'/' + owner + '/' + repo}>
            {owner}/{repo}
          </Link>

        <progress
          value={this.state.progress.value}
          max={this.state.progress.max}
        ></progress>

        <Loader loaded={this.state.loaded}></Loader>

        <Loader loaded={this.state.anyLoaded}>

# issues: {this.state.rows.length}

          <Griddle
            results={this.state.rows}
            columnMetadata={this.state.columnMetadata}
            columns={this.state.columns}
            resultsPerPage={10}
            showSettings={true}
            showFilter={true}
          />
        </Loader>

        {children}

    )
  }
};

class IssueRoute extends React.Component {

  constructor(props) {
    super(props);
    this.state = {
      owner: null,
      repo: null,
      number: null,
      issue: {},
      comments: [],
      commentsWithVotes: [],
      loaded: false
    };
  }

  componentDidMount() {
    this.unmounting = false;
    this.showComments();
  }

  showComments() {
    var {owner, repo, number} = this.props.params;

    Auth.wait().then(() => {
      octokat()
        .repos(owner, repo)
        .issues(number)
        .fetch()
        .then(issue => {
          // console.log('issue', issue);
          this.setState({issue});
        }, (err) => {
          if (err) throw err;
        });

      octokatHelper().getComments(
        owner, repo, number,
        (err, comments, cancel) => {
          if (err) {
            return dispatcher.error(err);
          }

          // console.log('each', err, comments, cancel);
          comments = helper.mapCommentsHaveVotes(comments);
          this.setState({comments});
          var commentsWithVotes = comments.filter(c => {
            return c.hasVote;
          });
          this.setState({commentsWithVotes});
        },
        (err, comments) => {
          if (err) {
            return dispatcher.error(err);
          }
          // console.log('done', comments);
        }
      );
    });
  }

  closeIssueModal() {
    var {owner, repo} = this.props.params;
    history.pushState(null, '/' + owner + '/' + repo);
  }

  render() {
    var {owner, repo, number} = this.props.params;
    return (

        <Modal show={true} onHide={this.closeIssueModal.bind(this)}>
          <Modal.Header closeButton>
            <Modal.Title>

              <Link to={'/' + owner + '/' + repo}>
                {owner}/{repo}
              </Link>

              <Link to={'/' + owner + '/' + repo + '/' + number}>
                #{number}
              </Link>

            </Modal.Title>
          </Modal.Header>
          <Modal.Body>

              {this.state.issue.title}

              {this.state.commentsWithVotes.length} votes

              {this.state.commentsWithVotes.map(c => {
                return (

                );
              })}

          </Modal.Body>
          <Modal.Footer>
            <Button
              onClick={this.closeIssueModal.bind(this)}
            >
              Close
            </Button>
          </Modal.Footer>
        </Modal>

    );
  }
};

export class RouterComponent extends React.Component {
  render() {
    return (
      <Router history={history}>
        <Route path="/" component={AppRoute}>
          <Route path=":owner/:repo" component={RepoRoute}>
            <Route path=":number" component={IssueRoute}/>
          </Route>
          <Route path="*" component={NoRoute}/>
        </Route>
      </Router>
    );
  }
};
<|endoftext|>


<commit_before>import _ from 'underscore';
import $ from 'jquery';
import loadTemplate from '../../utils/loadTemplate';
import app from '../../app';
import { getSocket } from '../../utils/serverConnect';
import { openSimpleMessage } from '../modals/SimpleMessage';
import Moderators from '../../collections/Moderators';
import Moderator from '../../models/profile/Profile';
import baseVw from '../baseVw';
import ModCard from './ModeratorCard';
import ModeratorsStatus from './ModeratorsStatus';

export default class extends baseVw {
  /**
   * @param {string}  options.apiPath           - Current options are fetchprofiles and moderators.
   * @param {boolean} options.async             - Return profiles via websocket.
   * @param {boolean} options.useCache          - Use cached data for faster speed.
   * @param {array}   options.moderatorIDs      - list of moderators to retrieve. If none get all.
   * @param {array}   options.excludeIDs        - list of moderators to not use.
   * @param {string}  options.method            - POST or GET
   * @param {string}  options.include           - If apiPath is moderator, set to 'profile' or only
   *                                              the peerIDs of each moderator are returned.
   * @param {boolean} options.purchase          - If this is used in a purchase, pass this to the
   *                                              child moderator card views.
   * @param {boolean} options.singleSelect      - Allow only one moderator to be selected at a time.
   * @param {boolean} options.selectFirst       - Pre-select the first moderator.
   * @param {boolean} options.radioStyle        - Show the moderator cards with radio buttons.
   * @param {boolean} options.showInvalid       - Show invalid moderator cards.
   * @param {boolean} options.controlsOnInvalid - Show controls on invalid cards so they can be
   *                                              removed or otherwise acted on.
   * @param {string}  options.wrapperClasses    - Add classes to the card container.
   * @param {string}  options.fetchErrorTitle   - A title for the fetch error.
   * @param {string}  options.cardState         - The initial state for cards that are created.
   * @param {string}  options.notSelected       - Which not selected state to use on the mod cards.
   * @param {boolean} options.showLoadBtn       - Show the load more button in the status bar.
   * @param {boolean} options.showSpinner       - Show the spinner in the status bar
   * @param {boolean} options.showVerifiedOnly  - Show only verified moderators
   */

  constructor(options = {}) {
    if (!options.fetchErrorTitle) {
      throw new Error('Please provide error text for the moderator fetch.');
    }

    const opts = {
      className: 'moderatorsList',
      apiPath: 'fetchprofiles',
      async: true,
      useCache: true,
      moderatorIDs: [],
      excludeIDs: [],
      method: 'POST',
      include: '',
      purchase: false,
      singleSelect: false,
      selectFirst: false,
      radioStyle: false,
      controlsOnInvalid: false,
      cardState: 'unselected',
      notSelected: 'unselected',
      showLoadBtn: false,
      showSpinner: true,
      ...options,
    };

    if (!opts.apiPath || ['fetchprofiles', 'moderators'].indexOf(opts.apiPath) === -1) {
      throw new Error('The apiPath must be either fetchprofiles or moderators');
    }
    if (opts.apiPath === 'moderators') {
      if (opts.moderatorIDs.length) {
        throw new Error('If the apiPath is moderators, a list of IDs is not used.');
      }
    } else if (opts.apiPath === 'fetchprofiles' && opts.include) {
      throw new Error('If the apiPath is fetchprofiles, the include parameter is not used.');
    }
    if (typeof opts.async !== 'boolean') {
      throw new Error('The value of async must be a boolean');
    }
    if (!opts.fetchErrorTitle) {
      throw new Error('Please provide a title for the fetch error.');
    }

    if (opts.apiPath === 'moderators') {
      // this will fetch available moderators without POSTing a list of IDs.
      opts.include = 'profile';
      opts.method = 'GET';
    }

    super(opts);
    this.options = opts;
    this.excludeIDs = opts.excludeIDs;
    this.unfetchedMods = [];
    this.fetchingMods = [];
    this.verifiedMods = [];
    this.modFetches = [];
    this.moderatorsCol = new Moderators();
    this.listenTo(this.moderatorsCol, 'add', model => {
      const modCard = this.addMod(model);

      // if required, select the first  moderator
      if (opts.selectFirst && !this.firstSelected && !this.noneSelected) {
        let selectIt = true;
        // if only verified mods are shown, only select the first verified mod
        if (opts.showVerifiedOnly) selectIt = !!app.verifiedMods.get(model.get('peerID'));

        if (selectIt) {
          this.firstSelected = true;
          modCard.changeSelectState('selected');
        }
      }
      this.render();
    });
    this.listenTo(this.moderatorsCol, 'remove', (md) => {
      const removeIndex = this.modCards.findIndex(card => card.model === md);
      this.modCards.splice(removeIndex, 1)[0].remove();
    });
    this.modCards = [];

    // create a moderator status view. It should retain it's state between renders of this view.
    this.moderatorsStatus = this.createChild(ModeratorsStatus, {
      initialState: {
        mode: opts.method === 'GET' ? 'loaded' : '',
        showLoadBtn: opts.showLoadBtn,
        showSpinner: opts.showSpinner,
      },
    });
    this.listenTo(this.moderatorsStatus, 'browseMore', () => this.onBrowseMore());

    // listen to the websocket for moderator data
    this.serverSocket = getSocket();
  }

  events() {
    return {
      'click .js-showUnverified': 'clickShowUnverified',
    };
  }

  clickShowUnverified() {
    this.togVerifiedShown(false);
    this.trigger('clickShowUnverified');
  }

  onBrowseMore() {
    this.getModeratorsByID();
  }

  removeNotFetched(ID) {
    this.unfetchedMods = this.unfetchedMods.filter(peerID => peerID !== ID);
    this.checkNotFetched();
  }

  processMod(data) {
    // Don't add profiles that are not moderators unless showInvalid is true. The ID list may have
    // peerIDs that are out of date, and are no longer moderators.
    const validMod = data.moderator && data.moderatorInfo;
    // if the moderator has an invalid currency, remove them from the list
    const buyerCur = app.serverConfig.cryptoCurrency;
    const modCurs = data.moderatorInfo && data.moderatorInfo.acceptedCurrencies || [];
    const validCur = modCurs.includes(buyerCur);

    if ((!!validMod && validCur || this.options.showInvalid)) {
      this.moderatorsCol.add(new Moderator(data, { parse: true }));
      this.removeNotFetched(data.peerID);
    } else {
      // remove the invalid moderator from the notFetched list
      this.removeNotFetched(data.peerID);
    }
  }

  getModeratorsByID(opts) {
    const op = {
      ...this.options,
      ...opts,
    };

    if (!Array.isArray(op.moderatorIDs)) {
      throw new Error('Please provide the list of moderators as an array.');
    }

    // don't get any that have already been added or excluded, or the user's own id.
    const excluded = [...this.allIDs, ...this.excludeIDs, app.profile.id];
    const IDs = _.without(op.moderatorIDs, excluded);
    const includeString = op.include ? `&include=${op.include}` : '';
    const urlString =
      `ob/${op.apiPath}?async=${op.async}${includeString}&usecache=${op.useCache}`;
    const url = app.getServerUrl(urlString);

    this.unfetchedMods = IDs;
    this.fetchingMods = IDs;
    this.verifiedMods = app.verifiedMods.matched(IDs);

    this.setState({
      noValidModerators: false,
      noValidVerifiedModerators: !this.verifiedMods.length && this.fetchingMods.length,
    });

    // Either a list of IDs can be posted, or any available moderators can be retrieved with GET
    if (IDs.length || op.method === 'GET') {
      this.moderatorsStatus.setState({
        hidden: false,
        loaded: this.modCount,
        total: IDs.length ? IDs.length : this.modCount,
        showSpinner: op.showSpinner, //unhides the spinner if it's been hidden by the timer
      });

      const fetch = $.ajax({
        url,
        data: JSON.stringify(IDs),
        method: op.method,
      })
          .done((data) => {
            if (op.async) {
              const socketID = data.id;
              if (this.serverSocket) {
                this.listenTo(this.serverSocket, 'message', (event) => {
                  const eventData = event.jsonData;
                  if (eventData.error) {
                    // errors don't have a message id, check to see if the peerID matches
                    if (IDs.includes(eventData.peerId)) {
                      // provide the expected capitalization of peerID
                      eventData.peerID = eventData.peerId;
                      this.processMod(eventData);
                    }
                  } else if (eventData.id === socketID && !excluded.includes(eventData.peerId)) {
                    this.processMod(eventData.profile);
                  }
                });
              } else {
                throw new Error('There is no connection to the server to listen to.');
              }
            } else {
              data.forEach(mod => {
                if (!excluded.includes(mod.peerId)) this.processMod(mod.profile);
              });
              this.unfetchedMods = [];
              this.checkNotFetched();
              if (!data.length) this.trigger('noModsFound', { guids: IDs });
            }
          })
          .fail((xhr) => {
            if (xhr.statusText === 'abort') return;
            const failReason = xhr.responseJSON ? `\n\n${xhr.responseJSON.reason}` : '';
            const msg = `${op.fetchErrorMsg}${failReason}`;
            openSimpleMessage(op.fetchErrorTitle, msg);
          });
      this.modFetches.push(fetch);
    }
  }

  removeModeratorsByID(IDs) {
    if (!IDs) {
      throw new Error('You must provide the ID or IDs to remove.');
    }
    // collect the models so they can be returned to the caller
    const removed = [];
    IDs.forEach(id => {
      removed.push(this.moderatorsCol.get(id));
    });
    this.moderatorsCol.remove(IDs);
    return removed;
  }

  checkNotFetched() {
    const nfYet = this.unfetchedMods.length;
    if (nfYet === 0 && this.fetchingMods.length) {
      // all ids have been fetched and ids existed to fetch
      this.moderatorsStatus.setState({
        hidden: true,
      });
      // check if there are mods that loaded but none were valid
      if (!this.moderatorsCol.length && this.fetchingMods.length) {
        this.trigger('noValidModerators');
        this.setState({ noValidModerators: true });
      }
      // check if no valid verified mods where loaded
      if (this.verifiedMods.length && !this.modCount) {
        this.trigger('noValidVerifiedModerators');
        this.setState({ noValidVerifiedModerators: true });
      }
    } else {
      // either ids are still fetching, or this is an open fetch with no set ids
      this.moderatorsStatus.setState({
        loaded: this.fetchingMods.length - nfYet, // not shown if open fetch
        total: this.fetchingMods.length ? this.fetchingMods.length : this.modCount,
      });
    }
  }

  addMod(model) {
    if (!model || !(model instanceof Moderator)) {
      throw new Error('Please provide a moderator model.');
    }

    const modCard = this.createChild(ModCard, {
      model,
      purchase: this.options.purchase,
      notSelected: this.options.notSelected,
      cardState: this.options.cardState,
      radioStyle: this.options.radioStyle,
      controlsOnInvalid: this.options.controlsOnInvalid,
    });
    this.listenTo(modCard, 'modSelectChange', (data) => {
      if (data.selected) {
        this.noneSelected = false;
        // if only one moderator should be selected, deselect the other moderators
        if (this.options.singleSelect) this.deselectOthers(data.guid);
        this.trigger('cardSelect');
      }
    });
    // add verified mods to the beginning
    if (app.verifiedMods.get(model.get('peerID'))) {
      const firstUnverifiedIndex = this.modCards.findIndex(card =>
        !app.verifiedMods.get(card.model.get('peerID')));
      const insertAtIndex = firstUnverifiedIndex < 0 ?
        0 : firstUnverifiedIndex;
      this.modCards.splice(insertAtIndex, 0, modCard);
    } else {
      this.modCards.push(modCard);
    }
    return modCard;
  }

  get excludeIDs() {
    return this._excludeIDs;
  }

  set excludeIDs(IDs) {
    this._excludeIDs = IDs;
  }

  get modCount() {
    // return the number of visible cards. The collection may have unshown invalid moderators.
    return this.modCards.length;
  }

  get allIDs() {
    return this.moderatorsCol.pluck('peerID');
  }

  get selectedIDs() {
    const IDs = [];
    this.modCards.forEach((mod) => {
      if (mod.cardState === 'selected') {
        IDs.push(mod.model.id);
      }
    });
    return IDs;
  }

  get unselectedIDs() {
    const IDs = [];
    this.modCards.forEach((mod) => {
      if (mod.cardState !== 'selected') {
        IDs.push(mod.model.id);
      }
    });
    return IDs;
  }

  deselectMod(guid) {
    if (!guid) throw new Error('You must provide a guid.');

    const mod = this.modCards.filter(card => card.model.get('peerID') === guid);
    if (mod.length) mod[0].changeSelectState(this.options.notSelected);
  }

  deselectOthers(guid = '') {
    this.modCards.forEach((card) => {
      if (card.model.get('peerID') !== guid) {
        card.changeSelectState(this.options.notSelected);
      }
    });
    this.noneSelected = !guid;
  }

  togVerifiedShown(bool) {
    this.setState({ showVerifiedOnly: bool });
  }

  get noneSelected() {
    return this._noneSelected;
  }

  set noneSelected(bool) {
    this._noneSelected = bool;
  }

  remove() {
    this.modFetches.forEach(fetch => fetch.abort());
    super.remove();
  }

  render() {
    loadTemplate('components/moderators.html', t => {
      this.$el.html(t({
        wrapperClasses: this.options.wrapperClasses,
        showVerifiedOnly: this.options.showVerifiedOnly,
        placeholder: !this.modCards.length,
        purchase: this.options.purchase,
        ...this.getState(),
      }));

      super.render();
      this.modCards.forEach(mod => {
        mod.delegateEvents();
        this.getCachedEl('.js-moderatorsWrapper').append(mod.render().$el);
      });

      this.moderatorsStatus.delegateEvents();
      this.getCachedEl('.js-statusWrapper').append(this.moderatorsStatus.render().$el);
    });

    return this;
  }
}
<commit_msg>Remove changed data<commit_after>import _ from 'underscore';
import $ from 'jquery';
import loadTemplate from '../../utils/loadTemplate';
import app from '../../app';
import { getSocket } from '../../utils/serverConnect';
import { openSimpleMessage } from '../modals/SimpleMessage';
import Moderators from '../../collections/Moderators';
import Moderator from '../../models/profile/Profile';
import baseVw from '../baseVw';
import ModCard from './ModeratorCard';
import ModeratorsStatus from './ModeratorsStatus';

export default class extends baseVw {
  /**
   * @param {string}  options.apiPath           - Current options are fetchprofiles and moderators.
   * @param {boolean} options.async             - Return profiles via websocket.
   * @param {boolean} options.useCache          - Use cached data for faster speed.
   * @param {array}   options.moderatorIDs      - list of moderators to retrieve. If none get all.
   * @param {array}   options.excludeIDs        - list of moderators to not use.
   * @param {string}  options.method            - POST or GET
   * @param {string}  options.include           - If apiPath is moderator, set to 'profile' or only
   *                                              the peerIDs of each moderator are returned.
   * @param {boolean} options.purchase          - If this is used in a purchase, pass this to the
   *                                              child moderator card views.
   * @param {boolean} options.singleSelect      - Allow only one moderator to be selected at a time.
   * @param {boolean} options.selectFirst       - Pre-select the first moderator.
   * @param {boolean} options.radioStyle        - Show the moderator cards with radio buttons.
   * @param {boolean} options.showInvalid       - Show invalid moderator cards.
   * @param {boolean} options.controlsOnInvalid - Show controls on invalid cards so they can be
   *                                              removed or otherwise acted on.
   * @param {string}  options.wrapperClasses    - Add classes to the card container.
   * @param {string}  options.fetchErrorTitle   - A title for the fetch error.
   * @param {string}  options.cardState         - The initial state for cards that are created.
   * @param {string}  options.notSelected       - Which not selected state to use on the mod cards.
   * @param {boolean} options.showLoadBtn       - Show the load more button in the status bar.
   * @param {boolean} options.showSpinner       - Show the spinner in the status bar
   * @param {boolean} options.showVerifiedOnly  - Show only verified moderators
   */

  constructor(options = {}) {
    if (!options.fetchErrorTitle) {
      throw new Error('Please provide error text for the moderator fetch.');
    }

    const opts = {
      className: 'moderatorsList',
      apiPath: 'fetchprofiles',
      async: true,
      useCache: true,
      moderatorIDs: [],
      excludeIDs: [],
      method: 'POST',
      include: '',
      purchase: false,
      singleSelect: false,
      selectFirst: false,
      radioStyle: false,
      controlsOnInvalid: false,
      cardState: 'unselected',
      notSelected: 'unselected',
      showLoadBtn: false,
      showSpinner: true,
      ...options,
    };

    if (!opts.apiPath || ['fetchprofiles', 'moderators'].indexOf(opts.apiPath) === -1) {
      throw new Error('The apiPath must be either fetchprofiles or moderators');
    }
    if (opts.apiPath === 'moderators') {
      if (opts.moderatorIDs.length) {
        throw new Error('If the apiPath is moderators, a list of IDs is not used.');
      }
    } else if (opts.apiPath === 'fetchprofiles' && opts.include) {
      throw new Error('If the apiPath is fetchprofiles, the include parameter is not used.');
    }
    if (typeof opts.async !== 'boolean') {
      throw new Error('The value of async must be a boolean');
    }
    if (!opts.fetchErrorTitle) {
      throw new Error('Please provide a title for the fetch error.');
    }

    if (opts.apiPath === 'moderators') {
      // this will fetch available moderators without POSTing a list of IDs.
      opts.include = 'profile';
      opts.method = 'GET';
    }

    super(opts);
    this.options = opts;
    this.excludeIDs = opts.excludeIDs;
    this.unfetchedMods = [];
    this.fetchingMods = [];
    this.verifiedMods = [];
    this.modFetches = [];
    this.moderatorsCol = new Moderators();
    this.listenTo(this.moderatorsCol, 'add', model => {
      const modCard = this.addMod(model);

      // if required, select the first  moderator
      if (opts.selectFirst && !this.firstSelected && !this.noneSelected) {
        let selectIt = true;
        // if only verified mods are shown, only select the first verified mod
        if (opts.showVerifiedOnly) selectIt = !!app.verifiedMods.get(model.get('peerID'));

        if (selectIt) {
          this.firstSelected = true;
          modCard.changeSelectState('selected');
        }
      }
      this.render();
    });
    this.listenTo(this.moderatorsCol, 'remove', (md) => {
      const removeIndex = this.modCards.findIndex(card => card.model === md);
      this.modCards.splice(removeIndex, 1)[0].remove();
    });
    this.modCards = [];

    // create a moderator status view. It should retain it's state between renders of this view.
    this.moderatorsStatus = this.createChild(ModeratorsStatus, {
      initialState: {
        mode: opts.method === 'GET' ? 'loaded' : '',
        showLoadBtn: opts.showLoadBtn,
        showSpinner: opts.showSpinner,
      },
    });
    this.listenTo(this.moderatorsStatus, 'browseMore', () => this.onBrowseMore());

    // listen to the websocket for moderator data
    this.serverSocket = getSocket();
  }

  events() {
    return {
      'click .js-showUnverified': 'clickShowUnverified',
    };
  }

  clickShowUnverified() {
    this.togVerifiedShown(false);
    this.trigger('clickShowUnverified');
  }

  onBrowseMore() {
    this.getModeratorsByID();
  }

  removeNotFetched(ID) {
    this.unfetchedMods = this.unfetchedMods.filter(peerID => peerID !== ID);
    this.checkNotFetched();
  }

  processMod(data) {
    // Don't add profiles that are not moderators unless showInvalid is true. The ID list may have
    // peerIDs that are out of date, and are no longer moderators.
    const validMod = data.moderator && data.moderatorInfo;
    // if the moderator has an invalid currency, remove them from the list
    const buyerCur = app.serverConfig.cryptoCurrency;
    const modCurs = data.moderatorInfo && data.moderatorInfo.acceptedCurrencies || [];
    const validCur = modCurs.includes(buyerCur);

    if ((!!validMod && validCur || this.options.showInvalid)) {
      this.moderatorsCol.add(new Moderator(data, { parse: true }));
      this.removeNotFetched(data.peerID);
    } else {
      // remove the invalid moderator from the notFetched list
      this.removeNotFetched(data.peerID);
    }
  }

  getModeratorsByID(opts) {
    const op = {
      ...this.options,
      ...opts,
    };

    if (!Array.isArray(op.moderatorIDs)) {
      throw new Error('Please provide the list of moderators as an array.');
    }

    // don't get any that have already been added or excluded, or the user's own id.
    const excluded = [...this.allIDs, ...this.excludeIDs, app.profile.id];
    const IDs = _.without(op.moderatorIDs, excluded);
    const includeString = op.include ? `&include=${op.include}` : '';
    const urlString =
      `ob/${op.apiPath}?async=${op.async}${includeString}&usecache=${op.useCache}`;
    const url = app.getServerUrl(urlString);

    this.unfetchedMods = IDs;
    this.fetchingMods = IDs;
    this.verifiedMods = app.verifiedMods.matched(IDs);

    this.setState({
      noValidModerators: false,
      noValidVerifiedModerators: !this.verifiedMods.length && this.fetchingMods.length,
    });

    // Either a list of IDs can be posted, or any available moderators can be retrieved with GET
    if (IDs.length || op.method === 'GET') {
      this.moderatorsStatus.setState({
        hidden: false,
        loaded: this.modCount,
        total: IDs.length ? IDs.length : this.modCount,
        showSpinner: op.showSpinner, //unhides the spinner if it's been hidden by the timer
      });

      const fetch = $.ajax({
        url,
        data: JSON.stringify(IDs),
        method: op.method,
      })
          .done((data) => {
            if (op.async) {
              const socketID = data.id;
              if (this.serverSocket) {
                this.listenTo(this.serverSocket, 'message', (event) => {
                  const eventData = event.jsonData;
                  if (eventData.error) {
                    // errors don't have a message id, check to see if the peerID matches
                    if (IDs.includes(eventData.peerId)) {
                      // provide the expected capitalization of peerID
                      eventData.peerID = eventData.peerId;
                      delete eventData.peerId;
                      this.processMod(eventData);
                    }
                  } else if (eventData.id === socketID && !excluded.includes(eventData.peerId)) {
                    this.processMod(eventData.profile);
                  }
                });
              } else {
                throw new Error('There is no connection to the server to listen to.');
              }
            } else {
              data.forEach(mod => {
                if (!excluded.includes(mod.peerId)) this.processMod(mod.profile);
              });
              this.unfetchedMods = [];
              this.checkNotFetched();
              if (!data.length) this.trigger('noModsFound', { guids: IDs });
            }
          })
          .fail((xhr) => {
            if (xhr.statusText === 'abort') return;
            const failReason = xhr.responseJSON ? `\n\n${xhr.responseJSON.reason}` : '';
            const msg = `${op.fetchErrorMsg}${failReason}`;
            openSimpleMessage(op.fetchErrorTitle, msg);
          });
      this.modFetches.push(fetch);
    }
  }

  removeModeratorsByID(IDs) {
    if (!IDs) {
      throw new Error('You must provide the ID or IDs to remove.');
    }
    // collect the models so they can be returned to the caller
    const removed = [];
    IDs.forEach(id => {
      removed.push(this.moderatorsCol.get(id));
    });
    this.moderatorsCol.remove(IDs);
    return removed;
  }

  checkNotFetched() {
    const nfYet = this.unfetchedMods.length;
    if (nfYet === 0 && this.fetchingMods.length) {
      // all ids have been fetched and ids existed to fetch
      this.moderatorsStatus.setState({
        hidden: true,
      });
      // check if there are mods that loaded but none were valid
      if (!this.moderatorsCol.length && this.fetchingMods.length) {
        this.trigger('noValidModerators');
        this.setState({ noValidModerators: true });
      }
      // check if no valid verified mods where loaded
      if (this.verifiedMods.length && !this.modCount) {
        this.trigger('noValidVerifiedModerators');
        this.setState({ noValidVerifiedModerators: true });
      }
    } else {
      // either ids are still fetching, or this is an open fetch with no set ids
      this.moderatorsStatus.setState({
        loaded: this.fetchingMods.length - nfYet, // not shown if open fetch
        total: this.fetchingMods.length ? this.fetchingMods.length : this.modCount,
      });
    }
  }

  addMod(model) {
    if (!model || !(model instanceof Moderator)) {
      throw new Error('Please provide a moderator model.');
    }

    const modCard = this.createChild(ModCard, {
      model,
      purchase: this.options.purchase,
      notSelected: this.options.notSelected,
      cardState: this.options.cardState,
      radioStyle: this.options.radioStyle,
      controlsOnInvalid: this.options.controlsOnInvalid,
    });
    this.listenTo(modCard, 'modSelectChange', (data) => {
      if (data.selected) {
        this.noneSelected = false;
        // if only one moderator should be selected, deselect the other moderators
        if (this.options.singleSelect) this.deselectOthers(data.guid);
        this.trigger('cardSelect');
      }
    });
    // add verified mods to the beginning
    if (app.verifiedMods.get(model.get('peerID'))) {
      const firstUnverifiedIndex = this.modCards.findIndex(card =>
        !app.verifiedMods.get(card.model.get('peerID')));
      const insertAtIndex = firstUnverifiedIndex < 0 ?
        0 : firstUnverifiedIndex;
      this.modCards.splice(insertAtIndex, 0, modCard);
    } else {
      this.modCards.push(modCard);
    }
    return modCard;
  }

  get excludeIDs() {
    return this._excludeIDs;
  }

  set excludeIDs(IDs) {
    this._excludeIDs = IDs;
  }

  get modCount() {
    // return the number of visible cards. The collection may have unshown invalid moderators.
    return this.modCards.length;
  }

  get allIDs() {
    return this.moderatorsCol.pluck('peerID');
  }

  get selectedIDs() {
    const IDs = [];
    this.modCards.forEach((mod) => {
      if (mod.cardState === 'selected') {
        IDs.push(mod.model.id);
      }
    });
    return IDs;
  }

  get unselectedIDs() {
    const IDs = [];
    this.modCards.forEach((mod) => {
      if (mod.cardState !== 'selected') {
        IDs.push(mod.model.id);
      }
    });
    return IDs;
  }

  deselectMod(guid) {
    if (!guid) throw new Error('You must provide a guid.');

    const mod = this.modCards.filter(card => card.model.get('peerID') === guid);
    if (mod.length) mod[0].changeSelectState(this.options.notSelected);
  }

  deselectOthers(guid = '') {
    this.modCards.forEach((card) => {
      if (card.model.get('peerID') !== guid) {
        card.changeSelectState(this.options.notSelected);
      }
    });
    this.noneSelected = !guid;
  }

  togVerifiedShown(bool) {
    this.setState({ showVerifiedOnly: bool });
  }

  get noneSelected() {
    return this._noneSelected;
  }

  set noneSelected(bool) {
    this._noneSelected = bool;
  }

  remove() {
    this.modFetches.forEach(fetch => fetch.abort());
    super.remove();
  }

  render() {
    loadTemplate('components/moderators.html', t => {
      this.$el.html(t({
        wrapperClasses: this.options.wrapperClasses,
        showVerifiedOnly: this.options.showVerifiedOnly,
        placeholder: !this.modCards.length,
        purchase: this.options.purchase,
        ...this.getState(),
      }));

      super.render();
      this.modCards.forEach(mod => {
        mod.delegateEvents();
        this.getCachedEl('.js-moderatorsWrapper').append(mod.render().$el);
      });

      this.moderatorsStatus.delegateEvents();
      this.getCachedEl('.js-statusWrapper').append(this.moderatorsStatus.render().$el);
    });

    return this;
  }
}
<|endoftext|>


<commit_before>
var Layer = {}
  , Validator = require('schema-validator')
  , Inflection = require('inflection');

// Logging Layer
Layer.log = {
  ext: require('extlog'),
  level: 'info'
};

// Setup Logging
Layer.log.core = new Layer.log.ext('Layer', "green");
Layer.log.database = new Layer.log.ext('Database', "blue");
Layer.log.model = new Layer.log.ext('Model', "cyan");
Layer.log.routing = new Layer.log.ext('Routing', "magenta");

Layer.start = function (server, database) {
  Layer.log.ext.setMinLevel(Layer.log.level);

  if (!server) Layer.log.core.fatal("Server parameter is empty... we suggest using express.");
  if (!database || database && !database.name || database && typeof database !== 'object')
    Layer.log.core.fatal("Database argument is invalid, must be an object and have a name property.");

  Layer.log.core.info('Flattening Time & Space');
  Layer.log.core.info('Loading Database Adapter: layer-' + database.name);

  var Database = require('layer-' + database.name);
  if (!Database || typeof Database !== 'function')
    Layer.log.core.fatal("Invalid database adapter name", database.name);

  var library = {
    application: server,
    store: {},
    database: new Database(database, library, Layer),
    define: function (name, object) {
      Layer.log.core.info('Defining Model: ' + name);
      library.store[name] = Layer.model(library, name, object);

      if (object.collection) {
        var collection;

        // Check Collection Name
        if (typeof object.collection === 'string') collection = object.collection
        else collection = name;

        // Remove Collection
        delete object.collection;

        // Define Database Collection / Model
        library.database.define(name, library.store[name].build());
      }

      // Return built model
      return library.store[name];
    },

    listen: function (port) {
      Layer.log.core.info('Server started at:', 'http://localhost:' + port + '/');
      library.application.listen(port);
    }
  };

  return library;
};

Layer.model = function (parent, name, object) {
  var route = name.toLowerCase();
  var routing = {
    handle: function (route) {
      if (typeof route === 'undefined' || (typeof route === 'boolean' && route)) route = {
        fetchAll: { remove: [ 'password' ] },
        fetch: { by: 'id', searchable: true },
        create: true,
        count: false,
        empty: false,
        upsert: { by: 'id' },
        update: { by: 'id' },
        remove: { by: 'id' }
      };

      Layer.log.routing.info('Creating routes...');

      for (var key in route)
        if (!route.hasOwnProperty(key)) continue;
        else if (routing[key]) routing[key](typeof route[key] === 'object' ? route[key] : {});
    },

    all: function (remove, middleware) {
      Layer.log.routing.debug('Creating fetch-all route', 'GET /' + inflection.pluralize(route));

      parent.application.get('/' + inflection.pluralize(route), (middleware || []), function (request, result) {
        var filter = {};

        filter.offset = request.query['offset'] || undefined;

        parent.database.all(name, filter, function (error, data) {
          if (error) result.json(404, { error: { message: 'No data found.' }});
          else result.json(200, data);
        })
      });
    },

    fetch: function (options) {
      Layer.log.routing.debug('Creating fetch route', '   GET /' + route + ('/:' + options.by));

      parent.application.get('/' + route + ('/:' + options.by), (options.middleware || []), function (request, result) {
        var query = { };

        query[options.by] = request.params[options.by];

        parent.database.find(name, query, function (error, data) {
          if (error) result.json(404, { error: { message: 'No data found.' }});
          else result.json(200, data);
        });
      });
    },

    create: function (options) {
      Layer.log.routing.debug('Creating create route', '  POST /' + route);

      parent.application.post('/' + route, (options.middleware || []), function (request, result) {
        var data = request.body;
        var validation = new Validator(definition.build());
        var check = validation.check(data);

        if (check._error) {
          result.json(404, { error: { fields: check }});
        } else {
          parent.database.create(name, data, function (error, data) {
            if (error) result.json(404, { error: { message: 'Could not create entry.', details: error }});
            else result.json(200, data);
          });
        }
      });
    },

    upsert: function (options) {
      Layer.log.routing.debug('Creating update/insert (upsert) route', '   PUT /' + route + ('/:' + options.by));

      parent.application.put('/' + route + ('/:' + options.by), (options.middleware || []), function (request, result) {
        var data = request.body;
        var validation = new Validator(definition.build());
        var check = validation.check(data);

        if (check._error) {
          result.json(404, { error: { fields: check }});
        } else {
          data.where = {};
          data.where[options.by] = request.params[options.by];
          parent.database.upsert(name, data, function (error, data) {
            if (error) result.json(500, { error: { message: 'Could not upsert entry with ' + options.by + ' of ' + data.where[options.by], details: error }});
            else result.json(200, data);
          });
        }
      });
    },

    update: function (options) {
      Layer.log.routing.debug('Creating update route', ' PATCH /' + route + ('/:' + options.by));

      parent.application.patch('/' + route + ('/:' + options.by), (options.middleware || []), function (request, result) {
        var data = request.body;
        var validation = new Validator(definition.build());
        var check = validation.check(data);

        if (check._error) {
          result.json(404, { error: { fields: check }});
        } else {
          data.where = {};
          data.where[options.by] = request.params[options.by];
          parent.database.update(name, data, function (error, data) {
            if (error) result.json(500, { error: { message: 'Could not update entry with ' + options.by + ' of ' + data.where[options.by] }});
            else result.json(200, data);
          });
        }
      });
    },

    remove: function (options) {
      Layer.log.routing.debug('Creating remove route', 'DELETE /' + route + ('/:' + options.by));

      parent.application.delete('/' + route + ('/:' + options.by), (options.middleware || []), function (request, result) {
        var query = { where: {} };

        query.where[options.by] = request.params[options.by];

        parent.database.update(name, query, function (error, data) {
          if (error) result.json(500, { error: { message: 'Could not delete entry with ' + options.by + ' of ' + data.where[options.by] }});
          else result.json(200, data);
        });
      });
    },

    count: function (options) {
      Layer.log.routing.debug('Creating count route', '   GET /' + route);

      parent.application.get('/' + route + '/count', (options.middleware || []), function (request, result) {
        var filter = {};
        parent.database.count(name, filter, function (error, data) {
          if (error) result.json(500, { error: { message: 'No data found.' }});
          else result.json(200, data);
        });
      });
    },

    empty: function (options) {
      Layer.log.routing.debug('Creating empty route', '   GET /' + route);

      parent.application.get('/' + route + '/empty', (options.middleware || []), function (request, result) {
        parent.database.empty(name, function (error, data) {
          if (error) result.json(404, { error: { message: 'No data found.' }});
          else result.send(200);
        });
      });
    }
  };

  var definition = {
    name: name,
    store: {},

    property: function (key, object) {
      if (!key) Layer.log.model.fatal("Missing property key for object", object);
      if (key && !object) return definition.store[key];
      if (key && typeof object === 'string') {
        definition.store[key] = Layer.store[type];
      } else definition.store[key] = object;
      return definition;
    },

    build: function () {
      var output = {};

      for (var key in definition.store)
        if (definition.store.hasOwnProperty(key))
          if (Object.prototype.toString.call(definition.store[key]) === '[object Object]')
            if (definition.store[key].store) output[key] = definition.store[key].build();
            else output[key] = definition.store[key];
          else
            output[key] = definition.store[key];

      return output;
    }
  };

  // Routing
  routing.handle(object ? object.routing : undefined);

  // Properties
  if (object) {
    if (object.routing)
      delete object.routing;

    for (var key in object) {
      if (object[key].parent) definition.property(key, object[key].parent);
      else if (object[key].type) definition.property(key, object[key]);
    }
  }

  // Definition
  return definition;
};

module.exports = Layer;<commit_msg>logging level<commit_after>
var Layer = {}
  , Validator = require('schema-validator')
  , Inflection = require('inflection');

// Logging Layer
Layer.log = {
  ext: require('extlog'),
  level: 'info'
};

// Setup Logging
Layer.log.core = new Layer.log.ext('Layer', "green");
Layer.log.database = new Layer.log.ext('Database', "blue");
Layer.log.model = new Layer.log.ext('Model', "cyan");
Layer.log.routing = new Layer.log.ext('Routing', "magenta");

Layer.level = function (level) {
  Layer.log.level = level;
};

Layer.start = function (server, database) {
  Layer.log.ext.setMinLevel(Layer.log.level);

  if (!server) Layer.log.core.fatal("Server parameter is empty... we suggest using express.");
  if (!database || database && !database.name || database && typeof database !== 'object')
    Layer.log.core.fatal("Database argument is invalid, must be an object and have a name property.");

  Layer.log.core.info('Flattening Time & Space');
  Layer.log.core.info('Loading Database Adapter: layer-' + database.name);

  var Database = require('layer-' + database.name);
  if (!Database || typeof Database !== 'function')
    Layer.log.core.fatal("Invalid database adapter name", database.name);

  var library = {
    application: server,
    store: {},
    database: new Database(database, library, Layer),
    define: function (name, object) {
      Layer.log.core.info('Defining Model: ' + name);
      library.store[name] = Layer.model(library, name, object);

      if (object.collection) {
        var collection;

        // Check Collection Name
        if (typeof object.collection === 'string') collection = object.collection
        else collection = name;

        // Remove Collection
        delete object.collection;

        // Define Database Collection / Model
        library.database.define(name, library.store[name].build());
      }

      // Return built model
      return library.store[name];
    },

    listen: function (port) {
      Layer.log.core.info('Server started at:', 'http://localhost:' + port + '/');
      library.application.listen(port);
    }
  };

  return library;
};

Layer.model = function (parent, name, object) {
  var route = name.toLowerCase();
  var routing = {
    handle: function (route) {
      if (typeof route === 'undefined' || (typeof route === 'boolean' && route)) route = {
        fetchAll: { remove: [ 'password' ] },
        fetch: { by: 'id', searchable: true },
        create: true,
        count: false,
        empty: false,
        upsert: { by: 'id' },
        update: { by: 'id' },
        remove: { by: 'id' }
      };

      Layer.log.routing.info('Creating routes...');

      for (var key in route)
        if (!route.hasOwnProperty(key)) continue;
        else if (routing[key]) routing[key](typeof route[key] === 'object' ? route[key] : {});
    },

    all: function (remove, middleware) {
      Layer.log.routing.debug('Creating fetch-all route', 'GET /' + inflection.pluralize(route));

      parent.application.get('/' + inflection.pluralize(route), (middleware || []), function (request, result) {
        var filter = {};

        filter.offset = request.query['offset'] || undefined;

        parent.database.all(name, filter, function (error, data) {
          if (error) result.json(404, { error: { message: 'No data found.' }});
          else result.json(200, data);
        })
      });
    },

    fetch: function (options) {
      Layer.log.routing.debug('Creating fetch route', '   GET /' + route + ('/:' + options.by));

      parent.application.get('/' + route + ('/:' + options.by), (options.middleware || []), function (request, result) {
        var query = { };

        query[options.by] = request.params[options.by];

        parent.database.find(name, query, function (error, data) {
          if (error) result.json(404, { error: { message: 'No data found.' }});
          else result.json(200, data);
        });
      });
    },

    create: function (options) {
      Layer.log.routing.debug('Creating create route', '  POST /' + route);

      parent.application.post('/' + route, (options.middleware || []), function (request, result) {
        var data = request.body;
        var validation = new Validator(definition.build());
        var check = validation.check(data);

        if (check._error) {
          result.json(404, { error: { fields: check }});
        } else {
          parent.database.create(name, data, function (error, data) {
            if (error) result.json(404, { error: { message: 'Could not create entry.', details: error }});
            else result.json(200, data);
          });
        }
      });
    },

    upsert: function (options) {
      Layer.log.routing.debug('Creating update/insert (upsert) route', '   PUT /' + route + ('/:' + options.by));

      parent.application.put('/' + route + ('/:' + options.by), (options.middleware || []), function (request, result) {
        var data = request.body;
        var validation = new Validator(definition.build());
        var check = validation.check(data);

        if (check._error) {
          result.json(404, { error: { fields: check }});
        } else {
          data.where = {};
          data.where[options.by] = request.params[options.by];
          parent.database.upsert(name, data, function (error, data) {
            if (error) result.json(500, { error: { message: 'Could not upsert entry with ' + options.by + ' of ' + data.where[options.by], details: error }});
            else result.json(200, data);
          });
        }
      });
    },

    update: function (options) {
      Layer.log.routing.debug('Creating update route', ' PATCH /' + route + ('/:' + options.by));

      parent.application.patch('/' + route + ('/:' + options.by), (options.middleware || []), function (request, result) {
        var data = request.body;
        var validation = new Validator(definition.build());
        var check = validation.check(data);

        if (check._error) {
          result.json(404, { error: { fields: check }});
        } else {
          data.where = {};
          data.where[options.by] = request.params[options.by];
          parent.database.update(name, data, function (error, data) {
            if (error) result.json(500, { error: { message: 'Could not update entry with ' + options.by + ' of ' + data.where[options.by] }});
            else result.json(200, data);
          });
        }
      });
    },

    remove: function (options) {
      Layer.log.routing.debug('Creating remove route', 'DELETE /' + route + ('/:' + options.by));

      parent.application.delete('/' + route + ('/:' + options.by), (options.middleware || []), function (request, result) {
        var query = { where: {} };

        query.where[options.by] = request.params[options.by];

        parent.database.update(name, query, function (error, data) {
          if (error) result.json(500, { error: { message: 'Could not delete entry with ' + options.by + ' of ' + data.where[options.by] }});
          else result.json(200, data);
        });
      });
    },

    count: function (options) {
      Layer.log.routing.debug('Creating count route', '   GET /' + route);

      parent.application.get('/' + route + '/count', (options.middleware || []), function (request, result) {
        var filter = {};
        parent.database.count(name, filter, function (error, data) {
          if (error) result.json(500, { error: { message: 'No data found.' }});
          else result.json(200, data);
        });
      });
    },

    empty: function (options) {
      Layer.log.routing.debug('Creating empty route', '   GET /' + route);

      parent.application.get('/' + route + '/empty', (options.middleware || []), function (request, result) {
        parent.database.empty(name, function (error, data) {
          if (error) result.json(404, { error: { message: 'No data found.' }});
          else result.send(200);
        });
      });
    }
  };

  var definition = {
    name: name,
    store: {},

    property: function (key, object) {
      if (!key) Layer.log.model.fatal("Missing property key for object", object);
      if (key && !object) return definition.store[key];
      if (key && typeof object === 'string') {
        definition.store[key] = Layer.store[type];
      } else definition.store[key] = object;
      return definition;
    },

    build: function () {
      var output = {};

      for (var key in definition.store)
        if (definition.store.hasOwnProperty(key))
          if (Object.prototype.toString.call(definition.store[key]) === '[object Object]')
            if (definition.store[key].store) output[key] = definition.store[key].build();
            else output[key] = definition.store[key];
          else
            output[key] = definition.store[key];

      return output;
    }
  };

  // Routing
  routing.handle(object ? object.routing : undefined);

  // Properties
  if (object) {
    if (object.routing)
      delete object.routing;

    for (var key in object) {
      if (object[key].parent) definition.property(key, object[key].parent);
      else if (object[key].type) definition.property(key, object[key]);
    }
  }

  // Definition
  return definition;
};

module.exports = Layer;<|endoftext|>


<commit_before>import React, { Component, PropTypes } from 'react'
import { bindActionCreators } from 'redux'
import { connect } from 'react-redux'
import { Link } from 'react-router'

import SecondaryNavBar from '../components/SecondaryNavBar'
import PageHeader from '../components/PageHeader'
import StatusBar from '../components/StatusBar'

function mapDispatchToProps(dispatch) {
  return bindActionCreators({}, dispatch)
}

class AccountApp extends Component {
  static propTypes = {
    children: PropTypes.object
  }

  constructor(props) {
    super(props)

    this.state = { }
  }

  render() {
    return (

        <StatusBar />
        { this.props.location.pathname !== '/account' && (
        <SecondaryNavBar leftButtonTitle="Back" leftButtonLink="/account" />
        )}

              {this.props.children}

    )
  }
}

export default connect(null, mapDispatchToProps)(AccountApp)
<commit_msg>w-100 to account menu of settings<commit_after>import React, { Component, PropTypes } from 'react'
import { bindActionCreators } from 'redux'
import { connect } from 'react-redux'
import { Link } from 'react-router'

import SecondaryNavBar from '../components/SecondaryNavBar'
import PageHeader from '../components/PageHeader'
import StatusBar from '../components/StatusBar'

function mapDispatchToProps(dispatch) {
  return bindActionCreators({}, dispatch)
}

class AccountApp extends Component {
  static propTypes = {
    children: PropTypes.object
  }

  constructor(props) {
    super(props)

    this.state = { }
  }

  render() {
    return (

        <StatusBar />
        { this.props.location.pathname !== '/account' && (
        <SecondaryNavBar leftButtonTitle="Back" leftButtonLink="/account" />
        )}

              {this.props.children}

    )
  }
}

export default connect(null, mapDispatchToProps)(AccountApp)
<|endoftext|>