change indent spacing

change indent spacing

--- app/vis.coffee
+++ app/vis.coffee
@@ -6,553 +6,553 @@
 assert = require "assert"

 module.exports = class Main
-    SCALE_TEXT = 0.005
-
-    SPEED_MULTIPLIER = 0.5
-
-    CAMERA_PAN_DURATION = 2000 * SPEED_MULTIPLIER
-    FADE_DURATION = 3000 * SPEED_MULTIPLIER
-    # CHAIN_FADE_DELAY = 5000 * SPEED_MULTIPLIER
-
-    CAMERA_Z = -9
-
-    scaleText: SCALE_TEXT
-    speedMultiplier: SPEED_MULTIPLIER
-
-    constructor: ->
-        console.log "Starting Vis"
-
-        @renderer = new THREE.WebGLRenderer()
-        @renderer.setPixelRatio( window.devicePixelRatio )
-        @renderer.setSize( window.innerWidth, window.innerHeight )
-        @renderer.setClearColor( "rgb(255, 255, 255)" )
-
-        document.body.appendChild( @renderer.domElement )
-
-        @scene = new THREE.Scene()
-
-        fov = 70
-        aspect = window.innerWidth / window.innerHeight
-        [ near, far ] = [ 0.1, 1000 ]
-        @camera = new THREE.PerspectiveCamera( fov, aspect, near, far )
-
-        [_x, _y, _z] = [0, 0, CAMERA_Z]
-        @camera.position.z = _z
-        @camera.position.x = _x
-        @camera.position.y = _y
-        @camera.lookAt new THREE.Vector3(_x,_y,0)
-
-        onWindowResize = =>
-            @camera.aspect = window.innerWidth / window.innerHeight
-            @camera.updateProjectionMatrix()
-            @renderer.setSize( window.innerWidth, window.innerHeight )
-
-        window.addEventListener( 'resize', onWindowResize, false )
-
-        do animate = =>
-            requestAnimationFrame animate
-            @renderer.render @scene, @camera
-
-    addChain: (text) =>
-        chainVis = new ChainVis @scene, @camera, @font, @texture
-        chainVis.start text
-
-    addNetwork: (network) =>
-        colocationVis = new ColocationVis @scene, @camera, @font, @texture
-        colocationVis.start network
-
-    addLines: (lines) =>
-        linesVis = new LinesVis @scene, @camera, @font, @texture
-        linesVis.start lines
-
-    setTexture: (@texture) ->
-        maxAni = @renderer.getMaxAnisotropy()
-
-        @texture.needsUpdate = true
-        @texture.minFilter = THREE.LinearMipMapLinearFilter
-        @texture.magFilter = THREE.LinearFilter
-        @texture.generateMipmaps = true
-        @texture.anisotropy = maxAni
-
-    setFont: (@font) ->
-
-    getTextGeometry: (text) =>
-        createGeometry
-            text: text
-            font: @font
-
-    getTextMesh: (geometry) ->
-        material = new THREE.ShaderMaterial(Shader({
-            map: @texture,
-            smooth: 1/8,
-            side: THREE.DoubleSide,
-            transparent: true,
-            opacity: 0,
-            color: 'rgb(10, 10, 10)'
-        }))
-        mesh = new THREE.Mesh(geometry, material)
-        mesh
-
-    getMeshFromString: (string) =>
-        string_geom = @getTextGeometry string
-        string_mesh = @getTextMesh string_geom
-        string_mesh.scale.multiplyScalar(-1)
-        string_mesh
-
-    getLineObject: (line) =>
-        line_geometry = @getTextGeometry line
-        line_layout = line_geometry.layout
-        glyph_positions = line_layout.glyphs.map (g) -> g.position
-
-        assert line?
-
-        letterObjects = line.split("").map (letter, index) =>
-            assert glyph_positions[index], "#{line} -- #{letter}"
-            letter_mesh = @getMeshFromString letter
-            letter_mesh.position.x = - glyph_positions[index][0]
-            letter_mesh
-
-        # TODO: Pack up words...
-
-        lineObject = new THREE.Object3D()
-        lineObject.add.apply lineObject, letterObjects
+  SCALE_TEXT = 0.005
+
+  SPEED_MULTIPLIER = 0.5
+
+  CAMERA_PAN_DURATION = 2000 * SPEED_MULTIPLIER
+  FADE_DURATION = 3000 * SPEED_MULTIPLIER
+  # CHAIN_FADE_DELAY = 5000 * SPEED_MULTIPLIER
+
+  CAMERA_Z = -9
+
+  scaleText: SCALE_TEXT
+  speedMultiplier: SPEED_MULTIPLIER
+
+  constructor: ->
+    console.log "Starting Vis"
+
+    @renderer = new THREE.WebGLRenderer()
+    @renderer.setPixelRatio( window.devicePixelRatio )
+    @renderer.setSize( window.innerWidth, window.innerHeight )
+    @renderer.setClearColor( "rgb(255, 255, 255)" )
+
+    document.body.appendChild( @renderer.domElement )
+
+    @scene = new THREE.Scene()
+
+    fov = 70
+    aspect = window.innerWidth / window.innerHeight
+    [ near, far ] = [ 0.1, 1000 ]
+    @camera = new THREE.PerspectiveCamera( fov, aspect, near, far )
+
+    [_x, _y, _z] = [0, 0, CAMERA_Z]
+    @camera.position.z = _z
+    @camera.position.x = _x
+    @camera.position.y = _y
+    @camera.lookAt new THREE.Vector3(_x,_y,0)
+
+    onWindowResize = =>
+      @camera.aspect = window.innerWidth / window.innerHeight
+      @camera.updateProjectionMatrix()
+      @renderer.setSize( window.innerWidth, window.innerHeight )
+
+    window.addEventListener( 'resize', onWindowResize, false )
+
+    do animate = =>
+      requestAnimationFrame animate
+      @renderer.render @scene, @camera
+
+  addChain: (text) =>
+    chainVis = new ChainVis @scene, @camera, @font, @texture
+    chainVis.start text
+
+  addNetwork: (network) =>
+    colocationVis = new ColocationVis @scene, @camera, @font, @texture
+    colocationVis.start network
+
+  addLines: (lines) =>
+    linesVis = new LinesVis @scene, @camera, @font, @texture
+    linesVis.start lines
+
+  setTexture: (@texture) ->
+    maxAni = @renderer.getMaxAnisotropy()
+
+    @texture.needsUpdate = true
+    @texture.minFilter = THREE.LinearMipMapLinearFilter
+    @texture.magFilter = THREE.LinearFilter
+    @texture.generateMipmaps = true
+    @texture.anisotropy = maxAni
+
+  setFont: (@font) ->
+
+  getTextGeometry: (text) =>
+    createGeometry
+      text: text
+      font: @font
+
+  getTextMesh: (geometry) ->
+    material = new THREE.ShaderMaterial(Shader({
+      map: @texture,
+      smooth: 1/8,
+      side: THREE.DoubleSide,
+      transparent: true,
+      opacity: 0,
+      color: 'rgb(10, 10, 10)'
+    }))
+    mesh = new THREE.Mesh(geometry, material)
+    mesh
+
+  getMeshFromString: (string) =>
+    string_geom = @getTextGeometry string
+    string_mesh = @getTextMesh string_geom
+    string_mesh.scale.multiplyScalar(-1)
+    string_mesh
+
+  getLineObject: (line) =>
+    line_geometry = @getTextGeometry line
+    line_layout = line_geometry.layout
+    glyph_positions = line_layout.glyphs.map (g) -> g.position
+
+    assert line?
+
+    letterObjects = line.split("").map (letter, index) =>
+      assert glyph_positions[index], "#{line} -- #{letter}"
+      letter_mesh = @getMeshFromString letter
+      letter_mesh.position.x = - glyph_positions[index][0]
+      letter_mesh
+
+    # TODO: Pack up words...
+
+    lineObject = new THREE.Object3D()
+    lineObject.add.apply lineObject, letterObjects
+    lineObject._line = line
+    lineObject._layout = line_layout
+    lineObject
+
+  panCameraToPosition3: (target, duration, ignoreGlobal) =>
+    z_offset = if ignoreGlobal? then 0 else CAMERA_Z
+    new Promise (resolve) =>
+      d3.transition()
+        .duration duration || 1000
+        .tween "moveCamera", =>
+          current = @camera.position
+          x = d3.interpolate(current.x, target.x)
+          y = d3.interpolate(current.y, target.y)
+          z = d3.interpolate current.z, target.z + z_offset
+          (t) =>
+            @camera.position.x = x(t)
+            @camera.position.y = y(t)
+            @camera.position.z = z(t)
+        .each "end", resolve
+
+  getBBoxFromSubset: (parent, array) ->
+    siblings = parent.children.filter (child) ->
+      return array.indexOf(child) < 0
+    parent.remove.apply parent, siblings
+    box = getBBox parent
+    parent.add.apply parent, siblings
+    return box
+
+  getBBox = (object) ->
+    bbox = new THREE.BoundingBoxHelper( object, 0xff0000 )
+    do bbox.update
+    return bbox.box
+  getBBox: getBBox
+
+  panCameraToObject: (object, duration) =>
+    box = getBBox object
+    @panCameraToBBox box, duration
+
+  panCameraToBBox: (box, duration) =>
+    @panCameraToPosition3 box.center(), duration
+
+  zoomCameraToPosition: (target, duration) =>
+    new Promise (resolve) =>
+      d3.transition()
+        .duration duration
+        .tween "zoomCamera", =>
+          current = @camera.position
+          z = d3.interpolate current.z, target.z
+          (t) =>
+            @camera.position.z = z(t)
+        .each "end", resolve
+
+  addFadeOpacityTransition = (to, duration, target) ->
+    (selection) ->
+      selection.transition()
+        .duration duration
+        .delay (_, i) -> i * 10
+        .tween "fadeOpacity", ->
+          # if target? then debugger
+          obj = target || this
+          from = obj.material.uniforms.opacity.value
+          i = d3.interpolate from, to
+          (t) ->
+            obj.material.uniforms.opacity.value = i(t)
+
+  fadeToArray = (to, duration) ->
+    (array) ->
+      new Promise (resolve) ->
+        selection = d3.selectAll array
+        addFadeOpacityTransition(to, 1000)(selection)
+          .each "end", resolve
+
+  fadeToArray: fadeToArray
+
+  _radianScale = d3.scale.linear()
+      .domain([0, 360])
+      .range([0, Math.PI * 2])
+
+  getZoomDistanceFromBox: (box, distance_scale) ->
+    # Calculate horizontal field of view
+    # See: github.com/mrdoob/three.js/issues/1239
+    v_fov = _radianScale @camera.fov
+    h_fov = Math.atan( Math.tan(v_fov/2) * @camera.aspect )
+
+    # See: stackoverflow.com/questions/2866350/move-camera-to-fit-3d-scene
+    width = Math.abs(box.min.x - box.max.x)
+    return -(width / 2) / Math.tan(h_fov) * distance_scale
+
+  getWordIndex = (line, word) ->
+    expression = if word is "—" then word else "\\b#{word}\\b"
+    regex = new RegExp expression, "i"
+    assert line, "#{line}"
+    line.search regex
+
+  getWordIndex: getWordIndex
+
+  alignToNode = (parent) ->
+    (child) ->
+      parent_x = parent._text_object.position.x
+      word = parent.word
+      offset = [ parent, child ]
+          .map (_) ->
+            idx = getWordIndex _.line, word
+            assert idx isnt -1, "#{_.line}, #{word}"
+            _._text_object.children[idx].position.x
+          .reduce (a, b) -> a - b
+      child._text_object.position.x = parent_x + offset
+
+  alignToNode: alignToNode
+
+  getLetterObjectsForWord: (text_object, word, accessor) ->
+    line = if accessor? then accessor text_object else text_object._line
+    # debugger if typeof line isnt "string"
+    begin = getWordIndex line, word
+    end = begin + word.length
+    text_object.children.slice begin, end
+
+  chainedFadeIn: (array, duration) ->
+    reduction = (promise, curr, index, array) =>
+      promise.then =>
+        @fadeToArray(1, 1000) curr._text_object.children
+
+    promise = array.reduce reduction, Promise.resolve()
+
+class LinesVis extends Main
+  lineSpacing: 40
+
+  constructor: (@scene, @camera, @font, @texture) ->
+    console.info "New LinesVis."
+
+  start: (data) ->
+    root = @_addLines data
+    @animateLines root
+
+  ########################
+  # ANIMATE LINES
+  #
+  animateLines: (root) =>
+    # Fade in the root
+    @fadeToArray(1, 1000) root._text_object.children
+    @panCameraToObject root._text_object
+        .then -> traverse root
+
+    traverse = (node) =>
+      return if ! node.children?
+
+      next_child = node.children.filter((_) -> _.children?)[0]
+
+      # Start promise chain
+      Promise.resolve()
+        .then =>
+          # Fade out siblings
+          if node._parent?
+            siblings = node._parent.children
+              .filter (child) -> child isnt node
+            parent = node._parent
+            promises = siblings.concat(parent).map (child) =>
+              @fadeToArray(0, 1000) child._text_object.children
+            return Promise.all promises
+          return true
+        .then =>
+          # Fade in children
+          if next_child?
+            promises = node.children.map (child) =>
+              # Get the array of letters for the target word only
+              children = @getLetterObjectsForWord child._text_object, node.word
+              @fadeToArray(1, 1000) children
+            return Promise.all promises
+        .then =>
+          # Chained fade-in of child lines
+          if next_child?
+            pos = node._positions_array
+            curr = pos.indexOf(node)
+            next = pos.indexOf(next_child)
+            if curr < next
+              fade_array = pos.slice(curr + 1, next + 1)
+            else
+              fade_array = pos.slice(next, curr).reverse()
+            @chainedFadeIn fade_array, 1000
+        .then =>
+          if next_child?
+            # fadeToArray(1, 1000) next_child._text_object.children
+            @panCameraToObject next_child._text_object
+        .then ->
+          traverse next_child unless ! next_child?
+
+  _addLines: (lines) =>
+    lines_object = new THREE.Object3D()
+    lines_object.scale.multiplyScalar(@scaleText)
+    lines_object.updateMatrixWorld(true)
+    @scene.add lines_object
+
+    root = linesToTree lines
+
+    @addObjects(lines_object)(root)
+
+    traverse = (parent) =>
+      return if ! parent.children?
+
+      positions_array = d3.shuffle parent.children.concat parent
+      parent_index = positions_array.indexOf parent
+      parent_y = parent._text_object.position.y
+
+      parent._positions_array = positions_array
+
+      positions_array.forEach (node, index) =>
+        offset_from_parent = index - parent_index
+        obj = node._text_object
+        line_height = obj._layout.height + @lineSpacing
+        obj.position.y = parent_y + offset_from_parent * line_height
+
+      parent.children.forEach @alignToNode(parent)
+      parent.children.forEach traverse
+
+    traverse root
+    return root
+
+  addObjects: (lines_object) =>
+    (root) =>
+      traverse = (node) =>
+        # debugger if ! node.line?
+        node._text_object = @getLineObject(node.line)
+        # node._text_object.children.forEach (mesh) ->
+        #     mesh.material.uniforms.opacity.value = 0
+        lines_object.add node._text_object
+
+        node.children.forEach traverse if node.children
+
+      traverse(root)
+
+  linesToTree = (lines) ->
+    lines = lines.map (line) ->
+      line: line.line
+      word: line.word
+      sIdx: line.sIdx
+      eIdx: line.eIdx
+      children: line.lines
+
+    reducer = (prev, current, index, array) ->
+      prev_index = current.children
+          .map (_) -> _.line
+          .indexOf(prev.line)
+      current.children[prev_index] = prev
+      prev._parent = current
+      current
+
+    lines.reduceRight reducer
+
+class ColocationVis extends Main
+  constructor: (@scene, @camera, @font, @texture) ->
+    console.info "New ColocationVis."
+
+  start: (data) ->
+    @_addNetwork data
+
+  _addNetwork: (network) =>
+    radius = 700
+    rotation = 0 # Math.PI / 2.2
+
+    network_object = new THREE.Object3D()
+    network_object.scale.multiplyScalar(@scaleText)
+    network_object.rotateX rotation
+    @scene.add network_object
+
+    root = makeTree network
+    root = setNetworkPositions root, radius
+
+    traverse = (node) =>
+      return if ! node.position?
+
+      node = @setTextObject(node)
+      text_object = node._text_object
+      text_object.rotateX -rotation
+      network_object.add text_object
+
+      ################################
+      # ANIMATE COLOCATION NETWORK
+      #
+      faded_in = @fadeToArray(1, 1000) text_object.children
+
+      if node.children
+        faded_in.then =>
+          @panCameraToObject(text_object)
+        .then =>
+          if node.parent?
+            siblings = node.parent.children.filter (child) ->
+              return child._text_object isnt text_object
+            if node.parent.parent
+              siblings = siblings.concat(node.parent.parent)
+            promises = siblings.map (sibling) =>
+              @fadeToArray(0, 1000) sibling._text_object.children
+            return Promise.all promises
+        .then ->
+          node.children.forEach traverse
+
+    traverse(root)
+
+  setTextObject: (node) =>
+    text_object = @getLineObject(node.val)
+    text_object.children.forEach (mesh) ->
+      mesh.material.uniforms.opacity.value = 0
+    text_object.position.copy(node.position)
+    node._text_object = text_object
+    node
+
+  setNetworkPositions = (root, radius) ->
+    root.position = new THREE.Vector3()
+
+    radianScale = d3.scale.ordinal()
+        .rangePoints [0, 2 * Math.PI]
+
+    traverse = (node) ->
+      return if ! node.children?
+      offset = if node.parent? then getRadians(node.parent, node) else 0
+
+      circle_nodes = if node.parent?
+        [node.parent].concat(node.children)
+      else node.children
+
+      radianScale.domain d3.range(circle_nodes.length + 1)
+
+      circle_nodes.forEach (circle_node, index) ->
+        radians = radianScale(index) + offset
+        pos = new THREE.Vector3(
+          radius * Math.cos(radians) + node.position.x,
+          radius * Math.sin(radians) + node.position.y
+          # 0,
+          # radius * Math.sin(radians) + node.position.z
+        )
+        if circle_node.position?
+          # check = pos.y.toFixed(3) is circle_node.position.y.toFixed(3)
+          # assert(check)
+        else
+          circle_node.position = pos
+
+      if node.children?
+        node.children.forEach traverse
+
+    traverse(root)
+    return root
+
+  getRadians = (a, b) ->
+    dx = a.position.x - b.position.x
+    dy = a.position.y - b.position.y
+    Math.atan2(dy, dx)
+
+  makeTree = (network) ->
+    network = network.map (d) ->
+      val: d.word
+      children: d.colocations
+
+  _makeTree = (child, parent, index, array) ->
+    child.parent = parent
+
+    # Find the index of this child in the parent's children array
+    child_index = parent.children
+        .map (d) -> d.val
+        .indexOf child.val
+
+    # Set the child's "amount" to match the parent's child object
+    parents_child = parent.children[child_index]
+    child.amt = parents_child.amt
+
+    # Set the parent's reference to match the child object
+    parent.children[child_index] = child
+
+    return parent
+
+    network.reduceRight(_makeTree)
+
+class ChainVis extends Main
+  constructor: (@scene, @camera, @font, @texture) ->
+    console.info "New ChainVis."
+
+  start: (data) =>
+    @_addChain data
+
+  _addChain: (text) =>
+    lineObjects = @processChain(text)
+      .map (line, index) =>
+        lineObject = @getLineObject(line.line, index)
+        height = lineObject._layout.height
+        lineObject.position.y = - (index) * (height + 20)
         lineObject._line = line
-        lineObject._layout = line_layout
         lineObject
-
-    panCameraToPosition3: (target, duration, ignoreGlobal) =>
-        z_offset = if ignoreGlobal? then 0 else CAMERA_Z
-        new Promise (resolve) =>
-            d3.transition()
-                .duration duration || 1000
-                .tween "moveCamera", =>
-                    current = @camera.position
-                    x = d3.interpolate(current.x, target.x)
-                    y = d3.interpolate(current.y, target.y)
-                    z = d3.interpolate current.z, target.z + z_offset
-                    (t) =>
-                        @camera.position.x = x(t)
-                        @camera.position.y = y(t)
-                        @camera.position.z = z(t)
-                .each "end", resolve
-
-    getBBoxFromSubset: (parent, array) ->
-        siblings = parent.children.filter (child) ->
-            return array.indexOf(child) < 0
-        parent.remove.apply parent, siblings
-        box = getBBox parent
-        parent.add.apply parent, siblings
-        return box
-
-    getBBox = (object) ->
-        bbox = new THREE.BoundingBoxHelper( object, 0xff0000 )
-        do bbox.update
-        return bbox.box
-    getBBox: getBBox
-
-    panCameraToObject: (object, duration) =>
-        box = getBBox object
-        @panCameraToBBox box, duration
-
-    panCameraToBBox: (box, duration) =>
-        @panCameraToPosition3 box.center(), duration
-
-    zoomCameraToPosition: (target, duration) =>
-        new Promise (resolve) =>
-            d3.transition()
-                .duration duration
-                .tween "zoomCamera", =>
-                    current = @camera.position
-                    z = d3.interpolate current.z, target.z
-                    (t) =>
-                        @camera.position.z = z(t)
-                .each "end", resolve
-
-    addFadeOpacityTransition = (to, duration, target) ->
-        (selection) ->
-            selection.transition()
-                .duration duration
-                .delay (_, i) -> i * 10
-                .tween "fadeOpacity", ->
-                    # if target? then debugger
-                    obj = target || this
-                    from = obj.material.uniforms.opacity.value
-                    i = d3.interpolate from, to
-                    (t) ->
-                        obj.material.uniforms.opacity.value = i(t)
-
-    fadeToArray = (to, duration) ->
-        (array) ->
-            new Promise (resolve) ->
-                selection = d3.selectAll array
-                addFadeOpacityTransition(to, 1000)(selection)
-                    .each "end", resolve
-
-    fadeToArray: fadeToArray
-
-    _radianScale = d3.scale.linear()
-        .domain([0, 360])
-        .range([0, Math.PI * 2])
-
-    getZoomDistanceFromBox: (box, distance_scale) ->
-        # Calculate horizontal field of view
-        # See: github.com/mrdoob/three.js/issues/1239
-        v_fov = _radianScale @camera.fov
-        h_fov = Math.atan( Math.tan(v_fov/2) * @camera.aspect )
-
-        # See: stackoverflow.com/questions/2866350/move-camera-to-fit-3d-scene
-        width = Math.abs(box.min.x - box.max.x)
-        return -(width / 2) / Math.tan(h_fov) * distance_scale
-
-    getWordIndex = (line, word) ->
-        expression = if word is "—" then word else "\\b#{word}\\b"
-        regex = new RegExp expression, "i"
-        assert line, "#{line}"
-        line.search regex
-
-    getWordIndex: getWordIndex
-
-    alignToNode = (parent) ->
-        (child) ->
-            parent_x = parent._text_object.position.x
-            word = parent.word
-            offset = [ parent, child ]
-                .map (_) ->
-                    idx = getWordIndex _.line, word
-                    assert idx isnt -1, "#{_.line}, #{word}"
-                    _._text_object.children[idx].position.x
-                .reduce (a, b) -> a - b
-            child._text_object.position.x = parent_x + offset
-
-    alignToNode: alignToNode
-
-    getLetterObjectsForWord: (text_object, word, accessor) ->
-        line = if accessor? then accessor text_object else text_object._line
-        debugger if typeof line isnt "string"
-        begin = getWordIndex line, word
-        end = begin + word.length
-        text_object.children.slice begin, end
-
-    chainedFadeIn: (array, duration) ->
-        reduction = (promise, curr, index, array) =>
-            promise.then =>
-                @fadeToArray(1, 1000) curr._text_object.children
-
-        promise = array.reduce reduction, Promise.resolve()
-
-class LinesVis extends Main
-    lineSpacing: 40
-
-    constructor: (@scene, @camera, @font, @texture) ->
-        console.info "New LinesVis."
-
-    start: (data) ->
-        root = @_addLines data
-        @animateLines root
+      .map positionLines
+
+    chainObject = new THREE.Object3D()
+    chainObject.scale.multiplyScalar(@scaleText)
+
+    @scene.add(chainObject)

     ########################
-    # ANIMATE LINES
-    #
-    animateLines: (root) =>
-        # Fade in the root
-        @fadeToArray(1, 1000) root._text_object.children
-        @panCameraToObject root._text_object
-            .then -> traverse root
-
-        traverse = (node) =>
-            return if ! node.children?
-
-            next_child = node.children.filter((_) -> _.children?)[0]
-
-            # Start promise chain
-            Promise.resolve()
-                .then =>
-                    # Fade out siblings
-                    if node._parent?
-                        siblings = node._parent.children
-                            .filter (child) -> child isnt node
-                        parent = node._parent
-                        promises = siblings.concat(parent).map (child) =>
-                                @fadeToArray(0, 1000) child._text_object.children
-                        return Promise.all promises
-                    return true
-                .then =>
-                    # Fade in children
-                    if next_child?
-                        promises = node.children.map (child) =>
-                            # Get the array of letters for the target word only
-                            children = @getLetterObjectsForWord child._text_object, node.word
-                            @fadeToArray(1, 1000) children
-                        return Promise.all promises
-                .then =>
-                    # Chained fade-in of child lines
-                    if next_child?
-                        pos = node._positions_array
-                        curr = pos.indexOf(node)
-                        next = pos.indexOf(next_child)
-                        if curr < next
-                            fade_array = pos.slice(curr + 1, next + 1)
-                        else
-                            fade_array = pos.slice(next, curr).reverse()
-                        @chainedFadeIn fade_array, 1000
-                .then =>
-                    if next_child?
-                        # fadeToArray(1, 1000) next_child._text_object.children
-                        @panCameraToObject next_child._text_object
-                .then ->
-                    traverse next_child unless ! next_child?
-
-    _addLines: (lines) =>
-        lines_object = new THREE.Object3D()
-        lines_object.scale.multiplyScalar(@scaleText)
-        lines_object.updateMatrixWorld(true)
-        @scene.add lines_object
-
-        root = linesToTree lines
-
-        @addObjects(lines_object)(root)
-
-        traverse = (parent) =>
-            return if ! parent.children?
-
-            positions_array = d3.shuffle parent.children.concat parent
-            parent_index = positions_array.indexOf parent
-            parent_y = parent._text_object.position.y
-
-            parent._positions_array = positions_array
-
-            positions_array.forEach (node, index) =>
-                offset_from_parent = index - parent_index
-                obj = node._text_object
-                line_height = obj._layout.height + @lineSpacing
-                obj.position.y = parent_y + offset_from_parent * line_height
-
-            parent.children.forEach @alignToNode(parent)
-            parent.children.forEach traverse
-
-        traverse root
-        return root
-
-    addObjects: (lines_object) =>
-        (root) =>
-            traverse = (node) =>
-                debugger if ! node.line?
-                node._text_object = @getLineObject(node.line)
-                # node._text_object.children.forEach (mesh) ->
-                #     mesh.material.uniforms.opacity.value = 0
-                lines_object.add node._text_object
-
-                node.children.forEach traverse if node.children
-
-            traverse(root)
-
-    linesToTree = (lines) ->
-        lines = lines.map (line) ->
-            line: line.line
-            word: line.word
-            sIdx: line.sIdx
-            eIdx: line.eIdx
-            children: line.lines
-
-        reducer = (prev, current, index, array) ->
-            prev_index = current.children
-                .map (_) -> _.line
-                .indexOf(prev.line)
-            current.children[prev_index] = prev
-            prev._parent = current
-            current
-
-        lines.reduceRight reducer
-
-class ColocationVis extends Main
-    constructor: (@scene, @camera, @font, @texture) ->
-        console.info "New ColocationVis."
-
-    start: (data) ->
-        @_addNetwork data
-
-    _addNetwork: (network) =>
-        radius = 700
-        rotation = 0 # Math.PI / 2.2
-
-        network_object = new THREE.Object3D()
-        network_object.scale.multiplyScalar(@scaleText)
-        network_object.rotateX rotation
-        @scene.add network_object
-
-        root = makeTree network
-        root = setNetworkPositions root, radius
-
-        traverse = (node) =>
-            return if ! node.position?
-
-            node = @setTextObject(node)
-            text_object = node._text_object
-            text_object.rotateX -rotation
-            network_object.add text_object
-
-            ################################
-            # ANIMATE COLOCATION NETWORK
-            #
-            faded_in = @fadeToArray(1, 1000) text_object.children
-
-            if node.children
-                faded_in.then =>
-                    @panCameraToObject(text_object)
-                .then =>
-                    if node.parent?
-                        siblings = node.parent.children.filter (child) ->
-                            return child._text_object isnt text_object
-                        if node.parent.parent
-                            siblings = siblings.concat(node.parent.parent)
-                        promises = siblings.map (sibling) =>
-                            @fadeToArray(0, 1000) sibling._text_object.children
-                        return Promise.all promises
-                .then ->
-                    node.children.forEach traverse
-
-        traverse(root)
-
-    setTextObject: (node) =>
-        text_object = @getLineObject(node.val)
-        text_object.children.forEach (mesh) ->
-            mesh.material.uniforms.opacity.value = 0
-        text_object.position.copy(node.position)
-        node._text_object = text_object
-        node
-
-    setNetworkPositions = (root, radius) =>
-        root.position = new THREE.Vector3()
-
-        radianScale = d3.scale.ordinal()
-            .rangePoints [0, 2 * Math.PI]
-
-        traverse = (node) ->
-            return if ! node.children?
-            offset = if node.parent? then getRadians(node.parent, node) else 0
-
-            circle_nodes = if node.parent?
-                [node.parent].concat(node.children)
-            else node.children
-
-            radianScale.domain d3.range(circle_nodes.length + 1)
-
-            circle_nodes.forEach (circle_node, index) ->
-                radians = radianScale(index) + offset
-                pos = new THREE.Vector3(
-                    radius * Math.cos(radians) + node.position.x,
-                    radius * Math.sin(radians) + node.position.y
-                    # 0,
-                    # radius * Math.sin(radians) + node.position.z
-                )
-                if circle_node.position?
-                    # check = pos.y.toFixed(3) is circle_node.position.y.toFixed(3)
-                    # assert(check)
-                else
-                    circle_node.position = pos
-
-            if node.children?
-                node.children.forEach traverse
-
-        traverse(root)
-        return root
-
-    getRadians = (a, b) ->
-        dx = a.position.x - b.position.x
-        dy = a.position.y - b.position.y
-        Math.atan2(dy, dx)
-
-    makeTree = (network) =>
-        network = network.map (d) ->
-            val: d.word
-            children: d.colocations
-
-        _makeTree = (child, parent, index, array) ->
-            child.parent = parent
-
-            # Find the index of this child in the parent's children array
-            child_index = parent.children
-                .map (d) -> d.val
-                .indexOf child.val
-
-            # Set the child's "amount" to match the parent's child object
-            parents_child = parent.children[child_index]
-            child.amt = parents_child.amt
-
-            # Set the parent's reference to match the child object
-            parent.children[child_index] = child
-
-            return parent
-
-        network.reduceRight(_makeTree)
-
-class ChainVis extends Main
-    constructor: (@scene, @camera, @font, @texture) ->
-        console.info "New ChainVis."
-
-    start: (data) =>
-        @_addChain data
-
-    _addChain: (text) =>
-        lineObjects = @processChain(text)
-            .map (line, index) =>
-                lineObject = @getLineObject(line.line, index)
-                height = lineObject._layout.height
-                lineObject.position.y = - (index) * (height + 20)
-                lineObject._line = line
-                lineObject
-            .map positionLines
-
-        chainObject = new THREE.Object3D()
-        chainObject.scale.multiplyScalar(@scaleText)
-
-        @scene.add(chainObject)
-
-        ########################
-        # ANIMATE POETRY CHAIN
-        reducer = (prev, curr, index, array) =>
-            prev.then =>
-                chainObject.add curr
-                word = curr._line.prev_connector
-                return if ! word? # word.length is 0
-                accessor = (obj) -> obj._line.line
-                one_word_array = @getLetterObjectsForWord curr, word, accessor
-                bbox = @getBBoxFromSubset curr, one_word_array
-                return @fadeToArray(1, 1000) one_word_array
-                # return @panCameraToBBox bbox, 1000
-            .then =>
-                bbox = @getBBox chainObject
-                x = bbox.center().x
-                y = bbox.center().y
-                z = bbox.center().z + @getZoomDistanceFromBox bbox, 1.3
-                @panCameraToPosition3 new THREE.Vector3(x,y,z), 1000, true
-                @fadeToArray(1, 1000) curr.children
-
-        first = Promise.resolve()
-        lineObjects.reduce reducer, first
-
-    positionLines = (line, index, array) =>
-        return line if index is 0
-
-        prev = array[index - 1]
-        prev_connector_idx = prev._line.connector_index
-        my_prev_connector_idx = line._line.my_prev_connector_index
-
-        line.position.x = prev.position.x
-        line.position.x += prev.children[prev_connector_idx].position.x
-        line.position.x -= line.children[my_prev_connector_idx].position.x
-        line
-
-    processChain: (chain) ->
-        chain.map (obj, i, array) =>
-            obj.connector_index = obj.line.indexOf obj.connector
-            if i > 0
-                prev = array[i-1]
-                prev_con = prev.connector
-                # my_prev_idx = obj.line.indexOf prev_con
-                my_prev_idx = @getWordIndex obj.line, prev_con
-                prev_idx = prev.connector_index
-                obj.prev_connector = prev_con
-                obj.my_prev_connector_index = my_prev_idx
-                obj.prev_connector_index = prev_idx
-            obj
+    # ANIMATE POETRY CHAIN
+    reducer = (prev, curr, index, array) =>
+      prev.then =>
+        chainObject.add curr
+        word = curr._line.prev_connector
+        return if ! word? # word.length is 0
+        accessor = (obj) -> obj._line.line
+        one_word_array = @getLetterObjectsForWord curr, word, accessor
+        bbox = @getBBoxFromSubset curr, one_word_array
+        return @fadeToArray(1, 1000) one_word_array
+        # return @panCameraToBBox bbox, 1000
+      .then =>
+        bbox = @getBBox chainObject
+        x = bbox.center().x
+        y = bbox.center().y
+        z = bbox.center().z + @getZoomDistanceFromBox bbox, 1.3
+        @panCameraToPosition3 new THREE.Vector3(x,y,z), 1000, true
+        @fadeToArray(1, 1000) curr.children
+
+    first = Promise.resolve()
+    lineObjects.reduce reducer, first
+
+  positionLines = (line, index, array) ->
+    return line if index is 0
+
+    prev = array[index - 1]
+    prev_connector_idx = prev._line.connector_index
+    my_prev_connector_idx = line._line.my_prev_connector_index
+
+    line.position.x = prev.position.x
+    line.position.x += prev.children[prev_connector_idx].position.x
+    line.position.x -= line.children[my_prev_connector_idx].position.x
+    line
+
+  processChain: (chain) ->
+    chain.map (obj, i, array) =>
+      obj.connector_index = obj.line.indexOf obj.connector
+      if i > 0
+        prev = array[i-1]
+        prev_con = prev.connector
+        # my_prev_idx = obj.line.indexOf prev_con
+        my_prev_idx = @getWordIndex obj.line, prev_con
+        prev_idx = prev.connector_index
+        obj.prev_connector = prev_con
+        obj.my_prev_connector_index = my_prev_idx
+        obj.prev_connector_index = prev_idx
+      obj


code cleanup

code cleanup

--- src/Pump.coffee
+++ src/Pump.coffee
@@ -8,7 +8,7 @@
   @PAUSED: 2
   @ENDED: 3

-  constructor: () ->
+  constructor: ->
     @_state = Pump.STOPPED
     @_from = null
     @_id = null


Remove unused dependencies

Remove unused dependencies

--- src/scripts/modules/media/body/body.coffee
+++ src/scripts/modules/media/body/body.coffee
@@ -1,11 +1,8 @@
 define [
-  'jquery'
-  'underscore'
-  'backbone'
   'cs!helpers/backbone/views/base'
   'hbs!./body-template'
   'less!./body'
-], ($, _, Backbone, BaseView, template) ->
+], (BaseView, template) ->

   return class MediaBodyView extends BaseView
     template: template()


added spawn to make it easier to create an awesomebox from outside

--- lib/awesomebox.coffee
+++ lib/awesomebox.coffee
@@ -76,4 +76,29 @@
     delete cache.client_config
   catch e

+{spawn} = require 'child_process'
+walkabout = require 'walkabout'
+AWESOMEBOX_PATH = walkabout(__dirname).join('../bin/awesomebox').absolute_path
+
+awesomebox.spawn = (directory, callback) ->
+  directory = walkabout(directory)
+  proc = spawn(AWESOMEBOX_PATH, ['run'], cwd: directory.absolute_path)
+  on_exit = -> proc?.kill()
+  process.on('exit', on_exit)
+  proc.on 'close', -> process.removeListener('exit', on_exit)
+
+  rx = /Listening on port ([0-9]+)/
+  logs = ''
+  on_data = (data) ->
+    logs += data.toString()
+    match = rx.exec(logs)
+    if match?
+      callback(null, parseInt(match[1]))
+      proc.stdout.removeListener('data', on_data)
+      logs = null
+
+  proc.stdout.on('data', on_data)
+
+  proc
+
 module.exports = global.awesomebox


db query: fix  string-interpolation that should result in a string for postgres containing a "-string for the key in a json object

--- src/smc-hub/postgres-server-queries.coffee
+++ src/smc-hub/postgres-server-queries.coffee
@@ -1672,7 +1672,7 @@
             cb         : required
         if not @_validate_opts(opts) then return
         @_query
-            query : "SELECT invite#>'{#{opts.to}}' AS to FROM projects"
+            query : "SELECT invite#>'{\"#{opts.to}\"}' AS to FROM projects"
             where : 'project_id :: UUID = $' : opts.project_id
             cb    : one_result 'to', (err, y) =>
                 opts.cb(err, if not y? or y.error or not y.time then 0 else new Date(y.time))


removing leading slash from state urls

removing leading slash from state urls

--- lib/assets/javascripts/stark/stark.js.coffee
+++ lib/assets/javascripts/stark/stark.js.coffee
@@ -107,6 +107,9 @@
     @states[s.name] = state
     state.prototype.app = @
     if s.url?
+      if matches = s.url.match(/^\/+(.*)$/)
+        # strip any leading slashes so we can route correctly
+        s.url = matches[1]
       state.prototype.route = @router.match(s.url, s.name)

   # bound to app:route event which is triggered by Route.handler method


Avoid the possibility of stack overflow in spliceArray helper

--- src/helpers.coffee
+++ src/helpers.coffee
@@ -1,20 +1,27 @@
 Point = require './point'
-
-SpliceArrayChunkSize = 100000

 MULTI_LINE_REGEX_REGEX = /\\r|\\n|\r|\n|^\[\^|[^\\]\[\^/

 module.exports =
-  spliceArray: (originalArray, start, length, insertedArray=[]) ->
-    if insertedArray.length < SpliceArrayChunkSize
-      originalArray.splice(start, length, insertedArray...)
+  spliceArray: (array, start, removedCount, insertedItems=[]) ->
+    oldLength = array.length
+    insertedCount = insertedItems.length
+    removedCount = Math.min(removedCount, oldLength - start)
+    lengthDelta = insertedCount - removedCount
+    newLength = oldLength + lengthDelta
+
+    if lengthDelta > 0
+      array.length = newLength
+      for i in [(newLength - 1)..(start + insertedCount)] by -1
+        array[i] = array[i - lengthDelta]
     else
-      removedValues = originalArray.splice(start, length)
-      for chunkStart in [0..insertedArray.length] by SpliceArrayChunkSize
-        chunkEnd = chunkStart + SpliceArrayChunkSize
-        chunk = insertedArray.slice(chunkStart, chunkEnd)
-        originalArray.splice(start + chunkStart, 0, chunk...)
-      removedValues
+      for i in [(start + insertedCount)...newLength] by 1
+        array[i] = array[i - lengthDelta]
+      array.length = newLength
+
+    for value, i in insertedItems by 1
+      array[start + i] = insertedItems[i]
+    return

   newlineRegex: /\r\n|\n|\r/g


Remove unused variable handshake_complete

--- drivers/javascript/src/net.coffee
+++ drivers/javascript/src/net.coffee
@@ -251,7 +251,11 @@
         @rawSocket = net.connect @port, @host
         @rawSocket.setNoDelay()

-        handshake_complete = false
+        timeout = setTimeout( (()=>
+            @rawSocket.destroy()
+            @emit 'error', new RqlDriverError "Handshake timedout"
+        ), @timeout*1000)
+
         @rawSocket.once 'connect', =>
             # Initialize connection with magic number to validate version
             buf = new ArrayBuffer 8
@@ -293,12 +297,6 @@
         @rawSocket.on 'error', (args...) => @emit 'error', args...

         @rawSocket.on 'close', => @open = false; @emit 'close'
-
-        timeout = setTimeout( (()=>
-            @rawSocket.destroy()
-            @emit 'error', new RqlDriverError "Handshake timedout"
-        ), @timeout*1000)
-

     close: () ->
         super()


env helpers

env helpers

--- lib/assets/javascripts/stark/stark.js.coffee
+++ lib/assets/javascripts/stark/stark.js.coffee
@@ -29,6 +29,14 @@
     @env = env
     @login_route = login_route
     @default_route = default_route
+
+    # env helpers
+    if @env == "development"
+      @dev = @development = true
+      @prod = @production = false
+    else
+      @dev = @development = false
+      @prod = @production = true

     @router.app = @
     @subscribe('app:route', @matchRoute)


Acrescentado o c?digo para ajustar dinamicamente o dom?nio utilizado para rastreamento do Google Analytics.

--- assets/google-analytics.coffee
+++ assets/google-analytics.coffee
@@ -1,6 +1,8 @@
+_getDomain = ->
+  window.location.hostname.match(/(?:www[0-9]?\.)?(.[^/:]+)/)[1]
 _gaq = _gaq or []
 _gaq.push ["_setAccount", "UA-34001692-4"]
-_gaq.push ["_setDomainName", "gov.br"]
+_gaq.push ["_setDomainName", _getDomain]
 _gaq.push ["_trackPageview"]
 (->
   ga = document.createElement("script")


more verbose validation

more verbose validation

--- assets/scripts/app/auth.coffee
+++ assets/scripts/app/auth.coffee
@@ -35,7 +35,14 @@
       null

   validateUser: (user) ->
-    user.id && user.login && user.token
+    @validateHas('id', user) && @validateHas('login', user) && @validateHas('token', user)
+
+  validateHas: (field, user) ->
+    if user.field?
+      true
+    else
+      console.log("discarding user data, lacks #{field}")
+      false

   setData: (data) ->
     @storeData(data, Travis.sessionStorage)


collision with regular bounds

collision with regular bounds

--- test/collision.coffee
+++ test/collision.coffee
@@ -50,6 +50,7 @@
 test "Collision.rayRectangle", ->
   rect = GameObject
     x: 25
-    y:
+    y: 0
+
 module()


Use windowDimensions from load settings if they exist

--- src/atom.coffee
+++ src/atom.coffee
@@ -228,6 +228,9 @@
     localStorage.setItem("defaultWindowDimensions", dimensions)

   getDefaultWindowDimensions: ->
+    {windowDimensions} = @getLoadSettings()
+    return windowDimensions if windowDimensions?
+
     try
       JSON.parse(localStorage.getItem("defaultWindowDimensions"))
     catch error


Add restoreWindow helper

Add restoreWindow helper

--- src/atom.coffee
+++ src/atom.coffee
@@ -223,9 +223,8 @@
     @disposables?.dispose()
     @disposables = new CompositeDisposable

-    dimensions = @restoreWindowDimensions()
-    maximize = dimensions?.maximized and process.platform isnt 'darwin'
-    @displayWindow({maximize})
+    @restoreWindow()
+    @show()

     @setBodyPlatformClass()

@@ -496,16 +495,17 @@
   toggleFullScreen: ->
     @setFullScreen(not @isFullScreen())

-  # Schedule the window to be shown and focused on the next tick.
-  #
-  # This is done in a next tick to prevent a white flicker from occurring
-  # if called synchronously.
-  displayWindow: ({maximize}={}) ->
-    @show()
+  # Restore the window to its previous dimensions.
+  #
+  # Also restores the full screen and maximized state on the next tick to
+  # prevent resize glitches.
+  restoreWindow: ->
+    dimensions = @restoreWindowDimensions()
+    maximize = dimensions?.maximized and process.platform isnt 'darwin'

     setImmediate =>
       @focus()
-      @setFullScreen(true) if @workspace.fullScreen
+      @setFullScreen(true) if @workspace?.fullScreen
       @maximize() if maximize

   # Get the dimensions of this window.


Atom is Essential

Atom is Essential

--- src/atom.coffee
+++ src/atom.coffee
@@ -14,7 +14,7 @@
 {$} = require './space-pen-extensions'
 WindowEventHandler = require './window-event-handler'

-# Public: Atom global for dealing with packages, themes, menus, and the window.
+# Essential: Atom global for dealing with packages, themes, menus, and the window.
 #
 # An instance of this class is always available as the `atom` global.
 module.exports =


escape double qoute (#15)

escape double qoute (#15)

--- lib/executable.coffee
+++ lib/executable.coffee
@@ -7,7 +7,10 @@

 Stream = require './executable/stream'

-escape = (content) -> content.replace(/\n/g, '\\n')
+escape = (content) ->
+    content
+        .replace(/\n/g, '\\n')
+        .replace(/\"/g, '\\"')

 class Executable extends EventEmitter


Reintroduce Marker.fromPointWithDelta

Reintroduce Marker.fromPointWithDelta

--- src/range.coffee
+++ src/range.coffee
@@ -59,6 +59,24 @@
       endPoint.column += lines[0].length
     new this(startPoint, endPoint)

+  # Public: Returns a {Range} that starts at the given point and ends at the
+  # start point plus the given row and column deltas.
+  #
+  # * startPoint:
+  #     A {Point} or point-compatible {Array}
+  # * rowDelta:
+  #     A {Number} indicating how many rows to add to the start point to get the
+  #     end point.
+  # * columnDelta:
+  #     A {Number} indicating how many rows to columns to the start point to get
+  #     the end point.
+  #
+  # Returns a {Range}
+  @fromPointWithDelta: (startPoint, rowDelta, columnDelta) ->
+    startPoint = Point.fromObject(startPoint)
+    endPoint = new Point(startPoint.row + rowDelta, startPoint.column + columnDelta)
+    new this(startPoint, endPoint)
+
   constructor: (pointA = new Point(0, 0), pointB = new Point(0, 0)) ->
     pointA = Point.fromObject(pointA)
     pointB = Point.fromObject(pointB)


whitespace

whitespace

--- src/robot.coffee
+++ src/robot.coffee
@@ -220,7 +220,7 @@
   mergeData: (data) ->
     for k of (data or { })
       @data[k] = data[k]
-
+
     @emit 'loaded', @data

 class Robot.Message
@@ -310,7 +310,7 @@

   # Public: Posts a topic changing message
   #
-  # strings - One or more strings to set as the topic of the
+  # strings - One or more strings to set as the topic of the
   #           room the bot is in.
   #
   # Returns nothing.


Add a limit, defaulting to what express.multipart used to use by default

--- src/robot.coffee
+++ src/robot.coffee
@@ -292,7 +292,9 @@

     app.use express.json()
     app.use express.urlencoded()
-    app.use multipart()
+    # replacement for deprecated express.multipart/connect.multipart
+    # limit to 100mb, as per the old behavior
+    app.use multipart(maxFilesSize: 100 * 1024 * 1024)

     app.use express.static stat if stat


Add an error when we can't connect to the back end

--- app/api/auth.coffee
+++ app/api/auth.coffee
@@ -31,6 +31,8 @@
       .catch (request) ->
         # Back end is down or something.
         try {errors} = JSON.parse request.responseText
+        errors ?= [message: password: ['Could not connect to the server']]
+
         console?.error 'Failed to get auth token', errors
         Promise.reject errors


more tomdoc

more tomdoc

--- src/robot.coffee
+++ src/robot.coffee
@@ -142,6 +142,11 @@

   # Public: Helper for making quick HTTP GET requests.
   # otherwise, use @http for the rad Node 0.4 HTTP Client.
+  #
+  # url - String URL to GET.
+  # cb  - Function callback that is triggered with the HTTP response.
+  #
+  # Returns nothing.
   fetch: (url, cb) ->
     uri = Url.parse url
     body = ''


Fix reference of 'msg' to 'res' in error handling

--- src/robot.coffee
+++ src/robot.coffee
@@ -61,8 +61,8 @@
     @adapterName   = adapter
     @errorHandlers = []

-    @on 'error', (err, msg) =>
-      @invokeErrorHandlers(err, msg)
+    @on 'error', (err, res) =>
+      @invokeErrorHandlers(err, res)
     @onUncaughtException = (err) =>
       @emit 'error', err
     process.on 'uncaughtException', @onUncaughtException


Age in seconds

Age in seconds

--- test/expirable.coffee
+++ test/expirable.coffee
@@ -7,7 +7,7 @@
   obj.include Expirable

   4.times ->
-    obj.trigger(1)
+    obj.trigger "afterUpdate", 1

   equals obj.I.active, true, "object is active until duration is exceeded"


New attestations management

New attestations management

--- app/src/dongle/dongle.coffee
+++ app/src/dongle/dongle.coffee
@@ -15,18 +15,37 @@

 Firmwares = ledger.dongle.Firmwares

-# Ledger OS pubKey, used for pairing.
-Attestation =
-  String: "04c370d4013107a98dfef01d6db5bb3419deb9299535f0be47f05939a78b314a3c29b51fcaa9b3d46fa382c995456af50cd57fb017c0ce05e4a31864a79b8fbfd6"
-Attestation.Bytes = parseInt(hex, 16) for hex in Attestation.String.match(/\w\w/g)
-Attestation.xPoint = Attestation.String.substr(2,64)
-Attestation.yPoint = Attestation.String.substr(66)
-
-BetaAttestation =
-  String: "04e69fd3c044865200e66f124b5ea237c918503931bee070edfcab79a00a25d6b5a09afbee902b4b763ecf1f9c25f82d6b0cf72bce3faf98523a1066948f1a395f"
-BetaAttestation.Bytes = parseInt(hex, 16) for hex in BetaAttestation.String.match(/\w\w/g)
-BetaAttestation.xPoint = BetaAttestation.String.substr(2,64)
-BetaAttestation.yPoint = BetaAttestation.String.substr(66)
+Attestations = {}
+
+class Attestation
+
+  xPoint: null
+  yPoint: null
+  Bytes: null
+  BatchId: null
+  DerivationId: null
+
+  constructor: (batchId, derivationId, value) ->
+    if _(this).isKindOf(Attestation)
+      @BatchId = batchId
+      @DerivationId = derivationId
+      @Id = Convert.toHexInt(batchId) + Convert.toHexInt(derivationId)
+      @String = value
+      @Bytes = parseInt(hex, 16) for hex in @String.match(/\w\w/g)
+      @xPoint = @String.substr(2,64)
+      @yPoint = @String.substr(66)
+      Attestations[@Id] = this
+    else
+      new Attestation(batchId, derivationId, value)
+
+  isBeta: -> @BatchId is 0
+  isProduction: -> !@isBeta()
+
+  getAttestationId: -> new ByteString(@Id, HEX)
+
+Attestation(0, 0, "04e69fd3c044865200e66f124b5ea237c918503931bee070edfcab79a00a25d6b5a09afbee902b4b763ecf1f9c25f82d6b0cf72bce3faf98523a1066948f1a395f")
+Attestation(1, 1, "04223314cdffec8740150afe46db3575fae840362b137316c0d222a071607d61b2fd40abb2652a7fea20e3bb3e64dc6d495d59823d143c53c4fe4059c5ff16e406")
+Attestation(2, 1, "04c370d4013107a98dfef01d6db5bb3419deb9299535f0be47f05939a78b314a3c29b51fcaa9b3d46fa382c995456af50cd57fb017c0ce05e4a31864a79b8fbfd6")

 # This path do not need a verified PIN to sign messages.
@@ -40,8 +59,6 @@
 @ledger.dongle ?= {}
 _.extend @ledger.dongle,
   States: States
-  Attestation: Attestation
-  BetaAttestation: BetaAttestation
   BitIdRootPath: BitIdRootPath

 ###
@@ -82,6 +99,7 @@

   constructor: (card) ->
     super
+    @_attestation = null
     @_xpubs = _.clone(@_xpubs)
     @id = card.deviceId
     @deviceId = card.deviceId
@@ -227,12 +245,11 @@
   # Verify that dongle firmware is "official".
   # @param [Function] callback Optional argument
   # @return [Q.Promise]
-  isCertified: (callback=undefined) -> @_checkCertification(Attestation, callback)
-
-  isBetaCertified: (callback=undefined) ->
-    @_checkCertification(BetaAttestation, callback)
-
-  _checkCertification: (Attestation, callback) ->
+  isCertified: (callback=undefined) -> @_checkCertification(no, callback)
+
+  isBetaCertified: (callback=undefined) -> @_checkCertification(yes, callback)
+
+  _checkCertification: (isBeta, callback = undefined) ->
     _btchipQueue.enqueue "checkCertification", =>
       d = ledger.defer(callback)
       return d.resolve(true).promise if @getIntFirmwareVersion() < ledger.dongle.Firmwares.V_L_1_0_0
@@ -249,11 +266,16 @@
         dataSigBytes = (parseInt(n,16) for n in dataSig.match(/\w\w/g))
         sha = new JSUCrypt.hash.SHA256()
         domain = JSUCrypt.ECFp.getEcDomainByName("secp256k1")
+        if isBeta
+          Attestation = Attestations["0000000000000000"]
+        else
+          Attestation = Attestations[result.bytes(0, 8).toString()]
         affinePoint = new JSUCrypt.ECFp.AffinePoint(Attestation.xPoint, Attestation.yPoint)
         pubkey = new JSUCrypt.key.EcFpPublicKey(256, domain, affinePoint)
         ecsig = new JSUCrypt.signature.ECDSA(sha)
         ecsig.init(pubkey, JSUCrypt.signature.MODE_VERIFY)
         if ecsig.verify(dataToSign, dataSigBytes)
+          @_attestation = Attestation
           d.resolve(this)
         else
           d.rejectWithError(Errors.DongleNotCertified)
@@ -262,6 +284,8 @@
         d.rejectWithError(Errors.CommunicationError, err)
       .done()
       d.promise
+
+  getAttestation: -> @_attestation

   # Return asynchronously state. Wait until a state is set.
   # @param [Function] callback Optional argument


Reset formula when setting value

Reset formula when setting value

--- src/cell.coffee
+++ src/cell.coffee
@@ -83,7 +83,7 @@
         else
           val = @workbook._.sst.add(val)
           @_.type = "s"
-
+        @_.formula = undefined
         @_.value = val

     "formula":


better placeholder

better placeholder

--- jsapp/xlform/src/view.acceptedFiles.coffee
+++ jsapp/xlform/src/view.acceptedFiles.coffee
@@ -1,6 +1,7 @@
 Backbone = require 'backbone'
 $baseView = require './view.pluggedIn.backboneView'
 $viewTemplates = require './view.templates'
+_t = require('utils').t

 module.exports = do ->
   class AcceptedFilesView extends $baseView
@@ -8,7 +9,7 @@
     events: {
       'input input': 'onChange'
     }
-    placeholder: '.pdf,.doc,.odt,.docx'
+    placeholder: _t('e.g. ".pdf,.doc,.odt"')

     initialize: ({@rowView, @acceptedFiles=''}) -> return


Revert "setup: Execute participant command via shell"

Seems to be causing some issues on Travis
This reverts commit 84187d7c21de090af2b5bfe1fa40366e1574e92a.

--- src/setup.coffee
+++ src/setup.coffee
@@ -23,11 +23,13 @@
   env['MSGFLO_BROKER'] = options.broker
   childoptions =
     env: env
-  debug 'participant process start', cmd
-  child = child_process.exec cmd, childoptions
+  prog = cmd.split(' ')[0]
+  args = cmd.split(' ').splice(1)
+  debug 'participant process start', prog, args.join(' ')
+  child = child_process.spawn prog, args, childoptions
   returned = false
   child.on 'error', (err) ->
-    debug 'participant error', cmd
+    debug 'participant error', prog, args.join(' ')
     return if returned
     returned = true
     return callback err, child


Update src/skype.coffee

Update src/skype.coffee

Renamed env var to HUBOT_SKYPE_PYTHON to match existing vars

--- src/skype.coffee
+++ src/skype.coffee
@@ -17,7 +17,7 @@
     stdin = process.openStdin()
     stdout = process.stdout
     pyScriptPath = __dirname+'/skype.py'
-    py = process.env.HUBOT_SKYPE_ADAPTER_PYTHON
+    py = process.env.HUBOT_SKYPE_PYTHON

     if (!py)
         if (process.platform == 'win32')


:lipstick: Use consistent switch alignment

--- src/pattern.coffee
+++ src/pattern.coffee
@@ -68,8 +68,10 @@
               escaped.push("\\#{character}")
             else
               escaped.push(placeholder)
-          when 'z' then escaped.push('$(?!\n)(?<!\n)')
-          else escaped.push("\\#{character}")
+          when 'z'
+            escaped.push('$(?!\n)(?<!\n)')
+          else
+            escaped.push("\\#{character}")
         escape = false
       else if character is '\\'
         escape = true


fixed a bad test

fixed a bad test

--- test/searching.coffee
+++ test/searching.coffee
@@ -81,9 +81,11 @@

     it 'automated testing', ->

-      for i in [0...100]
-        list = ((Math.floor Math.random()*10000) for i in [0...1000])
-        list.sort (a, b)=> a - b
-        index = list.indexOf list[(Math.floor Math.random()*999)]
+      for i in [0...10]
+        protoList = ((Math.floor Math.random()*10000) for i in [0...1000])
+        protoList.sort (a, b)=> a - b
+        list = []
+        list.push item for item in list when (list.indexOf item) > -1
+        index = list.indexOf list[(Math.floor Math.random()*(list.length-3))]
         expect(fn list, list[index]).to.equal index


Remove brain reset save interval

Remove brain reset save interval

This should be set by the user's configuration / brain defaults.

--- src/steam.coffee
+++ src/steam.coffee
@@ -201,7 +201,7 @@
         DOTA_HEROES = heroes

     # TODO: Cleaner dependency
-    robot.brain.resetSaveInterval 1800
+    #robot.brain.resetSaveInterval 1800
     _brain = robot.brain

 _PossessionModifier = (noun) ->


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/stubs.coffee
+++ src/stubs.coffee
@@ -1,9 +1,3 @@
-###*
-Non-standard
-
-@name toSource
-@methodOf Boolean#
-###
 ###*
 Returns a string representing the specified Boolean object.


Wait until results have been written to STDOUT.

--- lib/guard/jasmine/phantomjs/run-jasmine.coffee
+++ lib/guard/jasmine/phantomjs/run-jasmine.coffee
@@ -38,7 +38,7 @@
 # Test if the specs have finished.
 #
 specsReady = ->
-  page.evaluate -> if document.body.querySelector('.finished-at') then true else false
+  page.evaluate -> window.resultReceived

 #
 # SCRIPT START
@@ -84,6 +84,7 @@
       page.addLogs(suite)

     console.log JSON.stringify(result, undefined, 2)
+    page.evaluate -> window.resultReceived = true

   else if /^SPEC_START: (\d+)$/.test(msg)
     currentSpecId = Number(RegExp.$1)
@@ -227,6 +228,7 @@
     # Attach the console reporter when the document is ready.
     #
     window.onload = ->
+      window.resultReceived = false
       jasmine.getEnv().addReporter(new ConsoleReporter())

 # Open web page and run the Jasmine test runner


Fix reference in tasks

Fix reference in tasks

--- cake/tasks.coffee
+++ cake/tasks.coffee
@@ -120,4 +120,4 @@
     npm:
       command:      'help:npm'
       description:  'Help for those running commands from npm\n'
-      task:         tasks.help.npm+      task:         -> do Help.npm


Handling invalid keys.

Handling invalid keys.

- Fixes #5.

--- lib/jumpy-view.coffee
+++ lib/jumpy-view.coffee
@@ -52,6 +52,8 @@
           Math.round(val / 10) * 10

       labelElement = atom.workspaceView.find(".jumpy.#{@firstChar}#{@secondChar}").get(0)
+      return null unless labelElement
+
       labelLocation = labelElement.getBoundingClientRect()
       lines = atom.workspaceView.find('.lines')
       offsetTop = lines.get(0).offsetTop


Refactor Session to be more testable

Refactor Session to be more testable

--- src/session.coffee
+++ src/session.coffee
@@ -12,24 +12,27 @@

       @_cleanUpCookies()

+      @yogurt_session = @_getCookieYogurtSession()
       @analytics_session = @_getCookieAnalyticsSession()
-      @yogurt_session = Biskoto.get(Settings.cookies.yogurt.name)

-      # Always create third party cookie on analytics domain
-      # if on create phase
-      if @analytics_session isnt null and @yogurt_session is null
-        ## TODO: SHOULD BE RE-SET expires ATTRIBUTE IF COOKIE ALREADY EXISTS?
-        @promise.resolve @analytics_session
-      else
-        @socket = @_createSocket @_socketUrl(@yogurt_session)
-        @_getAnalyticsSession()
+      @_establishSession(@yogurt_session, @analytics_session)

     then: (success, fail)-> @promise.then(success, fail)

-    _getAnalyticsSession: ->
+    _establishSession: (yogurt_session, analytics_session)->
+      # Always create third party cookie on analytics domain
+      # if on create phase
+      if analytics_session isnt null and yogurt_session is null
+        ## TODO: SHOULD BE RE-SET expires ATTRIBUTE IF COOKIE ALREADY EXISTS?
+        @promise.resolve analytics_session
+      else
+        @socket = @_createSocket @_socketUrl(yogurt_session)
+        @_extractAnalyticsSession()
+
+    _extractAnalyticsSession: ->
       Promise.all([
-        @_extractIframe()
-        @_extractGetParam()
+        @_extractFromIframe()
+        @_extractFromGetParam()
       ]).then (results) =>
         analytics_session = results[0] or results[1]

@@ -47,6 +50,8 @@

       if cookie_data.version isnt cookie_settings.version or !cookie_settings.first_party_enabled
         Biskoto.expire(cookie_name)
+
+    _getCookieYogurtSession: -> Biskoto.get(Settings.cookies.yogurt.name)

     _getCookieAnalyticsSession: ->
       data = Biskoto.get(Settings.cookies.analytics.name)
@@ -66,11 +71,11 @@
       @analytics_session = analytics_session
       @promise.resolve analytics_session

-    _extractGetParam: ->
+    _extractFromGetParam: ->
       promise = new Promise()
       promise.resolve URLHelper.extractGetParam(Settings.get_param_name)

-    _extractIframe: ->
+    _extractFromIframe: ->
       @socket.promise = new Promise()
       @socket.postMessage('get_analytics_session')
       @socket.promise


Enable \ref from multiple sub-folders just like with \cite

--- lib/label-view.coffee
+++ lib/label-view.coffee
@@ -1,6 +1,7 @@
 {$,SelectListView} = require 'atom-space-pen-views'
 FindLabels = require './find-labels'
 fs = require 'fs-plus'
+pathModule = require 'path'

 module.exports =
 class LabelView extends SelectListView
@@ -16,15 +17,18 @@
     @editor = editor
     file = editor?.buffer?.file
     basePath = file?.path
-    texRootRex = /%!TEX root = (.+)/g
+    activePaneItemPath = basePath
+   #  texRootRex = /%!TEX root = (.+)/g
+    texRootRex = /%(\s+)?!TEX root(\s+)?=(\s+)?(.+)/g
     while(match = texRootRex.exec(@editor.getText()))
-      absolutFilePath = FindLabels.getAbsolutePath(basePath,match[1])
+      # absolutFilePath = FindLabels.getAbsolutePath(basePath,match[1])
+      absolutFilePath = FindLabels.getAbsolutePath(activePaneItemPath,match[4])
+      basePath = pathModule.dirname(absolutFilePath)
       try
         text = fs.readFileSync(absolutFilePath).toString()
         labels = FindLabels.getLabelsByText(text, absolutFilePath)
       catch error
-        errmsg = 'could not load content of #{absolutFilePath}'
-        atom.notifications.addError(errmsg, { dismissable: true })
+        atom.notifications.addError('could not load content of '+ absolutFilePath, { dismissable: true })
         console.log(error)
     if labels == undefined or labels.length == 0
       labels = FindLabels.getLabelsByText(@editor.getText(), basePath)
@@ -44,7 +48,7 @@
     "label"

   viewForItem: ({label}) ->
-    "
#{label}
"
+     "
#{label}
"

   confirmed: ({label}) ->
     @editor.insertText label


remove inline style

remove inline style

--- app/pages/home.cjsx
+++ app/pages/home.cjsx
@@ -72,7 +72,7 @@
           {for item in aboutItems

-
+

                   <Translate component="h5" content="home.about.#{item}.title" />
                   <Translate component="p" content="home.about.#{item}.content" />


Start refactor touch.coffee

Start refactor touch.coffee

--- src/touch.coffee
+++ src/touch.coffee
@@ -1,37 +1,26 @@
-isTouch = do ->
-  el = document.createElement 'div'
-  el.setAttribute 'ongesturestart', 'return;'
-  el.setAttribute 'ontouchstart', 'return;'
-  return 1 if typeof el.ontouchstart is "function"
-  return 2 if typeof el.ongesturestart is "function"
-  return 0
-
-
-
-
 getOffset = (element) ->
   style = getStyle element
-  reg = /matrix\(([0-9-\.,\s]*)\)/
-  tr = style('transform') || style('-webkit-transform') || style('-ms-transform') || ''
-  if reg.test tr
-    tr = reg.exec(tr)[1].split(',')[4].trim() || 0
+  regex = /matrix\(([0-9-\.,\s]*)\)/
+
+  transform = style('transform') or style('-webkit-transform') or style('-ms-transform') or ''
+
+  if regex.test transform
+    transform = reg.exec(tr)[1].split(',')[4].trim() or 0
   else
-    tr = 0
-  return toInt tr
-
-
-
-
-round = do ->
+    transform = 0
+
+  return toInt transform
+
+touchShift = do ->
   length = window.innerHeight * 0.25
-  (t, offset) ->
+
+  return (t, offset) ->
     offset = Math.abs offset
+
     if (t.x0 - t.x > length) or (t.x - t.x0 < length)
       return Math.ceil offset
+
     return Math.floor offset
-
-
-

 class Touch

@@ -124,63 +113,96 @@

 Chocolate::initTouch = (env) ->
-  return false if isTouch is 0
+  return false if touchType is 0

   isThumbing = false
   isClosing = false
+
   max = -1
-
   opacity = 1

   overlay = @overlay
-
-  transparent = (n) ->
-    opacity = n
-    setStyle overlay, opacity: n
+  slider = @slider
+
+  transparent = (level) ->
+    opacity = level
+    setStyle overlay, opacity: level
+    return

   addClass overlay, 'touch'
-  addEvent @slider, 'click', (event) ->
-    event.preventDefault()
-    event.stopPropagation()
-
-  addEvent @slider, 'hover', (event) ->
-    event.preventDefault()
-    event.stopPropagation()
-
-  addEvent @slider, ['transitionend', 'webkitTransitionEnd'], =>
-    removeClass @slider, 'animated'
-
-  addEvent overlay, ['transitionend', 'webkitTransitionEnd'], =>
+
+  addEvent slider, 'click', (event) ->
+    stop event
+    return
+
+  addEvent slider, 'hover', (event) ->
+    stop event
+    return
+
+  addEvent slider, 'transitionend', ->
+    removeClass slider, 'animated'
+    return
+
+  addEvent slider, 'webkitTransitionEnd', ->
+    removeClass slider, 'animated'
+    return
+
+  finish =>
     removeClass overlay, 'animated'
     return unless isClosing
+
     @close()
     transparent 1

+    return
+
+  addEvent overlay, 'transitionend', =>
+    finish()
+    return
+
+  addEvent overlay, 'webkitTransitionEnd', =>
+    finish()
+    return
+
+  # addEvent overlay, 'touchend', (event) ->
+  #   touch.end()
+  #
+  # addEvent overlay, 'touchcancel', (event) ->
+  #   touch.cancel()
+  #
+  # addEvent overlay, 'touchleave', (event) ->
+  #   touch.end()
+
+
+
+
   t = new Touch overlay,
     start: (t) ->
-      false
+      return false

     move: (t) =>
-      s = getOffset @slider
+      s = getOffset slider
+
       dx = Math.abs(t.x0 - t.x)
       dy = Math.abs(t.y0 - t.y)
+
       if dx > dy
         isThumbing = true
         isClosing = false
         transparent 1
-        translate @slider, s + t.dx
+        translate slider, s + t.dx
       else
         isClosing = true
         isThumbing = false
-        transparent Math.round((1 - dy / env.h)*100)/100
+        transparent Math.round((1 - dy / env.h) * 100) / 100
       true

     end: (t) =>
       if isThumbing
-        addClass @slider, 'animated'
+        addClass slider, 'animated'
         max = @storage.length() - 1 if max is -1
-        s = getOffset @slider
-        s = round t, (s / env.w)
+        s = getOffset slider
+        s = touchShift t, (s / env.w)
         @select squeeze s, 0, max
       if isClosing
         addClass overlay, 'animated'
@@ -191,4 +213,15 @@
           isClosing = false
       false

-  true
+  return true
+
+touchType = do ->
+  element = document.createElement 'div'
+
+  element.setAttribute 'ongesturestart', 'return'
+  return 2 if typeof element.ongesturestart is 'function'
+
+  element.setAttribute 'ontouchstart', 'return'
+  return 1 if typeof element.ontouchstart is 'function'
+
+  return 0


Remove jQuery from up.hungry

Remove jQuery from up.hungry

--- lib/assets/javascripts/unpoly/radio.coffee
+++ lib/assets/javascripts/unpoly/radio.coffee
@@ -7,7 +7,7 @@

 @class up.radio
 ###
-up.radio = (($) ->
+up.radio = do ->

   u = up.util

@@ -58,5 +58,3 @@

   config: config
   hungrySelector: hungrySelector
-
-)(jQuery)


include elevation in dem file

include elevation in dem file

--- src/data.coffee
+++ src/data.coffee
@@ -172,11 +172,14 @@
     exec("gdal_contour
           data/contours/#{region}.tiff
           data/contours/#{region}.shp
+          -a elevation
           -i 100")
   .then ->
     exec("topojson
           contour=data/contours/#{region}.shp
           -o data/contours/#{region}.topojson
+          --id-property ID
+          -p elevation
           -s .00000000001")
   .done ->
     demDone.resolve()


Prefer listening to single Caman instance's events

--- assets/js/gimme/gimme.coffee
+++ assets/js/gimme/gimme.coffee
@@ -40,8 +40,10 @@

 	Caman.remoteProxy = "proxy";

-	Caman.Event.listen "processStart", () ->
+	camanImage = Caman "#uploaded-marker"
+
+	Caman.Event.listen camanImage, "processStart", ->
 		console.log 'started'

-	Caman.Event.listen "processStart", () ->
+	Caman.Event.listen camanImage, "processStart", ->
 		console.log "ended"


Points should already exist

Points should already exist

--- scripts/points.coffee
+++ scripts/points.coffee
@@ -126,5 +126,4 @@
     robot.respond /allpoints$/i, (msg) ->
         if robot.auth.hasRole(msg.envelope.user, ['admin', 'moderator'])

-            allpoints = [robot.brain.data.points]
-            msg.send "Stored points: #{allpoints}"
+            msg.send "Stored points: #{points}"


Buffer pusher updates to RecordArrays until content is set

When collections are loading as ajax requests, we may still get pusher
updates, which will try to add records to these collections. In order to
make it work nicely, we should wait until Ajax request finishes (ie.
content of record array is set) and only then add objects from pusher

--- assets/scripts/travis.coffee
+++ assets/scripts/travis.coffee
@@ -31,6 +31,34 @@
     @setItem(k, null)
   clear: ->
     @set('storage', {})
+
+Ember.RecordArray.reopen
+  # TODO: ember.js changed a way ArrayProxies behave, so that check for content is done
+  #       in _replace method. I should not be overriding it, because it's private, but
+  #       there is no easy other way to do it at this point
+  _replace: (index, removedCount, records) ->
+    # in Travis it's sometimes the case that we add new records to RecordArrays
+    # from pusher before its content has loaded from an ajax query. In order to handle
+    # this case nicer I'm extending record array to buffer those records and push them
+    # to content when it's available
+    @bufferedRecords = [] unless @bufferedRecords
+
+    if !@get('content')
+      for record in records
+        @bufferedRecords.pushObject(record) unless @bufferedRecords.contains(record)
+
+      records = []
+
+    # call super only if there's anything more to add
+    if removedCount || records.length
+      @_super(index, removedCount, records)
+
+  contentDidChange: (->
+    if (content = @get('content')) && @bufferedRecords && @bufferedRecords.length
+      for record in @bufferedRecords
+        content.pushObject(record) unless content.contains(record)
+      @bufferedRecords = []
+  ).observes('content')

 window.Travis = TravisApplication.create(
   LOG_TRANSITIONS: true


Zooniverse Talk copy edit

Zooniverse Talk copy edit

--- app/talk/index.cjsx
+++ app/talk/index.cjsx
@@ -14,7 +14,7 @@

   render: ->

-
Talk!

+
Zooniverse Talk

       <TalkBreadcrumbs {...@props} />

       <form className="talk-search-form" onSubmit={ @onSearchSubmit }>


:art: Render Messages in PanelView

:art: Render Messages in PanelView

--- lib/panel-view.coffee
+++ lib/panel-view.coffee
@@ -1,9 +1,9 @@
 class PanelView extends HTMLElement
-  registerModel: (Model)->
-    @Model = Model
+  initialize: (@Model, @Linter)->
     @Model.View = this
   createdCallback: ->
     this.id = 'linter-panel'
   render: (Messages)->
-    this.innerHTML = Messages.length
+    Messages.forEach (Message)=>
+      @appendChild @Linter.View.messageLine Message, @Model.Type is 'project'
 module.exports = PanelView = document.registerElement('linter-panel-view', {prototype: PanelView.prototype})


Add test for null option

Add test for null option

--- test/gulp-rebuild-html-test.coffee
+++ test/gulp-rebuild-html-test.coffee
@@ -54,6 +54,9 @@
         rebuildStream.write new File contents: new Buffer source
         rebuildStream.end()

+    it "should do nothing with null option", createRunner null, html, html
+    it "should do nothing without any option", createRunner {}, html, html
+
     it "should replace doctype", createRunner
       onprocessinginstruction: (name, value) ->
         '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">'
@@ -152,6 +155,9 @@
             contentStream.end()
         writeChunk()

+    it "should do nothing with null option", createRunner null, htmlChunks, html
+    it "should do nothing without any option", createRunner {}, htmlChunks, html
+
     it "should replace doctype", createRunner
       onprocessinginstruction: (name, value) ->
         '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">'


Disable quickcheck test on travis

Disable quickcheck test on travis

--- test/integration/quickcheck.coffee
+++ test/integration/quickcheck.coffee
@@ -1,3 +1,11 @@
+# Disable this test on travis because it's not simple as green or red.
+# It can fail for reasons other than a bug in cozy-desktop.
+# For example, it can be that the test has not waited long enough after a
+# conflict between 2 folders with many files. Cozy-desktop will manage the
+# situation but it takes several minutes for that.
+return if process.env.TRAVIS
+
+
 child  = require 'child_process'
 clone  = require 'lodash.clone'
 del    = require 'del'


fixes

fixes

--- server/startup.coffee
+++ server/startup.coffee
@@ -22,7 +22,7 @@
         TextDB.insertTranslations {name: "Mission 1st paragraph", html_tag: "mission_dialog1", category: "our mission", text_content: "Organized as a result of a close collaboration between the students of the Bachelor program in the biopharmaceutical science field at the pharmacy faculty of Montreal University and the DESS department and superiors cycles of the same university, the colloquim aims to promote and instigate the discovery of the biopharmaceutical and pharmacology science programs, these being respectively provided within these institutes. After this event, students from these areas are called to exchange on the many aspects of drug development, from basic research to the marketing of therapeutic agents. In addition, lectures and interviews with many experts in industrial, clinical, academic and business fields are expected to inform young academics regarding innovative research topics, state some of pharmacy issues and explain the role and importance of the various facets of the latter."}, {fr: {text_content: "Organisé à la suite d’une étroite collaboration entre les étudiants du programme de baccalauréats en sciences biopharmaceutiques de la faculté de pharmacie de l'Université de Montréal, ainsi que le département du DESS et des cycles supérieurs de cette même université, le Pharmacolloque vise à promouvoir et instiguer à la découverte des programmes de baccalauréats en sciences biopharmaceutiques et en pharmacologie, ceux-ci étant respectivement offerts au sein de ces instituts. Au terme de cet évènement, les étudiants provenant de ces domaines sont appelés à tergiverser sur les multiples aspects du développement du médicament, soit de la recherche fondamentale jusqu’à la mise en marché d’agents thérapeutiques. De plus, conférences et entretiens avec de nombreux experts des champs industriels, cliniques, universitaires et commerciaux sont prévus afin d’informer la relève universitaire en ce qui concerne des sujets de recherche novateurs, en plus d’énoncer certains enjeux du domaine pharmaceutique et d’expliquer le rôle et l’importance des différentes facettes de ce dernier."}}
         TextDB.insertTranslations {name: "Mission 2nd paragraph", html_tag: "mission_dialog2", category: "our mission", text_content: "Following these exchanges, all students present will then be invited to put it to work and integrate their knowledge by participating in simulation exercises and solving probable and applied problems. The organizing committee finally gives an award highlighting the teams that have demonstrated their knowledge, initiative and critical mind throughout the exercise. Beyond being an opportunity for the transmission of information, the conference encourages students to interact among themselves or with the professionals present in order to ultimately maintain relevant and substantial scientific discussions."}, {fr: {text_content: "Suivant ces échanges, tous les élèves présents sont ensuite invités à mettre à l’œuvre et intégrer leurs connaissances acquises en participant à des mises en situations et en résolvant des problématiques vraisemblables et appliquées. Le comité organisationnel procède finalement à une remise de prix soulignant les équipes ayant démontré leur savoir, leur initiative et leurs esprits critiques tout au long du déroulement de l’exercice. En plus d’être un moment propice à la transmission d’information, le colloque incite les étudiants à interagir entre eux ou avec les professionnels présents dans le but d’entretenir ultimement des discussions scientifiques pertinentes et substantielles."}}
         TextDB.insertTranslations {name: "Calendar Title", html_tag: "calendar_title", category: "calendar", text_content: "Schedule of activities for the event"}, {fr: {text_content: "Horaire des activités de l'événement"}}
-        TextDB.insertTranslations {name: "Day 1", html_tag: "day_1", category: "calendar", text_content: "January 9th 2016"}, {fr: {text_content: "9 janvier 2016"}}
+        TextDB.insertTranslations {name: "Day 1", html_tag: "day_1", category: "calendar", text_content: "January 9th 2017"}, {fr: {text_content: "9 janvier 2016"}}
         TextDB.insertTranslations {name: "Map Title", html_tag: "map_title", category: "how to get there", text_content: "We're pleased to welcome you at the following location: "}, {fr: {text_content: "Nous sommes heureux de pouvoir vous accueillir à l'endroit suivant: "}}
         TextDB.insertTranslations {name: "Event Location", html_tag: "event_location", category: "how to get there", text_content: "Pavillon Jean Coutu"}, {fr: {text_content: "Pavillon Jean Coutu"}}
         TextDB.insertTranslations {name: "Event Address", html_tag: "event_address", category: "how to get there", text_content: ", 2940 Chemin de Polytechnique, Montreal, QC, H3T 1J4"}, {fr: {text_content: ", 2940 chemin de Polytechnique, Montréal, QC, H3T1J4"}}


Corrected rest-xml protocol test

Corrected rest-xml protocol test

--- test/protocol/rest_xml.spec.coffee
+++ test/protocol/rest_xml.spec.coffee
@@ -338,4 +338,4 @@
               Baz: type: 'string'
       extractData '<Bar><Baz>Buffer data</Baz></Bar>'
       expect(response.data.Foo).to.equal('foo')
-      expect(response.data.Baz).to.equal('Buffer data')
+      expect(response.data.Bar.Baz).to.equal('Buffer data')


updated baseview orientation stuff to handle onInit and change. Means only need one function with layout settings in the views

--- Framework/Views/BaseView.coffee
+++ Framework/Views/BaseView.coffee
@@ -64,6 +64,12 @@
     @uiInitialised = true

   onInit: ->
+    if Ti.Platform.osname == "ipad"
+      if @isPortrait
+        @onPortrait()
+      if @isLandscape
+        @onLandscape()
+
   onFocus: ->

   showSpinner: -> @spinner.show()


Computeprovider: fix missing client passing

--- workers/social/lib/social/models/computeproviders/computeprovider.coffee
+++ workers/social/lib/social/models/computeproviders/computeprovider.coffee
@@ -208,7 +208,7 @@

   @generateStackFromTemplate = (data, options, callback) ->

-    { account, user, group, template } = data
+    { account, user, group, template, client } = data

     stackRevision = template.template?.sum or ''

@@ -305,6 +305,7 @@
         account.addStackTemplate template, (err) ->
           return callback err  if err

+          res.client = client
           ComputeProvider.generateStackFromTemplate res, options, callback


edit parallel sets

edit parallel sets

--- scripts/test-instance/parallel-sets.coffee
+++ scripts/test-instance/parallel-sets.coffee
@@ -30,16 +30,16 @@
     { name: 'dashboard myteam' }
     { name: 'dashboard credentials' }
     { name: 'dashboard utilities' }
-    # { name: 'dashboard welcome' }
+    { name: 'dashboard onboarding' }
+    { name: 'dashboard sidebar' }
+    { name: 'dashboard teambilling' }
   ]

   [
     { name: 'register' }
-    { name: 'login' }
-    { name: 'logout' }
     { name: 'unittests' }
-    # { name: 'account accountsettings' }
-    # { name: 'teams inviteteams' }
+    { name: 'dashboard myaccount' }
+    { name: 'teams teams' }
   ]

   [


Correctly use indicator.validateIndicatorDataFields in updateIndicatorData

--- server/mixins/update_indicator_data.coffee
+++ server/mixins/update_indicator_data.coffee
@@ -164,9 +164,11 @@
       @queryIndicatorData(
       ).then( (response) =>
         newIndicatorData = @convertResponseToIndicatorData(response.body)
-        newIndicatorData = @validateIndicatorDataFields(newIndicatorData)
-        newIndicatorData = @convertIndicatorDataFields(newIndicatorData)
-        @replaceIndicatorData(newIndicatorData)
+        if @validateIndicatorDataFields(newIndicatorData)
+          newIndicatorData = @convertIndicatorDataFields(newIndicatorData)
+          @replaceIndicatorData(newIndicatorData)
+        else
+          throw new Error("Validation of indicator data fields failed")
       ).then( (indicatorData) ->
         deferred.resolve(indicatorData)
       ).fail( (err) ->


Get user

Get user

--- src/tankipas.coffee
+++ src/tankipas.coffee
@@ -48,3 +48,7 @@
 if isNaN iGap
     console.log error "✘ gap must be a number, '#{ tankipas.gap }' given."
     process.exit 1
+
+# --- get user
+
+sUser = tankipas.user ? no


Pretty-printing support

Pretty-printing support

--- components/Jsonify.coffee
+++ components/Jsonify.coffee
@@ -8,15 +8,19 @@

   constructor: ->
     @raw = false
+    @pretty = false

     @inPorts =
       in: new noflo.Port 'object'
       raw: new noflo.Port 'boolean'
+      pretty: new noflo.Port 'boolean'
     @outPorts =
       out: new noflo.Port 'string'

     @inPorts.raw.on 'data', (raw) =>
       @raw = String(raw) is 'true'
+    @inPorts.pretty.on 'data', (pretty) =>
+      @pretty = String(pretty) is 'true'

     @inPorts.in.on 'begingroup', (group) =>
       @outPorts.out.beginGroup group
@@ -24,6 +28,10 @@
     @inPorts.in.on 'data', (data) =>
       if @raw and _.isString data
         @outPorts.out.send data
+        return
+
+      if @pretty
+        @outPorts.out.send JSON.stringify data, null, 4
         return

       @outPorts.out.send JSON.stringify data


Request: fix disconnection

Request: fix disconnection

--- components/Request.coffee
+++ components/Request.coffee
@@ -16,7 +16,7 @@
         req.abort()
         return
       @outPorts.request.send(req)
-      @outPorts.disconnect()
+      @outPorts.request.disconnect()

     @verb = 'get'
     @path = '/'


Fix chrome object dependency

Fix chrome object dependency

--- src/i18n.coffee
+++ src/i18n.coffee
@@ -7,7 +7,7 @@
 ###
 class I18n extends JSONable
   I18n.jsonable()
-  lang = chrome.i18n.getUILanguage()
+  lang = chrome?.i18n.getUILanguage()

   ###*
   @static
@@ -55,7 +55,7 @@
     Translated message
   ###
   @getMessage: (id, subs...) ->
-    r = chrome.i18n.getMessage(id, subs...)
+    r = chrome?.i18n.getMessage(id, subs) or ""
     if r == ""
       console.warn("No translation for id='#{id}'")
       r = id.replace(/_/g, " ")


Adjust marker color and size to be more visible.

--- app/assets/javascripts/app/controllers/candidates.js.coffee
+++ app/assets/javascripts/app/controllers/candidates.js.coffee
@@ -64,8 +64,8 @@
     ).addTo(map)

     MarkerIcon = L.Icon.extend(options:
-      iconSize: [20, 50]
-      iconAnchor: [10, 25]
+      iconSize: [30, 70]
+      iconAnchor: [15, 35]
       popupAnchor: [-3, -26]
     )

@@ -75,8 +75,8 @@
         candidate.lon
       ],
         icon: new MarkerIcon(
-          iconUrl: "http://api.tiles.mapbox.com/v3/marker/pin-s-#{_i + 1}.png"
-          iconRetinaUrl: "http://api.tiles.mapbox.com/v3/marker/pin-s-#{_i + 1}@2x.png"
+          iconUrl: "http://api.tiles.mapbox.com/v3/marker/pin-m-#{_i + 1}+008472.png"
+          iconRetinaUrl: "http://api.tiles.mapbox.com/v3/marker/pin-m-#{_i + 1}+008472@2x.png"
         )
       ).addTo(map)


TeamUtils: fix minimum size rule (it can be 3)

--- workers/social/lib/social/models/computeproviders/teamutils.coffee
+++ workers/social/lib/social/models/computeproviders/teamutils.coffee
@@ -83,7 +83,7 @@
   rules.push { 'resource.aws_instance.*.root_block_device': [
                  { 'volume_size?': { $typeof: 'number' } }
                  { 'volume_size?': { $lte: storagePerInstance } }
-                 { 'volume_size?': { $gt: 3 } }  # min volume size 3GB
+                 { 'volume_size?': { $gte: 3 } }  # min volume size 3GB
             ] }

   return rules


Correct field name

Correct field name

--- src/SumoCoders/FrameworkCoreBundle/Resources/assets/coffee/framework.form.search.coffee
+++ src/SumoCoders/FrameworkCoreBundle/Resources/assets/coffee/framework.form.search.coffee
@@ -4,7 +4,7 @@
     @_initSearch()

   _initSearch: ->
-    $searchField = $('.searchBox input[name=q]', @form)
+    $searchField = $('input[name=term]', @form)
     route = $(@form).attr('action') + '.json'

     $searchField.autocomplete(


warn on fetch error

warn on fetch error

--- client/code/router/main.coffee
+++ client/code/router/main.coffee
@@ -43,6 +43,8 @@
       success: =>
         view = new Cu.View.DatasetList {collection: window.datasets}
         @appView.showView view
+      error: (x,y,z) ->
+        console.warn 'ERRROR', x, y, z

   tool: (tool) ->
     model = window.tools.get tool


reorder dataset and datasetSettings endpoints in client/code/router/main.coffee

--- client/code/router/main.coffee
+++ client/code/router/main.coffee
@@ -43,17 +43,6 @@
       error: (x,y,z) ->
         console.warn 'ERRROR', x, y, z

-  datasetSettings: (box) ->
-    mod = Cu.Model.Dataset.findOrCreate box: box
-    mod.fetch
-      success: (model) =>
-        subnavView = new Cu.View.DatasetSettingsNav {model: model}
-        contentView = new Cu.View.AppContent {model: model}
-        @appView.showView contentView
-        @subnavView.showView subnavView
-      error: (x,y,z) ->
-        console.warn 'ERRROR', x, y, z
-
   dataset: (box) ->
     mod = Cu.Model.Dataset.findOrCreate box: box
     mod.fetch
@@ -68,6 +57,17 @@
             console.warn 'ERRROR', x, y, z
       error: (model, xhr, options) ->
         console.warn xhr
+
+  datasetSettings: (box) ->
+    mod = Cu.Model.Dataset.findOrCreate box: box
+    mod.fetch
+      success: (model) =>
+        subnavView = new Cu.View.DatasetSettingsNav {model: model}
+        contentView = new Cu.View.AppContent {model: model}
+        @appView.showView contentView
+        @subnavView.showView subnavView
+      error: (x,y,z) ->
+        console.warn 'ERRROR', x, y, z

   view: (datasetID, viewID) ->
     dataset = Cu.Model.Dataset.findOrCreate


Moved creation of routes to before rendering view

--- src/neck.coffee
+++ src/neck.coffee
@@ -271,9 +271,6 @@
     unless @app = opts?.app
       throw "Neck.Router require connection with App Controller"

-    unless @app._yieldList
-      throw "No yields for routing in App"
-
   route:(route, settings)->
     myCallback = (args...)=>
       query = {}
@@ -284,7 +281,7 @@
           query[name] = param if param = args.shift()

       for yieldName, options of (settings.yields or main: controller: settings.controller)
-        throw "No '#{yieldName}' yield defined in App" if !@app._yieldList[yieldName]
+        throw "No '#{yieldName}' yield defined in App" unless @app._yieldList?[yieldName]

         @app._yieldList[yieldName].append (options.controller or options),
           _.extend({}, options.params, query),
@@ -304,6 +301,6 @@
     super

     if @routes
-      @once 'render:after', =>
-        @router = new Neck.Router app: @, routes: @routes
-        Backbone.history.start @history if @history+      @router = new Neck.Router app: @, routes: @routes
+      if @history
+        @once 'render:after', => Backbone.history.start @history


Minimize number of internal component fields to only one.

We use _componentInternals now for all internal component fields. To minimize scope pollution.

--- lib.coffee
+++ lib.coffee
@@ -78,14 +78,16 @@
   # they would have by default. Probably component name should be set in the constructor for such classes, or by calling
   # componentName class method manually on the new class of this new component.
   @componentName: (componentName) ->
+    @_componentInternals ?= {}
+
     # Setter.
     if componentName
-      @_componentName = componentName
+      @_componentInternals.componentName = componentName
       # To allow chaining.
       return @

     # Getter.
-    @_componentName or null
+    @_componentInternals.componentName or null

   # We allow access to the component name through a method so that it can be accessed in templates in an easy way.
   componentName: ->
@@ -95,10 +97,11 @@
   # The order of components is arbitrary and does not necessary match siblings relations in DOM.
   # nameOrComponent is optional and it limits the returned children only to those.
   componentChildren: (nameOrComponent) ->
-    @_componentChildren ?= new ReactiveVar [], arrayReferenceEquals
+    @_componentInternals ?= {}
+    @_componentInternals.componentChildren ?= new ReactiveVar [], arrayReferenceEquals

     # Quick path. Returns a shallow copy.
-    return (child for child in @_componentChildren.get()) unless nameOrComponent
+    return (child for child in @_componentInternals.componentChildren.get()) unless nameOrComponent

     if _.isString nameOrComponent
       @componentChildrenWith (child, parent) =>
@@ -139,34 +142,37 @@
       child for child in @componentChildren() when propertyOrMatcherOrFunction.call @, child, @

   addComponentChild: (componentChild) ->
-    @_componentChildren ?= new ReactiveVar [], arrayReferenceEquals
-    @_componentChildren.set Tracker.nonreactive =>
-      @_componentChildren.get().concat [componentChild]
+    @_componentInternals ?= {}
+    @_componentInternals.componentChildren ?= new ReactiveVar [], arrayReferenceEquals
+    @_componentInternals.componentChildren.set Tracker.nonreactive =>
+      @_componentInternals.componentChildren.get().concat [componentChild]

     # To allow chaining.
     @

   removeComponentChild: (componentChild) ->
-    @_componentChildren ?= new ReactiveVar [], arrayReferenceEquals
-    @_componentChildren.set Tracker.nonreactive =>
-      _.without @_componentChildren.get(), componentChild
+    @_componentInternals ?= {}
+    @_componentInternals.componentChildren ?= new ReactiveVar [], arrayReferenceEquals
+    @_componentInternals.componentChildren.set Tracker.nonreactive =>
+      _.without @_componentInternals.componentChildren.get(), componentChild

     # To allow chaining.
     @

   componentParent: (componentParent) ->
+    @_componentInternals ?= {}
     # We use reference equality here. This makes reactivity not invalidate the
     # computation if the same component instance (by reference) is set as a parent.
-    @_componentParent ?= new ReactiveVar null, (a, b) -> a is b
+    @_componentInternals.componentParent ?= new ReactiveVar null, (a, b) -> a is b

     # Setter.
     unless _.isUndefined componentParent
-      @_componentParent.set componentParent
+      @_componentInternals.componentParent.set componentParent
       # To allow chaining.
       return @

     # Getter.
-    @_componentParent.get()
+    @_componentInternals.componentParent.get()

   @renderComponent: (componentParent) ->
     throw new Error "Not implemented"


Handle else case as well.

Handle else case as well.

--- lib.coffee
+++ lib.coffee
@@ -58,7 +58,7 @@
   # We skip contentBlock views which are injected by Meteor when using
   # block helpers (in addition to block helper view). This matches more
   # the visual structure of templates and not the internal implementation.
-  while view and (not view.template or view.name is '(contentBlock)')
+  while view and (not view.template or view.name is '(contentBlock)' or view.name is '(elseBlock)')
     view = view.originalParentView or view.parentView

   # Body view has template field, but not templateInstance. We return null in that case.


Send error directly to the error port

Send error directly to the error port

--- components/GetHistogram.coffee
+++ components/GetHistogram.coffee
@@ -84,16 +84,16 @@

   noflo.helpers.WirePattern c,
     in: ['canvas']
-    out: ['histogram']
+    out: ['histogram', 'error']
     forwardGroups: true
     async: true
-  , (payload, groups, out, callback) ->
+  , (payload, groups, outPorts, callback) ->
     canvas = payload
     unless canvas?.width > 0 and canvas?.height > 0
       err = new Error "Failed to compute histogram. Canvas has zero dimensions"
-      return callback err
+      outPorts.error.send err
+      do callback
+      return
     histogram = computeHistogram canvas
-    out.send histogram
+    outPorts.histogram.send histogram
     do callback
-
-  c


Reference existing translation: closed order cycle

https://github.com/openfoodfoundation/openfoodnetwork/issues/2465

Viewing a customers subscription could display a translation missing
error.

--- app/assets/javascripts/admin/subscriptions/controllers/orders_panel_controller.js.coffee
+++ app/assets/javascripts/admin/subscriptions/controllers/orders_panel_controller.js.coffee
@@ -16,7 +16,7 @@
     oc = OrderCycles.byID[id]
     return t('js.subscriptions.close_date_not_set') unless oc?.orders_close_at?
     closes_at = moment(oc.orders_close_at)
-    text = if closes_at > moment() then t('js.subscriptions.closes') else t('js.subscription.closed')
+    text = if closes_at > moment() then t('js.subscriptions.closes') else t('js.subscriptions.closed')
     "#{text} #{closes_at.fromNow()}"

   $scope.stateText = (state) -> t("spree.order_state.#{state}")


Fix runaway vote percentage

Fix runaway vote percentage

--- lineman/app/models/proposal_model.coffee
+++ lineman/app/models/proposal_model.coffee
@@ -67,7 +67,7 @@
       numVoted = @numberVoted()
       groupSize = @groupSizeWhenVoting()
       return 0 if numVoted == 0 or groupSize == 0
-      numVoted / groupSize * 100
+      (100 * numVoted / groupSize).toFixed(2)

     groupSizeWhenVoting: ->
       if @isActive()


Clean a little

Clean a little

--- admin/static/coffee/dataexplorer.coffee
+++ admin/static/coffee/dataexplorer.coffee
@@ -39,7 +39,7 @@
             'click .input_query': 'make_suggestion' # Click and not focus for webkit browsers
             'mousedown .suggestion_name_li': 'select_suggestion' # Keep mousedown to compete with blur on .input_query
             'mouseup .suggestion_name_li': 'position_cursor_after_click'
-            'mouseover .suggestion_name_li' : 'highlight_suggestion'
+            'mouseover .suggestion_name_li' : 'mouseover_suggestion'
             'click .clear_query': 'clear_query'
             'click .execute_query': 'execute_query'
             'click .namespace_link': 'write_query_namespace'
@@ -49,31 +49,27 @@

         displaying_full_view: false

+        # We have to keep track of a lot of things because web-kit browsers handle the events keydown, keyup, blur etc... in a strange way.
         current_suggestions: []
         current_highlighted_suggestion: -1
         keypress_is_tab: false
         current_conpleted_query: ''
         query_first_part: ''
         query_last_part: ''
-        refocus_position: '' # Trick for web-kit browsers (the order of events is mousedown, blur, mouseup)
+        refocus_position: ''

         handle_tab: (event) =>
-            if event.which is 9
+            if event.which is 9 # is tab
                 event.preventDefault()
                 @keypress_is_tab = true

-                @.$('.suggestion_name_li').removeClass 'suggestion_name_li_hl'

                 @current_highlighted_suggestion++
                 if @current_highlighted_suggestion >= @current_suggestions.length
                     @current_highlighted_suggestion = 0

                 if @current_suggestions[@current_highlighted_suggestion]?
-                    @.$('.suggestion_name_li').eq(@current_highlighted_suggestion).addClass 'suggestion_name_li_hl'
-
-                    @.$('.suggestion_description').html @current_suggestions[@current_highlighted_suggestion].description
-                    @show_suggestion_description()
-
+                    @highlight_suggestion @current_highlighted_suggestion
                     @write_suggestion @current_suggestions[@current_highlighted_suggestion].suggestion

                     position = (@query_first_part + @current_completed_query + @current_suggestions[@current_highlighted_suggestion].suggestion).length
@@ -88,18 +84,20 @@
         write_suggestion: (suggestion_to_write) =>
             @.$('.input_query').val @query_first_part + @current_completed_query + suggestion_to_write + @query_last_part

-        highlight_suggestion: (event) =>
+        mouseover_suggestion: (event) =>
+            @highlight_suggestion event.target.dataset.id
+
+        highlight_suggestion: (id) =>
             @.$('.suggestion_name_li').removeClass 'suggestion_name_li_hl'
-            @.$(event.target).addClass 'suggestion_name_li_hl'
-
-            @.$('.suggestion_description').html @current_suggestions[event.target.dataset.id].description
+            @.$('.suggestion_name_li').eq(id).addClass 'suggestion_name_li_hl'
+
+            @.$('.suggestion_description').html @current_suggestions[id].description
             @show_suggestion_description()

         position_cursor_after_click: (event) =>
-            @position_cursor @.$(event.target).html().length
+            @position_cursor @.$(event.target).html().length #TODO doesn't work with Firefox

         position_cursor: (position) =>
-            # Set the cursor in the good plac
             @.$('.input_query').focus()
             if @.$('.input_query').get(0)?.setSelectionRange?
                 @.$('.input_query').get(0).setSelectionRange position, position
@@ -263,7 +261,7 @@
                     suggestion: "plot("
                     description: "plot ( x: blabla, y: blabla)"
                 suggestions.push
-                    suggestion: "update"
+                    suggestion: "update("
                     description: "update( where, attribute, value )"
                 @append_suggestion(query, suggestions)
             else