<commit_before>/*
    sfu.js
    SFU-specific client-side modifications for Canvas

    SFU require.js modules are located in $CANVAS_ROOT/public/javascripts/sfu-modules

 */

(function($) {

    var utils = {

        onPage: function(regex, fn) {
          if (location.pathname.match(regex)) fn();
        },

        hasAnyRole: function(/*roles, cb*/) {
          var roles = [].slice.call(arguments, 0);
          var cb = roles.pop();
          for (var i = 0; i < arguments.length; i++) {
            if (ENV.current_user_roles.indexOf(arguments[i]) !== -1) {
              return cb(true);
            }
          }
          return cb(false);
        },

        isUser: function(id, cb) {
          cb(ENV.current_user_id == id);
        },

        onElementRendered: function(selector, cb, _attempts) {
          var el = $(selector);
          _attempts = ++_attempts || 1;
          if (el.length) return cb(el);
          if (_attempts == 60) return;
          setTimeout(function() {
            utils.onElementRendered(selector, cb, _attempts);
          }, 250);
        }

    }

    // header rainbow
    $('#header').append('
');

    // help links
    var helpHtml = [
        '
',
        '<select class="sfu_help_links">',
        '<option value="">Help</option>',
        '<option value="https://canvas.sfu.ca/courses/14504">Help for Students</option>',
        '<option value="https://canvas.sfu.ca/courses/14686">Help for Instructors</option>',
        '<option value="http://www.sfu.ca/techforum">Q&A Forum</option>',
        '
'
    ].join('');
    $('#topbar .logout').before(helpHtml);
    $('#topbar .sfu_help_links').on('change', function(ev) {
        if (this.value) {
            window.location = this.value;
        }
    });

    // sfu logo in footer
    $('footer').html('').show();

    // handle no-user case
    if ($('#header').hasClass('no-user')) {
        // add in a dummy #menu div
        $('#header-inner').append('

');
        // remove the register link
        $('#header.no-user a[href="/register"]').parent().remove()
    }

    // hijack Start New Course button (CANVAS-192)
    // first, cache the original event handler and disable it
    function hijackStartNewCourseButton() {
        if (!jQuery._data(document, "events")) {
            // bit of a hack for IE which seems to randomly not have the events
            // loaded by the time this script loads
            window.setTimeout(hijackStartNewCourseButton, 100);
        } else {
            var eventlist = jQuery._data( document, "events" ).click,
                targetSelector = '.element_toggler[aria-controls]',
                origHandler, e;
            // cache the handler
            for (var i = 0; i < eventlist.length; i++) {
                e = eventlist[i];
                if (e.selector === targetSelector) {
                    origHandler = e.handler;
                }
            }
            if (origHandler) {
                // remove the handler, and add our own
                $(document).off('click change', targetSelector).on('click change', targetSelector, function(event) {
                    if (this.id === 'start_new_course') {
                        event.stopImmediatePropagation();
                        window.location = '/sfu/course/new';
                    } else {
                        origHandler.call(this, event);
                    }
                });
            }
        }
    }
    $(document).ready(function() {
        hijackStartNewCourseButton();
    });

    // END CANVAS-192

    /*  Add copyright compliance notice to the publish course button
        When a course page loads, check to see if the course is unpublished.
        If so, first immediately disable the publish button to allow time for the bundle to be required async.
        Then load the CANVAS_ROOT/public/javascripts/sfu-modules/copyright_notice_modal_dialog bundle
        This bundle handles attaching a click handler to the submit button (and re-enabling it).
        The click handler renders the SFUCopyrightComplianceNoticeModalDialog react component.

        If the course is published, nothing happens.
    */
    utils.onPage(/^\/courses\/\d+$/, function() {
        var $publishButton = $('.btn-publish')
        if ($publishButton.length) {
            $publishButton.attr('disabled', true);
            require(['sfu-modules/copyright_notice_modal_dialog'], function(module) {
                module.attachClickHandlerTo(location.pathname.replace('/courses/', 'edit_course_'));
            });
        }
    });

    /*  Add PIA notice to Google Docs section on /courses/DDDDD/collaborations
        When the collaboration page loads, load the google_docs_pia_notice bundle
        In the bundle, check the current user's role within the current course and
        display the appropriate message.
    */
    utils.onPage(/^\/courses\/\d+\/collaborations\/?$/, function () {
        require(['sfu-modules/google_docs_pia_notice'], function(module) {
            module.showGoogleDocsWarning();
        });
    });

    // Fixes for Import Content page only
    utils.onPage(/courses\/\d+\/content_migrations/, function() {
        // The fixes are for elements that are dynamically generated when a specific XHR call completes
        $(document).ajaxComplete(function (event, XMLHttpRequest, ajaxOptions) {
            if (ajaxOptions.url && ajaxOptions.url.match(/users\/\d+\/manageable_courses/)) {
                // Alphabetize course drop-down list, by sorting <option>s inside each <optgroup>
                // NOTE: This is no longer needed when XHR results are pre-sorted by Canvas
                $('optgroup', $('#courseSelect')).each(function (i, termGroup) {
                    $('option', termGroup)
                        .sort(function (a, b) { return $(a).text().localeCompare($(b).text()); })
                        .appendTo(termGroup);
                });
                // END Alphabetize course drop-down list
            }
        });
    });

    // Fix for the new conversations page - toolbar renders underneath the rainbow bar
    utils.onPage(/conversations/, function() {
        // are we on the new conversations page?
        if (ENV.CONVERSATIONS && (ENV.CONVERSATIONS.ATTACHMENTS_FOLDER_ID && !ENV.hasOwnProperty('CONTEXT_ACTION_SOURCE'))) {
            jQuery('div#main').css('top', '92px');
        }
    });

    // CANVAS-246 Create button that links to the Start a New Ad Hoc Space form (only on these pages: / and /courses)
    utils.onPage(/^\/(courses)?$/, function () {
        // Add the button right after the existing Start a New Course button
        var addAdHocButton = function () {
            return; // TODO: Remove this line when Ad Hoc Spaces are ready
            var $courseButton = $('#start_new_course');
            var $adhocButton = $courseButton.clone();
            $adhocButton
                .text('Start a New Ad Hoc Space')
                .attr('id', 'start_new_adhoc')
                .attr('aria-controls', 'new_adhoc_form')
                .insertAfter($courseButton)
                .on('click', function () {
                    window.location = '/sfu/adhoc/new';
                });
        }

        // If the button is not there yet, it's likely still being loaded in the sidebar.
        // Wait for it to complete, and then add the button. This is meant for the home page.
        if ($('#start_new_course').length == 0) {
            $(document).ajaxComplete(function (event, XMLHttpRequest, ajaxOptions) {
                if (ajaxOptions.url && ajaxOptions.url.match(/dashboard-sidebar/)) {
                    addAdHocButton();
                }
            });
        } else {
            addAdHocButton();
        }
    });
    // END CANVAS-246

    // CANVAS-252 Manage user profile names
    utils.onPage(/^\/profile\/settings\/?$/, function () {
        $(document).ready(function () {
            var $fieldsToLock = $('.full_name.display_data, .sortable_name.display_data');
            var $helpText = $('.short_name').siblings('span.edit_or_show_data');

            // CANVAS-253 Temporarily make full/sortable names read-only
            $fieldsToLock.removeClass('display_data').addClass('edit_or_show_data');
            $fieldsToLock.siblings('input').remove();

            // CANVAS-254 Add verbiage about Display Name
            $helpText.append('
Changing this will only affect your display name within Canvas, ' +
                'and not in other systems (e.g. goSFU, ' +
                'myInfo, etc.)');
        });
    });
    // END CANVAS-252

    // Add "Add People" button to Ad-Hoc Groups
    // We only want to add this button to groups in the ad-hoc group set
    utils.onPage(/^\/groups\/\d+\/users$/, function() {
        var groupId = /^\/groups\/(\d+)\/users$/.exec(window.location.pathname)[1];
        var buttonApiUrl = '/sfu/api/v1/adhoc_group_button/' + groupId;

        $.ajax({
            url: buttonApiUrl,
            success: function(html) {
                $('#right-side div').prepend(html);
                $('#addUsers').on('click', loadFrame);
            },
            error: function() { }
        });

        var loadFrame = function() {
            $('#addUsers').attr('disabled', 'true');
            var token = $('#addUsers').data('token');
            var iframe = $('', {
                name: 'adhoc_group_users_frame',
                id:   'adhoc_group_users_frame',
                src: 'https://canvas-group.sfu.ca/' + groupId + '/users?server=' + window.location.hostname + '&token=' + token,
                style: 'width:100%;min-height:500px;border:none'
            });
            $('#content').empty().append(iframe);
        };

    });

    // Setup Backbone event handler that will be called when all the content DOM elements
    // have been rendered. This will activate the accordion and tab components on the page.
    function setupAccordionAndTabActivation() {
        $.subscribe('userContent/change', function () {
            $("div.accordion").accordion({header: "h3"});
            $(".sfu-tabs").tabs();
        });
    }

    // On course and wiki pages, activate accordion and tab components, if they exist.
    utils.onPage(/^\/(courses|groups)\/\d+\/pages\/[A-Za-z0-9_\-+~<>]+$/, setupAccordionAndTabActivation);

    // A page designated as a front page has a different url.
    utils.onPage(/^\/(courses|groups)\/\d+\/wiki$/, setupAccordionAndTabActivation);

})(jQuery);

// google analytics
if (window.location.hostname && 'canvas.sfu.ca' === window.location.hostname) {
    var _gaq = _gaq || [];
    _gaq.push(['_setAccount', 'UA-36473171-1']);
    _gaq.push(['_trackPageview']);

    (function() {
        var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
        ga.src = ('https:' === document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
        var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
    })();
}

<commit_msg>add Canvas Spaces to nav if plugin enabled<commit_after>/*
    sfu.js
    SFU-specific client-side modifications for Canvas

    SFU require.js modules are located in $CANVAS_ROOT/public/javascripts/sfu-modules

 */

(function($) {

    var utils = {

        onPage: function(regex, fn) {
          if (location.pathname.match(regex)) fn();
        },

        hasAnyRole: function(/*roles, cb*/) {
          var roles = [].slice.call(arguments, 0);
          var cb = roles.pop();
          for (var i = 0; i < arguments.length; i++) {
            if (ENV.current_user_roles.indexOf(arguments[i]) !== -1) {
              return cb(true);
            }
          }
          return cb(false);
        },

        isUser: function(id, cb) {
          cb(ENV.current_user_id == id);
        },

        onElementRendered: function(selector, cb, _attempts) {
          var el = $(selector);
          _attempts = ++_attempts || 1;
          if (el.length) return cb(el);
          if (_attempts == 60) return;
          setTimeout(function() {
            utils.onElementRendered(selector, cb, _attempts);
          }, 250);
        }

    }

    // header rainbow
    $('#header').append('
');

    // help links
    var helpHtml = [
        '
',
        '<select class="sfu_help_links">',
        '<option value="">Help</option>',
        '<option value="https://canvas.sfu.ca/courses/14504">Help for Students</option>',
        '<option value="https://canvas.sfu.ca/courses/14686">Help for Instructors</option>',
        '<option value="http://www.sfu.ca/techforum">Q&A Forum</option>',
        '
'
    ].join('');
    $('#topbar .logout').before(helpHtml);
    $('#topbar .sfu_help_links').on('change', function(ev) {
        if (this.value) {
            window.location = this.value;
        }
    });

    // sfu logo in footer
    $('footer').html('').show();

    // handle no-user case
    if ($('#header').hasClass('no-user')) {
        // add in a dummy #menu div
        $('#header-inner').append('

');
        // remove the register link
        $('#header.no-user a[href="/register"]').parent().remove()
    }

    // add Canvas Spaces to nav
    $(document).ready(function() {
      if (!ENV.CANVAS_SPACES_ENABLED) { return; }
      $('#menu').append('
Canvas Spaces
')
    });

    // hijack Start New Course button (CANVAS-192)
    // first, cache the original event handler and disable it
    function hijackStartNewCourseButton() {
        if (!jQuery._data(document, "events")) {
            // bit of a hack for IE which seems to randomly not have the events
            // loaded by the time this script loads
            window.setTimeout(hijackStartNewCourseButton, 100);
        } else {
            var eventlist = jQuery._data( document, "events" ).click,
                targetSelector = '.element_toggler[aria-controls]',
                origHandler, e;
            // cache the handler
            for (var i = 0; i < eventlist.length; i++) {
                e = eventlist[i];
                if (e.selector === targetSelector) {
                    origHandler = e.handler;
                }
            }
            if (origHandler) {
                // remove the handler, and add our own
                $(document).off('click change', targetSelector).on('click change', targetSelector, function(event) {
                    if (this.id === 'start_new_course') {
                        event.stopImmediatePropagation();
                        window.location = '/sfu/course/new';
                    } else {
                        origHandler.call(this, event);
                    }
                });
            }
        }
    }
    $(document).ready(function() {
        hijackStartNewCourseButton();
    });

    // END CANVAS-192

    /*  Add copyright compliance notice to the publish course button
        When a course page loads, check to see if the course is unpublished.
        If so, first immediately disable the publish button to allow time for the bundle to be required async.
        Then load the CANVAS_ROOT/public/javascripts/sfu-modules/copyright_notice_modal_dialog bundle
        This bundle handles attaching a click handler to the submit button (and re-enabling it).
        The click handler renders the SFUCopyrightComplianceNoticeModalDialog react component.

        If the course is published, nothing happens.
    */
    utils.onPage(/^\/courses\/\d+$/, function() {
        var $publishButton = $('.btn-publish')
        if ($publishButton.length) {
            $publishButton.attr('disabled', true);
            require(['sfu-modules/copyright_notice_modal_dialog'], function(module) {
                module.attachClickHandlerTo(location.pathname.replace('/courses/', 'edit_course_'));
            });
        }
    });

    /*  Add PIA notice to Google Docs section on /courses/DDDDD/collaborations
        When the collaboration page loads, load the google_docs_pia_notice bundle
        In the bundle, check the current user's role within the current course and
        display the appropriate message.
    */
    utils.onPage(/^\/courses\/\d+\/collaborations\/?$/, function () {
        require(['sfu-modules/google_docs_pia_notice'], function(module) {
            module.showGoogleDocsWarning();
        });
    });

    // Fixes for Import Content page only
    utils.onPage(/courses\/\d+\/content_migrations/, function() {
        // The fixes are for elements that are dynamically generated when a specific XHR call completes
        $(document).ajaxComplete(function (event, XMLHttpRequest, ajaxOptions) {
            if (ajaxOptions.url && ajaxOptions.url.match(/users\/\d+\/manageable_courses/)) {
                // Alphabetize course drop-down list, by sorting <option>s inside each <optgroup>
                // NOTE: This is no longer needed when XHR results are pre-sorted by Canvas
                $('optgroup', $('#courseSelect')).each(function (i, termGroup) {
                    $('option', termGroup)
                        .sort(function (a, b) { return $(a).text().localeCompare($(b).text()); })
                        .appendTo(termGroup);
                });
                // END Alphabetize course drop-down list
            }
        });
    });

    // Fix for the new conversations page - toolbar renders underneath the rainbow bar
    utils.onPage(/conversations/, function() {
        // are we on the new conversations page?
        if (ENV.CONVERSATIONS && (ENV.CONVERSATIONS.ATTACHMENTS_FOLDER_ID && !ENV.hasOwnProperty('CONTEXT_ACTION_SOURCE'))) {
            jQuery('div#main').css('top', '92px');
        }
    });

    // CANVAS-246 Create button that links to the Start a New Ad Hoc Space form (only on these pages: / and /courses)
    utils.onPage(/^\/(courses)?$/, function () {
        // Add the button right after the existing Start a New Course button
        var addAdHocButton = function () {
            return; // TODO: Remove this line when Ad Hoc Spaces are ready
            var $courseButton = $('#start_new_course');
            var $adhocButton = $courseButton.clone();
            $adhocButton
                .text('Start a New Ad Hoc Space')
                .attr('id', 'start_new_adhoc')
                .attr('aria-controls', 'new_adhoc_form')
                .insertAfter($courseButton)
                .on('click', function () {
                    window.location = '/sfu/adhoc/new';
                });
        }

        // If the button is not there yet, it's likely still being loaded in the sidebar.
        // Wait for it to complete, and then add the button. This is meant for the home page.
        if ($('#start_new_course').length == 0) {
            $(document).ajaxComplete(function (event, XMLHttpRequest, ajaxOptions) {
                if (ajaxOptions.url && ajaxOptions.url.match(/dashboard-sidebar/)) {
                    addAdHocButton();
                }
            });
        } else {
            addAdHocButton();
        }
    });
    // END CANVAS-246

    // CANVAS-252 Manage user profile names
    utils.onPage(/^\/profile\/settings\/?$/, function () {
        $(document).ready(function () {
            var $fieldsToLock = $('.full_name.display_data, .sortable_name.display_data');
            var $helpText = $('.short_name').siblings('span.edit_or_show_data');

            // CANVAS-253 Temporarily make full/sortable names read-only
            $fieldsToLock.removeClass('display_data').addClass('edit_or_show_data');
            $fieldsToLock.siblings('input').remove();

            // CANVAS-254 Add verbiage about Display Name
            $helpText.append('
Changing this will only affect your display name within Canvas, ' +
                'and not in other systems (e.g. goSFU, ' +
                'myInfo, etc.)');
        });
    });
    // END CANVAS-252

    // Add "Add People" button to Ad-Hoc Groups
    // We only want to add this button to groups in the ad-hoc group set
    utils.onPage(/^\/groups\/\d+\/users$/, function() {
        var groupId = /^\/groups\/(\d+)\/users$/.exec(window.location.pathname)[1];
        var buttonApiUrl = '/sfu/api/v1/adhoc_group_button/' + groupId;

        $.ajax({
            url: buttonApiUrl,
            success: function(html) {
                $('#right-side div').prepend(html);
                $('#addUsers').on('click', loadFrame);
            },
            error: function() { }
        });

        var loadFrame = function() {
            $('#addUsers').attr('disabled', 'true');
            var token = $('#addUsers').data('token');
            var iframe = $('', {
                name: 'adhoc_group_users_frame',
                id:   'adhoc_group_users_frame',
                src: 'https://canvas-group.sfu.ca/' + groupId + '/users?server=' + window.location.hostname + '&token=' + token,
                style: 'width:100%;min-height:500px;border:none'
            });
            $('#content').empty().append(iframe);
        };

    });

    // Setup Backbone event handler that will be called when all the content DOM elements
    // have been rendered. This will activate the accordion and tab components on the page.
    function setupAccordionAndTabActivation() {
        $.subscribe('userContent/change', function () {
            $("div.accordion").accordion({header: "h3"});
            $(".sfu-tabs").tabs();
        });
    }

    // On course and wiki pages, activate accordion and tab components, if they exist.
    utils.onPage(/^\/(courses|groups)\/\d+\/pages\/[A-Za-z0-9_\-+~<>]+$/, setupAccordionAndTabActivation);

    // A page designated as a front page has a different url.
    utils.onPage(/^\/(courses|groups)\/\d+\/wiki$/, setupAccordionAndTabActivation);

})(jQuery);

// google analytics
if (window.location.hostname && 'canvas.sfu.ca' === window.location.hostname) {
    var _gaq = _gaq || [];
    _gaq.push(['_setAccount', 'UA-36473171-1']);
    _gaq.push(['_trackPageview']);

    (function() {
        var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
        ga.src = ('https:' === document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
        var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
    })();
}

<|endoftext|>


<commit_before>/*
 * jitsu.js: Top-level include for the jitsu module.
 *
 * (C) 2010, Nodejitsu Inc.
 *
 */

var jitsu = exports;

var path = require('path');

// Failure HTTP Response codes based
// off of `/lib/broodmother/slave/service.js`
var failCodes = jitsu.failCodes = {
  400: 'Bad Request',
  401: 'Unauthorized',
  403: 'Not Authorized',
  404: 'Item not found',
  409: 'Conflict',
  500: 'Internal Server Error'
};

// Success HTTP Response codes based
// off of `/lib/broodmother/slave/service.js`
var successCodes = jitsu.successCodes = {
  200: 'OK',
  201: 'Created'
};

jitsu.skipAuth          = false;
jitsu.started           = false;
jitsu.utils             = require('./jitsu/utils');
jitsu.package           = require('./jitsu/package');
jitsu.log               = require('cliff');
jitsu.logFile           = new (require('./jitsu/utils/logfile').LogFile)(path.join(process.env.HOME, '.jitsulog'));
jitsu.api               = {};
jitsu.api.Client        = require('nodejitsu-api').Client;
jitsu.api.Apps          = require('nodejitsu-api').Apps;
jitsu.api.Databases     = require('nodejitsu-api').Databases;
jitsu.api.Logs          = require('nodejitsu-api').Logs;
jitsu.api.Snapshots     = require('nodejitsu-api').Snapshots;
jitsu.api.Users         = require('nodejitsu-api').Users;
jitsu.prompt            = require('prompt');
jitsu.prompt.properties = require('./jitsu/properties').properties;
jitsu.prompt.override   = require('optimist').argv;
jitsu.config            = require('./jitsu/config');
jitsu.commands          = require('./jitsu/commands');

//
// Alias the appropriate commands for simplier CLI usage
//
jitsu.commands.alias('create', { resource: 'apps',   command: 'create' });
jitsu.commands.alias('deploy', { resource: 'apps',   command: 'deploy' });
jitsu.commands.alias('list',   { resource: 'apps',   command: 'list' });
jitsu.commands.alias('conf',   { resource: 'config', command: 'list' });
jitsu.commands.alias('login',  { resource: 'users',  command: 'login' });
jitsu.commands.alias('logout', { resource: 'users',  command: 'logout' });
jitsu.commands.alias('signup', { resource: 'users',  command: 'create' });
jitsu.commands.alias('whoami', { resource: 'users',  command: 'whoami' });

var eyes = require('eyes'),
    winston = require('winston'),
    colors = require('colors');

//
// Setup `jitsu` to use `pkginfo` to expose version
//
require('pkginfo')(module, 'version');

//
// ### function start (command, callback)
// #### @command {string} Command to execute once started
// #### @callback {function} Continuation to pass control to when complete.
// Starts the jitsu CLI and runs the specified command.
//
jitsu.start = function (argv, callback) {
  var command = argv._;

  //
  // Special -v command for showing current version without winston formatting
  //
  if (argv.version || argv.v) {
    console.log('v' + jitsu.version);
    process.exit(0);
  }

  //
  // Check for --no-colors/--colors option, without hitting the config file
  // yet
  //
  (typeof argv.colors == "undefined" || argv.colors) || (colors.mode = "none");

  // Setup the initial prompt but don't leave it open
  jitsu.prompt.start().pause();

  //
  // Default to the `help` command.
  //
  command[0] || (command[0] = 'help');

  jitsu.utils.checkVersion(function (err) {
    if (err) return callback();
    jitsu.config.load(function (err) {
      if (err) {
        jitsu.welcome();
        callback(err);
        return jitsu.showError.apply(null, [command[0]].concat(arguments));
      }

      //
      // --no-colors option turns off output coloring, and so does setting
      // colors: false in ~/.jitsuconf (see
      // https://github.com/nodejitsu/jitsu/issues/101 )
      //
      jitsu.config.get('colors') || (colors.mode = "none");

      jitsu.welcome();

      var username = jitsu.config.get('username');
      if (!username && jitsu.commands.requiresAuth(command[0])) {
        return jitsu.setupUser(function (err) {
          if (err) {
            callback(err);
            return jitsu.showError.apply(jitsu, [command[0]].concat(arguments));
          }

          var username = jitsu.config.get('username');
          winston.info('Successfully configured user ' + username.magenta);
          return command.length > 0 ? jitsu.exec(command, callback) : callback();
        });
      }

      return jitsu.exec(command, callback)
    });
  });
};

//
// ### function welcome ()
// Print welcome message.
//
jitsu.welcome = function () {
  winston.info('Welcome to ' + 'Nodejitsu'.grey);
  winston.info('It worked if it ends with ' + 'Nodejitsu'.grey + ' ok'.green.bold);
};

//
// ### function exec (command, callback)
// #### @command {string} Command to execute
// #### @callback {function} Continuation to pass control to when complete.
// Runs the specified command in the jitsu CLI.
//
jitsu.exec = function (command, callback) {
  function execCommand (err) {
    if (err) {
      return callback(err);
    }

    //
    // Remark: This is a temporary fix for aliasing init=>install,
    // was having a few issues with the alias command on the install resource
    //
    if (command[0] === 'init'){
      command[0] = 'install';
    }

    // Alias db to databases
    if (command[0] === 'db'){
      command[0] = 'databases';
    }

    // Allow `jitsu logs` as a shortcut for `jitsu logs app`
    if (command[0] === 'logs' && command.length === 1) {
      command[1] = 'app';
    }

    winston.info('Executing command ' + command.join(' ').magenta);
    jitsu.commands.run(command, function (err, shallow) {
      if (err) {
        callback(err);
        return jitsu.showError(command.join(' '), err, shallow);
      }

      //
      // TODO (indexzero): Something here
      //
      callback();
    });
  }

  return !jitsu.started ? jitsu.setup(execCommand) : execCommand();
};

//
// ### function setup (callback)
// #### @callback {function} Continuation to pass control to when complete.
// Sets up the instances of the Resource clients for jitsu.
// there is no io here, yet this function is ASYNC.
//
jitsu.setup = function (callback) {
  if (jitsu.started === true) {
    return callback();
  }

  jitsu.users = new jitsu.api.Users(jitsu.config);
  jitsu.apps = new jitsu.api.Apps(jitsu.config);
  jitsu.snapshots = new jitsu.api.Snapshots(jitsu.config);
  jitsu.databases = new jitsu.api.Databases(jitsu.config);
  jitsu.logs = new jitsu.api.Logs(jitsu.config);
  jitsu.started = true;

  callback();
};

//
// ### function auth (callback)
// #### @callback {function} Continuation to pass control to when complete.
// Attempts to authenicate the user identified in `jitsu.config.get('username'|'password')`
//
jitsu.auth = function (callback) {
  if (jitsu.skipAuth) {
    return callback();
  }

  var username = jitsu.config.get('username');
  winston.silly('Attempting to authenticate as ' + username.magenta);
  jitsu.users.auth(function (err, success) {
    if (err || !success) {
      winston.error('Unable to Authenticate as ' + username.magenta);
      winston.error(err.message);
      return callback(err);
    }

    jitsu.skipAuth = true;
    winston.info('Authenticated as ' + username.magenta);
    return callback();
  })
}

//
// ### function setupUser (callback)
// #### @callback {function} Continuation to pass control to when complete.
// Prompts the user for their username / password combo, then sets up the jitsu CLI,
// and saves the resulting configuration to disk.
//
jitsu.setupUser = function (callback) {
  winston.warn('You will need to login to continue');
  winston.info('To login, you will need an activated nodejitsu account');
  winston.help('You can create an account using the ' + 'jitsu signup'.magenta + ' command');
  jitsu.setupUserNoWarn(callback);
};

//
// ### function setupUserNoWarn (callback)
// #### @callback {function} Continuation to pass control to when complete.
// Prompts the user for their username / password combo, then sets up the jitsu CLI,
// and saves the resulting configuration to disk without a warning
//
jitsu.setupUserNoWarn = function(callback) {
  //
  // Attempt to get the password three times.
  //
  var tries = 0;

  function offerReset (username) {
    jitsu.prompt.get(['reset'], function (err, res) {
      if (err) {
        return callback(err);
      }
      if (/^y[es]+/i.test(res['request password reset'])) {
        return jitsu.commands.run(['users', 'forgot', username], callback);
      }

      callback(new Error('Invalid username / password.'));
    });
  }

  (function setupAuth () {
    jitsu.prompt.get(['username', 'password'], function (err, result) {
      if (err) {
        return callback(err);
      }

      jitsu.config.set('username', result.username);
      jitsu.config.set('password', result.password);
      jitsu.setup(function () {
        jitsu.auth(function (err) {
          //
          // Increment the auth attempts
          //
          tries += 1;

          if (err) {
            if (tries >= 3) {
              winston.error('Three failed login attempts');
              winston.info('Would you like to reset your password?');
              return offerReset(result.username);
            }
            return setupAuth();
          }

          jitsu.config.save(function (err) {
            return err ? callback(err) : callback();
          });
        });
      });
    });
  })();
};

//
// ### function showError (command, err, shallow, skip)
// #### @command {string} Command which has errored.
// #### @err {Error} Error received for the command.
// #### @shallow {boolean} Value indicating if a deep stack should be displayed
// #### @skip {boolean} Value indicating if this error should be forcibly suppressed.
// Displays the `err` to the user for the `command` supplied.
//
jitsu.showError = function (command, err, shallow, skip) {
  if (err.statusCode !== '403' && !skip) {
    winston.error('Error running command ' + command.magenta);

    if (!jitsu.config.get('nolog')) {
      jitsu.logFile.log(err);
    }

    if (err.message) {
      winston.error(err.message);
    }

    if (err.result) {
      if (err.result.error) {
        winston.error(err.result.error);
      }

      if (err.result.result && err.result.result.error) {
        if (err.result.result.error.stderr) {
          winston.error('');
          winston.error('There was an error while attempting to start your application.');
          winston.error(err.result.result.error.message);
          if (err.result.result.error.blame) {
            winston.error(err.result.result.error.blame.message);
            winston.error('');
            winston.error('This type of error is usually a ' + err.result.result.error.blame.type + ' error.');
          }
          winston.error('Error output from your application:');
          winston.error('');
          err.result.result.error.stderr.split('\n').forEach(function (line) {
            winston.error(line);
          })
        }
        else if (err.result.result.error.stack && jitsu.config.get('debug')) {
          winston.error('There was an error while attempting to deploy your application.');
          winston.error('');
          winston.error(err.result.result.error.message);
          if (err.result.result.error.blame) {
            winston.error(err.result.result.error.blame.message);
            winston.error('');
            winston.error('This type of error is usually a ' + err.result.result.error.blame.type + ' error.');
          }
          winston.error('Error output from Haibu:');
          winston.error('');
          err.result.result.error.stack.split('\n').forEach(function (line) {
            winston.error(line);
          });
        }
      }
      else if (err.result.stack) {
        winston.warn('Error returned from Nodejitsu');
        err.result.stack.split('\n').forEach(function (line) {
          winston.error(line);
        })
      }
    }
    else {
      if (err.stack && !shallow) {
        err.stack.split('\n').forEach(function (trace) {
          winston.error(trace);
        });
      }
    }
  }

  winston.info('Nodejitsu '.grey + 'not ok'.red.bold);
};
<commit_msg>[fix] Fix to properly display npm error output, when received.<commit_after>/*
 * jitsu.js: Top-level include for the jitsu module.
 *
 * (C) 2010, Nodejitsu Inc.
 *
 */

var jitsu = exports;

var path = require('path');

// Failure HTTP Response codes based
// off of `/lib/broodmother/slave/service.js`
var failCodes = jitsu.failCodes = {
  400: 'Bad Request',
  401: 'Unauthorized',
  403: 'Not Authorized',
  404: 'Item not found',
  409: 'Conflict',
  500: 'Internal Server Error'
};

// Success HTTP Response codes based
// off of `/lib/broodmother/slave/service.js`
var successCodes = jitsu.successCodes = {
  200: 'OK',
  201: 'Created'
};

jitsu.skipAuth          = false;
jitsu.started           = false;
jitsu.utils             = require('./jitsu/utils');
jitsu.package           = require('./jitsu/package');
jitsu.log               = require('cliff');
jitsu.logFile           = new (require('./jitsu/utils/logfile').LogFile)(path.join(process.env.HOME, '.jitsulog'));
jitsu.api               = {};
jitsu.api.Client        = require('nodejitsu-api').Client;
jitsu.api.Apps          = require('nodejitsu-api').Apps;
jitsu.api.Databases     = require('nodejitsu-api').Databases;
jitsu.api.Logs          = require('nodejitsu-api').Logs;
jitsu.api.Snapshots     = require('nodejitsu-api').Snapshots;
jitsu.api.Users         = require('nodejitsu-api').Users;
jitsu.prompt            = require('prompt');
jitsu.prompt.properties = require('./jitsu/properties').properties;
jitsu.prompt.override   = require('optimist').argv;
jitsu.config            = require('./jitsu/config');
jitsu.commands          = require('./jitsu/commands');

//
// Alias the appropriate commands for simplier CLI usage
//
jitsu.commands.alias('create', { resource: 'apps',   command: 'create' });
jitsu.commands.alias('deploy', { resource: 'apps',   command: 'deploy' });
jitsu.commands.alias('list',   { resource: 'apps',   command: 'list' });
jitsu.commands.alias('conf',   { resource: 'config', command: 'list' });
jitsu.commands.alias('login',  { resource: 'users',  command: 'login' });
jitsu.commands.alias('logout', { resource: 'users',  command: 'logout' });
jitsu.commands.alias('signup', { resource: 'users',  command: 'create' });
jitsu.commands.alias('whoami', { resource: 'users',  command: 'whoami' });

var eyes = require('eyes'),
    winston = require('winston'),
    colors = require('colors');

//
// Setup `jitsu` to use `pkginfo` to expose version
//
require('pkginfo')(module, 'version');

//
// ### function start (command, callback)
// #### @command {string} Command to execute once started
// #### @callback {function} Continuation to pass control to when complete.
// Starts the jitsu CLI and runs the specified command.
//
jitsu.start = function (argv, callback) {
  var command = argv._;

  //
  // Special -v command for showing current version without winston formatting
  //
  if (argv.version || argv.v) {
    console.log('v' + jitsu.version);
    process.exit(0);
  }

  //
  // Check for --no-colors/--colors option, without hitting the config file
  // yet
  //
  (typeof argv.colors == "undefined" || argv.colors) || (colors.mode = "none");

  // Setup the initial prompt but don't leave it open
  jitsu.prompt.start().pause();

  //
  // Default to the `help` command.
  //
  command[0] || (command[0] = 'help');

  jitsu.utils.checkVersion(function (err) {
    if (err) return callback();
    jitsu.config.load(function (err) {
      if (err) {
        jitsu.welcome();
        callback(err);
        return jitsu.showError.apply(null, [command[0]].concat(arguments));
      }

      //
      // --no-colors option turns off output coloring, and so does setting
      // colors: false in ~/.jitsuconf (see
      // https://github.com/nodejitsu/jitsu/issues/101 )
      //
      jitsu.config.get('colors') || (colors.mode = "none");

      jitsu.welcome();

      var username = jitsu.config.get('username');
      if (!username && jitsu.commands.requiresAuth(command[0])) {
        return jitsu.setupUser(function (err) {
          if (err) {
            callback(err);
            return jitsu.showError.apply(jitsu, [command[0]].concat(arguments));
          }

          var username = jitsu.config.get('username');
          winston.info('Successfully configured user ' + username.magenta);
          return command.length > 0 ? jitsu.exec(command, callback) : callback();
        });
      }

      return jitsu.exec(command, callback)
    });
  });
};

//
// ### function welcome ()
// Print welcome message.
//
jitsu.welcome = function () {
  winston.info('Welcome to ' + 'Nodejitsu'.grey);
  winston.info('It worked if it ends with ' + 'Nodejitsu'.grey + ' ok'.green.bold);
};

//
// ### function exec (command, callback)
// #### @command {string} Command to execute
// #### @callback {function} Continuation to pass control to when complete.
// Runs the specified command in the jitsu CLI.
//
jitsu.exec = function (command, callback) {
  function execCommand (err) {
    if (err) {
      return callback(err);
    }

    //
    // Remark: This is a temporary fix for aliasing init=>install,
    // was having a few issues with the alias command on the install resource
    //
    if (command[0] === 'init'){
      command[0] = 'install';
    }

    // Alias db to databases
    if (command[0] === 'db'){
      command[0] = 'databases';
    }

    // Allow `jitsu logs` as a shortcut for `jitsu logs app`
    if (command[0] === 'logs' && command.length === 1) {
      command[1] = 'app';
    }

    winston.info('Executing command ' + command.join(' ').magenta);
    jitsu.commands.run(command, function (err, shallow) {
      if (err) {
        callback(err);
        return jitsu.showError(command.join(' '), err, shallow);
      }

      //
      // TODO (indexzero): Something here
      //
      callback();
    });
  }

  return !jitsu.started ? jitsu.setup(execCommand) : execCommand();
};

//
// ### function setup (callback)
// #### @callback {function} Continuation to pass control to when complete.
// Sets up the instances of the Resource clients for jitsu.
// there is no io here, yet this function is ASYNC.
//
jitsu.setup = function (callback) {
  if (jitsu.started === true) {
    return callback();
  }

  jitsu.users = new jitsu.api.Users(jitsu.config);
  jitsu.apps = new jitsu.api.Apps(jitsu.config);
  jitsu.snapshots = new jitsu.api.Snapshots(jitsu.config);
  jitsu.databases = new jitsu.api.Databases(jitsu.config);
  jitsu.logs = new jitsu.api.Logs(jitsu.config);
  jitsu.started = true;

  callback();
};

//
// ### function auth (callback)
// #### @callback {function} Continuation to pass control to when complete.
// Attempts to authenicate the user identified in `jitsu.config.get('username'|'password')`
//
jitsu.auth = function (callback) {
  if (jitsu.skipAuth) {
    return callback();
  }

  var username = jitsu.config.get('username');
  winston.silly('Attempting to authenticate as ' + username.magenta);
  jitsu.users.auth(function (err, success) {
    if (err || !success) {
      winston.error('Unable to Authenticate as ' + username.magenta);
      winston.error(err.message);
      return callback(err);
    }

    jitsu.skipAuth = true;
    winston.info('Authenticated as ' + username.magenta);
    return callback();
  })
}

//
// ### function setupUser (callback)
// #### @callback {function} Continuation to pass control to when complete.
// Prompts the user for their username / password combo, then sets up the jitsu CLI,
// and saves the resulting configuration to disk.
//
jitsu.setupUser = function (callback) {
  winston.warn('You will need to login to continue');
  winston.info('To login, you will need an activated nodejitsu account');
  winston.help('You can create an account using the ' + 'jitsu signup'.magenta + ' command');
  jitsu.setupUserNoWarn(callback);
};

//
// ### function setupUserNoWarn (callback)
// #### @callback {function} Continuation to pass control to when complete.
// Prompts the user for their username / password combo, then sets up the jitsu CLI,
// and saves the resulting configuration to disk without a warning
//
jitsu.setupUserNoWarn = function(callback) {
  //
  // Attempt to get the password three times.
  //
  var tries = 0;

  function offerReset (username) {
    jitsu.prompt.get(['reset'], function (err, res) {
      if (err) {
        return callback(err);
      }
      if (/^y[es]+/i.test(res['request password reset'])) {
        return jitsu.commands.run(['users', 'forgot', username], callback);
      }

      callback(new Error('Invalid username / password.'));
    });
  }

  (function setupAuth () {
    jitsu.prompt.get(['username', 'password'], function (err, result) {
      if (err) {
        return callback(err);
      }

      jitsu.config.set('username', result.username);
      jitsu.config.set('password', result.password);
      jitsu.setup(function () {
        jitsu.auth(function (err) {
          //
          // Increment the auth attempts
          //
          tries += 1;

          if (err) {
            if (tries >= 3) {
              winston.error('Three failed login attempts');
              winston.info('Would you like to reset your password?');
              return offerReset(result.username);
            }
            return setupAuth();
          }

          jitsu.config.save(function (err) {
            return err ? callback(err) : callback();
          });
        });
      });
    });
  })();
};

//
// ### function showError (command, err, shallow, skip)
// #### @command {string} Command which has errored.
// #### @err {Error} Error received for the command.
// #### @shallow {boolean} Value indicating if a deep stack should be displayed
// #### @skip {boolean} Value indicating if this error should be forcibly suppressed.
// Displays the `err` to the user for the `command` supplied.
//
jitsu.showError = function (command, err, shallow, skip) {
  var stack;

  if (err.statusCode !== '403' && !skip) {
    winston.error('Error running command ' + command.magenta);

    if (!jitsu.config.get('nolog')) {
      jitsu.logFile.log(err);
    }

    if (err.message) {
      winston.error(err.message);
    }

    if (err.result) {
      if (err.result.error) {
        winston.error(err.result.error);
      }

      if (err.result.result && err.result.result.error) {
        if (err.result.result.error.stderr) {
          winston.error('');
          winston.error('There was an error while attempting to start your application.');
          winston.error(err.result.result.error.message);
          if (err.result.result.error.blame) {
            winston.error(err.result.result.error.blame.message);
            winston.error('');
            winston.error('This type of error is usually a ' + err.result.result.error.blame.type + ' error.');
          }
          winston.error('Error output from your application:');
          winston.error('');
          err.result.result.error.stderr.split('\n').forEach(function (line) {
            winston.error(line);
          })
        }
        else if (err.result.result.error.stack && jitsu.config.get('debug')) {
          winston.error('There was an error while attempting to deploy your application.');
          winston.error('');
          winston.error(err.result.result.error.message);
          if (err.result.result.error.blame) {
            winston.error(err.result.result.error.blame.message);
            winston.error('');
            winston.error('This type of error is usually a ' + err.result.result.error.blame.type + ' error.');
          }
          winston.error('Error output from Haibu:');
          winston.error('');
          stack = err.result.result.error.result || err.result.result.error.stack;
          stack.split('\n').forEach(function (line) {
            winston.error(line);
          });
        }
      }
      else if (err.result.stack) {
        winston.warn('Error returned from Nodejitsu');
        err.result.stack.split('\n').forEach(function (line) {
          winston.error(line);
        })
      }
    }
    else {
      if (err.stack && !shallow) {
        err.stack.split('\n').forEach(function (trace) {
          winston.error(trace);
        });
      }
    }
  }

  winston.info('Nodejitsu '.grey + 'not ok'.red.bold);
};
<|endoftext|>


<commit_before>window.Voucherify = (function (window, document, $) {
  "use strict";

  var API_BASE = "https://api.voucherify.io";

  var API = {
      validate: API_BASE + "/client/v1/validate",
      redeem:   API_BASE + "/client/v1/redeem",
  };

  var OPTIONS = {};

  var xhrImplementation = null;

  if (!!$ && typeof($.ajax) === "function" && !!$.Deferred) {
    xhrImplementation = function (method, url, payload, callback) {
      var deferred = null;

      if (typeof(callback) !== "function") {
        deferred = $.Deferred();
      }

      $.ajax({
        type: method,

        url: url,

        data: JSON.stringify(payload),

        xhrFields: {
          withCredentials: true
        },

        dataType: "json",
        headers: {
          "Accept": "application/json",
          "Content-Type": "application/json",
          "X-Client-Application-Id": OPTIONS.applicationId,
          "X-Client-Token": OPTIONS.token,
          "X-Voucherify-Channel": "Voucherify.js"
        },
        timeout: OPTIONS.timeout,

        success: function (data) {
          var result = null;

          if (data && (typeof(data.valid) === "boolean" || typeof(data.result) === "string")) {
            if (typeof(callback) === "function") {
              callback(data);
            } else {
              deferred.resolve(data);
            }
          } else {
            result = {
              type: "error",
              message: "Unexpected response structure.",
              context: data
            };

            if (typeof(callback) === "function") {
              callback(result);
            } else {
              deferred.reject(result);
            }
          }
        },

        error: function (error) {
          var result = {
            type: "error",
            message: "XHR error happened.",
            context: error
          };

          if (typeof(callback) === "function") {
            callback(result);
          } else {
            deferred.reject(result);
          }
        }
      });

      if (typeof(callback) !== "function") {
        return deferred.promise();
      } else {
        return undefined;
      }
    };
  } else {
    xhrImplementation = function (method, url, payload, callback) {
      var request = new window.XMLHttpRequest();

      request.withCredentials = true;
      request.open(method, url, true);
      request.timeout = OPTIONS.timeout;

      request.setRequestHeader("Accept", "application/json");
      request.setRequestHeader("Content-Type", "application/json");
      request.setRequestHeader("X-Client-Application-Id", OPTIONS.applicationId);
      request.setRequestHeader("X-Client-Token", OPTIONS.token);
      request.setRequestHeader("X-Voucherify-Channel", "Voucherify.js");

      request.onload = function() {
        var result = null;

        if (request.status >= 200 && request.status < 400) {
          var data = JSON.parse(request.responseText);

          if (data && (typeof(data.valid) === "boolean" || typeof(data.result) === "string")) {
            if (typeof(callback) === "function") {
              callback(data);
            }
          } else {
            result = {
              type: "error",
              message: "Unexpected response structure.",
              context: data
            };

            if (typeof(callback) === "function") {
              callback(result);
            }
          }
        } else {
          result = {
            type: "error",
            message: "Unexpected status code.",
            context: request.status
          };

          if (typeof(callback) === "function") {
            callback(result);
          }
        }
      };

      request.onerror = function (error) {
        var result = {
          type: "error",
          message: "XHR error happened.",
          context: error
        };

        if (typeof(callback) === "function") {
          callback(result);
        }
      };

      request.send(JSON.stringify(payload));
    };
  }

  function roundMoney(value) {
    return Math.round(value * (100 + 0.001)) / 100;
  }

  function validatePercentDiscount(discount) {
    if (!discount || discount < 0 || discount > 100) {
      throw new Error('Invalid voucher, percent discount should be between 0-100.');
    }
  }

  function validateAmountDiscount(discount) {
    if (!discount || discount < 0) {
      throw new Error("Invalid voucher, amount discount must be higher than zero.");
    }
  }

  function validateUnitDiscount(discount) {
    if (!discount || discount < 0) {
      throw new Error("Invalid voucher, unit discount must be higher than zero.");
    }
  }

  var voucherify = {
    initialize: function (clientAppId, token, timeout) {
      OPTIONS.applicationId = clientAppId;
      OPTIONS.token = token;
      OPTIONS.timeout = timeout || 5000;
    },

    setIdentity: function (trackingId) {
      OPTIONS.trackingId = trackingId;
    },

    validate: function (code, callback) {
      if (!OPTIONS.applicationId && !OPTIONS.token) {
        console.error("Voucherify client could not verify coupon - Lack of configuration - Missing Client Application ID or Token.");
        return null;
      }

      var amount;
      var items;

      if (typeof(code) === "object") {
        amount = code.amount;
        items = code.items;
        code = code.code;
      }

      if (!!code) {
        code = code.replace(/[\s\r\n]/g, "");
      }

      if (!code) {
        console.error("Voucherify client could not verify code, because it is missing - please provide Voucher Code.");
        return null;
      }

      var queryString = "?code=" + encodeURIComponent(code);

      if (amount) {
        queryString += "&amount=" + parseInt(amount); // in cents, amount=1000 means $10
      }

      if (items) {
        queryString += "&" + items.map(function(item, index) {
          return Object.keys(item).map(function(key) {
            return encodeURIComponent("item[" + index + "][" + key + "]") + "=" + encodeURIComponent(item[key]);
          }).join("&");
        }).join("&");
      }

      if (OPTIONS.trackingId) {
        queryString += "&tracking_id=" + encodeURIComponent(OPTIONS.trackingId);
      }

      return xhrImplementation("GET", API.validate + queryString, undefined, callback);
    },

    redeem: function (code, callback) {
      if (!OPTIONS.applicationId && !OPTIONS.token) {
        console.error("Voucherify client could not redeem coupon - Lack of configuration - Missing Client Application ID or Token.");
        return null;
      }

      var payload = {};

      if (typeof(code) === "object") {
        payload.order = {};

        if (code.amount) {
          payload.order.amount = code.amount;
        }

        if (code.items) {
          payload.order.items = code.items;
        }

        code = code.code;
      }

      if (OPTIONS.trackingId) {
        payload.customer = {
          source_id: OPTIONS.trackingId
        }
      }

      if (!!code) {
        code = code.replace(/[\s\r\n]/g, "");
      }

      if (!code) {
        console.error("Voucherify client could not verify code, because it is missing - please provide Voucher Code.");
        return null;
      }

      var queryString = "?code=" + encodeURIComponent(code);

      return xhrImplementation("POST", API.redeem + queryString, payload, callback);
    },

    utils: {
      calculatePrice: function (basePrice, voucher, unitPrice) {
        var e = 100; // Number of digits after the decimal separator.
        var discount;

        if (voucher.gift) {
          discount = Math.min(voucher.gift.balance / e, basePrice);
          return roundMoney(basePrice - discount);
        }

        if (!voucher.discount) {
          throw new Error("Unsupported voucher type.");
        }

        if (voucher.discount.type === 'PERCENT') {
          discount = voucher.discount.percent_off;
          validatePercentDiscount(discount);
          var priceDiscount = basePrice * (discount / 100);
          return roundMoney(basePrice - priceDiscount);

        } else if (voucher.discount.type === 'AMOUNT') {
          discount = voucher.discount.amount_off / e;
          validateAmountDiscount(discount);
          var newPrice = basePrice - discount;
          return roundMoney(newPrice > 0 ? newPrice : 0);

        } else if (voucher.discount.type === 'UNIT') {
          discount = voucher.discount.unit_off;
          validateUnitDiscount(discount);
          var newPrice = basePrice - unitPrice * discount;
          return roundMoney(newPrice > 0 ? newPrice : 0);

        } else {
          throw new Error("Unsupported discount type.");
        }
      },

      calculateDiscount: function(basePrice, voucher, unitPrice) {
        var e = 100; // Number of digits after the decimal separator.
        var discount;

        if (voucher.gift) {
          discount = Math.min(voucher.gift.balance / e, basePrice);
          return roundMoney(discount);
        }

        if (!voucher.discount) {
          throw new Error("Unsupported voucher type.");
        }

        if (voucher.discount.type === 'PERCENT') {
          discount = voucher.discount.percent_off;
          validatePercentDiscount(discount);
          return roundMoney(basePrice * (discount / 100));

        } else if (voucher.discount.type === 'AMOUNT') {
          discount = voucher.discount.amount_off / e;
          validateAmountDiscount(discount);
          var newPrice = basePrice - discount;
          return roundMoney(newPrice > 0 ? discount : basePrice);

        } else if (voucher.discount.type === 'UNIT') {
          discount = voucher.discount.unit_off;
          validateUnitDiscount(discount);
          var priceDiscount = unitPrice * discount;
          return roundMoney(priceDiscount > basePrice ? basePrice : priceDiscount);

        } else {
          throw new Error("Unsupported discount type.");
        }
      }
    },
    render: function(selector, options) {
      var $element = $(selector);
      if (!$element || !$element.length) {
        throw new Error("Element '" + selector + "' cannot be found");
      }
      options = options || {};

      function getCapitalizedName(name) {
        return name.charAt(0).toUpperCase() + name.slice(1);
      }

      function getPropertyName(prefix, name) {
        return prefix + getCapitalizedName(name);
      }

      function getConfigProperty(prefix, name) {
        return options[getPropertyName(prefix, name)];
      }

      function create$control(type, name, $container, config) {
        config = config || {};
        var $control = null;
        var configured$control = getConfigProperty("selector", name);

        if (config.configurable && configured$control) {
          $control = $(configured$control);
        }

        if (!$control || !$control.length) {
          $control = $(document.createElement(type));
          $container.append($control);

          for (var attribute in config) {
            if (attribute !== "configurable" && config.hasOwnProperty(attribute)) {
              $control.attr(attribute, config[attribute]);
            }
          }

          if (type === "input") {
            $control.attr("name", getPropertyName("voucherify", name));
          }

          if (type === "span" && config.text) {
            $control.text(config.text);
          }
        }

        $control.addClass(typeof getConfigProperty("class", name) === "string" ? getConfigProperty("class", name) : getPropertyName("voucherify", name));
        return $control;
      }

      var $container     = create$control("div", "container", $element);
      var $logoContainer = create$control("figure", "logo", $container);
      var $logo          = create$control("img", "logo", $logoContainer, { src: typeof options.logoSrc === "string" ? options.logoSrc : "https://app.voucherify.io/images/favicon.png" });
      var $code          = create$control("input", "code", $container, { type: "text", placeholder: typeof options.textPlaceholder === "string" ? options.textPlaceholder : "e.g. abc-123" });
      var $discountType  = create$control("input", "discountType", $container, { type: "hidden", configurable: true });
      var $percentOff    = create$control("input", "percentOff", $container, { type: "hidden", configurable: true });
      var $amountOff     = create$control("input", "amountOff", $container, { type: "hidden", configurable: true });
      var $unitOff       = create$control("input", "unitOff", $container, { type: "hidden", configurable: true });
      var $tracking      = create$control("input", "tracking", $container, { type: "hidden", configurable: true });
      var $validate      = create$control("button", "validate", $container, {});
      var $validateText  = create$control("span", "validateText", $validate, { text: typeof options.textValidate === "string" ? options.textValidate : "Validate" });

      var self = this;
      var classInvalid = options.classInvalid === "string" ? options.classInvalid : "voucherifyInvalid";
      var classValid = typeof options.classValid === "string" ? options.classValid : "voucherifyValid";
      var classInvalidAnimation = options.classInvalidAnimation === "string" ? options.classInvalidAnimation : "voucherifyAnimationShake";
      var classValidAnimation = options.classValidAnimation === "string" ? options.classValidAnimation : "voucherifyAnimationTada";

      $code.on("keyup", function(event) {
        $code.toggleClass(classInvalidAnimation, false);
      });

      $validate.on("click", function(event) {
        $discountType.val("");
        $amountOff.val("");
        $unitOff.val("");
        $percentOff.val("");
        $tracking.val("");

        $validate.toggleClass(classInvalid, false);
        $validate.toggleClass(classValid, false);

        if (!$code.val()) {
          $code.toggleClass(classInvalidAnimation, true)
              .delay(1000)
              .queue(function(){
                $code.toggleClass(classInvalidAnimation, false);
                $code.dequeue();
              });
          return;
        }

        self.validate($code.val(), function(response) {
          if (!response || !response.valid) {
            $validate.toggleClass(classInvalid, true);
            $validate.toggleClass(classValid, false);
            $code.toggleClass(classInvalid, true);
            $code.toggleClass(classValid, false);
            $code.toggleClass(classInvalidAnimation, true)
                .delay(1000)
                .queue(function(){
                  $code.toggleClass(classInvalidAnimation, false);
                  $code.dequeue();
                });
            return;
          }

          $code.toggleClass(classInvalid, false);
          $discountType.val(response.discount && response.discount.type || "");
          $amountOff.val(response.discount && response.discount.amount_off || 0);
          $unitOff.val(response.discount && response.discount.unit_off || 0);
          $percentOff.val(response.discount && response.discount.percent_off || 0);
          $tracking.val(response.tracking_id || "");

          $code.prop("disabled", true);
          $validate.prop("disabled", true);

          $code.toggleClass(classValid, true);
          $validate.toggleClass(classValid, true);
          $validate.toggleClass(classInvalid, false);
          $code.toggleClass(classInvalid, false);

          $code.toggleClass(classValidAnimation, true)

          if (options && options.onValidated && typeof options.onValidated === "function") {
            options.onValidated(response);
          }
        });
      });
    }
  };

  if (typeof module !== "undefined" && module.exports) {
    module.exports = voucherify;
  }

  return voucherify;
} (window, window.document, window.jQuery));
<commit_msg>Improve signature of redeem method<commit_after>window.Voucherify = (function (window, document, $) {
  "use strict";

  var API_BASE = "https://api.voucherify.io";

  var API = {
      validate: API_BASE + "/client/v1/validate",
      redeem:   API_BASE + "/client/v1/redeem",
  };

  var OPTIONS = {};

  var xhrImplementation = null;

  if (!!$ && typeof($.ajax) === "function" && !!$.Deferred) {
    xhrImplementation = function (method, url, payload, callback) {
      var deferred = null;

      if (typeof(callback) !== "function") {
        deferred = $.Deferred();
      }

      $.ajax({
        type: method,

        url: url,

        data: JSON.stringify(payload),

        xhrFields: {
          withCredentials: true
        },

        dataType: "json",
        headers: {
          "Accept": "application/json",
          "Content-Type": "application/json",
          "X-Client-Application-Id": OPTIONS.applicationId,
          "X-Client-Token": OPTIONS.token,
          "X-Voucherify-Channel": "Voucherify.js"
        },
        timeout: OPTIONS.timeout,

        success: function (data) {
          var result = null;

          if (data && (typeof(data.valid) === "boolean" || typeof(data.result) === "string")) {
            if (typeof(callback) === "function") {
              callback(data);
            } else {
              deferred.resolve(data);
            }
          } else {
            result = {
              type: "error",
              message: "Unexpected response structure.",
              context: data
            };

            if (typeof(callback) === "function") {
              callback(result);
            } else {
              deferred.reject(result);
            }
          }
        },

        error: function (error) {
          var result = {
            type: "error",
            message: "XHR error happened.",
            context: error
          };

          if (typeof(callback) === "function") {
            callback(result);
          } else {
            deferred.reject(result);
          }
        }
      });

      if (typeof(callback) !== "function") {
        return deferred.promise();
      } else {
        return undefined;
      }
    };
  } else {
    xhrImplementation = function (method, url, payload, callback) {
      var request = new window.XMLHttpRequest();

      request.withCredentials = true;
      request.open(method, url, true);
      request.timeout = OPTIONS.timeout;

      request.setRequestHeader("Accept", "application/json");
      request.setRequestHeader("Content-Type", "application/json");
      request.setRequestHeader("X-Client-Application-Id", OPTIONS.applicationId);
      request.setRequestHeader("X-Client-Token", OPTIONS.token);
      request.setRequestHeader("X-Voucherify-Channel", "Voucherify.js");

      request.onload = function() {
        var result = null;

        if (request.status >= 200 && request.status < 400) {
          var data = JSON.parse(request.responseText);

          if (data && (typeof(data.valid) === "boolean" || typeof(data.result) === "string")) {
            if (typeof(callback) === "function") {
              callback(data);
            }
          } else {
            result = {
              type: "error",
              message: "Unexpected response structure.",
              context: data
            };

            if (typeof(callback) === "function") {
              callback(result);
            }
          }
        } else {
          result = {
            type: "error",
            message: "Unexpected status code.",
            context: request.status
          };

          if (typeof(callback) === "function") {
            callback(result);
          }
        }
      };

      request.onerror = function (error) {
        var result = {
          type: "error",
          message: "XHR error happened.",
          context: error
        };

        if (typeof(callback) === "function") {
          callback(result);
        }
      };

      request.send(JSON.stringify(payload));
    };
  }

  function roundMoney(value) {
    return Math.round(value * (100 + 0.001)) / 100;
  }

  function validatePercentDiscount(discount) {
    if (!discount || discount < 0 || discount > 100) {
      throw new Error('Invalid voucher, percent discount should be between 0-100.');
    }
  }

  function validateAmountDiscount(discount) {
    if (!discount || discount < 0) {
      throw new Error("Invalid voucher, amount discount must be higher than zero.");
    }
  }

  function validateUnitDiscount(discount) {
    if (!discount || discount < 0) {
      throw new Error("Invalid voucher, unit discount must be higher than zero.");
    }
  }

  var voucherify = {
    initialize: function (clientAppId, token, timeout) {
      OPTIONS.applicationId = clientAppId;
      OPTIONS.token = token;
      OPTIONS.timeout = timeout || 5000;
    },

    setIdentity: function (trackingId) {
      OPTIONS.trackingId = trackingId;
    },

    validate: function (code, callback) {
      if (!OPTIONS.applicationId && !OPTIONS.token) {
        console.error("Voucherify client could not verify coupon - Lack of configuration - Missing Client Application ID or Token.");
        return null;
      }

      var amount;
      var items;

      if (typeof(code) === "object") {
        amount = code.amount;
        items = code.items;
        code = code.code;
      }

      if (!!code) {
        code = code.replace(/[\s\r\n]/g, "");
      }

      if (!code) {
        console.error("Voucherify client could not verify code, because it is missing - please provide Voucher Code.");
        return null;
      }

      var queryString = "?code=" + encodeURIComponent(code);

      if (amount) {
        queryString += "&amount=" + parseInt(amount); // in cents, amount=1000 means $10
      }

      if (items) {
        queryString += "&" + items.map(function(item, index) {
          return Object.keys(item).map(function(key) {
            return encodeURIComponent("item[" + index + "][" + key + "]") + "=" + encodeURIComponent(item[key]);
          }).join("&");
        }).join("&");
      }

      if (OPTIONS.trackingId) {
        queryString += "&tracking_id=" + encodeURIComponent(OPTIONS.trackingId);
      }

      return xhrImplementation("GET", API.validate + queryString, undefined, callback);
    },

    redeem: function (code, payload, callback) {
      if (!OPTIONS.applicationId && !OPTIONS.token) {
        console.error("Voucherify client could not redeem coupon - Lack of configuration - Missing Client Application ID or Token.");
        return null;
      }

      if (!code) {
        console.error("Voucherify client could not verify code, because it is missing - please provide Voucher Code.");
        return null;
      }

      var queryString = "?code=" + encodeURIComponent(code.replace(/[\s\r\n]/g, ""));

      // -- Tracking ID fallback
      payload = payload || {};
      payload.customer = payload.customer || {};
      payload.customer.source_id = payload.customer.source_id || OPTIONS.trackingId;

      return xhrImplementation("POST", API.redeem + queryString, payload, callback);
    },

    utils: {
      calculatePrice: function (basePrice, voucher, unitPrice) {
        var e = 100; // Number of digits after the decimal separator.
        var discount;

        if (voucher.gift) {
          discount = Math.min(voucher.gift.balance / e, basePrice);
          return roundMoney(basePrice - discount);
        }

        if (!voucher.discount) {
          throw new Error("Unsupported voucher type.");
        }

        if (voucher.discount.type === 'PERCENT') {
          discount = voucher.discount.percent_off;
          validatePercentDiscount(discount);
          var priceDiscount = basePrice * (discount / 100);
          return roundMoney(basePrice - priceDiscount);

        } else if (voucher.discount.type === 'AMOUNT') {
          discount = voucher.discount.amount_off / e;
          validateAmountDiscount(discount);
          var newPrice = basePrice - discount;
          return roundMoney(newPrice > 0 ? newPrice : 0);

        } else if (voucher.discount.type === 'UNIT') {
          discount = voucher.discount.unit_off;
          validateUnitDiscount(discount);
          var newPrice = basePrice - unitPrice * discount;
          return roundMoney(newPrice > 0 ? newPrice : 0);

        } else {
          throw new Error("Unsupported discount type.");
        }
      },

      calculateDiscount: function(basePrice, voucher, unitPrice) {
        var e = 100; // Number of digits after the decimal separator.
        var discount;

        if (voucher.gift) {
          discount = Math.min(voucher.gift.balance / e, basePrice);
          return roundMoney(discount);
        }

        if (!voucher.discount) {
          throw new Error("Unsupported voucher type.");
        }

        if (voucher.discount.type === 'PERCENT') {
          discount = voucher.discount.percent_off;
          validatePercentDiscount(discount);
          return roundMoney(basePrice * (discount / 100));

        } else if (voucher.discount.type === 'AMOUNT') {
          discount = voucher.discount.amount_off / e;
          validateAmountDiscount(discount);
          var newPrice = basePrice - discount;
          return roundMoney(newPrice > 0 ? discount : basePrice);

        } else if (voucher.discount.type === 'UNIT') {
          discount = voucher.discount.unit_off;
          validateUnitDiscount(discount);
          var priceDiscount = unitPrice * discount;
          return roundMoney(priceDiscount > basePrice ? basePrice : priceDiscount);

        } else {
          throw new Error("Unsupported discount type.");
        }
      }
    },
    render: function(selector, options) {
      var $element = $(selector);
      if (!$element || !$element.length) {
        throw new Error("Element '" + selector + "' cannot be found");
      }
      options = options || {};

      function getCapitalizedName(name) {
        return name.charAt(0).toUpperCase() + name.slice(1);
      }

      function getPropertyName(prefix, name) {
        return prefix + getCapitalizedName(name);
      }

      function getConfigProperty(prefix, name) {
        return options[getPropertyName(prefix, name)];
      }

      function create$control(type, name, $container, config) {
        config = config || {};
        var $control = null;
        var configured$control = getConfigProperty("selector", name);

        if (config.configurable && configured$control) {
          $control = $(configured$control);
        }

        if (!$control || !$control.length) {
          $control = $(document.createElement(type));
          $container.append($control);

          for (var attribute in config) {
            if (attribute !== "configurable" && config.hasOwnProperty(attribute)) {
              $control.attr(attribute, config[attribute]);
            }
          }

          if (type === "input") {
            $control.attr("name", getPropertyName("voucherify", name));
          }

          if (type === "span" && config.text) {
            $control.text(config.text);
          }
        }

        $control.addClass(typeof getConfigProperty("class", name) === "string" ? getConfigProperty("class", name) : getPropertyName("voucherify", name));
        return $control;
      }

      var $container     = create$control("div", "container", $element);
      var $logoContainer = create$control("figure", "logo", $container);
      var $logo          = create$control("img", "logo", $logoContainer, { src: typeof options.logoSrc === "string" ? options.logoSrc : "https://app.voucherify.io/images/favicon.png" });
      var $code          = create$control("input", "code", $container, { type: "text", placeholder: typeof options.textPlaceholder === "string" ? options.textPlaceholder : "e.g. abc-123" });
      var $discountType  = create$control("input", "discountType", $container, { type: "hidden", configurable: true });
      var $percentOff    = create$control("input", "percentOff", $container, { type: "hidden", configurable: true });
      var $amountOff     = create$control("input", "amountOff", $container, { type: "hidden", configurable: true });
      var $unitOff       = create$control("input", "unitOff", $container, { type: "hidden", configurable: true });
      var $tracking      = create$control("input", "tracking", $container, { type: "hidden", configurable: true });
      var $validate      = create$control("button", "validate", $container, {});
      var $validateText  = create$control("span", "validateText", $validate, { text: typeof options.textValidate === "string" ? options.textValidate : "Validate" });

      var self = this;
      var classInvalid = options.classInvalid === "string" ? options.classInvalid : "voucherifyInvalid";
      var classValid = typeof options.classValid === "string" ? options.classValid : "voucherifyValid";
      var classInvalidAnimation = options.classInvalidAnimation === "string" ? options.classInvalidAnimation : "voucherifyAnimationShake";
      var classValidAnimation = options.classValidAnimation === "string" ? options.classValidAnimation : "voucherifyAnimationTada";

      $code.on("keyup", function(event) {
        $code.toggleClass(classInvalidAnimation, false);
      });

      $validate.on("click", function(event) {
        $discountType.val("");
        $amountOff.val("");
        $unitOff.val("");
        $percentOff.val("");
        $tracking.val("");

        $validate.toggleClass(classInvalid, false);
        $validate.toggleClass(classValid, false);

        if (!$code.val()) {
          $code.toggleClass(classInvalidAnimation, true)
              .delay(1000)
              .queue(function(){
                $code.toggleClass(classInvalidAnimation, false);
                $code.dequeue();
              });
          return;
        }

        self.validate($code.val(), function(response) {
          if (!response || !response.valid) {
            $validate.toggleClass(classInvalid, true);
            $validate.toggleClass(classValid, false);
            $code.toggleClass(classInvalid, true);
            $code.toggleClass(classValid, false);
            $code.toggleClass(classInvalidAnimation, true)
                .delay(1000)
                .queue(function(){
                  $code.toggleClass(classInvalidAnimation, false);
                  $code.dequeue();
                });
            return;
          }

          $code.toggleClass(classInvalid, false);
          $discountType.val(response.discount && response.discount.type || "");
          $amountOff.val(response.discount && response.discount.amount_off || 0);
          $unitOff.val(response.discount && response.discount.unit_off || 0);
          $percentOff.val(response.discount && response.discount.percent_off || 0);
          $tracking.val(response.tracking_id || "");

          $code.prop("disabled", true);
          $validate.prop("disabled", true);

          $code.toggleClass(classValid, true);
          $validate.toggleClass(classValid, true);
          $validate.toggleClass(classInvalid, false);
          $code.toggleClass(classInvalid, false);

          $code.toggleClass(classValidAnimation, true)

          if (options && options.onValidated && typeof options.onValidated === "function") {
            options.onValidated(response);
          }
        });
      });
    }
  };

  if (typeof module !== "undefined" && module.exports) {
    module.exports = voucherify;
  }

  return voucherify;
} (window, window.document, window.jQuery));
<|endoftext|>


<commit_before>
WebFontConfig = {
	google: {
		families: [
			'Iceland::latin',
			'VT323::latin'
		]
	}
};

(function() {
	var wf = document.createElement('script')
	wf.src = ('https:' == document.location.protocol ? 'https' : 'http')
		+ '://ajax.googleapis.com/ajax/libs/webfont/1/webfont.js'
	wf.type = 'application/javascript'
	wf.async = false
	var s = document.getElementsByTagName('script')[0]
	s.parentNode.insertBefore(wf, s)
})()
<commit_msg>Removed extra newline<commit_after>WebFontConfig = {
	google: {
		families: [
			'Iceland::latin',
			'VT323::latin'
		]
	}
};

(function() {
	var wf = document.createElement('script')
	wf.src = ('https:' == document.location.protocol ? 'https' : 'http')
		+ '://ajax.googleapis.com/ajax/libs/webfont/1/webfont.js'
	wf.type = 'application/javascript'
	wf.async = false
	var s = document.getElementsByTagName('script')[0]
	s.parentNode.insertBefore(wf, s)
})()
<|endoftext|>


<commit_before>"use babel";

import { TextEditor } from "atom";

import ResultView from "./result-view";
import { log } from "./utils";

export default class WatchView {
  constructor(kernel) {
    this.kernel = kernel;
    this.element = document.createElement("div");
    this.element.classList.add("hydrogen", "watch-view");

    this.inputEditor = new TextEditor();
    this.inputEditor.element.classList.add("watch-input");
    this.inputEditor.setGrammar(this.kernel.grammar);
    this.inputEditor.setSoftWrapped(true);
    this.inputEditor.setLineNumberGutterVisible(false);
    this.inputEditor.moveToTop();

    this.resultView = new ResultView();
    this.resultView.setMultiline(true);

    this.element.appendChild(this.inputEditor.element);
    this.element.appendChild(this.resultView.element);

    this.addHistorySwitch().clearHistory();
  }

  clearHistory(currentHistory = []) {
    this.currentHistory = currentHistory;
    return this;
  }

  addToHistory(result) {
    if (result.stream === "status" || result.stream === "execution_count")
      return;
    this.currentHistory.push(result);
    this.currentHistory.pos = this.currentHistory.length - 1;
    this.counter.innerText = `${this.currentHistory.length} / ${this.currentHistory.length}`;
    const total = this.currentHistory.length * this.scrollbar.offsetWidth;
    this.scrollbar.querySelector(".hidden").style.width = `${total}px`;
    this.scrollbar.scrollLeft = total;
    this.historySwitch.classList.add("show");
  }

  addHistorySwitch() {
    this.historySwitch = document.createElement("div");
    this.historySwitch.classList.add("history-switch", "hide");

    this.scrollbar = document.createElement("div");
    const filler = document.createElement("div");
    this.scrollbar.classList.add("scrollbar");
    filler.classList.add("hidden");
    this.scrollbar.appendChild(filler);
    this.scrollbar.onscroll = () => {
      this.currentHistory.pos = Math.ceil(
        this.scrollbar.scrollLeft / (this.scrollbar.offsetWidth + 1)
      );
      this.counter.innerText = `${this.currentHistory.pos + 1} / ${this.currentHistory.length}`;
      this.clearResults();
      this.resultView.addResult(this.currentHistory[this.currentHistory.pos]);
    };

    this.counter = document.createElement("div");
    this.counter.classList.add("counter");

    const nextButton = document.createElement("button");
    nextButton.classList.add(
      "btn",
      "btn-xs",
      "icon",
      "icon-chevron-right",
      "next-btn"
    );
    nextButton.onclick = () => {
      if (
        this.currentHistory.pos &&
        this.currentHistory.pos !== this.currentHistory.length - 1
      ) {
        this.currentHistory.pos += 1;
        this.counter.innerText = `${this.currentHistory.pos + 1} / ${this.currentHistory.length}`;
        this.scrollbar.scrollLeft =
          this.currentHistory.pos * (this.scrollbar.offsetWidth + 1);
        this.clearResults();
        this.resultView.addResult(this.currentHistory[this.currentHistory.pos]);
      }
    };

    const prevButton = document.createElement("button");
    prevButton.classList.add("btn", "btn-xs", "icon", "icon-chevron-left");
    prevButton.onclick = () => {
      if (this.currentHistory.pos && this.currentHistory.pos !== 0) {
        this.currentHistory.pos -= 1;
        this.counter.innerText = `${this.currentHistory.pos + 1} / ${this.currentHistory.length}`;
        this.scrollbar.scrollLeft =
          this.currentHistory.pos * (this.scrollbar.offsetWidth + 1);
        this.clearResults();
        this.resultView.addResult(this.currentHistory[this.currentHistory.pos]);
      }
    };

    this.historySwitch.appendChild(prevButton);
    this.historySwitch.appendChild(this.counter);
    this.historySwitch.appendChild(nextButton);
    this.historySwitch.appendChild(this.scrollbar);
    this.element.appendChild(this.historySwitch);
    return this;
  }

  run() {
    const code = this.getCode();
    this.clearResults();
    log("watchview running:", code);
    if (code && code.length && code.length > 0) {
      this.kernel.executeWatch(code, result => {
        log("watchview got result:", result);
        this.resultView.addResult(result);
        this.addToHistory(result);
      });
    }
  }

  setCode(code) {
    this.inputEditor.setText(code);
  }

  getCode() {
    return this.inputEditor.getText();
  }

  focus() {
    this.inputEditor.element.focus();
  }

  clearResults() {
    try {
      this.element.removeChild(this.resultView.element);
      this.resultView.destroy();
    } catch (e) {
      console.error(e);
    }

    this.resultView = new ResultView();
    this.resultView.setMultiline(true);
    this.element.appendChild(this.resultView.element);
  }

  destroy() {
    this.clearResults();
    this.element.parentNode.removeChild(this.element);
  }
}
<commit_msg>Fix watches scrollbar<commit_after>"use babel";

import { TextEditor } from "atom";

import ResultView from "./result-view";
import { log } from "./utils";

export default class WatchView {
  constructor(kernel) {
    this.kernel = kernel;
    this.element = document.createElement("div");
    this.element.classList.add("hydrogen", "watch-view");

    this.inputEditor = new TextEditor();
    this.inputEditor.element.classList.add("watch-input");
    this.inputEditor.setGrammar(this.kernel.grammar);
    this.inputEditor.setSoftWrapped(true);
    this.inputEditor.setLineNumberGutterVisible(false);
    this.inputEditor.moveToTop();

    this.resultView = new ResultView();
    this.resultView.setMultiline(true);

    this.element.appendChild(this.inputEditor.element);
    this.element.appendChild(this.resultView.element);

    this.addHistorySwitch().clearHistory();
  }

  clearHistory(currentHistory = []) {
    this.currentHistory = currentHistory;
    return this;
  }

  addToHistory(result) {
    if (result.stream === "status" || result.stream === "execution_count")
      return;
    this.currentHistory.push(result);
    this.currentHistory.pos = this.currentHistory.length - 1;
    this.counter.innerText = `${this.currentHistory.length} / ${this.currentHistory.length}`;
    const total = this.currentHistory.length * this.scrollbar.offsetWidth;
    this.scrollbar.querySelector(".hidden").style.width = `${total}px`;
    this.scrollbar.scrollLeft = total;
    this.historySwitch.classList.add("show");
  }

  addHistorySwitch() {
    this.historySwitch = document.createElement("div");
    this.historySwitch.classList.add("history-switch", "hide");

    this.scrollbar = document.createElement("div");
    const filler = document.createElement("div");
    this.scrollbar.classList.add("scrollbar");
    filler.classList.add("hidden");
    this.scrollbar.appendChild(filler);
    this.scrollbar.onscroll = () => {
      this.currentHistory.pos = Math.ceil(
        this.scrollbar.scrollLeft / (this.scrollbar.offsetWidth + 1)
      );
      this.counter.innerText = `${this.currentHistory.pos + 1} / ${this.currentHistory.length}`;
      this.clearResults();
      this.resultView.addResult(this.currentHistory[this.currentHistory.pos]);
    };

    this.counter = document.createElement("div");
    this.counter.classList.add("counter");

    const nextButton = document.createElement("button");
    nextButton.classList.add(
      "btn",
      "btn-xs",
      "icon",
      "icon-chevron-right",
      "next-btn"
    );
    nextButton.onclick = () => {
      if (
        this.currentHistory.pos != null &&
        this.currentHistory.pos !== this.currentHistory.length - 1
      ) {
        this.currentHistory.pos += 1;
        this.counter.innerText = `${this.currentHistory.pos + 1} / ${this.currentHistory.length}`;
        this.scrollbar.scrollLeft =
          this.currentHistory.pos * (this.scrollbar.offsetWidth + 1);
        this.clearResults();
        this.resultView.addResult(this.currentHistory[this.currentHistory.pos]);
      }
    };

    const prevButton = document.createElement("button");
    prevButton.classList.add("btn", "btn-xs", "icon", "icon-chevron-left");
    prevButton.onclick = () => {
      if (this.currentHistory.pos) {
        this.currentHistory.pos -= 1;
        this.counter.innerText = `${this.currentHistory.pos + 1} / ${this.currentHistory.length}`;
        this.scrollbar.scrollLeft =
          this.currentHistory.pos * (this.scrollbar.offsetWidth + 1);
        this.clearResults();
        this.resultView.addResult(this.currentHistory[this.currentHistory.pos]);
      }
    };

    this.historySwitch.appendChild(prevButton);
    this.historySwitch.appendChild(this.counter);
    this.historySwitch.appendChild(nextButton);
    this.historySwitch.appendChild(this.scrollbar);
    this.element.appendChild(this.historySwitch);
    return this;
  }

  run() {
    const code = this.getCode();
    this.clearResults();
    log("watchview running:", code);
    if (code && code.length && code.length > 0) {
      this.kernel.executeWatch(code, result => {
        log("watchview got result:", result);
        this.resultView.addResult(result);
        this.addToHistory(result);
      });
    }
  }

  setCode(code) {
    this.inputEditor.setText(code);
  }

  getCode() {
    return this.inputEditor.getText();
  }

  focus() {
    this.inputEditor.element.focus();
  }

  clearResults() {
    try {
      this.element.removeChild(this.resultView.element);
      this.resultView.destroy();
    } catch (e) {
      console.error(e);
    }

    this.resultView = new ResultView();
    this.resultView.setMultiline(true);
    this.element.appendChild(this.resultView.element);
  }

  destroy() {
    this.clearResults();
    this.element.parentNode.removeChild(this.element);
  }
}
<|endoftext|>