<commit_before>import Component from '@ember/component';
import {
  computed, get, observer, set, setProperties
} from '@ember/object';
import { alias, equal, union } from '@ember/object/computed';
import { on } from '@ember/object/evented';
import { next } from '@ember/runloop';
import { inject as service } from '@ember/service';
import { isEmpty } from '@ember/utils';
import ClusterDriver from 'shared/mixins/cluster-driver';
import { EKS_REGIONS } from 'shared/utils/amazon';
import { randomStr } from 'shared/utils/util';
import layout from './template';

export default Component.extend(ClusterDriver, {
  globalStore: service(),
  growl:       service(),
  settings:    service(),
  intl:        service(),

  layout,
  configField:     'eksConfig',
  step:            1,
  loading:         false,
  nodeForInfo:     null,
  loadingClusters: false,
  regionChoices:   EKS_REGIONS,
  errors:          null,
  otherErrors:     null,
  clusterErrors:   null,
  selectedCred:    null,

  isEdit:       equal('mode', 'edit'),
  clusterState: alias('model.originalCluster.state'),

  allErrors: union('errors', 'otherErrors', 'clusterErrors'),

  init() {
    this._super(...arguments);

    setProperties(this, {
      errors:        [],
      clusterErrors: [],
      otherErrors:   [],
    });

    if (this.isEdit) {
      const cloudCredId = get(this, 'model.cluster.eksConfig.amazonCredentialSecret');
      const cloudCred = (this.model.cloudCredentials || []).find((cc) => cc.id === cloudCredId);

      if (!isEmpty(cloudCred)) {
        next(() => {
          this.send('finishAndSelectCloudCredential', cloudCred);
        });
      }
    } else {
      this.bootstrapEksV2Cluster();
    }
  },

  actions: {
    finishAndSelectCloudCredential(cred) {
      if (isEmpty(cred)) {
        set(this, 'config.amazonCredentialSecret', null);
        set(this, 'selectedCred', null);
      } else {
        set(this, 'config.amazonCredentialSecret', cred.id);
        set(this, 'selectedCred', cred);

        this.send('fetchEksResources');
      }
    },

    async fetchEksResources(cb) {
      const errors   = [];
      let step       = 2;
      let allClusters;

      set(this, 'loadingClusters', true);

      try {
        const authCreds = this.authCreds();

        allClusters = await this.listEksClusters(authCreds);

        setProperties(this, {
          allClusters: (allClusters || []).map((c) => {
            return {
              label: c,
              value: c
            };
          }),
          step,
        });

        if (cb) {
          cb()
        }
      } catch (err) {
        errors.pushObject(`Failed to load Clusters from Amazon: ${ err.message }`);

        // EKS List Clusters API fails sometimes to list this, user cnn input a cluster name though so dont fail
        setProperties(this, {
          loadFailedAllClusters: true,
          errors
        });

        if (cb) {
          cb(false, err);
        }
      } finally {
        setProperties(this, {
          loadingClusters: false,
          step,
        });
      }
    },
  },

  regionOrCredentialChanged: on('init', observer('config.region', 'config.amazonCredentialSecret', function() {
    const {
      config: {
        region,
        amazonCredentialSecret,
      },
      loadingClusters
    } = this;

    if (!isEmpty(region) && !isEmpty(amazonCredentialSecret) && !loadingClusters) {
      this.send('fetchEksResources');
    }
  })),

  cloudCredentials: computed('model.cloudCredentials.[]', function() {
    const { model: { cloudCredentials } } = this;

    return cloudCredentials.filter((cc) => !isEmpty(get(cc, 'amazonec2credentialConfig')));
  }),

  disableImport: computed('step', 'config.{amazonCredentialSecret,displayName}', function() {
    const { step, config: { amazonCredentialSecret, displayName } } = this;

    if (step <= 2 && !isEmpty(amazonCredentialSecret) && !isEmpty(displayName)) {
      return false;
    }

    return true;
  }),

  doneSaving() {
    if (this.close) {
      this.close();
    }
  },

  bootstrapEksV2Cluster() {
    const eksConfig = this.globalStore.createRecord({
      displayName:       '',
      imported:          true,
      region:            'us-west-2',
      type:              'eksclusterconfigspec',
    });

    set(this, 'model.cluster.eksConfig', eksConfig);
    set(this, 'config', eksConfig);
  },

  listEksClusters(auth) {
    return new Promise((resolve, reject) => {
      const eks = new AWS.EKS(auth);

      eks.listClusters({}, (err, data) => {
        if (err) {
          console.log(err, err.stack);

          return reject(err);
        }

        return resolve(data.clusters);
      });
    })
  },

  authCreds() {
    let {
      config: {
        region,
        amazonCredentialSecret
      }
    } = this;

    const auth = {
      accessKeyId:         randomStr(),
      secretAccessKey:     randomStr(),
      region,
      httpOptions:         { cloudCredentialId: amazonCredentialSecret },
    };

    return auth;
  },
});
<commit_msg>clear errors and loadFailedAllClusters when refetching clusters<commit_after>import Component from '@ember/component';
import {
  computed, get, observer, set, setProperties
} from '@ember/object';
import { alias, equal, union } from '@ember/object/computed';
import { on } from '@ember/object/evented';
import { next } from '@ember/runloop';
import { inject as service } from '@ember/service';
import { isEmpty } from '@ember/utils';
import ClusterDriver from 'shared/mixins/cluster-driver';
import { EKS_REGIONS } from 'shared/utils/amazon';
import { randomStr } from 'shared/utils/util';
import layout from './template';

export default Component.extend(ClusterDriver, {
  globalStore: service(),
  growl:       service(),
  settings:    service(),
  intl:        service(),

  layout,
  configField:     'eksConfig',
  step:            1,
  loading:         false,
  nodeForInfo:     null,
  loadingClusters: false,
  regionChoices:   EKS_REGIONS,
  errors:          null,
  otherErrors:     null,
  clusterErrors:   null,
  selectedCred:    null,

  isEdit:       equal('mode', 'edit'),
  clusterState: alias('model.originalCluster.state'),

  allErrors: union('errors', 'otherErrors', 'clusterErrors'),

  init() {
    this._super(...arguments);

    setProperties(this, {
      errors:        [],
      clusterErrors: [],
      otherErrors:   [],
    });

    if (this.isEdit) {
      const cloudCredId = get(this, 'model.cluster.eksConfig.amazonCredentialSecret');
      const cloudCred = (this.model.cloudCredentials || []).find((cc) => cc.id === cloudCredId);

      if (!isEmpty(cloudCred)) {
        next(() => {
          this.send('finishAndSelectCloudCredential', cloudCred);
        });
      }
    } else {
      this.bootstrapEksV2Cluster();
    }
  },

  actions: {
    finishAndSelectCloudCredential(cred) {
      if (isEmpty(cred)) {
        set(this, 'config.amazonCredentialSecret', null);
        set(this, 'selectedCred', null);
      } else {
        set(this, 'config.amazonCredentialSecret', cred.id);
        set(this, 'selectedCred', cred);

        this.send('fetchEksResources');
      }
    },

    async fetchEksResources(cb) {
      const errors   = [];
      let step       = 2;
      let allClusters;

      set(this, 'loadingClusters', true);

      try {
        const authCreds = this.authCreds();

        allClusters = await this.listEksClusters(authCreds);

        setProperties(this, {
          allClusters: (allClusters || []).map((c) => {
            return {
              label: c,
              value: c
            };
          }),
          step,
        });

        if (cb) {
          cb()
        }
      } catch (err) {
        errors.pushObject(`Failed to load Clusters from Amazon: ${ err.message }`);

        // EKS List Clusters API fails sometimes to list this, user cnn input a cluster name though so dont fail
        setProperties(this, {
          loadFailedAllClusters: true,
          errors
        });

        if (cb) {
          cb(false, err);
        }
      } finally {
        setProperties(this, {
          loadingClusters: false,
          step,
        });
      }
    },
  },

  regionOrCredentialChanged: on('init', observer('config.region', 'config.amazonCredentialSecret', function() {
    const {
      config: {
        region,
        amazonCredentialSecret,
      },
      loadingClusters,
      errors
    } = this;

    if (errors && errors.length >= 1) {
      setProperties(this, {
        loadFailedAllClusters: false,
        errors:                [],
      });
    }

    if (!isEmpty(region) && !isEmpty(amazonCredentialSecret) && !loadingClusters) {
      this.send('fetchEksResources');
    }
  })),

  cloudCredentials: computed('model.cloudCredentials.[]', function() {
    const { model: { cloudCredentials } } = this;

    return cloudCredentials.filter((cc) => !isEmpty(get(cc, 'amazonec2credentialConfig')));
  }),

  disableImport: computed('step', 'config.{amazonCredentialSecret,displayName}', function() {
    const { step, config: { amazonCredentialSecret, displayName } } = this;

    if (step <= 2 && !isEmpty(amazonCredentialSecret) && !isEmpty(displayName)) {
      return false;
    }

    return true;
  }),

  doneSaving() {
    if (this.close) {
      this.close();
    }
  },

  bootstrapEksV2Cluster() {
    const eksConfig = this.globalStore.createRecord({
      displayName:       '',
      imported:          true,
      region:            'us-west-2',
      type:              'eksclusterconfigspec',
    });

    set(this, 'model.cluster.eksConfig', eksConfig);
    set(this, 'config', eksConfig);
  },

  listEksClusters(auth) {
    return new Promise((resolve, reject) => {
      const eks = new AWS.EKS(auth);

      eks.listClusters({}, (err, data) => {
        if (err) {
          console.log(err, err.stack);

          return reject(err);
        }

        return resolve(data.clusters);
      });
    })
  },

  authCreds() {
    let {
      config: {
        region,
        amazonCredentialSecret
      }
    } = this;

    const auth = {
      accessKeyId:         randomStr(),
      secretAccessKey:     randomStr(),
      region,
      httpOptions:         { cloudCredentialId: amazonCredentialSecret },
    };

    return auth;
  },
});
<|endoftext|>


<commit_before>module.exports = function (RED) {
    "use strict";

    var when = require('when');
    var _ = require('underscore');
    var EventEmitter = require('events').EventEmitter;
    var common = require("../lib/common");

    /*******************************************
    Switch node and config
    *******************************************/
    function NoreliteSwitchConfig(n) {
        RED.nodes.createNode(this, n);
        this.name = n.name;
        this.times = n.times;
    }
    RED.nodes.registerType("nrl-switch-config", NoreliteSwitchConfig);
    function NoreliteSwitch(n) {
        RED.nodes.createNode(this, n);
        this.times = RED.nodes.getNode(n.times).times;
        this.name = n.name;
        this.timer = null;
        var self = this;

        //timeout
        if (n.repeatUnits === "milliseconds") {
            this.repeat = n.repeat;
        } else if (n.repeatUnits === "seconds") {
            this.repeat = n.repeat * 1000;
        } else if (n.repeatUnits === "minutes") {
            this.repeat = n.repeat * 1000 * 60;
        } else if (n.repeatUnits === "hours") {
            this.repeat = n.repeat * 1000 * 60 * 60;
        } else if (n.repeatUnits === "days") {
            this.repeat = n.repeat * 1000 * 60 * 60 * 24;
        }
        //Set init status message
        common.setStatus(self);

        /* Holds messages from all different linkIds
        Sturcture of incoming messages
        { lid: xyz, status: 0/1, value:0-100, type: "rule"/"scenario"/"direct"}
        */
        self.allIds = []; //Keeps a list of all payloads
        //Functions to work with the values store (keeps updates from the sources)
        self.allIdsAdd = function(payload){
            var found = _.findIndex(self.allIds, function(obj){return obj.lid == payload.lid});
            if (found === -1){
                self.allIds.push(payload);
            } else {
                self.allIds[found]= payload;
            }
        }
        self.allIdsGet = function(id){
            var found = _.find(self.allIds, function(obj){return obj.lid == payload.lid});
            return (found != undefined ? found : undefined);
        }
        self.activeId; //Keeps the active id of receiving message
        self.prevMsg; //Keeps store of the last sent out msg

        /* Method to create output message */
        //Validate function of what the output msg should look like
        self.getOutputMsg = function () {
                var id = self.id;
                var sendit = false;

                var out_msg = {
                    lid: id,
                    status: 0,
                    value: 0,
                    type: "none"
                };

                _.each(self.allIds, function(cid){
                    //Skip all that are not active
                    if (cid.status === 0){
                        return;
                    }

                    if (cid.type === "direct"){
                        if (cid.status){
                            out_msg.status = 1;
                        }

                        //Reset value if type is changed
                        if (out_msg.type !== "direct"){
                            out_msg.value = cid.value;
                            self.activeId = cid.lid;
                        }

                        //Set the type
                        out_msg.type = cid.type;

                        //Aways use the highest value
                        if (cid.value > out_msg.value){
                            out_msg.value = cid.value;
                            self.activeId = cid.lid;
                        }
                    }//if direct

                    else if (cid.type === "scenario" && (out_msg.type === "rule" || out_msg.type === "none" || out_msg.type === "scenario")){
                        /* If the input is active */
                        if (cid.status === 1) {
                            out_msg.status = 1;

                            /* Reset value if the type is changed */
                            if (out_msg.type !== "scenario") {
                                out_msg.value = cid.value;
                                self.activeId = cid.lid;
                            }

                            //Set the active type
                            out_msg.type = cid.type;

                            //Always use the highest value
                            if (cid.value > out_msg.value) {
                                out_msg.value = cid.value;
                                /*Set the active id*/
                                self.activeId = cid.lid;
                            }
                        }
                    }//if scenario

                    else if (cid.type === "rule" && (out_msg.type === "rule" || out_msg.type === "none")) {
                        /* If the input is active */
                        if (cid.status === 1) {
                            out_msg.status = 1;

                            //Set the active type
                            out_msg.type = cid.type;

                            //Always use the highest value
                            if (cid.value > out_msg.value) {
                                out_msg.value = cid.value;
                                /*Set the active id*/
                                self.activeId = cid.lid;
                            }
                        }
                    }//if rule
                });//each

            //If nothing has been applied set to rule
            if (out_msg.type === "none"){
                out_msg.type = "rule";
                self.activeId = "none";
            }

            return out_msg;
        } //getOutputMsg

        /* Send the message */

        self.sendMsg = function (repeatCall) {
            //Save prev id that is active
            var prevActiveId = self.activeId;

            if (self.allIds.length > 0) {
                var output = self.getOutputMsg();
                var msg = {
                    name: self.name,
                    payload: output
                };

                /* Send the message the specified number of times */
                if (prevActiveId == undefined || prevActiveId != self.activeId || repeatCall) {
                    /* Clear timer if it is not a repeatCall (called from timer) */
                    if (self.timer){
                        clearTimeout(self.timer);
                    }

                    //Check if status and value has changed from prev
                    if (self.prevMsg == undefined ||
                        (self.prevMsg.payload.status != msg.payload.status || self.prevMsg.payload.value != msg.payload.value) ||
                        repeatCall){

                        for (var i = 0; i < self.times; i++) {
                            setTimeout( function(){ self.send(msg); }, 100*(i+1));
                        }
                        //Save current message for review next time the method is called
                        self.prevMsg = msg;
                    }

                    /* Set timer repeat function*/
                    self.timer = setTimeout(function () {
                            self.sendMsg(true)
                        }, self.repeat);
                }

                //Set status message
                var state = 1;
                if (msg.payload.state === 0 || msg.payload.value === 0) {
                    state = -1;
                }
                common.setStatus(self, state, output.type + "/" + output.value + "%");
            }
        }

        /* On received messages */
        self.receiveTimeout;
        self.on("input", function (msg) {
            //Validate input
            var validate = common.validatePayload(msg.payload);
            if (!validate.valid){
                common.warn(self, validate.error);
                return;
            }

            //Add to local array used for validation on out message
            self.allIdsAdd(msg.payload);

            /* Set a small delay to prevent unnecessary processing before actually all messages
            have been received. E.g. when starting for the first time */
            if (self.receiveTimeout){
                clearTimeout(self.receiveTimeout);
            }
            self.receiveTimeout = setTimeout(function(){self.sendMsg();},1000);

        });

        /* When a node is closed */
        self.on("close", function(){
            //Clear the repeat timer
            if (self.timer){
                clearTimeout(self.timer);
            }
            //Clear the receive timeout
            if (self.receiveTimeout){
                clearTimeout(self.receiveTimeout);
            }
            //Reset holder of all incoming msgs
            self.allIds = [];
        });

    }
     RED.nodes.registerType("nrl-switch out", NoreliteSwitch);
}
<commit_msg>Adding reset of msg values<commit_after>module.exports = function (RED) {
    "use strict";

    var when = require('when');
    var _ = require('underscore');
    var EventEmitter = require('events').EventEmitter;
    var common = require("../lib/common");

    /*******************************************
    Switch node and config
    *******************************************/
    function NoreliteSwitchConfig(n) {
        RED.nodes.createNode(this, n);
        this.name = n.name;
        this.times = n.times;
    }
    RED.nodes.registerType("nrl-switch-config", NoreliteSwitchConfig);
    function NoreliteSwitch(n) {
        RED.nodes.createNode(this, n);
        this.times = RED.nodes.getNode(n.times).times;
        this.name = n.name;
        this.timer = null;
        var self = this;

        //timeout
        if (n.repeatUnits === "milliseconds") {
            this.repeat = n.repeat;
        } else if (n.repeatUnits === "seconds") {
            this.repeat = n.repeat * 1000;
        } else if (n.repeatUnits === "minutes") {
            this.repeat = n.repeat * 1000 * 60;
        } else if (n.repeatUnits === "hours") {
            this.repeat = n.repeat * 1000 * 60 * 60;
        } else if (n.repeatUnits === "days") {
            this.repeat = n.repeat * 1000 * 60 * 60 * 24;
        }
        //Set init status message
        common.setStatus(self);

        /* Holds messages from all different linkIds
        Sturcture of incoming messages
        { lid: xyz, status: 0/1, value:0-100, type: "rule"/"scenario"/"direct"}
        */
        self.allIds = []; //Keeps a list of all payloads
        //Functions to work with the values store (keeps updates from the sources)
        self.allIdsAdd = function(payload){
            var found = _.findIndex(self.allIds, function(obj){return obj.lid == payload.lid});
            if (found === -1){
                self.allIds.push(payload);
            } else {
                self.allIds[found]= payload;
            }
        }
        self.allIdsGet = function(id){
            var found = _.find(self.allIds, function(obj){return obj.lid == payload.lid});
            return (found != undefined ? found : undefined);
        }
        self.activeId; //Keeps the active id of receiving message
        self.prevMsg; //Keeps store of the last sent out msg

        /* Method to create output message */
        //Validate function of what the output msg should look like
        self.getOutputMsg = function () {
                var id = self.id;
                var sendit = false;

                var out_msg = {
                    lid: id,
                    status: 0,
                    value: 0,
                    type: "none"
                };

                _.each(self.allIds, function(cid){
                    //Skip all that are not active
                    if (cid.status === 0){
                        return;
                    }

                    if (cid.type === "direct"){
                        if (cid.status){
                            out_msg.status = 1;
                        }

                        //Reset value if type is changed
                        if (out_msg.type !== "direct"){
                            out_msg.value = cid.value;
                            self.activeId = cid.lid;
                        }

                        //Set the type
                        out_msg.type = cid.type;

                        //Aways use the highest value
                        if (cid.value > out_msg.value){
                            out_msg.value = cid.value;
                            self.activeId = cid.lid;
                        }
                    }//if direct

                    else if (cid.type === "scenario" && (out_msg.type === "rule" || out_msg.type === "none" || out_msg.type === "scenario")){
                        /* If the input is active */
                        if (cid.status === 1) {
                            out_msg.status = 1;

                            /* Reset value if the type is changed */
                            if (out_msg.type !== "scenario") {
                                out_msg.value = cid.value;
                                self.activeId = cid.lid;
                            }

                            //Set the active type
                            out_msg.type = cid.type;

                            //Always use the highest value
                            if (cid.value > out_msg.value) {
                                out_msg.value = cid.value;
                                /*Set the active id*/
                                self.activeId = cid.lid;
                            }
                        }
                    }//if scenario

                    else if (cid.type === "rule" && (out_msg.type === "rule" || out_msg.type === "none")) {
                        /* If the input is active */
                        if (cid.status === 1) {
                            out_msg.status = 1;

                            //Set the active type
                            out_msg.type = cid.type;

                            //Always use the highest value
                            if (cid.value > out_msg.value) {
                                out_msg.value = cid.value;
                                /*Set the active id*/
                                self.activeId = cid.lid;
                            }
                        }
                    }//if rule
                });//each

            //If nothing has been applied set to rule
            if (out_msg.type === "none"){
                out_msg.type = "rule";
                self.activeId = "none";
            }

            return out_msg;
        } //getOutputMsg

        /* Send the message */

        self.sendMsg = function (repeatCall) {
            //Save prev id that is active
            var prevActiveId = self.activeId;

            if (self.allIds.length > 0) {
                var output = self.getOutputMsg();
                var msg = {
                    name: self.name,
                    payload: output
                };

                /* Send the message the specified number of times */
                if (prevActiveId == undefined || prevActiveId != self.activeId || repeatCall) {
                    /* Clear timer if it is not a repeatCall (called from timer) */
                    if (self.timer){
                        clearTimeout(self.timer);
                    }

                    //Check if status and value has changed from prev
                    if (self.prevMsg == undefined ||
                        (self.prevMsg.payload.status != msg.payload.status || self.prevMsg.payload.value != msg.payload.value) ||
                        repeatCall){

                        for (var i = 0; i < self.times; i++) {
                            setTimeout( function(){ self.send(msg); }, 100*(i+1));
                        }
                        //Save current message for review next time the method is called
                        self.prevMsg = msg;
                    }

                    /* Set timer repeat function*/
                    self.timer = setTimeout(function () {
                            self.sendMsg(true)
                        }, self.repeat);
                }

                //Set status message
                var state = 1;
                if (msg.payload.state === 0 || msg.payload.value === 0) {
                    state = -1;
                }
                common.setStatus(self, state, output.type + "/" + output.value + "%");
            }
        }

        /* On received messages */
        self.receiveTimeout;
        self.on("input", function (msg) {
            //Validate input
            var validate = common.validatePayload(msg.payload);
            if (!validate.valid){
                common.warn(self, validate.error);
                return;
            }

            //Add to local array used for validation on out message
            self.allIdsAdd(msg.payload);

            /* Set a small delay to prevent unnecessary processing before actually all messages
            have been received. E.g. when starting for the first time */
            if (self.receiveTimeout){
                clearTimeout(self.receiveTimeout);
            }
            self.receiveTimeout = setTimeout(function(){self.sendMsg();},1000);

        });

        /* When a node is closed */
        self.on("close", function(){
            //Clear the repeat timer
            if (self.timer){
                clearTimeout(self.timer);
            }
            //Clear the receive timeout
            if (self.receiveTimeout){
                clearTimeout(self.receiveTimeout);
            }
            //Reset holder of all incoming msgs and msg values
            self.allIds = [];
            self.activeId=null;
            self.prevMsg=null;

            //Reset the status
            common.setStatus(self);

        });

    }
     RED.nodes.registerType("nrl-switch out", NoreliteSwitch);
}
<|endoftext|>


<commit_before>var FieldType = require('../Type');
var marked = require('marked');
var util = require('util');
var validators = require('../validators');

/**
 * Markdown FieldType Constructor
 * @extends Field
 * @api public
 */

function markdown (list, path, options) {
	this._defaultSize = 'full';

	this.toolbarOptions = options.toolbarOptions || {};
	this.markedOptions = options.markedOptions || {};
	this.height = options.height || 90;
	this.wysiwyg = ('wysiwyg' in options) ? options.wysiwyg : true;

	this._properties = ['wysiwyg', 'height', 'toolbarOptions'];
	markdown.super_.call(this, list, path, options);
}
util.inherits(markdown, FieldType);

/* Use text validators */
markdown.prototype.validateInput = validators.text.input;
markdown.prototype.validateRequiredInput = validators.text.required;

/**
 * Registers the field on the List's Mongoose Schema.
 *
 * Adds String properties for .md and .html markdown, and a setter for .md
 * that generates html when it is updated.
 *
 * @api public
 */
markdown.prototype.addToSchema = function () {

	var schema = this.list.schema;

	var paths = this.paths = {
		md: this._path.append('.md'),
		html: this._path.append('.html'),
	};

	var markedOptions = this.markedOptions;

	var setMarkdown = function (value) {
		if (value === this.get(paths.md)) {
			return value;
		}
		if (typeof value === 'string') {
			this.set(paths.html, marked(value, markedOptions));
			return value;
		} else {
			this.set(paths.html, undefined);
			return undefined;
		}
	};

	schema.nested[this.path] = true;
	schema.add({
		html: { type: String },
		md: { type: String, set: setMarkdown },
	}, this.path + '.');

	this.bindUnderscoreMethods();
};

/**
 * Formats the field value
 *
 * @api public
 */
markdown.prototype.format = function (item) {
	return item.get(this.paths.html);
};

/**
 * Validates that a value for this field has been provided in a data object
 *
 * Deprecated
 */
markdown.prototype.inputIsValid = function (data, required, item) {
	if (!(this.path in data) && item && item.get(this.paths.md)) {
		return true;
	}
	return (!required || data[this.path]) ? true : false;
};

/**
 * Detects whether the field has been modified
 *
 * @api public
 */
markdown.prototype.isModified = function (item) {
	return item.isModified(this.paths.md);
};

/**
 * Updates the value for this field in the item from a data object
 *
 * Will accept either the field path, or paths.md
 *
 * @api public
 */
markdown.prototype.updateItem = function (item, data, callback) {
	var value = this.getValueFromData(data);
	if (value !== undefined) {
		item.set(this.paths.md, value);
	}	else if (this.paths.md in data) {
			item.set(this.paths.md, data[this.paths.md]);
	}
	process.nextTick(callback);
};

/* Export Field Type */
module.exports = markdown;
<commit_msg>Fixed lint error<commit_after>var FieldType = require('../Type');
var marked = require('marked');
var util = require('util');
var validators = require('../validators');

/**
 * Markdown FieldType Constructor
 * @extends Field
 * @api public
 */

function markdown (list, path, options) {
	this._defaultSize = 'full';

	this.toolbarOptions = options.toolbarOptions || {};
	this.markedOptions = options.markedOptions || {};
	this.height = options.height || 90;
	this.wysiwyg = ('wysiwyg' in options) ? options.wysiwyg : true;

	this._properties = ['wysiwyg', 'height', 'toolbarOptions'];
	markdown.super_.call(this, list, path, options);
}
util.inherits(markdown, FieldType);

/* Use text validators */
markdown.prototype.validateInput = validators.text.input;
markdown.prototype.validateRequiredInput = validators.text.required;

/**
 * Registers the field on the List's Mongoose Schema.
 *
 * Adds String properties for .md and .html markdown, and a setter for .md
 * that generates html when it is updated.
 *
 * @api public
 */
markdown.prototype.addToSchema = function () {

	var schema = this.list.schema;

	var paths = this.paths = {
		md: this._path.append('.md'),
		html: this._path.append('.html'),
	};

	var markedOptions = this.markedOptions;

	var setMarkdown = function (value) {
		if (value === this.get(paths.md)) {
			return value;
		}
		if (typeof value === 'string') {
			this.set(paths.html, marked(value, markedOptions));
			return value;
		} else {
			this.set(paths.html, undefined);
			return undefined;
		}
	};

	schema.nested[this.path] = true;
	schema.add({
		html: { type: String },
		md: { type: String, set: setMarkdown },
	}, this.path + '.');

	this.bindUnderscoreMethods();
};

/**
 * Formats the field value
 *
 * @api public
 */
markdown.prototype.format = function (item) {
	return item.get(this.paths.html);
};

/**
 * Validates that a value for this field has been provided in a data object
 *
 * Deprecated
 */
markdown.prototype.inputIsValid = function (data, required, item) {
	if (!(this.path in data) && item && item.get(this.paths.md)) {
		return true;
	}
	return (!required || data[this.path]) ? true : false;
};

/**
 * Detects whether the field has been modified
 *
 * @api public
 */
markdown.prototype.isModified = function (item) {
	return item.isModified(this.paths.md);
};

/**
 * Updates the value for this field in the item from a data object
 *
 * Will accept either the field path, or paths.md
 *
 * @api public
 */
markdown.prototype.updateItem = function (item, data, callback) {
	var value = this.getValueFromData(data);
	if (value !== undefined) {
		item.set(this.paths.md, value);
	}	else if (this.paths.md in data) {
		item.set(this.paths.md, data[this.paths.md]);
	}
	process.nextTick(callback);
};

/* Export Field Type */
module.exports = markdown;
<|endoftext|>


<commit_before>var mate = require('../');

var node = mate.createNode();

node.on('listening', function() {
	console.log('The mate node started.');
});<commit_msg>update example<commit_after>var mate = require('../');

var node = mate.createNode({
	networkPort: 2017
});

node.on('listening', function() {
	console.log('The mate node ' + node.localNodeContact.id.toString() + ' started');

	node.directConnect({
		ip: '127.0.0.1',
		port: 2016
	}, function(success) {
		console.log('connect ' + success);
		node.lookup('95499720-b4f6-11e4-afba-efd5b147fc02', function(success, lookupNode, fatherNode) {
			if (success === true) {
				if (fatherNode) {
					console.log('find ' + lookupNode.id.toString() + ' from ' + fatherNode.id.toString());
				} else {
					console.log('find ' + lookupNode.id.toString() + ' locally');
				}
			} else {
				console.log('can\'t find it');
			}
		});
	});

});<|endoftext|>


<commit_before>'use strict';

function Promisifier(inner, funcNames) {
  this.__proto__ = inner;
  for (const funcName of funcNames) {
    this[funcName] = function(...args) {
      return new Promise((resolve, reject) => {
        setTimeout(() => {
          try {
            const result = inner[funcName](...args);
            setTimeout(() => resolve(result));
          } catch (error) {
            setTimeout(() => reject(error));
          }
        }, 100);
      });
    };
  }
}

function Clonifier(inner, funcNames) {
  this.__proto__ = inner;
  for (const funcName of funcNames) {
    this[funcName] = function(...args) {
      return Utils.copyOf(inner[funcName](...args.map(Utils.copyOf)));
    }
  }
}

function Protectifier(inner, isLoggedIn, funcNames) {
  this.__proto__ = inner;
  for (const funcName of funcNames) {
    this[funcName] = function(...args) {
      if (!isLoggedIn()) {
        throw "Not logged in! Can't call " + funcName;
      }
      return inner[funcName];
    }
  }
}

function makeFakePrepopulatedServerBridge() {
  var kimUserId = Utils.generateId("user");
  var evanUserId = Utils.generateId("user");
  var kimPlayerId = Utils.generateId("player");
  var evanPlayerId = Utils.generateId("player");
  var gameId = Utils.generateId("game");
  var humanChatRoom = Utils.generateId("chat");
  var zedChatRoom = Utils.generateId("chat");
  var firstMissionId = Utils.generateId("mission");
  var innerServer = new FakeServer();
  innerServer.register(kimUserId, 'kimikimkim@kim.com');
  innerServer.register(evanUserId, 'verdagon@evan.com');
  innerServer.createGame(gameId, kimUserId);
  innerServer.joinGame(kimUserId, gameId, kimPlayerId, 'Kim the Ultimate', {});
  innerServer.joinGame(evanUserId, gameId, evanPlayerId, 'Evanpocalypse', {});
  innerServer.createChatRoom(humanChatRoom, kimPlayerId);
  innerServer.addPlayerToChatRoom(humanChatRoom, evanPlayerId);
  innerServer.addMessageToChatRoom(humanChatRoom, kimPlayerId, 'hi');
  innerServer.createChatRoom(zedChatRoom, evanPlayerId);
  innerServer.addPlayerToChatRoom(zedChatRoom, kimPlayerId);
  innerServer.addMessageToChatRoom(zedChatRoom, evanPlayerId, 'zeds rule!');
  innerServer.addMessageToChatRoom(zedChatRoom, kimPlayerId, 'hoomans drool!');
  innerServer.addMessageToChatRoom(zedChatRoom, kimPlayerId, 'monkeys eat stool!');
  innerServer.addMission(gameId, firstMissionId, new Date().getTime() - 1000, new Date().getTime() + 1000 * 60 * 60, "/firstgame/missions/first-mission.html");

  var loggedInUserId = null;

  innerServer.logIn = function(authcode) {
    if (authcode != 'firstuserauthcode') {
      throw "Couldnt find auth code";
    }
    var userId = kimUserId;
    // To check it exists. this.__proto__ to skip the security check
    this.__proto__.getUserById.call(this, userId);
    loggedInUserId = userId;
    return userId;
  };

  innerServer.getUserById = function(userId) {
    if (loggedInUserId != userId)
      throw 'Cant get other user';
    return this.__proto__.getUserById.call(this, userId);
  };

  const clonifiedServer =
      new Clonifier(innerServer, SERVER_METHODS);

  const promisifiedClonifiedServer =
      new Promisifier(clonifiedServer, SERVER_METHODS);

  const protectifiedClonifiedPromisifiedServer =
      new Protectifier(
          promisifiedClonifiedServer,
          (() => !!loggedInUserId),
          subtract(SERVER_METHODS, "logIn", "register"));

  return protectifiedClonifiedPromisifiedServer;
}
<commit_msg>more fake server bridge magic<commit_after>'use strict';

function Promisifier(inner, funcNames) {
  this.__proto__ = inner;
  for (const funcName of funcNames) {
    this[funcName] = function(...args) {
      return new Promise((resolve, reject) => {
        setTimeout(() => {
          try {
            const result = inner[funcName](...args);
            setTimeout(() => resolve(result));
          } catch (error) {
            setTimeout(() => reject(error));
          }
        }, 100);
      });
    };
  }
}

function Clonifier(inner, funcNames) {
  this.__proto__ = inner;
  for (const funcName of funcNames) {
    this[funcName] = function(...args) {
      return Utils.copyOf(inner[funcName](...args.map(Utils.copyOf)));
    }
  }
}

function Protectifier(inner, isLoggedIn, funcNames) {
  this.__proto__ = inner;
  for (const funcName of funcNames) {
    this[funcName] = function(...args) {
      if (!isLoggedIn()) {
        throw "Not logged in! Can't call " + funcName;
      }
      return inner[funcName](...args);
    }
  }
}

function makeFakePrepopulatedServerBridge() {
  var kimUserId = Utils.generateId("user");
  var evanUserId = Utils.generateId("user");
  var kimPlayerId = Utils.generateId("player");
  var evanPlayerId = Utils.generateId("player");
  var gameId = Utils.generateId("game");
  var humanChatRoom = Utils.generateId("chat");
  var zedChatRoom = Utils.generateId("chat");
  var firstMissionId = Utils.generateId("mission");
  var innerServer = new FakeServer();
  innerServer.register(kimUserId, 'kimikimkim@kim.com');
  innerServer.register(evanUserId, 'verdagon@evan.com');
  innerServer.createGame(gameId, kimUserId);
  innerServer.joinGame(kimUserId, gameId, kimPlayerId, 'Kim the Ultimate', {});
  innerServer.joinGame(evanUserId, gameId, evanPlayerId, 'Evanpocalypse', {});
  innerServer.createChatRoom(humanChatRoom, kimPlayerId);
  innerServer.addPlayerToChatRoom(humanChatRoom, evanPlayerId);
  innerServer.addMessageToChatRoom(humanChatRoom, kimPlayerId, 'hi');
  innerServer.createChatRoom(zedChatRoom, evanPlayerId);
  innerServer.addPlayerToChatRoom(zedChatRoom, kimPlayerId);
  innerServer.addMessageToChatRoom(zedChatRoom, evanPlayerId, 'zeds rule!');
  innerServer.addMessageToChatRoom(zedChatRoom, kimPlayerId, 'hoomans drool!');
  innerServer.addMessageToChatRoom(zedChatRoom, kimPlayerId, 'monkeys eat stool!');
  innerServer.addMission(gameId, firstMissionId, new Date().getTime() - 1000, new Date().getTime() + 1000 * 60 * 60, "/firstgame/missions/first-mission.html");

  var loggedInUserId = null;

  innerServer.logIn = function(authcode) {
    if (authcode != 'firstuserauthcode') {
      throw "Couldnt find auth code";
    }
    var userId = kimUserId;
    // To check it exists. this.__proto__ to skip the security check
    this.__proto__.getUserById.call(this, userId);
    loggedInUserId = userId;
    return userId;
  };

  innerServer.getUserById = function(userId) {
    if (loggedInUserId != userId)
      throw 'Cant get other user';
    return this.__proto__.getUserById.call(this, userId);
  };

  const clonifiedServer =
      new Clonifier(innerServer, SERVER_METHODS);

  const promisifiedClonifiedServer =
      new Promisifier(clonifiedServer, SERVER_METHODS);

  const protectifiedClonifiedPromisifiedServer =
      new Protectifier(
          promisifiedClonifiedServer,
          (() => !!loggedInUserId),
          subtract(SERVER_METHODS, "logIn", "register"));

  return protectifiedClonifiedPromisifiedServer;
}
<|endoftext|>


<commit_before>'use strict';

const {Program, makeInstructions} = require('../index.js');

// Defining the instructions we want to provide:
const StackInstr = makeInstructions({
  Push: ['value'],
  Pop: [],
  Add: []
});

const {Push, Pop, Add} = StackInstr;

// A test program operating on stacks: (needs to be lazy, because we can't reuse a generator)
const testProgram1 = () => Program.do(function*() {
  yield Push(10);
  yield Push(20);
  yield Add;
  yield Push(33);
  let thirtyTwo = yield Pop;
  let sum = yield Pop;
  return `last element: ${thirtyTwo}, sum of previous: ${sum}`;
});

// the same thing in "verbose syntax":
// const testProgram2 = Push(10).andThen(Push(20))
//     .andThen(Add())
//     .andThen(Push(33))
//     .andThen(Pop()).flatMap(
//         thirtyTwo => Pop().flatMap(
//             sum => Program.inject([sum, thirtyTwo])));

// an interpreter, which will execute a stack program on an immutable list
const interpreter1 = (program, initialStack) => Program.interpret(program)({
  Pop: recur => {
    const [first, ...rest] = initialStack;
    return interpreter1(recur(first), rest);
  },
  Push: (recur, x) => interpreter1(recur({}), [x, ...initialStack]),
  Add: recur => {
    const [first, second, ...rest] = initialStack;
    const sum = first + second;
    return interpreter1(recur(sum), [sum, ...rest]);
  }
});

console.log(interpreter1(testProgram1(), []));
// expected: `last element: 33, sum of previous: 30`

// an interpreter mutating an array:
function interpreter2(program) {
  const stack = [];

  const go = p =>  Program.interpret(p)({
    Pop: recur => {
      const x = stack.pop();
      return go(recur(x));
    },
    Push: (recur, x) => {
      stack.push(x);
      return go(recur({}));
    },
    Add: recur => {
      const first = stack.pop();
      const second = stack.pop();
      const sum = first + second;
      stack.push(sum);
      return go(recur({}));
    }
  });

  return go(program);
}

console.log(interpreter2(testProgram1()));
// expected: `last element: 33, sum of previous: 30`

<commit_msg>Add example for monadic interpretation: stacks and state monad.<commit_after>'use strict';

const {Program, makeInstructions} = require('../index.js');

// Defining the instructions we want to provide:
const StackInstr = makeInstructions({
  Push: ['value'],
  Pop: [],
  Add: []
});

const {Push, Pop, Add} = StackInstr;

// A test program operating on stacks: (needs to be lazy, because we can't reuse a generator)
const testProgram = () => Program.do(function*() {
  yield Push(10);
  yield Push(20);
  yield Add;
  yield Push(33);
  let thirtyTwo = yield Pop;
  let sum = yield Pop;
  return `last element: ${thirtyTwo}, sum of previous: ${sum}`;
});

// the same thing in "verbose syntax":
// const testProgram2 = Push(10).andThen(Push(20))
//     .andThen(Add())
//     .andThen(Push(33))
//     .andThen(Pop()).flatMap(
//         thirtyTwo => Pop().flatMap(
//             sum => Program.inject([sum, thirtyTwo])));

(function test1() {
// an immutable interpreter, which will execute a stack program on an immutable list
  const interpreter = (program, initialStack) => Program.interpret(program)({
    Pop: recur => {
      const [first, ...rest] = initialStack;
      return interpreter(recur(first), rest);
    },
    Push: (recur, x) => interpreter(recur({}), [x, ...initialStack]),
    Add: recur => {
      const [first, second, ...rest] = initialStack;
      const sum = first + second;
      return interpreter(recur(sum), [sum, ...rest]);
    }
  });

  console.log(interpreter(testProgram(), []));
// expected: `last element: 33, sum of previous: 30`
})();

(function test2() {
// an interpreter mutating an array:

  function interpreter(program) {
    const stack = [];

    const go = p => Program.interpret(p)({
      Pop: recur => {
        const x = stack.pop();
        return go(recur(x));
      },
      Push: (recur, x) => {
        stack.push(x);
        return go(recur({}));
      },
      Add: recur => {
        const first = stack.pop();
        const second = stack.pop();
        const sum = first + second;
        stack.push(sum);
        return go(recur({}));
      }
    });

    return go(program);
  }

  console.log(interpreter(testProgram()));
// expected: `last element: 33, sum of previous: 30`
})();

(function test3() {
//  An interpreter into the State monad. Immutable, and eta-equivalent to test1.

  class State {
    constructor(run) {
      this.run = run;
    }

    static of(x) {
      return new State(s => [x, s]);
    }

    map(f) {
      return new State(s => {
        const [result, s_prime] = this.run(s);
        return [f(result), s_prime];
      });
    }

    chain(f) {
      return new State(s => {
        const [result, s_prime] = this.run(s);
        return f(result).run(s_prime);
      });
    }

    andThen(k) {
      return this.chain(_ => k);
    }

    static get() {
      return new State(s => [s, s]);
    }

    static put(new_s) {
      return new State(_ => [{}, new_s]);
    }

    static modify(f) {
      return State.get().chain(x => State.put(f(x)));
    }
  }

  function interpreter(program) {
    const stack = [];

    const go = p => Program.interpretMonadic(p)({
      Return: State.of,
      Pop: () => State.get().chain(stack => {
        const [first, ...rest] = stack;
        return State.put(rest).andThen(State.of(first));
      }),
      Push: (x) => State.modify(stack => [x, ...stack]),
      Add: () => State.get().chain(stack => {
        const [first, second, ...rest] = stack;
        const sum = first + second;
        return State.put([sum, ...rest]).andThen(State.of(sum));
      })
    });

    return go(program);
  }

  // we run this with a stack already containing a value, which should be kept untouched
  console.log(interpreter(testProgram()).run([666]));
// expected: [ 'last element: 33, sum of previous: 30', [ 666 ] ]
})();

<|endoftext|>


<commit_before>var fs = require('fs');
var path = require('path');

var cheerio = require('cheerio'),
    wrench = require('wrench');

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

exports.checkMissingImage = function(filename, $, options, callback) {
    var errors = [];
    var imgList = $("img");

    if (imgList !== undefined) {
        for (var i = 0; i < imgList.length; i++) {
            var src = $(imgList[i]).attr("src");

            if (src === undefined || src.length === 0) {
                errors.push(filename, printMessage("Missing src attribute"));//, l, lines[l]));
            }
            else {
                if (options.mapPrefix && src.charAt(0) == "/") {
                    src = "." + src;
                }

                var lastSlashPos = src.lastIndexOf("/");
                var refDirName = src.substr(0, lastSlashPos);
                var refFileName = src.substr(lastSlashPos + 1);

                var filepath = path.resolve(path.dirname(filename) + "/"  + src);

                if (refDirName.length === 0 || lastSlashPos < 0) {
                    refDirName = path.dirname(filepath);
                }
                refDirName = path.resolve(path.dirname(filename), refDirName);

                if (!path.existsSync(refDirName + "/" + refFileName)) {
                    errors.push(printMessage(filename,  "image " + src + " does not exist"));
                }
                else {
                    var files = wrench.readdirSyncRecursive(refDirName);
                    var fileExists = helpers.casedFileCheck(refDirName, files, filepath);

                    if (!fileExists) {
                        errors.push(printMessage(filename, "image " + src + " does not exist (due to case sensitivity)"));//, l, lines[l]));
                    }
                }
            }
        }
    }

    callback(errors);
};

exports.checkBrokenLocalLink = function(filename, $, readFiles, options, callback) {
    var errors = [];
    var aList = $("a");

    if (aList !== undefined) {
        for (var i = 0; i < aList.length; i++) {
            var href = $(aList[i]).attr("href");

            if (href !== undefined && href.length > 0) {
                // not an external link, or ignorable link (/,, #)
                if (!href.match(/www/) && !href.match(/https?:/) && !href.match(/^mailto:/) && href !== '/' && href != '#' && href != 'javascript:void(0)')  {
                    var filepath = path.resolve(path.dirname(filename) + "/"  + href);

                    if (href.match(/\#/)) { // a reference to somewhere internal
                        var hashFile = href.substr(0, href.indexOf("#"));
                        var hashId = href.substr(href.indexOf("#") + 1);

                        // there's a file associated with the hash
                        if (hashFile.length > 0) {
                            filepath = path.resolve(path.dirname(filename) + "/"  + hashFile);

                            // check if file exists first
                            if (fileCheck(hashFile, filename, filepath, false, errors)) {
                                // console.log(hashFile + " is legit. Let's check " + hashId);
                                // then, validate the hash ref

                                // prevent too many files from being read--just see if the content exists already
                                var hash$ = readFiles[filepath];

                                if (hash$ == undefined) {
                                    var content = fs.readFileSync(filepath, 'utf8');
                                    hash$ = cheerio.load(content);
                                    readFiles[filepath] = hash$;
                                }

                                var hashElement = $("#" + hashId);
                                var hashName = $("[name=" + hashId + "]");

                                // do the actual check (finally!)
                                if (hashElement === undefined && hashName === undefined) {
                                    console.log(hashElement)
                                    console.log(hashName)
                                    errors.push(printMessage(filename, hashFile + " has an incorrect external hash to '#" + hashId +"'"));
                                }
                                else {
                                    //console.log("Yes, " + hashFile + "#" + hashId + " is okay.");
                                }
                            }
                            else {
                                errors.push(printMessage(filename, "found an external hash to a file that doesn't exist (" + filepath + ")"));
                            }
                        }
                        else { // if the hash file doesn't exist, this is an internal hash (i.e. "href='#blah'")
                            if ($('#' + hashId) !== null) {
                                // checking the name and id attributes; I weep to do this twice
                                var foundName = false;
                                var links = $('a');

                                var hashElement = $("#" + hashId);
                                var hashName = $("[name=" + hashId + "]");
                                if (hashElement === undefined && hashName === undefined) {
                                    errors.push(printMessage(filename, "found an an incorrect internal hash to '#" + hashId + "'"));
                                }
                            }
                            else {
                                //console.log("Yes, " + filename + "#" + hashId + " is okay.");
                            }
                        }
                    }
                    else {
                        fileCheck(href, filename, filepath, true, errors);
                    }
                }
            }
        }
    }

  callback(errors);
};

function fileCheck(href, file, filepath, noHash, errors)
{
    var found = false;
    var lastSlashPos = href.lastIndexOf("/");
    var refDirName = href.substr(0, lastSlashPos);
    var refFileName = href.substr(lastSlashPos + 1);

    if (refDirName.length === 0 || lastSlashPos < 0) {
        refDirName = path.dirname(filepath);
    }

    refDirName = path.resolve(path.dirname(file), refDirName);

    if (!path.existsSync(refDirName)) {
        errors.push(printMessage(file, " trying to link to " + refDirName + ", which isn't a directory"));
        return false;
    }

    if (!path.existsSync(refDirName + "/" + refFileName)) {
        errors.push(printMessage(file, " trying to incorrectly link to " + href + " as " + filepath));
        return false;
    }

    if (noHash) {
        return true;
        var files = wrench.readdirSyncRecursive(refDirName);
        var fileExists = helpers.casedFileCheck(refDirName, files, filepath);

        if (!fileExists) {
            errors.push(printMessage(file, " trying to incorrectly link to " + href + " (due to case sensitivity) as " + filepath));
            return false;
        }
    }

    return true;
}

function printMessage(filename, msg, line, string) {
  return filename + ": " + msg;// + " around line " + line + ": " + string;
}<commit_msg>Initial express support<commit_after>var fs = require('fs');
var path = require('path');

var cheerio = require('cheerio'),
    wrench = require('wrench');

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

exports.checkMissingImage = function(filename, $, options, callback) {
    var errors = [];
    var imgList = $("img");

    if (imgList !== undefined) {
        for (var i = 0; i < imgList.length; i++) {
            var src = $(imgList[i]).attr("src");

            if (src === undefined || src.length === 0) {
                errors.push(filename, printMessage("Missing src attribute"));//, l, lines[l]));
            }
            else {
                if (options.mapPrefix && src.charAt(0) == "/") {
                    src = "." + src;
                }

                var lastSlashPos = src.lastIndexOf("/");
                var refDirName = src.substr(0, lastSlashPos);
                var refFileName = src.substr(lastSlashPos + 1);

                var filepath = path.resolve(path.dirname(filename) + "/"  + src);

                if (refDirName.length === 0 || lastSlashPos < 0) {
                    refDirName = path.dirname(filepath);
                }
                refDirName = path.resolve(path.dirname(filename), refDirName);

                if (options.remap.images[refDirName] !== undefined) {
                    var remappedRef = options.remap.images[refDirName];
                    if (remappedRef == "ignore")
                        return true;
                    refDirName = remappedRef;
                }
                if (options.remap.images[refFileName] !== undefined) {
                    var remappedRef = options.remap.images[refFileName];
                    if (remappedRef == "ignore")
                        return true;
                    refFileName = remappedRef;
                }

                if (!path.existsSync(refDirName + "/" + refFileName)) {
                    errors.push(printMessage(filename,  "image " + src + " does not exist"));
                }
                else {
                    var files = wrench.readdirSyncRecursive(refDirName);
                    var fileExists = helpers.casedFileCheck(refDirName, files, filepath);

                    if (!fileExists) {
                        errors.push(printMessage(filename, "image " + src + " does not exist (due to case sensitivity)"));//, l, lines[l]));
                    }
                }
            }
        }
    }

    callback(errors);
};

exports.checkBrokenLocalLink = function(filename, $, readFiles, options, callback) {
    var errors = [];
    var aList = $("a");

    if (aList !== undefined) {
        for (var i = 0; i < aList.length; i++) {
            var href = $(aList[i]).attr("href");

            if (href !== undefined && href.length > 0 ) {
                // not an external link, or ignorable link (/,, #)
                if (!href.match(/www/) && !href.match(/https?:/) && !href.match(/^mailto:/) && href !== '/' && href != '#' && href != 'javascript:void(0)')  {
                    var filepath = path.resolve(path.dirname(filename) + "/"  + href);

                    if (href.match(/\#/)) { // a reference to somewhere internal
                        var hashFile = href.substr(0, href.indexOf("#"));
                        var hashId = href.substr(href.indexOf("#") + 1);

                        // there's a different file associated with the hash
                        if (hashFile.length > 0) {
                            filepath = path.resolve(path.dirname(filename) + "/"  + hashFile);

                            // check if file exists first
                            if (fileCheck(options, hashFile, filename, filepath, false, errors)) {
                                // console.log(hashFile + " is legit. Let's check " + hashId);
                                // then, validate the hash ref

                                var hashFileDir = path.dirname(hashFile);
                                if (options.remap.links[hashFileDir] !== undefined)
                                    filepath = options.remap.links[hashFileDir] + "/" + hashFile.replace(hashFileDir, "");

                                // prevent too many files from being read--just see if the content exists already
                                var hash$ = readFiles[filepath];

                                if (hash$ == undefined) {
                                    var content = fs.readFileSync(filepath, 'utf8');
                                    hash$ = cheerio.load(content);
                                    readFiles[filepath] = hash$;
                                }

                                var hashElement = hash$("#" + hashId);
                                var hashName = hash$("[name=" + hashId + "]");

                                // do the actual check (finally!)
                                if (hashName.length <= 0) {
                                    hashName = hash$("[id=" + hashId + "]");
                                    if (hashName.length <= 0)
                                        errors.push(printMessage(filename, hashFile + " has an incorrect internal hash to '#" + hashId +"'"));
                                }
                                else {
                                    //console.log("Yes, " + hashFile + "#" + hashId + " is okay.");
                                }
                            }
                            else {
                                errors.push(printMessage(filename, "found an external hash to a file that doesn't exist (" + filepath + ")"));
                            }
                        }
                        else { // if the hash file doesn't exist, this is an internal hash (i.e. "href='#blah'")
                            if ($('#' + hashId) !== null) {
                                // checking the name and id attributes; I weep to do this twice
                                var foundName = false;
                                var links = $('a');

                                var hashElement = $("#" + hashId);
                                var hashName = $("[name=" + hashId + "]");
                                if (hashElement === undefined && hashName === undefined) {
                                    errors.push(printMessage(filename, "found an an incorrect internal hash to '#" + hashId + "'"));
                                }
                            }
                            else {
                                //console.log("Yes, " + filename + "#" + hashId + " is okay.");
                            }
                        }
                    }
                    else {
                        fileCheck(options, href, filename, filepath, true, errors);
                    }
                }
            }
        }
    }

  callback(errors);
};

function fileCheck(options, href, file, filepath, noHash, errors)
{
    var found = false;
    var lastSlashPos = href.lastIndexOf("/");
    var refDirName = href.substr(0, lastSlashPos);
    var refFileName = href.substr(lastSlashPos + 1);

    if (refDirName.length === 0 || lastSlashPos < 0) {
        refDirName = path.dirname(filepath);
    }

    refDirName = path.resolve(path.dirname(file), refDirName);
    if (options.remap.links[refDirName] !== undefined) {
        var remappedRef = options.remap.links[refDirName];
        if (remappedRef == "ignore")
            return true;
        refDirName = remappedRef;
    }
    if (options.remap.links[refFileName] !== undefined) {
        var remappedRef = options.remap.links[refFileName];
        if (remappedRef == "ignore")
            return true;
        refFileName = remappedRef;
    }

    if (!path.existsSync(refDirName)) {
        errors.push(printMessage(file, " trying to link to " + refDirName + ", which isn't a directory"));
        return false;
    }

    if (!path.existsSync(refDirName + "/" + refFileName)) {
        errors.push(printMessage(file, " trying to incorrectly link to " + href + " as " + filepath));
        return false;
    }

    if (noHash) {
        return true;
        var files = wrench.readdirSyncRecursive(refDirName);
        var fileExists = helpers.casedFileCheck(refDirName, files, filepath);

        if (!fileExists) {
            errors.push(printMessage(file, " trying to incorrectly link to " + href + " (due to case sensitivity) as " + filepath));
            return false;
        }
    }

    return true;
}

function printMessage(filename, msg, line, string) {
  return filename + ": " + msg;// + " around line " + line + ": " + string;
}<|endoftext|>


<commit_before>// We set this specifically for 2 reasons.
// 1. It makes sense for both CI tests and local tests to behave the same so issues are found earlier
// 2. Any wrong timezone handling could be hidden if we use UTC/GMT local time (which would happen in CI).
process.env.TZ = 'Pacific/Easter';

module.exports = {
  verbose: false,
  transform: {
    '^.+\\.(ts|tsx|js|jsx)$': 'ts-jest',
  },
  moduleDirectories: ['node_modules', 'public'],
  roots: ['<rootDir>/public/app', '<rootDir>/public/test', '<rootDir>/packages', '<rootDir>/scripts'],
  testRegex: '(\\.|/)(test)\\.(jsx?|tsx?)$',
  moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json'],
  setupFiles: ['jest-canvas-mock', './public/test/jest-shim.ts', './public/test/jest-setup.ts'],
  setupFilesAfterEnv: ['./public/test/setupTests.ts'],
  snapshotSerializers: ['enzyme-to-json/serializer'],
  globals: {
    'ts-jest': { isolatedModules: true },
    __webpack_public_path__: '', // empty string
  },
  moduleNameMapper: {
    '\\.svg': '<rootDir>/public/test/mocks/svg.ts',
    '\\.css': '<rootDir>/public/test/mocks/style.ts',
    'monaco-editor/esm/vs/editor/editor.api': '<rootDir>/public/test/mocks/monaco.ts',
    '^react($|/.+)': '<rootDir>/node_modules/react$1',
  },
  watchPathIgnorePatterns: ['<rootDir>/node_modules/'],
};
<commit_msg>Temporarily extend default jest timeout to alliviate stress on drone (#34369)<commit_after>// We set this specifically for 2 reasons.
// 1. It makes sense for both CI tests and local tests to behave the same so issues are found earlier
// 2. Any wrong timezone handling could be hidden if we use UTC/GMT local time (which would happen in CI).
process.env.TZ = 'Pacific/Easter';

module.exports = {
  verbose: false,
  transform: {
    '^.+\\.(ts|tsx|js|jsx)$': 'ts-jest',
  },
  moduleDirectories: ['node_modules', 'public'],
  roots: ['<rootDir>/public/app', '<rootDir>/public/test', '<rootDir>/packages', '<rootDir>/scripts'],
  testRegex: '(\\.|/)(test)\\.(jsx?|tsx?)$',
  moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json'],
  setupFiles: ['jest-canvas-mock', './public/test/jest-shim.ts', './public/test/jest-setup.ts'],
  testTimeout: 30000,
  setupFilesAfterEnv: ['./public/test/setupTests.ts'],
  snapshotSerializers: ['enzyme-to-json/serializer'],
  globals: {
    'ts-jest': { isolatedModules: true },
    __webpack_public_path__: '', // empty string
  },
  moduleNameMapper: {
    '\\.svg': '<rootDir>/public/test/mocks/svg.ts',
    '\\.css': '<rootDir>/public/test/mocks/style.ts',
    'monaco-editor/esm/vs/editor/editor.api': '<rootDir>/public/test/mocks/monaco.ts',
    '^react($|/.+)': '<rootDir>/node_modules/react$1',
  },
  watchPathIgnorePatterns: ['<rootDir>/node_modules/'],
};
<|endoftext|>


<commit_before>/* This file is part of Indico.
 * Copyright (C) 2002 - 2016 European Organization for Nuclear Research (CERN).
 *
 * Indico is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; either version 3 of the
 * License, or (at your option) any later version.
 *
 * Indico is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Indico; if not, see <http://www.gnu.org/licenses/>.
 */

(function($) {
    'use strict';

    $.widget('indico.categorypicker', {
        options: {
            categoryId: 0,
            actionButtonText: $T.gettext("Select"),
            openInDialog: false,
            dialogTitle: $T.gettext("Select a category"),
            onAction: function() {}
        },

        // Cache for data of already visited categories
        // Shared between instances
        _cache: {},

        _typeaheadTemplate:
        '<form>\

\

\
                    \

\

\
        </form>',

        _create: function() {
            var self = this;
            if (self.options.openInDialog) {
                self._createInDialog();
            } else {
                self._createInline();
            }
        },

        _createInline: function() {
            var self = this;
            self.element.addClass('category-picker');
            self._createNavigator();
            self._createSearchField();
            self.goToCategory(self.options.categoryId);
        },

        _createInDialog: function() {
            var self = this;
            ajaxDialog({
                title: self.options.dialogTitle,
                content: $('
', {class: 'category-picker-wrapper'}).append($('
'))[0].outerHTML,
                closeButton: '',
                onOpen: function(dialog) {
                    self.element = dialog.contentContainer.children().first();
                    self.dialog = dialog;
                    self._createInline();
                }
            });
        },

        _createNavigator: function() {
            var self = this;
            self.$category = $('
');
            self.$categoryTree = $('
', {class: 'group-list fixed-height'});
            self.$categoryResults = $('
', {class: 'group-list fixed-height'});
            self.$categoryNavigator = $('
', {class: 'category-navigator i-box just-group-list with-hover-effect'})
                .append($('
', {class: 'i-box-content'})
                    .append(self.$category)
                    .append(self.$categoryTree)
                    .append(self.$categoryResults));
            self.element.append(self.$categoryNavigator);
        },

        _createSearchField: function() {
            var self = this;

            // Visible search field
            self.$searchInput = $('<input>', {
                class: 'js-search-category js-typeahead',
                type: 'search',
                name: 'q',
                placeholder: $T.gettext("Search")
            }).attr('autocomplete', 'off');

            // Insert elements in DOM
            var $typeaheadForm = $($.parseHTML(self._typeaheadTemplate)[0]);
            $typeaheadForm.find('.typeahead__query').append(self.$searchInput);
            self.element.prepend($typeaheadForm);

            // Typeahead init
            self.$searchInput.typeahead({
                dynamic: true,
                filter: false,
                source: {
                    ajax: {
                        url: build_url(Indico.Urls.Categories.search),
                        path: 'categories',
                        data: {
                            q: "{{query}}"
                        }
                    }
                },
                resultContainer: false,
                cancelButton: false,
                display: 'title',
                callback: {
                    onClickAfter: function(node, a, item) {
                        self.goToCategory(item.id);
                    },
                    onResult: function(node, query, result) {
                        self._renderResults(result);
                    },
                    onHideLayout: function() {
                        self.$categoryResults.html('');
                        self.$category.show();
                        self.$categoryTree.show();
                    },
                    onShowLayout: function(node, query) {
                        self.$category.hide();
                        self.$categoryTree.hide();
                    }
                }
            });
        },

        _buildBreadcrumbs: function(path, clickable) {
            var self = this;
            var $breadcrumbs = $('
', {class: 'breadcrumbs'});
            var tag = clickable ? '' : '';

            _.each(path, function(category) {
                var $item = $(tag, {text: category.title});
                if (clickable) {
                    $item.attr('href', '');
                    $item.attr('title', $T.gettext("Go to: {0}".format(category.title)));
                }
                self._bindGoToCategoryOnClick($item, category.id);
                $breadcrumbs.append($('
').append($item));
            });

            return $breadcrumbs;
        },

        _buildCategory: function(category, isSubcategory, withBreadcrumbs, clickableBreadcrumbs) {
            var self = this;
            var tag = isSubcategory ? '
' : '
';
            var itemClass = isSubcategory ? 'subcategory' : 'current-category';

            var $category = $(tag, {
                class: 'item ' + itemClass,
                id: 'category-' + category.id
            });

            var $categoryTitle = $('
', {class: 'title-wrapper'});
            $categoryTitle.append($('', {
                class: 'title',
                text: category.title
            }));
            if (withBreadcrumbs) {
                $categoryTitle.append(self._buildBreadcrumbs(category.path, clickableBreadcrumbs));
            }

            $category.append($('
', {class: 'icon-wrapper'}));
            $category.append($categoryTitle);
            $category.append(self._buildSidePanel(category, !isSubcategory));
            return $category;
        },

        _buildCurrentCategory: function(category) {
            var self = this;
            return self._buildCategory(category, false, true, true);
        },

        _buildSubcategory: function(category, withBreadcrumbs) {
            var self = this;
            var $subcategory = self._buildCategory(category, true, withBreadcrumbs);

            if (category.can_access) {
                self._bindGoToCategoryOnClick($subcategory, category.id);
                $subcategory.addClass('can-access');
            }

            return $subcategory;
        },

        _buildSidePanel: function(category, withGoToParent) {
            var self = this;
            var $buttonWrapper = $('
', {class: 'button-wrapper'});

            var $button = $('
').append($('', {
                class: 'action-button',
                text: self.options.actionButtonText
            }).on('click', function(evt) {
                evt.stopPropagation();
                self._onAction(category);
            }));
            $buttonWrapper.append($button);

            if (withGoToParent && category.path && category.path.length) {
                var parent = _.last(category.path);
                var $arrowUp = $('', {
                    class: 'icon-arrow-up navigate-up',
                    title: $T.gettext("Go to parent: {0}".format(parent.title))
                }).on('click', function() {
                    self.goToCategory(parent.id);
                });
                $buttonWrapper.append($arrowUp);
            }

            if (category.is_protected) {
                var $protection = $('
').append($('', {class: 'icon-shield'}));
                $buttonWrapper.append($protection);
            }

            var $info = $('
')
                .append($('
', {text: $T.ngettext("{0} category", "{0} categories", category.category_count)
                                            .format(category.category_count)}))
                .append($('
', {text: $T.ngettext("{0} event", "{0} events", category.event_count)
                                            .format(category.event_count)}));
            $buttonWrapper.append($info);

            return $buttonWrapper;
        },

        _ellipsizeBreadcrumbs: function($category) {
            var self = this;
            var $breadcrumbs = $category.find('.breadcrumbs');
            var availableSpace = $category.find('.title-wrapper').width();
            var $ellipsis = $('
', {class: 'ellipsis'});
            var shortened = false;

            while ($breadcrumbs.outerWidth() >= availableSpace) {
                var $segments = $breadcrumbs.children(':not(.ellipsis)');
                var middleIndex = Math.floor($segments.length / 2);
                if (!shortened) {
                    $segments.eq(middleIndex).replaceWith($ellipsis);
                } else {
                    $segments.eq(middleIndex).remove();
                }
            }
        },

        _renderNavigator: function(data) {
            var self = this;

            if (data.category) {
                self.$category.html(self._buildCurrentCategory(data.category));
                self._ellipsizeBreadcrumbs(self.$category);
            }
            if (data.subcategories) {
                _.each(data.subcategories, function(subcategory) {
                    self.$categoryTree.append(self._buildSubcategory(subcategory));
                });
            }
        },

        _renderResults: function(categories) {
            var self = this;

            self.$categoryResults.html('');
            _.each(categories, function(category) {
                var $result = self._buildSubcategory(category, true);
                $result.find('.icon-wrapper').append($('', {
                    class: 'icon-search',
                    title: $T.gettext("Search result")
                }));
                self.$categoryResults.append($result);
                self._ellipsizeBreadcrumbs($result);
            });
        },

        _bindGoToCategoryOnClick: function($element, id) {
            var self = this;

            $element.on('click', function(evt) {
                evt.preventDefault();
                self.goToCategory(id);
            });
        },

        _getCategoryInfo: function(id) {
            var self = this;
            var dfd = $.Deferred();

            if (self._cache[id]) {
                _.defer(function() {
                    dfd.resolve(self._cache[id]);
                });
            } else {
                $.ajax({
                    url: build_url(Indico.Urls.Categories.info, {category_id: id}),
                    dataType: 'json',
                    error: handleAjaxError,
                    success: function(data) {
                        if (data) {
                            self._cache[id] = data;
                            dfd.resolve(data);
                        }
                    }
                });
            }

            return dfd.promise();
        },

        _onAction: function(category) {
            var self = this;
            self.options.onAction(category);
            if (self.dialog) {
                self.dialog.close();
            }
        },

        goToCategory: function(id) {
            var self = this;

            $('.category-navigator .item').animate({
                height: 0,
                paddingTop: 0,
                paddingBottom: 0,
                border: 0
            }, {
                complete: function() {
                    $(this).remove();
                }
            }).find('.title').fadeOut();

            self._getCategoryInfo(id).then(self._renderNavigator.bind(self));
        }
    });
})(jQuery);
<commit_msg>Category picker: Add selectLeafOnly option<commit_after>/* This file is part of Indico.
 * Copyright (C) 2002 - 2016 European Organization for Nuclear Research (CERN).
 *
 * Indico is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; either version 3 of the
 * License, or (at your option) any later version.
 *
 * Indico is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Indico; if not, see <http://www.gnu.org/licenses/>.
 */

(function($) {
    'use strict';

    $.widget('indico.categorypicker', {
        options: {
            categoryId: 0,
            actionButtonText: $T.gettext("Select"),
            openInDialog: false,
            dialogTitle: $T.gettext("Select a category"),
            selectLeafOnly: false, // Allow only the leaf categories to be selected
            onAction: function() {}
        },

        // Cache for data of already visited categories
        // Shared between instances
        _cache: {},

        _typeaheadTemplate:
        '<form>\

\

\
                    \

\

\
        </form>',

        _create: function() {
            var self = this;
            if (self.options.openInDialog) {
                self._createInDialog();
            } else {
                self._createInline();
            }
        },

        _createInline: function() {
            var self = this;
            self.element.addClass('category-picker');
            self._createNavigator();
            self._createSearchField();
            self.goToCategory(self.options.categoryId);
        },

        _createInDialog: function() {
            var self = this;
            ajaxDialog({
                title: self.options.dialogTitle,
                content: $('
', {class: 'category-picker-wrapper'}).append($('
'))[0].outerHTML,
                closeButton: '',
                onOpen: function(dialog) {
                    self.element = dialog.contentContainer.children().first();
                    self.dialog = dialog;
                    self._createInline();
                }
            });
        },

        _createNavigator: function() {
            var self = this;
            self.$category = $('
');
            self.$categoryTree = $('
', {class: 'group-list fixed-height'});
            self.$categoryResults = $('
', {class: 'group-list fixed-height'});
            self.$categoryNavigator = $('
', {class: 'category-navigator i-box just-group-list with-hover-effect'})
                .append($('
', {class: 'i-box-content'})
                    .append(self.$category)
                    .append(self.$categoryTree)
                    .append(self.$categoryResults));
            self.element.append(self.$categoryNavigator);
        },

        _createSearchField: function() {
            var self = this;

            // Visible search field
            self.$searchInput = $('<input>', {
                class: 'js-search-category js-typeahead',
                type: 'search',
                name: 'q',
                placeholder: $T.gettext("Search")
            }).attr('autocomplete', 'off');

            // Insert elements in DOM
            var $typeaheadForm = $($.parseHTML(self._typeaheadTemplate)[0]);
            $typeaheadForm.find('.typeahead__query').append(self.$searchInput);
            self.element.prepend($typeaheadForm);

            // Typeahead init
            self.$searchInput.typeahead({
                dynamic: true,
                filter: false,
                source: {
                    ajax: {
                        url: build_url(Indico.Urls.Categories.search),
                        path: 'categories',
                        data: {
                            q: "{{query}}"
                        }
                    }
                },
                resultContainer: false,
                cancelButton: false,
                display: 'title',
                callback: {
                    onClickAfter: function(node, a, item) {
                        self.goToCategory(item.id);
                    },
                    onResult: function(node, query, result) {
                        self._renderResults(result);
                    },
                    onHideLayout: function() {
                        self.$categoryResults.html('');
                        self.$category.show();
                        self.$categoryTree.show();
                    },
                    onShowLayout: function(node, query) {
                        self.$category.hide();
                        self.$categoryTree.hide();
                    }
                }
            });
        },

        _buildBreadcrumbs: function(path, clickable) {
            var self = this;
            var $breadcrumbs = $('
', {class: 'breadcrumbs'});
            var tag = clickable ? '' : '';

            _.each(path, function(category) {
                var $item = $(tag, {text: category.title});
                if (clickable) {
                    $item.attr('href', '');
                    $item.attr('title', $T.gettext("Go to: {0}".format(category.title)));
                }
                self._bindGoToCategoryOnClick($item, category.id);
                $breadcrumbs.append($('
').append($item));
            });

            return $breadcrumbs;
        },

        _buildCategory: function(category, isSubcategory, withBreadcrumbs, clickableBreadcrumbs) {
            var self = this;
            var tag = isSubcategory ? '
' : '
';
            var itemClass = isSubcategory ? 'subcategory' : 'current-category';

            var $category = $(tag, {
                class: 'item ' + itemClass,
                id: 'category-' + category.id
            });

            var $categoryTitle = $('
', {class: 'title-wrapper'});
            $categoryTitle.append($('', {
                class: 'title',
                text: category.title
            }));
            if (withBreadcrumbs) {
                $categoryTitle.append(self._buildBreadcrumbs(category.path, clickableBreadcrumbs));
            }

            $category.append($('
', {class: 'icon-wrapper'}));
            $category.append($categoryTitle);
            $category.append(self._buildSidePanel(category, !isSubcategory));
            return $category;
        },

        _buildCurrentCategory: function(category) {
            var self = this;
            return self._buildCategory(category, false, true, true);
        },

        _buildSubcategory: function(category, withBreadcrumbs) {
            var self = this;
            var $subcategory = self._buildCategory(category, true, withBreadcrumbs);

            if (category.can_access) {
                self._bindGoToCategoryOnClick($subcategory, category.id);
                $subcategory.addClass('can-access');
            }

            return $subcategory;
        },

        _buildSidePanel: function(category, withGoToParent) {
            var self = this;
            var $buttonWrapper = $('
', {class: 'button-wrapper'});

            var $button = $('', {
                class: 'action-button',
                text: self.options.actionButtonText
            }).on('click', function(evt) {
                evt.stopPropagation();
                if (!self.options.selectLeafOnly || category.category_count === 0) {
                    self._onAction(category);
                }
            });
            if (self.options.selectLeafOnly && category.category_count) {
                $button.addClass('disabled').attr('title', $T.gettext("You cannot select this category"));
            }
            $buttonWrapper.append($('
').append($button));

            if (withGoToParent && category.path && category.path.length) {
                var parent = _.last(category.path);
                var $arrowUp = $('', {
                    class: 'icon-arrow-up navigate-up',
                    title: $T.gettext("Go to parent: {0}".format(parent.title))
                }).on('click', function() {
                    self.goToCategory(parent.id);
                });
                $buttonWrapper.append($arrowUp);
            }

            if (category.is_protected) {
                var $protection = $('
').append($('', {class: 'icon-shield'}));
                $buttonWrapper.append($protection);
            }

            var $info = $('
')
                .append($('
', {text: $T.ngettext("{0} category", "{0} categories", category.category_count)
                                            .format(category.category_count)}))
                .append($('
', {text: $T.ngettext("{0} event", "{0} events", category.event_count)
                                            .format(category.event_count)}));
            $buttonWrapper.append($info);

            return $buttonWrapper;
        },

        _ellipsizeBreadcrumbs: function($category) {
            var self = this;
            var $breadcrumbs = $category.find('.breadcrumbs');
            var availableSpace = $category.find('.title-wrapper').width();
            var $ellipsis = $('
', {class: 'ellipsis'});
            var shortened = false;

            while ($breadcrumbs.outerWidth() >= availableSpace) {
                var $segments = $breadcrumbs.children(':not(.ellipsis)');
                var middleIndex = Math.floor($segments.length / 2);
                if (!shortened) {
                    $segments.eq(middleIndex).replaceWith($ellipsis);
                } else {
                    $segments.eq(middleIndex).remove();
                }
            }
        },

        _renderNavigator: function(data) {
            var self = this;

            if (data.category) {
                self.$category.html(self._buildCurrentCategory(data.category));
                self._ellipsizeBreadcrumbs(self.$category);
            }
            if (data.subcategories) {
                _.each(data.subcategories, function(subcategory) {
                    self.$categoryTree.append(self._buildSubcategory(subcategory));
                });
            }
        },

        _renderResults: function(categories) {
            var self = this;

            self.$categoryResults.html('');
            _.each(categories, function(category) {
                var $result = self._buildSubcategory(category, true);
                $result.find('.icon-wrapper').append($('', {
                    class: 'icon-search',
                    title: $T.gettext("Search result")
                }));
                self.$categoryResults.append($result);
                self._ellipsizeBreadcrumbs($result);
            });
        },

        _bindGoToCategoryOnClick: function($element, id) {
            var self = this;

            $element.on('click', function(evt) {
                evt.preventDefault();
                self.goToCategory(id);
            });
        },

        _getCategoryInfo: function(id) {
            var self = this;
            var dfd = $.Deferred();

            if (self._cache[id]) {
                _.defer(function() {
                    dfd.resolve(self._cache[id]);
                });
            } else {
                $.ajax({
                    url: build_url(Indico.Urls.Categories.info, {category_id: id}),
                    dataType: 'json',
                    error: handleAjaxError,
                    success: function(data) {
                        if (data) {
                            self._cache[id] = data;
                            dfd.resolve(data);
                        }
                    }
                });
            }

            return dfd.promise();
        },

        _onAction: function(category) {
            var self = this;
            self.options.onAction(category);
            if (self.dialog) {
                self.dialog.close();
            }
        },

        goToCategory: function(id) {
            var self = this;

            $('.category-navigator .item').animate({
                height: 0,
                paddingTop: 0,
                paddingBottom: 0,
                border: 0
            }, {
                complete: function() {
                    $(this).remove();
                }
            }).find('.title').fadeOut();

            self._getCategoryInfo(id).then(self._renderNavigator.bind(self));
        }
    });
})(jQuery);
<|endoftext|>