<commit_before>'use strict';

module.exports = function(kbox, app) {

  // Grab some kalabox modules
  var events = kbox.core.events;
  var engine = kbox.engine;
  var Promise = kbox.Promise;
  Promise.longStackTraces();

  // Intrinsic modules.
  var url = require('url');
  var fs = require('fs');
  var path = require('path');

  // NPM modules
  var _ = require('lodash');

  // "Constants"
  var PLUGIN_NAME = 'kalabox-plugin-pantheon';

  // Grab the terminus client
  var Terminus = require('./terminus.js');
  var terminus = new Terminus(kbox, app);

  // Grab delegated helpers
  var pathToRoot = path.resolve(__dirname, '..', '..', '..');
  var pathToNode = path.join(pathToRoot, 'node_modules');
  var Git = require(pathToNode + '/kalabox-plugin-git/lib/git.js');
  var git = new Git(kbox, app);
  var Rsync = require(pathToNode + '/kalabox-plugin-rsync/lib/rsync.js');
  var rsync = new Rsync(kbox, app);

  /*
   * Check to see if this is the first time we are going to do a pull or not
   * @todo: this is pretty weak for now
   */
  var firstTime = function() {
    // @todo: this only works if you have started your app first
    var gitFile = path.join(app.config.codeRoot, '.git');
    return !fs.existsSync(gitFile);
  };

  /*
   * Pull down our sites code
   */
  var pullCode = function(site, env) {

    // Determine correct operation
    var type = (firstTime()) ? 'clone' : 'pull';

    // the pantheon site UUID
    var siteid;
    var repo;

    // Grab the sites UUID from teh machinename
    return terminus.getUUID(site)

    // Wake the site up
    .then(function(uuid) {
      siteid = uuid;
      return terminus.wakeSite(site, env);
    })

    // Generate our code repo URL and CUT THAT MEAT!
    // errr PULL THAT CODE!
    .then(function() {
      var build = {
        protocol: 'ssh',
        slashes: true,
        auth: ['codeserver', env, siteid].join('.'),
        hostname: ['codeserver', 'dev', siteid, 'drush', 'in'].join('.'),
        port: 2222,
        pathname: ['~', 'repository.git'].join('/')
      };

      // Format our metadata into a url
      repo = url.format(build);

      // Do this if we are gitting for the first time
      if (type === 'clone') {

        // Clone the repo
        return git.cmd(['clone', repo, './'], [])

        // Grab branches if we need more than master
        .then(function() {
          if (env !== 'dev') {
            return git.cmd(['fetch', 'origin'], []);
          }
        })

        // Checkout correct branch if needed
        .then(function() {
          if (env !== 'dev') {
            return git.cmd(['checkout', env], []);
          }
        });
      }

      // If we already have da git then just pull down the correct branch
      else {
        var branch = (env === 'dev') ? 'master' : env;
        return git.cmd(['pull', 'origin', branch], []);
      }

    });
  };

  /*
   * Pull down our sites database
   */
  var pullDB = function(site, env, newBackup) {
    // Get the cid of this apps database
    // @todo: this looks gross
    var dbID = _.result(_.find(app.components, function(cmp) {
      return cmp.name === 'db';
    }), 'containerId');

    // Set some default things
    var wasRunning = null;
    var defaults = {
      PublishAllPorts: true,
      Binds: [app.rootBind + ':/src:rw']
    };

    // Check if the DB container is already running
    return engine.isRunning(dbID)

    // If not running START IT UP
    .then(function(status) {
      wasRunning = status;
      if (!wasRunning) {
        return engine.start(dbID, defaults);
      }
    })

    // GEt the UUID for this site
    .then(function() {
      return terminus.getUUID(site);
    })

    // Check if site has a backup
    .then(function(uuid) {
      return terminus.hasBackup(uuid, env, 'database');
    })

    // If no backup or for backup then MAKE THAT SHIT
    .then(function(hasBackup) {
      if (!hasBackup || newBackup) {
        return terminus.createBackup(site, env, 'db');
      }
    })

    // Download the backup
    .then(function() {
      return terminus.downloadBackup(site, env, 'db');
    })

    // Import the backup
    .then(function(importFile) {
      // Perform a container run.
      var payload = ['import-mysql', 'localhost', null, '3306', importFile];
      return engine.queryData(dbID, payload);
    })

    // Stop the DB container if that is how we found it initially
    .then(function() {
      if (!wasRunning) {
        return engine.stop(dbID);
      }
    });

  };

  /*
   * Pull files via RSYNC
   */
  var pullFilesRsync = function(site, env) {
    // Get our UUID
    return terminus.getUUID(site)

    // Generate our code repo URL and CUT THAT MEAT!
    // errr PULL THAT CODE!
    .then(function(uuid) {
      // This can have a newline in it that F's everything
      var siteid = uuid;

      // @todo: lots of cleanup here
      // Hack together an rsync command
      var envSite = [env, siteid].join('.');
      var fileBox = envSite + '@appserver.' + envSite + '.drush.in:files/';
      var fileMount = '/media';
      var connect = '-rlvz --size-only --ipv4 --progress -e \'ssh -p 2222\'';
      var opts = [connect, terminus.getExcludes()].join(' ');

      // Rysnc our files
      return rsync.cmd([opts, fileBox, fileMount], true);

    });
  };

  /*
   * Pull files via an archive
   */
  var pullFilesArchive = function(site, env, newBackup) {

    // Get our UUID
    return terminus.getUUID(site)

    // Check if site has a backup
    .then(function(uuid) {
      return terminus.hasBackup(uuid, env, 'files');
    })

    // If no backup or for backup then MAKE THAT SHIT
    .then(function(hasBackup) {
      if (!hasBackup || newBackup) {
        return terminus.createBackup(site, env, 'files');
      }
    })

    // Download the backup
    .then(function() {
      return terminus.downloadBackup(site, env, 'files');
    })

    // Extract the backup and remove
    .then(function(importFile) {

      // Image name
      var image = 'kalabox/debian:stable';

      // Build create options
      var createOpts = {};

      // Build start options
      var startOpts = kbox.util.docker.StartOpts()
        .bind(app.rootBind, '/src')
        .volumeFrom(app.dataContainerName)
        .json();

      // CMD to extract our archive file to /media
      var extractCmd = [
        'tar',
        '-zxvf',
        importFile,
        '--strip-components=1',
        '--overwrite',
        '--directory=/media'
      ];

      // Cmd to remove the archive
      var rmCmd = ['rm', '-f', importFile];

      // Extract the archive
      return kbox.engine.run(image, extractCmd, createOpts, startOpts)

      // Remove the archive
      .then(function() {
        return kbox.engine.run(image, rmCmd, createOpts, startOpts);
      });

    });
  };

  /*
   * Pull down our sites database
   */
  var pullFiles = function(site, env, newBackup) {
    // If this is the first time we want to grab the files from an archive
    // since this will be way faster. subsequent pulls we will use rsync since
    // this will be way faster
    if (firstTime()) {
      return pullFilesArchive(site, env, newBackup);
    }
    else {
      return pullFilesRsync(site, env);
    }
  };

  return {
    pullCode: pullCode,
    pullDB: pullDB,
    pullFiles: pullFiles
  };

};
<commit_msg>#635: Notes about no files backup<commit_after>'use strict';

module.exports = function(kbox, app) {

  // Grab some kalabox modules
  var events = kbox.core.events;
  var engine = kbox.engine;
  var Promise = kbox.Promise;
  Promise.longStackTraces();

  // Intrinsic modules.
  var url = require('url');
  var fs = require('fs');
  var path = require('path');

  // NPM modules
  var _ = require('lodash');

  // "Constants"
  var PLUGIN_NAME = 'kalabox-plugin-pantheon';

  // Grab the terminus client
  var Terminus = require('./terminus.js');
  var terminus = new Terminus(kbox, app);

  // Grab delegated helpers
  var pathToRoot = path.resolve(__dirname, '..', '..', '..');
  var pathToNode = path.join(pathToRoot, 'node_modules');
  var Git = require(pathToNode + '/kalabox-plugin-git/lib/git.js');
  var git = new Git(kbox, app);
  var Rsync = require(pathToNode + '/kalabox-plugin-rsync/lib/rsync.js');
  var rsync = new Rsync(kbox, app);

  /*
   * Check to see if this is the first time we are going to do a pull or not
   * @todo: this is pretty weak for now
   */
  var firstTime = function() {
    // @todo: this only works if you have started your app first
    var gitFile = path.join(app.config.codeRoot, '.git');
    return !fs.existsSync(gitFile);
  };

  /*
   * Pull down our sites code
   */
  var pullCode = function(site, env) {

    // Determine correct operation
    var type = (firstTime()) ? 'clone' : 'pull';

    // the pantheon site UUID
    var siteid;
    var repo;

    // Grab the sites UUID from teh machinename
    return terminus.getUUID(site)

    // Wake the site up
    .then(function(uuid) {
      siteid = uuid;
      return terminus.wakeSite(site, env);
    })

    // Generate our code repo URL and CUT THAT MEAT!
    // errr PULL THAT CODE!
    .then(function() {
      var build = {
        protocol: 'ssh',
        slashes: true,
        auth: ['codeserver', env, siteid].join('.'),
        hostname: ['codeserver', 'dev', siteid, 'drush', 'in'].join('.'),
        port: 2222,
        pathname: ['~', 'repository.git'].join('/')
      };

      // Format our metadata into a url
      repo = url.format(build);

      // Do this if we are gitting for the first time
      if (type === 'clone') {

        // Clone the repo
        return git.cmd(['clone', repo, './'], [])

        // Grab branches if we need more than master
        .then(function() {
          if (env !== 'dev') {
            return git.cmd(['fetch', 'origin'], []);
          }
        })

        // Checkout correct branch if needed
        .then(function() {
          if (env !== 'dev') {
            return git.cmd(['checkout', env], []);
          }
        });
      }

      // If we already have da git then just pull down the correct branch
      else {
        var branch = (env === 'dev') ? 'master' : env;
        return git.cmd(['pull', 'origin', branch], []);
      }

    });
  };

  /*
   * Pull down our sites database
   */
  var pullDB = function(site, env, newBackup) {
    // Get the cid of this apps database
    // @todo: this looks gross
    var dbID = _.result(_.find(app.components, function(cmp) {
      return cmp.name === 'db';
    }), 'containerId');

    // Set some default things
    var wasRunning = null;
    var defaults = {
      PublishAllPorts: true,
      Binds: [app.rootBind + ':/src:rw']
    };

    // Check if the DB container is already running
    return engine.isRunning(dbID)

    // If not running START IT UP
    .then(function(status) {
      wasRunning = status;
      if (!wasRunning) {
        return engine.start(dbID, defaults);
      }
    })

    // GEt the UUID for this site
    .then(function() {
      return terminus.getUUID(site);
    })

    // Check if site has a backup
    .then(function(uuid) {
      return terminus.hasBackup(uuid, env, 'database');
    })

    // If no backup or for backup then MAKE THAT SHIT
    .then(function(hasBackup) {
      if (!hasBackup || newBackup) {
        return terminus.createBackup(site, env, 'db');
      }
    })

    // Download the backup
    .then(function() {
      return terminus.downloadBackup(site, env, 'db');
    })

    // Import the backup
    .then(function(importFile) {
      // Perform a container run.
      var payload = ['import-mysql', 'localhost', null, '3306', importFile];
      return engine.queryData(dbID, payload);
    })

    // Stop the DB container if that is how we found it initially
    .then(function() {
      if (!wasRunning) {
        return engine.stop(dbID);
      }
    });

  };

  /*
   * Pull files via RSYNC
   */
  var pullFilesRsync = function(site, env) {
    // Get our UUID
    return terminus.getUUID(site)

    // Generate our code repo URL and CUT THAT MEAT!
    // errr PULL THAT CODE!
    .then(function(uuid) {
      // This can have a newline in it that F's everything
      var siteid = uuid;

      // @todo: lots of cleanup here
      // Hack together an rsync command
      var envSite = [env, siteid].join('.');
      var fileBox = envSite + '@appserver.' + envSite + '.drush.in:files/';
      var fileMount = '/media';
      var connect = '-rlvz --size-only --ipv4 --progress -e \'ssh -p 2222\'';
      var opts = [connect, terminus.getExcludes()].join(' ');

      // Rysnc our files
      return rsync.cmd([opts, fileBox, fileMount], true);

    });
  };

  /*
   * Pull files via an archive
   */
  var pullFilesArchive = function(site, env, newBackup) {

    // Get our UUID
    return terminus.getUUID(site)

    // Check if site has a backup
    .then(function(uuid) {
      return terminus.hasBackup(uuid, env, 'files');
    })

    // If no backup or for backup then MAKE THAT SHIT
    .then(function(hasBackup) {
      if (!hasBackup || newBackup) {
        // @todo: it might make more sense to default to
        // rsync here?
        return terminus.createBackup(site, env, 'files');
      }
    })

    // Download the backup
    .then(function() {
      return terminus.downloadBackup(site, env, 'files');
    })

    // Extract the backup and remove
    .then(function(importFile) {

      // Image name
      var image = 'kalabox/debian:stable';

      // Build create options
      var createOpts = {};

      // Build start options
      var startOpts = kbox.util.docker.StartOpts()
        .bind(app.rootBind, '/src')
        .volumeFrom(app.dataContainerName)
        .json();

      // CMD to extract our archive file to /media
      var extractCmd = [
        'tar',
        '-zxvf',
        importFile,
        '--strip-components=1',
        '--overwrite',
        '--directory=/media'
      ];

      // Cmd to remove the archive
      var rmCmd = ['rm', '-f', importFile];

      // Extract the archive
      return kbox.engine.run(image, extractCmd, createOpts, startOpts)

      // Remove the archive
      .then(function() {
        return kbox.engine.run(image, rmCmd, createOpts, startOpts);
      });

    });
  };

  /*
   * Pull down our sites database
   */
  var pullFiles = function(site, env, newBackup) {
    // If this is the first time we want to grab the files from an archive
    // since this will be way faster. subsequent pulls we will use rsync since
    // this will be way faster
    if (firstTime()) {
      return pullFilesArchive(site, env, newBackup);
    }
    else {
      return pullFilesRsync(site, env);
    }
  };

  return {
    pullCode: pullCode,
    pullDB: pullDB,
    pullFiles: pullFiles
  };

};
<|endoftext|>


<commit_before>var XMPPEvents = {
    CONNECTION_FAILED: "xmpp.connection.failed",
    // Indicates an interrupted connection event.
    CONNECTION_INTERRUPTED: "xmpp.connection.interrupted",
    // Indicates a restored connection event.
    CONNECTION_RESTORED: "xmpp.connection.restored",
    CONFERENCE_CREATED: "xmpp.conferenceCreated.jingle",
    CALL_INCOMING: "xmpp.callincoming.jingle",
    DISPOSE_CONFERENCE: "xmpp.dispose_conference",
    GRACEFUL_SHUTDOWN: "xmpp.graceful_shutdown",
    KICKED: "xmpp.kicked",
    BRIDGE_DOWN: "xmpp.bridge_down",
    USER_ID_CHANGED: "xmpp.user_id_changed",
    // We joined the MUC
    MUC_JOINED: "xmpp.muc_joined",
    // A member joined the MUC
    MUC_MEMBER_JOINED: "xmpp.muc_member_joined",
    // A member left the MUC
    MUC_MEMBER_LEFT: "xmpp.muc_member_left",
    MUC_ROLE_CHANGED: "xmpp.muc_role_changed",
    MUC_DESTROYED: "xmpp.muc_destroyed",
    DISPLAY_NAME_CHANGED: "xmpp.display_name_changed",
    REMOTE_STATS: "xmpp.remote_stats",
    LOCAL_ROLE_CHANGED: "xmpp.localrole_changed",
    PRESENCE_STATUS: "xmpp.presence_status",
    RESERVATION_ERROR: "xmpp.room_reservation_error",
    SUBJECT_CHANGED: "xmpp.subject_changed",
    MESSAGE_RECEIVED: "xmpp.message_received",
    SENDING_CHAT_MESSAGE: "xmpp.sending_chat_message",
    PASSWORD_REQUIRED: "xmpp.password_required",
    AUTHENTICATION_REQUIRED: "xmpp.authentication_required",
    CHAT_ERROR_RECEIVED: "xmpp.chat_error_received",
    ETHERPAD: "xmpp.etherpad",
    DEVICE_AVAILABLE: "xmpp.device_available",
    VIDEO_TYPE: "xmpp.video_type",
    PEERCONNECTION_READY: "xmpp.peerconnection_ready",
    CONFERENCE_SETUP_FAILED: "xmpp.conference_setup_failed",
    AUDIO_MUTED: "xmpp.audio_muted",
    VIDEO_MUTED: "xmpp.video_muted",
    AUDIO_MUTED_BY_FOCUS: "xmpp.audio_muted_by_focus",
    START_MUTED_SETTING_CHANGED: "xmpp.start_muted_setting_changed",
    START_MUTED_FROM_FOCUS: "xmpp.start_muted_from_focus",
    SET_LOCAL_DESCRIPTION_ERROR: 'xmpp.set_local_description_error',
    SET_REMOTE_DESCRIPTION_ERROR: 'xmpp.set_remote_description_error',
    CREATE_ANSWER_ERROR: 'xmpp.create_answer_error',
    JINGLE_FATAL_ERROR: 'xmpp.jingle_fatal_error',
    PROMPT_FOR_LOGIN: 'xmpp.prompt_for_login',
    FOCUS_DISCONNECTED: 'xmpp.focus_disconnected',
    ROOM_JOIN_ERROR: 'xmpp.room_join_error',
    ROOM_CONNECT_ERROR: 'xmpp.room_connect_error',
    // xmpp is connected and obtained user media
    READY_TO_JOIN: 'xmpp.ready_to_join',
    FOCUS_LEFT: "xmpp.focus_left",
    REMOTE_STREAM_RECEIVED: "xmpp.remote_stream_received"
};
module.exports = XMPPEvents;<commit_msg>Documents some of the XMPP events. Renames some of them.<commit_after>var XMPPEvents = {
    // Designates an event indicating that the connection to the XMPP server
    // failed.
    CONNECTION_FAILED: "xmpp.connection.failed",
    // Designates an event indicating that the media (ICE) connection was
    // interrupted. This should go to the RTC module.
    CONNECTION_INTERRUPTED: "xmpp.connection.interrupted",
    // Designates an event indicating that the media (ICE) connection was
    // restored. This should go to the RTC module.
    CONNECTION_RESTORED: "xmpp.connection.restored",
    // Designates an event indicating that an offer (e.g. Jingle
    // session-initiate) was received.
    CALL_INCOMING: "xmpp.callincoming.jingle",
    // Designates an event indicating that we were kicked from the XMPP MUC.
    KICKED: "xmpp.kicked",
    // Designates an event indicating that the userID for a specific JID has
    // changed.
    USER_ID_CHANGED: "xmpp.user_id_changed",
    // Designates an event indicating that we have joined the XMPP MUC.
    MUC_JOINED: "xmpp.muc_joined",
    // Designates an event indicating that a participant joined the XMPP MUC.
    MUC_MEMBER_JOINED: "xmpp.muc_member_joined",
    // Designates an event indicating that a participant left the XMPP MUC.
    MUC_MEMBER_LEFT: "xmpp.muc_member_left",
    // Designates an event indicating that the MUC role of a participant has
    // changed.
    MUC_ROLE_CHANGED: "xmpp.muc_role_changed",
    // Designates an event indicating that the XMPP MUC was destroyed.
    MUC_DESTROYED: "xmpp.muc_destroyed",
    // Designates an event indicating that the display name of a participant
    // has changed.
    DISPLAY_NAME_CHANGED: "xmpp.display_name_changed",
    // Designates an event indicating that we received statistics from a
    // participant in the MUC.
    REMOTE_STATS: "xmpp.remote_stats",
    // Designates an event indicating that our role in the XMPP MUC has changed.
    LOCAL_ROLE_CHANGED: "xmpp.localrole_changed",
    // Designates an event indicating that the subject of the XMPP MUC has
    // changed.
    SUBJECT_CHANGED: "xmpp.subject_changed",
    // Designates an event indicating that an XMPP message in the MUC was
    // received.
    MESSAGE_RECEIVED: "xmpp.message_received",
    // Designates an event indicating that we sent an XMPP message to the MUC.
    SENDING_CHAT_MESSAGE: "xmpp.sending_chat_message",
    // Designates an event indicating that the video type (e.g. 'camera' or
    // 'screen') for a participant has changed.
    PARTICIPANT_VIDEO_TYPE_CHANGED: "xmpp.video_type",
    // Designates an event indicating that a participant in the XMPP MUC has
    // advertised that they have audio muted (or unmuted).
    PARTICIPANT_AUDIO_MUTED: "xmpp.audio_muted",
    // Designates an event indicating that a participant in the XMPP MUC has
    // advertised that they have video muted (or unmuted).
    PARTICIPANT_VIDEO_MUTED: "xmpp.video_muted",
    // Designates an event indicating that the focus has asked us to mute our
    // audio.
    AUDIO_MUTED_BY_FOCUS: "xmpp.audio_muted_by_focus",
    // Designates an event indicating that a moderator in the room changed the
    // "start muted" settings for the conference.
    START_MUTED_SETTING_CHANGED: "xmpp.start_muted_setting_changed",
    // Designates an event indicating that we should join the conference with
    // audio and/or video muted.
    START_MUTED_FROM_FOCUS: "xmpp.start_muted_from_focus",

    PEERCONNECTION_READY: "xmpp.peerconnection_ready",
    CONFERENCE_SETUP_FAILED: "xmpp.conference_setup_failed",
    PASSWORD_REQUIRED: "xmpp.password_required",
    AUTHENTICATION_REQUIRED: "xmpp.authentication_required",
    CHAT_ERROR_RECEIVED: "xmpp.chat_error_received",
    ETHERPAD: "xmpp.etherpad",
    DEVICE_AVAILABLE: "xmpp.device_available",
    BRIDGE_DOWN: "xmpp.bridge_down",
    PRESENCE_STATUS: "xmpp.presence_status",
    RESERVATION_ERROR: "xmpp.room_reservation_error",
    DISPOSE_CONFERENCE: "xmpp.dispose_conference",
    GRACEFUL_SHUTDOWN: "xmpp.graceful_shutdown",
    // TODO: only used in a hack, should probably be removed.
    SET_LOCAL_DESCRIPTION_ERROR: 'xmpp.set_local_description_error',
    // TODO: only used in a hack, should probably be removed.
    SET_REMOTE_DESCRIPTION_ERROR: 'xmpp.set_remote_description_error',
    // TODO: only used in a hack, should probably be removed.
    CREATE_ANSWER_ERROR: 'xmpp.create_answer_error',
    JINGLE_FATAL_ERROR: 'xmpp.jingle_fatal_error',
    PROMPT_FOR_LOGIN: 'xmpp.prompt_for_login',
    FOCUS_DISCONNECTED: 'xmpp.focus_disconnected',
    ROOM_JOIN_ERROR: 'xmpp.room_join_error',
    ROOM_CONNECT_ERROR: 'xmpp.room_connect_error',
    // xmpp is connected and obtained user media
    READY_TO_JOIN: 'xmpp.ready_to_join',
    FOCUS_LEFT: "xmpp.focus_left",
    REMOTE_STREAM_RECEIVED: "xmpp.remote_stream_received"
};
module.exports = XMPPEvents;<|endoftext|>


<commit_before>import ResourceFeatures from '../resource/features';

const projectsList = {
  templateUrl: 'views/partials/filtered-list.html',
  controller: ProjectsListController,
  controllerAs: 'ListController',
};

export default projectsList;

// @ngInject
function ProjectsListController(
  baseControllerListClass,
  projectsService,
  customersService,
  $filter,
  $q,
  $state,
  $scope,
  $timeout,
  $uibModal,
  ncUtils,
  ncUtilsFlash,
  TableExtensionService,
  currentStateService,
  QuotaUtilsService,
  usersService) {
  let controllerScope = this;
  let Controller = baseControllerListClass.extend({
    init: function() {
      this.controllerScope = controllerScope;
      this.service = projectsService;
      let fn = this._super.bind(this);
      this.activate().then(function() {
        fn();
      });
      $scope.$on('refreshProjectList', function() {
        $timeout(function() {
          controllerScope.resetCache();
        });
      });
    },
    activate: function() {
      let vm = this;
      vm.loading = true;
      return $q.all([
        currentStateService.getCustomer().then(function(customer) {
          vm.currentCustomer = customer;
        }),
        usersService.getCurrentUser().then(function(user) {
          vm.currentUser = user;
        })
      ]).finally(function() {
        vm.loading = false;
      }).then(function() {
        vm.controllerScope.addProjectDisabled = !vm.userCanAddProject();
        vm.tableOptions = {
          searchFieldName: 'name',
          noDataText: gettext('You have no projects yet.'),
          noMatchesText: gettext('No projects found matching filter.'),
          columns: vm.getColumns(),
          tableActions: vm.getTableActions(),
          rowActions: vm.getRowActions(),
        };
      });
    },
    refreshCustomer: function() {
      return customersService.$get(this.currentCustomer.uuid).then((customer) => {
        this.currentCustomer = customer;
        currentStateService.setCustomer(this.currentCustomer);
        this.controllerScope.addProjectDisabled = !this.userCanAddProject();
      });
    },
    getFilter: function() {
      return {
        customer: controllerScope.currentCustomer.uuid,
        o: 'name'
      };
    },
    getColumns: function() {
      const extraColumns = TableExtensionService.getColumns('projects-list');
      const baseColumns = [
        {
          title: gettext('Name'),
          render: function(row) {
            let href = $state.href('project.details', {uuid: row.uuid});
            return '{name}'
                   .replace('{href}', href)
                   .replace('{name}', row.name);
          },
          index: 10,
        },
        {
          title: gettext('Description'),
          width: '200px',
          render: row => this.renderLongText(row.description),
          index: 20,
        },
        {
          title: gettext('Creation date'),
          render: function(row) {
            return $filter('dateTime')(row.created);
          },
          index: 30,
        },
        {
          title: gettext('VMs'),
          render: row => row.vm_count,
          index: 100,
        },
        {
          title: gettext('Storage'),
          render: row => row.storage_count,
          index: 110,
        },
        {
          title: gettext('Apps'),
          feature: ResourceFeatures.APPLICATIONS,
          render: row => row.app_count,
          index: 120,
        },
        {
          title: gettext('Private clouds'),
          render: row => row.private_cloud_count,
          index: 130,
        },
        {
          title: gettext('Estimated cost'),
          feature: 'projectCostDetails',
          render: row => $filter('defaultCurrency')(row.billing_price_estimate && row.billing_price_estimate.tota || 0),
          index: 310,
        },
      ];
      return baseColumns.concat(extraColumns).sort((a, b) => a.index - b.index);
    },
    userCanAddProject: function() {
      const ownerOrStaff = customersService.checkCustomerUser(this.currentCustomer, this.currentUser);
      let quotaReached = ncUtils.isCustomerQuotaReached(this.currentCustomer, 'project');
      return ownerOrStaff && !quotaReached;
    },
    getTableActions: function() {
      let vm = this;
      let ownerOrStaff = customersService.checkCustomerUser(vm.currentCustomer, vm.currentUser);
      let quotaReached = ncUtils.isCustomerQuotaReached(vm.currentCustomer, 'project');
      let title;
      if (!ownerOrStaff) {
        title = gettext('You don\'t have enough privileges to perform this operation.');
      } else if (quotaReached) {
        title = gettext('Quota has been reached.');
      }
      return [
        {
          title: gettext('Add project'),
          iconClass: 'fa fa-plus',
          callback: function() {
            $state.go('organization.createProject', {
              uuid: vm.currentCustomer.uuid
            });
          },
          disabled: vm.controllerScope.addProjectDisabled,
          isDisabled: () => { return vm.controllerScope.addProjectDisabled; },
          titleAttr: title,
        }
      ];
    },
    getRowActions: function() {
      let vm = this;
      let ownerOrStaff = customersService.checkCustomerUser(vm.currentCustomer, vm.currentUser);
      return [
        {
          title: gettext('Details'),
          iconClass: 'fa fa-eye',
          callback: this.openDetailsDialog.bind(this),
        },
        {
          title: gettext('Remove'),
          iconClass: 'fa fa-trash',
          callback: this.remove.bind(this.controllerScope),
          isDisabled: (project) => {
            return !ownerOrStaff || vm.hasConnectedResources(project);
          },
          tooltip: (project) => {
            if (!ownerOrStaff) {
              return gettext('You don\'t have enough privileges to perform this operation.');
            } else if(vm.hasConnectedResources(project)) {
              return gettext('Project has connected resources and can\'t be removed');
            }
          }
        }
      ];
    },
    openDetailsDialog: function(project) {
      $uibModal.open({
        component: 'projectDialog',
        resolve: {
          project: () => project
        },
        size: 'lg',
      });
    },
    afterGetList: function() {
      this.list.forEach(item => angular.extend(item, QuotaUtilsService.parseCounters(item)));
      this._super();
    },
    hasConnectedResources: function(project) {
      return ncUtils.getQuotaUsage(project.quotas).nc_resource_count !== 0;
    },
    removeInstance: function(project) {
      return project.$delete()
        .then(() => {
          return this.refreshCustomer().then(()=> {
            ncUtilsFlash.success(gettext('Project has been removed successfully.'));
          });
        })
        .catch((response)=>{
          let message = response.data.detail || gettext('An error occurred on project removal.');
          ncUtilsFlash.error(message);
        });
    },
  });

  controllerScope.__proto__ = new Controller();
}
<commit_msg>Fix typo in field name<commit_after>import ResourceFeatures from '../resource/features';

const projectsList = {
  templateUrl: 'views/partials/filtered-list.html',
  controller: ProjectsListController,
  controllerAs: 'ListController',
};

export default projectsList;

// @ngInject
function ProjectsListController(
  baseControllerListClass,
  projectsService,
  customersService,
  $filter,
  $q,
  $state,
  $scope,
  $timeout,
  $uibModal,
  ncUtils,
  ncUtilsFlash,
  TableExtensionService,
  currentStateService,
  QuotaUtilsService,
  usersService) {
  let controllerScope = this;
  let Controller = baseControllerListClass.extend({
    init: function() {
      this.controllerScope = controllerScope;
      this.service = projectsService;
      let fn = this._super.bind(this);
      this.activate().then(function() {
        fn();
      });
      $scope.$on('refreshProjectList', function() {
        $timeout(function() {
          controllerScope.resetCache();
        });
      });
    },
    activate: function() {
      let vm = this;
      vm.loading = true;
      return $q.all([
        currentStateService.getCustomer().then(function(customer) {
          vm.currentCustomer = customer;
        }),
        usersService.getCurrentUser().then(function(user) {
          vm.currentUser = user;
        })
      ]).finally(function() {
        vm.loading = false;
      }).then(function() {
        vm.controllerScope.addProjectDisabled = !vm.userCanAddProject();
        vm.tableOptions = {
          searchFieldName: 'name',
          noDataText: gettext('You have no projects yet.'),
          noMatchesText: gettext('No projects found matching filter.'),
          columns: vm.getColumns(),
          tableActions: vm.getTableActions(),
          rowActions: vm.getRowActions(),
        };
      });
    },
    refreshCustomer: function() {
      return customersService.$get(this.currentCustomer.uuid).then((customer) => {
        this.currentCustomer = customer;
        currentStateService.setCustomer(this.currentCustomer);
        this.controllerScope.addProjectDisabled = !this.userCanAddProject();
      });
    },
    getFilter: function() {
      return {
        customer: controllerScope.currentCustomer.uuid,
        o: 'name'
      };
    },
    getColumns: function() {
      const extraColumns = TableExtensionService.getColumns('projects-list');
      const baseColumns = [
        {
          title: gettext('Name'),
          render: function(row) {
            let href = $state.href('project.details', {uuid: row.uuid});
            return '{name}'
                   .replace('{href}', href)
                   .replace('{name}', row.name);
          },
          index: 10,
        },
        {
          title: gettext('Description'),
          width: '200px',
          render: row => this.renderLongText(row.description),
          index: 20,
        },
        {
          title: gettext('Creation date'),
          render: function(row) {
            return $filter('dateTime')(row.created);
          },
          index: 30,
        },
        {
          title: gettext('VMs'),
          render: row => row.vm_count,
          index: 100,
        },
        {
          title: gettext('Storage'),
          render: row => row.storage_count,
          index: 110,
        },
        {
          title: gettext('Apps'),
          feature: ResourceFeatures.APPLICATIONS,
          render: row => row.app_count,
          index: 120,
        },
        {
          title: gettext('Private clouds'),
          render: row => row.private_cloud_count,
          index: 130,
        },
        {
          title: gettext('Estimated cost'),
          feature: 'projectCostDetails',
          render: row => $filter('defaultCurrency')(row.billing_price_estimate && row.billing_price_estimate.total || 0),
          index: 310,
        },
      ];
      return baseColumns.concat(extraColumns).sort((a, b) => a.index - b.index);
    },
    userCanAddProject: function() {
      const ownerOrStaff = customersService.checkCustomerUser(this.currentCustomer, this.currentUser);
      let quotaReached = ncUtils.isCustomerQuotaReached(this.currentCustomer, 'project');
      return ownerOrStaff && !quotaReached;
    },
    getTableActions: function() {
      let vm = this;
      let ownerOrStaff = customersService.checkCustomerUser(vm.currentCustomer, vm.currentUser);
      let quotaReached = ncUtils.isCustomerQuotaReached(vm.currentCustomer, 'project');
      let title;
      if (!ownerOrStaff) {
        title = gettext('You don\'t have enough privileges to perform this operation.');
      } else if (quotaReached) {
        title = gettext('Quota has been reached.');
      }
      return [
        {
          title: gettext('Add project'),
          iconClass: 'fa fa-plus',
          callback: function() {
            $state.go('organization.createProject', {
              uuid: vm.currentCustomer.uuid
            });
          },
          disabled: vm.controllerScope.addProjectDisabled,
          isDisabled: () => { return vm.controllerScope.addProjectDisabled; },
          titleAttr: title,
        }
      ];
    },
    getRowActions: function() {
      let vm = this;
      let ownerOrStaff = customersService.checkCustomerUser(vm.currentCustomer, vm.currentUser);
      return [
        {
          title: gettext('Details'),
          iconClass: 'fa fa-eye',
          callback: this.openDetailsDialog.bind(this),
        },
        {
          title: gettext('Remove'),
          iconClass: 'fa fa-trash',
          callback: this.remove.bind(this.controllerScope),
          isDisabled: (project) => {
            return !ownerOrStaff || vm.hasConnectedResources(project);
          },
          tooltip: (project) => {
            if (!ownerOrStaff) {
              return gettext('You don\'t have enough privileges to perform this operation.');
            } else if(vm.hasConnectedResources(project)) {
              return gettext('Project has connected resources and can\'t be removed');
            }
          }
        }
      ];
    },
    openDetailsDialog: function(project) {
      $uibModal.open({
        component: 'projectDialog',
        resolve: {
          project: () => project
        },
        size: 'lg',
      });
    },
    afterGetList: function() {
      this.list.forEach(item => angular.extend(item, QuotaUtilsService.parseCounters(item)));
      this._super();
    },
    hasConnectedResources: function(project) {
      return ncUtils.getQuotaUsage(project.quotas).nc_resource_count !== 0;
    },
    removeInstance: function(project) {
      return project.$delete()
        .then(() => {
          return this.refreshCustomer().then(()=> {
            ncUtilsFlash.success(gettext('Project has been removed successfully.'));
          });
        })
        .catch((response)=>{
          let message = response.data.detail || gettext('An error occurred on project removal.');
          ncUtilsFlash.error(message);
        });
    },
  });

  controllerScope.__proto__ = new Controller();
}
<|endoftext|>


<commit_before>import React, { PropTypes } from 'react'
import { injectIntl, intlShape, defineMessages, FormattedMessage, FormattedHTMLMessage } from 'react-intl'

class Footer extends React.Component {
  constructor (props) {
    super(props)
    this.handleChangeLanguage = this.handleChangeLanguage.bind(this)
  }

  handleChangeLanguage () {
    this.props.loadLanguage(this.props.locale === 'no' ? 'en' : 'no')
  }

  ignore (event) {
    event.preventDefault()
  }

  render () {
    return (

              <FormattedHTMLMessage {...messages.info} />

                    <FormattedMessage {...messages.contactUs} />

                    <FormattedMessage {...messages.openingHours} />

                    <FormattedMessage {...messages.howToLoan} />

                    <FormattedMessage {...messages.languageChoice} />

                    <FormattedMessage {...messages.aboutUs} />

                    <FormattedMessage {...messages.faq} />

                    <FormattedMessage {...messages.feedback} />

Personvern og informasjonskapsler

             OSLO KOMMUNE

    )
  }
}

Footer.propTypes = {
  loadLanguage: PropTypes.func.isRequired,
  locale: PropTypes.string.isRequired,
  intl: intlShape.isRequired
}

export const messages = defineMessages({
  contactUs: {
    id: 'Footer.contactUs',
    description: 'Label for the contact us link',
    defaultMessage: 'Contact us'
  },
  openingHours: {
    id: 'Footer.openingHours',
    description: 'Label for the opening hours link',
    defaultMessage: 'Opening hours'
  },
  languageChoice: {
    id: 'Footer.languageChoice',
    description: 'Label for the link that changes the language, will be displayed in the language being chosen',
    defaultMessage: 'På norsk'
  },
  currentLanguage: {
    id: 'Footer.currentLanguage',
    description: 'The value of the currently applied language',
    defaultMessage: 'English'
  },
  aboutUs: {
    id: 'Footer.aboutUs',
    description: 'Label for the about us link',
    defaultMessage: 'About us'
  },
  info: {
    id: 'Footer.info',
    description: 'The text containing the address, telephone number and other information',
    defaultMessage: 'Arne Garborgs plass 4
0179 Oslo
Telephone: +47 23 43 29 00

Chief editor:
Knut Skansen, director
'
  },
  feedback: {
    id: 'Footer.feedback',
    description: 'Feeback description',
    defaultMessage: 'Give feedback on search/my page'
  },
  faq: {
    id: 'Footer.faq',
    description: 'Faq link in footer menu',
    defaultMessage: 'FAQ'
  },
  howToLoan: {
    id: 'Footer.howToLoan',
    description: 'Link describing how to loan',
    defaultMessage: 'How to loan'
  }
})

export default injectIntl(Footer)
<commit_msg>patron-client: correct social media links in footer<commit_after>import React, { PropTypes } from 'react'
import { injectIntl, intlShape, defineMessages, FormattedMessage, FormattedHTMLMessage } from 'react-intl'

class Footer extends React.Component {
  constructor (props) {
    super(props)
    this.handleChangeLanguage = this.handleChangeLanguage.bind(this)
  }

  handleChangeLanguage () {
    this.props.loadLanguage(this.props.locale === 'no' ? 'en' : 'no')
  }

  ignore (event) {
    event.preventDefault()
  }

  render () {
    return (

              <FormattedHTMLMessage {...messages.info} />

                    <FormattedMessage {...messages.contactUs} />

                    <FormattedMessage {...messages.openingHours} />

                    <FormattedMessage {...messages.howToLoan} />

                    <FormattedMessage {...messages.languageChoice} />

                    <FormattedMessage {...messages.aboutUs} />

                    <FormattedMessage {...messages.faq} />

                    <FormattedMessage {...messages.feedback} />

Personvern og informasjonskapsler

             OSLO KOMMUNE

    )
  }
}

Footer.propTypes = {
  loadLanguage: PropTypes.func.isRequired,
  locale: PropTypes.string.isRequired,
  intl: intlShape.isRequired
}

export const messages = defineMessages({
  contactUs: {
    id: 'Footer.contactUs',
    description: 'Label for the contact us link',
    defaultMessage: 'Contact us'
  },
  openingHours: {
    id: 'Footer.openingHours',
    description: 'Label for the opening hours link',
    defaultMessage: 'Opening hours'
  },
  languageChoice: {
    id: 'Footer.languageChoice',
    description: 'Label for the link that changes the language, will be displayed in the language being chosen',
    defaultMessage: 'På norsk'
  },
  currentLanguage: {
    id: 'Footer.currentLanguage',
    description: 'The value of the currently applied language',
    defaultMessage: 'English'
  },
  aboutUs: {
    id: 'Footer.aboutUs',
    description: 'Label for the about us link',
    defaultMessage: 'About us'
  },
  info: {
    id: 'Footer.info',
    description: 'The text containing the address, telephone number and other information',
    defaultMessage: 'Arne Garborgs plass 4
0179 Oslo
Telephone: +47 23 43 29 00

Chief editor:
Knut Skansen, director
'
  },
  feedback: {
    id: 'Footer.feedback',
    description: 'Feeback description',
    defaultMessage: 'Give feedback on search/my page'
  },
  faq: {
    id: 'Footer.faq',
    description: 'Faq link in footer menu',
    defaultMessage: 'FAQ'
  },
  howToLoan: {
    id: 'Footer.howToLoan',
    description: 'Link describing how to loan',
    defaultMessage: 'How to loan'
  }
})

export default injectIntl(Footer)
<|endoftext|>


<commit_before>module.exports = function(survey){
    return '
'+survey.body+'
'
}<commit_msg>survey templte modified<commit_after>module.exports = function(survey){
    return `

I'd like your input!

Please answer the following question:

${survey.body}

            Yes

            No

  `;
}<|endoftext|>


<commit_before>(function () {
  "use strict";

  angular
    .module('mnPluggableUiRegistry', [])
    .provider('mnPluggableUiRegistry', mnPluggableUiRegistryProvider)
    .factory('mnPluggableTabUtil', mnPluggableTabUtil)
    .directive('mnPluggableUiTabs', mnPluggableUiTabs);

  function mnPluggableTabUtil() {
    var defaultTemplate = "
{{ ::pluggableUiConfig.name }}
";
    var tabTemplates = {
      adminTab:    "
{{ ::pluggableUiConfig.name }}
",
      indexesTab:  "
{{ ::pluggableUiConfig.name }}
"
    };

    return {
      getTabTemplate: getTabTemplate
    };

    function getTabTemplate(tabBarName) {
      return tabTemplates[tabBarName] || defaultTemplate;
    }
  }

  function mnPluggableUiTabs(mnPluggableUiRegistry, mnPluggableTabUtil, $compile) {

    return {
      link: link
    };

    function link($scope, $element, $attrs) {
      var pluggableUiConfigs = mnPluggableUiRegistry.getConfigsByTabBarName($attrs.mnTabBarName);
      if (!pluggableUiConfigs.length) {
        return;
      }
      angular.forEach(pluggableUiConfigs, function (config) {
        config.ngShow = config.ngShow || true;
        $scope.pluggableUiConfig = config;
        if (config.after) {
          var targetTab = $element[0].querySelector("[mn-tab='" + config.after + "']");
          if (!targetTab) {
            throw new Error("There is no tab with mn-tab=" + config.after + " in " + $attrs.mnTabBarName);
          }
          var compiled = $compile(mnPluggableTabUtil.getTabTemplate($attrs.mnTabBarName))($scope);
          angular.element(targetTab).after(compiled);
        } else {
          var compiled = $compile(mnPluggableTabUtil.getTabTemplate($attrs.mnTabBarName))($scope);
          $element.append(compiled);
        }
      });
    }
  }

  function mnPluggableUiRegistryProvider() {
    var _configs = [];

    this.registerConfig = registerConfig;
    this.$get = mnPluggableUiRegistryFactory;

    /**
     * Registers a UI component config with the pluggable UI registry service so that the
     * component can be displayed in the UI.
     *
     * The pluggable UI framework understands the following attributes of the config:
     * 1) name - the name of the UI component to be registered. This is used appropriately
     *    in the UI when the component is displayed. For instance, if the component is
     *    rendered in a tab, the value of name will be used in the tab name.
     * 2) state - the name of the ui.router state for this component. When the time comes to
     *    render the component this is what is used to find it.
     * 3) plugIn - where the component plugs-in to the UI. Currently only 2 values are supported.
     *    If a value other than these 2 is specified, the component won't be displayed.
     *    a) 'adminTab' - to be used in the case that the pluggable component wishes to plug-in to
     *        global nav bar
     *    b) 'settingsTab' - to be used in the case that the pluggable component wishes to
     *        plug in to the the settings tab bar
     *
     * Any attributes beyond these are not understood and are ignored.
     * @param config
     */
    function registerConfig(config) {
      _configs.push(config);
    }

    function mnPluggableUiRegistryFactory() {

      return {
        getConfigs: getConfigs,
        getConfigsByTabBarName: getConfigsByTabBarName
      };

      /**
       * Returns a list of the registered plugabble UI configs.
       * @returns {Array}
       */
      function getConfigs() {
        return _configs;
      }
      function getConfigsByTabBarName(tabBarName) {
        return _.filter(_configs, {plugIn: tabBarName});
      }
    }
  }
}());
<commit_msg>MB-19725: allow more than one pluggable tab per tab container<commit_after>(function () {
  "use strict";

  angular
    .module('mnPluggableUiRegistry', [])
    .provider('mnPluggableUiRegistry', mnPluggableUiRegistryProvider)
    .factory('mnPluggableTabUtil', mnPluggableTabUtil)
    .directive('mnPluggableUiTabs', mnPluggableUiTabs);

  function mnPluggableTabUtil() {

    return {
      getTabTemplate: getTabTemplate
    };

    function getTabTemplate(tabBarName, index) {
      var configLocation = "::" + tabBarName + ".pluggableUiConfigs[" + index + "]";
      var defaultTemplate = "
{{ " + configLocation + ".name }}
"
      var tabTemplates = {
        adminTab: "
{{" + configLocation + ".name}}
",
        indexesTab:  "
{{" + configLocation + ".name}}
"
     };

      return tabTemplates[tabBarName] || defaultTemplate;
    }
  }

  function mnPluggableUiTabs(mnPluggableUiRegistry, mnPluggableTabUtil, $compile) {

    return {
      link: link
    };

    function link($scope, $element, $attrs) {
      var pluggableUiConfigs = mnPluggableUiRegistry.getConfigsByTabBarName($attrs.mnTabBarName);
      if (!pluggableUiConfigs.length) {
        return;
      }
      $scope[$attrs.mnTabBarName] = {
        pluggableUiConfigs: pluggableUiConfigs
      };
      angular.forEach(pluggableUiConfigs, function (config, index) {
        config.ngShow = config.ngShow == undefined ? true : config.ngShow;
        if (config.after) {
          var targetTab = $element[0].querySelector("[mn-tab='" + config.after + "']");
          if (!targetTab) {
            throw new Error("There is no tab with mn-tab=" + config.after + " in " + $attrs.mnTabBarName);
          }
          var compiled = $compile(mnPluggableTabUtil.getTabTemplate($attrs.mnTabBarName, index))($scope);
          angular.element(targetTab).after(compiled);
        } else {
          var compiled = $compile(mnPluggableTabUtil.getTabTemplate($attrs.mnTabBarName, index))($scope);
          $element.append(compiled);
        }
      });
    }
  }

  function mnPluggableUiRegistryProvider() {
    var _configs = [];

    this.registerConfig = registerConfig;
    this.$get = mnPluggableUiRegistryFactory;

    /**
     * Registers a UI component config with the pluggable UI registry service so that the
     * component can be displayed in the UI.
     *
     * The pluggable UI framework understands the following attributes of the config:
     * 1) name - the name of the UI component to be registered. This is used appropriately
     *    in the UI when the component is displayed. For instance, if the component is
     *    rendered in a tab, the value of name will be used in the tab name.
     * 2) state - the name of the ui.router state for this component. When the time comes to
     *    render the component this is what is used to find it.
     * 3) plugIn - where the component plugs-in to the UI. Currently only 2 values are supported.
     *    If a value other than these 2 is specified, the component won't be displayed.
     *    a) 'adminTab' - to be used in the case that the pluggable component wishes to plug-in to
     *        global nav bar
     *    b) 'settingsTab' - to be used in the case that the pluggable component wishes to
     *        plug in to the the settings tab bar
     *
     * Any attributes beyond these are not understood and are ignored.
     * @param config
     */
    function registerConfig(config) {
      _configs.push(config);
    }

    function mnPluggableUiRegistryFactory() {

      return {
        getConfigs: getConfigs,
        getConfigsByTabBarName: getConfigsByTabBarName
      };

      /**
       * Returns a list of the registered plugabble UI configs.
       * @returns {Array}
       */
      function getConfigs() {
        return _configs;
      }
      function getConfigsByTabBarName(tabBarName) {
        return _.filter(_configs, {plugIn: tabBarName});
      }
    }
  }
}());
<|endoftext|>


<commit_before>---
yuiCompress: !!bool false
---
(function($, window, document) {

    var $c = $('#footer'),
        $f = $c.find('#feedback'),
        $i = $c.find('#input'),
        $fi = $c.find('#fake-input'),
        $cursor = $c.find('#cursor'),
        $dummy = $('<div />', {id: 'dummy'}).appendTo('body'),
        cEnabled = false,
        cInput = '',
        cLastKeyWasTab = false,
        cMaxLen = 40,
        cToggleKey = 192, // this is ~
        cmds = {}, // defined later
        spaceWidth = 0, // defined later
        siteMap = <%= get_sitemap %>, // used for completion (ls/cd)
        siteMapIds = <%= get_sitemap_ids %>, // used for navigation

    /**
     * Useful to create alias functions.
     */
    alias = function(cmd, type) {
        return function() {
            if (type === 'completers') {
                return completers[cmd].apply(this, arguments);
            }
            return cmds[cmd].apply(this, arguments);
        }
    },

    /**
     * Given a command, return options what the command could be.
     */
    completeCmd = function(cmd) {
        var cmdSplit = cmd.split(/\s+/g);
        if (cmdSplit.length === 1) {
            return helpers.filterPrefixed(cmd, Object.keys(cmds));
        } else if (completers.hasOwnProperty(cmdSplit[0])) {
            cmd = cmdSplit.splice(0, 1);
            var matches = completers[cmd].apply(this, cmdSplit);
            matches.prefix = cmd;
            return matches;
        }
        return [];
    },

    /**
     * Encode HTML entities in the given string, and make every space a
     * non-breaking one. Useful when showing user input to user.
     *
     * \param str The string to encode.
     * \param plainStr Optional boolean, default \c false. When true, return
     *            a string with plain characters instead of HTML escape
     *            sequences. The string will be Unicode.
     */
    encode = function(str, plainStr) {
        if (typeof(str) !== 'string') {
            return '';
        }
        if (typeof(plainStr) !== 'boolean') {
            plainStr = false;
        }
        return str.replace(/[\u00A0-\u9999<>\& ]/gim, function(i) {
            if (i === ' ') {
                return (plainStr ? '\u00A0' : ' ');
            }
            return (plainStr ? i.charAt(0) : '&#' + i.charCodeAt(0) + ';');
        });
    },

    /**
     * Compute the width of the given string using a dummy element.
     */
    getTextWidth = function(str) {
        // use a cached dummy
        return $dummy.html(str).width();
    },

    /**
     * Triggered for \c keypress events on the window. Should toggle the
     * console status for the activation key.
     */
    onKeyDown = function(e) {
        // handle tab here, before it changes the focus
        // also respond to global toggle key events
        if (e.which === 9 || e.which === cToggleKey) {
            onKeyUp(e);
        }
    },

    /**
     * Handler for key press events in the console input. Should only do
     * something if the console is enabled.
     */
    onKeyUp = function(e) {
        if (cEnabled && e.which === cToggleKey) {
            return;
        }
        // see if we should handle this key press
        var handled = true,
            thisKeyIsTab = false;
        switch (e.which) {
            case cToggleKey: // is this to blur the console?
                cEnabled = !cEnabled;
                if (cEnabled) {
                    $fi.focus();
                } else {
                    $i.blur();
                }
                break;
            case 8: // backspace
                // we only update the input after the switch, that is all
                break;
            case 9: // tab
                thisKeyIsTab = true;
                if (e.type === 'keyup') {
                    // already handled in keydown
                    break;
                }
                var matches = completeCmd(cInput);
                if (matches.length === 1) {
                    setInput((matches.prefix ? matches.prefix + ' ' : '') +
                        matches[0] + (matches.noSpace ? '' : ' '));
                } else if (matches.length > 1 && cLastKeyWasTab) {
                    setFeedback(matches);
                }
                break;
            case 13: // enter
                setFeedback(parseCmd(cInput));
                cmds.clear();
                break;
            default:
                handled = false;
        }
        // did we do anything? then mark event as handled
        if (handled) {
            e.preventDefault();
            e.stopPropagation();
            cLastKeyWasTab = thisKeyIsTab;
            // update text in input
            updateInput();
        }
        // always update cursor position, because we do not explicitly handle
        // home, end, arrow keys, et cetera
        updateCursorPosition();
    },

    /**
     * See if we know the given command and act on it.
     */
    parseCmd = function(cmd) {
        cmd = cmd.trim().split(/\s+/g);
        if (cmd.length > 0) {
            if (cmds.hasOwnProperty(cmd[0])) {
                var cmdNm = cmd.splice(0, 1);
                return cmds[cmdNm[0]].apply(this, cmd);
            }
        }
        return 'desh: Unknown command "' + cmd + '".';
    },

    /**
     * Shorthand function to create a function that returns its argument.
     */
    s = function(str) {
        return function() {
            return str;
        }
    },

    /**
     * Set the contents of the feedback.
     */
    setFeedback = function(str) {
        if (typeof(str) !== 'string') {
            if ((Array.isArray && Array.isArray(str)) ||
                    str instanceof Array) {
                str = str.join('    ');
            } else {
                str = '';
            }
        }
        $f.html(encode(str.toLowerCase()));
        $c.toggleClass('tall', (str != ''));
    },

    /**
     * Set the contents of the input.
     *
     * Both the hidden and the fake one. Does need a call to updateInput to
     * see the effect though.
     */
    setInput = function(str) {
        cInput = str.toLowerCase();
        $i.val(cInput);
    },

    /**
     * Update the position of the cursor so it matches the position of the
     * cursor in the hidden input element.
     */
    updateCursorPosition = function() {
        // position cursor absolute iff console is enabled
        $cursor.toggleClass('dynamic', cEnabled);
        if (cEnabled) {
            // determine cursor position
            var input = $i.get(0),
                selS = input.selectionStart,
                selE = input.selectionEnd,
                lastChar = cInput.substr(selE, 1);
            // selection is not supported, only cursor movement
            if (selE !== selS) {
                input.selectionStart = selE;
            }
            // get the length of the string up to the cursor
            $cursor.css('margin-left', getTextWidth(encode('>' + cInput.substr(0, selE))));
            if (lastChar === '') {
                $cursor.html(' ').css('width', '.7em');
            } else if (lastChar === ' ') {
                $cursor.html(' ').css('width', spaceWidth);
            } else {
                $cursor.text(lastChar).css('width', '');
            }
        } else {
            $cursor.html(' ').css({
                marginLeft: '',
                width: ''
            });
        }
    },

    /**
     * Update input text from the global \c cInput variable.
     */
    updateInput = function() {
        $fi.html(encode(cInput));
    },

    /**
     * Command definitions. Hey. Cheater! Reading the source and all that.
     * Well. Have fun, hacker :-)
     */
    cmds = {
        'about': alias('help'),
        'about-me': s('This is desh, the Denvelop Shell, programmed by Thom.'),
        'cd': function(path) {
            if (typeof(path) === 'undefined') {
                path = '/';
            }
            // make path absolute
            path = helpers.dirResolve(location.pathname, path);
            // if the path can be found in the sitemap, go there, otherwise error
            if (siteMap.indexOf(path) === -1) {
                return 'desh: Invalid path.';
            }
            // navigate to page
            $(window).trigger('denvelop-navigate', [path, siteMapIds[path]]);
        },
        'clear': function() { setInput(''); updateInput(); },
        'help': s('desh: This is a tiny console for geeks. No room for ' +
                    'further help, sorry.'),
        'ls': function(path) {
            var cwd = location.pathname,
                items = [], i, l, item, itemIndex;
            if (typeof(path) === 'string') {
                cwd = helpers.dirResolve(cwd, path);
                if (cwd === null) {
                    return 'desh: Invalid path.';
                }
            }
            // consider all items the user may mean
            for (i = 0, l = siteMap.length; i < l; ++i) {
                if (siteMap[i].startsWith(cwd)) {
                    item = siteMap[i].substr(cwd.length);
                    // the item itself is referred to with a single dot
                    if (item === '') {
                        item = '.';
                    }
                    // we do not list directories recursively
                    itemIndex = item.indexOf('/');
                    if (itemIndex < 0 || itemIndex === item.length - 1) {
                        items.push(item);
                    }
                }
            }
            // if may be possible to move up a directory
            if (cwd !== '/' && items.indexOf('.') !== -1) {
                items.push('..');
            }
            // present items in sorted order
            items.sort();
            return items;
        },
        'theme': function(name) {
            $(window).one('denvelop-theme-switched', function(e, switched, newTheme) {
                if (!switched) {
                    if (typeof(name) === 'undefined') {
                        setFeedback('desh: Current theme is "' + newTheme + '".');
                    } else {
                        setFeedback('desh: Invalid theme name.');
                    }
                }
            });
            $(window).trigger('denvelop-theme-switch', [name]);
        }
    },

    /**
     * Tab completion functions per command.
     */
    completers = {
        'cd': alias('ls', 'completers'),
        'ls': function(path) {
            if (typeof(path) !== 'string') return [];
            // absolute path?
            var absPath = false;
            if (path.charAt(0) === '/') {
                path = path.substr(1);
                absPath = true;
            }
            // split in directory and basename
            var dirName = path.split('/'),
                baseName = dirName.pop();
            if (!baseName) baseName = '';
            if (baseName === '.' || baseName === '..') {
                dirName.push(baseName);
                baseName = '';
            }
            dirName = dirName.join('/');
            if (absPath) {
                dirName = '/' + dirName;
            }
            // use this to call ls and filter the results
            var lsResult = cmds.ls(dirName);
            if (typeof(lsResult) === 'string') {
                return lsResult; // error message
            }
            lsResult = helpers.filterPrefixed(baseName, lsResult).
                map(function(c) {
                    if (dirName !== '') {
                        return dirName + (dirName === '/' ? '' : '/') + c;
                    }
                    return c;
                });
            lsResult.noSpace = true;
            return lsResult;
        },
        'theme': function(name) {
            var themeNames = [];
            $('link[rel$=stylesheet]').each(function() {
                themeNames.push($(this).attr('title').match(/-(\w+)$/)[1]);
            });
            return helpers.filterPrefixed(name, themeNames);
        }
    },

    /**
     * Helper function definitions.
     */
    helpers = {
        dirDiff: function(dirA, dirB) {
            var dA = helpers.trimSlashes(dirA).split('/'),
                dB = helpers.trimSlashes(dirB).split('/'),
                diff = [],
                offset = 0;
            while (dA.length > offset && dB.length > offset &&
                    dA[offset] === dB[offset]) {
                offset++;
            }
            for (var i = offset; i < dA.length; i++) { diff.push('..'); }
            for (var i = offset; i < dB.length; i++) { diff.push(dB[i]); }
            if (diff.length === 0) { diff.push('.'); }
            return diff.join('/');
        },
        dirResolve: function(dir, subDir) {
            if (dir.charAt(0) !== '/') {
                console.error('dirResolve can only resolve paths from absolute paths.');
            }
            var d = dir.split('/'),
                lastSlash = (d[d.length - 1] === '');
            if (lastSlash) {
                d.pop();
            }
            // remove first empty component, since we asserted that dir is absolute
            d.splice(0, 1);

            subDir = (subDir === '' ? [] : subDir.split('/'));
            for (var i = 0; i < subDir.length; i++) {
                if (subDir[i] === '') {
                    if (i === 0) {
                        d = ['']; // absolute path support
                    }
                    continue;
                }
                if (subDir[i] === '.') continue;
                if (subDir[i] === '..') {
                    if (typeof(d.pop()) === 'undefined') {
                        return null;
                    }
                } else {
                    d.push(subDir[i]);
                }
            }

            if (lastSlash) {
                d.push('');
            }
            var result = d.join('/');
            if (result.charAt(0) !== '/') {
                result = '/' + result;
            }
            return result;
        },
        filterPrefixed: function(key, haystack) {
            var matches = [];
            for (var i = 0; i < haystack.length; i++) {
                if (haystack[i].indexOf(key) === 0) {
                    matches.push(haystack[i]);
                }
            }
            return matches;
        },
        trimSlashes: function(str) {
            return str.replace(/\/^|\/$/g, '');
        }
    };

    /**
     * Initialize console on document load.
     */
    $(function() {
        // calculate the width of a non-breaking space
        spaceWidth = getTextWidth(' ');

        if ($c.size() > 0) {
            var initialText = $fi.text(),
                feedbackUp = $f.text();
            // enable toggling the console
            $(window).on('keydown', onKeyDown);
            // enable console writing
            $i.on('keyup', onKeyUp);
            $i.on('input', function(e) {
                // in case a user left with alt+tab
                cEnabled = true;
                $c.addClass('active');
                // update input contents
                cInput = $i.val().toLowerCase();
                updateInput();
                updateCursorPosition();
            });
            // update state if console is blurred/focused
            $fi.on('focus', function() {
                // in case focus happens outside our control (e.g. by clicking)
                cEnabled = true;
                $c.addClass('active');
                updateInput();
                setFeedback(feedbackUp);
                $i.focus();
                updateCursorPosition();
            });
            $i.on('blur', function() {
                cEnabled = false; // in case blur happens outside our control
                $c.removeClass('active');
                $fi.text(initialText);
                feedbackUp = $f.text();
                setFeedback('');
                updateCursorPosition();
            });
        }
    });

}(jQuery, window, document));
<commit_msg>Make it possible to pass extra parameters to aliases<commit_after>---
yuiCompress: !!bool false
---
(function($, window, document) {

    var $c = $('#footer'),
        $f = $c.find('#feedback'),
        $i = $c.find('#input'),
        $fi = $c.find('#fake-input'),
        $cursor = $c.find('#cursor'),
        $dummy = $('<div />', {id: 'dummy'}).appendTo('body'),
        cEnabled = false,
        cInput = '',
        cLastKeyWasTab = false,
        cMaxLen = 40,
        cToggleKey = 192, // this is ~
        cmds = {}, // defined later
        spaceWidth = 0, // defined later
        siteMap = <%= get_sitemap %>, // used for completion (ls/cd)
        siteMapIds = <%= get_sitemap_ids %>, // used for navigation

    /**
     * Useful to create alias functions.
     */
    alias = function(cmd, type, extraArguments) {
        return function() {
            if (type === 'completers') {
                if (typeof(extraArguments) === 'object') {
                    Array.prototype.push.call(arguments, extraArguments);
                }
                return completers[cmd].apply(this, arguments);
            }
            return cmds[cmd].apply(this, arguments);
        }
    },

    /**
     * Given a command, return options what the command could be.
     */
    completeCmd = function(cmd) {
        var cmdSplit = cmd.split(/\s+/g);
        if (cmdSplit.length === 1) {
            return helpers.filterPrefixed(cmd, Object.keys(cmds));
        } else if (completers.hasOwnProperty(cmdSplit[0])) {
            cmd = cmdSplit.splice(0, 1);
            var matches = completers[cmd].apply(this, cmdSplit);
            matches.prefix = cmd;
            return matches;
        }
        return [];
    },

    /**
     * Encode HTML entities in the given string, and make every space a
     * non-breaking one. Useful when showing user input to user.
     *
     * \param str The string to encode.
     * \param plainStr Optional boolean, default \c false. When true, return
     *            a string with plain characters instead of HTML escape
     *            sequences. The string will be Unicode.
     */
    encode = function(str, plainStr) {
        if (typeof(str) !== 'string') {
            return '';
        }
        if (typeof(plainStr) !== 'boolean') {
            plainStr = false;
        }
        return str.replace(/[\u00A0-\u9999<>\& ]/gim, function(i) {
            if (i === ' ') {
                return (plainStr ? '\u00A0' : ' ');
            }
            return (plainStr ? i.charAt(0) : '&#' + i.charCodeAt(0) + ';');
        });
    },

    /**
     * Compute the width of the given string using a dummy element.
     */
    getTextWidth = function(str) {
        // use a cached dummy
        return $dummy.html(str).width();
    },

    /**
     * Triggered for \c keypress events on the window. Should toggle the
     * console status for the activation key.
     */
    onKeyDown = function(e) {
        // handle tab here, before it changes the focus
        // also respond to global toggle key events
        if (e.which === 9 || e.which === cToggleKey) {
            onKeyUp(e);
        }
    },

    /**
     * Handler for key press events in the console input. Should only do
     * something if the console is enabled.
     */
    onKeyUp = function(e) {
        if (cEnabled && e.which === cToggleKey) {
            return;
        }
        // see if we should handle this key press
        var handled = true,
            thisKeyIsTab = false;
        switch (e.which) {
            case cToggleKey: // is this to blur the console?
                cEnabled = !cEnabled;
                if (cEnabled) {
                    $fi.focus();
                } else {
                    $i.blur();
                }
                break;
            case 8: // backspace
                // we only update the input after the switch, that is all
                break;
            case 9: // tab
                thisKeyIsTab = true;
                if (e.type === 'keyup') {
                    // already handled in keydown
                    break;
                }
                var matches = completeCmd(cInput);
                if (matches.length === 1) {
                    setInput((matches.prefix ? matches.prefix + ' ' : '') +
                        matches[0] + (matches.noSpace ? '' : ' '));
                } else if (matches.length > 1 && cLastKeyWasTab) {
                    setFeedback(matches);
                }
                break;
            case 13: // enter
                setFeedback(parseCmd(cInput));
                cmds.clear();
                break;
            default:
                handled = false;
        }
        // did we do anything? then mark event as handled
        if (handled) {
            e.preventDefault();
            e.stopPropagation();
            cLastKeyWasTab = thisKeyIsTab;
            // update text in input
            updateInput();
        }
        // always update cursor position, because we do not explicitly handle
        // home, end, arrow keys, et cetera
        updateCursorPosition();
    },

    /**
     * See if we know the given command and act on it.
     */
    parseCmd = function(cmd) {
        cmd = cmd.trim().split(/\s+/g);
        if (cmd.length > 0) {
            if (cmds.hasOwnProperty(cmd[0])) {
                var cmdNm = cmd.splice(0, 1);
                return cmds[cmdNm[0]].apply(this, cmd);
            }
        }
        return 'desh: Unknown command "' + cmd + '".';
    },

    /**
     * Shorthand function to create a function that returns its argument.
     */
    s = function(str) {
        return function() {
            return str;
        }
    },

    /**
     * Set the contents of the feedback.
     */
    setFeedback = function(str) {
        if (typeof(str) !== 'string') {
            if ((Array.isArray && Array.isArray(str)) ||
                    str instanceof Array) {
                str = str.join('    ');
            } else {
                str = '';
            }
        }
        $f.html(encode(str.toLowerCase()));
        $c.toggleClass('tall', (str != ''));
    },

    /**
     * Set the contents of the input.
     *
     * Both the hidden and the fake one. Does need a call to updateInput to
     * see the effect though.
     */
    setInput = function(str) {
        cInput = str.toLowerCase();
        $i.val(cInput);
    },

    /**
     * Update the position of the cursor so it matches the position of the
     * cursor in the hidden input element.
     */
    updateCursorPosition = function() {
        // position cursor absolute iff console is enabled
        $cursor.toggleClass('dynamic', cEnabled);
        if (cEnabled) {
            // determine cursor position
            var input = $i.get(0),
                selS = input.selectionStart,
                selE = input.selectionEnd,
                lastChar = cInput.substr(selE, 1);
            // selection is not supported, only cursor movement
            if (selE !== selS) {
                input.selectionStart = selE;
            }
            // get the length of the string up to the cursor
            $cursor.css('margin-left', getTextWidth(encode('>' + cInput.substr(0, selE))));
            if (lastChar === '') {
                $cursor.html(' ').css('width', '.7em');
            } else if (lastChar === ' ') {
                $cursor.html(' ').css('width', spaceWidth);
            } else {
                $cursor.text(lastChar).css('width', '');
            }
        } else {
            $cursor.html(' ').css({
                marginLeft: '',
                width: ''
            });
        }
    },

    /**
     * Update input text from the global \c cInput variable.
     */
    updateInput = function() {
        $fi.html(encode(cInput));
    },

    /**
     * Command definitions. Hey. Cheater! Reading the source and all that.
     * Well. Have fun, hacker :-)
     */
    cmds = {
        'about': alias('help'),
        'about-me': s('This is desh, the Denvelop Shell, programmed by Thom.'),
        'cd': function(path) {
            if (typeof(path) === 'undefined') {
                path = '/';
            }
            // make path absolute
            path = helpers.dirResolve(location.pathname, path);
            // if the path can be found in the sitemap, go there, otherwise error
            if (siteMap.indexOf(path) === -1) {
                return 'desh: Invalid path.';
            }
            // navigate to page
            $(window).trigger('denvelop-navigate', [path, siteMapIds[path]]);
        },
        'clear': function() { setInput(''); updateInput(); },
        'help': s('desh: This is a tiny console for geeks. No room for ' +
                    'further help, sorry.'),
        'ls': function(path) {
            var cwd = location.pathname,
                items = [], i, l, item, itemIndex;
            if (typeof(path) === 'string') {
                cwd = helpers.dirResolve(cwd, path);
                if (cwd === null) {
                    return 'desh: Invalid path.';
                }
            }
            // consider all items the user may mean
            for (i = 0, l = siteMap.length; i < l; ++i) {
                if (siteMap[i].startsWith(cwd)) {
                    item = siteMap[i].substr(cwd.length);
                    // the item itself is referred to with a single dot
                    if (item === '') {
                        item = '.';
                    }
                    // we do not list directories recursively
                    itemIndex = item.indexOf('/');
                    if (itemIndex < 0 || itemIndex === item.length - 1) {
                        items.push(item);
                    }
                }
            }
            // if may be possible to move up a directory
            if (cwd !== '/' && items.indexOf('.') !== -1) {
                items.push('..');
            }
            // present items in sorted order
            items.sort();
            return items;
        },
        'theme': function(name) {
            $(window).one('denvelop-theme-switched', function(e, switched, newTheme) {
                if (!switched) {
                    if (typeof(name) === 'undefined') {
                        setFeedback('desh: Current theme is "' + newTheme + '".');
                    } else {
                        setFeedback('desh: Invalid theme name.');
                    }
                }
            });
            $(window).trigger('denvelop-theme-switch', [name]);
        }
    },

    /**
     * Tab completion functions per command.
     */
    completers = {
        'cd': alias('ls', 'completers'),
        'ls': function(path) {
            if (typeof(path) !== 'string') return [];
            // absolute path?
            var absPath = false;
            if (path.charAt(0) === '/') {
                path = path.substr(1);
                absPath = true;
            }
            // split in directory and basename
            var dirName = path.split('/'),
                baseName = dirName.pop();
            if (!baseName) baseName = '';
            if (baseName === '.' || baseName === '..') {
                dirName.push(baseName);
                baseName = '';
            }
            dirName = dirName.join('/');
            if (absPath) {
                dirName = '/' + dirName;
            }
            // use this to call ls and filter the results
            var lsResult = cmds.ls(dirName);
            if (typeof(lsResult) === 'string') {
                return lsResult; // error message
            }
            lsResult = helpers.filterPrefixed(baseName, lsResult).
                map(function(c) {
                    if (dirName !== '') {
                        return dirName + (dirName === '/' ? '' : '/') + c;
                    }
                    return c;
                });
            lsResult.noSpace = true;
            return lsResult;
        },
        'theme': function(name) {
            var themeNames = [];
            $('link[rel$=stylesheet]').each(function() {
                themeNames.push($(this).attr('title').match(/-(\w+)$/)[1]);
            });
            return helpers.filterPrefixed(name, themeNames);
        }
    },

    /**
     * Helper function definitions.
     */
    helpers = {
        dirDiff: function(dirA, dirB) {
            var dA = helpers.trimSlashes(dirA).split('/'),
                dB = helpers.trimSlashes(dirB).split('/'),
                diff = [],
                offset = 0;
            while (dA.length > offset && dB.length > offset &&
                    dA[offset] === dB[offset]) {
                offset++;
            }
            for (var i = offset; i < dA.length; i++) { diff.push('..'); }
            for (var i = offset; i < dB.length; i++) { diff.push(dB[i]); }
            if (diff.length === 0) { diff.push('.'); }
            return diff.join('/');
        },
        dirResolve: function(dir, subDir) {
            if (dir.charAt(0) !== '/') {
                console.error('dirResolve can only resolve paths from absolute paths.');
            }
            var d = dir.split('/'),
                lastSlash = (d[d.length - 1] === '');
            if (lastSlash) {
                d.pop();
            }
            // remove first empty component, since we asserted that dir is absolute
            d.splice(0, 1);

            subDir = (subDir === '' ? [] : subDir.split('/'));
            for (var i = 0; i < subDir.length; i++) {
                if (subDir[i] === '') {
                    if (i === 0) {
                        d = ['']; // absolute path support
                    }
                    continue;
                }
                if (subDir[i] === '.') continue;
                if (subDir[i] === '..') {
                    if (typeof(d.pop()) === 'undefined') {
                        return null;
                    }
                } else {
                    d.push(subDir[i]);
                }
            }

            if (lastSlash) {
                d.push('');
            }
            var result = d.join('/');
            if (result.charAt(0) !== '/') {
                result = '/' + result;
            }
            return result;
        },
        filterPrefixed: function(key, haystack) {
            var matches = [];
            for (var i = 0; i < haystack.length; i++) {
                if (haystack[i].indexOf(key) === 0) {
                    matches.push(haystack[i]);
                }
            }
            return matches;
        },
        trimSlashes: function(str) {
            return str.replace(/\/^|\/$/g, '');
        }
    };

    /**
     * Initialize console on document load.
     */
    $(function() {
        // calculate the width of a non-breaking space
        spaceWidth = getTextWidth(' ');

        if ($c.size() > 0) {
            var initialText = $fi.text(),
                feedbackUp = $f.text();
            // enable toggling the console
            $(window).on('keydown', onKeyDown);
            // enable console writing
            $i.on('keyup', onKeyUp);
            $i.on('input', function(e) {
                // in case a user left with alt+tab
                cEnabled = true;
                $c.addClass('active');
                // update input contents
                cInput = $i.val().toLowerCase();
                updateInput();
                updateCursorPosition();
            });
            // update state if console is blurred/focused
            $fi.on('focus', function() {
                // in case focus happens outside our control (e.g. by clicking)
                cEnabled = true;
                $c.addClass('active');
                updateInput();
                setFeedback(feedbackUp);
                $i.focus();
                updateCursorPosition();
            });
            $i.on('blur', function() {
                cEnabled = false; // in case blur happens outside our control
                $c.removeClass('active');
                $fi.text(initialText);
                feedbackUp = $f.text();
                setFeedback('');
                updateCursorPosition();
            });
        }
    });

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


<commit_before>/**
 * Followers/following timeline def
 */
var FollowersTimeline = function() {

	sch.debug('Firing FollowersTimeline constructor');

	var thisFLT			 = this,
		$timeline		 = $('#timeline-followerslist'),
		$timelineWrapper = $timeline.parent();
	this.twit = new SpazTwit();

	this.mode = 'friends';

	this.followers_more_cursor = -1;

	/*
		redefine this to work with different selector
	*/
	this.getTimelineSelector = function() {
		return $timeline.selector;
	};

	/*
		redefine this to work with different selector
	*/
	this.getEntrySelector = function() {
		return this.getTimelineSelector()+' div.followers-row';
	};

	/**
	 * build the view menu
	 */
	this.buildViewMenu = function() {

		sch.debug('Firing buildViewMenu');

		var i, iMax, menu,
		    menuId  = 'view-followers-menu',
		    $toggle = $('#view-followerslist');

		// Build menu
		function menuItemId(id){
			return menuId + '-' + id;
		}
		function onMenuItemClick(e, itemData){
			thisULT.setlist(itemData.slug, itemData.username);
			jQuery('#' + menuItemId(itemData.id)).addClass('selected').
				siblings('.selected').removeClass('selected');
		}
		menu = thisFLT.viewmenu = new SpazMenu({
			base_id:    menuId,
			base_class: 'spaz-menu',
			li_class:   'spaz-menu-item',
			items_func: function(itemsData){
				var i, iMax, itemData, items = [];

				items = [
					{
						id:'view-followerslist-friends',
						label:$L('Following'),
						handler:function(){thisFLT.setMode('friends');}
					},
					{
						id:'view-followerslist-followers',
						label:$L('Followers'),
						handler:function(){thisFLT.setMode('followers');}
					}
				];

				sch.debug('items:' + sch.enJSON(items));

				return items;
			}
		});
		// binds the trigger element
		menu.bindToggle($toggle.selector, {
			afterShow: function(e){
				sch.debug('thisFLT.mode:'+thisFLT.mode);
				jQuery('#view-followerslist-' + thisFLT.mode).addClass('selected').
					siblings('.selected').removeClass('selected');
			}
		});
	};

	this.bindListeners = function() {
		/*
			bind load more button
		*/
		jQuery('#load-more-followers').live('click', function(e) {
			thisFLT.loadMore(e);
		});

	};

	/**
	 * sets/changes the mode
	 */
	this.setMode = function(mode) {
		sch.debug('thisFLT.mode:'+thisFLT.mode);
		sch.debug('mode:'+mode);

		$('#timeline-followerslist-full-name').
			text(mode == 'friends' ? 'following' : mode).show();

		// no change
		if (thisFLT.mode == mode) {
			return;
		}

		thisFLT.resetState();

		// change!
		thisFLT.mode = mode;
		thisFLT.refresh();
	};

	/**
	 * this does the real work of loading stuff
	 */
	this.refresh = function(event) {
		var thisA = this;

		var method_name = 'getFriendsList';

		var cursor = -1;
		if (event !== null && (sch.isNumber(event) || sch.isString(event))) {
			cursor = event;
		}

		sch.debug('CURSOR:'+ cursor);

		if (this.mode === 'friends') {
		    method_name = 'getFriendsList';
		} else if (this.mode === 'followers') {
		    method_name = 'getFollowersList';
		} else {
		    sch.debug('Invalid mode:%s', this.mode);
		    return;
		}

		this.twit[method_name](
			'@'+Spaz.Prefs.getUsername(),
			cursor,
			function(data, cursor_obj) {

				Spaz.Hooks.trigger('followers_timeline_data_success_start');

				/*
					if mode is wrong, don't add
				*/
				if ( (thisA.mode === 'friends' && method_name === 'getFollowersList') ) {
					return;
				} else if ( (thisA.mode === 'followers' && method_name === 'getFriendsList') ) {
					return;
				}

				if (sch.isArray(data)) {

	                // data = data.reverse();
					var no_dupes = [];

					for (var i=0; i < data.length; i++) {
						/*
							only add if it doesn't already exist
						*/
						if (!thisFLT.itemExists(data[i].id)) {
							no_dupes.push(data[i]);
						}

					};

				    sch.debug('no_dupes.length:', no_dupes.length);
					$timelineWrapper.children('.loading, .new-user').hide();

					thisA.addItems(no_dupes);

					/*
						reapply filtering
					*/
					$('#filter-followers').trigger('keyup');

				}

				sch.debug('cursor_obj:'+sch.enJSON(cursor_obj));

				if (cursor_obj && cursor_obj.next) {
				    thisFLT.followers_more_cursor = cursor_obj.next;
				}

				Spaz.UI.hideLoading();
				Spaz.UI.statusBar("Ready");

				Spaz.Hooks.trigger('followers_timeline_data_success_finish');
			},
			function(xhr, msg, exc) {
				sch.debug('EROROR in getFriends');

				var err_msg = "There was an error retrieving the user timeline";
				Spaz.UI.statusBar(err_msg);
				$timelineWrapper.children('.loading').hide();

				Spaz.UI.hideLoading();
			}
		);

	};

	/**
	 * triggered by the "load mode" button
	 */
	this.loadMore = function(event) {

	    sch.debug('this.followers_more_cursor:'+this.followers_more_cursor);

		this.refresh(this.followers_more_cursor);
	};

	/**
	 * add items to timeline
	 */
	this.addItems = function(items) {
		var html_items = this.renderItems(items);
		$timeline.append(html_items);
	};

	/**
	 * check if this item already exists
	 */
	this.itemExists = function(id) {
		sch.debug('Looking for:'+id );
		if (jQuery('div.followers-row[data-user-id="'+id+'"]', $timeline).length > 0) {
			sch.debug('found:'+id );
			return true;
		}
		sch.debug('Did not find:'+id );
		return false;
	};

	/**
	 * render the items for the timeline
	 */
	this.renderItems = function(items) {
		return Spaz.Tpl.parseArray('followerslist_row', items);
	};

	/**
	 * Reset the state
	 */
	this.resetState = function() {
	    this.followers_more_cursor = -1;
		this.mode = 'friends';
		$timeline.empty();
	};

	this.buildViewMenu();

	this.bindListeners();

	/*
		listen for account switches
	*/
	jQuery(document).bind('before_account_switched', function(e, account){
		thisFLT.viewmenu.hideAndDestroy();
		thisFLT.resetState();
	});

	/*
		after all these definitions, set the mode
	*/
	this.setMode('friends');

};

FollowersTimeline.prototype = new AppTimeline();
<commit_msg>Authenticate and set API URL correctly for Followers timeline<commit_after>/**
 * Followers/following timeline def
 */
var FollowersTimeline = function() {

	sch.debug('Firing FollowersTimeline constructor');

	var thisFLT			 = this,
		$timeline		 = $('#timeline-followerslist'),
		$timelineWrapper = $timeline.parent();

    var auth = Spaz.Prefs.getAuthObject();
	this.twit = new SpazTwit({auth: auth});
	Spaz.Data.setAPIUrl(this.twit);

	this.mode = 'friends';

	this.followers_more_cursor = -1;

	/*
		redefine this to work with different selector
	*/
	this.getTimelineSelector = function() {
		return $timeline.selector;
	};

	/*
		redefine this to work with different selector
	*/
	this.getEntrySelector = function() {
		return this.getTimelineSelector()+' div.followers-row';
	};

	/**
	 * build the view menu
	 */
	this.buildViewMenu = function() {

		sch.debug('Firing buildViewMenu');

		var i, iMax, menu,
		    menuId  = 'view-followers-menu',
		    $toggle = $('#view-followerslist');

		// Build menu
		function menuItemId(id){
			return menuId + '-' + id;
		}
		function onMenuItemClick(e, itemData){
			thisULT.setlist(itemData.slug, itemData.username);
			jQuery('#' + menuItemId(itemData.id)).addClass('selected').
				siblings('.selected').removeClass('selected');
		}
		menu = thisFLT.viewmenu = new SpazMenu({
			base_id:    menuId,
			base_class: 'spaz-menu',
			li_class:   'spaz-menu-item',
			items_func: function(itemsData){
				var i, iMax, itemData, items = [];

				items = [
					{
						id:'view-followerslist-friends',
						label:$L('Following'),
						handler:function(){thisFLT.setMode('friends');}
					},
					{
						id:'view-followerslist-followers',
						label:$L('Followers'),
						handler:function(){thisFLT.setMode('followers');}
					}
				];

				sch.debug('items:' + sch.enJSON(items));

				return items;
			}
		});
		// binds the trigger element
		menu.bindToggle($toggle.selector, {
			afterShow: function(e){
				sch.debug('thisFLT.mode:'+thisFLT.mode);
				jQuery('#view-followerslist-' + thisFLT.mode).addClass('selected').
					siblings('.selected').removeClass('selected');
			}
		});
	};

	this.bindListeners = function() {
		/*
			bind load more button
		*/
		jQuery('#load-more-followers').live('click', function(e) {
			thisFLT.loadMore(e);
		});

	};

	/**
	 * sets/changes the mode
	 */
	this.setMode = function(mode) {
		sch.debug('thisFLT.mode:'+thisFLT.mode);
		sch.debug('mode:'+mode);

		$('#timeline-followerslist-full-name').
			text(mode == 'friends' ? 'following' : mode).show();

		// no change
		if (thisFLT.mode == mode) {
			return;
		}

		thisFLT.resetState();

		// change!
		thisFLT.mode = mode;
		thisFLT.refresh();
	};

	/**
	 * this does the real work of loading stuff
	 */
	this.refresh = function(event) {
		var thisA = this;

		var method_name = 'getFriendsList';

		var cursor = -1;
		if (event !== null && (sch.isNumber(event) || sch.isString(event))) {
			cursor = event;
		}

		sch.debug('CURSOR:'+ cursor);

		if (this.mode === 'friends') {
		    method_name = 'getFriendsList';
		} else if (this.mode === 'followers') {
		    method_name = 'getFollowersList';
		} else {
		    sch.debug('Invalid mode:%s', this.mode);
		    return;
		}

		this.twit[method_name](
			'@'+Spaz.Prefs.getUsername(),
			cursor,
			function(data, cursor_obj) {

				Spaz.Hooks.trigger('followers_timeline_data_success_start');

				/*
					if mode is wrong, don't add
				*/
				if ( (thisA.mode === 'friends' && method_name === 'getFollowersList') ) {
					return;
				} else if ( (thisA.mode === 'followers' && method_name === 'getFriendsList') ) {
					return;
				}

				if (sch.isArray(data)) {

	                // data = data.reverse();
					var no_dupes = [];

					for (var i=0; i < data.length; i++) {
						/*
							only add if it doesn't already exist
						*/
						if (!thisFLT.itemExists(data[i].id)) {
							no_dupes.push(data[i]);
						}

					};

				    sch.debug('no_dupes.length:', no_dupes.length);
					$timelineWrapper.children('.loading, .new-user').hide();

					thisA.addItems(no_dupes);

					/*
						reapply filtering
					*/
					$('#filter-followers').trigger('keyup');

				}

				sch.debug('cursor_obj:'+sch.enJSON(cursor_obj));

				if (cursor_obj && cursor_obj.next) {
				    thisFLT.followers_more_cursor = cursor_obj.next;
				}

				Spaz.UI.hideLoading();
				Spaz.UI.statusBar("Ready");

				Spaz.Hooks.trigger('followers_timeline_data_success_finish');
			},
			function(xhr, msg, exc) {
				sch.debug('EROROR in getFriends');

				var err_msg = "There was an error retrieving the user timeline";
				Spaz.UI.statusBar(err_msg);
				$timelineWrapper.children('.loading').hide();

				Spaz.UI.hideLoading();
			}
		);

	};

	/**
	 * triggered by the "load mode" button
	 */
	this.loadMore = function(event) {

	    sch.debug('this.followers_more_cursor:'+this.followers_more_cursor);

		this.refresh(this.followers_more_cursor);
	};

	/**
	 * add items to timeline
	 */
	this.addItems = function(items) {
		var html_items = this.renderItems(items);
		$timeline.append(html_items);
	};

	/**
	 * check if this item already exists
	 */
	this.itemExists = function(id) {
		sch.debug('Looking for:'+id );
		if (jQuery('div.followers-row[data-user-id="'+id+'"]', $timeline).length > 0) {
			sch.debug('found:'+id );
			return true;
		}
		sch.debug('Did not find:'+id );
		return false;
	};

	/**
	 * render the items for the timeline
	 */
	this.renderItems = function(items) {
		return Spaz.Tpl.parseArray('followerslist_row', items);
	};

	/**
	 * Reset the state
	 */
	this.resetState = function() {
	    this.followers_more_cursor = -1;
		this.mode = 'friends';
		$timeline.empty();
	};

	this.buildViewMenu();

	this.bindListeners();

	/*
		listen for account switches
	*/
	jQuery(document).bind('before_account_switched', function(e, account){
		thisFLT.viewmenu.hideAndDestroy();
		thisFLT.resetState();
	});

	/*
		after all these definitions, set the mode
	*/
	this.setMode('friends');

};

FollowersTimeline.prototype = new AppTimeline();
<|endoftext|>