<commit_before>module.exports = {
  committers: [
    {
      name: 'Ali Ahmed',
      apacheId: 'aahmed',
      roles: 'Committer'
    },
    {
      name: 'Boyang Jerry Peng',
      apacheId: 'jerrypeng',
      org: 'Streamlio',
      roles: 'Committer, PMC'
    },
    {
      name: 'Brad McMillen',
      apacheId: 'bradtm',
      org: 'Yahoo',
      roles: 'Committer, PMC',
    },
    {
      name: 'Daniel Blankensteiner',
      apacheId: 'blankensteiner',
      roles: 'Committer'
    },
    {
      name: 'David Fisher',
      apacheId: 'wave',
      roles: 'Committer, PMC'
    },
    {
      name: 'Fangbin Sun',
      apacheId: 'sunfangbin',
      roles: 'Committer'
    },
    {
      name: 'Francis Christopher Liu',
      apacheId: 'toffer',
      roles: 'Committer, PMC'
    },
    {
      name: 'Guangning E',
      apacheId: 'guangning',
      roles: 'Committer'
    },
    {
      name: 'Hang Chen',
      apacheId: 'chenhang',
      org: 'BIGO Technology',
      roles: 'Committer'
    },
    {
      name: 'Hiroyuki Sakai',
      apacheId: 'hrsakai',
      org: 'Yahoo Japan Corporation',
      roles: 'Committer, PMC'
    },
    {
      name: 'Huanli Meng',
      apacheId: 'huanlimeng',
      org: 'StreamNative',
      roles: 'Committer'
    },
    {
      name: 'Ivan Brendan Kelly',
      apacheId: 'ivank',
      org: 'Streamlio',
      roles: 'Committer, PMC'
    },
    {
      name: 'Jai Asher',
      apacheId: 'jai1',
      org: 'Yahoo',
      roles: 'Committer, PMC'
    },
    {
      name: 'Jennifer Huang',
      apacheId: 'hjf',
      org: 'StreamNative',
      roles: 'Committer, PMC'
    },
    {
      name: 'Jia Zhai',
      apacheId: 'zhaijia',
      org: 'StreamNative',
      roles: 'Committer, PMC'
    },
    {
      name: 'Jim Jagielski',
      apacheId: 'jim',
      roles: 'Committer, PMC'
    },
    {
      name: 'Joe Francis',
      apacheId: 'joef',
      org: 'Yahoo',
      roles: 'Committer, PMC'
    },
    {
      name: 'Lin Lin',
      apacheId: 'linlin',
      org: 'Tencent',
      roles: 'Committer'
    },
    {
      name: 'Ludwig Pummer',
      apacheId: 'ludwigp',
      org: 'Yahoo',
      roles: 'Committer, PMC'
    },
    {
      name: 'Masahiro Sakamoto',
      apacheId: 'massakam',
      org: 'Yahoo Japan Corporation',
      roles: 'Committer, PMC'
    },
    {
      name: 'Masakazu Kitajo',
      apacheId: 'maskit',
      org: '',
      roles: 'Committer, PMC'
    },
    {
      name: 'Matteo Merli',
      apacheId: 'mmerli',
      org: 'Streamlio',
      roles: 'Committer, PMC'
    },
    {
      name: 'Nozomi Kurihara',
      apacheId: 'nkurihar',
      org: 'Yahoo Japan Corporation',
      roles: 'Committer, PMC'
    },
    {
      name: 'P. Taylor Goetz',
      apacheId: 'ptgoetz',
      roles: 'Committer, PMC'
    },
    {
      name: 'Penghui Li',
      apacheId: 'penghui',
      roles: 'Committer, PMC'
    },
    {
      name: 'Rajan Dhabalia',
      apacheId: 'rdhabalia',
      org: 'Yahoo',
      roles: 'Committer, PMC'
    },
    {
      name: 'Sahaya Andrews',
      apacheId: 'andrews',
      org: 'Yahoo',
      roles: 'Committer, PMC'
    },
    {
      name: 'Sanjeev Kulkarni',
      apacheId: 'sanjeevrk',
      org: 'Streamlio',
      roles: 'Committer, PMC'
    },
    {
      name: 'Sebastián Schepens',
      apacheId: 'sschepens',
      org: 'MercadoLibre',
      roles: 'Committer, PMC'
    },
    {
      name: 'Siddharth Boobna',
      apacheId: 'sboobna',
      org: 'Salesforce',
      roles: 'Committer, PMC'
    },
    {
      name: 'Sijie Guo',
      apacheId: 'sijie',
      org: 'StreamNative',
      roles: 'Committer, PMC'
    },
    {
      name: 'Xiaolong Ran',
      apacheId: 'rxl',
      org: 'StreamNative',
      roles: 'Committer'
    },
    {
      name: 'Yijie Shen',
      apacheId: 'yjshen',
      org: 'KUAISHOU',
      roles: 'Committer'
    },
    {
      name: 'Yu Liu',
      apacheId: 'liuyu',
      org: 'StreamNative',
      roles: 'Committer'
    },
    {
      name: 'Yuki Shiga',
      apacheId: 'yushiga',
      org: 'Yahoo Japan Corporation',
      roles: 'Committer, PMC'
    },
    {
      name: 'Yong Zhang',
      apacheId: 'yong',
      org: 'StreamNative',
      roles: 'Committer'
    }
  ]
}
<commit_msg>Add Enrico Olivelli as committer in the team page (#10234)<commit_after>module.exports = {
  committers: [
    {
      name: 'Ali Ahmed',
      apacheId: 'aahmed',
      roles: 'Committer'
    },
    {
      name: 'Boyang Jerry Peng',
      apacheId: 'jerrypeng',
      org: 'Streamlio',
      roles: 'Committer, PMC'
    },
    {
      name: 'Brad McMillen',
      apacheId: 'bradtm',
      org: 'Yahoo',
      roles: 'Committer, PMC',
    },
    {
      name: 'Daniel Blankensteiner',
      apacheId: 'blankensteiner',
      roles: 'Committer'
    },
    {
      name: 'David Fisher',
      apacheId: 'wave',
      roles: 'Committer, PMC'
    },
    {
      name: 'Enrico Olivelli',
      apacheId: 'eolivelli',
      roles: 'Committer'
    },
    {
      name: 'Fangbin Sun',
      apacheId: 'sunfangbin',
      roles: 'Committer'
    },
    {
      name: 'Francis Christopher Liu',
      apacheId: 'toffer',
      roles: 'Committer, PMC'
    },
    {
      name: 'Guangning E',
      apacheId: 'guangning',
      roles: 'Committer'
    },
    {
      name: 'Hang Chen',
      apacheId: 'chenhang',
      org: 'BIGO Technology',
      roles: 'Committer'
    },
    {
      name: 'Hiroyuki Sakai',
      apacheId: 'hrsakai',
      org: 'Yahoo Japan Corporation',
      roles: 'Committer, PMC'
    },
    {
      name: 'Huanli Meng',
      apacheId: 'huanlimeng',
      org: 'StreamNative',
      roles: 'Committer'
    },
    {
      name: 'Ivan Brendan Kelly',
      apacheId: 'ivank',
      org: 'Streamlio',
      roles: 'Committer, PMC'
    },
    {
      name: 'Jai Asher',
      apacheId: 'jai1',
      org: 'Yahoo',
      roles: 'Committer, PMC'
    },
    {
      name: 'Jennifer Huang',
      apacheId: 'hjf',
      org: 'StreamNative',
      roles: 'Committer, PMC'
    },
    {
      name: 'Jia Zhai',
      apacheId: 'zhaijia',
      org: 'StreamNative',
      roles: 'Committer, PMC'
    },
    {
      name: 'Jim Jagielski',
      apacheId: 'jim',
      roles: 'Committer, PMC'
    },
    {
      name: 'Joe Francis',
      apacheId: 'joef',
      org: 'Yahoo',
      roles: 'Committer, PMC'
    },
    {
      name: 'Lin Lin',
      apacheId: 'linlin',
      org: 'Tencent',
      roles: 'Committer'
    },
    {
      name: 'Ludwig Pummer',
      apacheId: 'ludwigp',
      org: 'Yahoo',
      roles: 'Committer, PMC'
    },
    {
      name: 'Masahiro Sakamoto',
      apacheId: 'massakam',
      org: 'Yahoo Japan Corporation',
      roles: 'Committer, PMC'
    },
    {
      name: 'Masakazu Kitajo',
      apacheId: 'maskit',
      org: '',
      roles: 'Committer, PMC'
    },
    {
      name: 'Matteo Merli',
      apacheId: 'mmerli',
      org: 'Streamlio',
      roles: 'Committer, PMC'
    },
    {
      name: 'Nozomi Kurihara',
      apacheId: 'nkurihar',
      org: 'Yahoo Japan Corporation',
      roles: 'Committer, PMC'
    },
    {
      name: 'P. Taylor Goetz',
      apacheId: 'ptgoetz',
      roles: 'Committer, PMC'
    },
    {
      name: 'Penghui Li',
      apacheId: 'penghui',
      roles: 'Committer, PMC'
    },
    {
      name: 'Rajan Dhabalia',
      apacheId: 'rdhabalia',
      org: 'Yahoo',
      roles: 'Committer, PMC'
    },
    {
      name: 'Sahaya Andrews',
      apacheId: 'andrews',
      org: 'Yahoo',
      roles: 'Committer, PMC'
    },
    {
      name: 'Sanjeev Kulkarni',
      apacheId: 'sanjeevrk',
      org: 'Streamlio',
      roles: 'Committer, PMC'
    },
    {
      name: 'Sebastián Schepens',
      apacheId: 'sschepens',
      org: 'MercadoLibre',
      roles: 'Committer, PMC'
    },
    {
      name: 'Siddharth Boobna',
      apacheId: 'sboobna',
      org: 'Salesforce',
      roles: 'Committer, PMC'
    },
    {
      name: 'Sijie Guo',
      apacheId: 'sijie',
      org: 'StreamNative',
      roles: 'Committer, PMC'
    },
    {
      name: 'Xiaolong Ran',
      apacheId: 'rxl',
      org: 'StreamNative',
      roles: 'Committer'
    },
    {
      name: 'Yijie Shen',
      apacheId: 'yjshen',
      org: 'KUAISHOU',
      roles: 'Committer'
    },
    {
      name: 'Yu Liu',
      apacheId: 'liuyu',
      org: 'StreamNative',
      roles: 'Committer'
    },
    {
      name: 'Yuki Shiga',
      apacheId: 'yushiga',
      org: 'Yahoo Japan Corporation',
      roles: 'Committer, PMC'
    },
    {
      name: 'Yong Zhang',
      apacheId: 'yong',
      org: 'StreamNative',
      roles: 'Committer'
    }
  ]
}
<|endoftext|>


<commit_before>/////
// Calculate node radius based on size of area and number of nodes
window.calculateAvatarRadius = function(width, height, opinions) {
  var ratio_filled = .3, r

  r = Math.sqrt(width * height / opinions.length * ratio_filled)/2
  r = Math.min(r, width/2, height/2)

  // Travis: what's the purpose of this?
  if (opinions.length > 10) {
    // Now round r up until it fits perfectly within height
    var times_fit = height / (2*r)
    r = (height / (Math.floor(times_fit))) / 2 - .001
  }

  return r
}

//////
// Uses a d3-based physics simulation to calculate a reasonable layout
// of avatars within a given area.

window.positionAvatars = function(width, height, opinions) {
  width = width || 400
  height = height || 70

  var opinions = opinions.slice()
                   .sort(function (a,b) {return a.stance-b.stance}),
      n = opinions.length,
      r = calculateAvatarRadius(width, height, opinions),
      x_force_mult = 2,
      y_force_mult = height <= 100 ? 1 : 4,
      nodes, force, ticks = 0

  // Initialize positions of each node
  nodes = d3.range(opinions.length).map(function(i) {
    var radius = opinions[i].radius || r

    if(parseFloat(opinions[i].icon.style.width) != radius * 2)
      opinions[i].icon.style.width = opinions[i].icon.style.height = radius*2 + 'px'

    opinions[i].x_target = (opinions[i].stance + 1)/2 * width

    // Travis: I'm finding that different initial conditions work
    // better at different scales.
    //   - Give large numbers of avatars some good initial spacing
    //   - Small numbers of avatars can be more precisely placed for quick
    //     convergence with little churn
    x = opinions.length > 10 ? radius + (width- 2 * radius) * (i / n) : opinions[i].x_target
    y = opinions.length == 1 ? height - radius : radius + Math.random() * (height - 2 * radius)

    return {
      index: i,
      radius: radius,
      x: x,
      y: y
    }
  })

  // see https://github.com/mbostock/d3/wiki/Force-Layout for docs
  force = d3.layout.force()
    .nodes(nodes)
    .on("tick", tick)
    .on('end', end)
    .gravity(0)
    .charge(0)
    .chargeDistance(0)
    .start()

  // Called after the simulation stops
  function end() {
    total_energy = calculate_global_energy()
    console.log('Simulation complete after ' + ticks + ' ticks. ' +
                'Energy of system could be reduced by at most ' + total_energy + ' by global sort.')
  }

  // One iteration of the simulation
  function tick(e) {

    var q, i = 0, some_node_moved = false

    //////
    // Repel colliding nodes
    // A quadtree helps efficiently detect collisions
    q = d3.geom.quadtree(nodes)
    while (++i < n)
      q.visit(collide(nodes[i]))

    //////
    // Apply standard forces
    nodes.forEach(function(o, i) {

      // Push node toward its desired x-location (e.g. stance)
      o.x += e.alpha * (x_force_mult * width  * .001) * (opinions[o.index].x_target - o.x)

      // Push node downwards
      o.y += e.alpha * y_force_mult

      // Ensure node is still within the bounding box
      o.x = Math.max(o.radius, Math.min(width  - o.radius, o.x))
      o.y = Math.max(o.radius, Math.min(height - o.radius, o.y))

      // Re-position dom element...if it's moved enough
      if ( !opinions[i].icon.style.left || Math.abs( parseFloat(opinions[i].icon.style.left) - (o.x - o.radius)) > .1 ){
        opinions[i].icon.style.left = o.x - o.radius + 'px'
        some_node_moved = true
      }

      if ( !opinions[i].icon.style.top || Math.abs( parseFloat(opinions[i].icon.style.top) - (o.y - o.radius)) > .1 ) {
        opinions[i].icon.style.top  = o.y - o.radius + 'px'
        some_node_moved = true
      }
    })

    ticks += 1

    // Complete the simulation if we've reached a steady state
    if (!some_node_moved) force.stop()

  }

  function collide(node) {

    return function(quad, x1, y1, x2, y2) {
      if (quad.leaf && quad.point && quad.point !== node) {
        var dx = node.x - quad.point.x,
            dy = node.y - quad.point.y,
            dist = Math.sqrt(dx * dx + dy * dy),
            combined_r = node.radius + quad.point.radius

        // Transpose two points in the same neighborhood if it would reduce energy of system
        // 10 is not a principled threshold.
        if ( energy_reduced_by_swap(node, quad.point) > 10) {
          swap_position(node, quad.point)
          dx *= -1; dy *= -1
        }

        // repel both points equally in opposite directions if they overlap
        if (dist < combined_r) {
          var separate_by, offset_x, offset_y
          separate_by = dist == 0 ? 1 : ( dist - combined_r ) / dist
          offset_x = dx * separate_by * .5,
          offset_y = dy * separate_by * .5

          node.x -= offset_x
          node.y -= offset_y
          quad.point.x += offset_x
          quad.point.y += offset_y
        }
      }

      // Visit subregions if we could possibly have a collision there
      // Travis: I understand what the 16 *does* but not the significance
      //         of the particular value. Does 16 make sense for all
      //         avatar sizes and sizes of the bounding box?
      var neighborhood_radius = node.radius + 16,
          nx1 = node.x - neighborhood_radius,
          nx2 = node.x + neighborhood_radius,
          ny1 = node.y - neighborhood_radius,
          ny2 = node.y + neighborhood_radius

      return x1 > nx2
          || x2 < nx1
          || y1 > ny2
          || y2 < ny1
    }
  }

  // Check if system energy would be reduced if two nodes' positions would
  // be swapped. We square the difference in order to favor large differences
  // for one vs small differences for the pair.
  function energy_reduced_by_swap(p1, p2) {
    var p1_jealousy = Math.pow(p1.x - opinions[p1.index].x_target, 2) -
                      Math.pow(p2.x - opinions[p1.index].x_target, 2),
        p2_jealousy = Math.pow(p2.x - opinions[p2.index].x_target, 2) -
                      Math.pow(p1.x - opinions[p2.index].x_target, 2)

    return p1_jealousy + p2_jealousy
  }

  // Swaps the positions of two nodes
  var position_props = ['x', 'y', 'px', 'py']
  function swap_position(p1, p2) {
    var swap
    for(var i=0; i < position_props.length; i++){
      swap = p1[position_props[i]]
      p1[position_props[i]] = p2[position_props[i]]
      p2[position_props[i]] = swap
    }
  }

  ///////////////////////////////////////////////////////
  // The rest of these methods are only used for testing
  ///////////////////////////////////////////////////////

  // Calculates the reduction in global energy that a sort would have,
  // where global energy is the sum across all nodes of the square of their
  // distance from desired x position. We square the difference in order
  // to favor large differences for individuals over small differences for
  // many.
  function calculate_global_energy() {
    var energy_unsorted = 0,
        energy_sorted = 0,
        sorted = global_sort(false)

    for (var i = 0; i < nodes.length; i++){
      energy_sorted   += Math.pow(Math.abs( sorted[i].x - opinions[sorted[i].index].x_target ), 2)
      energy_unsorted += Math.pow(Math.abs(  nodes[i].x - opinions[nodes[i].index].x_target ), 2)
    }

    return Math.sqrt(energy_unsorted) - Math.sqrt(energy_sorted)
  }

  //////
  // global_sort
  //
  // Given a set of simulated face positions, reassigns avatars to the positions based on
  // stance to enforce a global ordering.
  // This method is visually jarring, so using it to sort nodes in place should be
  // used as little as possible.
  function global_sort(sort_in_place) {
    if (sort_in_place === undefined) sort_in_place = true

    // Create one node list sorted by x position
    x_sorted_nodes = nodes.slice()
                  .sort(function (a,b) {return a.x-b.x})
    // ... and another sorted by desired x position
    desired_x_sorted_nodes = nodes.slice()
                  .sort(function (a,b) {return opinions[a.index].x_target - opinions[b.index].x_target})

    // Create a new dummy set of nodes optimally arranged
    new_nodes = []
    for (var i = 0; i < nodes.length; i++){
      new_nodes.push({
        // assign the avatar...
        index: desired_x_sorted_nodes[i].index,
        radius: desired_x_sorted_nodes[i].radius,
        weight: desired_x_sorted_nodes[i].weight,
        // ...to a good position
        x: x_sorted_nodes[i].x,
        y: x_sorted_nodes[i].y,
        px: x_sorted_nodes[i].px,
        py: x_sorted_nodes[i].py
      })
    }

    // Walk through nodes and reassign the faces given
    // the optimal assignments discovered earlier. We
    // can't assign nodes=new_nodes because the layout
    // depends on nodes pointing to the same object.
    if (sort_in_place){
      var props = ['index', 'radius', 'x', 'y', 'px', 'py', 'weight']
      for (var i = 0; i < nodes.length; i++)
        for (var j = 0; j < props.length; j++)
          nodes[i][props[j]] = new_nodes[i][props[j]]
    }
    return new_nodes
  }

}

<commit_msg>parameter adjustments<commit_after>/////
// Calculate node radius based on size of area and number of nodes
window.calculateAvatarRadius = function(width, height, opinions) {
  var ratio_filled = .3, r

  r = Math.sqrt(width * height / opinions.length * ratio_filled)/2
  r = Math.min(r, width/2, height/2)

  // Travis: what's the purpose of this?
  if (opinions.length > 10) {
    // Now round r up until it fits perfectly within height
    var times_fit = height / (2*r)
    r = (height / (Math.floor(times_fit))) / 2 - .001
  }

  return r
}

//////
// Uses a d3-based physics simulation to calculate a reasonable layout
// of avatars within a given area.

window.positionAvatars = function(width, height, opinions) {
  width = width || 400
  height = height || 70

  var opinions = opinions.slice()
                   .sort(function (a,b) {return a.stance-b.stance}),
      n = opinions.length,
      r = calculateAvatarRadius(width, height, opinions),
      x_force_mult = 2,
      y_force_mult = height <= 100 ? 2 : 6,
      nodes, force, ticks = 0

  // Initialize positions of each node
  nodes = d3.range(opinions.length).map(function(i) {
    var radius = opinions[i].radius || r

    if(parseFloat(opinions[i].icon.style.width) != radius * 2)
      opinions[i].icon.style.width = opinions[i].icon.style.height = radius*2 + 'px'

    opinions[i].x_target = (opinions[i].stance + 1)/2 * width

    // Travis: I'm finding that different initial conditions work
    // better at different scales.
    //   - Give large numbers of avatars some good initial spacing
    //   - Small numbers of avatars can be more precisely placed for quick
    //     convergence with little churn
    x = opinions.length > 10 ? radius + (width- 2 * radius) * (i / n) : opinions[i].x_target
    y = opinions.length == 1 ? height - radius : radius + Math.random() * (height - 2 * radius)

    return {
      index: i,
      radius: radius,
      x: x,
      y: y
    }
  })

  // see https://github.com/mbostock/d3/wiki/Force-Layout for docs
  force = d3.layout.force()
    .nodes(nodes)
    .on("tick", tick)
    .on('end', end)
    .gravity(0)
    .charge(0)
    .chargeDistance(0)
    .start()

  // Called after the simulation stops
  function end() {
    total_energy = calculate_global_energy()
    console.log('Simulation complete after ' + ticks + ' ticks. ' +
                'Energy of system could be reduced by at most ' + total_energy + ' by global sort.')
  }

  // One iteration of the simulation
  function tick(e) {

    var q, i = 0, some_node_moved = false

    //////
    // Repel colliding nodes
    // A quadtree helps efficiently detect collisions
    q = d3.geom.quadtree(nodes)
    while (++i < n)
      q.visit(collide(nodes[i]))

    //////
    // Apply standard forces
    nodes.forEach(function(o, i) {

      // Push node toward its desired x-location (e.g. stance)
      o.x += e.alpha * (x_force_mult * width  * .001) * (opinions[o.index].x_target - o.x)

      // Push node downwards
      o.y += e.alpha * y_force_mult

      // Ensure node is still within the bounding box
      o.x = Math.max(o.radius, Math.min(width  - o.radius, o.x))
      o.y = Math.max(o.radius, Math.min(height - o.radius, o.y))

      // Re-position dom element...if it's moved enough
      if ( !opinions[i].icon.style.left || Math.abs( parseFloat(opinions[i].icon.style.left) - (o.x - o.radius)) > .1 ){
        opinions[i].icon.style.left = o.x - o.radius + 'px'
        some_node_moved = true
      }

      if ( !opinions[i].icon.style.top || Math.abs( parseFloat(opinions[i].icon.style.top) - (o.y - o.radius)) > .1 ) {
        opinions[i].icon.style.top  = o.y - o.radius + 'px'
        some_node_moved = true
      }
    })

    ticks += 1

    // Complete the simulation if we've reached a steady state
    if (!some_node_moved) force.stop()

  }

  function collide(node) {

    return function(quad, x1, y1, x2, y2) {
      if (quad.leaf && quad.point && quad.point !== node) {
        var dx = node.x - quad.point.x,
            dy = node.y - quad.point.y,
            dist = Math.sqrt(dx * dx + dy * dy),
            combined_r = node.radius + quad.point.radius

        // Transpose two points in the same neighborhood if it would reduce energy of system
        // 10 is not a principled threshold.
        if ( energy_reduced_by_swap(node, quad.point) > 10) {
          swap_position(node, quad.point)
          dx *= -1; dy *= -1
        }

        // repel both points equally in opposite directions if they overlap
        if (dist < combined_r) {
          var separate_by, offset_x, offset_y
          separate_by = dist == 0 ? 1 : ( dist - combined_r ) / dist
          offset_x = dx * separate_by * .6,
          offset_y = dy * separate_by * .6

          node.x -= offset_x
          node.y -= offset_y
          quad.point.x += offset_x
          quad.point.y += offset_y
        }
      }

      // Visit subregions if we could possibly have a collision there
      // Travis: I understand what the 16 *does* but not the significance
      //         of the particular value. Does 16 make sense for all
      //         avatar sizes and sizes of the bounding box?
      var neighborhood_radius = node.radius + 16,
          nx1 = node.x - neighborhood_radius,
          nx2 = node.x + neighborhood_radius,
          ny1 = node.y - neighborhood_radius,
          ny2 = node.y + neighborhood_radius

      return x1 > nx2
          || x2 < nx1
          || y1 > ny2
          || y2 < ny1
    }
  }

  // Check if system energy would be reduced if two nodes' positions would
  // be swapped. We square the difference in order to favor large differences
  // for one vs small differences for the pair.
  function energy_reduced_by_swap(p1, p2) {
    var p1_jealousy = Math.pow(p1.x - opinions[p1.index].x_target, 2) -
                      Math.pow(p2.x - opinions[p1.index].x_target, 2),
        p2_jealousy = Math.pow(p2.x - opinions[p2.index].x_target, 2) -
                      Math.pow(p1.x - opinions[p2.index].x_target, 2)

    return p1_jealousy + p2_jealousy
  }

  // Swaps the positions of two nodes
  var position_props = ['x', 'y', 'px', 'py']
  function swap_position(p1, p2) {
    var swap
    for(var i=0; i < position_props.length; i++){
      swap = p1[position_props[i]]
      p1[position_props[i]] = p2[position_props[i]]
      p2[position_props[i]] = swap
    }
  }

  ///////////////////////////////////////////////////////
  // The rest of these methods are only used for testing
  ///////////////////////////////////////////////////////

  // Calculates the reduction in global energy that a sort would have,
  // where global energy is the sum across all nodes of the square of their
  // distance from desired x position. We square the difference in order
  // to favor large differences for individuals over small differences for
  // many.
  function calculate_global_energy() {
    var energy_unsorted = 0,
        energy_sorted = 0,
        sorted = global_sort(false)

    for (var i = 0; i < nodes.length; i++){
      energy_sorted   += Math.pow(Math.abs( sorted[i].x - opinions[sorted[i].index].x_target ), 2)
      energy_unsorted += Math.pow(Math.abs(  nodes[i].x - opinions[nodes[i].index].x_target ), 2)
    }

    return Math.sqrt(energy_unsorted) - Math.sqrt(energy_sorted)
  }

  //////
  // global_sort
  //
  // Given a set of simulated face positions, reassigns avatars to the positions based on
  // stance to enforce a global ordering.
  // This method is visually jarring, so using it to sort nodes in place should be
  // used as little as possible.
  function global_sort(sort_in_place) {
    if (sort_in_place === undefined) sort_in_place = true

    // Create one node list sorted by x position
    x_sorted_nodes = nodes.slice()
                  .sort(function (a,b) {return a.x-b.x})
    // ... and another sorted by desired x position
    desired_x_sorted_nodes = nodes.slice()
                  .sort(function (a,b) {return opinions[a.index].x_target - opinions[b.index].x_target})

    // Create a new dummy set of nodes optimally arranged
    new_nodes = []
    for (var i = 0; i < nodes.length; i++){
      new_nodes.push({
        // assign the avatar...
        index: desired_x_sorted_nodes[i].index,
        radius: desired_x_sorted_nodes[i].radius,
        weight: desired_x_sorted_nodes[i].weight,
        // ...to a good position
        x: x_sorted_nodes[i].x,
        y: x_sorted_nodes[i].y,
        px: x_sorted_nodes[i].px,
        py: x_sorted_nodes[i].py
      })
    }

    // Walk through nodes and reassign the faces given
    // the optimal assignments discovered earlier. We
    // can't assign nodes=new_nodes because the layout
    // depends on nodes pointing to the same object.
    if (sort_in_place){
      var props = ['index', 'radius', 'x', 'y', 'px', 'py', 'weight']
      for (var i = 0; i < nodes.length; i++)
        for (var j = 0; j < props.length; j++)
          nodes[i][props[j]] = new_nodes[i][props[j]]
    }
    return new_nodes
  }

}

<|endoftext|>


<commit_before>function relativeOffset(node, ancestor, offset, getRelativeToAncestor, count) {
	if(count){
		offset += node.textContent.length;
	}
	if(node !== ancestor) {
		if(node.previousSibling &&
				(getRelativeToAncestor || node.nodeType !== 1 || offset > node.textContent.length)) {
			if(node.previousSibling) {
				return relativeOffset(node.previousSibling, ancestor, offset, getRelativeToAncestor, true);
			} else {
				if(offset > node.textContent.length) {
					return { ref: node, offset: node.textContent.length };
				}
			}
		} else if((getRelativeToAncestor || node.nodeType !== 1) && node.parentNode) return relativeOffset(node.parentNode, ancestor, offset, getRelativeToAncestor, false);
	}
	return { ref: node, offset: offset };
}

function position(ancestor, isStart, getRelativeToAncestor) {
	var rng = s().getRangeAt(0);
	var ref = isStart ? rng.startContainer : rng.endContainer;
	var offset = isStart ? rng.startOffset : rng.endOffset;

	if(ref.textContent.length !== 0) {
		var node = ref;
		if(isStart && offset === node.textContent.length) {
			while(node && node !== ancestor) {
				if(node.nextSibling) {
					ref = node.nextSibling;
					offset = 0;
					break;
				} else node = node.parentNode;
			}
		} else if(!isStart && !rng.collapsed && offset === 0) {
			while(node && node !== ancestor) {
				if(node.previousSibling) {
					//obj = relativeOffset(node.previousSibling, ancestor, node.previousSibling.textContent.length, isStart, getRelativeToAncestor, false);
					ref = node.previousSibling;
					offset = node.previousSibling.textContent.length;
					break;
				} else node = node.parentNode;
			}
		}
	}

	var isAtStart = offset === 0;

	if(ancestor) {
		var tmp = relativeOffset(ref, ancestor, offset, getRelativeToAncestor, false);
		ref = tmp.ref;
		offset = tmp.offset;
	}
	return { ref: ref, offset: offset, isAtStart: isAtStart };
}

function check(node) {
	function isBlock(node) {
		return node && node.nodeType === 1 && !getComputedStyle(node).display.match(/inline/);
	}
	var excludedTags = ['SCRIPT', 'STYLE'];
	// TODO figure out best way to exclude text nodes that only contain
	// whitespaces and are adjacent to block elements. (these text nodes
	// are just results from the developer using spaces or tabs to align their
	// HTML.
	return (node.nodeType === 1 && excludedTags.indexOf(node.nodeName) === -1) ||
		(node.nodeType === 3 && (!node.textContent.match(/^\s+$/) || (!isBlock(node.nextSibling) && !isBlock(node.previousSibling))));
}

function descendants(element, ufo, levels) {
	//if(ufo instanceof Node) checkNodes = [ ufo ];
	//else if(ufo instanceof NodeList || ufo instanceof Array) checkNodes = ufo ;
	//else if(typeof ufo === 'string') checkNodes = this.element.querySelectorAll(ufo);
	//else if(typeof ufo === 'number') {
	//	if(ufo === 1) checkNodes = this.element.querySelectorAll('*');
	//	else {
	//		// assume ufo === 3, text nodes
	//		// emulate standard behaviour of text nodes
	//		notPartlyContained = false;
	//		var n, walk=document.createTreeWalker(this.element, NodeFilter.SHOW_TEXT,null,false);
	//		while(n=walk.nextNode()) checkNodes.push(n);
	//	}
	//}

	// TODO implement with TreeWalker instead
	function recurse(node, level) {
		var children = nodeType === 1 ? node.children : node.childNodes;
		if(!children) return;
		for(var i = 0; i < children.length; i++) {
			if(check(children[i]) && (getAnyType || (!nodeType || children[i].nodeType === nodeType) && (!selector || (children[i].nodeType === 1 && children[i].matches(selector))))) nodes.push(children[i]);
			if (!levels || level < levels) {
				recurse(children[i], level + 1);
			}
		}
	}
	var getAnyType, nodeType, selector;

	if(typeof ufo === 'string') selector = ufo;
	else if(typeof ufo === 'number') {
		if(ufo === 0) getAnyType = true;
		else nodeType = ufo;
	} else nodeType = 1;

	var nodes = [];
	recurse(element, 1);

	return nodes;
}
var s = window.getSelection;

var Selectron = function(element) {
	this.element = element;
};

Selectron.prototype = {
	contained: function(ufo, levels, notPartlyContained) {
		// default behaviour of containsNode for textNodes
		if(ufo === 3) notPartlyContained = false;

		levels = levels || 0;

		var checkNodes, sel = s(), nodes = [];

		if(ufo instanceof Node) checkNodes = [ ufo ];
		else if(ufo instanceof NodeList || ufo instanceof Array) checkNodes = ufo;
		else checkNodes = descendants(this.element, ufo, levels);

		if(sel.containsNode) {
			checkNodes.forEach(function(node) {
				if(sel.containsNode(node, !notPartlyContained)) nodes.push(node);
			});
		} else {
			var rng = this.range();
			var startOffset = relativeOffset(rng.startContainer, this.element, rng.startOffset, true, false).offset;
			var endOffset = relativeOffset(rng.endContainer, this.element, rng.endOffset, true, false).offset;

			for(var j = 0; j < checkNodes.length; j++) {
				var node = checkNodes[j];
				var currentStartOffset = relativeOffset(node, this.element, 0, true, false).offset;
				var currentEndOffset = relativeOffset(node, this.element, node.textContent.length, true, false).offset;

				if(
						(currentStartOffset >= startOffset && currentEndOffset <= endOffset) ||
						(!notPartlyContained &&
						 ((rng.collapsed && startOffset >= currentStartOffset && startOffset <= currentEndOffset) ||
							(startOffset > currentStartOffset && startOffset < currentEndOffset) ||
							(endOffset > currentStartOffset && endOffset < currentEndOffset)
							))) {
					if(notPartlyContained ||
							(endOffset !== currentStartOffset && startOffset !== currentEndOffset) ||
							(node.textContent.length === 0 || node.nodeType !== 1 || getComputedStyle(node).display.match(/inline/)) ||
							(rng.collapsed && (rng.startContainer.closest(node, this.element))))
						nodes.push(node);
				}
			}
		}

		return nodes;
	},

	normalize: function() {
		this.set(this.get());
	},

	range: function() {
		var sel = s();
		if(sel.rangeCount > 0) return sel.getRangeAt(0);
		else return null;
	},

	get: function(ufo, getRelativeToAncestor) {
		var ancestor = this.element;
		var positions;
		if(this.countRanges() > 0) {
			if(ufo === true) getRelativeToAncestor = ufo;
			else if(ufo instanceof Node) ancestor = ufo;
			positions = { start: position(ancestor, true, getRelativeToAncestor), end: position(ancestor, false, getRelativeToAncestor) };
		} else {
			var ref = this.element.children[this.element.children.length - 1];
			var pos = { ref: ref, offset: ref.textContent.length, isAtStart: false };
			positions = {
				start: pos,
				end: pos
			};
		}
		var positron = new Positron(positions,this);
		return positron;
	},

	positron: function(positions) {
		return new Positron(positions,this);
	},

	select: function(node) {
		var children = node.offspring();
		var first = children[0];
		var last = children[children.length - 1];
		this.set(new Positron({
			start: { ref: first, offset: 0, isAtStart: true },
			end: { ref: last, offset: last.textContent.length, isAtStart: last.textContent.length === 0 }
		}, this));
	},

	set: function(ufo, startOffset, endNode, endOffset) {
		function recurse(node, offset, isStart) {
			if(!node) return null;
			var limit = isStart ? node.textContent.length - 1 : node.textContent.length;
			if(offset === 0 && node.textContent.length === 0) return { ref: node, offset: offset };
			else if(offset > limit && node.nextSibling) return recurse(node.nextSibling, offset - node.textContent.length, isStart);
			else if(node.firstChild) return recurse(node.firstChild, offset, isStart);
			else return { ref: node, offset: offset };
		}
		var start, end;
		if(ufo instanceof Positron) {
			start = recurse(ufo.start.ref, ufo.start.offset, ufo.start.isAtStart);
			end = recurse(ufo.end.ref, ufo.end.offset, ufo.end.isAtStart);
		} else if(ufo instanceof Node) {
			start = recurse(ufo, startOffset || 0);
			end = endNode instanceof Node ? recurse(endNode, endOffset || endNode.textContent.length) : start;
		}

		var rng = document.createRange();
		rng.setStart(start.ref, start.offset);
		rng.setEnd(end.ref, end.offset);

		s().removeAllRanges();
		s().addRange(rng);
	},

	countRanges: function() {
		return s().rangeCount;
	}
};

var Positron = function(positions, selectron) {
	this.selectron = selectron;
	for(var property in positions) {
		this[property] = positions[property];
	}
};

Positron.prototype = {
	restore: function() {
		this.selectron.set(this);
	},

	isCollapsed: function() {
		return this.start.ref === this.end.ref && this.start.offset === this.end.offset;
	},

	clone: function() {
		return new Positron({ start: this.start, end: this.end }, this.selectron );
	}
};

module.exports = Selectron;
<commit_msg>Updated selectron to not be a constructor function. Instead functions that need an element take it as their first argument.<commit_after>function relativeOffset(node, ancestor, offset, getRelativeToAncestor, count) {
	if(count){
		offset += node.textContent.length;
	}
	if(node !== ancestor) {
		if(node.previousSibling &&
				(getRelativeToAncestor || node.nodeType !== 1 || offset > node.textContent.length)) {
			if(node.previousSibling) {
				return relativeOffset(node.previousSibling, ancestor, offset, getRelativeToAncestor, true);
			} else {
				if(offset > node.textContent.length) {
					return { ref: node, offset: node.textContent.length };
				}
			}
		} else if((getRelativeToAncestor || node.nodeType !== 1) && node.parentNode) return relativeOffset(node.parentNode, ancestor, offset, getRelativeToAncestor, false);
	}
	return { ref: node, offset: offset };
}

function position(ancestor, isStart, getRelativeToAncestor) {
	var rng = s().getRangeAt(0);
	var ref = isStart ? rng.startContainer : rng.endContainer;
	var offset = isStart ? rng.startOffset : rng.endOffset;

	if(ref.textContent.length !== 0) {
		var node = ref;
		if(isStart && offset === node.textContent.length) {
			while(node && node !== ancestor) {
				if(node.nextSibling) {
					ref = node.nextSibling;
					offset = 0;
					break;
				} else node = node.parentNode;
			}
		} else if(!isStart && !rng.collapsed && offset === 0) {
			while(node && node !== ancestor) {
				if(node.previousSibling) {
					//obj = relativeOffset(node.previousSibling, ancestor, node.previousSibling.textContent.length, isStart, getRelativeToAncestor, false);
					ref = node.previousSibling;
					offset = node.previousSibling.textContent.length;
					break;
				} else node = node.parentNode;
			}
		}
	}

	var isAtStart = offset === 0;

	if(ancestor) {
		var tmp = relativeOffset(ref, ancestor, offset, getRelativeToAncestor, false);
		ref = tmp.ref;
		offset = tmp.offset;
	}
	return { ref: ref, offset: offset, isAtStart: isAtStart };
}

function check(node) {
	function isBlock(node) {
		return node && node.nodeType === 1 && !getComputedStyle(node).display.match(/inline/);
	}
	var excludedTags = ['SCRIPT', 'STYLE'];
	// TODO figure out best way to exclude text nodes that only contain
	// whitespaces and are adjacent to block elements. (these text nodes
	// are just results from the developer using spaces or tabs to align their
	// HTML.
	return (node.nodeType === 1 && excludedTags.indexOf(node.nodeName) === -1) ||
		(node.nodeType === 3 && (!node.textContent.match(/^\s+$/) || (!isBlock(node.nextSibling) && !isBlock(node.previousSibling))));
}

function descendants(element, ufo, levels) {
	//if(ufo instanceof Node) checkNodes = [ ufo ];
	//else if(ufo instanceof NodeList || ufo instanceof Array) checkNodes = ufo ;
	//else if(typeof ufo === 'string') checkNodes = this.element.querySelectorAll(ufo);
	//else if(typeof ufo === 'number') {
	//	if(ufo === 1) checkNodes = this.element.querySelectorAll('*');
	//	else {
	//		// assume ufo === 3, text nodes
	//		// emulate standard behaviour of text nodes
	//		notPartlyContained = false;
	//		var n, walk=document.createTreeWalker(this.element, NodeFilter.SHOW_TEXT,null,false);
	//		while(n=walk.nextNode()) checkNodes.push(n);
	//	}
	//}

	// TODO implement with TreeWalker instead
	function recurse(node, level) {
		var children = nodeType === 1 ? node.children : node.childNodes;
		if(!children) return;
		for(var i = 0; i < children.length; i++) {
			if(check(children[i]) && (getAnyType || (!nodeType || children[i].nodeType === nodeType) && (!selector || (children[i].nodeType === 1 && children[i].matches(selector))))) nodes.push(children[i]);
			if (!levels || level < levels) {
				recurse(children[i], level + 1);
			}
		}
	}
	var getAnyType, nodeType, selector;

	if(typeof ufo === 'string') selector = ufo;
	else if(typeof ufo === 'number') {
		if(ufo === 0) getAnyType = true;
		else nodeType = ufo;
	} else nodeType = 1;

	var nodes = [];
	recurse(element, 1);

	return nodes;
}

var s = window.getSelection;

var Positron = function(positions, selectron) {
	this.selectron = selectron;
	for(var property in positions) {
		this[property] = positions[property];
	}
};

Positron.prototype = {
	restore: function() {
		this.selectron.set(this);
	},

	isCollapsed: function() {
		return this.start.ref === this.end.ref && this.start.offset === this.end.offset;
	},

	clone: function() {
		return new Positron({ start: this.start, end: this.end }, this.selectron );
	}
};

module.exports = {
	contained: function(element, ufo, levels, notPartlyContained) {
		// default behaviour of containsNode for textNodes
		if(ufo === 3) notPartlyContained = false;

		levels = levels || 0;

		var checkNodes, sel = s(), nodes = [];

		if(ufo instanceof Node) checkNodes = [ ufo ];
		else if(ufo instanceof NodeList || ufo instanceof Array) checkNodes = ufo;
		else checkNodes = descendants(element, ufo, levels);

		if(sel.containsNode) {
			checkNodes.forEach(function(node) {
				if(sel.containsNode(node, !notPartlyContained)) nodes.push(node);
			});
		} else {
			var rng = this.range();
			var startOffset = relativeOffset(rng.startContainer, element, rng.startOffset, true, false).offset;
			var endOffset = relativeOffset(rng.endContainer, element, rng.endOffset, true, false).offset;

			for(var j = 0; j < checkNodes.length; j++) {
				var node = checkNodes[j];
				var currentStartOffset = relativeOffset(node, element, 0, true, false).offset;
				var currentEndOffset = relativeOffset(node, element, node.textContent.length, true, false).offset;

				if(
						(currentStartOffset >= startOffset && currentEndOffset <= endOffset) ||
						(!notPartlyContained &&
						 ((rng.collapsed && startOffset >= currentStartOffset && startOffset <= currentEndOffset) ||
							(startOffset > currentStartOffset && startOffset < currentEndOffset) ||
							(endOffset > currentStartOffset && endOffset < currentEndOffset)
							))) {
					if(notPartlyContained ||
							(endOffset !== currentStartOffset && startOffset !== currentEndOffset) ||
							(node.textContent.length === 0 || node.nodeType !== 1 || getComputedStyle(node).display.match(/inline/)) ||
							(rng.collapsed && (rng.startContainer.closest(node, element))))
						nodes.push(node);
				}
			}
		}

		return nodes;
	},

	normalize: function() {
		this.set(this.get());
	},

	range: function() {
		var sel = s();
		if(sel.rangeCount > 0) return sel.getRangeAt(0);
		else return null;
	},

	get: function(element, ufo, getRelativeToAncestor) {
		var ancestor = element;
		var positions;
		if(this.countRanges() > 0) {
			if(ufo === true) getRelativeToAncestor = ufo;
			else if(ufo instanceof Node) ancestor = ufo;
			positions = { start: position(ancestor, true, getRelativeToAncestor), end: position(ancestor, false, getRelativeToAncestor) };
		} else {
			var ref = element.children[element.children.length - 1];
			var pos = { ref: ref, offset: ref.textContent.length, isAtStart: false };
			positions = {
				start: pos,
				end: pos
			};
		}
		var positron = new Positron(positions,this);
		return positron;
	},

	positron: function(positions) {
		return new Positron(positions,this);
	},

	select: function(node) {
		var children = node.offspring();
		var first = children[0];
		var last = children[children.length - 1];
		this.set(new Positron({
			start: { ref: first, offset: 0, isAtStart: true },
			end: { ref: last, offset: last.textContent.length, isAtStart: last.textContent.length === 0 }
		}, this));
	},

	set: function(ufo, startOffset, endNode, endOffset) {
		function recurse(node, offset, isStart) {
			if(!node) return null;
			var limit = isStart ? node.textContent.length - 1 : node.textContent.length;
			if(offset === 0 && node.textContent.length === 0) return { ref: node, offset: offset };
			else if(offset > limit && node.nextSibling) return recurse(node.nextSibling, offset - node.textContent.length, isStart);
			else if(node.firstChild) return recurse(node.firstChild, offset, isStart);
			else return { ref: node, offset: offset };
		}
		var start, end;
		if(ufo instanceof Positron) {
			start = recurse(ufo.start.ref, ufo.start.offset, ufo.start.isAtStart);
			end = recurse(ufo.end.ref, ufo.end.offset, ufo.end.isAtStart);
		} else if(ufo instanceof Node) {
			start = recurse(ufo, startOffset || 0);
			end = endNode instanceof Node ? recurse(endNode, endOffset || endNode.textContent.length) : start;
		}

		var rng = document.createRange();
		rng.setStart(start.ref, start.offset);
		rng.setEnd(end.ref, end.offset);

		s().removeAllRanges();
		s().addRange(rng);
	},

	countRanges: function() {
		return s().rangeCount;
	}
};
<|endoftext|>


<commit_before>/**
 * @fileOverview Pitää sisällään asetuksiin liittyvät toiminnot ja oletusasetusten nimiavaruuden.
 * Kts. {@link Config} ja {@link Config.defaults}
 */

"use strict";

/**#nocode+*/
var log = require('./Utils').log
  , cjson = require('cjson')
  , path = require('path')
  , colors = require('colors');
/**#nocode-*/

/**
 * Alustaa luokan käyttöä varten, lataa {@link Config.defaults} kentät tämän instanssin kentiksi
 * @class Asetukset
 *
 * @param {Server} server  NetMatch-palvelin, joka kutsuu tätä konstruktoria
 */
function Config(server) {
  this.server = server;

  // Laajennetaan tämän Configin ominaisuuksia oletusconfigeilla
  cjson.extend(this, Config.defaults);
}

/**
 * @namespace Sisältää NetMatch-palvelimen oletusasetukset. Näitä voi muuttaa antamalla
 * {@link Server}-konstruktorille parametrina objektin, jonka avaimet sopivat tämän muotoon.
 */
Config.defaults = {
  /**
   * GSS-palvelimen osoite
   * @type String
   * @default "http://tuhoojabotti.com/nm"
   */
  regHost: "http://tuhoojabotti.com/nm",
  /**
   * GSS-palvelimella oleva polku gss.php tiedostoon
   * @type String
   * @default "/reg/gss.php"
   */
  regPath: "/reg/gss.php",
  /**
   * Rekisteröidäänkö palvelin
   * @type Boolean
   * @default true
   */
  register: true,
  /**
   * Osoite, josta pelaaja voi ladata puuttuvan kartan
   * @type String
   * @default "http://netmatch.vesq.org/maps/"
   */
  mapDownloadUrl: "http://netmatch.vesq.org/maps/",
  /**
   * Palvelimen kuvaus, näkyy listauksessa
   * @type String
   * @default "Node.js powered server"
   */
  description: "Node.js powered server",
  /**
   * Pelattava kartta. Voi olla merkkijono, jolloin pelataan vain yhtä karttaa, tai taulukko,
   * joka sisältää useamman kartan ja niitä kierrätetään erien välillä. Kaikkien karttojen tulee
   * olla olemassa kun palvelin käynnistetään.
   * @type String|Array
   * @default ["Luna", "Warehouse", "Mictlan"]
   */
  map: ["Luna", "Warehouse", "Mictlan"],
  /**
   * Maksimimäärä pelaajia. Suurin mahdollinen maksimimäärä on 64.
   * @type Number
   * @default 10
   */
  maxPlayers: 10,
  /**
   * Bottien määrä. Jos tämä on nollaa pienempi, käytetään kartan asetuksissa määriteltyä arvoa.
   * @type Number
   * @default -1
   */
  botCount: -1,
  /**
   * Minkä pelaajamäärän jälkeen (pelaajat + botit) botteja lähtee itsestään pois. Jos tämä arvo on
   * pienempi kuin botCount-arvo, niin tämä asetetaan samaksi botCountin kanssa. Jos tämä on 0, niin
   * botit eivät poistu automaattisesti koskaan. Jos taas tämä on nollaa pienempi, käytetään kartan
   * asetuksissa määriteltyä arvoa.
   * @type Number
   * @default -1
   */
  botDepartLimit: -1,
  /**
   * Bottien käytössä olevat aseet. Jos tämä on tyhjä, käytetään kartan asetuksissa olevia aseita.
   * Jos niitäkään ei ole, on käytössä kaikki aseet.
   *  - 1 = Pistooli
   *  - 2 = Konepistooli
   *  - 3 = Sinko
   *  - 4 = Haulikko
   *  - 5 = Kranaatinlaukaisin
   *  - 6 = Moottorisaha
   * @type Array
   * @default []
   */
  botWeapons: [],
  /**
   * Pelimoodi: DM = 1, TDM = 2, Zombiemode = 3
   * @type Byte
   * @default 1
   */
  gameMode: 1,
  /**
   * Kuinka pitkään yksi kierros kestää. Aika sekunneissa. 0 tarkoittaa ettei erä pääty koskaan.
   * @type Number
   * @default 300
   */
  periodLength: 300,
  /**
   * Kuinka pitkän ajan pelaajilla on suoja spawnauksen jälkeen. Aika millisekunteissa
   * @type Number
   * @default 3000
   */
  spawnProtection: 3000,
  /**
   * Kuinka kauan kestää (millisekunteina) että pelaajat heräävät henkiin uudelleen
   * @type Number
   * @default 3000
   */
  deathDelay: 3000,
  /**
   * Kuinka pitkän ajan jälkeen (millisekunteina) pelaaja poistetaan pelistä, kun hänestä ei ole
   * kuulunut mitään
   * @type Number
   * @default 10000
   */
  maxInactiveTime: 10000,
  /**
   * Näytetäänkö palvelimen konsolissa tappoviestit
   * @type Boolean
   * @default false
   */
  logKillMessages: false,
  /**
   * Ovatko tutkanuolet käytössä vai ei
   * @type Boolean
   * @default true
   */
  radarArrows: true,
  /**
   * Palvelimen pelimoottorin päivitystahti, kuinka monta päivitystä per sekunti tehdään.
   * @type Number
   * @default 60
   */
  updatesPerSec: 60,
  /**
   * Palvelimen salasana. Jos tätä ei aseteta, pelaaja voi saada admin-oikeudet vain palvelimen
   * konsolikomennon op kautta.
   * @type String
   * @default ""
   */
  password: "",
  /**
   * Onko palvelin kehitysversio
   * @type Boolean
   * @default false
   */
  devBuild: false
};

/**
 * Lataa asetukset annetusta tiedostosta. Tämä metodi hakee tiedostoa node-NetMatchin juuresta.
 *
 * @param {String} [config="config"]  Asetustiedoston nimi, ilman .json päätettä.
 *
 * @returns {Boolean}  Pystytäänkö peliä jatkamaan nykyisillä asetuksilla
 */
Config.prototype.load = function (config) {
  if (config.substr(-5) !== '.json') {
    config += '.json';
  }
  var filePath = path.resolve(__dirname, '..', config)
    , loadedConfig
    , i, mapPath;

  if (!path.existsSync(filePath)) {
    log.error('Config %0 doesn\'t exist', filePath.green);
    log.error('Use `%0` to load your own config', 'npm set NetMatch:config /path/to/your/config.json'.yellow);
    return true;
  }

  log.info('Loading config from %0', filePath.green);
  loadedConfig = cjson.load(filePath);
  // Laajennetaan tämän Configin ominaisuuksia ladatulla json-tiedostolla
  cjson.extend(this, loadedConfig);

  // Tarkistetaan onko maksimimäärä liian suuri ja kaadetaan palvelin mikäli on
  if (this.maxPlayers > 64) {
    log.fatal('maxPlayers set to %0 while the hardcoded maximum is %1!', this.maxPlayers, 64);
    return false;
  }

  // Tarkistetaan onko kartta merkkijono. Jos se on merkkijono, niin muutetaan se listaksi jossa
  // on vain yksi arvo.
  if ('string' === typeof this.map) {
    this.map = [this.map];
  }

  // Tarkistetaan että kartta todellakin on lista tässä vaiheessa
  if (!Array.isArray(this.map)) {
    log.fatal('Invalid value for "map" in config %0', filePath.green);
    return false;
  }

  // Tarkistetaan onko määritellyt kartat olemassa
  for (i = 0; i < this.map.length; i++) {
    mapPath = path.resolve(__dirname, '..', 'maps', this.map[i] + '.json');

    if (!path.existsSync(mapPath)) {
      log.fatal('Map %0 doesn\'t exist in %1', this.map[i].green, mapPath.green);
      return false;
    }
  }

  if (this.mapDownloadUrl[this.mapDownloadUrl.length - 1] !== '/') {
    this.mapDownloadUrl = this.mapDownloadUrl + '/';
  }

  return true;
};

module.exports = Config;
<commit_msg>Error out better if port is set in config but it isn't a number<commit_after>/**
 * @fileOverview Pitää sisällään asetuksiin liittyvät toiminnot ja oletusasetusten nimiavaruuden.
 * Kts. {@link Config} ja {@link Config.defaults}
 */

"use strict";

/**#nocode+*/
var log = require('./Utils').log
  , cjson = require('cjson')
  , path = require('path')
  , colors = require('colors');
/**#nocode-*/

/**
 * Alustaa luokan käyttöä varten, lataa {@link Config.defaults} kentät tämän instanssin kentiksi
 * @class Asetukset
 *
 * @param {Server} server  NetMatch-palvelin, joka kutsuu tätä konstruktoria
 */
function Config(server) {
  this.server = server;

  // Laajennetaan tämän Configin ominaisuuksia oletusconfigeilla
  cjson.extend(this, Config.defaults);
}

/**
 * @namespace Sisältää NetMatch-palvelimen oletusasetukset. Näitä voi muuttaa antamalla
 * {@link Server}-konstruktorille parametrina objektin, jonka avaimet sopivat tämän muotoon.
 */
Config.defaults = {
  /**
   * GSS-palvelimen osoite
   * @type String
   * @default "http://tuhoojabotti.com/nm"
   */
  regHost: "http://tuhoojabotti.com/nm",
  /**
   * GSS-palvelimella oleva polku gss.php tiedostoon
   * @type String
   * @default "/reg/gss.php"
   */
  regPath: "/reg/gss.php",
  /**
   * Rekisteröidäänkö palvelin
   * @type Boolean
   * @default true
   */
  register: true,
  /**
   * Osoite, josta pelaaja voi ladata puuttuvan kartan
   * @type String
   * @default "http://netmatch.vesq.org/maps/"
   */
  mapDownloadUrl: "http://netmatch.vesq.org/maps/",
  /**
   * Palvelimen kuvaus, näkyy listauksessa
   * @type String
   * @default "Node.js powered server"
   */
  description: "Node.js powered server",
  /**
   * Pelattava kartta. Voi olla merkkijono, jolloin pelataan vain yhtä karttaa, tai taulukko,
   * joka sisältää useamman kartan ja niitä kierrätetään erien välillä. Kaikkien karttojen tulee
   * olla olemassa kun palvelin käynnistetään.
   * @type String|Array
   * @default ["Luna", "Warehouse", "Mictlan"]
   */
  map: ["Luna", "Warehouse", "Mictlan"],
  /**
   * Maksimimäärä pelaajia. Suurin mahdollinen maksimimäärä on 64.
   * @type Number
   * @default 10
   */
  maxPlayers: 10,
  /**
   * Bottien määrä. Jos tämä on nollaa pienempi, käytetään kartan asetuksissa määriteltyä arvoa.
   * @type Number
   * @default -1
   */
  botCount: -1,
  /**
   * Minkä pelaajamäärän jälkeen (pelaajat + botit) botteja lähtee itsestään pois. Jos tämä arvo on
   * pienempi kuin botCount-arvo, niin tämä asetetaan samaksi botCountin kanssa. Jos tämä on 0, niin
   * botit eivät poistu automaattisesti koskaan. Jos taas tämä on nollaa pienempi, käytetään kartan
   * asetuksissa määriteltyä arvoa.
   * @type Number
   * @default -1
   */
  botDepartLimit: -1,
  /**
   * Bottien käytössä olevat aseet. Jos tämä on tyhjä, käytetään kartan asetuksissa olevia aseita.
   * Jos niitäkään ei ole, on käytössä kaikki aseet.
   *  - 1 = Pistooli
   *  - 2 = Konepistooli
   *  - 3 = Sinko
   *  - 4 = Haulikko
   *  - 5 = Kranaatinlaukaisin
   *  - 6 = Moottorisaha
   * @type Array
   * @default []
   */
  botWeapons: [],
  /**
   * Pelimoodi: DM = 1, TDM = 2, Zombiemode = 3
   * @type Byte
   * @default 1
   */
  gameMode: 1,
  /**
   * Kuinka pitkään yksi kierros kestää. Aika sekunneissa. 0 tarkoittaa ettei erä pääty koskaan.
   * @type Number
   * @default 300
   */
  periodLength: 300,
  /**
   * Kuinka pitkän ajan pelaajilla on suoja spawnauksen jälkeen. Aika millisekunteissa
   * @type Number
   * @default 3000
   */
  spawnProtection: 3000,
  /**
   * Kuinka kauan kestää (millisekunteina) että pelaajat heräävät henkiin uudelleen
   * @type Number
   * @default 3000
   */
  deathDelay: 3000,
  /**
   * Kuinka pitkän ajan jälkeen (millisekunteina) pelaaja poistetaan pelistä, kun hänestä ei ole
   * kuulunut mitään
   * @type Number
   * @default 10000
   */
  maxInactiveTime: 10000,
  /**
   * Näytetäänkö palvelimen konsolissa tappoviestit
   * @type Boolean
   * @default false
   */
  logKillMessages: false,
  /**
   * Ovatko tutkanuolet käytössä vai ei
   * @type Boolean
   * @default true
   */
  radarArrows: true,
  /**
   * Palvelimen pelimoottorin päivitystahti, kuinka monta päivitystä per sekunti tehdään.
   * @type Number
   * @default 60
   */
  updatesPerSec: 60,
  /**
   * Palvelimen salasana. Jos tätä ei aseteta, pelaaja voi saada admin-oikeudet vain palvelimen
   * konsolikomennon op kautta.
   * @type String
   * @default ""
   */
  password: "",
  /**
   * Onko palvelin kehitysversio
   * @type Boolean
   * @default false
   */
  devBuild: false
};

/**
 * Lataa asetukset annetusta tiedostosta. Tämä metodi hakee tiedostoa node-NetMatchin juuresta.
 *
 * @param {String} [config="config"]  Asetustiedoston nimi, ilman .json päätettä.
 *
 * @returns {Boolean}  Pystytäänkö peliä jatkamaan nykyisillä asetuksilla
 */
Config.prototype.load = function (config) {
  if (config.substr(-5) !== '.json') {
    config += '.json';
  }
  var filePath = path.resolve(__dirname, '..', config)
    , loadedConfig
    , i, mapPath;

  if (!path.existsSync(filePath)) {
    log.error('Config %0 doesn\'t exist', filePath.green);
    log.error('Use `%0` to load your own config', 'npm set NetMatch:config /path/to/your/config.json'.yellow);
    return true;
  }

  log.info('Loading config from %0', filePath.green);
  loadedConfig = cjson.load(filePath);
  // Laajennetaan tämän Configin ominaisuuksia ladatulla json-tiedostolla
  cjson.extend(this, loadedConfig);

  // Tarkistetaan onko maksimimäärä liian suuri ja kaadetaan palvelin mikäli on
  if (this.maxPlayers > 64) {
    log.fatal('maxPlayers set to %0 while the hardcoded maximum is %1!', this.maxPlayers, 64);
    return false;
  }

  // Tarkistetaan onko kartta merkkijono. Jos se on merkkijono, niin muutetaan se listaksi jossa
  // on vain yksi arvo.
  if ('string' === typeof this.map) {
    this.map = [this.map];
  }

  // Tarkistetaan että kartta todellakin on lista tässä vaiheessa
  if (!Array.isArray(this.map)) {
    log.fatal('Invalid value for "map" in config %0', filePath.green);
    return false;
  }

  // Tarkistetaan onko määritellyt kartat olemassa
  for (i = 0; i < this.map.length; i++) {
    mapPath = path.resolve(__dirname, '..', 'maps', this.map[i] + '.json');

    if (!path.existsSync(mapPath)) {
      log.fatal('Map %0 doesn\'t exist in %1', this.map[i].green, mapPath.green);
      return false;
    }
  }

  if (this.mapDownloadUrl[this.mapDownloadUrl.length - 1] !== '/') {
    this.mapDownloadUrl = this.mapDownloadUrl + '/';
  }

  // Tarkistetaan että portti on numero
  if (this.port && 'number' !== typeof this.port) {
    log.fatal('You must set %0 to be a number in %1!', 'port'.yellow, filePath.green);
    return false;
  }

  return true;
};

module.exports = Config;
<|endoftext|>


<commit_before>// Copyright (c) 2011-2012 UX Productivity Pty Ltd. All rights reserved.

(function() {

    // public
    function ensureCursorVisible()
    {
        var rect = Selection.getCursorRect();
        if (rect != null) {
            var cursorY = rect.top + window.scrollY + rect.height/2;
            if ((cursorY < window.scrollY) ||
                (cursorY > window.scrollY + window.innerHeight)) {
                var newScrollY = cursorY - window.innerHeight/2;
                if (newScrollY < 0)
                    newScrollY = 0;
                window.scrollTo(window.scrollX,newScrollY);
            }
        }
    }

    // public
    function isValidCursorPosition(pos)
    {
        var result = false;

        var node = pos.node;
        var offset = pos.offset;

        if (isOpaqueNode(node))
            return false;

        if (node.nodeType == Node.TEXT_NODE) {
            var value = node.nodeValue;
            var prev = node.previousSibling;
            var next = node.nextSibling;

            // Immediately after a non-whitespace character -> YES
            if ((offset > 0) && !isWhitespaceString(value.charAt(offset-1)))
                result = true;

            // At the end of a text node (with no next sibling), and the preceding character
            // is a whitespace character, but there is a non-whitespace character before it -> YES
            if (isWhitespaceString(value.slice(offset)) &&
                (offset >= 2) &&
                isWhitespaceString(value.charAt(offset-1)) &&
                !isWhitespaceString(value.charAt(offset-2)) &&
                (node.nextSibling == null)) {
                result = true;
            }

            // Immediately before a non-whitespace character -> YES
            if ((offset < value.length) && !isWhitespaceString(value.charAt(offset)))
                result = true;

            // Right at the end of a whitespace node which is the first child in a paragraph,
            // and has no next sibling, or is followed by a BR -> YES
            if ((offset == value.length) &&
                !isInlineNode(node.parentNode) &&
                isWhitespaceTextNode(node) &&
                (prev == null) &&
                ((next == null) || (DOM.upperName(next) == "BR")))
                result = true;
        }
        else if (node.nodeType == Node.ELEMENT_NODE) {
            var prev = (offset == 0) ? null : node.childNodes[offset-1];
            var next = (offset >= node.childNodes.length) ? null : node.childNodes[offset];

            // Directly after an IMG, TABLE, UL, or OL -> YES
            if ((prev != null) &&
                ((DOM.upperName(prev) == "IMG") ||
                 (DOM.upperName(prev) == "TABLE") ||
                 (DOM.upperName(prev) == "UL") ||
                 (DOM.upperName(prev) == "OL")))
                result = true;

            // Directly before an IMG, TABLE, UL, or OL -> YES
            if ((next != null) &&
                ((DOM.upperName(next) == "IMG") ||
                 (DOM.upperName(next) == "TABLE") ||
                 (DOM.upperName(next) == "UL") ||
                 (DOM.upperName(next) == "OL")))
                result = true;

            // In an empty paragraph or one that only contains a BR
            if ((prev == null) && (next != null) && (DOM.upperName(next) == "BR"))
                result = true;

            if ((prev == null) && (next == null) &&
                (isParagraphNode(node) ||
                 INLINE_ELEMENTS_THAT_CAN_HAVE_CHILDREN[DOM.upperName(node)]))
                result = true;

            // Special case for an IMG that directly follows some text that ends in a
            // non-whitespace character. The cursor will be allowed at the end of the text
            // node, so we don't want to allow it before the image (which corresponds to the
            // same location on screen)
            if ((next != null) && (prev != null) &&
                (DOM.upperName(next) == "IMG") &&
                (prev.nodeType == Node.TEXT_NODE) &&
                (prev.nodeValue.length > 0) &&
                !isWhitespaceString(prev.nodeValue.charAt(prev.nodeValue.length-1))) {
                result = false;
            }

            // As above, but for an IMG that directly precedes some text
            if ((prev != null) && (next != null) &&
                (DOM.upperName(prev) == "IMG") &&
                (next.nodeType == Node.TEXT_NODE) &&
                (next.nodeValue.length > 0) &&
                !isWhitespaceString(next.nodeValue.charAt(0))) {
                result = false;
            }
        }

        return result;
    }

    // public
    function positionCursor(x,y)
    {
        var zoom = Viewport.getZoom();
        var position = positionAtPoint(x/zoom,y/zoom);
        if ((position != null) && isOpaqueNode(position.node))
            position = nextCursorPosition(position);
        if (position == null)
            return false;

        var selectionRange = Selection.getSelectionRange();
        var samePosition = ((selectionRange != null) && selectionRange.isEmpty() &&
                            (position.node == selectionRange.start.node) &&
                            (position.offset == selectionRange.start.offset));
        Selection.setEmptySelectionAt(position.node,position.offset);
        ensureCursorVisible();
        return samePosition;
    }

    // public
    function getCursorPosition()
    {
        var rect = Selection.getCursorRect();
        if (rect == null)
            return null;

        var zoom = Viewport.getZoom();
        var left = (rect.left + window.scrollX) * zoom;
        var top = (rect.top + window.scrollY) * zoom;
        var height = rect.height * zoom;
        return { x: left, y: top, width: 0, height: height };
    }

    function prevCursorPosition(pos)
    {
        do {
            pos = pos.prev();
        } while ((pos != null) && !isValidCursorPosition(pos));
        return pos;
    }

    function nextCursorPosition(pos)
    {
        do {
            pos = pos.next();
        } while ((pos != null) && !isValidCursorPosition(pos));
        return pos;
    }

    // public
    function moveLeft()
    {
        var selectionRange = Selection.getSelectionRange();
        if (selectionRange == null)
            return;

        var pos = prevCursorPosition(selectionRange.start);

        if (pos != null) {
            Selection.setEmptySelectionAt(pos.node,pos.offset);
            ensureCursorVisible();
        }
    }

    // public
    function moveRight()
    {
        var selectionRange = Selection.getSelectionRange();
        if (selectionRange == null)
            return;

        var pos = nextCursorPosition(selectionRange.start);

        if (pos != null) {
            Selection.setEmptySelectionAt(pos.node,pos.offset);
            ensureCursorVisible();
        }
    }

    function moveToStartOfDocument()
    {
        var pos = new Position(document.body,0);
        pos = closestPositionBackwards(pos);
        Selection.setEmptySelectionAt(pos.node,pos.offset);
        ensureCursorVisible();
    }

    function moveToEndOfDocument()
    {
        var pos = new Position(document.body,document.body.childNodes.length);
        pos = closestPositionForwards(pos);
        Selection.setEmptySelectionAt(pos.node,pos.offset);
        ensureCursorVisible();
    }

    // An empty paragraph does not get shown and cannot be edited. We can fix this by adding
    // a BR element as a child
    // public
    function updateBRAtEndOfParagraph(node)
    {
        var paragraph = node;
        while ((paragraph != null) && !isParagraphNode(paragraph))
            paragraph = paragraph.parentNode;
        if (paragraph != null) {

            var br = null;
            for (var last = paragraph; last != null; last = last.lastChild) {

                var child = last;
                while ((child != null) && isWhitespaceTextNode(child))
                    child = child.previousSibling;

                if ((child != null) && (DOM.upperName(child) == "BR"))
                    br = child;
            }

            if (nodeHasContent(paragraph)) {
                // Paragraph has content: don't want BR at end
                if (br != null) {
                    DOM.deleteNode(br);
                }
            }
            else {
                // Paragraph consists only of whitespace: must have BR at end
                if (br == null) {
                    br = DOM.createElement(document,"BR");
                    DOM.appendChild(paragraph,br);
                }
            }
        }
    }

    function tryAndFindEquivalentValidPosition(pos)
    {
        if (isValidCursorPosition(pos))
            return pos;

        if ((pos.node.nodeType == Node.TEXT_NODE) &&
            isWhitespaceString(pos.node.nodeValue.slice(pos.offset))) {
            var str = pos.node.nodeValue;
            var whitespace = str.match(/\s+$/);
            if (whitespace) {
                var adjusted = new Position(pos.node,
                                            str.length - whitespace[0].length + 1);
                return adjusted;
            }
        }
        return pos;
    }

    // public
    function closestPositionForwards(pos)
    {
        pos = tryAndFindEquivalentValidPosition(pos);

        if (isValidCursorPosition(pos))
            return pos;

        var next = nextCursorPosition(pos);
        if (next != null)
            return next;

        var prev = prevCursorPosition(pos);
        if (prev != null)
            return prev;

        return new Position(document.body,document.body.childNodes.length);
    }

    // public
    function closestPositionBackwards(pos)
    {
        pos = tryAndFindEquivalentValidPosition(pos);

        if (isValidCursorPosition(pos))
            return pos;

        var prev = prevCursorPosition(pos);
        if (prev != null)
            return prev;

        var next = nextCursorPosition(pos);
        if (next != null)
            return next;

        return new Position(document.body,0);
    }

    // public
    function insertReference(itemId)
    {
        var a = DOM.createElement(document,"A");
        a.setAttribute("href","#"+itemId);
        Clipboard.pasteNodes([a]);
    }

    // public
    function insertCharacter(character)
    {
        var selectionRange = Selection.getSelectionRange();
        if (selectionRange == null)
            return;

        if (!selectionRange.isEmpty())
            Selection.deleteSelectionContents();
        var pos = closestPositionForwards(selectionRange.start);
        var node = pos.node;
        var offset = pos.offset;

        if (node.nodeType == Node.ELEMENT_NODE) {
            var emptyTextNode = DOM.createTextNode(document,"");
            if (offset >= node.childNodes.length)
                DOM.appendChild(node,emptyTextNode);
            else
                DOM.insertBefore(node,emptyTextNode,node.childNodes[offset]);
            node = emptyTextNode;
            offset = 0;
        }

        DOM.insertCharacters(node,offset,character);
        Selection.setEmptySelectionAt(node,offset+1,node,offset+1);
        Selection.getSelectionRange().trackWhileExecuting(function() {
            updateBRAtEndOfParagraph(node);
        });
        ensureCursorVisible();
    }

    // public
    function deleteCharacter()
    {
        var selectionRange = Selection.getSelectionRange();
        if (selectionRange == null)
            return;

        if (!selectionRange.isEmpty()) {
            Selection.deleteSelectionContents();
            return;
        }
        else {
            var currentPos = selectionRange.start;
            var prevPos = prevCursorPosition(currentPos);
            if (prevPos != null) {
                selectionRange.start.node = prevPos.node;
                selectionRange.start.offset = prevPos.offset;
                Selection.deleteSelectionContents();
            }
        }
    }

    // public
    function enterPressed()
    {
        var selectionRange = Selection.getSelectionRange();
        if (selectionRange == null)
            return;

        selectionRange.trackWhileExecuting(function() {
            selectionRange.ensureRangeValidHierarchy();
            if (!selectionRange.isEmpty())
                Selection.deleteSelectionContents();
        });

        var pos = selectionRange.start;

        var detail = selectionRange.detail();
        if ((DOM.upperName(detail.startParent) == "OL") ||
            (DOM.upperName(detail.startParent) == "UL")) {
            var li = DOM.createElement(document,"LI");
            DOM.insertBefore(detail.startParent,li,detail.startChild);

            selectionRange.start.node = li;
            selectionRange.start.offset = 0;
            selectionRange.end.node = li;
            selectionRange.end.offset = 0;
            return;
        }

        selectionRange.trackWhileExecuting(function() {

            if (pos.node.nodeType == Node.TEXT_NODE)
                pos = Formatting.splitTextAfter(pos.node,pos.offset,enterPressedFilter,true);
            else
                pos = Formatting.moveFollowing(pos.node,pos.offset,enterPressedFilter,true);

            selectionRange.start.node = pos.node;
            selectionRange.start.offset = pos.offset;
            selectionRange.end.node = pos.node;
            selectionRange.end.offset = pos.offset;

            if ((pos.node.nodeType == Node.TEXT_NODE) && (pos.node.nodeValue.length == 0)) {
                DOM.deleteNode(pos.node);
            }

            var detail = selectionRange.detail();

            var start = detail.startChild ? detail.startChild : detail.startParent;
            for (var ancestor = start; ancestor != null; ancestor = ancestor.parentNode) {
                var prev = ancestor.previousSibling;
                if ((prev != null) && isParagraphNode(prev) && !nodeHasContent(prev)) {
                    DOM.deleteAllChildren(prev);
                    updateBRAtEndOfParagraph(prev);
                    break;
                }
                else if ((prev != null) && (DOM.upperName(prev) == "LI") && !nodeHasContent(prev)) {
                    DOM.deleteAllChildren(prev);
                    break;
                }
            }

            for (var ancestor = start; ancestor != null; ancestor = ancestor.parentNode) {
                if (isParagraphNode(ancestor) && isHeadingNode(ancestor)) {
                    ancestor = DOM.replaceElement(ancestor,"P");
                    ancestor.removeAttribute("id");
                }

                if (isParagraphNode(ancestor) && !nodeHasContent(ancestor)) {
                    updateBRAtEndOfParagraph(prev);
                    break;
                }
                else if ((DOM.upperName(ancestor) == "LI") && !nodeHasContent(ancestor)) {
                    DOM.deleteAllChildren(ancestor);
                    break;
                }
            }

            updateBRAtEndOfParagraph(selectionRange.singleNode());
        });

        Selection.setSelectionRange(selectionRange);
        ensureCursorVisible();

        function enterPressedFilter(node)
        {
            return (isContainerNode(node) && (DOM.upperName(node) != "LI"));
        }
    }

    window.Cursor = new Object();
    Cursor.ensureCursorVisible = ensureCursorVisible;
    Cursor.isValidCursorPosition = isValidCursorPosition;
    Cursor.positionCursor = positionCursor;
    Cursor.getCursorPosition = getCursorPosition;
    Cursor.moveLeft = moveLeft;
    Cursor.moveRight = moveRight;
    Cursor.moveToStartOfDocument = moveToStartOfDocument;
    Cursor.moveToEndOfDocument = moveToEndOfDocument;
    Cursor.updateBRAtEndOfParagraph = updateBRAtEndOfParagraph;
    Cursor.closestPositionForwards = closestPositionForwards;
    Cursor.closestPositionBackwards = closestPositionBackwards;
    Cursor.insertReference = insertReference;
    Cursor.insertCharacter = insertCharacter;
    Cursor.deleteCharacter = deleteCharacter;
    Cursor.enterPressed = enterPressed;

})();
<commit_msg>Intead of inserting one character at a time using Cursor.insertCharacter(), maintain an "insertion string" which represents the latest piece of text that the user has typed. This is maintained in a UITextField, which allows us to use the built-in autocorrect mechanism. Now, instead of calling insertCharacter(), we:<commit_after>// Copyright (c) 2011-2012 UX Productivity Pty Ltd. All rights reserved.

(function() {

    var insertionNode = null;
    var insertionTextBefore = null;
    var insertionTextAfter = null;

    // public
    function ensureCursorVisible()
    {
        var rect = Selection.getCursorRect();
        if (rect != null) {
            var cursorY = rect.top + window.scrollY + rect.height/2;
            if ((cursorY < window.scrollY) ||
                (cursorY > window.scrollY + window.innerHeight)) {
                var newScrollY = cursorY - window.innerHeight/2;
                if (newScrollY < 0)
                    newScrollY = 0;
                window.scrollTo(window.scrollX,newScrollY);
            }
        }
    }

    // public
    function isValidCursorPosition(pos)
    {
        var result = false;

        var node = pos.node;
        var offset = pos.offset;

        if (isOpaqueNode(node))
            return false;

        if (node.nodeType == Node.TEXT_NODE) {
            var value = node.nodeValue;
            var prev = node.previousSibling;
            var next = node.nextSibling;

            // Immediately after a non-whitespace character -> YES
            if ((offset > 0) && !isWhitespaceString(value.charAt(offset-1)))
                result = true;

            // At the end of a text node (with no next sibling), and the preceding character
            // is a whitespace character, but there is a non-whitespace character before it -> YES
            if (isWhitespaceString(value.slice(offset)) &&
                (offset >= 2) &&
                isWhitespaceString(value.charAt(offset-1)) &&
                !isWhitespaceString(value.charAt(offset-2)) &&
                (node.nextSibling == null)) {
                result = true;
            }

            // Immediately before a non-whitespace character -> YES
            if ((offset < value.length) && !isWhitespaceString(value.charAt(offset)))
                result = true;

            // Right at the end of a whitespace node which is the first child in a paragraph,
            // and has no next sibling, or is followed by a BR -> YES
            if ((offset == value.length) &&
                !isInlineNode(node.parentNode) &&
                isWhitespaceTextNode(node) &&
                (prev == null) &&
                ((next == null) || (DOM.upperName(next) == "BR")))
                result = true;
        }
        else if (node.nodeType == Node.ELEMENT_NODE) {
            var prev = (offset == 0) ? null : node.childNodes[offset-1];
            var next = (offset >= node.childNodes.length) ? null : node.childNodes[offset];

            // Directly after an IMG, TABLE, UL, or OL -> YES
            if ((prev != null) &&
                ((DOM.upperName(prev) == "IMG") ||
                 (DOM.upperName(prev) == "TABLE") ||
                 (DOM.upperName(prev) == "UL") ||
                 (DOM.upperName(prev) == "OL")))
                result = true;

            // Directly before an IMG, TABLE, UL, or OL -> YES
            if ((next != null) &&
                ((DOM.upperName(next) == "IMG") ||
                 (DOM.upperName(next) == "TABLE") ||
                 (DOM.upperName(next) == "UL") ||
                 (DOM.upperName(next) == "OL")))
                result = true;

            // In an empty paragraph or one that only contains a BR
            if ((prev == null) && (next != null) && (DOM.upperName(next) == "BR"))
                result = true;

            if ((prev == null) && (next == null) &&
                (isParagraphNode(node) ||
                 INLINE_ELEMENTS_THAT_CAN_HAVE_CHILDREN[DOM.upperName(node)]))
                result = true;

            // Special case for an IMG that directly follows some text that ends in a
            // non-whitespace character. The cursor will be allowed at the end of the text
            // node, so we don't want to allow it before the image (which corresponds to the
            // same location on screen)
            if ((next != null) && (prev != null) &&
                (DOM.upperName(next) == "IMG") &&
                (prev.nodeType == Node.TEXT_NODE) &&
                (prev.nodeValue.length > 0) &&
                !isWhitespaceString(prev.nodeValue.charAt(prev.nodeValue.length-1))) {
                result = false;
            }

            // As above, but for an IMG that directly precedes some text
            if ((prev != null) && (next != null) &&
                (DOM.upperName(prev) == "IMG") &&
                (next.nodeType == Node.TEXT_NODE) &&
                (next.nodeValue.length > 0) &&
                !isWhitespaceString(next.nodeValue.charAt(0))) {
                result = false;
            }
        }

        return result;
    }

    // public
    function positionCursor(x,y)
    {
        var zoom = Viewport.getZoom();
        var position = positionAtPoint(x/zoom,y/zoom);
        if ((position != null) && isOpaqueNode(position.node))
            position = nextCursorPosition(position);
        if (position == null)
            return false;

        var selectionRange = Selection.getSelectionRange();
        var samePosition = ((selectionRange != null) && selectionRange.isEmpty() &&
                            (position.node == selectionRange.start.node) &&
                            (position.offset == selectionRange.start.offset));
        Selection.setEmptySelectionAt(position.node,position.offset);
        ensureCursorVisible();
        return samePosition;
    }

    // public
    function getCursorPosition()
    {
        var rect = Selection.getCursorRect();
        if (rect == null)
            return null;

        var zoom = Viewport.getZoom();
        var left = (rect.left + window.scrollX) * zoom;
        var top = (rect.top + window.scrollY) * zoom;
        var height = rect.height * zoom;
        return { x: left, y: top, width: 0, height: height };
    }

    function prevCursorPosition(pos)
    {
        do {
            pos = pos.prev();
        } while ((pos != null) && !isValidCursorPosition(pos));
        return pos;
    }

    function nextCursorPosition(pos)
    {
        do {
            pos = pos.next();
        } while ((pos != null) && !isValidCursorPosition(pos));
        return pos;
    }

    // public
    function moveLeft()
    {
        var selectionRange = Selection.getSelectionRange();
        if (selectionRange == null)
            return;

        var pos = prevCursorPosition(selectionRange.start);

        if (pos != null) {
            Selection.setEmptySelectionAt(pos.node,pos.offset);
            ensureCursorVisible();
        }
    }

    // public
    function moveRight()
    {
        var selectionRange = Selection.getSelectionRange();
        if (selectionRange == null)
            return;

        var pos = nextCursorPosition(selectionRange.start);

        if (pos != null) {
            Selection.setEmptySelectionAt(pos.node,pos.offset);
            ensureCursorVisible();
        }
    }

    function moveToStartOfDocument()
    {
        var pos = new Position(document.body,0);
        pos = closestPositionBackwards(pos);
        Selection.setEmptySelectionAt(pos.node,pos.offset);
        ensureCursorVisible();
    }

    function moveToEndOfDocument()
    {
        var pos = new Position(document.body,document.body.childNodes.length);
        pos = closestPositionForwards(pos);
        Selection.setEmptySelectionAt(pos.node,pos.offset);
        ensureCursorVisible();
    }

    // An empty paragraph does not get shown and cannot be edited. We can fix this by adding
    // a BR element as a child
    // public
    function updateBRAtEndOfParagraph(node)
    {
        var paragraph = node;
        while ((paragraph != null) && !isParagraphNode(paragraph))
            paragraph = paragraph.parentNode;
        if (paragraph != null) {

            var br = null;
            for (var last = paragraph; last != null; last = last.lastChild) {

                var child = last;
                while ((child != null) && isWhitespaceTextNode(child))
                    child = child.previousSibling;

                if ((child != null) && (DOM.upperName(child) == "BR"))
                    br = child;
            }

            if (nodeHasContent(paragraph)) {
                // Paragraph has content: don't want BR at end
                if (br != null) {
                    DOM.deleteNode(br);
                }
            }
            else {
                // Paragraph consists only of whitespace: must have BR at end
                if (br == null) {
                    br = DOM.createElement(document,"BR");
                    DOM.appendChild(paragraph,br);
                }
            }
        }
    }

    function tryAndFindEquivalentValidPosition(pos)
    {
        if (isValidCursorPosition(pos))
            return pos;

        if ((pos.node.nodeType == Node.TEXT_NODE) &&
            isWhitespaceString(pos.node.nodeValue.slice(pos.offset))) {
            var str = pos.node.nodeValue;
            var whitespace = str.match(/\s+$/);
            if (whitespace) {
                var adjusted = new Position(pos.node,
                                            str.length - whitespace[0].length + 1);
                return adjusted;
            }
        }
        return pos;
    }

    // public
    function closestPositionForwards(pos)
    {
        pos = tryAndFindEquivalentValidPosition(pos);

        if (isValidCursorPosition(pos))
            return pos;

        var next = nextCursorPosition(pos);
        if (next != null)
            return next;

        var prev = prevCursorPosition(pos);
        if (prev != null)
            return prev;

        return new Position(document.body,document.body.childNodes.length);
    }

    // public
    function closestPositionBackwards(pos)
    {
        pos = tryAndFindEquivalentValidPosition(pos);

        if (isValidCursorPosition(pos))
            return pos;

        var prev = prevCursorPosition(pos);
        if (prev != null)
            return prev;

        var next = nextCursorPosition(pos);
        if (next != null)
            return next;

        return new Position(document.body,0);
    }

    // public
    function insertReference(itemId)
    {
        var a = DOM.createElement(document,"A");
        a.setAttribute("href","#"+itemId);
        Clipboard.pasteNodes([a]);
    }

    // public
    function insertCharacter(character)
    {
        var selectionRange = Selection.getSelectionRange();
        if (selectionRange == null)
            return;

        if (!selectionRange.isEmpty())
            Selection.deleteSelectionContents();
        var pos = closestPositionForwards(selectionRange.start);
        var node = pos.node;
        var offset = pos.offset;

        if (node.nodeType == Node.ELEMENT_NODE) {
            var emptyTextNode = DOM.createTextNode(document,"");
            if (offset >= node.childNodes.length)
                DOM.appendChild(node,emptyTextNode);
            else
                DOM.insertBefore(node,emptyTextNode,node.childNodes[offset]);
            node = emptyTextNode;
            offset = 0;
        }

        DOM.insertCharacters(node,offset,character);
        Selection.setEmptySelectionAt(node,offset+1,node,offset+1);
        Selection.getSelectionRange().trackWhileExecuting(function() {
            updateBRAtEndOfParagraph(node);
        });
        ensureCursorVisible();
    }

    // public
    function beginInsertion()
    {
        var selectionRange = Selection.getSelectionRange();
        if (selectionRange == null)
            return;

        if (!selectionRange.isEmpty())
            Selection.deleteSelectionContents();
        var pos = closestPositionForwards(selectionRange.start);
        var node = pos.node;
        var offset = pos.offset;

        if (node.nodeType == Node.ELEMENT_NODE) {
            var emptyTextNode = DOM.createTextNode(document,"");
            if (offset >= node.childNodes.length)
                DOM.appendChild(node,emptyTextNode);
            else
                DOM.insertBefore(node,emptyTextNode,node.childNodes[offset]);
            node = emptyTextNode;
            offset = 0;
        }

        insertionNode = node;
        insertionTextBefore = insertionNode.nodeValue.slice(0,offset);
        insertionTextAfter = insertionNode.nodeValue.slice(offset);

        Selection.setEmptySelectionAt(node,offset,node,offset);
        Selection.getSelectionRange().trackWhileExecuting(function() {
            updateBRAtEndOfParagraph(node);
        });
        ensureCursorVisible();
    }

    // public
    function updateInsertion(str)
    {
        DOM.setNodeValue(insertionNode,insertionTextBefore+str+insertionTextAfter);

        var node = insertionNode;
        var offset = (insertionTextBefore+str).length;
        Selection.setEmptySelectionAt(node,offset,node,offset);
        Selection.getSelectionRange().trackWhileExecuting(function() {
            updateBRAtEndOfParagraph(node);
        });
        ensureCursorVisible();
    }

    // public
    function endInsertion(str)
    {
        insertionNode = null;
        insertionTextBefore = null;
        insertionTextAfter = null;
    }

    // public
    function deleteCharacter()
    {
        var selectionRange = Selection.getSelectionRange();
        if (selectionRange == null)
            return;

        if (!selectionRange.isEmpty()) {
            Selection.deleteSelectionContents();
            return;
        }
        else {
            var currentPos = selectionRange.start;
            var prevPos = prevCursorPosition(currentPos);
            if (prevPos != null) {
                selectionRange.start.node = prevPos.node;
                selectionRange.start.offset = prevPos.offset;
                Selection.deleteSelectionContents();
            }
        }
    }

    // public
    function enterPressed()
    {
        var selectionRange = Selection.getSelectionRange();
        if (selectionRange == null)
            return;

        selectionRange.trackWhileExecuting(function() {
            selectionRange.ensureRangeValidHierarchy();
            if (!selectionRange.isEmpty())
                Selection.deleteSelectionContents();
        });

        var pos = selectionRange.start;

        var detail = selectionRange.detail();
        if ((DOM.upperName(detail.startParent) == "OL") ||
            (DOM.upperName(detail.startParent) == "UL")) {
            var li = DOM.createElement(document,"LI");
            DOM.insertBefore(detail.startParent,li,detail.startChild);

            selectionRange.start.node = li;
            selectionRange.start.offset = 0;
            selectionRange.end.node = li;
            selectionRange.end.offset = 0;
            return;
        }

        selectionRange.trackWhileExecuting(function() {

            if (pos.node.nodeType == Node.TEXT_NODE)
                pos = Formatting.splitTextAfter(pos.node,pos.offset,enterPressedFilter,true);
            else
                pos = Formatting.moveFollowing(pos.node,pos.offset,enterPressedFilter,true);

            selectionRange.start.node = pos.node;
            selectionRange.start.offset = pos.offset;
            selectionRange.end.node = pos.node;
            selectionRange.end.offset = pos.offset;

            if ((pos.node.nodeType == Node.TEXT_NODE) && (pos.node.nodeValue.length == 0)) {
                DOM.deleteNode(pos.node);
            }

            var detail = selectionRange.detail();

            var start = detail.startChild ? detail.startChild : detail.startParent;
            for (var ancestor = start; ancestor != null; ancestor = ancestor.parentNode) {
                var prev = ancestor.previousSibling;
                if ((prev != null) && isParagraphNode(prev) && !nodeHasContent(prev)) {
                    DOM.deleteAllChildren(prev);
                    updateBRAtEndOfParagraph(prev);
                    break;
                }
                else if ((prev != null) && (DOM.upperName(prev) == "LI") && !nodeHasContent(prev)) {
                    DOM.deleteAllChildren(prev);
                    break;
                }
            }

            for (var ancestor = start; ancestor != null; ancestor = ancestor.parentNode) {
                if (isParagraphNode(ancestor) && isHeadingNode(ancestor)) {
                    ancestor = DOM.replaceElement(ancestor,"P");
                    ancestor.removeAttribute("id");
                }

                if (isParagraphNode(ancestor) && !nodeHasContent(ancestor)) {
                    updateBRAtEndOfParagraph(prev);
                    break;
                }
                else if ((DOM.upperName(ancestor) == "LI") && !nodeHasContent(ancestor)) {
                    DOM.deleteAllChildren(ancestor);
                    break;
                }
            }

            updateBRAtEndOfParagraph(selectionRange.singleNode());
        });

        Selection.setSelectionRange(selectionRange);
        ensureCursorVisible();

        function enterPressedFilter(node)
        {
            return (isContainerNode(node) && (DOM.upperName(node) != "LI"));
        }
    }

    window.Cursor = new Object();
    Cursor.ensureCursorVisible = ensureCursorVisible;
    Cursor.isValidCursorPosition = isValidCursorPosition;
    Cursor.positionCursor = positionCursor;
    Cursor.getCursorPosition = getCursorPosition;
    Cursor.moveLeft = moveLeft;
    Cursor.moveRight = moveRight;
    Cursor.moveToStartOfDocument = moveToStartOfDocument;
    Cursor.moveToEndOfDocument = moveToEndOfDocument;
    Cursor.updateBRAtEndOfParagraph = updateBRAtEndOfParagraph;
    Cursor.closestPositionForwards = closestPositionForwards;
    Cursor.closestPositionBackwards = closestPositionBackwards;
    Cursor.insertReference = insertReference;
    Cursor.insertCharacter = insertCharacter;
    Cursor.beginInsertion = beginInsertion;
    Cursor.updateInsertion = updateInsertion;
    Cursor.endInsertion = endInsertion;
    Cursor.deleteCharacter = deleteCharacter;
    Cursor.enterPressed = enterPressed;

})();
<|endoftext|>