<commit_before>'use strict';

var child_process = require('child_process');
var fs = require('fs');
var path = require('path');
var _ = require('lodash');
var XRegExp = require('xregexp').XRegExp;

var SaveConfirmView = require('./save-confirm-view');
var TargetsView = require('./targets-view');
var BuildView = require('./build-view');
var GoogleAnalytics = require('./GoogleAnalytics');
var tools = require('./tools');

module.exports = {
  config: {
    panelVisibility: {
      title: 'Panel Visibility',
      description: 'Set when the build panel should be visible.',
      type: 'string',
      default: 'Toggle',
      enum: [ 'Toggle', 'Keep Visible', 'Show on Error' ],
      order: 1
    },
    buildOnSave: {
      title: 'Automatically build on save',
      description: 'Autmatically build your project each time an editor is saved.',
      type: 'boolean',
      default: false,
      order: 2
    },
    saveOnBuild: {
      title: 'Automatically save on build',
      description: 'Automatically save all edited files when triggering a build.',
      type: 'boolean',
      default: false,
      order: 3
    },
    monocleHeight: {
      title: 'Monocle Height',
      description: 'How much of the workspace to use for build panel when it is "maximized".',
      type: 'number',
      default: 0.75,
      minimum: 0.1,
      maximum: 0.9,
      order: 4
    },
    minimizedHeight: {
      title: 'Minimized Height',
      description: 'How much of the workspace to use for build panel when it is "minimized".',
      type: 'number',
      default: 0.15,
      minimum: 0.1,
      maximum: 0.9,
      order: 5
    }
  },

  reservedCommands: [
    'trigger',
    'stop',
    'confirm',
    'no-confirm',
    'error-match',
    'error-match-first',
    'select-active-targets'
  ],

  activate: function(state) {
    // Manually append /usr/local/bin as it may not be set on some systems,
    // and it's common to have node installed here. Keep it at end so it won't
    // accidentially override any other node installation
    process.env.PATH += ':/usr/local/bin';

    this.buildView = new BuildView();

    this.cmd = {};
    this.targets = {};
    this.activeTarget = null;
    this.match = [];
    this.stdout = new Buffer(0);
    this.stderr = new Buffer(0);
    atom.commands.add('atom-workspace', 'build:trigger', this.build.bind(this));
    atom.commands.add('atom-workspace', 'build:error-match', this.errorMatch.bind(this));
    atom.commands.add('atom-workspace', 'build:error-match-first', this.errorMatchFirst.bind(this));
    atom.commands.add('atom-workspace', 'build:stop', this.stop.bind(this));
    atom.commands.add('atom-workspace', 'build:confirm', function() {
      GoogleAnalytics.sendEvent('build', 'confirmed');
      document.activeElement.click();
    });
    atom.commands.add('atom-workspace', 'build:no-confirm', function() {
      GoogleAnalytics.sendEvent('build', 'not confirmed');
      this.saveConfirmView.cancel();
    }.bind(this));
    atom.commands.add('atom-workspace', 'build:select-active-target', function() {
      this.selectActiveTarget();
    }.bind(this));

    atom.workspace.observeTextEditors(function(editor) {
      editor.onDidSave(function(event) {
        if (atom.config.get('build.buildOnSave')) {
          var workspaceElement = atom.views.getView(atom.workspace);
          atom.commands.dispatch(workspaceElement, 'build:trigger');
        }
      });
    });

    GoogleAnalytics.sendEvent('core', 'activated');
  },

  deactivate: function() {
    GoogleAnalytics.sendEvent('core', 'deactivated');
    if (this.child) {
      this.child.kill('SIGKILL');
    }
    clearTimeout(this.finishedTimer);
  },

  refreshTargets: function() {
    var path;
    var textEditor = atom.workspace.getActiveTextEditor();
    if (!textEditor || !textEditor.getPath()) {
      /* default to building the first one if no editor is active */
      path = atom.project.getPaths()[0];
    } else {
      /* otherwise, build the one in the root of the active editor */
      path = _.find(atom.project.getPaths(), function (path) {
        var realpath = fs.realpathSync(path);
        return textEditor.getPath().substr(0, realpath.length) === realpath;
      });
    }

    var cmd = {
      env: {},
      args: [],
      cwd: path,
      sh: true,
      errorMatch: ''
    };

    var targets = _.chain(tools)
      .filter(function(tool) {
        return tool.isEligable(path);
      })
      .map(function(tool) {
        var settings = tool.settings(path);
        if (!settings.targets) { return null; }
        return _.map(settings.targets, function(target, name) {
          return [name, _.defaults(target, cmd)];
        });
      })
      .compact()
      .flatten(true)
      .value();

    // turn the array of target descriptions into one object. Targets will be
    // overwritten by later targets with the same name if present.
    targets = _.reduce(targets, function(targets, target) {
      targets[target[0]] = target[1];
      return targets;
    }, {});

    if (!_.isNull(this.activeTarget) && _.isUndefined(targets[this.activeTarget])) {
      // our active target has been removed, so it is no longer valid
      this.activeTarget = null;
    }

    this.targets = targets;
    return this.targets;
  },

  selectActiveTarget: function() {
    var targets = this.refreshTargets();
    var targetNames = _.keys(targets);
    var targetView = new TargetsView();
    targetView.show(targetNames, this.activeTarget, function(newTarget) {
      this.activeTarget = (newTarget === this.activeTarget) ? null : newTarget;
    }.bind(this));
  },

  buildCommand: function() {
    var path;
    var textEditor = atom.workspace.getActiveTextEditor();
    if (!textEditor || !textEditor.getPath()) {
      /* default to building the first one if no editor is active */
      path = atom.project.getPaths()[0];
    } else {
      /* otherwise, build the one in the root of the active editor */
      path = _.find(atom.project.getPaths(), function (path) {
        var realpath = fs.realpathSync(path);
        return textEditor.getPath().substr(0, realpath.length) === realpath;
      });
    }

    var cmd = {
      env: {},
      args: [],
      cwd: path,
      sh: true,
      errorMatch: ''
    };
    _.find(tools, function (tool) {
      if (!tool.isEligable(path)) {
        return;
      }
      GoogleAnalytics.sendEvent('build', 'tool selected', tool.niceName);
      cmd = _.defaults(tool.settings(path), cmd);
      return true;
    });

    return cmd;
  },

  replace: function(value) {
    var editor = atom.workspace.getActiveTextEditor();
    if (editor && 'untitled' !== editor.getTitle()) {
      var activeFile = fs.realpathSync(editor.getPath());
      value = value.replace('{FILE_ACTIVE}', activeFile);
      value = value.replace('{FILE_ACTIVE_PATH}', path.dirname(activeFile));
      value = value.replace('{FILE_ACTIVE_NAME}', path.basename(activeFile));
      value = value.replace('{FILE_ACTIVE_NAME_BASE}', path.basename(activeFile, path.extname(activeFile)));
    }

    value = value.replace('{PROJECT_PATH}', fs.realpathSync(atom.project.getPaths()[0]));
    if (atom.project.getRepositories[0]) {
      value = value.replace('{REPO_BRANCH_SHORT}', atom.project.getRepositories()[0].getShortHead());
    }

    return value;
  },

  errorParse: function() {
    this.match = [];
    var regex = XRegExp(this.cmd.errorMatch);

    var matchErr, matchOut;

    matchErr = XRegExp.forEach(this.stderr.toString('utf8'), regex, function (match) {
      return this.push(match);
    }, []);

    matchOut = XRegExp.forEach(this.stdout.toString('utf8'), regex, function (match) {
      return this.push(match);
    }, []);

    this.match = matchErr.concat(matchOut);
  },

  errorMatchGoTo: function () {
    var file = this.replace(this.cmd.cwd) + path.sep + this.match[0].file;
    if (!this.match[0].file) {
      file = this.match[0].absFile;
    }
    var row = this.match[0].line ? this.match[0].line - 1 : 0; /* Because atom is zero-based */
    var col =  this.match[0].col ? this.match[0].col - 1 : 0; /* Because atom is zero-based */

    fs.exists(file, function (exists) {
      if (!exists) {
        this.buildView.reset();
        this.buildView.errorMessage('Error matching failed:', true);
        this.buildView.append('Matched file does not exist: ' + file);
        return;
      }

      atom.workspace.open(file, {
        initialLine: row,
        initialColumn: col
      });
    }.bind(this));
  },

  errorMatch: function() {
    GoogleAnalytics.sendEvent('errorMatch', 'match');
    if (!this.cmd.errorMatch) {
      return;
    }

    if (this.match.length === 0) {
      this.errorParse();
      if (this.match.length === 0) {
        return;
      }
    }

    this.errorMatchGoTo();
    this.match.push(this.match.shift());
  },

  errorMatchFirst: function() {
    GoogleAnalytics.sendEvent('errorMatch', 'first');
    if (!this.cmd.errorMatch) {
      return;
    }
    this.errorParse();
    if (this.match.length === 0) {
      return;
    }

    this.errorMatchGoTo();
    this.match.push(this.match.shift());
  },

  startNewBuild: function() {
    this.cmd = {};
    this.match = [];
    try {
      this.cmd = this.activeTarget ? this.targets[this.activeTarget] : this.buildCommand();
    } catch (error) {
      this.buildView.reset();
      this.buildView.errorMessage('You have a syntax error in your build file.', true);
      this.buildView.append(error.message);
      return;
    }

    GoogleAnalytics.sendEvent('build', 'triggered');

    if (!this.cmd.exec) {
      return;
    }

    var env = _.extend(process.env, this.cmd.env);
    _.each(env, function(value, key, list) {
      list[key] = this.replace(value);
    }.bind(this));

    var args = _.map(this.cmd.args, this.replace);

    this.cmd.exec = this.replace(this.cmd.exec);

    this.child = child_process.spawn(
      this.cmd.sh ? '/bin/sh' : this.cmd.exec,
      this.cmd.sh ? [ '-c', [ this.cmd.exec ].concat(args).join(' ') ] : args,
      { cwd: this.replace(this.cmd.cwd), env: env }
    );

    this.stdout = new Buffer(0);
    this.child.stdout.on('data', function (buffer) {
      this.stdout = Buffer.concat([ this.stdout, buffer ]);
      this.buildView.append(buffer);
    }.bind(this));

    this.stderr = new Buffer(0);
    this.child.stderr.on('data', function (buffer) {
      this.stderr = Buffer.concat([ this.stderr, buffer ]);
      this.buildView.append(buffer);
    }.bind(this));

    this.child.on('error', function(err) {
      this.buildView.append((this.cmd.sh ? 'Unable to execute with sh: ' : 'Unable to execute: ') + this.cmd.exec);
      this.buildView.append(/\s/.test(this.cmd.exec) ? '`cmd` cannot contain space. Use `args` for arguments.' : '');
    }.bind(this));

    this.child.on('close', function(exitCode) {
      this.buildView.buildFinished(0 === exitCode);
      if (0 === exitCode) {
        GoogleAnalytics.sendEvent('build', 'succeeded');
        this.finishedTimer = setTimeout(function() {
          this.buildView.detach();
        }.bind(this), 1000);
      } else {
        GoogleAnalytics.sendEvent('build', 'failed');
      }
      this.child = null;
    }.bind(this));

    this.buildView.buildStarted();
    this.buildView.append((this.cmd.sh ? 'Executing with sh: ' : 'Executing: ') + this.cmd.exec + [ ' ' ].concat(args).join(' '));
  },

  abort: function(cb) {
    this.child.removeAllListeners('close');
    this.child.on('close', function() {
      this.child = null;
      if (cb) {
        cb();
      }
    }.bind(this));
    this.child.kill();
    this.child.killed = true;
  },

  build: function() {
    clearTimeout(this.finishedTimer);

    this.doSaveConfirm(this.unsavedTextEditors(), function() {
      if (this.child) {
        this.abort(function() {
          this.startNewBuild();
        }.bind(this));
      } else {
        this.startNewBuild();
      }
    }.bind(this));
  },

  doSaveConfirm: function(modifiedTextEditors, continuecb, cancelcb) {
    var saveAndContinue = function(save) {
      _.each(modifiedTextEditors, function(textEditor) {
        save && textEditor.save();
      });
      continuecb();
    };

    if (0 === _.size(modifiedTextEditors) || atom.config.get('build.saveOnBuild')) {
      return saveAndContinue(true);
    }

    this.saveConfirmView = new SaveConfirmView();
    this.saveConfirmView.show(saveAndContinue, cancelcb);
  },

  unsavedTextEditors: function() {
    return _.filter(atom.workspace.getTextEditors(), function(textEditor) {
      return textEditor.isModified() && ('untitled' !== textEditor.getTitle());
    });
  },

  stop: function() {

    clearTimeout(this.finishedTimer);
    if (this.child) {
      if (this.child.killed){
        // This child has been killed, but hasn't terminated. Hide it from user.
        this.child.removeAllListeners();
        this.child = null;
        this.buildView.buildAborted();
        return;
      }

      this.abort(this.buildView.buildAborted.bind(this.buildView));

      this.buildView.buildAbortInitiated();
    } else {
      this.buildView.reset();
    }
  }
};
<commit_msg>Concise abort param<commit_after>'use strict';

var child_process = require('child_process');
var fs = require('fs');
var path = require('path');
var _ = require('lodash');
var XRegExp = require('xregexp').XRegExp;

var SaveConfirmView = require('./save-confirm-view');
var TargetsView = require('./targets-view');
var BuildView = require('./build-view');
var GoogleAnalytics = require('./GoogleAnalytics');
var tools = require('./tools');

module.exports = {
  config: {
    panelVisibility: {
      title: 'Panel Visibility',
      description: 'Set when the build panel should be visible.',
      type: 'string',
      default: 'Toggle',
      enum: [ 'Toggle', 'Keep Visible', 'Show on Error' ],
      order: 1
    },
    buildOnSave: {
      title: 'Automatically build on save',
      description: 'Autmatically build your project each time an editor is saved.',
      type: 'boolean',
      default: false,
      order: 2
    },
    saveOnBuild: {
      title: 'Automatically save on build',
      description: 'Automatically save all edited files when triggering a build.',
      type: 'boolean',
      default: false,
      order: 3
    },
    monocleHeight: {
      title: 'Monocle Height',
      description: 'How much of the workspace to use for build panel when it is "maximized".',
      type: 'number',
      default: 0.75,
      minimum: 0.1,
      maximum: 0.9,
      order: 4
    },
    minimizedHeight: {
      title: 'Minimized Height',
      description: 'How much of the workspace to use for build panel when it is "minimized".',
      type: 'number',
      default: 0.15,
      minimum: 0.1,
      maximum: 0.9,
      order: 5
    }
  },

  reservedCommands: [
    'trigger',
    'stop',
    'confirm',
    'no-confirm',
    'error-match',
    'error-match-first',
    'select-active-targets'
  ],

  activate: function(state) {
    // Manually append /usr/local/bin as it may not be set on some systems,
    // and it's common to have node installed here. Keep it at end so it won't
    // accidentially override any other node installation
    process.env.PATH += ':/usr/local/bin';

    this.buildView = new BuildView();

    this.cmd = {};
    this.targets = {};
    this.activeTarget = null;
    this.match = [];
    this.stdout = new Buffer(0);
    this.stderr = new Buffer(0);
    atom.commands.add('atom-workspace', 'build:trigger', this.build.bind(this));
    atom.commands.add('atom-workspace', 'build:error-match', this.errorMatch.bind(this));
    atom.commands.add('atom-workspace', 'build:error-match-first', this.errorMatchFirst.bind(this));
    atom.commands.add('atom-workspace', 'build:stop', this.stop.bind(this));
    atom.commands.add('atom-workspace', 'build:confirm', function() {
      GoogleAnalytics.sendEvent('build', 'confirmed');
      document.activeElement.click();
    });
    atom.commands.add('atom-workspace', 'build:no-confirm', function() {
      GoogleAnalytics.sendEvent('build', 'not confirmed');
      this.saveConfirmView.cancel();
    }.bind(this));
    atom.commands.add('atom-workspace', 'build:select-active-target', function() {
      this.selectActiveTarget();
    }.bind(this));

    atom.workspace.observeTextEditors(function(editor) {
      editor.onDidSave(function(event) {
        if (atom.config.get('build.buildOnSave')) {
          var workspaceElement = atom.views.getView(atom.workspace);
          atom.commands.dispatch(workspaceElement, 'build:trigger');
        }
      });
    });

    GoogleAnalytics.sendEvent('core', 'activated');
  },

  deactivate: function() {
    GoogleAnalytics.sendEvent('core', 'deactivated');
    if (this.child) {
      this.child.kill('SIGKILL');
    }
    clearTimeout(this.finishedTimer);
  },

  refreshTargets: function() {
    var path;
    var textEditor = atom.workspace.getActiveTextEditor();
    if (!textEditor || !textEditor.getPath()) {
      /* default to building the first one if no editor is active */
      path = atom.project.getPaths()[0];
    } else {
      /* otherwise, build the one in the root of the active editor */
      path = _.find(atom.project.getPaths(), function (path) {
        var realpath = fs.realpathSync(path);
        return textEditor.getPath().substr(0, realpath.length) === realpath;
      });
    }

    var cmd = {
      env: {},
      args: [],
      cwd: path,
      sh: true,
      errorMatch: ''
    };

    var targets = _.chain(tools)
      .filter(function(tool) {
        return tool.isEligable(path);
      })
      .map(function(tool) {
        var settings = tool.settings(path);
        if (!settings.targets) { return null; }
        return _.map(settings.targets, function(target, name) {
          return [name, _.defaults(target, cmd)];
        });
      })
      .compact()
      .flatten(true)
      .value();

    // turn the array of target descriptions into one object. Targets will be
    // overwritten by later targets with the same name if present.
    targets = _.reduce(targets, function(targets, target) {
      targets[target[0]] = target[1];
      return targets;
    }, {});

    if (!_.isNull(this.activeTarget) && _.isUndefined(targets[this.activeTarget])) {
      // our active target has been removed, so it is no longer valid
      this.activeTarget = null;
    }

    this.targets = targets;
    return this.targets;
  },

  selectActiveTarget: function() {
    var targets = this.refreshTargets();
    var targetNames = _.keys(targets);
    var targetView = new TargetsView();
    targetView.show(targetNames, this.activeTarget, function(newTarget) {
      this.activeTarget = (newTarget === this.activeTarget) ? null : newTarget;
    }.bind(this));
  },

  buildCommand: function() {
    var path;
    var textEditor = atom.workspace.getActiveTextEditor();
    if (!textEditor || !textEditor.getPath()) {
      /* default to building the first one if no editor is active */
      path = atom.project.getPaths()[0];
    } else {
      /* otherwise, build the one in the root of the active editor */
      path = _.find(atom.project.getPaths(), function (path) {
        var realpath = fs.realpathSync(path);
        return textEditor.getPath().substr(0, realpath.length) === realpath;
      });
    }

    var cmd = {
      env: {},
      args: [],
      cwd: path,
      sh: true,
      errorMatch: ''
    };
    _.find(tools, function (tool) {
      if (!tool.isEligable(path)) {
        return;
      }
      GoogleAnalytics.sendEvent('build', 'tool selected', tool.niceName);
      cmd = _.defaults(tool.settings(path), cmd);
      return true;
    });

    return cmd;
  },

  replace: function(value) {
    var editor = atom.workspace.getActiveTextEditor();
    if (editor && 'untitled' !== editor.getTitle()) {
      var activeFile = fs.realpathSync(editor.getPath());
      value = value.replace('{FILE_ACTIVE}', activeFile);
      value = value.replace('{FILE_ACTIVE_PATH}', path.dirname(activeFile));
      value = value.replace('{FILE_ACTIVE_NAME}', path.basename(activeFile));
      value = value.replace('{FILE_ACTIVE_NAME_BASE}', path.basename(activeFile, path.extname(activeFile)));
    }

    value = value.replace('{PROJECT_PATH}', fs.realpathSync(atom.project.getPaths()[0]));
    if (atom.project.getRepositories[0]) {
      value = value.replace('{REPO_BRANCH_SHORT}', atom.project.getRepositories()[0].getShortHead());
    }

    return value;
  },

  errorParse: function() {
    this.match = [];
    var regex = XRegExp(this.cmd.errorMatch);

    var matchErr, matchOut;

    matchErr = XRegExp.forEach(this.stderr.toString('utf8'), regex, function (match) {
      return this.push(match);
    }, []);

    matchOut = XRegExp.forEach(this.stdout.toString('utf8'), regex, function (match) {
      return this.push(match);
    }, []);

    this.match = matchErr.concat(matchOut);
  },

  errorMatchGoTo: function () {
    var file = this.replace(this.cmd.cwd) + path.sep + this.match[0].file;
    if (!this.match[0].file) {
      file = this.match[0].absFile;
    }
    var row = this.match[0].line ? this.match[0].line - 1 : 0; /* Because atom is zero-based */
    var col =  this.match[0].col ? this.match[0].col - 1 : 0; /* Because atom is zero-based */

    fs.exists(file, function (exists) {
      if (!exists) {
        this.buildView.reset();
        this.buildView.errorMessage('Error matching failed:', true);
        this.buildView.append('Matched file does not exist: ' + file);
        return;
      }

      atom.workspace.open(file, {
        initialLine: row,
        initialColumn: col
      });
    }.bind(this));
  },

  errorMatch: function() {
    GoogleAnalytics.sendEvent('errorMatch', 'match');
    if (!this.cmd.errorMatch) {
      return;
    }

    if (this.match.length === 0) {
      this.errorParse();
      if (this.match.length === 0) {
        return;
      }
    }

    this.errorMatchGoTo();
    this.match.push(this.match.shift());
  },

  errorMatchFirst: function() {
    GoogleAnalytics.sendEvent('errorMatch', 'first');
    if (!this.cmd.errorMatch) {
      return;
    }
    this.errorParse();
    if (this.match.length === 0) {
      return;
    }

    this.errorMatchGoTo();
    this.match.push(this.match.shift());
  },

  startNewBuild: function() {
    this.cmd = {};
    this.match = [];
    try {
      this.cmd = this.activeTarget ? this.targets[this.activeTarget] : this.buildCommand();
    } catch (error) {
      this.buildView.reset();
      this.buildView.errorMessage('You have a syntax error in your build file.', true);
      this.buildView.append(error.message);
      return;
    }

    GoogleAnalytics.sendEvent('build', 'triggered');

    if (!this.cmd.exec) {
      return;
    }

    var env = _.extend(process.env, this.cmd.env);
    _.each(env, function(value, key, list) {
      list[key] = this.replace(value);
    }.bind(this));

    var args = _.map(this.cmd.args, this.replace);

    this.cmd.exec = this.replace(this.cmd.exec);

    this.child = child_process.spawn(
      this.cmd.sh ? '/bin/sh' : this.cmd.exec,
      this.cmd.sh ? [ '-c', [ this.cmd.exec ].concat(args).join(' ') ] : args,
      { cwd: this.replace(this.cmd.cwd), env: env }
    );

    this.stdout = new Buffer(0);
    this.child.stdout.on('data', function (buffer) {
      this.stdout = Buffer.concat([ this.stdout, buffer ]);
      this.buildView.append(buffer);
    }.bind(this));

    this.stderr = new Buffer(0);
    this.child.stderr.on('data', function (buffer) {
      this.stderr = Buffer.concat([ this.stderr, buffer ]);
      this.buildView.append(buffer);
    }.bind(this));

    this.child.on('error', function(err) {
      this.buildView.append((this.cmd.sh ? 'Unable to execute with sh: ' : 'Unable to execute: ') + this.cmd.exec);
      this.buildView.append(/\s/.test(this.cmd.exec) ? '`cmd` cannot contain space. Use `args` for arguments.' : '');
    }.bind(this));

    this.child.on('close', function(exitCode) {
      this.buildView.buildFinished(0 === exitCode);
      if (0 === exitCode) {
        GoogleAnalytics.sendEvent('build', 'succeeded');
        this.finishedTimer = setTimeout(function() {
          this.buildView.detach();
        }.bind(this), 1000);
      } else {
        GoogleAnalytics.sendEvent('build', 'failed');
      }
      this.child = null;
    }.bind(this));

    this.buildView.buildStarted();
    this.buildView.append((this.cmd.sh ? 'Executing with sh: ' : 'Executing: ') + this.cmd.exec + [ ' ' ].concat(args).join(' '));
  },

  abort: function(cb) {
    this.child.removeAllListeners('close');
    this.child.on('close', function() {
      this.child = null;
      if (cb) {
        cb();
      }
    }.bind(this));
    this.child.kill();
    this.child.killed = true;
  },

  build: function() {
    clearTimeout(this.finishedTimer);

    this.doSaveConfirm(this.unsavedTextEditors(), function() {
      if (this.child) {
        this.abort(this.startNewBuild.bind(this));
      } else {
        this.startNewBuild();
      }
    }.bind(this));
  },

  doSaveConfirm: function(modifiedTextEditors, continuecb, cancelcb) {
    var saveAndContinue = function(save) {
      _.each(modifiedTextEditors, function(textEditor) {
        save && textEditor.save();
      });
      continuecb();
    };

    if (0 === _.size(modifiedTextEditors) || atom.config.get('build.saveOnBuild')) {
      return saveAndContinue(true);
    }

    this.saveConfirmView = new SaveConfirmView();
    this.saveConfirmView.show(saveAndContinue, cancelcb);
  },

  unsavedTextEditors: function() {
    return _.filter(atom.workspace.getTextEditors(), function(textEditor) {
      return textEditor.isModified() && ('untitled' !== textEditor.getTitle());
    });
  },

  stop: function() {

    clearTimeout(this.finishedTimer);
    if (this.child) {
      if (this.child.killed){
        // This child has been killed, but hasn't terminated. Hide it from user.
        this.child.removeAllListeners();
        this.child = null;
        this.buildView.buildAborted();
        return;
      }

      this.abort(this.buildView.buildAborted.bind(this.buildView));

      this.buildView.buildAbortInitiated();
    } else {
      this.buildView.reset();
    }
  }
};
<|endoftext|>


<commit_before>
// npm build command

// everything about the installation after the creation of
// the .npm/{name}/{version}/package folder.
// linking the modules into the npm.root,
// resolving dependencies, etc.

// This runs AFTER install or link are completed.

var npm = require("../npm")
  , log = require("./utils/log")
  , chain = require("./utils/chain")
  , fs = require("./utils/graceful-fs")
  , path = require("path")
  , lifecycle = require("./utils/lifecycle")
  , readJson = require("./utils/read-json")
  , link = require("./utils/link")
  , linkIfExists = link.ifExists
  , asyncMap = require("./utils/async-map")
  , output = require("./utils/output")

module.exports = build
build.usage = "npm build <folder>\n(this is plumbing)"

function build (args, cb) { asyncMap(args, build_, cb) }

function build_ (folder, cb) {
  folder = path.resolve(folder)
  log(folder, "build")
  readJson(path.resolve(folder, "package.json"), function (er, pkg) {
    if (er) return cb(er)
    chain
      ( [lifecycle, pkg, "preinstall", folder]
      , [linkStuff, pkg, folder]
      , [lifecycle, pkg, "install", folder]
      , cb )
  })
}

function linkStuff (pkg, folder, cb) {
  // if it's global, and folder is in {prefix}/node_modules,
  // then bins are in {prefix}/bin
  // otherwise, then bins are in folder/../.bin
  var parent = path.dirname(folder)
    , gnm = npm.config.get("global")
          && path.resolve(npm.prefix, "node_modules")
    , top = gnm === parent

  log(pkg._id, "linkStuff")
  asyncMap([linkBins, linkMans, rebuildBundles], function (fn, cb) {
    log(pkg._id, fn.name)
    fn(pkg, folder, parent, top, cb)
  }, cb)
}

function rebuildBundles (pkg, folder, parent, top, cb) {
  if (!npm.config.get("rebuild-bundle")) return cb()

  var deps = Object.keys(pkg.dependencies || {})
             .concat(Object.keys(pkg.devDependencies || {}))
    , bundles = pkg.bundleDependencies || pkg.bundledDependencies || []

  fs.readdir(path.resolve(folder, "node_modules"), function (er, files) {
    // error means no bundles
    if (er) return cb()

    asyncMap(files.filter(function (file) {
      return file.charAt(0) !== "."
          && file.indexOf("@") === -1
          && (deps.indexOf(file) === -1 || bundles.indexOf(file) !== -1)
    }).map(function (file) {
      return path.resolve(folder, "node_modules", file)
    }), function (file, cb) {
      // if not a dir, then don't do it.
      fs.lstat(file, function (er, st) {
        if (er || !st.isDirectory()) return cb()
        build_(file, cb)
      })
    }, cb)
  })
}

function linkBins (pkg, folder, parent, top, cb) {
  if (!pkg.bin) return cb()
  log(pkg.bin, "bins linking")
  var binRoot = top ? path.resolve(npm.config.get("prefix"), "bin")
                    : path.resolve(parent, ".bin")
  asyncMap(Object.keys(pkg.bin), function (b, cb) {
    linkIfExists(path.resolve(folder, pkg.bin[b])
                ,path.resolve(binRoot, b)
                ,top && folder
                ,function (er) {
      if (er || !top) return cb(er)
      output.write(path.resolve(binRoot, b)+" -> "
                  +path.resolve(folder, pkg.bin[b]), cb)
    })
  }, cb)
}

function linkMans (pkg, folder, parent, top, cb) {
  if (!pkg.man || !top) return cb()
  var manRoot = path.resolve(npm.config.get("prefix"), "share", "man")
  asyncMap(pkg.man, function (man, cb) {
    var parseMan = man.match(/(.*)\.([0-9]+)(\.gz)?$/)
      , stem = parseMan[1]
      , sxn = parseMan[2]
      , gz = parseMan[3] || ""
      , bn = path.basename(stem)
      , manSrc = path.join( folder, man )
      , manDest = path.join( manRoot
                           , "man"+sxn
                           , (bn.indexOf(pkg.name) === 0 ? bn
                             : pkg.name + "-" + bn)
                             + "." + sxn + gz
                           )
    linkIfExists(manSrc, manDest, top && folder, cb)
  }, cb)
}
<commit_msg>postinstall event<commit_after>
// npm build command

// everything about the installation after the creation of
// the .npm/{name}/{version}/package folder.
// linking the modules into the npm.root,
// resolving dependencies, etc.

// This runs AFTER install or link are completed.

var npm = require("../npm")
  , log = require("./utils/log")
  , chain = require("./utils/chain")
  , fs = require("./utils/graceful-fs")
  , path = require("path")
  , lifecycle = require("./utils/lifecycle")
  , readJson = require("./utils/read-json")
  , link = require("./utils/link")
  , linkIfExists = link.ifExists
  , asyncMap = require("./utils/async-map")
  , output = require("./utils/output")

module.exports = build
build.usage = "npm build <folder>\n(this is plumbing)"

function build (args, cb) { asyncMap(args, build_, cb) }

function build_ (folder, cb) {
  folder = path.resolve(folder)
  log(folder, "build")
  readJson(path.resolve(folder, "package.json"), function (er, pkg) {
    if (er) return cb(er)
    chain
      ( [lifecycle, pkg, "preinstall", folder]
      , [linkStuff, pkg, folder]
      , [lifecycle, pkg, "install", folder]
      , [lifecycle, pkg, "postinstall", folder]
      , cb )
  })
}

function linkStuff (pkg, folder, cb) {
  // if it's global, and folder is in {prefix}/node_modules,
  // then bins are in {prefix}/bin
  // otherwise, then bins are in folder/../.bin
  var parent = path.dirname(folder)
    , gnm = npm.config.get("global")
          && path.resolve(npm.prefix, "node_modules")
    , top = gnm === parent

  log(pkg._id, "linkStuff")
  asyncMap([linkBins, linkMans, rebuildBundles], function (fn, cb) {
    log(pkg._id, fn.name)
    fn(pkg, folder, parent, top, cb)
  }, cb)
}

function rebuildBundles (pkg, folder, parent, top, cb) {
  if (!npm.config.get("rebuild-bundle")) return cb()

  var deps = Object.keys(pkg.dependencies || {})
             .concat(Object.keys(pkg.devDependencies || {}))
    , bundles = pkg.bundleDependencies || pkg.bundledDependencies || []

  fs.readdir(path.resolve(folder, "node_modules"), function (er, files) {
    // error means no bundles
    if (er) return cb()

    asyncMap(files.filter(function (file) {
      return file.charAt(0) !== "."
          && file.indexOf("@") === -1
          && (deps.indexOf(file) === -1 || bundles.indexOf(file) !== -1)
    }).map(function (file) {
      return path.resolve(folder, "node_modules", file)
    }), function (file, cb) {
      // if not a dir, then don't do it.
      fs.lstat(file, function (er, st) {
        if (er || !st.isDirectory()) return cb()
        build_(file, cb)
      })
    }, cb)
  })
}

function linkBins (pkg, folder, parent, top, cb) {
  if (!pkg.bin) return cb()
  log(pkg.bin, "bins linking")
  var binRoot = top ? path.resolve(npm.config.get("prefix"), "bin")
                    : path.resolve(parent, ".bin")
  asyncMap(Object.keys(pkg.bin), function (b, cb) {
    linkIfExists(path.resolve(folder, pkg.bin[b])
                ,path.resolve(binRoot, b)
                ,top && folder
                ,function (er) {
      if (er || !top) return cb(er)
      output.write(path.resolve(binRoot, b)+" -> "
                  +path.resolve(folder, pkg.bin[b]), cb)
    })
  }, cb)
}

function linkMans (pkg, folder, parent, top, cb) {
  if (!pkg.man || !top) return cb()
  var manRoot = path.resolve(npm.config.get("prefix"), "share", "man")
  asyncMap(pkg.man, function (man, cb) {
    var parseMan = man.match(/(.*)\.([0-9]+)(\.gz)?$/)
      , stem = parseMan[1]
      , sxn = parseMan[2]
      , gz = parseMan[3] || ""
      , bn = path.basename(stem)
      , manSrc = path.join( folder, man )
      , manDest = path.join( manRoot
                           , "man"+sxn
                           , (bn.indexOf(pkg.name) === 0 ? bn
                             : pkg.name + "-" + bn)
                             + "." + sxn + gz
                           )
    linkIfExists(manSrc, manDest, top && folder, cb)
  }, cb)
}
<|endoftext|>


<commit_before>/**
 * z.build 0.0.2
 */

var z = require('../dist/z').z;
var imports = require('../dist/z').imports;
var define = require('../dist/z').define;
var UglifyJS = require("uglify-js");
var fs = require('fs');

/**
 * Overwrite z.module's plugins for use in the node env.
 */

/**
 * Mock script loading.
 */
var ScriptMock = z.Loader.extend({
  load: function(req){
    var contents = fs.readFileSync(req.url, 'utf-8')
      // Wrap the contents in a function that we can pass stuff to.
      // This is done to get around the fact that node doesn't have any globals,
      // so z will be undefined if we try to load it normally.
      , zModule;

    try{
      zModule = Function('z, imports, define', contents);
      zModule(z, imports, define);
    } catch(e) {
      console.log('  ...building failed, wrapping file');
      // Stick it in a wrapper and hope for the best!
      zModule = Function('z, imports, define', 'z(function(){'+ contents +'});');
      zModule(z, imports, define);
    }

    this._resolve(true, 1);
  }
});

z.plugin('script', ScriptMock, function(req, res, next, error){
  var name = req.from;
  console.log('  ...building: ', name);

  z.ensureModule(name);
  next();
});

/**
 * Mock ajax loading
 */
AjaxMock = z.Loader.extend({
  load: function(req){
    var name = req.from
      , contents = fs.readFileSync(req.url, 'utf-8');

    this._value = contents;

    this._resolve(contents, 1);
  }
});

z.plugin('ajax', AjaxMock, function(req, res, next, error){
  var name = req.from;

  console.log('  ...added file: ', name);

  if(res){
    var factory = "z('"+name+"').exports(function(){ return '" + escape(res) + "'; });";
    var zModule = Function('z, imports, define', factory);

    // Run the module.
    zModule(z, imports, exports, define);
  } else {
    console.log('  Warning: result was empty. Continuing, but be sure this is desired...')
  }

  next();
}, {
  ext: 'txt'
});

var escapes = {
      "'" : "'",
      '\\': '\\',
      '\r': 'r',
      '\n': 'n',
      '\t': 't',
      '\u2028': 'u2028',
      '\u2029': 'u2029'
    }
  , escaper = /\\|'|\r|\n|\t|\u2028|\u2029/g;
var escape = function(txt){
  return txt.replace(escaper, function(match) { return '\\' + escapes[match]; });
}

/**
 * ----------------------------------------------------------------------
 * z.Build
 *
 * Fit's module-compiler, used to crunch things into a single file.
 *
 * Fit projects ideally should be built before they are made live --
 * the module system should be used mainly as a development tool.
 */
var Build = function(options){

  var defaults = {
    optimize: false,
    main: 'main',
    buildfile: false,
    shim: {
      'jquery' : function(mod){
        var compile = mod._factory.toString();
        return '/*jquery*/\njquery: (' + compile + ')(),\n\n'
      }
    }
  };

  this.options = z.u.defaults(defaults, options);
}

Build.prototype.setup = function(options){
  this.options = z.u.defaults(this.options, options);
}

Build.prototype.start = function(main, dest, next){
  var self = this
    , mainName = main.replace('.js', '').replace(/\//g, '.');

  if(this.options.buildfile){
    try{
      var options = require(main)
        , paths = z.u.extract(['main', 'dest'], options);
      main = paths.src;
      dest = paths.dest;
      this.setup(options);
    } catch(e){
      console.log('Could not find a buildz.js file.\nTry providing a main file (e.g. \'main.js\') or make sure you are in the correct dir.')
      return false;
    }
  }

  z.setup({
    env: 'server'
  });

  console.log('\nbuilding...\n');

  var loader = new ScriptMock({url: main});

  loader.done(function(){
    if(self.options.main){
      mainName = self.options.main;
    }

    z.ensureModule(mainName);

    console.log('  ...main module found');
    console.log('  ...added: ', mainName);

    if(self.options.project === undefined){
      self.options.project = main.replace('.js', '').replace(/\//g, '.');
    }

    z(mainName).done(function(){
      var rendered = self.render(dest);
      if(z.u.isFunction(next)){
        next(rendered);
      }
    });
  }, function(e){
    console.error(e);
  });

}

Build.prototype.compile = function(name){

  if(false === z.has(name)){
    console.log('   The module '+ name + 'was loaded, but is not in the registry.')
    console.log('   continuing...');
    console.log();
    return;
  }

  var mod = z(name)
    , rendered = ''
    , deps = mod._deps
    , factory;

  if(mod._factory){
    factory = mod._factory.toString();
  } else {
    console.log('   Warning: factory not found for module: '+ name);
    console.log('   Continuing...')
  }

  if(this.options.shim.hasOwnProperty(name)){
    rendered = this.options.shim[name](mod);
    return rendered;
  }

  // Build the compiled string.
  rendered = '/*'+name+'*/\n';
  rendered += "'"+name+"': new Module(";

  // Build imports.
  if(deps.length > 0){
    var imp = [];
    z.u(deps).each(function(dep){
      imp.push('  ' + JSON.stringify(dep)); // Seems the most flexable method.
    });
    rendered += "[\n" + imp.join(',\n') +'\n]';
  }

  rendered += ').exports(' + factory + '),\n\n';

  return rendered;
}

Build.prototype.renderZ = function(){
  // Eventually make this configurable.
  var root = __dirname + '/../'
    , rendered = '';

  z.u([
    "src/util_large.js",
    // "src/util_small.js",
    "src/core.js",
    "src/Class.js",
    "src/Module.js",
    "src/Plugable.js",
    "src/Loader.js", // Should be optional
    "src/Script.js", // Should be optional
    "src/Ajax.js",   // Should be optional
    "src/amd.js",
    "src/plugins.js"
  ]).each(function(file){
    rendered += fs.readFileSync(root + file, 'utf-8') + "\n\n";
  });

  rendered = '\n/*! zjs */\n' + UglifyJS.minify(rendered, {fromString: true}).code;

  return rendered;
}

Build.prototype.render = function(dest){

  var self = this
    , rendered = '';

  // Check to see if the user defined any build settings in
  // z.setup. This could include compile shims, destinations, etc.
  if(z.config.build){
    z.u.extend(this.options, z.config.build);
    delete z.config.build;
  }

  dest = (dest || this.options.dest);

  if(!dest){
    console.log('No destination!');
    console.log('Set [dest] in one of the following ways:');
    console.log('   console: $ zjs build [src] [dest]');
    console.log('   in main.js: z.setup({ ... build:{ ... dest:[dest]... } ... });');
    console.log('   in zbuild.js: ... dest:[dest] ...');
    console.log('No files have been modified.');
    return;
  }

  z.u.each(z.modules, function(mod, name){
    rendered += self.compile(name);
  });

  rendered = '/* modules */\nz.modules = {\n' + rendered + '};\n\n';

  delete z.config.env;

  // Add the config
  rendered = 'z.setup(' + JSON.stringify(z.config) + ');\n\n' + rendered;

  // Add z itself to the top of the compiled project
  rendered = '(function(root){\n' + this.renderZ() + rendered + '\n})(window);';

  if(this.options.optimize){
    console.log('\nuglifying...');
    rendered = UglifyJS.minify(rendered, {fromString: true}).code;
    console.log('\n  ...uglified');
  }

  if(this.options.about){
    this.options.project += '\n' + this.options.about;
  }

  rendered = '/*'+this.options.project+'*/\n' + rendered;

  fs.writeFileSync(dest, rendered);

  console.log('\nbuilt to '+ dest + '\n');

  if(typeof next == 'function'){
    next(dest, rendered);
  }

  return rendered;
}

// Export instance of build.
exports = module.exports = new Build;

// Export Build
exports.Build = Build
<commit_msg>Actually, that didn't work<commit_after>/**
 * z.build 0.0.2
 */

var z = require('../dist/z').z;
var imports = require('../dist/z').imports;
var define = require('../dist/z').define;
var UglifyJS = require("uglify-js");
var fs = require('fs');

/**
 * Overwrite z.module's plugins for use in the node env.
 */

/**
 * Mock script loading.
 */
var ScriptMock = z.Loader.extend({
  load: function(req){
    var contents = fs.readFileSync(req.url, 'utf-8')
      // Wrap the contents in a function that we can pass stuff to.
      // This is done to get around the fact that node doesn't have any globals,
      // so z will be undefined if we try to load it normally.
      , zModule;

    try{
      zModule = Function('z, imports, define', contents);
      zModule(z, imports, define);
    } catch(e) {
      console.log('  ...building failed, wrapping file');
      // Stick it in a wrapper and hope for the best!
      zModule = Function('z, imports, define', 'z(function(){'+ contents +'});');
      zModule(z, imports, define);
    }

    this._resolve(true, 1);
  }
});

z.plugin('script', ScriptMock, function(req, res, next, error){
  var name = req.from;
  console.log('  ...building: ', name);

  z.ensureModule(name);
  next();
});

/**
 * Mock ajax loading
 */
AjaxMock = z.Loader.extend({
  load: function(req){
    var name = req.from
      , contents = fs.readFileSync(req.url, 'utf-8');

    this._value = contents;

    this._resolve(contents, 1);
  }
});

z.plugin('ajax', AjaxMock, function(req, res, next, error){
  var name = req.from;

  console.log('  ...added file: ', name);

  if(res){
    var factory = "z('"+name+"').exports(function(){ return '" + escape(res) + "'; });";
    var zModule = Function('z, imports, define', factory);

    // Run the module.
    zModule(z, imports, exports, define);
  } else {
    console.log('  Warning: result was empty. Continuing, but be sure this is desired...')
  }

  next();
}, {
  ext: 'txt'
});

var escapes = {
      "'" : "'",
      '\\': '\\',
      '\r': 'r',
      '\n': 'n',
      '\t': 't',
      '\u2028': 'u2028',
      '\u2029': 'u2029'
    }
  , escaper = /\\|'|\r|\n|\t|\u2028|\u2029/g;
var escape = function(txt){
  return txt.replace(escaper, function(match) { return '\\' + escapes[match]; });
}

/**
 * ----------------------------------------------------------------------
 * z.Build
 *
 * Fit's module-compiler, used to crunch things into a single file.
 *
 * Fit projects ideally should be built before they are made live --
 * the module system should be used mainly as a development tool.
 */
var Build = function(options){

  var defaults = {
    optimize: false,
    main: 'main',
    buildfile: false,
    shim: {
      'jquery' : function(mod){
        var compile = mod._factory.toString();
        return '/*jquery*/\njquery: (' + compile + ')(),\n\n'
      }
    }
  };

  this.options = z.u.defaults(defaults, options);
}

Build.prototype.setup = function(options){
  this.options = z.u.defaults(this.options, options);
}

Build.prototype.start = function(main, dest, next){
  var self = this
    , mainName = main.replace('.js', '').replace(/\//g, '.');

  if(this.options.buildfile){
    try{
      var options = require(main)
        , paths = z.u.extract(['main', 'dest'], options);
      main = paths.src;
      dest = paths.dest;
      this.setup(options);
    } catch(e){
      console.log('Could not find a buildz.js file.\nTry providing a main file (e.g. \'main.js\') or make sure you are in the correct dir.')
      return false;
    }
  }

  z.setup({
    env: 'server'
  });

  console.log('\nbuilding...\n');

  var loader = new ScriptMock({url: main});

  loader.done(function(){
    if(self.options.main){
      mainName = self.options.main;
    }

    z.ensureModule(mainName);

    console.log('  ...main module found');
    console.log('  ...added: ', mainName);

    if(self.options.project === undefined){
      self.options.project = main.replace('.js', '').replace(/\//g, '.');
    }

    z(mainName).done(function(){
      var rendered = self.render(dest);
      if(z.u.isFunction(next)){
        next(rendered);
      }
    });
  }, function(e){
    console.error(e);
  });

}

Build.prototype.compile = function(name){

  if(false === z.has(name)){
    console.log('   The module '+ name + 'was loaded, but is not in the registry.')
    console.log('   continuing...');
    console.log();
    return;
  }

  var mod = z(name)
    , rendered = ''
    , deps = mod._deps
    , factory;

  if(mod._factory){
    factory = mod._factory.toString();
  } else {
    console.log('   Warning: factory not found for module: '+ name);
    console.log('   Continuing...')
  }

  if(this.options.shim.hasOwnProperty(name)){
    rendered = this.options.shim[name](mod);
    return rendered;
  }

  // Build the compiled string.
  rendered = '/*'+name+'*/\n';
  rendered += "z('"+name+"').\n";

  // Build imports.
  if(deps.length > 0){
    var imps = [];
    z.u(deps).each(function(dep){
      var imp = 'imports(';
      imp += "'" + dep.from + "'"
      if(dep.uses){
        if(z.u.isArray(dep.uses)){
          imp += ", ['" + dep.uses.join("', '") + "']";
        } else {
          imp += ", '" + dep.uses + "'";
        }
      } else {
        imp += ", '*'"
      }
      if(dep.options && dep.options.type !== 'script'){
        imp += ", " +  JSON.stringify(dep.options);
      }
      imp += ").\n";
      imps.push(imp);
    });
    rendered += imps.join('');
  }

  rendered += 'exports(' + factory + ');\n\n';

  return rendered;
}

Build.prototype.renderZ = function(){
  // Eventually make this configurable.
  var root = __dirname + '/../'
    , rendered = '';

  z.u([
    "src/util_large.js",
    // "src/util_small.js",
    "src/core.js",
    "src/Class.js",
    "src/Module.js",
    "src/Plugable.js",
    "src/Loader.js", // Should be optional
    "src/Script.js", // Should be optional
    "src/Ajax.js",   // Should be optional
    "src/amd.js",
    "src/plugins.js"
  ]).each(function(file){
    rendered += fs.readFileSync(root + file, 'utf-8') + "\n\n";
  });

  rendered = '\n/*! zjs */\n' + UglifyJS.minify(rendered, {fromString: true}).code;

  return rendered;
}

Build.prototype.render = function(dest){

  var self = this
    , rendered = '';

  // Check to see if the user defined any build settings in
  // z.setup. This could include compile shims, destinations, etc.
  if(z.config.build){
    z.u.extend(this.options, z.config.build);
    delete z.config.build;
  }

  dest = (dest || this.options.dest);

  if(!dest){
    console.log('No destination!');
    console.log('Set [dest] in one of the following ways:');
    console.log('   console: $ zjs build [src] [dest]');
    console.log('   in main.js: z.setup({ ... build:{ ... dest:[dest]... } ... });');
    console.log('   in zbuild.js: ... dest:[dest] ...');
    console.log('No files have been modified.');
    return;
  }

  z.u.each(z.modules, function(mod, name){
    rendered += self.compile(name);
  });

  rendered = '/* modules */\n\n' + rendered + '\n';

  delete z.config.env;

  // Add the config
  rendered = '\n\nz.setup(' + JSON.stringify(z.config) + ');\n\n' + rendered;

  // Add z itself to the top of the compiled project
  rendered = '(function(root){\n' + this.renderZ() + rendered + '\n})(window);';

  if(this.options.optimize){
    console.log('\nuglifying...');
    rendered = UglifyJS.minify(rendered, {fromString: true}).code;
    console.log('\n  ...uglified');
  }

  if(this.options.about){
    this.options.project += '\n' + this.options.about;
  }

  rendered = '/*'+this.options.project+'*/\n' + rendered;

  fs.writeFileSync(dest, rendered);

  console.log('\nbuilt to '+ dest + '\n');

  if(typeof next == 'function'){
    next(dest, rendered);
  }

  return rendered;
}

// Export instance of build.
exports = module.exports = new Build;

// Export Build
exports.Build = Build
<|endoftext|>


<commit_before>import React from 'react';
import ImmutablePropTypes from 'react-immutable-proptypes';
import PropTypes from 'prop-types';
import { is } from 'immutable';
import IconButton from './icon_button';
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
import { isIOS } from '../is_mobile';
import classNames from 'classnames';
import { autoPlayGif } from '../initial_state';

const messages = defineMessages({
  toggle_visible: { id: 'media_gallery.toggle_visible', defaultMessage: 'Toggle visibility' },
});

class Item extends React.PureComponent {

  static contextTypes = {
    router: PropTypes.object,
  };

  static propTypes = {
    attachment: ImmutablePropTypes.map.isRequired,
    standalone: PropTypes.bool,
    index: PropTypes.number.isRequired,
    size: PropTypes.number.isRequired,
    onClick: PropTypes.func.isRequired,
    pawooMaxWidth: PropTypes.string,
  };

  static defaultProps = {
    standalone: false,
    index: 0,
    size: 1,
  };

  handleMouseEnter = (e) => {
    if (this.hoverToPlay()) {
      e.target.play();
    }
  }

  handleMouseLeave = (e) => {
    if (this.hoverToPlay()) {
      e.target.pause();
      e.target.currentTime = 0;
    }
  }

  hoverToPlay () {
    const { attachment } = this.props;
    return !autoPlayGif && attachment.get('type') === 'gifv';
  }

  handleClick = (e) => {
    const { index, onClick } = this.props;

    if (this.context.router && e.button === 0) {
      e.preventDefault();
      onClick(index);
    }

    e.stopPropagation();
  }

  render () {
    const { attachment, index, size, standalone, pawooMaxWidth } = this.props;

    let width  = 50;
    let height = 100;
    let top    = 'auto';
    let left   = 'auto';
    let bottom = 'auto';
    let right  = 'auto';

    if (size === 1) {
      width = 100;
    }

    if (size === 4 || (size === 3 && index > 0)) {
      height = 50;
    }

    if (size === 2) {
      if (index === 0) {
        right = '2px';
      } else {
        left = '2px';
      }
    } else if (size === 3) {
      if (index === 0) {
        right = '2px';
      } else if (index > 0) {
        left = '2px';
      }

      if (index === 1) {
        bottom = '2px';
      } else if (index > 1) {
        top = '2px';
      }
    } else if (size === 4) {
      if (index === 0 || index === 2) {
        right = '2px';
      }

      if (index === 1 || index === 3) {
        left = '2px';
      }

      if (index < 2) {
        bottom = '2px';
      } else {
        top = '2px';
      }
    }

    let thumbnail = '';

    if (attachment.get('type') === 'image') {
      const previewUrl = attachment.get('preview_url');
      const previewWidth = attachment.getIn(['meta', 'small', 'width']);

      const originalUrl = attachment.get('url');
      const originalWidth = attachment.getIn(['meta', 'original', 'width']);

      const hasSize = typeof originalWidth === 'number' && typeof previewWidth === 'number';

      const srcSet = hasSize ? `${originalUrl} ${originalWidth}w, ${previewUrl} ${previewWidth}w` : null;
      const sizes = hasSize ? `(min-width: 1025px) ${320 * (width / 100)}px, ${width}vw` : null;

      thumbnail = (

      );
    } else if (attachment.get('type') === 'gifv') {
      const autoPlay = !isIOS() && autoPlayGif;

      thumbnail = (

          <video
            className='media-gallery__item-gifv-thumbnail'
            aria-label={attachment.get('description')}
            role='application'
            src={attachment.get('url')}
            onClick={this.handleClick}
            onMouseEnter={this.handleMouseEnter}
            onMouseLeave={this.handleMouseLeave}
            autoPlay={autoPlay}
            loop
            muted
          />

          GIF

      );
    }

    return (
      <div className={classNames('media-gallery__item', { standalone })} key={attachment.get('id')} style={{ left: left, top: top, right: right, bottom: bottom, width: `${width}%`, height: `${height}%`, maxWidth: pawooMaxWidth }}>
        {thumbnail}

    );
  }

}

@injectIntl
export default class MediaGallery extends React.PureComponent {

  static propTypes = {
    sensitive: PropTypes.bool,
    standalone: PropTypes.bool,
    media: ImmutablePropTypes.list.isRequired,
    size: PropTypes.object,
    onOpenMedia: PropTypes.func.isRequired,
    intl: PropTypes.object.isRequired,
    pawooOnClick: PropTypes.func,
    pawooScale: PropTypes.string.isRequired,
    pawooWide: PropTypes.bool,
  };

  static defaultProps = {
    standalone: false,
  };

  state = {
    visible: !this.props.sensitive,
  };

  componentWillReceiveProps (nextProps) {
    if (!is(nextProps.media, this.props.media)) {
      this.setState({ visible: !nextProps.sensitive });
    }
  }

  handleOpen = () => {
    this.setState({ visible: !this.state.visible });
  }

  handleClick = (index) => {
    this.props.onOpenMedia(this.props.media, index);
  }

  handleRef = (node) => {
    if (node && this.isStandaloneEligible()) {
      // offsetWidth triggers a layout, so only calculate when we need to
      this.setState({
        width: node.offsetWidth,
      });
    }
  }

  isStandaloneEligible() {
    const { media, standalone } = this.props;
    return standalone && media.size === 1 && media.getIn([0, 'meta', 'small', 'aspect']);
  }

  render () {
    const { media, intl, sensitive, pawooOnClick, pawooScale, pawooWide } = this.props;
    const { width, visible } = this.state;

    let pawooMaxWidth;
    let children;

    const style = {};

    if (this.isStandaloneEligible()) {
      if (!visible && width) {
        // only need to forcibly set the height in "sensitive" mode
        style.height = width / this.props.media.getIn([0, 'meta', 'small', 'aspect']);
      } else {
        // layout automatically, using image's natural aspect ratio
        style.height = '';
      }
    } else if (media.size === 1) {
      // crop the image
      if (!this.state.visible) {
        style.height = pawooScale;
      } else if (pawooWide) {
        pawooMaxWidth = pawooScale;
        style.height = `calc(${pawooScale}/${Math.max(media.getIn([0, 'meta', 'small', 'aspect']), 1)})`;
      } else {
        pawooMaxWidth = `calc(${pawooScale}*${Math.max(media.getIn([0, 'meta', 'small', 'aspect']), 1)})`;
        style.height = pawooScale;
      }
    } else {
      style.height = pawooWide && media.size < 3 ? `calc(${pawooScale}/2)` : pawooScale;
    }

    if (!visible) {
      let warning;

      if (sensitive) {
        warning = <FormattedMessage id='status.sensitive_warning' defaultMessage='Sensitive content' />;
      } else {
        warning = <FormattedMessage id='status.media_hidden' defaultMessage='Media hidden' />;
      }

      children = (
        <button type='button' className='media-spoiler' onClick={this.handleOpen} style={style} ref={this.handleRef}>
          {warning}
          <FormattedMessage id='status.sensitive_toggle' defaultMessage='Click to view' />
        </button>
      );
    } else {
      const size = media.take(4).size;

      if (this.isStandaloneEligible()) {
        children = <Item standalone onClick={this.handleClick} attachment={media.get(0)} pawooMaxWidth={pawooMaxWidth} />;
      } else {
        children = media.take(4).map((attachment, i) => <Item key={attachment.get('id')} onClick={this.handleClick} attachment={attachment} index={i} size={size} pawooMaxWidth={pawooMaxWidth} />);
      }
    }

    return (

        {pawooOnClick && !sensitive &&
}

          <IconButton title={intl.formatMessage(messages.toggle_visible)} icon={visible ? 'eye' : 'eye-slash'} overlay onClick={this.handleOpen} />

        {children}

    );
  }

}
<commit_msg>Fix media-spoiler<commit_after>import React from 'react';
import ImmutablePropTypes from 'react-immutable-proptypes';
import PropTypes from 'prop-types';
import { is } from 'immutable';
import IconButton from './icon_button';
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
import { isIOS } from '../is_mobile';
import classNames from 'classnames';
import { autoPlayGif } from '../initial_state';

const messages = defineMessages({
  toggle_visible: { id: 'media_gallery.toggle_visible', defaultMessage: 'Toggle visibility' },
});

class Item extends React.PureComponent {

  static contextTypes = {
    router: PropTypes.object,
  };

  static propTypes = {
    attachment: ImmutablePropTypes.map.isRequired,
    standalone: PropTypes.bool,
    index: PropTypes.number.isRequired,
    size: PropTypes.number.isRequired,
    onClick: PropTypes.func.isRequired,
    pawooMaxWidth: PropTypes.string,
  };

  static defaultProps = {
    standalone: false,
    index: 0,
    size: 1,
  };

  handleMouseEnter = (e) => {
    if (this.hoverToPlay()) {
      e.target.play();
    }
  }

  handleMouseLeave = (e) => {
    if (this.hoverToPlay()) {
      e.target.pause();
      e.target.currentTime = 0;
    }
  }

  hoverToPlay () {
    const { attachment } = this.props;
    return !autoPlayGif && attachment.get('type') === 'gifv';
  }

  handleClick = (e) => {
    const { index, onClick } = this.props;

    if (this.context.router && e.button === 0) {
      e.preventDefault();
      onClick(index);
    }

    e.stopPropagation();
  }

  render () {
    const { attachment, index, size, standalone, pawooMaxWidth } = this.props;

    let width  = 50;
    let height = 100;
    let top    = 'auto';
    let left   = 'auto';
    let bottom = 'auto';
    let right  = 'auto';

    if (size === 1) {
      width = 100;
    }

    if (size === 4 || (size === 3 && index > 0)) {
      height = 50;
    }

    if (size === 2) {
      if (index === 0) {
        right = '2px';
      } else {
        left = '2px';
      }
    } else if (size === 3) {
      if (index === 0) {
        right = '2px';
      } else if (index > 0) {
        left = '2px';
      }

      if (index === 1) {
        bottom = '2px';
      } else if (index > 1) {
        top = '2px';
      }
    } else if (size === 4) {
      if (index === 0 || index === 2) {
        right = '2px';
      }

      if (index === 1 || index === 3) {
        left = '2px';
      }

      if (index < 2) {
        bottom = '2px';
      } else {
        top = '2px';
      }
    }

    let thumbnail = '';

    if (attachment.get('type') === 'image') {
      const previewUrl = attachment.get('preview_url');
      const previewWidth = attachment.getIn(['meta', 'small', 'width']);

      const originalUrl = attachment.get('url');
      const originalWidth = attachment.getIn(['meta', 'original', 'width']);

      const hasSize = typeof originalWidth === 'number' && typeof previewWidth === 'number';

      const srcSet = hasSize ? `${originalUrl} ${originalWidth}w, ${previewUrl} ${previewWidth}w` : null;
      const sizes = hasSize ? `(min-width: 1025px) ${320 * (width / 100)}px, ${width}vw` : null;

      thumbnail = (

      );
    } else if (attachment.get('type') === 'gifv') {
      const autoPlay = !isIOS() && autoPlayGif;

      thumbnail = (

          <video
            className='media-gallery__item-gifv-thumbnail'
            aria-label={attachment.get('description')}
            role='application'
            src={attachment.get('url')}
            onClick={this.handleClick}
            onMouseEnter={this.handleMouseEnter}
            onMouseLeave={this.handleMouseLeave}
            autoPlay={autoPlay}
            loop
            muted
          />

          GIF

      );
    }

    return (
      <div className={classNames('media-gallery__item', { standalone })} key={attachment.get('id')} style={{ left: left, top: top, right: right, bottom: bottom, width: `${width}%`, height: `${height}%`, maxWidth: pawooMaxWidth }}>
        {thumbnail}

    );
  }

}

@injectIntl
export default class MediaGallery extends React.PureComponent {

  static propTypes = {
    sensitive: PropTypes.bool,
    standalone: PropTypes.bool,
    media: ImmutablePropTypes.list.isRequired,
    size: PropTypes.object,
    onOpenMedia: PropTypes.func.isRequired,
    intl: PropTypes.object.isRequired,
    pawooOnClick: PropTypes.func,
    pawooScale: PropTypes.string.isRequired,
    pawooWide: PropTypes.bool,
  };

  static defaultProps = {
    standalone: false,
  };

  state = {
    visible: !this.props.sensitive,
  };

  componentWillReceiveProps (nextProps) {
    if (!is(nextProps.media, this.props.media)) {
      this.setState({ visible: !nextProps.sensitive });
    }
  }

  handleOpen = () => {
    this.setState({ visible: !this.state.visible });
  }

  handleClick = (index) => {
    this.props.onOpenMedia(this.props.media, index);
  }

  handleRef = (node) => {
    if (node && this.isStandaloneEligible()) {
      // offsetWidth triggers a layout, so only calculate when we need to
      this.setState({
        width: node.offsetWidth,
      });
    }
  }

  isStandaloneEligible() {
    const { media, standalone } = this.props;
    return standalone && media.size === 1 && media.getIn([0, 'meta', 'small', 'aspect']);
  }

  render () {
    const { media, intl, sensitive, pawooOnClick, pawooScale, pawooWide } = this.props;
    const { width, visible } = this.state;

    let pawooMaxWidth;
    let children;

    const style = {};

    if (this.isStandaloneEligible()) {
      if (!visible && width) {
        // only need to forcibly set the height in "sensitive" mode
        style.height = width / this.props.media.getIn([0, 'meta', 'small', 'aspect']);
      } else {
        // layout automatically, using image's natural aspect ratio
        style.height = '';
      }
    } else if (media.size === 1) {
      // crop the image
      if (!this.state.visible) {
        style.height = pawooScale;
      } else if (pawooWide) {
        pawooMaxWidth = pawooScale;
        style.height = `calc(${pawooScale}/${Math.max(media.getIn([0, 'meta', 'small', 'aspect']), 1)})`;
      } else {
        pawooMaxWidth = `calc(${pawooScale}*${Math.max(media.getIn([0, 'meta', 'small', 'aspect']), 1)})`;
        style.height = pawooScale;
      }
    } else {
      style.height = pawooWide && media.size < 3 ? `calc(${pawooScale}/2)` : pawooScale;
    }

    if (!visible) {
      let warning;

      if (sensitive) {
        warning = <FormattedMessage id='status.sensitive_warning' defaultMessage='Sensitive content' />;
      } else {
        warning = <FormattedMessage id='status.media_hidden' defaultMessage='Media hidden' />;
      }

      children = (
        <button type='button' className='media-spoiler' onClick={this.handleOpen} style={style} ref={this.handleRef}>
          {warning}
          <FormattedMessage id='status.sensitive_toggle' defaultMessage='Click to view' />
        </button>
      );
    } else {
      const size = media.take(4).size;

      if (this.isStandaloneEligible()) {
        children = <Item standalone onClick={this.handleClick} attachment={media.get(0)} pawooMaxWidth={pawooMaxWidth} />;
      } else {
        children = media.take(4).map((attachment, i) => <Item key={attachment.get('id')} onClick={this.handleClick} attachment={attachment} index={i} size={size} pawooMaxWidth={pawooMaxWidth} />);
      }
    }

    return (

        {pawooOnClick && visible &&
}

          <IconButton title={intl.formatMessage(messages.toggle_visible)} icon={visible ? 'eye' : 'eye-slash'} overlay onClick={this.handleOpen} />

        {children}

    );
  }

}
<|endoftext|>


<commit_before>
'use strict';

var sh = require('shelljs')
  , fs = require('fs')
  , Handlebars = require('handlebars')
  , Trello = require('./trello')
  , async = require('async')
  , slug = require('slug')
  , path = require('path')
  , kramed = require('kramed');

var registerHelpers = function() {
  // We'll provide a `markdown` helper but do before registering uerland helpers
  // so they  can override use if desired
  Handlebars.registerHelper('markdown', function(markup, options) {
    return new Handlebars.SafeString(kramed(markup));
  });

  sh.ls('./src/helpers/*.js').forEach(function(f) {
    var helperName = path.basename(f, '.js')
      , helper = require(path.join(process.cwd(), f));
    Handlebars.registerHelper(helperName, helper);
  });
};

module.exports = function(opts, cb) {
  registerHelpers();
  sh.rm('-rf', 'dist');
  sh.mkdir('dist');
  fs.readFile('./src/index.hbs', function(err, data) {
    if(err) { return cb(err); }
    var cardTplSrc = data.toString()
      , cardTpl = Handlebars.compile(cardTplSrc);

    var t = new Trello(opts.trelloApiKey, opts.trelloToken);

    t.getBoardCards(opts.trelloBoardUrl, function(err, cards) {
      if(err) { return cb(err); }

      // Don't really want to force the use to install a markdow compiler...
      // just augment card data here?

      async.each(cards,
        function(card, done) {
          fs.writeFile('dist/' + card.pos + '-' + slug(card.name).toLowerCase() + '.html', cardTpl(card), done)
        },
        function(err) {
          cb(err);
        });
    });
  });
  cb();
};
<commit_msg>style: Kill outdated comment<commit_after>
'use strict';

var sh = require('shelljs')
  , fs = require('fs')
  , Handlebars = require('handlebars')
  , Trello = require('./trello')
  , async = require('async')
  , slug = require('slug')
  , path = require('path')
  , kramed = require('kramed');

var registerHelpers = function() {
  // We'll provide a `markdown` helper but do before registering uerland helpers
  // so they  can override use if desired
  Handlebars.registerHelper('markdown', function(markup, options) {
    return new Handlebars.SafeString(kramed(markup));
  });

  sh.ls('./src/helpers/*.js').forEach(function(f) {
    var helperName = path.basename(f, '.js')
      , helper = require(path.join(process.cwd(), f));
    Handlebars.registerHelper(helperName, helper);
  });
};

module.exports = function(opts, cb) {
  registerHelpers();
  sh.rm('-rf', 'dist');
  sh.mkdir('dist');
  fs.readFile('./src/index.hbs', function(err, data) {
    if(err) { return cb(err); }
    var cardTplSrc = data.toString()
      , cardTpl = Handlebars.compile(cardTplSrc);

    var t = new Trello(opts.trelloApiKey, opts.trelloToken);

    t.getBoardCards(opts.trelloBoardUrl, function(err, cards) {
      if(err) { return cb(err); }
      async.each(cards,
        function(card, done) {
          fs.writeFile('dist/' + card.pos + '-' + slug(card.name).toLowerCase() + '.html', cardTpl(card), done)
        },
        function(err) {
          cb(err);
        });
    });
  });
  cb();
};
<|endoftext|>


<commit_before>import React, { Component, Fragment } from 'react';
import { connect } from 'react-redux';
import { FormattedMessage } from 'react-intl';
import classnames from 'classnames';
import _ from 'lodash';
import StepContent from '../../components/Stepper/StepContent';
import StepWrapper from '../../components/Stepper/StepWrapper';
import AmountSelection from '../../components/AmountSelection/AmountSelection';
import MemberDetailsForm from '../../components/MemberDetailsForm/MemberDetailsForm';
import Payment from '../../components/Payment/Payment';
import OneClick from '../../components/OneClick/OneClick';
import Cookie from 'js-cookie';
import { localCurrencies } from './utils';
import unintendedDonationsExperiment from '../../experiments/unintended-donations';
import { setExperimentVariant } from '../../state/experiments';
import { resetMember } from '../../state/member/reducer';
import {
  changeAmount,
  changeCurrency,
  changeStep,
  setSelectedAmountButton,
  setSubmitting,
  setSupportedLocalCurrency,
  setIsCustomAmount,
} from '../../state/fundraiser/actions';

export class FundraiserView extends Component {
  constructor(props) {
    super(props);

    this.state = {
      isLoaded: false,
    };
  }

  async componentDidMount() {
    if (window.dataLayer) {
      await window.dataLayer.push({
        event: unintendedDonationsExperiment.activationEvent,
      });
    }
    this.intervalId = setInterval(() => {
      if (window.google_optimize !== undefined) {
        const variant = window.google_optimize.get(
          unintendedDonationsExperiment.experimentId
        );

        if (variant) {
          this.props.setExperimentVariant({
            variant,
            experimentId: unintendedDonationsExperiment.experimentId,
          });
        }
        clearInterval(this.intervalId);
        this.setState({
          isLoaded: true,
        });
      } else {
        this.setState({
          isLoaded: true,
        });
      }
    }, 500);

    const { donationAmount } = this.props.fundraiser;
    this.props.setSupportedLocalCurrency(this.supportedLocalCurrency());
    if (donationAmount && donationAmount > 0) {
      this.props.selectAmount(donationAmount);
      this.props.changeStep(1);
    }
  }

  componentDidUpdate(prevProps) {
    if (
      prevProps.experiments.length == 0 &&
      this.props.experiments.length > 0
    ) {
      const { variant } =
        this.props.experiments.find(
          e => (e.experimentId = unintendedDonationsExperiment.experimentId)
        ) || {};

      if (variant && variant === '1' && this.props.idMismatch) {
        const { donationAmount } = this.props.fundraiser;
        this.props.resetMember();
        if (donationAmount && donationAmount > 0) {
          this.props.selectAmount(donationAmount);
          this.props.changeStep(1);
        } else {
          this.props.changeStep(0);
        }
      }
    }
  }

  selectAmount(amount) {
    this.props.selectAmount(amount);
    const userId =
      window.champaign.personalization.member.id || Cookie.get('__bpmx');
    if (typeof window.fbq === 'function') {
      window.fbq('track', 'InitiateCheckout', {
        value: amount,
        currency: this.props.fundraiser.currency,
        content_name: this.props.page.title,
        content_ids: [this.props.page.id],
        content_type: 'product',
        user_id: userId,
        product_catalog_id: 445876772724152,
        page_id: this.props.page.id,
      });
    }
  }

  supportedLocalCurrency() {
    const country = window.champaign.personalization.member.country;
    // Member is in a country where we are not concerned of confusion in currencies when we display the currency sign
    // - this is currently any country outside of Lat Am
    if (!Object.keys(localCurrencies).includes(country)) return true;
    return localCurrencies[country] === this.props.fundraiser.currency;
  }

  proceed() {
    this.props.changeStep(this.props.fundraiser.currentStep + 1);
  }

  showStepTwo() {
    const { outstandingFields } = this.props.fundraiser;
    return !outstandingFields || outstandingFields.length !== 0;
  }

  render() {
    const {
      fundraiser: {
        formId,
        fields,
        formValues,
        donationBands,
        donationAmount,
        donationFeaturedAmount,
        currency,
        currentStep,
        outstandingFields,
        submitting,
        oneClickError,
        supportedLocalCurrency,
      },
    } = this.props;

    // todo move this into AmountSelection (connect it to store)
    const firstStepButtonTitle = _.isEmpty(formValues) ? (
      <FormattedMessage
        id="fundraiser.proceed_to_details"
        defaultMessage="Proceed to details (default)"
      />
    ) : (
      <FormattedMessage
        id="fundraiser.proceed_to_payment"
        defaultMessage="Proceed to payment (default)"
      />
    );

    const classNames = classnames({
      'FundraiserView-container': true,
      'form--big': true,
    });

    const oneClickErrorMessage = oneClickError ? (

        <FormattedMessage
          id="fundraiser.one_click_failed"
          defaultMessage="We're sorry but we could not process your donation. Please try again with a different card"
        />

    ) : null;

    const supportedCurrencyDisclaimer = !supportedLocalCurrency ? (

        <FormattedMessage
          id="fundraiser.currency_disclaimer"
          defaultMessage="Hello! We're working hard to soon be able to accept donations in your local currency. Meanwhile, we appreciate your patience — your donation will be processed in the foreign currency you select. Please note that your credit card might impose fees which SumOfUs has no control of. We truly appreciate your understanding."
        />

    ) : null;

    if (this.props.oneClickDonate) {
      return (

          <OneClick />

      );
    }

    return (
      <Fragment>
        {this.state.isLoaded ? (

            <StepWrapper
              title={this.props.fundraiser.title}
              submitting={submitting}
              currentStep={currentStep}
              changeStep={this.props.changeStep}
            >
              <StepContent
                title={AmountSelection.title(donationAmount, currency)}
              >

                  {oneClickErrorMessage}
                  {supportedCurrencyDisclaimer}
                  <AmountSelection
                    donationAmount={donationAmount}
                    currency={currency}
                    donationBands={donationBands}
                    donationFeaturedAmount={donationFeaturedAmount}
                    nextStepTitle={firstStepButtonTitle}
                    changeCurrency={this.props.selectCurrency.bind(this)}
                    selectAmount={amount => this.selectAmount(amount)}
                    setSelectedAmountButton={this.props.setSelectedAmountButton.bind(
                      this
                    )}
                    proceed={this.proceed.bind(this)}
                    setIsCustomAmount={this.props.setIsCustomAmount.bind(this)}
                  />

              </StepContent>

              {this.showStepTwo() && (
                <StepContent
                  title={<FormattedMessage id="fundraiser.details" />}
                >
                  <MemberDetailsForm
                    buttonText={
                      <FormattedMessage
                        id="fundraiser.proceed_to_payment"
                        defaultMessage="Proceed to payment"
                      />
                    }
                    fields={fields}
                    outstandingFields={outstandingFields}
                    formValues={formValues}
                    formId={formId}
                    pageId={this.props.page.id}
                    proceed={this.proceed.bind(this)}
                  />
                </StepContent>
              )}

              <StepContent title={<FormattedMessage id="fundraiser.payment" />}>
                <Payment
                  page={this.props.page}
                  disableFormReveal={this.showStepTwo()}
                  setSubmitting={s => this.props.setSubmitting(s)}
                />
              </StepContent>
            </StepWrapper>

        ) : null}
      </Fragment>
    );
  }
}

export const mapStateToProps = state => ({
  paymentMethods: state.paymentMethods,
  features: state.features,
  fundraiser: state.fundraiser,
  member: state.member,
  page: state.page,
  oneClickError: state.fundraiser.oneClickError,
  oneClickDonate:
    state.fundraiser.oneClick &&
    state.paymentMethods.length > 0 &&
    !state.fundraiser.disableSavedPayments,
  supportedLocalCurrency: state.fundraiser.supportedLocalCurrency,
  experiments: state.abTests.experiments,
  idMismatch: state.fundraiser.id_mismatch,
});

export const mapDispatchToProps = dispatch => ({
  resetMember: () => dispatch(resetMember()),
  changeStep: step => dispatch(changeStep(step)),
  selectAmount: amount => dispatch(changeAmount(amount)),
  setSelectedAmountButton: selectedButton =>
    dispatch(setSelectedAmountButton(selectedButton)),
  selectCurrency: currency => dispatch(changeCurrency(currency)),
  setSubmitting: submitting => dispatch(setSubmitting(submitting)),
  setSupportedLocalCurrency: value =>
    dispatch(setSupportedLocalCurrency(value)),
  setExperimentVariant: value => dispatch(setExperimentVariant(value)),
  setIsCustomAmount: (isCustomAmount, amount) =>
    dispatch(setIsCustomAmount(isCustomAmount, amount)),
});

export default connect(mapStateToProps, mapDispatchToProps)(FundraiserView);
<commit_msg>checking currency<commit_after>import React, { Component, Fragment } from 'react';
import { connect } from 'react-redux';
import { FormattedMessage } from 'react-intl';
import classnames from 'classnames';
import _ from 'lodash';
import StepContent from '../../components/Stepper/StepContent';
import StepWrapper from '../../components/Stepper/StepWrapper';
import AmountSelection from '../../components/AmountSelection/AmountSelection';
import MemberDetailsForm from '../../components/MemberDetailsForm/MemberDetailsForm';
import Payment from '../../components/Payment/Payment';
import OneClick from '../../components/OneClick/OneClick';
import Cookie from 'js-cookie';
import { localCurrencies } from './utils';
import unintendedDonationsExperiment from '../../experiments/unintended-donations';
import { setExperimentVariant } from '../../state/experiments';
import { resetMember } from '../../state/member/reducer';
import {
  changeAmount,
  changeCurrency,
  changeStep,
  setSelectedAmountButton,
  setSubmitting,
  setSupportedLocalCurrency,
  setIsCustomAmount,
} from '../../state/fundraiser/actions';
import { supportedCurrency } from '../../state/fundraiser/reducer';

export class FundraiserView extends Component {
  constructor(props) {
    super(props);

    this.state = {
      isLoaded: false,
    };
  }

  async componentDidMount() {
    if (window.dataLayer) {
      await window.dataLayer.push({
        event: unintendedDonationsExperiment.activationEvent,
      });
    }
    this.intervalId = setInterval(() => {
      if (window.google_optimize !== undefined) {
        const variant = window.google_optimize.get(
          unintendedDonationsExperiment.experimentId
        );

        if (variant) {
          this.props.setExperimentVariant({
            variant,
            experimentId: unintendedDonationsExperiment.experimentId,
          });
        }
        clearInterval(this.intervalId);
        this.setState({
          isLoaded: true,
        });
      } else {
        this.setState({
          isLoaded: true,
        });
      }
    }, 500);

    const { donationAmount } = this.props.fundraiser;
    this.props.setSupportedLocalCurrency(this.supportedLocalCurrency());
    if (donationAmount && donationAmount > 0) {
      this.props.selectAmount(donationAmount);

      const urlParams = new URLSearchParams(window.location.search);
      const currencyFromUrl = urlParams.get('currency');
      const currencyFromState = this.props.supportedCurrency(currencyFromUrl);
      const invalidCurrency =
        currencyFromState === 'USD' && currencyFromState !== currencyFromUrl;
      console.log('invalidCurrency', invalidCurrency);
      console.log('currencyFromState', currencyFromState);
      if (invalidCurrency === false) {
        this.props.changeStep(1);
      }
    }
  }

  componentDidUpdate(prevProps) {
    if (
      prevProps.experiments.length == 0 &&
      this.props.experiments.length > 0
    ) {
      const { variant } =
        this.props.experiments.find(
          e => (e.experimentId = unintendedDonationsExperiment.experimentId)
        ) || {};

      if (variant && variant === '1' && this.props.idMismatch) {
        const { donationAmount } = this.props.fundraiser;
        this.props.resetMember();
        if (donationAmount && donationAmount > 0) {
          this.props.selectAmount(donationAmount);
          this.props.changeStep(1);
        } else {
          this.props.changeStep(0);
        }
      }
    }
  }

  selectAmount(amount) {
    this.props.selectAmount(amount);
    const userId =
      window.champaign.personalization.member.id || Cookie.get('__bpmx');
    if (typeof window.fbq === 'function') {
      window.fbq('track', 'InitiateCheckout', {
        value: amount,
        currency: this.props.fundraiser.currency,
        content_name: this.props.page.title,
        content_ids: [this.props.page.id],
        content_type: 'product',
        user_id: userId,
        product_catalog_id: 445876772724152,
        page_id: this.props.page.id,
      });
    }
  }

  supportedLocalCurrency() {
    const country = window.champaign.personalization.member.country;
    // Member is in a country where we are not concerned of confusion in currencies when we display the currency sign
    // - this is currently any country outside of Lat Am
    if (!Object.keys(localCurrencies).includes(country)) return true;
    return localCurrencies[country] === this.props.fundraiser.currency;
  }

  proceed() {
    this.props.changeStep(this.props.fundraiser.currentStep + 1);
  }

  showStepTwo() {
    const { outstandingFields } = this.props.fundraiser;
    return !outstandingFields || outstandingFields.length !== 0;
  }

  render() {
    const {
      fundraiser: {
        formId,
        fields,
        formValues,
        donationBands,
        donationAmount,
        donationFeaturedAmount,
        currency,
        currentStep,
        outstandingFields,
        submitting,
        oneClickError,
        supportedLocalCurrency,
      },
    } = this.props;

    // todo move this into AmountSelection (connect it to store)
    const firstStepButtonTitle = _.isEmpty(formValues) ? (
      <FormattedMessage
        id="fundraiser.proceed_to_details"
        defaultMessage="Proceed to details (default)"
      />
    ) : (
      <FormattedMessage
        id="fundraiser.proceed_to_payment"
        defaultMessage="Proceed to payment (default)"
      />
    );

    const classNames = classnames({
      'FundraiserView-container': true,
      'form--big': true,
    });

    const oneClickErrorMessage = oneClickError ? (

        <FormattedMessage
          id="fundraiser.one_click_failed"
          defaultMessage="We're sorry but we could not process your donation. Please try again with a different card"
        />

    ) : null;

    const supportedCurrencyDisclaimer = !supportedLocalCurrency ? (

        <FormattedMessage
          id="fundraiser.currency_disclaimer"
          defaultMessage="Hello! We're working hard to soon be able to accept donations in your local currency. Meanwhile, we appreciate your patience — your donation will be processed in the foreign currency you select. Please note that your credit card might impose fees which SumOfUs has no control of. We truly appreciate your understanding."
        />

    ) : null;

    if (this.props.oneClickDonate) {
      return (

          <OneClick />

      );
    }

    return (
      <Fragment>
        {this.state.isLoaded ? (

            <StepWrapper
              title={this.props.fundraiser.title}
              submitting={submitting}
              currentStep={currentStep}
              changeStep={this.props.changeStep}
            >
              <StepContent
                title={AmountSelection.title(donationAmount, currency)}
              >

                  {oneClickErrorMessage}
                  {supportedCurrencyDisclaimer}
                  <AmountSelection
                    donationAmount={donationAmount}
                    currency={currency}
                    donationBands={donationBands}
                    donationFeaturedAmount={donationFeaturedAmount}
                    nextStepTitle={firstStepButtonTitle}
                    changeCurrency={this.props.selectCurrency.bind(this)}
                    selectAmount={amount => this.selectAmount(amount)}
                    setSelectedAmountButton={this.props.setSelectedAmountButton.bind(
                      this
                    )}
                    proceed={this.proceed.bind(this)}
                    setIsCustomAmount={this.props.setIsCustomAmount.bind(this)}
                  />

              </StepContent>

              {this.showStepTwo() && (
                <StepContent
                  title={<FormattedMessage id="fundraiser.details" />}
                >
                  <MemberDetailsForm
                    buttonText={
                      <FormattedMessage
                        id="fundraiser.proceed_to_payment"
                        defaultMessage="Proceed to payment"
                      />
                    }
                    fields={fields}
                    outstandingFields={outstandingFields}
                    formValues={formValues}
                    formId={formId}
                    pageId={this.props.page.id}
                    proceed={this.proceed.bind(this)}
                  />
                </StepContent>
              )}

              <StepContent title={<FormattedMessage id="fundraiser.payment" />}>
                <Payment
                  page={this.props.page}
                  disableFormReveal={this.showStepTwo()}
                  setSubmitting={s => this.props.setSubmitting(s)}
                />
              </StepContent>
            </StepWrapper>

        ) : null}
      </Fragment>
    );
  }
}

export const mapStateToProps = state => ({
  paymentMethods: state.paymentMethods,
  features: state.features,
  fundraiser: state.fundraiser,
  member: state.member,
  page: state.page,
  oneClickError: state.fundraiser.oneClickError,
  oneClickDonate:
    state.fundraiser.oneClick &&
    state.paymentMethods.length > 0 &&
    !state.fundraiser.disableSavedPayments,
  supportedLocalCurrency: state.fundraiser.supportedLocalCurrency,
  experiments: state.abTests.experiments,
  idMismatch: state.fundraiser.id_mismatch,
});

export const mapDispatchToProps = dispatch => ({
  resetMember: () => dispatch(resetMember()),
  changeStep: step => dispatch(changeStep(step)),
  selectAmount: amount => dispatch(changeAmount(amount)),
  setSelectedAmountButton: selectedButton =>
    dispatch(setSelectedAmountButton(selectedButton)),
  selectCurrency: currency => dispatch(changeCurrency(currency)),
  setSubmitting: submitting => dispatch(setSubmitting(submitting)),
  setSupportedLocalCurrency: value =>
    dispatch(setSupportedLocalCurrency(value)),
  setExperimentVariant: value => dispatch(setExperimentVariant(value)),
  setIsCustomAmount: (isCustomAmount, amount) =>
    dispatch(setIsCustomAmount(isCustomAmount, amount)),
  supportedCurrency: currency => dispatch(supportedCurrency(currency)),
});

export default connect(mapStateToProps, mapDispatchToProps)(FundraiserView);
<|endoftext|>