<commit_before>import React from 'react';

import TextField from 'material-ui/TextField';
import RaisedButton from 'material-ui/RaisedButton';
import Checkbox from 'material-ui/Checkbox';
import FlatButton from 'material-ui/FlatButton';

import StageSelective from './utils/StageSelective';
import CommitMessage from './utils/CommitMessage';

import * as fs from 'fs';
// to watch over files and reload diffPanel when any changes occur
import watch from 'watch';
import Git, { Diff } from 'nodegit';

const constStyles = {
  fontFamily: `apple-system,
          BlinkMacSystemFont,"Segoe UI",
          Roboto,Helvetica,Arial,sans-serif,
          "Apple Color Emoji","Segoe UI Emoji",
          "Segoe UI Symbol"`,
  darkRed: '#b60a0a',
  grey: '#ededed',
  blue: '#9CE8FA',
  darkBlue: '#0C6990'
};

class gitFunctions {

  static createCommit(repoPath, oid, commitMsg) {
    let index;
    let repo;

    // repoName = "../"+repoName;

    let pathToRepo = require('path').resolve(repoPath);

    return Git.Repository.open(pathToRepo)
      .then(function(repoResult) {
        repo = repoResult;
        return Git.Reference.nameToId(repo, "HEAD");
      })
      .then(function(head) {
        return repo.getCommit(head);
      })
      .then(function(parent) {
        const currTimeInSecs = (new Date()).getTime()/1000;
        let author = Git.Signature.create("Author name",
          "emailid@domain.com", currTimeInSecs , 0);
        let committer = Git.Signature.create("Author name",
          "emailid@domain.com", currTimeInSecs , 0);

        return repo.createCommit("HEAD", author, committer, commitMsg, oid, [parent]);
      });
  }

  static getStatus(repoPath) {

    let pathToRepo = require('path').resolve(repoPath);

    return Git.Repository.open(pathToRepo);
  }

  static getIndex(repoPath) {

    let repo;
    let pathToRepo = require('path').resolve(repoPath);

    return Git.Repository.open(pathToRepo)
    .then( (repoResult) => {
      repo = repoResult;
    })
    .then(function() {
      return repo.refreshIndex();
    });

  }

  static getUnstagedChanges(repoPath) {

    const pathToRepo = require('path').resolve(repoPath);

    return Git.Repository.open(pathToRepo)
    .then( (repo) => {
      return Diff.indexToWorkdir(repo, null, {
      flags: Diff.OPTION.SHOW_UNTRACKED_CONTENT |
             Diff.OPTION.RECURSE_UNTRACKED_DIRS
      });
    });
  }
}

class CheckBoxWrapper extends React.Component {
    constructor(props) {
    super(props);
    this.state={
      checked: this.props.checked
    };
  }

  onEventCheck = (event, isInputChecked) => {
    event.stopPropagation();

    this.changeCheckedState(isInputChecked);
  }

  changeCheckedState = (isInputChecked) => {

    window.setTimeout( () => {
      this.setState({
        checked: isInputChecked? true : false
      });
    }, 0);

    // this.props.onChangeSelected(this.props.uniqueId, isInputChecked );
  }

  componentWillReceiveProps = (newprops) => {
  }

  render = () => {
    const styles={
      checkbox: {
        marginBottom: 6,
      },
      label: {
        color: constStyles.darkBlue,
        fontFamily: constStyles.fontFamily,
        fontSize: 15,
        width: '100%',
      }
    };

    return(

        <Checkbox style={styles.checkbox} label={this.props.label} labelStyle={styles.label}
         onCheck={this.onEventCheck} checked={this.state.checked} />

    )
  }
}

class ChangesList extends React.Component {
  constructor(props) {
    super(props);
    //contains refs to all the CheckBoxWrapper components
    this.checkboxRefs= [];
  }

  toggleWrapperState = () => {
    this.checkboxRefs.forEach( (ref, index) => {
      if(ref !== null)
        ref.changeCheckedState(this.props.selectAll);
    })
  }

  render = () => {
    let renderStatus;
    if(this.props.statuses.length === 0) {
      renderStatus =
No Changes!

    }
    else {
      renderStatus = this.props.statuses.map((status, index) => {
                      return <CheckBoxWrapper key={index} uniqueId={index} label={status.label}
                      ref={(ref) => this.checkboxRefs[index] = ref}
                      onChangeSelected={this.props.onChangeSelected} checked={this.props.selectAll} />;
        });
    }

    const styles = {
      main: {
        width: '85%',
        paddingLeft: '5%',
        paddingRight: '10%'
      }
    };
    return(

        {renderStatus}

    )
  }
}

class StatusTable extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      statuses: [],
      selectAll: true
    };
  }

  getStatus = (repo) => {
    let promise = gitFunctions.getStatus(repo);
    promise.then(function(repo) {
      repo.getStatus().then(function(status) {
        return status;
      })
      .done(function(statuses) {
        this.setState({
          statuses:statuses
        });
      }.bind(this));
    }.bind(this));
  }

  checkSelectAll = (event, isInputChecked) => {

    event.stopPropagation();

    window.setTimeout( () => {
      this.setState({
        selectAll: isInputChecked? true : false
      });

      //reaches into the changesList component, which
      //in turn reaches into individual CheckBoxWrapper components
      //and sets state accordingly
      this.changesList.toggleWrapperState(this.state.selectAll);
    }, 0);

  }

  componentWillReceiveProps = (newprops) => {
      this.getStatus(newprops.repo.path);
  }

  render = () => {
    let localStatus = [];
    if(this.state.statuses.length === 0)
    {
      localStatus = [];
    }
    else {

      const statusToText=function(status) {
        let words=[];
        let flag = 0;
        let key = 0;

        if (status.isNew()) { words.push(N); flag = 1;}
        if (status.isModified()) { words.push(M); flag = 1;}
        if (status.isTypechange()) { words.push(TYPECHANGED); flag = 1;}
        if (status.isRenamed()) { words.push(RENAMED); flag = 1;}
        if (status.isIgnored()) { words.push(IGNORED); flag = 1; }
        if(flag == 0) { words.push(D); }

        return words;
      };

      localStatus = this.state.statuses.map((file, index) => {

        let change=statusToText(file);

        const styles = {
          main: {
            display: 'flex',
            justifyContent: 'space-between',
          },
          path: {
            whiteSpace: 'nowrap',
            overflow: 'hidden',
            textOverflow: 'ellipsis',
            width: '90%',
          }
        };

        let changes = (

{file.path()}

{change}

        );

        return ({
          label:changes
        });

      });
    }

    const styles = {
      main:{
        width: '100%',
        border: '2px solid'+constStyles.blue,
        display: 'flex',
        flexDirection: 'column',
        justifyContent: 'center',
        alignItems: 'stretch',
        borderLeft: '0px',
        fontFamily: constStyles.fontFamily,
        color: constStyles.darkBlue,
      },
      status: {
        height: 50,
        backgroundColor: constStyles.blue,
        color:  constStyles.darkBlue,
        display: 'flex',
        paddingLeft: '7%',
        alignItems: 'center',
        fontWeight: 700,
      },
      labelStyle: {
        fontFamily: constStyles.fontFamily,
        color:constStyles.darkBlue,
      },
      checkBox: {
        border: '2px solid'+constStyles.blue,
        margin: 5,
        marginTop: 13,
        marginBottom: 13,
        width: '95%',
      },
      checkBoxes: {
        display: 'flex',
        flexDirection: 'column',
        justifyContent: 'flexStart',
        alignItems: 'center',
      }
    };
    return(

STATUS

          <Checkbox label='Select All:'
          labelStyle={styles.labelStyle}
          ref={(ref) => this.selectAll = ref} style={styles.checkBox}
          onCheck={this.checkSelectAll} checked={this.state.selectAll} labelPosition='left' />
          <ChangesList ref={(ref) => this.changesList = ref} selectAll={this.state.selectAll}
          statuses={localStatus} />

    )
  }
}

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

  render = () => {
    const styles = {
      main: {
        fontFamily: constStyles.fontFamily,
        color: constStyles.darkBlue,
        padding: '4%',
        paddingTop: '3%',
        paddingRight: '10%',
        borderTop: '2px solid'+constStyles.blue,
      },
      file: {
        padding: 4,
        fontSize: 14,
      },
      heading: {
        fontFamily: `'Oswald', sans-serif`,
        letterSpacing: 0,
        fontSize: 16,
        paddingBottom: 5,
      }
    }
    return (

      {this.props.indexEntries.length?

Staged Files:

          {this.props.indexEntries.map( (entry, index) => {
            return
{entry}
;
          })}

:
      null}

    );
  }
}

class StagingArea extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      indexPaths: [],
      diffs: [],
    };
  }

  addToIndex = () => {

    let checkedChanges = [];
    //reaches into each checkboxWrapper to obtain checked value
    checkedChanges = this.statusTable.changesList.checkboxRefs.map( (ref, index) => {
      if(ref !== null)
        return ref.state.checked;
    });

    let indexPaths =[];

    this.statusTable.changesList.props.statuses.forEach( (status, index) => {

      //if corresponding checkbox has been checked only
      if(checkedChanges[index])
        indexPaths.push(status);
    });

    window.setTimeout( () => {

      this.setState({
        indexPaths: indexPaths
      });
    }, 0);
  }

  createCommit = (commitMsg) => {

    let index;
    let oid;

    let promise = gitFunctions.getIndex(this.props.repo.path);
    promise = promise.then((indexResult) => {
      index = indexResult;
    });

    let filePaths = this.state.indexPaths.map( (status) => {
      return status.value;
    });

    //takes in initial promise, uses it to chain a new promise to it, and returns the promise,
    //which is used in the next iteration
    promise = filePaths.reduce( (prevPromise, path, currentIndex) => {
                return prevPromise.then( () => {
                  return index.addByPath(path);
                });
              }, promise);

    promise.then(() => {
      return index.write();
    })
    .then(() => {
      return index.writeTree();
    })
    .then( (oidResult) => {
      oid = oidResult;
      console.log('index tree written!');
    })
    //until here, all entries were added to the index and an index tree was written
    //from here, the new commit is generated using the index tree
    .then( () => {
      let innerPromise = gitFunctions.createCommit(this.props.repo.path, oid, commitMsg);
      innerPromise.done(function(commitId) {
        console.log('Commit Hash:'+commitId);
      });

    });
  }

  setupWatchActions = () => {

    const path = this.props.repo.path;

    fs.readFile(require('path').resolve(path, '.gitignore'), (err, data) => {
      if (err) {
        console.log('no .gitignore file found!');
        // no .gitignore file found, assume that all files
        // are being tracked by git
        return;
      }
      const lines = data.toString().split('\n').map((line) => {
        return line.trim();
      });
      let regexes = [];
      lines.forEach((line, index) => {

        // ignore comments
        if(!line.startsWith('#')) {
          // file extensions
          if(line.startsWith('*'))
            line = line.slice(1);
          // for directories
          else if(line.length !== 0)
            line = this.props.repo.name+'/'+line+'$';

          if(line.endsWith('*'))
            line = line.slice(0, line.length-1);
          if(line.startsWith('/'))
            line = line.slice(1);

          if(line.length !== 0)
            regexes.push(new RegExp(line));
        }
      });
      // to ignore the .git directory as well
      regexes.push(new RegExp('.git'+'$'));

      const filterIgnored = (f, stat) => {
        // tracks file/directory if true is returned
        let ignore = true;
        regexes.forEach((regex) => {
          if(regex.test(f)) {
            ignore = false;
          }
        });
        return ignore;
      };

      const configWatch = {
        ignoreDotFiles: false,
        ignoreUnreadAbleDir: true,
        ignoreNotPermitted: true,
        filter: filterIgnored
      };

      watch.createMonitor(this.props.repo.path, configWatch, (monitor) => {
        console.log('Files/directories being monitored: ', monitor.files);
        monitor.on("created", (f, stat) => {
          console.log('created',f, stat);
        })
        monitor.on("changed", (f, curr, prev) => {
          console.log('changed',f, curr, prev);
        })
        monitor.on("removed", (f, stat) => {
          console.log('removed',f, stat);
        })
      });
    });
  }

  componentDidMount = () => {
    let diffArr = [];
    if(this.props.repo) {
      gitFunctions.getUnstagedChanges(this.props.repo.path)
      .done( (diff) => {
        this.setState({
          diffs: [ diff ]
        });
      });

      this.setupWatchActions();
    }

  }

  componentWillReceiveProps = (newprops) => {
    let diffArr = [];
    if(newprops.repo)
      gitFunctions.getUnstagedChanges(newprops.repo.path)
      .done( (diff) => {
        this.setState({
          diffs: [ diff ]
        });
      });
  }

  render = () => {
    const styles = {
      main: {
        overflow: 'auto',
        display: 'flex',
      },
      createCommit: {
        overflow: 'auto',
        width: '30%',
        display: 'flex',
        flexDirection: 'column',
        color: constStyles.blue,
        border: '2px solid'+constStyles.blue,
      },
      selectiveDiffPanel: {
        width: '69%',
        height: 500,
        overflow: 'auto',
      },
      commitmsg: {
        borderTop: '2px solid'+constStyles.blue,
        borderBottom: '2px solid'+constStyles.blue,
      }
    };
    return (

            <StatusTable {...this.props} ref={(ref) => this.statusTable = ref} />
            <RaisedButton
            style={{width: '100%'}}
            label='Add to Index!'
            labelColor={constStyles.darkBlue} onMouseDown={this.addToIndex} />
            <IndexTable indexEntries={this.state.indexPaths.map( (status) => {return status.label})} />

            <CommitMessage buttonMsg='Create Commit!' commitCB={this.createCommit} />

          <StageSelective diffs={this.state.diffs} repo={this.props.repo} />

    )
  }
}

export default StagingArea;
<commit_msg>View: Added snackbar component to show what files were changed<commit_after>import React from 'react';

import TextField from 'material-ui/TextField';
import RaisedButton from 'material-ui/RaisedButton';
import Checkbox from 'material-ui/Checkbox';
import FlatButton from 'material-ui/FlatButton';
import Snackbar from 'material-ui/Snackbar';

import StageSelective from './utils/StageSelective';
import CommitMessage from './utils/CommitMessage';

import * as fs from 'fs';
// to watch over files and reload diffPanel when any changes occur
import watch from 'watch';
import Git, { Diff } from 'nodegit';

const constStyles = {
  fontFamily: `apple-system,
          BlinkMacSystemFont,"Segoe UI",
          Roboto,Helvetica,Arial,sans-serif,
          "Apple Color Emoji","Segoe UI Emoji",
          "Segoe UI Symbol"`,
  darkRed: '#b60a0a',
  grey: '#ededed',
  blue: '#9CE8FA',
  darkBlue: '#0C6990'
};

class gitFunctions {

  static createCommit(repoPath, oid, commitMsg) {
    let index;
    let repo;

    // repoName = "../"+repoName;

    let pathToRepo = require('path').resolve(repoPath);

    return Git.Repository.open(pathToRepo)
      .then(function(repoResult) {
        repo = repoResult;
        return Git.Reference.nameToId(repo, "HEAD");
      })
      .then(function(head) {
        return repo.getCommit(head);
      })
      .then(function(parent) {
        const currTimeInSecs = (new Date()).getTime()/1000;
        let author = Git.Signature.create("Author name",
          "emailid@domain.com", currTimeInSecs , 0);
        let committer = Git.Signature.create("Author name",
          "emailid@domain.com", currTimeInSecs , 0);

        return repo.createCommit("HEAD", author, committer, commitMsg, oid, [parent]);
      });
  }

  static getStatus(repoPath) {

    let pathToRepo = require('path').resolve(repoPath);

    return Git.Repository.open(pathToRepo);
  }

  static getIndex(repoPath) {

    let repo;
    let pathToRepo = require('path').resolve(repoPath);

    return Git.Repository.open(pathToRepo)
    .then( (repoResult) => {
      repo = repoResult;
    })
    .then(function() {
      return repo.refreshIndex();
    });

  }

  static getUnstagedChanges(repoPath) {

    const pathToRepo = require('path').resolve(repoPath);

    return Git.Repository.open(pathToRepo)
    .then( (repo) => {
      return Diff.indexToWorkdir(repo, null, {
      flags: Diff.OPTION.SHOW_UNTRACKED_CONTENT |
             Diff.OPTION.RECURSE_UNTRACKED_DIRS
      });
    });
  }
}

class CheckBoxWrapper extends React.Component {
    constructor(props) {
    super(props);
    this.state={
      checked: this.props.checked
    };
  }

  onEventCheck = (event, isInputChecked) => {
    event.stopPropagation();

    this.changeCheckedState(isInputChecked);
  }

  changeCheckedState = (isInputChecked) => {

    window.setTimeout( () => {
      this.setState({
        checked: isInputChecked? true : false
      });
    }, 0);

    // this.props.onChangeSelected(this.props.uniqueId, isInputChecked );
  }

  componentWillReceiveProps = (newprops) => {
  }

  render = () => {
    const styles={
      checkbox: {
        marginBottom: 6,
      },
      label: {
        color: constStyles.darkBlue,
        fontFamily: constStyles.fontFamily,
        fontSize: 15,
        width: '100%',
      }
    };

    return(

        <Checkbox style={styles.checkbox} label={this.props.label} labelStyle={styles.label}
         onCheck={this.onEventCheck} checked={this.state.checked} />

    )
  }
}

class ChangesList extends React.Component {
  constructor(props) {
    super(props);
    //contains refs to all the CheckBoxWrapper components
    this.checkboxRefs= [];
  }

  toggleWrapperState = () => {
    this.checkboxRefs.forEach( (ref, index) => {
      if(ref !== null)
        ref.changeCheckedState(this.props.selectAll);
    })
  }

  render = () => {
    let renderStatus;
    if(this.props.statuses.length === 0) {
      renderStatus =
No Changes!

    }
    else {
      renderStatus = this.props.statuses.map((status, index) => {
                      return <CheckBoxWrapper key={index} uniqueId={index} label={status.label}
                      ref={(ref) => this.checkboxRefs[index] = ref}
                      onChangeSelected={this.props.onChangeSelected} checked={this.props.selectAll} />;
        });
    }

    const styles = {
      main: {
        width: '85%',
        paddingLeft: '5%',
        paddingRight: '10%'
      }
    };
    return(

        {renderStatus}

    )
  }
}

class StatusTable extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      statuses: [],
      selectAll: true
    };
  }

  getStatus = (repo) => {
    let promise = gitFunctions.getStatus(repo);
    promise.then(function(repo) {
      repo.getStatus().then(function(status) {
        return status;
      })
      .done(function(statuses) {
        this.setState({
          statuses:statuses
        });
      }.bind(this));
    }.bind(this));
  }

  checkSelectAll = (event, isInputChecked) => {

    event.stopPropagation();

    window.setTimeout( () => {
      this.setState({
        selectAll: isInputChecked? true : false
      });

      //reaches into the changesList component, which
      //in turn reaches into individual CheckBoxWrapper components
      //and sets state accordingly
      this.changesList.toggleWrapperState(this.state.selectAll);
    }, 0);

  }

  componentWillReceiveProps = (newprops) => {
      this.getStatus(newprops.repo.path);
  }

  render = () => {
    let localStatus = [];
    if(this.state.statuses.length === 0)
    {
      localStatus = [];
    }
    else {

      const statusToText=function(status) {
        let words=[];
        let flag = 0;
        let key = 0;

        if (status.isNew()) { words.push(N); flag = 1;}
        if (status.isModified()) { words.push(M); flag = 1;}
        if (status.isTypechange()) { words.push(TYPECHANGED); flag = 1;}
        if (status.isRenamed()) { words.push(RENAMED); flag = 1;}
        if (status.isIgnored()) { words.push(IGNORED); flag = 1; }
        if(flag == 0) { words.push(D); }

        return words;
      };

      localStatus = this.state.statuses.map((file, index) => {

        let change=statusToText(file);

        const styles = {
          main: {
            display: 'flex',
            justifyContent: 'space-between',
          },
          path: {
            whiteSpace: 'nowrap',
            overflow: 'hidden',
            textOverflow: 'ellipsis',
            width: '90%',
          }
        };

        let changes = (

{file.path()}

{change}

        );

        return ({
          label:changes
        });

      });
    }

    const styles = {
      main:{
        width: '100%',
        border: '2px solid'+constStyles.blue,
        display: 'flex',
        flexDirection: 'column',
        justifyContent: 'center',
        alignItems: 'stretch',
        borderLeft: '0px',
        fontFamily: constStyles.fontFamily,
        color: constStyles.darkBlue,
      },
      status: {
        height: 50,
        backgroundColor: constStyles.blue,
        color:  constStyles.darkBlue,
        display: 'flex',
        paddingLeft: '7%',
        alignItems: 'center',
        fontWeight: 700,
      },
      labelStyle: {
        fontFamily: constStyles.fontFamily,
        color:constStyles.darkBlue,
      },
      checkBox: {
        border: '2px solid'+constStyles.blue,
        margin: 5,
        marginTop: 13,
        marginBottom: 13,
        width: '95%',
      },
      checkBoxes: {
        display: 'flex',
        flexDirection: 'column',
        justifyContent: 'flexStart',
        alignItems: 'center',
      }
    };
    return(

STATUS

          <Checkbox label='Select All:'
          labelStyle={styles.labelStyle}
          ref={(ref) => this.selectAll = ref} style={styles.checkBox}
          onCheck={this.checkSelectAll} checked={this.state.selectAll} labelPosition='left' />
          <ChangesList ref={(ref) => this.changesList = ref} selectAll={this.state.selectAll}
          statuses={localStatus} />

    )
  }
}

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

  render = () => {
    const styles = {
      main: {
        fontFamily: constStyles.fontFamily,
        color: constStyles.darkBlue,
        padding: '4%',
        paddingTop: '3%',
        paddingRight: '10%',
        borderTop: '2px solid'+constStyles.blue,
      },
      file: {
        padding: 4,
        fontSize: 14,
      },
      heading: {
        fontFamily: `'Oswald', sans-serif`,
        letterSpacing: 0,
        fontSize: 16,
        paddingBottom: 5,
      }
    }
    return (

      {this.props.indexEntries.length?

Staged Files:

          {this.props.indexEntries.map( (entry, index) => {
            return
{entry}
;
          })}

:
      null}

    );
  }
}

class StagingArea extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      indexPaths: [],
      diffs: [],
      snackbarOpen: false,
      snackbarMessage: ''
    };
  }

  addToIndex = () => {

    let checkedChanges = [];
    //reaches into each checkboxWrapper to obtain checked value
    checkedChanges = this.statusTable.changesList.checkboxRefs.map( (ref, index) => {
      if(ref !== null)
        return ref.state.checked;
    });

    let indexPaths =[];

    this.statusTable.changesList.props.statuses.forEach( (status, index) => {

      //if corresponding checkbox has been checked only
      if(checkedChanges[index])
        indexPaths.push(status);
    });

    window.setTimeout( () => {

      this.setState({
        indexPaths: indexPaths
      });
    }, 0);
  }

  createCommit = (commitMsg) => {

    let index;
    let oid;

    let promise = gitFunctions.getIndex(this.props.repo.path);
    promise = promise.then((indexResult) => {
      index = indexResult;
    });

    let filePaths = this.state.indexPaths.map( (status) => {
      return status.value;
    });

    //takes in initial promise, uses it to chain a new promise to it, and returns the promise,
    //which is used in the next iteration
    promise = filePaths.reduce( (prevPromise, path, currentIndex) => {
                return prevPromise.then( () => {
                  return index.addByPath(path);
                });
              }, promise);

    promise.then(() => {
      return index.write();
    })
    .then(() => {
      return index.writeTree();
    })
    .then( (oidResult) => {
      oid = oidResult;
      console.log('index tree written!');
    })
    //until here, all entries were added to the index and an index tree was written
    //from here, the new commit is generated using the index tree
    .then( () => {
      let innerPromise = gitFunctions.createCommit(this.props.repo.path, oid, commitMsg);
      innerPromise.done(function(commitId) {
        console.log('Commit Hash:'+commitId);
      });

    });
  }

  setupWatchActions = () => {

    const path = this.props.repo.path;

    fs.readFile(require('path').resolve(path, '.gitignore'), (err, data) => {
      if (err) {
        console.log('no .gitignore file found!');
        // no .gitignore file found, assume that all files
        // are being tracked by git
        return;
      }
      const lines = data.toString().split('\n').map((line) => {
        return line.trim();
      });
      let regexes = [];
      lines.forEach((line, index) => {

        // ignore comments
        if(!line.startsWith('#')) {
          // file extensions
          if(line.startsWith('*'))
            line = line.slice(1);
          // for directories
          else if(line.length !== 0)
            line = this.props.repo.name+'/'+line+'$';

          if(line.endsWith('*'))
            line = line.slice(0, line.length-1);
          if(line.startsWith('/'))
            line = line.slice(1);

          if(line.length !== 0)
            regexes.push(new RegExp(line));
        }
      });
      // to ignore the .git directory as well
      regexes.push(new RegExp('.git'+'$'));

      const filterIgnored = (f, stat) => {
        // tracks file/directory if true is returned
        let ignore = true;
        regexes.forEach((regex) => {
          if(regex.test(f)) {
            ignore = false;
          }
        });
        return ignore;
      };

      const configWatch = {
        ignoreDotFiles: false,
        ignoreUnreadAbleDir: true,
        ignoreNotPermitted: true,
        filter: filterIgnored
      };

      watch.createMonitor(this.props.repo.path, configWatch, (monitor) => {
        console.log('Files/directories being monitored: ', monitor.files);
        monitor.on("created", (f, stat) => {
          console.log('created',f, stat);
        })
        monitor.on("changed", (f, curr, prev) => {
          console.log('changed',f, curr, prev);
        })
        monitor.on("removed", (f, stat) => {
          console.log('removed',f, stat);
        })
      });
    });
  }

  handleSnackbarClose = () => {
    this.setState({
      snackbarOpen: false,
    });
  }

  componentDidMount = () => {
    let diffArr = [];
    if(this.props.repo) {
      gitFunctions.getUnstagedChanges(this.props.repo.path)
      .done( (diff) => {
        this.setState({
          diffs: [ diff ]
        });
      });

      this.setupWatchActions();
    }

  }

  componentWillReceiveProps = (newprops) => {
    let diffArr = [];
    if(newprops.repo)
      gitFunctions.getUnstagedChanges(newprops.repo.path)
      .done( (diff) => {
        this.setState({
          diffs: [ diff ]
        });
      });
  }

  render = () => {
    const styles = {
      main: {
        overflow: 'auto',
        display: 'flex',
      },
      createCommit: {
        overflow: 'auto',
        width: '30%',
        display: 'flex',
        flexDirection: 'column',
        color: constStyles.blue,
        border: '2px solid'+constStyles.blue,
      },
      selectiveDiffPanel: {
        width: '69%',
        height: 500,
        overflow: 'auto',
      },
      commitmsg: {
        borderTop: '2px solid'+constStyles.blue,
        borderBottom: '2px solid'+constStyles.blue,
      }
    };
    return (

            <StatusTable {...this.props} ref={(ref) => this.statusTable = ref} />
            <RaisedButton
            style={{width: '100%'}}
            label='Add to Index!'
            labelColor={constStyles.darkBlue} onMouseDown={this.addToIndex} />
            <IndexTable indexEntries={this.state.indexPaths.map( (status) => {return status.label})} />

            <CommitMessage buttonMsg='Create Commit!' commitCB={this.createCommit} />

          <StageSelective diffs={this.state.diffs} repo={this.props.repo} />

        <Snackbar
          style={{fontFamily: constStyles.fontFamily}}
          open={this.state.snackbarOpen}
          autoHideDuration={4000}
          message={this.state.snackbarMessage}
          onRequestClose={this.handleSnackbarClose}
        />

    )
  }
}

export default StagingArea;
<|endoftext|>


<commit_before>(function() {

  'use strict';

  angular.module('ac-dropdown-datepicker', [])

    .directive('acDropdownDatepicker', ['$filter', '$document', function ($filter, $document) {

      return {
        restrict: 'AEC',
        scope: {
          ngModel: '=',
          minDate: '=',
          showWeeks: '=',
          datepickerOptions: '=',
          extraSettings: '=',
          events: '=',
          translationTexts: '='
        },

        template: function (element, attributes) {

          var template =  '
';
              template += '<button type="button" class="dropdown-toggle" ng-class="settings.buttonClasses" ng-click="toggleDropdown()">{{getButtonText()}} </button>';
              template += '
';
              template += '
<datepicker ng-model="ngModel" min-date="minDate" show-weeks="showWeeks" datepicker-options="datepickerOptions" ng-keydown="keydown($event)" class="well well-sm"></datepicker>
'
              template += '
';
              template += '
';

          return template;
        },

        link: function (scope, element, attributes) {

          var dropdownTrigger = element.children()[0];

          scope.toggleDropdown = function () {
              scope.open = !scope.open;
          };

          scope.externalEvents = {
              onItemSelect: angular.noop,
              onItemDeselect: angular.noop,
              onSelectAll: angular.noop,
              onDeselectAll: angular.noop,
              onInitDone: angular.noop,
              onMaxSelectionReached: angular.noop,
              onNewItemAdd: angular.noop,
              onItemEdit: angular.noop,
              onItemRemove: angular.noop
          };

          scope.settings = {
              scrollable: false,
              scrollableHeight: '300px',
              closeOnBlur: true,
              alwaysOpened: false,
              closeOnSelect: false,
              buttonClasses: 'btn btn-default',
              closeOnDeselect: false
          };

          scope.texts = {
              buttonDefaultText: 'Select'
          };

          angular.extend(scope.settings, scope.extraSettings || []);
          angular.extend(scope.externalEvents, scope.events || []);
          angular.extend(scope.texts, scope.translationTexts);

          if (scope.settings.closeOnBlur) {
              $document.on('click', function (e) {
                  var target = e.target.parentElement;
                  var parentFound = $(target).closest('.multiselect-parent').length > 0;

                  if (!parentFound) {
                      scope.$apply(function () {
                          scope.open = false;
                      });
                  }
              });
          }

          scope.getButtonText = function () {
              return scope.texts.buttonDefaultText;
          };

          scope.externalEvents.onInitDone();
        }
      };
  }]);
})();<commit_msg>add caret_down icon to dropdown<commit_after>(function() {

  'use strict';

  angular.module('ac-dropdown-datepicker', [])

    .directive('acDropdownDatepicker', ['$filter', '$document', function ($filter, $document) {

      return {
        restrict: 'AEC',
        scope: {
          ngModel: '=',
          minDate: '=',
          showWeeks: '=',
          datepickerOptions: '=',
          extraSettings: '=',
          events: '=',
          translationTexts: '='
        },

        template: function (element, attributes) {

          var template =  '
';
              template += '<button type="button" class="dropdown-toggle" ng-class="settings.buttonClasses" ng-click="toggleDropdown()">{{getButtonText()}} </button>';
              template += '
';
              template += '
<datepicker ng-model="ngModel" min-date="minDate" show-weeks="showWeeks" datepicker-options="datepickerOptions" ng-keydown="keydown($event)" class="well well-sm"></datepicker>
'
              template += '
';
              template += '
';

          return template;
        },

        link: function (scope, element, attributes) {

          var dropdownTrigger = element.children()[0];

          scope.toggleDropdown = function () {
              scope.open = !scope.open;
          };

          scope.externalEvents = {
              onItemSelect: angular.noop,
              onItemDeselect: angular.noop,
              onSelectAll: angular.noop,
              onDeselectAll: angular.noop,
              onInitDone: angular.noop,
              onMaxSelectionReached: angular.noop,
              onNewItemAdd: angular.noop,
              onItemEdit: angular.noop,
              onItemRemove: angular.noop
          };

          scope.settings = {
              scrollable: false,
              scrollableHeight: '300px',
              closeOnBlur: true,
              alwaysOpened: false,
              closeOnSelect: false,
              buttonClasses: 'btn btn-default',
              closeOnDeselect: false
          };

          scope.texts = {
              buttonDefaultText: 'Select'
          };

          angular.extend(scope.settings, scope.extraSettings || []);
          angular.extend(scope.externalEvents, scope.events || []);
          angular.extend(scope.texts, scope.translationTexts);

          if (scope.settings.closeOnBlur) {
              $document.on('click', function (e) {
                  var target = e.target.parentElement;
                  var parentFound = $(target).closest('.multiselect-parent').length > 0;

                  if (!parentFound) {
                      scope.$apply(function () {
                          scope.open = false;
                      });
                  }
              });
          }

          scope.getButtonText = function () {
              return scope.texts.buttonDefaultText;
          };

          scope.externalEvents.onInitDone();
        }
      };
  }]);
})();<|endoftext|>


<commit_before>// Client side resources to be injected for gData extension

// Add items to the exporter menu
MenuBar.insertBefore(
        [ "core/project", "core/export", "core/export-templating" ],
        [
            {
                "label":"Google Spreadsheet",
                "click": function() {}
            },
            {
                "label":"Google Data",
                "click": function() {}
            },
            {} // separator
        ]
);

var gdataExtension = {};
<commit_msg>Comment out unused exporter injection.  Target has moved anyway<commit_after>// Client side resources to be injected for gData extension

// Add items to the exporter menu
//ExportManager.MenuItems.append(
//        [ "core/project", "core/export", "core/export-templating" ],
//        [
//            {
//                "label":"Google Spreadsheet",
//                "click": function() {}
//            },
//            {
//                "label":"Google Data",
//                "click": function() {}
//            },
//            {} // separator
//        ]
//);

var gdataExtension = {};
<|endoftext|>


<commit_before>import * as type from '../constants';
import database from '../database';
import * as util from '../utils';
import { GOOGLEFONTS_API_KEY } from '../config';
import Axios from 'axios';

const ActionCreators = {
	updateOption(name, value, data) {
		return {
			type: type.UPDATE_OPTION,
			name: name,
			value: value,
			data: data
		}
	},
	updateContent(id, value, data) {
		return {
			type: type.UPDATE_CONTENT,
			id: id,
			value: value,
			data: data
		}
	},
	updateToken(name, value, data) {
		return {
			type: type.UPDATE_TOKEN,
			name: name,
			value: value,
			data: data
		}
	},
	updateFontToken(name, token, file) {
		return dispatch => {
			dispatch(ActionCreators.loadFont(name, token, file));
			dispatch(ActionCreators.updateToken(name, token.value, token));
		}
	},
	setTokens(value) {
		return {
			type: type.SET_TOKENS,
			tokens: value
		}
	},
	setItems(value) {
		return {
			type: type.SET_ITEMS,
			items: value
		}
	},
	setContents(value) {
		return {
			type: type.SET_CONTENTS,
			contents: value
		}
	},
	setHotkey(shortcutName, hot) {
		return {
			type: type.SET_HOTKEY,
			shortcutName: shortcutName,
			hot: hot,
		}
	},
	setModules(value) {
		return {
			type: type.SET_MODULES,
			modules: value
		}
	},
	addItemToModule(itemId, moduleId) {
		return {
			type: type.ADD_ITEM_TO_MODULE,
			itemId: itemId,
			moduleId: moduleId
		}
	},
	createModule(type) {
		return (dispatch, getState) => {
			const currentState = getState();
			const modules = currentState.modules;
			const modulesSchema = currentState.modulesSchema;
			const schema = modulesSchema[type];
			// Generate ID
			const typeCounter = util.countItemsBy(type, modules) + 1;
			const id = type + '-' + typeCounter;
			// Generate Item only if NOT repeatable
			var itemsId = [];
			if (schema.items.repeatable === false) {
				// const itemsOptions = {
				// 	options: schema.items.options,
				// 	defaults: schema.items.optionsDefaults
				// };
				schema.items.acceptedTypes.map(itemType => {
					// TODO: return item id or compute it here also
					itemsId.push(
						dispatch(ActionCreators.createItem(itemType, null, null))
					);
				});
			}
			// Generate options
			var optionsObject = {};
			schema.options.map((optionName, index) => {
				optionsObject[optionName] = schema.optionsDefaults[index];
			});
			const newModule = {
				id: id,
				type: type,
				title: id,
				items: itemsId,
				options: optionsObject
			}
			dispatch(ActionCreators.addModule(id, newModule));
		}
	},
	createItem(type, moduleId = null, extraOptions = null) {
		return (dispatch, getState) => {
			const currentState = getState();
			const contents = currentState.contents;
			const items = currentState.items;
			const itemsSchema = currentState.itemsSchema;
			const schema = itemsSchema[type];
			// Generate ID
			const typeCounter = util.countItemsBy(type, items) + 1;
			const id = type + '-' + typeCounter;
			// Generate Options
			var optionsObject = {};
			if (schema.options) {
				schema.options.map((optionName, index) => {
					// TODO: support defaults
					optionsObject[optionName] = schema.optionsDefaults[index];
				});
			}
			// Extra options
			if (extraOptions) {
				extraOptions.options.map((optionName, index) => {
					// TODO: support defaults
					optionsObject[optionName] = extraOptions.defaults[index];
				});
			}
			// Generate content...
			var contentsId = [];
			for (var contentType in schema.content) {

				const contentSchema = schema.content[contentType];
				// ID
				const contentTypeCounter = util.countItemsBy(contentType, contents) + 1;
				const contentId = contentType + '-' + contentTypeCounter;
				contentsId.push(contentId);
				// Input
				const contentInput = contentSchema.input;
				// Options
				var contentOptionsObject = {};
				if (contentSchema.options) {
					contentSchema.options.map((optionName, index) => {
						// TODO: support defaults
						contentOptionsObject[optionName] = contentSchema.optionsDefaults[index];
					});
				}

				const newContent = {
					id: contentId,
					type: contentType,
					input: contentInput,
					value: "",
					options: contentOptionsObject
				}
				dispatch(ActionCreators.addContent(contentId, newContent));
			}
			const newItem = {
				id: id,
				type: type,
				title: id,
				content: contentsId,
				options: optionsObject
			}
			if (moduleId) {
				dispatch(ActionCreators.addItemToModule(id, moduleId));
			}
			dispatch(ActionCreators.addItem(id, newItem));
			return id;
		}
	},
	addModule(id, object) {
		return {
			type: type.ADD_MODULE,
			id: id,
			object: object
		}
	},
	addItem(id, object) {
		return {
			type: type.ADD_ITEM,
			id: id,
			object: object
		}
	},
	addContent(id, object) {
		return {
			type: type.ADD_CONTENT,
			id: id,
			object: object
		}
	},
	setModulesOrder(newOrder) {
		return {
			type: type.SET_MODULES_ORDER,
			modulesOrder: newOrder
		}
	},
	setItemsOrder(moduleId, newOrder) {
		return {
			type: type.SET_ITEMS_ORDER,
			moduleId: moduleId,
			itemsOrder: newOrder
		}
	},
	setProject({tokens, modulesOrder, modules, items, contents}) {
		return {
			type: type.SET_PROJECT,
			tokens: tokens,
			modulesOrder: modulesOrder,
			modules: modules,
			items: items,
			contents: contents
		}
	},
	updateFontList(fonts) {
		return {
			type: type.UPDATE_FONT_LIST,
			fonts: fonts
		}
	},
	getFontList() {
		return dispatch => {
			var fonts = [];
			const endpoint = 'https://www.googleapis.com/webfonts/v1/webfonts?key=';
			const url = endpoint + GOOGLEFONTS_API_KEY;

			var apiRequest = Axios.get(url)
			.then(response => {
				response.data.items.map(item => {
					fonts.push({
						'name': item.family,
						'value': item.family
					});
				});
				dispatch(ActionCreators.updateFontList(fonts));
			})
			.catch(error => {
				// TODO Handle error
			});
		}
	},
	changeTab(tabName) {
		return (dispatch, getState) => {
			const currentState = getState();
			const guidesSetByUser = currentState.ui.guidesSetByUser;
			const currentTab = currentState.activeTab;
			if (currentTab != tabName) {
				if (!guidesSetByUser) {
					if (tabName == 'variables' || tabName == 'modules') {
						dispatch(ActionCreators.toggleGuides(false));
					}
				}
				dispatch(ActionCreators.setActiveTab(tabName));
			}
			dispatch(ActionCreators.setActiveTabItem(''));
		}
	},
	setActiveTab(tabName) {
		return {
			type: type.SET_ACTIVE_TAB,
			tabName: tabName
		}
	},
	changeTabItem(itemName) {
		return (dispatch, getState) => {
			// Scroll to anchor?
			dispatch(ActionCreators.setActiveTabItem(itemName));
		}
	},
	setActiveTabItem(itemName) {
		return {
			type: type.SET_ACTIVE_TAB_ITEM,
			itemName: itemName
		}
	},
	loadFont(name, token, file) {
		return () => {
			switch(token.source) {
				case 'google-font':
					util.loadGoogleFont(name, token);
					break;
				case 'upload':
					util.loadFileFont(file, token);
					break;
			}
		}
	},
	loadProject() {
		return dispatch => {
			const projectID = util.getParam('project');
			const projectRef = database.ref('/projects/' + projectID);
			const projectTokensRef = database.ref('/projects/' + projectID + '/tokens/');
			const projectModulesRef = database.ref('/projects/' + projectID + '/modules/');
			const projectModulesOrderRef = database.ref('/projects/' + projectID + '/modulesOrder/');
			const projectItemsRef = database.ref('/projects/' + projectID + '/items/');
			const projectContentsRef = database.ref('/projects/' + projectID + '/contents/');
			var tokens = {};
			var modulesOrder = [];
			var modules = {};
			var items = {};
			var contents = {};
			var responseCounter = 0;

			projectRef.once('value', snapshot => {
				if (snapshot.val()) {
					for (var tokenKey in tokens) {
						const token = tokens[tokenKey];
						if (token.type == 'font') {
							dispatch(ActionCreators.loadFont(tokens[tokenKey], tokenKey, null));
						}
					}
					dispatch(ActionCreators.setProject(snapshot.val()));
				}
			});
		}
	},
	saveProject() {
		return (dispatch, getState) => {
			const currentState = getState();
			const projectsRef = database.ref('/projects');
			const projectID = util.getParam('project');
			const projectState = {
				tokens: currentState.tokens,
				modulesOrder: currentState.modulesOrder,
				modules: currentState.modules,
				items: currentState.items,
				contents: currentState.contents
			}

			dispatch(ActionCreators.saveProjectStart());

			database.ref('/projects/' + projectID).once('value', data => {
				const value = data.val();

				if (value) {
					projectsRef.child(projectID).set(projectState, error => {
						if (error) {
							dispatch(ActionCreators.saveProjectError());
						}
						else {
							dispatch(ActionCreators.saveProjectSuccess());
						}
					});
				}
				else {
					const newProjectRef = projectsRef.push(projectState, error => {
						if (error) {
							dispatch(ActionCreators.saveProjectError());
						}
						else {
							const newProjectID = newProjectRef.key;
							const queryString = util.setParam('project', newProjectRef.key);
							window.history.replaceState({}, '', location.pathname + '?' + queryString);
							dispatch(ActionCreators.saveProjectSuccess(newProjectID));
						}
					});

				}
			});
		}
	},
	saveProjectStart() {
		return {
			type: type.SAVE_PROJECT_START,
			buttonSaveState: 'start'
		}
	},
	saveProjectError() {
		return {
			type: type.SAVE_PROJECT_ERROR,
			buttonSaveState: 'error'
		}
	},
	saveProjectSuccess(id) {
		return {
			type: type.SAVE_PROJECT_SUCCESS,
			buttonSaveState: 'success',
			project: {
				status: 'saved',
				id: id
			}
		}
	},
	toggleGuides(userAction) {
		return {
			type: type.TOGGLE_GUIDES,
			userAction: userAction
		}
	}
};

export default ActionCreators;<commit_msg>Add condition to project loading<commit_after>import * as type from '../constants';
import database from '../database';
import * as util from '../utils';
import { GOOGLEFONTS_API_KEY } from '../config';
import Axios from 'axios';

const ActionCreators = {
	updateOption(name, value, data) {
		return {
			type: type.UPDATE_OPTION,
			name: name,
			value: value,
			data: data
		}
	},
	updateContent(id, value, data) {
		return {
			type: type.UPDATE_CONTENT,
			id: id,
			value: value,
			data: data
		}
	},
	updateToken(name, value, data) {
		return {
			type: type.UPDATE_TOKEN,
			name: name,
			value: value,
			data: data
		}
	},
	updateFontToken(name, token, file) {
		return dispatch => {
			dispatch(ActionCreators.loadFont(name, token, file));
			dispatch(ActionCreators.updateToken(name, token.value, token));
		}
	},
	setTokens(value) {
		return {
			type: type.SET_TOKENS,
			tokens: value
		}
	},
	setItems(value) {
		return {
			type: type.SET_ITEMS,
			items: value
		}
	},
	setContents(value) {
		return {
			type: type.SET_CONTENTS,
			contents: value
		}
	},
	setHotkey(shortcutName, hot) {
		return {
			type: type.SET_HOTKEY,
			shortcutName: shortcutName,
			hot: hot,
		}
	},
	setModules(value) {
		return {
			type: type.SET_MODULES,
			modules: value
		}
	},
	addItemToModule(itemId, moduleId) {
		return {
			type: type.ADD_ITEM_TO_MODULE,
			itemId: itemId,
			moduleId: moduleId
		}
	},
	createModule(type) {
		return (dispatch, getState) => {
			const currentState = getState();
			const modules = currentState.modules;
			const modulesSchema = currentState.modulesSchema;
			const schema = modulesSchema[type];
			// Generate ID
			const typeCounter = util.countItemsBy(type, modules) + 1;
			const id = type + '-' + typeCounter;
			// Generate Item only if NOT repeatable
			var itemsId = [];
			if (schema.items.repeatable === false) {
				// const itemsOptions = {
				// 	options: schema.items.options,
				// 	defaults: schema.items.optionsDefaults
				// };
				schema.items.acceptedTypes.map(itemType => {
					// TODO: return item id or compute it here also
					itemsId.push(
						dispatch(ActionCreators.createItem(itemType, null, null))
					);
				});
			}
			// Generate options
			var optionsObject = {};
			schema.options.map((optionName, index) => {
				optionsObject[optionName] = schema.optionsDefaults[index];
			});
			const newModule = {
				id: id,
				type: type,
				title: id,
				items: itemsId,
				options: optionsObject
			}
			dispatch(ActionCreators.addModule(id, newModule));
		}
	},
	createItem(type, moduleId = null, extraOptions = null) {
		return (dispatch, getState) => {
			const currentState = getState();
			const contents = currentState.contents;
			const items = currentState.items;
			const itemsSchema = currentState.itemsSchema;
			const schema = itemsSchema[type];
			// Generate ID
			const typeCounter = util.countItemsBy(type, items) + 1;
			const id = type + '-' + typeCounter;
			// Generate Options
			var optionsObject = {};
			if (schema.options) {
				schema.options.map((optionName, index) => {
					// TODO: support defaults
					optionsObject[optionName] = schema.optionsDefaults[index];
				});
			}
			// Extra options
			if (extraOptions) {
				extraOptions.options.map((optionName, index) => {
					// TODO: support defaults
					optionsObject[optionName] = extraOptions.defaults[index];
				});
			}
			// Generate content...
			var contentsId = [];
			for (var contentType in schema.content) {

				const contentSchema = schema.content[contentType];
				// ID
				const contentTypeCounter = util.countItemsBy(contentType, contents) + 1;
				const contentId = contentType + '-' + contentTypeCounter;
				contentsId.push(contentId);
				// Input
				const contentInput = contentSchema.input;
				// Options
				var contentOptionsObject = {};
				if (contentSchema.options) {
					contentSchema.options.map((optionName, index) => {
						// TODO: support defaults
						contentOptionsObject[optionName] = contentSchema.optionsDefaults[index];
					});
				}

				const newContent = {
					id: contentId,
					type: contentType,
					input: contentInput,
					value: "",
					options: contentOptionsObject
				}
				dispatch(ActionCreators.addContent(contentId, newContent));
			}
			const newItem = {
				id: id,
				type: type,
				title: id,
				content: contentsId,
				options: optionsObject
			}
			if (moduleId) {
				dispatch(ActionCreators.addItemToModule(id, moduleId));
			}
			dispatch(ActionCreators.addItem(id, newItem));
			return id;
		}
	},
	addModule(id, object) {
		return {
			type: type.ADD_MODULE,
			id: id,
			object: object
		}
	},
	addItem(id, object) {
		return {
			type: type.ADD_ITEM,
			id: id,
			object: object
		}
	},
	addContent(id, object) {
		return {
			type: type.ADD_CONTENT,
			id: id,
			object: object
		}
	},
	setModulesOrder(newOrder) {
		return {
			type: type.SET_MODULES_ORDER,
			modulesOrder: newOrder
		}
	},
	setItemsOrder(moduleId, newOrder) {
		return {
			type: type.SET_ITEMS_ORDER,
			moduleId: moduleId,
			itemsOrder: newOrder
		}
	},
	setProject({tokens, modulesOrder, modules, items, contents}) {
		return {
			type: type.SET_PROJECT,
			tokens: tokens,
			modulesOrder: modulesOrder,
			modules: modules,
			items: items,
			contents: contents
		}
	},
	updateFontList(fonts) {
		return {
			type: type.UPDATE_FONT_LIST,
			fonts: fonts
		}
	},
	getFontList() {
		return dispatch => {
			var fonts = [];
			const endpoint = 'https://www.googleapis.com/webfonts/v1/webfonts?key=';
			const url = endpoint + GOOGLEFONTS_API_KEY;

			var apiRequest = Axios.get(url)
			.then(response => {
				response.data.items.map(item => {
					fonts.push({
						'name': item.family,
						'value': item.family
					});
				});
				dispatch(ActionCreators.updateFontList(fonts));
			})
			.catch(error => {
				// TODO Handle error
			});
		}
	},
	changeTab(tabName) {
		return (dispatch, getState) => {
			const currentState = getState();
			const guidesSetByUser = currentState.ui.guidesSetByUser;
			const currentTab = currentState.activeTab;
			if (currentTab != tabName) {
				if (!guidesSetByUser) {
					if (tabName == 'variables' || tabName == 'modules') {
						dispatch(ActionCreators.toggleGuides(false));
					}
				}
				dispatch(ActionCreators.setActiveTab(tabName));
			}
			dispatch(ActionCreators.setActiveTabItem(''));
		}
	},
	setActiveTab(tabName) {
		return {
			type: type.SET_ACTIVE_TAB,
			tabName: tabName
		}
	},
	changeTabItem(itemName) {
		return (dispatch, getState) => {
			// Scroll to anchor?
			dispatch(ActionCreators.setActiveTabItem(itemName));
		}
	},
	setActiveTabItem(itemName) {
		return {
			type: type.SET_ACTIVE_TAB_ITEM,
			itemName: itemName
		}
	},
	loadFont(name, token, file) {
		return () => {
			switch(token.source) {
				case 'google-font':
					util.loadGoogleFont(name, token);
					break;
				case 'upload':
					util.loadFileFont(file, token);
					break;
			}
		}
	},
	loadProject() {
		return dispatch => {
			const projectID = util.getParam('project');
			if (projectID) {
				const projectRef = database.ref('/projects/' + projectID);
				const projectTokensRef = database.ref('/projects/' + projectID + '/tokens/');
				const projectModulesRef = database.ref('/projects/' + projectID + '/modules/');
				const projectModulesOrderRef = database.ref('/projects/' + projectID + '/modulesOrder/');
				const projectItemsRef = database.ref('/projects/' + projectID + '/items/');
				const projectContentsRef = database.ref('/projects/' + projectID + '/contents/');
				var tokens = {};
				var modulesOrder = [];
				var modules = {};
				var items = {};
				var contents = {};
				var responseCounter = 0;

				projectRef.once('value', snapshot => {
					if (snapshot.val()) {
						for (var tokenKey in tokens) {
							const token = tokens[tokenKey];
							if (token.type == 'font') {
								dispatch(ActionCreators.loadFont(tokens[tokenKey], tokenKey, null));
							}
						}
						dispatch(ActionCreators.setProject(snapshot.val()));
					}
				});
			}
			else {
				return null;
			}

		}
	},
	saveProject() {
		return (dispatch, getState) => {
			const currentState = getState();
			const projectsRef = database.ref('/projects');
			const projectID = util.getParam('project');
			const projectState = {
				tokens: currentState.tokens,
				modulesOrder: currentState.modulesOrder,
				modules: currentState.modules,
				items: currentState.items,
				contents: currentState.contents
			}

			dispatch(ActionCreators.saveProjectStart());

			database.ref('/projects/' + projectID).once('value', data => {
				const value = data.val();

				if (value) {
					projectsRef.child(projectID).set(projectState, error => {
						if (error) {
							dispatch(ActionCreators.saveProjectError());
						}
						else {
							dispatch(ActionCreators.saveProjectSuccess());
						}
					});
				}
				else {
					const newProjectRef = projectsRef.push(projectState, error => {
						if (error) {
							dispatch(ActionCreators.saveProjectError());
						}
						else {
							const newProjectID = newProjectRef.key;
							const queryString = util.setParam('project', newProjectRef.key);
							window.history.replaceState({}, '', location.pathname + '?' + queryString);
							dispatch(ActionCreators.saveProjectSuccess(newProjectID));
						}
					});

				}
			});
		}
	},
	saveProjectStart() {
		return {
			type: type.SAVE_PROJECT_START,
			buttonSaveState: 'start'
		}
	},
	saveProjectError() {
		return {
			type: type.SAVE_PROJECT_ERROR,
			buttonSaveState: 'error'
		}
	},
	saveProjectSuccess(id) {
		return {
			type: type.SAVE_PROJECT_SUCCESS,
			buttonSaveState: 'success',
			project: {
				status: 'saved',
				id: id
			}
		}
	},
	toggleGuides(userAction) {
		return {
			type: type.TOGGLE_GUIDES,
			userAction: userAction
		}
	}
};

export default ActionCreators;<|endoftext|>


<commit_before>/*
* ---
* GLOBALs
* ---
*/

var CES_GLOBALS = {};

/*
* CSRF Token
* ---
*
* CodePen uses a meta tag to defend against XSRF.
* This variable grabs that tag's content to allow POST requests
* which are required to change settings/save Pens/etc.
*
*/
CES_GLOBALS.CSRF_TOKEN = $("meta[name='csrf-token']").attr("content");

/*
* Init Data
* ---
*
* Grabs a copy of the init-data hidden input that's used on all CodePen pages.
*
* Awkwardly, these are actually merged into the window object by CodePen but
* this is an easier way to get access to everything.
*
* The JSON parsing gets incredibly awkward... Would prefer something easier.
*/
CES_GLOBALS.INIT_DATA = (function() {
  var data = $("#init-data").val();
  var parsedData = $.parseJSON(data);
  for (var key in parsedData) {
    var prop = parsedData[ key ];
    if (needsParsing(prop)) {
      parsedData[ key ] = $.parseJSON(prop);
    }
  }

  // Checks if a prop is valid JSON,
  // and if it's not parses it again
  function needsParsing(prop) {
    if ("string" == typeof prop) {
      var t = prop.substring(0, 1);
      if ("[" === t || "{" === t) {
        return true;
      }
    }
    return false;
  }

  // Sends INIT_DATA off to init.js since content scripts can't access the window object.
  // This allows conditional loading of modules based on INIT_DATA.
  var sendDataToContentScript = new CustomEvent("init-data-ready", {detail: parsedData});
  window.dispatchEvent(sendDataToContentScript);

  return parsedData;
})();

// Small utility to shortcut the AJAX requests needed to
// save/update stuff on CodePen.

// URL should be the relative path of the request,
// KEY should be the key of the data,
// VALUE should be the raw JSON data that you're sending.
CES_GLOBALS.CP = function(url, key, value, callback) {
  $.ajax({
    url: url,
    method: "POST",
    headers: {
      "X-CSRF-Token": CSRF_TOKEN,
    },
    data: key + "=" + encodeURIComponent(JSON.stringify(value)),
    complete: callback
  });
};

// Returns TRUE if currently using https
CES_GLOBALS.IS_HTTPS = function() {
  return window.location.protocol === "https:";
};

// Grabs the absolute path of a file via the message API.
// REQUESTEDURL is the relative path of the file,
// CALLBACK is... self explanatory. Fires once the URL is received.
CES_GLOBALS.REQUEST_EXTENSION_URL = function(requestedUrl, callback) {
  window.addEventListener("receivedUrl", function handleUrl(e) {
    window.removeEventListener("receivedUrl", handleUrl);
    callback(e.detail);
  });
  var requestExtensionUrl = new CustomEvent("requestExtensionUrl", {detail: requestedUrl});
  window.dispatchEvent(requestExtensionUrl);
};

// Appends a stylesheet to the head of the document.
// URL is the absolute path of the stylesheet.
CES_GLOBALS.APPEND_STYLESHEET = function(url) {
  var link = $("<link>");
  link.attr("type", "text/css");
  link.attr("rel", "stylesheet");
  link.attr("href", url);
  $("head").append(link);
};

// Binds to CodePen's internal pubhub dealio.
// I abstracted this into my own function incase it changes
// or this strategy stops being viable.
CES_GLOBALS.ON_PEN_SAVE = function(callback) {
  Hub.sub("pen-saved", callback);
};

// Uses CodePen's internal modal function which means
// I don't need to handle closing the modal or any of that
// nonsense which is preeeetty nice.
CES_GLOBALS.THROW_ERROR_MODAL = function(message) {
  CES_GLOBALS.REQUEST_EXTENSION_URL("modules/html/error-modal.html", function(response) {
    var errorModal = $("<div>").load(response, function() {
      errorModal.find("#ces__error-message").html(message);
      $.showModal(errorModal[ 0 ].innerHTML);
    });
  });
};

// http://stackoverflow.com/a/9251169
CES_GLOBALS.ESCAPE_HTML = (function() {
  var escape = document.createElement("textarea");
  return function(html) {
    escape.textContent = html;
    return escape.innerHTML;
  };
})();
<commit_msg>Fix reference to globals<commit_after>/*
* ---
* GLOBALs
* ---
*/

var CES_GLOBALS = {};

/*
* CSRF Token
* ---
*
* CodePen uses a meta tag to defend against XSRF.
* This variable grabs that tag's content to allow POST requests
* which are required to change settings/save Pens/etc.
*
*/
CES_GLOBALS.CSRF_TOKEN = $("meta[name='csrf-token']").attr("content");

/*
* Init Data
* ---
*
* Grabs a copy of the init-data hidden input that's used on all CodePen pages.
*
* Awkwardly, these are actually merged into the window object by CodePen but
* this is an easier way to get access to everything.
*
* The JSON parsing gets incredibly awkward... Would prefer something easier.
*/
CES_GLOBALS.INIT_DATA = (function() {
  var data = $("#init-data").val();
  var parsedData = $.parseJSON(data);
  for (var key in parsedData) {
    var prop = parsedData[ key ];
    if (needsParsing(prop)) {
      parsedData[ key ] = $.parseJSON(prop);
    }
  }

  // Checks if a prop is valid JSON,
  // and if it's not parses it again
  function needsParsing(prop) {
    if ("string" == typeof prop) {
      var t = prop.substring(0, 1);
      if ("[" === t || "{" === t) {
        return true;
      }
    }
    return false;
  }

  // Sends INIT_DATA off to init.js since content scripts can't access the window object.
  // This allows conditional loading of modules based on INIT_DATA.
  var sendDataToContentScript = new CustomEvent("init-data-ready", {detail: parsedData});
  window.dispatchEvent(sendDataToContentScript);

  return parsedData;
})();

// Small utility to shortcut the AJAX requests needed to
// save/update stuff on CodePen.

// URL should be the relative path of the request,
// KEY should be the key of the data,
// VALUE should be the raw JSON data that you're sending.
CES_GLOBALS.CP = function(url, key, value, callback) {
  $.ajax({
    url: url,
    method: "POST",
    headers: {
      "X-CSRF-Token": CES_GLOBALS.CSRF_TOKEN,
    },
    data: key + "=" + encodeURIComponent(JSON.stringify(value)),
    complete: callback
  });
};

// Returns TRUE if currently using https
CES_GLOBALS.IS_HTTPS = function() {
  return window.location.protocol === "https:";
};

// Grabs the absolute path of a file via the message API.
// REQUESTEDURL is the relative path of the file,
// CALLBACK is... self explanatory. Fires once the URL is received.
CES_GLOBALS.REQUEST_EXTENSION_URL = function(requestedUrl, callback) {
  window.addEventListener("receivedUrl", function handleUrl(e) {
    window.removeEventListener("receivedUrl", handleUrl);
    callback(e.detail);
  });
  var requestExtensionUrl = new CustomEvent("requestExtensionUrl", {detail: requestedUrl});
  window.dispatchEvent(requestExtensionUrl);
};

// Appends a stylesheet to the head of the document.
// URL is the absolute path of the stylesheet.
CES_GLOBALS.APPEND_STYLESHEET = function(url) {
  var link = $("<link>");
  link.attr("type", "text/css");
  link.attr("rel", "stylesheet");
  link.attr("href", url);
  $("head").append(link);
};

// Binds to CodePen's internal pubhub dealio.
// I abstracted this into my own function incase it changes
// or this strategy stops being viable.
CES_GLOBALS.ON_PEN_SAVE = function(callback) {
  Hub.sub("pen-saved", callback);
};

// Uses CodePen's internal modal function which means
// I don't need to handle closing the modal or any of that
// nonsense which is preeeetty nice.
CES_GLOBALS.THROW_ERROR_MODAL = function(message) {
  CES_GLOBALS.REQUEST_EXTENSION_URL("modules/html/error-modal.html", function(response) {
    var errorModal = $("<div>").load(response, function() {
      errorModal.find("#ces__error-message").html(message);
      $.showModal(errorModal[ 0 ].innerHTML);
    });
  });
};

// http://stackoverflow.com/a/9251169
CES_GLOBALS.ESCAPE_HTML = (function() {
  var escape = document.createElement("textarea");
  return function(html) {
    escape.textContent = html;
    return escape.innerHTML;
  };
})();
<|endoftext|>


<commit_before>// Controller for Answer.
var generic = require('./generic');
var enums = require('../enums');
var oembed = require('oembed');
var common = require('../static/js/common');

var async = require('async');

var userController = require('./users');
var role = require('../lib/roles').user;

function oneAnswerResponse(res, req, crisis, question, answer, user) {
    //Sort comments in reverse chronological order
    answer.comments.sort(function(a,b){return b.comment.date - a.comment.date;});
    res.render('evidence/one', {
        crisis: crisis,
        question: question,
        answer: answer,
        properUser: common.properUser(req),
        page: {
            title: answer.post.title
        },
        user: user,
        properUser: common.properUser(req)
    });
}

// Creates a provisional user if the user is not logged in.
var applyUserAndRespond = function(req, res, crisis, question, answer) {
    if (req.user) {
        // User is currently logged in.
        oneAnswerResponse(res, req, crisis, question, answer, req.user);
    } else {
        // User is not currently logged in --
        // let's make them a provisional account
        // so they can immediately do stuff.
        userController.newProvisionalUser(req, function(err, user) {
            // Provisional user account created.
            // Respond.
            oneAnswerResponse(res, req, crisis, question, answer, req.user);
        });
    }
};

// Get a specific answer
var getOne = function (req, res) {

    // ETag support.
    var reqIfNoneMatch = req.get(enums.ifNoneMatch);

    generic.get(req.models.Crisis, req.params.crisis_id, undefined, function (err, crisis) {
        if (!err && crisis) {
            generic.get(req.models.Question, req.params.question_id, undefined, function (err, question) {
                if (!err && question) {
                    generic.get(req.models.Answer, req.params.answer_id, reqIfNoneMatch, function (err, answer) {
                        if (!err && answer) {
                            if (req.user){var user = req.user; }
                            answer.getComments(function(err){
                                //Filter hidden comments
                                answer.comments = answer.comments.filter(function(answerComment){ return common.isUserContentShow(answerComment.comment.user)&& answerComment.comment.show;});

                                if(answer.post.targetVideoUrl) {

                                    oembed.fetch(answer.post.targetVideoUrl, {}, function(err, result){

                                        if(!err){
                                            answer.post.targetVideoHtml = result.html;
                                        }else{
                                            answer.post.VideoUrlNotEmbeddable = answer.post.targetVideoUrl;
                                        }

                                        applyUserAndRespond(req, res, crisis, question, answer, user);
                                    });
                                }
                                else{
                                    applyUserAndRespond(req, res, crisis, question, answer, user);
                                }
                            });
                        }else {
                            generic.genericErrorHandler(req, res, err);
                        }
                    });
                } else {
                    generic.genericErrorHandler(req, res, err);
                }
            });

        } else {
            generic.genericErrorHandler(req, res, err);
        }
    });
};

exports.get = [role.can('view challenge pages'), getOne];

exports.head = function (req, res) {
    res.redirect('/');
    res.end();
};

// Get all answers ever, regardless of question id.
var allEver = function (req, res) { // this function finds all answers in db,regardless of the question id.
    req.models.Answer.find({}, function (err, Answers) {
        if (err || !Answers || Answers.length === 0) {
            generic.genericErrorHandler(req, res, err);
        } else {
            async.each(Answers, generic.load_post_ratings_count, function (err) {
                if (err) {
                    generic.genericErrorHandler(req, res, err);
                } else {
                    var wrapper = {
                        answers: Answers
                    };
                    res.status(200);
                    res.json(wrapper);
                    res.end();
                }
            });
        }
    });
};
exports.allEver = [role.can('assign roles'), allEver];

// Get all answers for a specific question.
var all = function (req, res) { // this function finds all answers of an specific question.
    generic.get(req.models.Question, req.params.question_id, undefined, function (err, question) {
        if (!err && question) {
            //question exists
            question.getAnswers(function (err, answers) {
                if (err || !answers || answers.length === 0) {
                    err = {};
                    err.code = 2;
                    generic.genericErrorHandler(req, res, err);
                } else {
                    async.each(answers, generic.load_post_ratings_count, function (err) {
                        if (err) {
                            generic.genericErrorHandler(req, res, err);
                        } else {
                            var wrapper = {
                                answers: answers
                            };
                            res.status(200);
                            res.json(wrapper);
                            res.end();
                        }
                    });
                }
            });
        } else {
            generic.genericErrorHandler(req, res, err);
        }
    });
};

exports.all = [role.can('assign roles'), all];

// Create an answer and add it to a question.
var createAnswer = function (req, res) {
    var crisis_id = req.params.crisis_id;
    generic.get(req.models.Question, req.params.question_id, undefined, function (err, question) {
        if (!err && question) {
            //question exists
            generic.create(req.models.Answer, {
                type: req.body.type
            }, req, function (err, answer) {
                if (!err && answer) {
                    answer.setQuestion(question, function (err) {
                        if (!err) {
                            var link_string = 'View evidence';
                            req.flash('info', 'Thanks, your evidence has been posted! ' + link_string);
                            res.redirect('/crisis/' + crisis_id +'/question/' + answer.question_id);
                            //res.json(wrapper);
                            res.end();
                        } else {
                            generic.genericErrorHandler(req, res, err);
                        }
                    });

                } else {
                    //special err: if 404 then it means the create just excuted is invalid.
                    res.status(500);
                    res.end('Error 500: Server Error');
                    console.r.error(req, 500, err);
                }
            });
        } else {
            generic.genericErrorHandler(req, res, err);
        }
    });
};

var checkRole = role.can('create an answer');

exports.create = [checkRole, createAnswer];

// Update answer
var update = function (req, res) {
    generic.get(req.models.Question, req.params.question_id, undefined, function (err, question) {
        if (!err && question) {
            generic.get(req.models.Answer, req.params.answer_id, undefined, function (err, answer) {
                if (!err && answer) {
                    generic.update(req.models.Answer, req.params.answer_id, req, function (err) {
                        if (!err) {
                            res.status(204);
                            res.end();
                        } else {
                            generic.genericErrorHandler(req, res, err);
                        }
                    });
                } else {
                    generic.genericErrorHandler(req, res, err);
                }
            });
        }
    });
};

exports.update = [role.can('edit an answer'), update];

// Remove an answer
var remove = function (req, res) {
    generic.get(req.models.Question, req.params.question_id, undefined, function (err, question) {
        if (!err && question) {
            generic.get(req.models.Answer, req.params.answer_id, undefined, function (err, answer) {
                if (!err && answer) {
                    answer.getComments(function (err, acomments) {
                        var j,
                            afterRemove = function (err) {
                                if (err) {
                                    generic.genericErrorHandler(req, res, err);
                                    throw err;
                                }
                            };
                        for (j in acomments) {
                            if (acomments.hasOwnProperty(j)) {
                                //start to delete acomments
                                generic.removeOne(acomments[j], req, afterRemove);
                            }
                        }
                        //start to delete answers
                        generic.removeOne(answer, req, function (err) {
                            if (!err) {
                                res.status(200);
                                // res.json(answer);
                                res.end();
                            } else {
                                generic.genericErrorHandler(req, res, err);
                                throw err;
                            }
                        });
                        // answers are deleted.
                    });
                } else {
                    generic.genericErrorHandler(req, res, err);
                }
            });
        } else {
            generic.genericErrorHandler(req, res, err);
        }
    });
};

exports.remove = [role.can('edit an answer'), remove];

var upvote = function (req, res) {
    generic.get(req.models.Question, req.params.question_id, undefined, function (err, question) {
        if (!err && question) {
            generic.get(req.models.Answer, req.params.answer_id, undefined, function (err, answer) {
                if (!err && answer) {
                    require('./ratings').upvote(req, answer.post, function(err, rating){
                        generic.load_answers_extra_fields(answer, function(){
                            if(!err){
                                res.status(200);
                                //Return answer for ajax update
                                res.json(answer);
                            } else {
                                generic.genericErrorHandler(req, res, err);
                            }
                        });
                    });
                } else {
                    generic.genericErrorHandler(req, res, err);
                }
            });
        } else {
            generic.genericErrorHandler(req, res, err);
        }
    });
};

exports.upvote = [role.can('upvote downvote'), upvote];

var downvote = function (req, res) {
    generic.get(req.models.Question, req.params.question_id, undefined, function (err, question) {
        if (!err && question) {
            generic.get(req.models.Answer, req.params.answer_id, undefined, function (err, answer) {
                if (!err && answer) {
                    answer.getPost(function(err, post){
                        require('./ratings').downvote(req, post, function(err, rating){
                            generic.load_answers_extra_fields(answer, function(){
                                if(!err){
                                    res.status(200);
                                    //Return answer for ajax update
                                    res.json(answer);
                                } else {
                                    generic.genericErrorHandler(req, res, err);
                                }
                            });
                        });
                    });
                } else {
                    generic.genericErrorHandler(req, res, err);
                }
            });
        } else {
            generic.genericErrorHandler(req, res, err);
        }
    });
};

exports.downvote = [role.can('upvote downvote'), downvote];

exports.getVideoHtml = function (req, res) {
    oembed.fetch(req.body.videoUrl, {}, function(err, result){

        if(!err){
            res.status(200);
            res.json(result);
        }else{
            res.status(200);
            res.json(err);
        }
    });
};<commit_msg>message for provisional users upon submitting evidence<commit_after>// Controller for Answer.
var generic = require('./generic');
var enums = require('../enums');
var oembed = require('oembed');
var common = require('../static/js/common');

var async = require('async');

var userController = require('./users');
var role = require('../lib/roles').user;

function oneAnswerResponse(res, req, crisis, question, answer, user) {
    //Sort comments in reverse chronological order
    answer.comments.sort(function(a,b){return b.comment.date - a.comment.date;});
    res.render('evidence/one', {
        crisis: crisis,
        question: question,
        answer: answer,
        properUser: common.properUser(req),
        page: {
            title: answer.post.title
        },
        user: user,
        properUser: common.properUser(req)
    });
}

// Creates a provisional user if the user is not logged in.
var applyUserAndRespond = function(req, res, crisis, question, answer) {
    if (req.user) {
        // User is currently logged in.
        oneAnswerResponse(res, req, crisis, question, answer, req.user);
    } else {
        // User is not currently logged in --
        // let's make them a provisional account
        // so they can immediately do stuff.
        userController.newProvisionalUser(req, function(err, user) {
            // Provisional user account created.
            // Respond.
            oneAnswerResponse(res, req, crisis, question, answer, req.user);
        });
    }
};

// Get a specific answer
var getOne = function (req, res) {

    // ETag support.
    var reqIfNoneMatch = req.get(enums.ifNoneMatch);

    generic.get(req.models.Crisis, req.params.crisis_id, undefined, function (err, crisis) {
        if (!err && crisis) {
            generic.get(req.models.Question, req.params.question_id, undefined, function (err, question) {
                if (!err && question) {
                    generic.get(req.models.Answer, req.params.answer_id, reqIfNoneMatch, function (err, answer) {
                        if (!err && answer) {
                            if (req.user){var user = req.user; }
                            answer.getComments(function(err){
                                //Filter hidden comments
                                answer.comments = answer.comments.filter(function(answerComment){ return common.isUserContentShow(answerComment.comment.user)&& answerComment.comment.show;});

                                if(answer.post.targetVideoUrl) {

                                    oembed.fetch(answer.post.targetVideoUrl, {}, function(err, result){

                                        if(!err){
                                            answer.post.targetVideoHtml = result.html;
                                        }else{
                                            answer.post.VideoUrlNotEmbeddable = answer.post.targetVideoUrl;
                                        }

                                        applyUserAndRespond(req, res, crisis, question, answer, user);
                                    });
                                }
                                else{
                                    applyUserAndRespond(req, res, crisis, question, answer, user);
                                }
                            });
                        }else {
                            generic.genericErrorHandler(req, res, err);
                        }
                    });
                } else {
                    generic.genericErrorHandler(req, res, err);
                }
            });

        } else {
            generic.genericErrorHandler(req, res, err);
        }
    });
};

exports.get = [role.can('view challenge pages'), getOne];

exports.head = function (req, res) {
    res.redirect('/');
    res.end();
};

// Get all answers ever, regardless of question id.
var allEver = function (req, res) { // this function finds all answers in db,regardless of the question id.
    req.models.Answer.find({}, function (err, Answers) {
        if (err || !Answers || Answers.length === 0) {
            generic.genericErrorHandler(req, res, err);
        } else {
            async.each(Answers, generic.load_post_ratings_count, function (err) {
                if (err) {
                    generic.genericErrorHandler(req, res, err);
                } else {
                    var wrapper = {
                        answers: Answers
                    };
                    res.status(200);
                    res.json(wrapper);
                    res.end();
                }
            });
        }
    });
};
exports.allEver = [role.can('assign roles'), allEver];

// Get all answers for a specific question.
var all = function (req, res) { // this function finds all answers of an specific question.
    generic.get(req.models.Question, req.params.question_id, undefined, function (err, question) {
        if (!err && question) {
            //question exists
            question.getAnswers(function (err, answers) {
                if (err || !answers || answers.length === 0) {
                    err = {};
                    err.code = 2;
                    generic.genericErrorHandler(req, res, err);
                } else {
                    async.each(answers, generic.load_post_ratings_count, function (err) {
                        if (err) {
                            generic.genericErrorHandler(req, res, err);
                        } else {
                            var wrapper = {
                                answers: answers
                            };
                            res.status(200);
                            res.json(wrapper);
                            res.end();
                        }
                    });
                }
            });
        } else {
            generic.genericErrorHandler(req, res, err);
        }
    });
};

exports.all = [role.can('assign roles'), all];

// Create an answer and add it to a question.
var createAnswer = function (req, res) {
    var crisis_id = req.params.crisis_id;
    generic.get(req.models.Question, req.params.question_id, undefined, function (err, question) {
        if (!err && question) {
            //question exists
            generic.create(req.models.Answer, {
                type: req.body.type
            }, req, function (err, answer) {
                if (!err && answer) {
                    answer.setQuestion(question, function (err) {
                        if (!err) {
                            var link_string = 'View evidence.';

                            var signupLinkString = 'Sign up';

                            var message = 'Thanks, your evidence has been posted! ' + link_string;

                            if (req.user.type === 'provisional') {
                                message += '
You are currently posting as a guest as ' + req.user.name + '. ' + signupLinkString + ' to keep this content associated with your identity and earn reputation points on the platform.';
                            }

                            req.flash('info', message);
                            res.redirect('/crisis/' + crisis_id +'/question/' + answer.question_id);
                            //res.json(wrapper);
                            res.end();
                        } else {
                            generic.genericErrorHandler(req, res, err);
                        }
                    });

                } else {
                    //special err: if 404 then it means the create just excuted is invalid.
                    res.status(500);
                    res.end('Error 500: Server Error');
                    console.r.error(req, 500, err);
                }
            });
        } else {
            generic.genericErrorHandler(req, res, err);
        }
    });
};

var checkRole = role.can('create an answer');

exports.create = [checkRole, createAnswer];

// Update answer
var update = function (req, res) {
    generic.get(req.models.Question, req.params.question_id, undefined, function (err, question) {
        if (!err && question) {
            generic.get(req.models.Answer, req.params.answer_id, undefined, function (err, answer) {
                if (!err && answer) {
                    generic.update(req.models.Answer, req.params.answer_id, req, function (err) {
                        if (!err) {
                            res.status(204);
                            res.end();
                        } else {
                            generic.genericErrorHandler(req, res, err);
                        }
                    });
                } else {
                    generic.genericErrorHandler(req, res, err);
                }
            });
        }
    });
};

exports.update = [role.can('edit an answer'), update];

// Remove an answer
var remove = function (req, res) {
    generic.get(req.models.Question, req.params.question_id, undefined, function (err, question) {
        if (!err && question) {
            generic.get(req.models.Answer, req.params.answer_id, undefined, function (err, answer) {
                if (!err && answer) {
                    answer.getComments(function (err, acomments) {
                        var j,
                            afterRemove = function (err) {
                                if (err) {
                                    generic.genericErrorHandler(req, res, err);
                                    throw err;
                                }
                            };
                        for (j in acomments) {
                            if (acomments.hasOwnProperty(j)) {
                                //start to delete acomments
                                generic.removeOne(acomments[j], req, afterRemove);
                            }
                        }
                        //start to delete answers
                        generic.removeOne(answer, req, function (err) {
                            if (!err) {
                                res.status(200);
                                // res.json(answer);
                                res.end();
                            } else {
                                generic.genericErrorHandler(req, res, err);
                                throw err;
                            }
                        });
                        // answers are deleted.
                    });
                } else {
                    generic.genericErrorHandler(req, res, err);
                }
            });
        } else {
            generic.genericErrorHandler(req, res, err);
        }
    });
};

exports.remove = [role.can('edit an answer'), remove];

var upvote = function (req, res) {
    generic.get(req.models.Question, req.params.question_id, undefined, function (err, question) {
        if (!err && question) {
            generic.get(req.models.Answer, req.params.answer_id, undefined, function (err, answer) {
                if (!err && answer) {
                    require('./ratings').upvote(req, answer.post, function(err, rating){
                        generic.load_answers_extra_fields(answer, function(){
                            if(!err){
                                res.status(200);
                                //Return answer for ajax update
                                res.json(answer);
                            } else {
                                generic.genericErrorHandler(req, res, err);
                            }
                        });
                    });
                } else {
                    generic.genericErrorHandler(req, res, err);
                }
            });
        } else {
            generic.genericErrorHandler(req, res, err);
        }
    });
};

exports.upvote = [role.can('upvote downvote'), upvote];

var downvote = function (req, res) {
    generic.get(req.models.Question, req.params.question_id, undefined, function (err, question) {
        if (!err && question) {
            generic.get(req.models.Answer, req.params.answer_id, undefined, function (err, answer) {
                if (!err && answer) {
                    answer.getPost(function(err, post){
                        require('./ratings').downvote(req, post, function(err, rating){
                            generic.load_answers_extra_fields(answer, function(){
                                if(!err){
                                    res.status(200);
                                    //Return answer for ajax update
                                    res.json(answer);
                                } else {
                                    generic.genericErrorHandler(req, res, err);
                                }
                            });
                        });
                    });
                } else {
                    generic.genericErrorHandler(req, res, err);
                }
            });
        } else {
            generic.genericErrorHandler(req, res, err);
        }
    });
};

exports.downvote = [role.can('upvote downvote'), downvote];

exports.getVideoHtml = function (req, res) {
    oembed.fetch(req.body.videoUrl, {}, function(err, result){

        if(!err){
            res.status(200);
            res.json(result);
        }else{
            res.status(200);
            res.json(err);
        }
    });
};<|endoftext|>


<commit_before>/**
 * The "glue" between TwoBit.js and GenomeTrack.js.
 *
 * GenomeTrack is pure view code -- it renders data which is already in-memory
 * in the browser.
 *
 * TwoBit is purely for data parsing and fetching. It only knows how to return
 * promises for various genome features.
 *
 * This code acts as a bridge between the two. It maintains a local version of
 * the data, fetching remote data and informing the view when it becomes
 * available.
 *
 * @flow
 */
'use strict';

// import type * as TwoBit from './TwoBit';

var Events = require('backbone').Events,
    Q = require('q'),
    _ = require('underscore');

var ContigInterval = require('./ContigInterval');

import type * as TwoBit from './TwoBit';

// Requests for 2bit ranges are expanded to begin & end at multiples of this
// constant. Doing this means that panning typically won't require
// additional network requests.
var BASE_PAIRS_PER_FETCH = 1000;

var MAX_BASE_PAIRS_TO_FETCH = 10000;

// Flow type for export.
type TwoBitSource = {
  rangeChanged: (newRange: GenomeRange) => void;
  needContigs: () => void;
  getRange: (range: GenomeRange) => ?{[key:string]: string};
  contigList: () => string[];
  on: (event: string, handler: Function) => void;
  off: (event: string) => void;
  trigger: (event: string, ...args:any) => void;
}

// Expand range to begin and end on multiples of BASE_PAIRS_PER_FETCH.
function expandRange(range) {
  var roundDown = x => x - x % BASE_PAIRS_PER_FETCH;
  var newStart = Math.max(1, roundDown(range.start())),
      newStop = roundDown(range.stop() + BASE_PAIRS_PER_FETCH - 1);

  return new ContigInterval(range.contig, newStart, newStop);
}

// TODO: make the return type more precise
var createTwoBitDataSource = function(remoteSource: TwoBit): TwoBitSource {
  // Local cache of genomic data.
  var contigList = [];
  var basePairs = {};  // contig -> locus -> letter
  function getBasePair(contig: string, position: number) {
    return (basePairs[contig] && basePairs[contig][position]) || null;
  }
  function setBasePair(contig: string, position: number, letter: string) {
    if (!basePairs[contig]) basePairs[contig] = {};
    basePairs[contig][position] = letter;
  }
  // Are all base pairs in the interval known? If so, no need to fetch.
  function isEntirelyKnown(interval: ContigInterval<string>) {
    for (var i = interval.start(); i <= interval.stop(); i++) {
      if (getBasePair(interval.contig, i) === null) return false;
    }
    return true;
  }

  function fetch(range: ContigInterval) {
    var span = range.stop() - range.start();
    if (span > MAX_BASE_PAIRS_TO_FETCH) {
      return Q.when();  // empty promise
    }
    if (isEntirelyKnown(range)) {
      return Q.when();
    }

    range = expandRange(range);

    console.log(`Fetching ${span} base pairs`);
    return remoteSource.getFeaturesInRange(range.contig, range.start(), range.stop())
        .then(letters => {
          for (var i = 0; i < letters.length; i++) {
            setBasePair(range.contig, range.start() + i, letters[i]);
          }
        });
  }

  // Returns a {"chr12:123" -> "[ATCG]"} mapping for the range.
  function getRange(range: GenomeRange) {
    if (!range) return null;
    var span = range.stop - range.start;
    if (span > MAX_BASE_PAIRS_TO_FETCH) {
      return {};
    }
    return _.chain(_.range(range.start, range.stop + 1))
        .map(x => [range.contig + ':' + x, getBasePair(range.contig, x)])
        .object()
        .value();
  }

  var o = {
    rangeChanged: function(newRange: GenomeRange) {
      // Range has changed! Fetch new data.
      var range = new ContigInterval(newRange.contig, newRange.start, newRange.stop);

      fetch(range)
          .then(() => o.trigger('newdata', range))
          .done();
    },
    needContigs: () => {
      remoteSource.getContigList().then(c => {
        contigList = c;
        o.trigger('contigs', contigList);
      }).done();
    },
    getRange: getRange,
    contigList: () => contigList,

    // These are here to make Flow happy.
    on: () => {},
    off: () => {},
    trigger: () => {}
  };
  _.extend(o, Events);  // Make this an event emitter

  return o;
};

module.exports = createTwoBitDataSource;
<commit_msg>Reduce TwoBit fetch limit (optimization)<commit_after>/**
 * The "glue" between TwoBit.js and GenomeTrack.js.
 *
 * GenomeTrack is pure view code -- it renders data which is already in-memory
 * in the browser.
 *
 * TwoBit is purely for data parsing and fetching. It only knows how to return
 * promises for various genome features.
 *
 * This code acts as a bridge between the two. It maintains a local version of
 * the data, fetching remote data and informing the view when it becomes
 * available.
 *
 * @flow
 */
'use strict';

// import type * as TwoBit from './TwoBit';

var Events = require('backbone').Events,
    Q = require('q'),
    _ = require('underscore');

var ContigInterval = require('./ContigInterval');

import type * as TwoBit from './TwoBit';

// Requests for 2bit ranges are expanded to begin & end at multiples of this
// constant. Doing this means that panning typically won't require
// additional network requests.
var BASE_PAIRS_PER_FETCH = 1000;

var MAX_BASE_PAIRS_TO_FETCH = 2000;

// Flow type for export.
type TwoBitSource = {
  rangeChanged: (newRange: GenomeRange) => void;
  needContigs: () => void;
  getRange: (range: GenomeRange) => ?{[key:string]: string};
  contigList: () => string[];
  on: (event: string, handler: Function) => void;
  off: (event: string) => void;
  trigger: (event: string, ...args:any) => void;
}

// Expand range to begin and end on multiples of BASE_PAIRS_PER_FETCH.
function expandRange(range) {
  var roundDown = x => x - x % BASE_PAIRS_PER_FETCH;
  var newStart = Math.max(1, roundDown(range.start())),
      newStop = roundDown(range.stop() + BASE_PAIRS_PER_FETCH - 1);

  return new ContigInterval(range.contig, newStart, newStop);
}

// TODO: make the return type more precise
var createTwoBitDataSource = function(remoteSource: TwoBit): TwoBitSource {
  // Local cache of genomic data.
  var contigList = [];
  var basePairs = {};  // contig -> locus -> letter
  function getBasePair(contig: string, position: number) {
    return (basePairs[contig] && basePairs[contig][position]) || null;
  }
  function setBasePair(contig: string, position: number, letter: string) {
    if (!basePairs[contig]) basePairs[contig] = {};
    basePairs[contig][position] = letter;
  }
  // Are all base pairs in the interval known? If so, no need to fetch.
  function isEntirelyKnown(interval: ContigInterval<string>) {
    for (var i = interval.start(); i <= interval.stop(); i++) {
      if (getBasePair(interval.contig, i) === null) return false;
    }
    return true;
  }

  function fetch(range: ContigInterval) {
    var span = range.stop() - range.start();
    if (span > MAX_BASE_PAIRS_TO_FETCH) {
      return Q.when();  // empty promise
    }
    if (isEntirelyKnown(range)) {
      return Q.when();
    }

    range = expandRange(range);

    console.log(`Fetching ${span} base pairs`);
    return remoteSource.getFeaturesInRange(range.contig, range.start(), range.stop())
        .then(letters => {
          for (var i = 0; i < letters.length; i++) {
            setBasePair(range.contig, range.start() + i, letters[i]);
          }
        });
  }

  // Returns a {"chr12:123" -> "[ATCG]"} mapping for the range.
  function getRange(range: GenomeRange) {
    if (!range) return null;
    var span = range.stop - range.start;
    if (span > MAX_BASE_PAIRS_TO_FETCH) {
      return {};
    }
    return _.chain(_.range(range.start, range.stop + 1))
        .map(x => [range.contig + ':' + x, getBasePair(range.contig, x)])
        .object()
        .value();
  }

  var o = {
    rangeChanged: function(newRange: GenomeRange) {
      // Range has changed! Fetch new data.
      var range = new ContigInterval(newRange.contig, newRange.start, newRange.stop);

      fetch(range)
          .then(() => o.trigger('newdata', range))
          .done();
    },
    needContigs: () => {
      remoteSource.getContigList().then(c => {
        contigList = c;
        o.trigger('contigs', contigList);
      }).done();
    },
    getRange: getRange,
    contigList: () => contigList,

    // These are here to make Flow happy.
    on: () => {},
    off: () => {},
    trigger: () => {}
  };
  _.extend(o, Events);  // Make this an event emitter

  return o;
};

module.exports = createTwoBitDataSource;
<|endoftext|>


<commit_before><commit_msg>Fix merging issues in Admin.js<commit_after><|endoftext|>