<commit_before>var dnss = {
  skills: {},
  types: null,
  queue: [],
  messages: {},
  max_levels: null,
  max_sp: null,
  build: Array(72 + 1 + 1).join('-').split(''),
  positions: [],
  current: {},
  total_skills: $('.skill[data-id]').length,
  parsed_total: 0,
  jobs: [],

  init: function(jobIdentifiers, max_levels, max_sp, start_build) {
    this.jobs = jobIdentifiers;
    this.max_levels = max_levels;
    this.max_sp = max_sp;

    // sets the starting build
    if (start_build) {
      var m = start_build.match(/^[0-9a-zA-Z-]+/g);
      var build = m.shift();
      if (build && build.length > 72) {
        build = build.split('');
        for (var i = 0; i < build.length - 1; i++) {
          this.build[i] = build[i];
        }
        this.build[this.build.length - 1] = build[build.length - 1];

        if (inv_build_map[build[build.length - 1]] & 1) {
          description.flip();
        }
      }
    }

    // gives position to every container
    $('.container').each(function(i) {
      var element = $(this).find('.skill');
      if (element.length) {
        element.data('position', i);
      }
    });

    // types
    $.getJSON('/json/types.min.json', function(json) {
      dnss.types = json;
      while (dnss.queue.length) {
        dnss.add_skills(dnss.queue.shift());
      }
    });

    // get json
    for (var i in jobIdentifiers) {
      $.getJSON(fmt('/json/$0.min.json', jobIdentifiers[i]), function(json) {
        for (var j in json.messages) { // I don't trust async $.extends()
          dnss.messages[j] = json.messages[j];
        }

        if (dnss.types) {
          dnss.add_skills(json);
        } else {
          dnss.queue.push(json);
        }
      });
    }

    // mode bind
    $('#mode').bind('click', function() {
      switch($(this).val()) {
        case 'pve': $(this).val('pvp'); break;
        case 'pvp': $(this).val('pve'); break;
      }
     description.flip();
    });

    // job list bind
    $('#job-list-sp li[data-job]').each(function() {
      var idx = $(this).data('job');
      $(this).click(function() {
        $('#job-list-sp li[data-job][class="active"]').removeClass('active');
        $(this).addClass('active');
        $('.skill-tree').hide();
        $('#skill-tree-' + idx).show();
      })
    });
  },

  add_skills: function(job) {
    var skills = job.skills, default_skills = job.default_skills, adv = job.advancement;
    delete job.advancement;
    for (i in skills) {
      var skill = skills[i];
      var levels = skill.levels;
      delete skill.levels; // don't want this lingering

      var l = 0, is_default = false;
      if (default_skills) {
        for (var j = 0; j < default_skills.length; j++) {
          if (default_skills[j] == i) {
            is_default = true;
            break;
          }
        }
      }

      if (is_default) {
        this.skills[i] = new Skill($.extend({}, skill, levels[0], {level: 1, advancement: adv, id: i}));
        l++;
      } else {
        this.skills[i] = new Skill($.extend({}, skill, {image: skill.image+'_b', required_level: 0, level: 0, advancement: adv, id: i}));
      }

      var curr = this.skills[i], max = curr, end, sp_sum = 0;
      max = curr;

      for (; l < levels.length; l++) {
        curr.next = new Skill($.extend({}, skill, levels[l], {level: l+1, advancement: adv, id: i}));
        curr.next.prev = curr;
        curr = curr.next;

        sp_sum += curr.get_spcost();
        curr.total_sp_cost = sp_sum;

        if (curr.required_level <= this.max_levels[adv] ) {
          max = curr;
        }
      }
      end = curr;

      curr = this.skills[i];
      while (curr) {
        curr.start = this.skills[i];
        curr.max = max;
        curr.end = end;
        curr = curr.next;
      }

      this.parsed_total++;
      this.set_starting_skill(i);
    }
  },

  get_skill: function(id, lvl) {
    var skill = this.skills[id];
    if (skill.level == 1 && lvl == 0) {
      return skill;
    }

    while (skill.level != lvl) {
      skill = skill.next;
    }

    return skill;
  },

  $: function(id) {
    return $('.skill[data-id=' + id + ']');
  },

  set_starting_skill: function(id) {
    var $skill = this.$(id), position = $skill.data('position');
    var skill = this.get_skill(id, inv_build_map[this.build[position]]);
    skill.bind($skill);

    $skill.bind({
      mousedown: function(e) {
        var skill = $(this).data('skill'), change;
        var extreme = e.shiftKey || e.ctrlKey;
        if (e.button == 0) { // left click
          if (extreme) {
            if (skill.level < skill.max.level) {
              change = skill.max;
            }
          } else {
            if (skill.next) {
              change = skill.next;
            }
          }
        } else if(e.button == 2) { // right click
          if (extreme) {
            if (skill.level != skill.start.level) {
              change = skill.start;
            }
          } else {
            if (skill.prev) {
              change = skill.prev;
            }
          }
        }

        if (change) {
            description.use(change);
            change.bind($(this));
            dnss.update();
            requirements.check();
        }
      },
      mouseenter: function() {
        description.use($(this).data('skill'));
      }
    });

    $skill.on('contextmenu', function(){ // disable menu
      return false;
    });

    if (this.parsed_total == this.total_skills) {
      this.update();
    }
  },

  get_all_sp: function() {
    var sp = [0,0,0];
    for (var i = 0; i < 72; i++) {
      if (this.positions[i]) {
        sp[Math.floor(i/24)] += this.positions[i].total_sp_cost;
      }
    }

    return sp;
  },

  update: function() {
    var sp = this.get_all_sp();
    for (i in sp) {
      $('#job-list-sp li[data-job=' + i + '] .sp').html(sp[i] + '/' + this.max_sp[i]);
    }

    $('#job-list-sp li:last .sp').html(sp[0]+sp[1]+sp[2] + '/' + this.max_sp[3]);

    var $build = $('#build');
    $build.val(fmt($build.data('base'), window.location) + '/' + this.build.join(''));
  }
};<commit_msg>Wrong location. Wrong URL.<commit_after>var dnss = {
  skills: {},
  types: null,
  queue: [],
  messages: {},
  max_levels: null,
  max_sp: null,
  build: Array(72 + 1 + 1).join('-').split(''),
  positions: [],
  current: {},
  total_skills: $('.skill[data-id]').length,
  parsed_total: 0,
  jobs: [],

  init: function(jobIdentifiers, max_levels, max_sp, start_build) {
    this.jobs = jobIdentifiers;
    this.max_levels = max_levels;
    this.max_sp = max_sp;

    // sets the starting build
    if (start_build) {
      var m = start_build.match(/^[0-9a-zA-Z-]+/g);
      var build = m.shift();
      if (build && build.length > 72) {
        build = build.split('');
        for (var i = 0; i < build.length - 1; i++) {
          this.build[i] = build[i];
        }
        this.build[this.build.length - 1] = build[build.length - 1];
      }
    }

    // gives position to every container
    $('.container').each(function(i) {
      var element = $(this).find('.skill');
      if (element.length) {
        element.data('position', i);
      }
    });

    // types
    $.getJSON('/json/types.min.json', function(json) {
      dnss.types = json;
      while (dnss.queue.length) {
        dnss.add_skills(dnss.queue.shift());
      }
    });

    // get json
    for (var i in jobIdentifiers) {
      $.getJSON(fmt('/json/$0.min.json', jobIdentifiers[i]), function(json) {
        for (var j in json.messages) { // I don't trust async $.extends()
          dnss.messages[j] = json.messages[j];
        }

        if (dnss.types) {
          dnss.add_skills(json);
        } else {
          dnss.queue.push(json);
        }
      });
    }

    // mode bind
    $('#mode').bind('click', function() {
      switch($(this).val()) {
        case 'pve': $(this).val('pvp'); break;
        case 'pvp': $(this).val('pve'); break;
      }
     description.flip();
    });

    // job list bind
    $('#job-list-sp li[data-job]').each(function() {
      var idx = $(this).data('job');
      $(this).click(function() {
        $('#job-list-sp li[data-job][class="active"]').removeClass('active');
        $(this).addClass('active');
        $('.skill-tree').hide();
        $('#skill-tree-' + idx).show();
      })
    });
  },

  add_skills: function(job) {
    var skills = job.skills, default_skills = job.default_skills, adv = job.advancement;
    delete job.advancement;
    for (i in skills) {
      var skill = skills[i];
      var levels = skill.levels;
      delete skill.levels; // don't want this lingering

      var l = 0, is_default = false;
      if (default_skills) {
        for (var j = 0; j < default_skills.length; j++) {
          if (default_skills[j] == i) {
            is_default = true;
            break;
          }
        }
      }

      if (is_default) {
        this.skills[i] = new Skill($.extend({}, skill, levels[0], {level: 1, advancement: adv, id: i}));
        l++;
      } else {
        this.skills[i] = new Skill($.extend({}, skill, {image: skill.image+'_b', required_level: 0, level: 0, advancement: adv, id: i}));
      }

      var curr = this.skills[i], max = curr, end, sp_sum = 0;
      max = curr;

      for (; l < levels.length; l++) {
        curr.next = new Skill($.extend({}, skill, levels[l], {level: l+1, advancement: adv, id: i}));
        curr.next.prev = curr;
        curr = curr.next;

        sp_sum += curr.get_spcost();
        curr.total_sp_cost = sp_sum;

        if (curr.required_level <= this.max_levels[adv] ) {
          max = curr;
        }
      }
      end = curr;

      curr = this.skills[i];
      while (curr) {
        curr.start = this.skills[i];
        curr.max = max;
        curr.end = end;
        curr = curr.next;
      }

      this.parsed_total++;
      this.set_starting_skill(i);
    }
  },

  get_skill: function(id, lvl) {
    var skill = this.skills[id];
    if (skill.level == 1 && lvl == 0) {
      return skill;
    }

    while (skill.level != lvl) {
      skill = skill.next;
    }

    return skill;
  },

  $: function(id) {
    return $('.skill[data-id=' + id + ']');
  },

  set_starting_skill: function(id) {
    var $skill = this.$(id), position = $skill.data('position');
    var skill = this.get_skill(id, inv_build_map[this.build[position]]);
    skill.bind($skill);

    $skill.bind({
      mousedown: function(e) {
        var skill = $(this).data('skill'), change;
        var extreme = e.shiftKey || e.ctrlKey;
        if (e.button == 0) { // left click
          if (extreme) {
            if (skill.level < skill.max.level) {
              change = skill.max;
            }
          } else {
            if (skill.next) {
              change = skill.next;
            }
          }
        } else if(e.button == 2) { // right click
          if (extreme) {
            if (skill.level != skill.start.level) {
              change = skill.start;
            }
          } else {
            if (skill.prev) {
              change = skill.prev;
            }
          }
        }

        if (change) {
            description.use(change);
            change.bind($(this));
            dnss.update();
            requirements.check();
        }
      },
      mouseenter: function() {
        description.use($(this).data('skill'));
      }
    });

    $skill.on('contextmenu', function(){ // disable menu
      return false;
    });

    if (this.parsed_total == this.total_skills) {
      this.update();
      if (inv_build_map[this.build[this.build.length - 1]] & 1) {
        description.flip();
      }
    }
  },

  get_all_sp: function() {
    var sp = [0,0,0];
    for (var i = 0; i < 72; i++) {
      if (this.positions[i]) {
        sp[Math.floor(i/24)] += this.positions[i].total_sp_cost;
      }
    }

    return sp;
  },

  update: function() {
    var sp = this.get_all_sp();
    for (i in sp) {
      $('#job-list-sp li[data-job=' + i + '] .sp').html(sp[i] + '/' + this.max_sp[i]);
    }

    $('#job-list-sp li:last .sp').html(sp[0]+sp[1]+sp[2] + '/' + this.max_sp[3]);

    var $build = $('#build');
    $build.val(fmt($build.data('base'), window.location) + '/?' + this.build.join(''));
  }
};<|endoftext|>


<commit_before>// Endpoint class

function Endpoint(address) {
    var ClosedState = 0;
    var ConnectingState = 1;
    var ActiveState = 2;

    var reconnectTries = 0;

    console.log("connecting to " + address);
    var webSocket = null;
    var state = ClosedState;

    var that = this;

    var incomingMessage = null;
    var outgoingArray = null;

    open();

    function open() {
        if (webSocket != null) {
            webSocket.onopen = null;
            webSocket.onclose = null;
            webSocket.onmessage = null;
        }

        outgoingArray = [];

        webSocket = new window.WebSocket(address, ["WSNetMQ"]);
        webSocket.binaryType = "arraybuffer";
        state = ConnectingState;

        webSocket.onopen = onopen;
        webSocket.onclose = onclose;
        webSocket.onmessage = onmessage;
    }

    function onopen (e) {
        console.log("WebSocket opened to " + address);
        reconnectTries = 0;

        state = ActiveState;

        if (that.activated != null) {
            that.activated(that);
        }
    };

    function onclose(e) {
        console.log("WebSocket closed " + address);
        var stateBefore = state;
        state = ClosedState;

        if (stateBefore == ActiveState && that.deactivated != null) {
            that.deactivated(that);
        }

        if (reconnectTries > 10) {
            window.setTimeout(open, 2000);
        } else {
            open();
        }
    };

    function onmessage(ev) {
        var view = new Uint8Array(ev.data);
        var more = view[0];

        if (incomingMessage == null) {
            incomingMessage = new JSMQMessage();
        }

        incomingMessage.addBuffer(ev.data);

        // last message
        if (more == 0) {
            if (that.onMessage != null) {
                that.onMessage(that, incomingMessage);
            }

            incomingMessage = null;
        }
    };

    // activated event, when the socket is open
    this.activated = null;

    // deactivated event
    this.deactivated = null;

    this.onMessage = null;

    this.getIsActive = function() {
        return state == ActiveState;
    };

    this.write = function (frame, more) {
        if (more) {
            outgoingArray.push(frame);
        } else {
            outgoingArray.push(frame);

            for (var i = 0; i < outgoingArray.length; i++) {
                webSocket.send(outgoingArray[i]);
            }

            outgoingArray = [];
        }
    };
}

// LoadBalancer

function LB() {
    var endpoints = [];
    var current = 0;
    var isActive = false;

    var inprogress = false;
    var dropping = false;

    this.writeActivated = null;

    var that = this;

    this.attach = function(endpoint) {
        endpoints.push(endpoint);

        if (!isActive) {
            isActive = true;

            if (that.writeActivated != null)
                that.writeActivated();
        }
    };

    this.terminated = function(endpoint) {
        var index = endpoints.indexOf(endpoint);

        if (current == index && inprogress) {
                dropping = true;
        }

        if (current == endpoints.length - 1) {
            current = 0;
        }

        endpoints.splice(index, 1);
    };

    this.send = function (message, more) {
        if (dropping) {
            inprogress = more;
            dropping = more;

            return true;
        }

        if (endpoints.length == 0) {
            isActive = false;
            return false;
        }

        endpoints[current].write(message, more);

        if (!more) {
            current = (current + 1) % endpoints.length;
        }

        return true;
    };

    this.getHasOut = function () {
        if (inprogress)
            return true;

        return endpoints.length > 0;
    };
}

// SocketBase Class

function SocketBase(xattachEndpoint, xendpointTerminated, xhasOut, xsend, xonMessage) {

    this.onMessage = null;
    this.sendReady = null;

    var endpoints = [];

    function onEndpointActivated(endpoint) {
        xattachEndpoint(endpoint);
    }

    function onEndpointDeactivated(endpoint) {
        xendpointTerminated(endpoint);
    }

    this.connect = function (address) {
        var endpoint = new Endpoint(address);
        endpoint.activated = onEndpointActivated;
        endpoint.deactivated = onEndpointDeactivated;
        endpoint.onMessage = xonMessage;
        endpoints.push(endpoint);
    };

    this.disconnect = function(address) {
        // TODO: implement disconnect
    };

    this.send = function (message) {
        for (var i = 0; i < message.getSize(); i++) {

            var frame = message.getFrameBuffer(i);
            var more = new Uint8Array(frame)[0] == 1;

            return xsend(frame, more);
        }
    };

    this.getHasOut = function() {
        return xhasOut();
    };
}

function Dealer() {

    var lb = new LB();

    var that = new SocketBase(xattachEndpoint, xendpointTerminated, xhasOut, xsend, xonMessage);

    lb.writeActivated = function() {
        if (that.sendReady != null) {
            that.sendReady(that);
        }
    };

    function xattachEndpoint(endpoint) {
        lb.attach(endpoint);
    }

    function xendpointTerminated(endpoint) {
        lb.terminated(endpoint);
    }

    function xhasOut() {
        return lb.getHasOut();
    }

    function xsend(message, more) {
        return lb.send(message, more);
    }

    function xonMessage(endpoint, message) {
        if (that.onMessage != null) {
            that.onMessage(message);
        }
    }

    return that;
}

function Subscriber() {

    var that = new SocketBase(xattachEndpoint, xendpointTerminated, xhasOut, xsend, xonMessage);;

    var subscriptions = [];
    var endpoints = [];

    var isActive = false;

    that.subscribe = function (subscription) {
        // TODO: check if the subscription already exist
        subscriptions.push(subscription);

        for (var i = 0; i < endpoints.length; i++) {
            endpoints[i].write("1" + subscription);
        }
    }

    that.unsubscribe = function (subscription) {
        // TODO: check if the subscription even exist
        var index = subscriptions.indexOf(subscription);
        subscriptions.splice(index, 1);

        for (var i = 0; i < endpoints.length; i++) {
            endpoints[i].write("0" + subscription);
        }
    }

    function xattachEndpoint(endpoint) {
        endpoints.push(endpoint);

        for (var i = 0; i < subscriptions.length; i++) {
            endpoint.write("1" + subscriptions[i], false);
        }

        if (!isActive) {
            isActive = true;

            if (that.sendReady != null) {
                that.sendReady(that);
            }
        }
    }

    function xendpointTerminated(endpoint) {
        var index = endpoints.indexOf(endpoint);
        endpoints.splice(index, 1);
    }

    function xhasOut() {
        return false;
    }

    function xsend(message, more) {
        throw new "Send not supported on sub socket";
    }

    function xonMessage(endpoint, message) {
        if (that.onMessage != null) {
            that.onMessage(message);
        }
    }

    return that;
}

function JSMQMessage() {
    var frames = [];

    this.getSize = function() {
        return frames.length;
    }

    this.addString = function (str) {
        str = String(str);

        // one more byte is saved for the more byte
        var buffer = new ArrayBuffer(str.length + 1);

        str2ab(str, buffer);

        frames.push(buffer);
    }

    this.popString = function() {
        var frame = frames[0];
        frames.splice(0, 1);

        return ab2str(frame);
    }

    this.getFrameBuffer = function (i) {
        return frames[i];
    }

    this.addBuffer = function(buffer) {
        frames.push(buffer);
    }

    function ab2str(buffer) {
        var bufView = new Uint8Array(buffer, 1);

        return String.fromCharCode.apply(null, bufView);
    }

    function str2ab(str, buffer) {
        var bufView = new Uint8Array(buffer, 1);
        for (var i = 0, strLen = str.length; i < strLen; i++) {
            var char =  str.charCodeAt(i);

            if (char > 255) {
                // only ASCII are supported at the moment, we will put ? instead
                bufView[i] = "?".charCodeAt();
            } else {
                bufView[i] = char;
            }
        }
    }
}

<commit_msg>fix subscriber to be binary as well<commit_after>// Endpoint class

function Endpoint(address) {
    var ClosedState = 0;
    var ConnectingState = 1;
    var ActiveState = 2;

    var reconnectTries = 0;

    console.log("connecting to " + address);
    var webSocket = null;
    var state = ClosedState;

    var that = this;

    var incomingMessage = null;

    open();

    function open() {
        if (webSocket != null) {
            webSocket.onopen = null;
            webSocket.onclose = null;
            webSocket.onmessage = null;
        }

        outgoingArray = [];

        webSocket = new window.WebSocket(address, ["WSNetMQ"]);
        webSocket.binaryType = "arraybuffer";
        state = ConnectingState;

        webSocket.onopen = onopen;
        webSocket.onclose = onclose;
        webSocket.onmessage = onmessage;

        reconnectTries++;
    }

    function onopen (e) {
        console.log("WebSocket opened to " + address);
        reconnectTries = 0;

        state = ActiveState;

        if (that.activated != null) {
            that.activated(that);
        }
    };

    function onclose(e) {
        console.log("WebSocket closed " + address);
        var stateBefore = state;
        state = ClosedState;

        if (stateBefore == ActiveState && that.deactivated != null) {
            that.deactivated(that);
        }

        if (reconnectTries > 10) {
            window.setTimeout(open, 2000);
        } else {
            open();
        }
    };

    function onmessage(ev) {
        if (ev.data instanceof Blob) {
            var arrayBuffer;
            var fileReader = new FileReader();
            fileReader.onload = function () {
                processFrame(this.result);
            };
            fileReader.readAsArrayBuffer(ev.data);
        } else if (ev.data instanceof ArrayBuffer) {
            processFrame(ev.data);
        }
        // Other message type are not supported and will just be dropped
    };

    function processFrame(frame) {
        var view = new Uint8Array(frame);
        var more = view[0];

        if (incomingMessage == null) {
            incomingMessage = new JSMQMessage();
        }

        incomingMessage.addBuffer(frame);

        // last message
        if (more == 0) {
            if (that.onMessage != null) {
                that.onMessage(that, incomingMessage);
            }

            incomingMessage = null;
        }
    }

    // activated event, when the socket is open
    this.activated = null;

    // deactivated event
    this.deactivated = null;

    this.onMessage = null;

    this.getIsActive = function() {
        return state == ActiveState;
    };

    this.write = function (message) {
        var messageSize = message.getSize();

        for (var j = 0; j < messageSize; j++) {
            var frame = message.getBuffer(j);

            var data = new Uint8Array(frame.length + 1);
            data[0] = j == messageSize - 1 ? 0 : 1; // set the more byte

            data.set(frame, 1);

            webSocket.send(data);
        }
    };
}

// LoadBalancer

function LB() {
    var endpoints = [];
    var current = 0;
    var isActive = false;

    this.writeActivated = null;

    var that = this;

    this.attach = function(endpoint) {
        endpoints.push(endpoint);

        if (!isActive) {
            isActive = true;

            if (that.writeActivated != null)
                that.writeActivated();
        }
    };

    this.terminated = function(endpoint) {
        var index = endpoints.indexOf(endpoint);

        if (current == endpoints.length - 1) {
            current = 0;
        }

        endpoints.splice(index, 1);
    };

    this.send = function (message) {
        if (endpoints.length == 0) {
            isActive = false;
            return false;
        }

        endpoints[current].write(message);
        current = (current + 1) % endpoints.length;

        return true;
    };

    this.getHasOut = function () {
        if (inprogress)
            return true;

        return endpoints.length > 0;
    };
}

// SocketBase Class

function SocketBase(xattachEndpoint, xendpointTerminated, xhasOut, xsend, xonMessage) {

    this.onMessage = null;
    this.sendReady = null;

    var endpoints = [];

    function onEndpointActivated(endpoint) {
        xattachEndpoint(endpoint);
    }

    function onEndpointDeactivated(endpoint) {
        xendpointTerminated(endpoint);
    }

    this.connect = function (address) {
        var endpoint = new Endpoint(address);
        endpoint.activated = onEndpointActivated;
        endpoint.deactivated = onEndpointDeactivated;
        endpoint.onMessage = xonMessage;
        endpoints.push(endpoint);
    };

    this.disconnect = function(address) {
        // TODO: implement disconnect
    };

    this.send = function (message) {
        return xsend(message);
    };

    this.getHasOut = function() {
        return xhasOut();
    };
}

function Dealer() {

    var lb = new LB();

    var that = new SocketBase(xattachEndpoint, xendpointTerminated, xhasOut, xsend, xonMessage);

    lb.writeActivated = function() {
        if (that.sendReady != null) {
            that.sendReady(that);
        }
    };

    function xattachEndpoint(endpoint) {
        lb.attach(endpoint);
    }

    function xendpointTerminated(endpoint) {
        lb.terminated(endpoint);
    }

    function xhasOut() {
        return lb.getHasOut();
    }

    function xsend(message) {
        return lb.send(message);
    }

    function xonMessage(endpoint, message) {
        if (that.onMessage != null) {
            that.onMessage(message);
        }
    }

    return that;
}

function Subscriber() {

    var that = new SocketBase(xattachEndpoint, xendpointTerminated, xhasOut, xsend, xonMessage);;

    var subscriptions = [];
    var endpoints = [];

    var isActive = false;

    that.subscribe = function (subscription) {

        if (subscription instanceof Uint8Array) {
            subscriptions.push(subscription);
        }
        else if (subscription instanceof ArrayBuffer) {
            subscription = new Uint8Array(subscription);

            subscriptions.push(subscription);
        } else {
            subscription = String(subscription);

            subscription = StringUtility.StringToUint8Array(subscription);
        }

        // TODO: check if the subscription already exist
        subscriptions.push(subscription)

        var message = createSubscriptionMessage(subscription, true);

        for (var i = 0; i < endpoints.length; i++) {
            endpoints[i].write(message);
        }
    }

    that.unsubscribe = function (subscription) {
        // TODO: check if the subscription even exist
        var index = subscriptions.indexOf(subscription);
        subscriptions.splice(index, 1);

        var message = createSubscriptionMessage(subscription, false);

        for (var i = 0; i < endpoints.length; i++) {
            endpoints[i].write(message);
        }
    }

    function createSubscriptionMessage(subscription, subscribe) {
        var frame = new Uint8Array(subscription.length + 1);
        frame[0] = subscribe ? 1 : 0;
        frame.set(subscription, 1);

        var message = new JSMQMessage();
        message.addBuffer(frame);

        return message;
    }

    function xattachEndpoint(endpoint) {
        endpoints.push(endpoint);

        for (var i = 0; i < subscriptions.length; i++) {
            var message = createSubscriptionMessage(subscriptions[i], true);

            endpoint.write(message);
        }

        if (!isActive) {
            isActive = true;

            if (that.sendReady != null) {
                that.sendReady(that);
            }
        }
    }

    function xendpointTerminated(endpoint) {
        var index = endpoints.indexOf(endpoint);
        endpoints.splice(index, 1);
    }

    function xhasOut() {
        return false;
    }

    function xsend(message, more) {
        throw new "Send not supported on sub socket";
    }

    function xonMessage(endpoint, message) {
        if (that.onMessage != null) {
            that.onMessage(message);
        }
    }

    return that;
}

function JSMQMessage() {
    var frames = [];

    this.getSize = function() {
        return frames.length;
    }

    // add string at the begining of the message
    this.prependString = function(str) {
        str = String(str);

        // one more byte is saved for the more byte
        var buffer = new Uint8Array(str.length);

        StringUtility.StringToUint8Array(str, buffer);

        frames.splice(0, 0, buffer);
    }

    // add the string at the end of the message
    this.addString = function(str) {
        str = String(str);

        // one more byte is saved for the more byte
        var buffer = new Uint8Array(str.length);

        StringUtility.StringToUint8Array(str, buffer);
        frames.push(buffer);
    }

    // pop a string from the begining of the message
    this.popString = function() {
        var frame = this.popBuffer();

        return StringUtility.Uint8ArrayToString(frame);
    }

    this.popBuffer = function() {
        var frame = frames[0];
        frames.splice(0, 1);

        return frame;
    }

    // addd buffer at the end of the message
    this.addBuffer = function (buffer) {

        if (buffer instanceof ArrayBuffer) {
            frames.push(new Uint8Array(buffer));
        }
        else if (buffer instanceof Uint8Array) {
            frames.push(buffer);
        } else {
            throw new "unknown buffer type";
        }
    }

    // return Uint8Array at location i
    this.getBuffer = function(i) {
        return frames[i];
    }
}

function StringUtility()
{ }

StringUtility.StringToUint8Array = function (str, buffer) {
    if (typeof buffer === 'undefined') {
        buffer = new Uint8Array(str.length);
    }

    for (var i = 0, strLen = str.length; i < strLen; i++) {
        var char = str.charCodeAt(i);

        if (char > 255) {
            // only ASCII are supported at the moment, we will put ? instead
            buffer[i] = "?".charCodeAt();
        } else {
            buffer[i] = char;
        }
    }

    return buffer;
}

StringUtility.Uint8ArrayToString = function(buffer) {
    return String.fromCharCode.apply(null, buffer);
}<|endoftext|>


<commit_before>'use strict';

var $ = require('jquery');
var m = require('mithril');
var Raven = require('raven-js');
var Treebeard = require('treebeard');
var citations = require('js/citations');
var clipboard = require('js/clipboard');

var apaStyle = require('raw!styles/apa.csl');

var errorPage = require('raw!citations_load_error.html');

require('css/fangorn.css');

function resolveToggle(item) {
    var toggleMinus = m('i.fa.fa-minus', ' ');
    var togglePlus = m('i.fa.fa-plus', ' ');
    if (item.kind === 'folder') {
        return item.open ? toggleMinus : togglePlus;
    } else {
        return '';
    }
}

function resolveIcon(item) {
    var privateFolder = m('img', {
        src: '/static/img/hgrid/fatcowicons/folder_delete.png'
    });
    var openFolder = m('i.fa fa-folder-open', ' ');
    var closedFolder = m('i.fa fa-folder', ' ');

    if (item.kind === 'folder') {
        return item.open ? openFolder : closedFolder;
    } else if (item.kind === 'message'){
        return '';
    } else if (item.data.icon) {
        return m('i.fa.' + item.data.icon, ' ');
    } else {
        return m('i.fa fa-file-o');
    }
}

var utils = {
    reduce: function(array, dst, fn) {
        for (var i = 0; i < array.length; i++) {
            fn(array[i], dst);
        }
        return dst;
    },
    zip: function(array1, array2) {
        var zipped = [];
        var min = Math.min(array1.length, array2.length);
        for (var i = 0; i < min; i++) {
            zipped.push([array1[i], array2[i]]);
        }
        return zipped;
    }
};

var objectify = function(array, key) {
    key = key || 'id';
    return utils.reduce(
        array, {},
        function(item, acc) {
            return acc[item[key]] = item;
        }
    );
};

var formatResult = function(state) {
    return '
' + state.title + '
';
};

var formatSelection = function(state) {
    return state.title;
};

var mergeConfigs = function() {
    var unloads = [];
    var args = Array.prototype.slice.call(arguments);
    return function(elm, isInit, ctx) {
        for (var i = 0; i < args.length; i++) {
            args[i] && args[i](elm, isInit, ctx);
            ctx.onunload && unloads.push(ctx.onunload);
            ctx.onunload = null;
        }
        ctx.onunload = function() {
            for (var i = 0; i < unloads.length; i++) {
                unloads[i]();
            }
        };
    };
};

var tooltipConfig = function(elm, isInit, ctx) {
    var $elm = $(elm);
    $elm.tooltip({
        container: 'body'
    });
    ctx.onunload = function() {
        $elm.tooltip('destroy');
    };
};

var makeButtons = function(item, col, buttons) {
    return buttons.map(function(button) {
        var self = this;
        return m(
            'a', {
                'data-col': item.id
            }, [
                m(
                    'i', {
                        title: button.tooltip,
                        style: button.style,
                        class: button.css,
                        'data-toggle': 'tooltip',
                        'data-placement': 'bottom',
                        'data-clipboard-target': item.data.csl ? item.data.csl.id : button.clipboard,
                        config: mergeConfigs(button.config, tooltipConfig),
                        onclick: button.onclick ?
                            function(event) {
                                button.onclick.call(self, event, item, col);
                            } : null
                    }, [
                        m(
                            'span', {
                                class: button.icon
                            },
                            button.name
                        )
                    ]
                )
            ]
        );
    });
};

var buildExternalUrl = function(csl) {
    if (csl.URL) {
        return csl.URL;
    } else if (csl.DOI) {
        return 'http://dx.doi.org/' + csl.DOI;
    } else if (csl.PMID) {
        return 'http://www.ncbi.nlm.nih.gov/pubmed/' + csl.PMID;
    }
    return null;
};

var makeClipboardConfig = function(getText) {
    return function(elm, isInit, ctx) {
        var $elm = $(elm);
        if (!elm._client) {
            elm._client = clipboard(elm);
            // Attach `beforecopy` handler to ensure updated clipboard text
            if (getText) {
                elm._client.on('beforecopy', function() {
                    $elm.attr('data-clipboard-text', getText());
                });
            }
        }
        ctx.onunload = function() {
            elm._client && elm._client.destroy();
        };
    };
};

var renderActions = function(item, col) {
    var self = this;
    var buttons = [];
    if (item.kind === 'file') {
        buttons.push({
            name: '',
            icon: 'fa fa-file-o',
            css: 'btn btn-default btn-xs',
            tooltip: 'Copy citation',
            clipboard: self.getCitation(item),
            config: makeClipboardConfig()
        });
        // Add link to external document
        var externalUrl = buildExternalUrl(item.data.csl);
        if (externalUrl) {
            buttons.push({
                name: '',
                icon: 'fa fa-external-link',
                css: 'btn btn-default btn-xs',
                tooltip: 'View original document',
                onclick: function(event) {
                    window.open(externalUrl);
                }
            });
        }
        // Add link to document on reference management service
        if (item.data.serviceUrl) {
            buttons.push({
                name: '',
                icon: 'fa fa-link',
                css: 'btn btn-default btn-xs',
                tooltip: 'View on ' + self.provider,
                onclick: function(event) {
                    window.open(item.data.serviceUrl);
                }
            });
        }
    } else if (item.kind === 'folder' && item.open && item.children.length) {
        buttons.push({
            name: '',
            icon: 'fa fa-file-o',
            css: 'btn btn-default btn-xs',
            tooltip: 'Copy citations',
            config: makeClipboardConfig(function() {
                return self.getCitations(item).join('\n');
            })
        });
        buttons.push({
            name: '',
            icon: 'fa fa-arrow-circle-o-down',
            css: 'btn btn-default btn-xs',
            tooltip: 'Download citations',
            config: function(elm, isInit, ctx) {
                var text = '{\\rtf1\\ansi\n' + self.getCitations(item, true).join('\n') + '\n}';
                $(elm).parent('a')
                    .attr('href', 'data:text/enriched;charset=utf-8,' + encodeURIComponent(text))
                    .attr('download', item.data.name + '-' + self.styleName + '.rtf');
            }
        });
    }
    return makeButtons(item, col, buttons);
};

var treebeardOptions = {
    rowHeight: 30,
    lazyLoad: true,
    showFilter: false,
    resolveIcon: resolveIcon,
    resolveToggle: resolveToggle,
    lazyLoadPreprocess: function(res) {
        return res.contents;
    },
    columnTitles: function() {
        return [{
            title: 'Citation',
            width: '80%',
            sort: false
        }, {
            title: 'Actions',
            width: '20%',
            sort: false
        }];
    }
};

var CitationGrid = function(provider, gridSelector, styleSelector, apiUrl) {
    var self = this;

    self.provider = provider;
    self.gridSelector = gridSelector;
    self.styleSelector = styleSelector;
    self.apiUrl = apiUrl;

    self.styleName = 'apa';
    self.styleXml = apaStyle;
    self.bibliographies = {};

    self.initTreebeard();
    self.initStyleSelect();
};

CitationGrid.prototype.initTreebeard = function() {
    var self = this;
    var options = $.extend({
            divID: self.gridSelector.replace('#', ''),
            filesData: self.apiUrl,
            resolveLazyloadUrl: function(item) {
                return item.data.urls.fetch;
            },
            // Wrap callback in closure to preserve intended `this`
            resolveRows: function() {
                return self.resolveRowAux.call(self, arguments);
            },
            ondataloaderror: function(err) {
                $(self.gridSelector).html(errorPage);
            }
        },
        treebeardOptions
    );
    var preprocess = options.lazyLoadPreprocess;
    options.lazyLoadPreprocess = function(data){
        data = preprocess(data);
        // TODO remove special case for Zotero
        if (self.provider === 'Zotero') {
            if (data.length >= 200) {
        data.push({
                    name: 'Only 200 citations may be displayed',
                    kind: 'message'
                });
            }
        }
        return data;
    };
    self.treebeard = new Treebeard(options);
};

CitationGrid.prototype.initStyleSelect = function() {
    var self = this;
    var $input = $(self.styleSelector);
    $input.select2({
        allowClear: false,
        formatResult: formatResult,
        formatSelection: formatSelection,
        placeholder: 'Citation Style (e.g. "APA")',
        minimumInputLength: 1,
        ajax: {
            url: '/api/v1/citations/styles/',
            quietMillis: 200,
            data: function(term, page) {
                return {
                    q: term
                };
            },
            results: function(data, page) {
                return {
                    results: data.styles
                };
            },
            cache: true
        }
    }).on('select2-selecting', function(event) {
        var styleUrl = '/static/vendor/bower_components/styles/' + event.val + '.csl';
        $.get(styleUrl).done(function(xml) {
            self.updateStyle(event.val, xml);
        }).fail(function(jqxhr, status, error) {
            Raven.captureMessage('Error while selecting citation style: ' + event.val, {
                url: styleUrl,
                status: status,
                error: error
            });
        });
    });
};

CitationGrid.prototype.updateStyle = function(name, xml) {
    this.styleName = name;
    this.styleXml = xml;
    this.bibliographies = {};
    this.treebeard.tbController.redraw();
};

CitationGrid.prototype.makeBibliography = function(folder) {
    var data = objectify(
        folder.children.filter(function(child) {
            return child.kind === 'file';
        }).map(function(child) {
            return child.data.csl;
        })
    );
    var citeproc = citations.makeCiteproc(this.styleXml, data, 'html');
    var bibliography = citeproc.makeBibliography();
    if (bibliography[0].entry_ids) {
        return utils.reduce(
            utils.zip(bibliography[0].entry_ids, bibliography[1]), {},
            function(pair, acc) {
                return acc[pair[0][0]] = pair[1];
            }
        );
    }
    return {};
};

CitationGrid.prototype.getBibliography = function(folder) {
    this.bibliographies[folder.id] = this.bibliographies[folder.id] || this.makeBibliography(folder);
    return this.bibliographies[folder.id];
};

CitationGrid.prototype.getCitation = function(item) {
    var bibliography = this.getBibliography(item.parent());
    return bibliography[item.data.csl.id];
};

CitationGrid.prototype.parseRTF = function(item) {
    if (item.children.length) {
        for (var i = 0; i < item.children.length; i++){
            return self.parseRTF(item.childNodes[i])
        }
    }

    var rtfCitation = item.outerHTML;

    if (rtfCitation.includes('')) {
        rtfCitation = rtfCitation.replace('', '\\ul ')
            .replace('', '\\ulnone ');
    }

    return rtfCitation.replace('', '\\i ').replace('', '\\i0 ')
        .replace('', '\\b ').replace('', '\\b0 ')
        .replace('', '\\ul ').replace('', '\\ulnone ');
};

CitationGrid.prototype.getRTFCitation = function(item) {
    //Change HTML tags in each citation item to .rtf tags, for exporting
    var citation = this.getBibliography(item.parent())[item.data.csl.id];
    var htmlCitation = $.parseHTML(citation)[0];
    var rtfCitation = '';

    for(var i = 0; i < htmlCitation.childNodes.length; i++) {
        rtfCitation += self.parseRTF(htmlCitation.childNodes[i])
    }

    return rtfCitation.replace('\n', '').trim() + '\\';
};

CitationGrid.prototype.getCitations = function(folder, rtf) {
    var self = this;
    return folder.children.filter(function(child) {
        return child.kind === 'file';
    }).map(function(child) {
        if (rtf) {
            return self.getRTFCitation(child);
        } else {
            return self.getCitation(child);
        }
    });
};

CitationGrid.prototype.resolveRowAux = function(item) {
    var self = this;
    return [{
        data: 'csl',
        folderIcons: true,
        custom: function(item) {
            if (item.kind === 'folder'){
                return item.data.name;
            }
            else if (item.kind === 'message'){
                return item.data.name;
            }
            else {
                return m('span', {id: item.data.csl.id}, [
                    m.trust(self.getCitation(item))
                        ]);
            }
        }
    }, {
        // Wrap callback in closure to preserve intended `this`
        custom: function() {
            return renderActions.apply(self, arguments);
        }
    }];
};

module.exports = CitationGrid;
<commit_msg>Fix JShint issues<commit_after>'use strict';

var $ = require('jquery');
var m = require('mithril');
var Raven = require('raven-js');
var Treebeard = require('treebeard');
var citations = require('js/citations');
var clipboard = require('js/clipboard');

var apaStyle = require('raw!styles/apa.csl');

var errorPage = require('raw!citations_load_error.html');

require('css/fangorn.css');

function resolveToggle(item) {
    var toggleMinus = m('i.fa.fa-minus', ' ');
    var togglePlus = m('i.fa.fa-plus', ' ');
    if (item.kind === 'folder') {
        return item.open ? toggleMinus : togglePlus;
    } else {
        return '';
    }
}

function resolveIcon(item) {
    var privateFolder = m('img', {
        src: '/static/img/hgrid/fatcowicons/folder_delete.png'
    });
    var openFolder = m('i.fa fa-folder-open', ' ');
    var closedFolder = m('i.fa fa-folder', ' ');

    if (item.kind === 'folder') {
        return item.open ? openFolder : closedFolder;
    } else if (item.kind === 'message'){
        return '';
    } else if (item.data.icon) {
        return m('i.fa.' + item.data.icon, ' ');
    } else {
        return m('i.fa fa-file-o');
    }
}

var utils = {
    reduce: function(array, dst, fn) {
        for (var i = 0; i < array.length; i++) {
            fn(array[i], dst);
        }
        return dst;
    },
    zip: function(array1, array2) {
        var zipped = [];
        var min = Math.min(array1.length, array2.length);
        for (var i = 0; i < min; i++) {
            zipped.push([array1[i], array2[i]]);
        }
        return zipped;
    }
};

var objectify = function(array, key) {
    key = key || 'id';
    return utils.reduce(
        array, {},
        function(item, acc) {
            return acc[item[key]] = item;
        }
    );
};

var formatResult = function(state) {
    return '
' + state.title + '
';
};

var formatSelection = function(state) {
    return state.title;
};

var mergeConfigs = function() {
    var unloads = [];
    var args = Array.prototype.slice.call(arguments);
    return function(elm, isInit, ctx) {
        for (var i = 0; i < args.length; i++) {
            args[i] && args[i](elm, isInit, ctx);
            ctx.onunload && unloads.push(ctx.onunload);
            ctx.onunload = null;
        }
        ctx.onunload = function() {
            for (var i = 0; i < unloads.length; i++) {
                unloads[i]();
            }
        };
    };
};

var tooltipConfig = function(elm, isInit, ctx) {
    var $elm = $(elm);
    $elm.tooltip({
        container: 'body'
    });
    ctx.onunload = function() {
        $elm.tooltip('destroy');
    };
};

var makeButtons = function(item, col, buttons) {
    return buttons.map(function(button) {
        var self = this;
        return m(
            'a', {
                'data-col': item.id
            }, [
                m(
                    'i', {
                        title: button.tooltip,
                        style: button.style,
                        class: button.css,
                        'data-toggle': 'tooltip',
                        'data-placement': 'bottom',
                        'data-clipboard-target': item.data.csl ? item.data.csl.id : button.clipboard,
                        config: mergeConfigs(button.config, tooltipConfig),
                        onclick: button.onclick ?
                            function(event) {
                                button.onclick.call(self, event, item, col);
                            } : null
                    }, [
                        m(
                            'span', {
                                class: button.icon
                            },
                            button.name
                        )
                    ]
                )
            ]
        );
    });
};

var buildExternalUrl = function(csl) {
    if (csl.URL) {
        return csl.URL;
    } else if (csl.DOI) {
        return 'http://dx.doi.org/' + csl.DOI;
    } else if (csl.PMID) {
        return 'http://www.ncbi.nlm.nih.gov/pubmed/' + csl.PMID;
    }
    return null;
};

var makeClipboardConfig = function(getText) {
    return function(elm, isInit, ctx) {
        var $elm = $(elm);
        if (!elm._client) {
            elm._client = clipboard(elm);
            // Attach `beforecopy` handler to ensure updated clipboard text
            if (getText) {
                elm._client.on('beforecopy', function() {
                    $elm.attr('data-clipboard-text', getText());
                });
            }
        }
        ctx.onunload = function() {
            elm._client && elm._client.destroy();
        };
    };
};

var renderActions = function(item, col) {
    var self = this;
    var buttons = [];
    if (item.kind === 'file') {
        buttons.push({
            name: '',
            icon: 'fa fa-file-o',
            css: 'btn btn-default btn-xs',
            tooltip: 'Copy citation',
            clipboard: self.getCitation(item),
            config: makeClipboardConfig()
        });
        // Add link to external document
        var externalUrl = buildExternalUrl(item.data.csl);
        if (externalUrl) {
            buttons.push({
                name: '',
                icon: 'fa fa-external-link',
                css: 'btn btn-default btn-xs',
                tooltip: 'View original document',
                onclick: function(event) {
                    window.open(externalUrl);
                }
            });
        }
        // Add link to document on reference management service
        if (item.data.serviceUrl) {
            buttons.push({
                name: '',
                icon: 'fa fa-link',
                css: 'btn btn-default btn-xs',
                tooltip: 'View on ' + self.provider,
                onclick: function(event) {
                    window.open(item.data.serviceUrl);
                }
            });
        }
    } else if (item.kind === 'folder' && item.open && item.children.length) {
        buttons.push({
            name: '',
            icon: 'fa fa-file-o',
            css: 'btn btn-default btn-xs',
            tooltip: 'Copy citations',
            config: makeClipboardConfig(function() {
                return self.getCitations(item).join('\n');
            })
        });
        buttons.push({
            name: '',
            icon: 'fa fa-arrow-circle-o-down',
            css: 'btn btn-default btn-xs',
            tooltip: 'Download citations',
            config: function(elm, isInit, ctx) {
                var text = '{\\rtf1\\ansi\n' + self.getCitations(item, true).join('\n') + '\n}';
                $(elm).parent('a')
                    .attr('href', 'data:text/enriched;charset=utf-8,' + encodeURIComponent(text))
                    .attr('download', item.data.name + '-' + self.styleName + '.rtf');
            }
        });
    }
    return makeButtons(item, col, buttons);
};

var treebeardOptions = {
    rowHeight: 30,
    lazyLoad: true,
    showFilter: false,
    resolveIcon: resolveIcon,
    resolveToggle: resolveToggle,
    lazyLoadPreprocess: function(res) {
        return res.contents;
    },
    columnTitles: function() {
        return [{
            title: 'Citation',
            width: '80%',
            sort: false
        }, {
            title: 'Actions',
            width: '20%',
            sort: false
        }];
    }
};

var CitationGrid = function(provider, gridSelector, styleSelector, apiUrl) {
    var self = this;

    self.provider = provider;
    self.gridSelector = gridSelector;
    self.styleSelector = styleSelector;
    self.apiUrl = apiUrl;

    self.styleName = 'apa';
    self.styleXml = apaStyle;
    self.bibliographies = {};

    self.initTreebeard();
    self.initStyleSelect();
};

CitationGrid.prototype.initTreebeard = function() {
    var self = this;
    var options = $.extend({
            divID: self.gridSelector.replace('#', ''),
            filesData: self.apiUrl,
            resolveLazyloadUrl: function(item) {
                return item.data.urls.fetch;
            },
            // Wrap callback in closure to preserve intended `this`
            resolveRows: function() {
                return self.resolveRowAux.call(self, arguments);
            },
            ondataloaderror: function(err) {
                $(self.gridSelector).html(errorPage);
            }
        },
        treebeardOptions
    );
    var preprocess = options.lazyLoadPreprocess;
    options.lazyLoadPreprocess = function(data){
        data = preprocess(data);
        // TODO remove special case for Zotero
        if (self.provider === 'Zotero') {
            if (data.length >= 200) {
        data.push({
                    name: 'Only 200 citations may be displayed',
                    kind: 'message'
                });
            }
        }
        return data;
    };
    self.treebeard = new Treebeard(options);
};

CitationGrid.prototype.initStyleSelect = function() {
    var self = this;
    var $input = $(self.styleSelector);
    $input.select2({
        allowClear: false,
        formatResult: formatResult,
        formatSelection: formatSelection,
        placeholder: 'Citation Style (e.g. "APA")',
        minimumInputLength: 1,
        ajax: {
            url: '/api/v1/citations/styles/',
            quietMillis: 200,
            data: function(term, page) {
                return {
                    q: term
                };
            },
            results: function(data, page) {
                return {
                    results: data.styles
                };
            },
            cache: true
        }
    }).on('select2-selecting', function(event) {
        var styleUrl = '/static/vendor/bower_components/styles/' + event.val + '.csl';
        $.get(styleUrl).done(function(xml) {
            self.updateStyle(event.val, xml);
        }).fail(function(jqxhr, status, error) {
            Raven.captureMessage('Error while selecting citation style: ' + event.val, {
                url: styleUrl,
                status: status,
                error: error
            });
        });
    });
};

CitationGrid.prototype.updateStyle = function(name, xml) {
    this.styleName = name;
    this.styleXml = xml;
    this.bibliographies = {};
    this.treebeard.tbController.redraw();
};

CitationGrid.prototype.makeBibliography = function(folder) {
    var data = objectify(
        folder.children.filter(function(child) {
            return child.kind === 'file';
        }).map(function(child) {
            return child.data.csl;
        })
    );
    var citeproc = citations.makeCiteproc(this.styleXml, data, 'html');
    var bibliography = citeproc.makeBibliography();
    if (bibliography[0].entry_ids) {
        return utils.reduce(
            utils.zip(bibliography[0].entry_ids, bibliography[1]), {},
            function(pair, acc) {
                return acc[pair[0][0]] = pair[1];
            }
        );
    }
    return {};
};

CitationGrid.prototype.getBibliography = function(folder) {
    this.bibliographies[folder.id] = this.bibliographies[folder.id] || this.makeBibliography(folder);
    return this.bibliographies[folder.id];
};

CitationGrid.prototype.getCitation = function(item) {
    var bibliography = this.getBibliography(item.parent());
    return bibliography[item.data.csl.id];
};

CitationGrid.prototype.parseRTF = function(item) {
    var self = this;
    if (item.children.length) {
        for (var i = 0; i < item.children.length; i++){
            return self.parseRTF(item.childNodes[i]);
        }
    }

    var rtfCitation = item.outerHTML;

    if (rtfCitation.includes('')) {
        rtfCitation = rtfCitation.replace('', '\\ul ')
            .replace('', '\\ulnone ');
    }

    return rtfCitation.replace('', '\\i ').replace('', '\\i0 ')
        .replace('', '\\b ').replace('', '\\b0 ')
        .replace('', '\\ul ').replace('', '\\ulnone ');
};

CitationGrid.prototype.getRTFCitation = function(item) {
    //Change HTML tags in each citation item to .rtf tags, for exporting
    var self = this;
    var citation = this.getBibliography(item.parent())[item.data.csl.id];
    var htmlCitation = $.parseHTML(citation)[0];
    var rtfCitation = '';

    for(var i = 0; i < htmlCitation.childNodes.length; i++) {
        rtfCitation += self.parseRTF(htmlCitation.childNodes[i]);
    }

    return rtfCitation.replace('\n', '').trim() + '\\';
};

CitationGrid.prototype.getCitations = function(folder, rtf) {
    var self = this;
    return folder.children.filter(function(child) {
        return child.kind === 'file';
    }).map(function(child) {
        if (rtf) {
            return self.getRTFCitation(child);
        } else {
            return self.getCitation(child);
        }
    });
};

CitationGrid.prototype.resolveRowAux = function(item) {
    var self = this;
    return [{
        data: 'csl',
        folderIcons: true,
        custom: function(item) {
            if (item.kind === 'folder'){
                return item.data.name;
            }
            else if (item.kind === 'message'){
                return item.data.name;
            }
            else {
                return m('span', {id: item.data.csl.id}, [
                    m.trust(self.getCitation(item))
                        ]);
            }
        }
    }, {
        // Wrap callback in closure to preserve intended `this`
        custom: function() {
            return renderActions.apply(self, arguments);
        }
    }];
};

module.exports = CitationGrid;
<|endoftext|>


<commit_before>window.AudioContext = window.AudioContext || window.webkitAudioContext || window.mozAudioContext || window.msAudioContext || window.oAudioContext;

/**
 * @scope enchant.WebAudioSound.prototype
 */
enchant.WebAudioSound = enchant.Class.create(enchant.EventTarget, {
    /**
     * @name enchant.WebAudioSound
     * @class
     [lang:ja]
     * WebAudioをラップしたクラス.
     [/lang]
     [lang:en]
     * Sound wrapper class for Web Audio API (supported on some webkit-based browsers)
     [/lang]
     [lang:de]
     [/lang]
     * @constructs
     * @extends enchant.EventTarget
     */
    initialize: function() {
        if (!window.AudioContext) {
            throw new Error("This browser does not support WebAudio API.");
        }
        enchant.EventTarget.call(this);
        this.context = enchant.WebAudioSound.audioContext;
        this.src = this.context.createBufferSource();
        this.buffer = null;
        this._volume = 1;
        this._currentTime = 0;
        this._state = 0;
        this.connectTarget = enchant.WebAudioSound.destination;
    },
    /**
     [lang:ja]
     * 再生を開始する.
     * @param {Boolean} [dup=false] trueならオブジェクトの現在の再生を残したまま新しく音声を再生する.
     [/lang]
     [lang:en]
     * Begin playing.
     * @param {Boolean} [dup=false] If true, Object plays new sound while keeps last sound.
     [/lang]
     [lang:de]
     * Startet die Wiedergabe.
     * @param {Boolean} [dup=false] Wenn true, Objekt spielt neuen Sound während im letzten Ton hält.
     [/lang]
     */
    play: function(dup) {
        if (this._state === 1 && !dup) {
            this.src.disconnect(this.connectTarget);
        }
        if (this._state !== 2) {
            this._currentTime = 0;
        }
        var offset = this._currentTime;
        var actx = this.context;
        this.src = actx.createBufferSource();
        if (actx.createGain != null) {
            this._gain = actx.createGain();
        } else {
            this._gain = actx.createGainNode();
        }
        this.src.buffer = this.buffer;
        this._gain.gain.value = this._volume;

        this.src.connect(this._gain);
        this._gain.connect(this.connectTarget);
        if (this.src.start != null) {
            this.src.start(0, offset, this.buffer.duration - offset - 1.192e-7);
        } else {
            this.src.noteGrainOn(0, offset, this.buffer.duration - offset - 1.192e-7);
        }
        this._startTime = actx.currentTime - this._currentTime;
        this._state = 1;
    },
    /**
     [lang:ja]
     * 再生を中断する.
     [/lang]
     [lang:en]
     * Pause playback.
     [/lang]
     [lang:de]
     * Pausiert die Wiedergabe.
     [/lang]
     */
    pause: function() {
        var currentTime = this.currentTime;
        if (currentTime === this.duration) {
            return;
        }
        if (this.src.stop != null) {
            this.src.stop(0);
        } else {
            this.src.noteOff(0);
        }
        this._currentTime = currentTime;
        this._state = 2;
    },
    /**
     [lang:ja]
     * 再生を停止する.
     [/lang]
     [lang:en]
     * Stop playing.
     [/lang]
     [lang:de]
     * Stoppt die Wiedergabe.
     [/lang]
     */
    stop: function() {
        if (this.src.stop != null) {
            this.src.stop(0);
        } else {
            this.src.noteOff(0);
        }
        this._state = 0;
    },
    /**
     [lang:ja]
     * Soundを複製する.
     * @return {enchant.WebAudioSound} 複製されたSound.
     [/lang]
     [lang:en]
     * Create a copy of this Sound object.
     * @return {enchant.WebAudioSound} Copied Sound.
     [/lang]
     [lang:de]
     * Erstellt eine Kopie dieses Soundobjektes.
     * @return {enchant.WebAudioSound} Kopiertes Sound Objekt.
     [/lang]
     */
    clone: function() {
        var sound = new enchant.WebAudioSound();
        sound.buffer = this.buffer;
        return sound;
    },
    /**
     [lang:ja]
     * Soundの再生時間 (秒).
     [/lang]
     [lang:en]
     * Sound file duration (seconds).
     [/lang]
     [lang:de]
     * Die länge der Sounddatei in Sekunden.
     [/lang]
     * @type Number
     */
    duration: {
        get: function() {
            if (this.buffer) {
                return this.buffer.duration;
            } else {
                return 0;
            }
        }
    },
    /**
     [lang:ja]
     * 現在の再生位置 (秒).
     [/lang]
     [lang:en]
     * Current playback position (seconds).
     [/lang]
     [lang:de]
     * Aktuelle Wiedergabeposition (seconds).
     [/lang]
     * @type Number
     */
    volume: {
        get: function() {
            return this._volume;
        },
        set: function(volume) {
            volume = Math.max(0, Math.min(1, volume));
            this._volume = volume;
            if (this.src) {
                this._gain.gain.value = volume;
            }
        }
    },
    /**
     [lang:ja]
     * ボリューム. 0 (無音) ～ 1 (フルボリューム).
     [/lang]
     [lang:en]
     * Volume. 0 (muted) ～ 1 (full volume).
     [/lang]
     [lang:de]
     * Lautstärke. 0 (stumm) ～ 1 (volle Lautstärke).
     [/lang]
     * @type Number
     */
    currentTime: {
        get: function() {
            return Math.max(0, Math.min(this.duration, this.src.context.currentTime - this._startTime));
        },
        set: function(time) {
            this._currentTime = time;
            if (this._state !== 2) {
                this.play(false);
            }
        }
    }
});

/**
 [lang:ja]
 * 音声ファイルを読み込んでWebAudioSoundオブジェクトを作成する.
 * @param {String} src ロードする音声ファイルのパス.
 * @param {String} [type] 音声ファイルのMIME Type.
 * @param {Function} [callback] ロード完了時のコールバック.
 * @param {Function} [onerror] ロード失敗時のコールバック.
 [/lang]
 [lang:en]
 * Loads an audio file and creates WebAudioSound object.
 * @param {String} src Path of the audio file to be loaded.
 * @param {String} [type] MIME Type of the audio file.
 * @param {Function} [callback] on load callback.
 * @param {Function} [onerror] on error callback.
 [/lang]
 [lang:de]
 * Läd eine Audio Datei und erstellt ein WebAudioSound objekt.
 * @param {String} src Pfad zu der zu ladenden Audiodatei.
 * @param {String} [type] MIME Type der Audtiodatei.
 * @param {Function} [callback]
 * @param {Function} [onerror]
 [/lang]
 * @return {enchant.WebAudioSound} WebAudioSound
 * @static
 */
enchant.WebAudioSound.load = function(src, type, callback, onerror) {
    var canPlay = (new Audio()).canPlayType(type);
    var sound = new enchant.WebAudioSound();
    callback = callback || function() {};
    onerror = onerror || function() {};
    sound.addEventListener(enchant.Event.LOAD, callback);
    sound.addEventListener(enchant.Event.ERROR, onerror);
    function dispatchErrorEvent() {
        var e = new enchant.Event(enchant.Event.ERROR);
        e.message = 'Cannot load an asset: ' + src;
        enchant.Core.instance.dispatchEvent(e);
        sound.dispatchEvent(e);
    }
    var actx, xhr;
    if (canPlay === 'maybe' || canPlay === 'probably') {
        actx = enchant.WebAudioSound.audioContext;
        xhr = new XMLHttpRequest();
        xhr.open('GET', src, true);
        xhr.responseType = 'arraybuffer';
        xhr.onload = function() {
            actx.decodeAudioData(xhr.response, function(buffer) {
                sound.buffer = buffer;
                sound.dispatchEvent(new enchant.Event(enchant.Event.LOAD));
            }, dispatchErrorEvent);
        };
        xhr.onerror = dispatchErrorEvent;
        xhr.send(null);
    } else {
        setTimeout(dispatchErrorEvent,  50);
    }
    return sound;
};

if (window.AudioContext) {
    enchant.WebAudioSound.audioContext = new window.AudioContext();
    enchant.WebAudioSound.destination = enchant.WebAudioSound.audioContext.destination;
}
<commit_msg>only create audio context when sound is used for the first time<commit_after>window.AudioContext = window.AudioContext || window.webkitAudioContext || window.mozAudioContext || window.msAudioContext || window.oAudioContext;

/**
 * @scope enchant.WebAudioSound.prototype
 */
enchant.WebAudioSound = enchant.Class.create(enchant.EventTarget, {
    /**
     * @name enchant.WebAudioSound
     * @class
     [lang:ja]
     * WebAudioをラップしたクラス.
     [/lang]
     [lang:en]
     * Sound wrapper class for Web Audio API (supported on some webkit-based browsers)
     [/lang]
     [lang:de]
     [/lang]
     * @constructs
     * @extends enchant.EventTarget
     */
    initialize: function() {
        if (!window.AudioContext) {
            throw new Error("This browser does not support WebAudio API.");
        }
        enchant.EventTarget.call(this);
        if (!enchant.WebAudioSound.audioContext) {
          enchant.WebAudioSound.audioContext = new window.AudioContext();
          enchant.WebAudioSound.destination = enchant.WebAudioSound.audioContext.destination;
        }
        this.context = enchant.WebAudioSound.audioContext;
        this.src = this.context.createBufferSource();
        this.buffer = null;
        this._volume = 1;
        this._currentTime = 0;
        this._state = 0;
        this.connectTarget = enchant.WebAudioSound.destination;
    },
    /**
     [lang:ja]
     * 再生を開始する.
     * @param {Boolean} [dup=false] trueならオブジェクトの現在の再生を残したまま新しく音声を再生する.
     [/lang]
     [lang:en]
     * Begin playing.
     * @param {Boolean} [dup=false] If true, Object plays new sound while keeps last sound.
     [/lang]
     [lang:de]
     * Startet die Wiedergabe.
     * @param {Boolean} [dup=false] Wenn true, Objekt spielt neuen Sound während im letzten Ton hält.
     [/lang]
     */
    play: function(dup) {
        if (this._state === 1 && !dup) {
            this.src.disconnect(this.connectTarget);
        }
        if (this._state !== 2) {
            this._currentTime = 0;
        }
        var offset = this._currentTime;
        var actx = this.context;
        this.src = actx.createBufferSource();
        if (actx.createGain != null) {
            this._gain = actx.createGain();
        } else {
            this._gain = actx.createGainNode();
        }
        this.src.buffer = this.buffer;
        this._gain.gain.value = this._volume;

        this.src.connect(this._gain);
        this._gain.connect(this.connectTarget);
        if (this.src.start != null) {
            this.src.start(0, offset, this.buffer.duration - offset - 1.192e-7);
        } else {
            this.src.noteGrainOn(0, offset, this.buffer.duration - offset - 1.192e-7);
        }
        this._startTime = actx.currentTime - this._currentTime;
        this._state = 1;
    },
    /**
     [lang:ja]
     * 再生を中断する.
     [/lang]
     [lang:en]
     * Pause playback.
     [/lang]
     [lang:de]
     * Pausiert die Wiedergabe.
     [/lang]
     */
    pause: function() {
        var currentTime = this.currentTime;
        if (currentTime === this.duration) {
            return;
        }
        if (this.src.stop != null) {
            this.src.stop(0);
        } else {
            this.src.noteOff(0);
        }
        this._currentTime = currentTime;
        this._state = 2;
    },
    /**
     [lang:ja]
     * 再生を停止する.
     [/lang]
     [lang:en]
     * Stop playing.
     [/lang]
     [lang:de]
     * Stoppt die Wiedergabe.
     [/lang]
     */
    stop: function() {
        if (this.src.stop != null) {
            this.src.stop(0);
        } else {
            this.src.noteOff(0);
        }
        this._state = 0;
    },
    /**
     [lang:ja]
     * Soundを複製する.
     * @return {enchant.WebAudioSound} 複製されたSound.
     [/lang]
     [lang:en]
     * Create a copy of this Sound object.
     * @return {enchant.WebAudioSound} Copied Sound.
     [/lang]
     [lang:de]
     * Erstellt eine Kopie dieses Soundobjektes.
     * @return {enchant.WebAudioSound} Kopiertes Sound Objekt.
     [/lang]
     */
    clone: function() {
        var sound = new enchant.WebAudioSound();
        sound.buffer = this.buffer;
        return sound;
    },
    /**
     [lang:ja]
     * Soundの再生時間 (秒).
     [/lang]
     [lang:en]
     * Sound file duration (seconds).
     [/lang]
     [lang:de]
     * Die länge der Sounddatei in Sekunden.
     [/lang]
     * @type Number
     */
    duration: {
        get: function() {
            if (this.buffer) {
                return this.buffer.duration;
            } else {
                return 0;
            }
        }
    },
    /**
     [lang:ja]
     * 現在の再生位置 (秒).
     [/lang]
     [lang:en]
     * Current playback position (seconds).
     [/lang]
     [lang:de]
     * Aktuelle Wiedergabeposition (seconds).
     [/lang]
     * @type Number
     */
    volume: {
        get: function() {
            return this._volume;
        },
        set: function(volume) {
            volume = Math.max(0, Math.min(1, volume));
            this._volume = volume;
            if (this.src) {
                this._gain.gain.value = volume;
            }
        }
    },
    /**
     [lang:ja]
     * ボリューム. 0 (無音) ～ 1 (フルボリューム).
     [/lang]
     [lang:en]
     * Volume. 0 (muted) ～ 1 (full volume).
     [/lang]
     [lang:de]
     * Lautstärke. 0 (stumm) ～ 1 (volle Lautstärke).
     [/lang]
     * @type Number
     */
    currentTime: {
        get: function() {
            return Math.max(0, Math.min(this.duration, this.src.context.currentTime - this._startTime));
        },
        set: function(time) {
            this._currentTime = time;
            if (this._state !== 2) {
                this.play(false);
            }
        }
    }
});

/**
 [lang:ja]
 * 音声ファイルを読み込んでWebAudioSoundオブジェクトを作成する.
 * @param {String} src ロードする音声ファイルのパス.
 * @param {String} [type] 音声ファイルのMIME Type.
 * @param {Function} [callback] ロード完了時のコールバック.
 * @param {Function} [onerror] ロード失敗時のコールバック.
 [/lang]
 [lang:en]
 * Loads an audio file and creates WebAudioSound object.
 * @param {String} src Path of the audio file to be loaded.
 * @param {String} [type] MIME Type of the audio file.
 * @param {Function} [callback] on load callback.
 * @param {Function} [onerror] on error callback.
 [/lang]
 [lang:de]
 * Läd eine Audio Datei und erstellt ein WebAudioSound objekt.
 * @param {String} src Pfad zu der zu ladenden Audiodatei.
 * @param {String} [type] MIME Type der Audtiodatei.
 * @param {Function} [callback]
 * @param {Function} [onerror]
 [/lang]
 * @return {enchant.WebAudioSound} WebAudioSound
 * @static
 */
enchant.WebAudioSound.load = function(src, type, callback, onerror) {
    var canPlay = (new Audio()).canPlayType(type);
    var sound = new enchant.WebAudioSound();
    callback = callback || function() {};
    onerror = onerror || function() {};
    sound.addEventListener(enchant.Event.LOAD, callback);
    sound.addEventListener(enchant.Event.ERROR, onerror);
    function dispatchErrorEvent() {
        var e = new enchant.Event(enchant.Event.ERROR);
        e.message = 'Cannot load an asset: ' + src;
        enchant.Core.instance.dispatchEvent(e);
        sound.dispatchEvent(e);
    }
    var actx, xhr;
    if (canPlay === 'maybe' || canPlay === 'probably') {
        actx = enchant.WebAudioSound.audioContext;
        xhr = new XMLHttpRequest();
        xhr.open('GET', src, true);
        xhr.responseType = 'arraybuffer';
        xhr.onload = function() {
            actx.decodeAudioData(xhr.response, function(buffer) {
                sound.buffer = buffer;
                sound.dispatchEvent(new enchant.Event(enchant.Event.LOAD));
            }, dispatchErrorEvent);
        };
        xhr.onerror = dispatchErrorEvent;
        xhr.send(null);
    } else {
        setTimeout(dispatchErrorEvent,  50);
    }
    return sound;
};
<|endoftext|>


<commit_before>var http = require('http');
var server = http.createServer().listen(8005);
var io = require('socket.io')(server);
var cookie = require('cookie');
var querystring = require('querystring');

io.use(function (socket, next) {
  if (socket.request.headers.cookie) {
    socket.request.cookie = cookie.parse(socket.request.headers.cookie);
    return next();
  }
  return next(new Error('not authorized'));
});

io.on('connection', function (socket) {
  var id = socket.request.cookie.sessionid.slice(-8);

  console.log('client connected', id);

  socket.on('client', function (data) {
    console.log('received from', id, ':', data);
    socket.broadcast.emit('server', data);
  });

  socket.on('disconnect', function (data) {
    console.log('client disconnected');
  });
});
<commit_msg>Remove debug output from socket.io server<commit_after>var http = require('http');
var server = http.createServer().listen(8005);
var io = require('socket.io')(server);
var cookie = require('cookie');
var querystring = require('querystring');

io.use(function (socket, next) {
  if (socket.request.headers.cookie) {
    socket.request.cookie = cookie.parse(socket.request.headers.cookie);
    return next();
  }
  return next(new Error('not authorized'));
});

io.on('connection', function (socket) {
  socket.on('client', function (data) {
    socket.broadcast.emit('server', data);
  });
});
<|endoftext|>


<commit_before>/* exported RectCache */
/* Copyright 2015, Yahoo Inc.
   Designed by Irae Carvalho
   Copyrights licensed under the MIT License.
   See the accompanying LICENSE file for terms. */
"use strict";

var initialRect = { left : 0, right : 0, top : 0, height : 0, bottom : 0, width : 0 };

var RectCache = {
  rect: initialRect,
  _updateRectCache: function() {
    if(!this.isMounted()) {
      return; // Edge case, this should not happen, maybe react bug?
    }
    var oldRect = this.rect;
    var newRect = this.getDOMNode().getBoundingClientRect();
    this.rect = newRect;

    // Interesting fact: Checking for changes is not only better for performance. Since
    // a consumer of onResize might update state variables, preventing unnecessary events also
    // avoids weird side-effects.
    // This is specially true for nested rect-cached elements inside overflow: hidden; elements
    // where the outermost rect-cache is not updated while the inner most might be.
    if (oldRect.width !== newRect.width || oldRect.height !== newRect.height) {
      this.props.onResize && this.props.onResize();
      this.onResize && this.onResize();
    }
  },

  _bindImgLoad: function(event) {
    watchLoadImages(event.target, this._updateRectCache);
  },

  componentDidMount: function(){
    var node = this.getDOMNode();
    var update = this._updateRectCache;
    update();
    watchLoadImages(node, update);
    node.addEventListener('DOMSubtreeModified', update);
    node.addEventListener('DOMNodeInserted', this._bindImgLoad);
    if (this.props.hasOwnProperty('viewport')) {
      window.addEventListener('orientationchange', update);
      window.addEventListener("resize", update);
    }
  },

  componentWillUnmount: function(){
    var node = this.getDOMNode();
    var update = this._updateRectCache;
    node.removeEventListener('DOMSubtreeModified', update);
    node.removeEventListener('DOMNodeInserted', this._bindImgLoad);
    if (this.props.hasOwnProperty('viewport')) {
      window.removeEventListener('orientationchange', update);
      window.removeEventListener("resize", update);
    }
  },
};

function watchLoadImages(node, callback) {
  var imgArr = getAllImagesInTree(node);
  for (var i = 0; i < imgArr.length; i++) {
    var img = imgArr[i];
    img.addEventListener('load', callback);
  }
}

function getAllImagesInTree(node) {
  if (node && node.nodeName.toLowerCase() === 'img') {
    return [node];
  } else {
    return (node.getElementsByTagName && node.getElementsByTagName('img')) || [];
  }
}

/* istanbul ignore if */
if (typeof window === 'undefined'){
  module.exports = {
    rect: initialRect,
  };
} else {
  module.exports = RectCache;
}
<commit_msg>Add some documentation to RectCache<commit_after>/* exported RectCache */
/* Copyright 2015, Yahoo Inc.
   Designed by Irae Carvalho
   Copyrights licensed under the MIT License.
   See the accompanying LICENSE file for terms. */
"use strict";

/*

This mixin will add a `.rect` property to the consumer element and will do a "best effort"
to keep it updated, with the caveats stated below.

API:
----

`.rect` (Object): This property will have the same object signature as
DOMElement.getBoundingClientRect() or will be a direct instance returned by
the native method.

`.onResize` (optional hook event): The consumer might implement this method to get notified
of changes to `.rect` changes.

`onResize` (callback prop): Owners of component instances that implement RectCache can hook
into size changes to get notifications.

`viewport` (property): Owners can initialize component instances that implement RectCache
that will also update when window resizes or orientation changes.

Silly example, but with all API being used:
-------------------------------------------

    var HaveRectCache = React.createClass({
      mixins: [RectCache],
      render: function () {
        return (

);
      },
      theClick: function() {
        alert(this.rect.height);
      },
    });

    var Comp = React.createClass({
      render: function () {
        return (

          <HaveRectCache viewport ref="theElement" onResize={this.whenResized}>

);
      },
      whenResized: function() {
        // also called if window resizes or orientation change
        alert('Comp knows theElement height changed to ' + this.refs.theElement.rect.height);
      },
    });

How it works and quirks:
------------------------

The `.rect` object will have width and hight consistent at all times, but other properties
might get outdated if elements are dynamically re-positioned by application logic.

After initialization, the mixin will bind to DOM events to do a best effort into a watching
for element size changes. Current browser APIs won't allow for perfect resize detection on
a DOM node level, besides hacky solutions that add extra DOM and watch for scroll events. One
famous library that uses this technique is
[CSS Element Queries](https://github.com/marcj/css-element-queries). Instead, RectCache will
poll for `.getBoundingClientRect()` on reasonable events like "DOMSubtreeModified/Inserted"
and image onLoad events.

Because some edge cases might still happen, specially when window resizes or viewport
orientation changes (on mobile devices), the viewport property will help the relevant elements
to keep updated.

The reason this algorithm is used instead of the more reliable hacky solutions mentioned above,
is because using React is already adding a lot of DOM predictability to changes, and this
library only aims to "close the gap", when doing something with DOM outside of React code.

*/

var initialRect = { left : 0, right : 0, top : 0, height : 0, bottom : 0, width : 0 };

var RectCache = {
  rect: initialRect,
  _updateRectCache: function() {
    if(!this.isMounted()) {
      return; // Edge case, this should not happen, maybe react bug?
    }
    var oldRect = this.rect;
    var newRect = this.getDOMNode().getBoundingClientRect();
    this.rect = newRect;

    // Interesting fact: Checking for changes is not only better for performance. Since
    // a consumer of onResize might update state variables, preventing unnecessary events also
    // avoids weird side-effects.
    // This is specially true for nested rect-cached elements inside overflow: hidden; elements
    // where the outermost rect-cache is not updated while the inner most might be.
    if (oldRect.width !== newRect.width || oldRect.height !== newRect.height) {
      this.props.onResize && this.props.onResize();
      this.onResize && this.onResize();
    }
  },

  _bindImgLoad: function(event) {
    watchLoadImages(event.target, this._updateRectCache);
  },

  componentDidMount: function(){
    var node = this.getDOMNode();
    var update = this._updateRectCache;
    update();
    watchLoadImages(node, update);
    node.addEventListener('DOMSubtreeModified', update);
    node.addEventListener('DOMNodeInserted', this._bindImgLoad);
    if (this.props.hasOwnProperty('viewport')) {
      window.addEventListener('orientationchange', update);
      window.addEventListener("resize", update);
    }
  },

  componentWillUnmount: function(){
    var node = this.getDOMNode();
    var update = this._updateRectCache;
    node.removeEventListener('DOMSubtreeModified', update);
    node.removeEventListener('DOMNodeInserted', this._bindImgLoad);
    if (this.props.hasOwnProperty('viewport')) {
      window.removeEventListener('orientationchange', update);
      window.removeEventListener("resize", update);
    }
  },
};

function watchLoadImages(node, callback) {
  var imgArr = getAllImagesInTree(node);
  for (var i = 0; i < imgArr.length; i++) {
    var img = imgArr[i];
    img.addEventListener('load', callback);
  }
}

function getAllImagesInTree(node) {
  if (node && node.nodeName.toLowerCase() === 'img') {
    return [node];
  } else {
    return (node.getElementsByTagName && node.getElementsByTagName('img')) || [];
  }
}

/* istanbul ignore if */
if (typeof window === 'undefined'){
  module.exports = {
    rect: initialRect,
  };
} else {
  module.exports = RectCache;
}
<|endoftext|>


<commit_before>function format(roosterdata){
	function insert(cssclass, data){
		if(data != "" && data != undefined){
			$(div).append("" + data + "
")
		}
	}

	for( var i = 0; i < roosterdata.length; ++i ){

		var startdate = new Date(roosterdata[i]["start"]*1000)
		var starttime = startdate.getHours() + startdate.getMinutes()/60
		var enddate = new Date(roosterdata[i]["end"]*1000)
		var endtime = enddate.getHours() + enddate.getMinutes()/60
		var day = startdate.getDay()
		var size = endtime - starttime

		var desc = {}

		desc.vak = roosterdata[i]["subjects"].join(", ")

		desc.klas = roosterdata[i]["groups"].join(", ")

		desc.lokaal = roosterdata[i]["locations"].join(", ")

		desc.opmerking = roosterdata[i].remark

		desc.type = roosterdata[i].type

		desc.docenten = ""
		for( var j = 0; j < roosterdata[i]["teachers"].length; j++ ){
			if( j != 0 ){ desc.docenten += ", " };
			desc.docenten += ""
			desc.docenten += roosterdata[i]["teachers"][j]
			desc.docenten += ""
		}

		var div = $("
").appendTo("main .rooster")

		$(div).addClass("lesson")

		$(div).css("top", (starttime-8.5)*70)
		$(div).css("left", (day-1)*150+50)
		$(div).css("height", size*70)

		insert("les-vak", desc.vak)
		insert("les-lokaal", desc.lokaal)
		insert("les-docent", desc.docenten)
		insert("les-opmerking", desc.opmerking)
		insert("les-klas", desc.klas)
		insert("les-tijd", startdate.getHours() + ":" + startdate.getMinutes())
		insert("les-eindtijd", enddate.getHours() + ":" + enddate.getMinutes())

		if(desc.type == "exam"){
			$(div).addClass("toets")
		}
	}
}
<commit_msg>Update to 1.6.1<commit_after>function format(roosterdata){
	function insert(cssclass, data){
		if(data != "" && data != undefined){
			$(div).append("" + data + "
")
		}
	}

	for( var i = 0; i < roosterdata.length; ++i ){

		var startdate = new Date(roosterdata[i]["start"]*1000)
		var starttime = startdate.getHours() + startdate.getMinutes()/60
		var enddate = new Date(roosterdata[i]["end"]*1000)
		var endtime = enddate.getHours() + enddate.getMinutes()/60
		var day = startdate.getDay()
		var size = endtime - starttime

		var desc = {}

		desc.vak = roosterdata[i]["subjects"].join(", ")

		desc.klas = roosterdata[i]["groups"].join(", ")

		desc.lokaal = roosterdata[i]["locations"].join(", ")

		desc.opmerking = roosterdata[i].remark

		desc.type = roosterdata[i].type

		desc.docenten = ""
		for( var j = 0; j < roosterdata[i]["teachers"].length; j++ ){
			if( j != 0 ){ desc.docenten += ", " };
			desc.docenten += ""
			desc.docenten += roosterdata[i]["teachers"][j]
			desc.docenten += ""
		}

		var div = $("
").appendTo("main .rooster")

		$(div).addClass("lesson")

		$(div).css("top", (starttime-8.5)*70)
		$(div).css("left", (day-1)*150+50)
		$(div).css("height", size*70)

		insert("les-vak", desc.vak)
		insert("les-lokaal", desc.lokaal)
		insert("les-docent", desc.docenten)
		insert("les-opmerking", desc.opmerking)
		insert("les-klas", desc.klas)
		insert("les-tijd", startdate.getHours() + ":" + startdate.getMinutes())
		insert("les-eindtijd", enddate.getHours() + ":" + enddate.getMinutes())

		if(desc.type == "exam"){
			$(div).addClass("toets")
		}

		var top = $(div).css("top")
		var left = $(div).css("left")
		var height = $(div).css("height")
		$("main .rooster").append("

")
	}
}
<|endoftext|>