Fix screenPositionForPixelPosition with DisplayLayers

--- src/lines-yardstick.coffee
+++ src/lines-yardstick.coffee
@@ -1,5 +1,6 @@
 TokenIterator = require './token-iterator'
 {Point} = require 'text-buffer'
+{isPairedCharacter} = require './text-utils'

 module.exports =
 class LinesYardstick
@@ -19,63 +20,45 @@
   screenPositionForPixelPosition: (pixelPosition) ->
     targetTop = pixelPosition.top
     targetLeft = pixelPosition.left
-    defaultCharWidth = @model.getDefaultCharWidth()
     row = Math.floor(targetTop / @model.getLineHeightInPixels())
     targetLeft = 0 if row < 0
     targetLeft = Infinity if row > @model.getLastScreenRow()
     row = Math.min(row, @model.getLastScreenRow())
     row = Math.max(0, row)

-    line = @model.tokenizedLineForScreenRow(row)
-    lineNode = @lineNodesProvider.lineNodeForLineIdAndScreenRow(line?.id, row)
+    lineNode = @lineNodesProvider.lineNodeForScreenRow(row)
+    return Point(row, 0) unless lineNode

-    return Point(row, 0) unless lineNode? and line?
+    textNodes = @lineNodesProvider.textNodesForScreenRow(row)
+    lineOffset = lineNode.getBoundingClientRect().left
+    targetLeft += lineOffset

-    textNodes = @lineNodesProvider.textNodesForLineIdAndScreenRow(line.id, row)
-    column = 0
-    previousColumn = 0
-    previousLeft = 0
+    textNodeStartColumn = 0
+    for textNode in textNodes
+      {length: textNodeLength, textContent: textNodeContent} = textNode
+      textNodeRight = @clientRectForRange(textNode, 0, textNodeLength).right

-    @tokenIterator.reset(line, false)
-    while @tokenIterator.next()
-      text = @tokenIterator.getText()
-      textIndex = 0
-      while textIndex < text.length
-        if @tokenIterator.isPairedCharacter()
-          char = text
-          charLength = 2
-          textIndex += 2
-        else
-          char = text[textIndex]
-          charLength = 1
-          textIndex++
+      if textNodeRight > targetLeft
+        characterIndex = 0
+        while characterIndex < textNodeLength
+          if isPairedCharacter(textNodeContent, characterIndex)
+            nextCharacterIndex = characterIndex + 2
+          else
+            nextCharacterIndex = characterIndex + 1

-        unless textNode?
-          textNode = textNodes.shift()
-          textNodeLength = textNode.textContent.length
-          textNodeIndex = 0
-          nextTextNodeIndex = textNodeLength
+          rangeRect = @clientRectForRange(textNode, characterIndex, nextCharacterIndex)

-        while nextTextNodeIndex <= column
-          textNode = textNodes.shift()
-          textNodeLength = textNode.textContent.length
-          textNodeIndex = nextTextNodeIndex
-          nextTextNodeIndex = textNodeIndex + textNodeLength
+          if rangeRect.right > targetLeft
+            if targetLeft <= ((rangeRect.left + rangeRect.right) / 2)
+              return Point(row, textNodeStartColumn + characterIndex)
+            else
+              return Point(row, textNodeStartColumn + nextCharacterIndex)
+          else
+            characterIndex = nextCharacterIndex

-        indexWithinTextNode = column - textNodeIndex
-        left = @leftPixelPositionForCharInTextNode(lineNode, textNode, indexWithinTextNode)
-        charWidth = left - previousLeft
+      textNodeStartColumn += textNodeLength

-        return Point(row, previousColumn) if targetLeft <= previousLeft + (charWidth / 2)
-
-        previousLeft = left
-        previousColumn = column
-        column += charLength
-
-    if targetLeft <= previousLeft + (charWidth / 2)
-      Point(row, previousColumn)
-    else
-      Point(row, column)
+    Point(row, textNodeStartColumn)

   pixelPositionForScreenPosition: (screenPosition) ->
     targetRow = screenPosition.row
@@ -96,19 +79,24 @@
       return cachedPosition

     textNodes = @lineNodesProvider.textNodesForScreenRow(row)
-    textNodeStartIndex = 0
+    textNodeStartColumn = 0

     for textNode in textNodes
-      textNodeEndIndex = textNodeStartIndex + textNode.textContent.length
-      if textNodeEndIndex > column
-        indexInTextNode = column - textNodeStartIndex
+      textNodeEndColumn = textNodeStartColumn + textNode.textContent.length
+      if textNodeEndColumn > column
+        indexInTextNode = column - textNodeStartColumn
         break
       else
-        textNodeStartIndex = textNodeEndIndex
+        textNodeStartColumn = textNodeEndColumn

     if textNode?
       indexInTextNode ?= textNode.textContent.length
-      leftPixelPosition = @leftPixelPositionForCharInTextNode(lineNode, textNode, indexInTextNode)
+      lineOffset = lineNode.getBoundingClientRect().left
+      if indexInTextNode is 0
+        leftPixelPosition = @clientRectForRange(textNode, 0, 1).left
+      else
+        leftPixelPosition = @clientRectForRange(textNode, 0, indexInTextNode).right
+      leftPixelPosition -= lineOffset

       @leftPixelPositionCache[lineId] ?= {}
       @leftPixelPositionCache[lineId][column] = leftPixelPosition
@@ -116,18 +104,7 @@
     else
       0

-  leftPixelPositionForCharInTextNode: (lineNode, textNode, charIndex) ->
-    if charIndex is 0
-      width = 0
-    else
-      @rangeForMeasurement.setStart(textNode, 0)
-      @rangeForMeasurement.setEnd(textNode, charIndex)
-      width = @rangeForMeasurement.getBoundingClientRect().width
-
-    @rangeForMeasurement.setStart(textNode, 0)
-    @rangeForMeasurement.setEnd(textNode, textNode.textContent.length)
-    left = @rangeForMeasurement.getBoundingClientRect().left
-
-    offset = lineNode.getBoundingClientRect().left
-
-    left + width - offset
+  clientRectForRange: (textNode, startIndex, endIndex) ->
+    @rangeForMeasurement.setStart(textNode, startIndex)
+    @rangeForMeasurement.setEnd(textNode, endIndex)
+    @rangeForMeasurement.getBoundingClientRect()


Assume empty source string by default

Assume empty source string by default

--- src/liquid/template.coffee
+++ src/liquid/template.coffee
@@ -16,7 +16,7 @@

   # Parse source code.
   # Returns self for easy chaining
-  parse: (@engine, source) ->
+  parse: (@engine, source = "") ->
     @tags = @engine.tags
     @root = new Liquid.Document @, @tokenize(source)
     @


Added `window.` for consistency

Added `window.` for consistency

--- src/helpers/mathjax.coffee
+++ src/helpers/mathjax.coffee
@@ -68,7 +68,7 @@
   if window.MathJax?.Hub
     window.MathJax.Hub.Config(MATHJAX_CONFIG)
     # Does not seem to work when passed to Config
-    MathJax.Hub.processSectionDelay = 0
+    window.MathJax.Hub.processSectionDelay = 0
     configuredCallback()
   else
     # If the MathJax.js file has not loaded yet:


Update weather.coffee to use forecast.io

Google has deprecated/removed their weather API. Use the much nicer
forecast.io API instead.

--- src/scripts/weather.coffee
+++ src/scripts/weather.coffee
@@ -1,88 +1,94 @@
 # Description:
-#   Returns weather information from Google
-#
-# Dependencies:
-#   "jsdom": "0.2.14"
+#   Returns weather information from Forecast.io with a sprinkling of Google maps.
 #
 # Configuration:
 #   HUBOT_WEATHER_CELSIUS - Display in celsius
+#   HUBOT_FORECAST_API_KEY - Forecast.io API Key
 #
 # Commands:
-#   hubot weather <city> - Get the weather for a location
-#   hubot forecast <city> - Get the forecast for a location
+#   hubot weather <city> - Get the weather for a location.
+#   hubot forecast <city> - Get the 3 day forecast for a location.
 #
 # Author:
 #   markstory
 #   mbmccormick
-
-jsdom = require 'jsdom'
 env = process.env

+forecastIoUrl = 'https://api.forecast.io/forecast/' + process.env.HUBOT_FORECAST_API_KEY + '/'
+googleMapUrl = 'http://maps.googleapis.com/maps/api/geocode/json'
+
+lookupAddress = (msg, location, cb) ->
+  msg.http(googleMapUrl).query(address: location, sensor: true)
+    .get() (err, res, body) ->
+      try
+        body = JSON.parse body
+        coords = body.results[0].geometry.location
+      catch err
+        err = "Could not find #{location}"
+        return cb(msg, null, err)
+      cb(msg, coords, err)
+
+lookupWeather = (msg, coords, err) ->
+  return msg.send err if err
+  url = forecastIoUrl + coords.lat + ',' + coords.lng
+
+  msg.http(url).query(units: 'ca').get() (err, res, body) ->
+    return msg.send 'Could not get weather data' if err
+    try
+      body = JSON.parse body
+      current = body.currently
+    catch err
+      return msg.send "Could not parse weather data."
+    humidity = current.humidity * 100
+    temperature = getTemp(current.temperature)
+    text = "It is currently #{temperature} #{current.summary}, #{humidity}% humidity"
+    msg.send text
+
+lookupForecast = (msg, coords, err) ->
+  return msg.send err if err
+
+  url = forecastIoUrl + coords.lat + ',' + coords.lng
+  msg.http(url).query(units: 'ca').get() (err, res, body) ->
+    return msg.send 'Could not get weather forecast' if err
+    try
+      body = JSON.parse body
+      forecast = body.daily.data
+      today = forecast[0]
+      tomorrow = forecast[1]
+      dayAfter = forecast[2]
+    catch err
+      return msg.send 'Unable to parse forecast data.'
+    text = "The weather for:\n"
+
+    appendText = (text, data) ->
+      dateToday = new Date(data.time * 1000)
+      month = dateToday.getMonth() + 1
+      day = dateToday.getDate()
+      humidity = data.humidity * 100
+      maxTemp = getTemp data.temperatureMax
+      minTemp = getTemp data.temperatureMin
+
+      text += "#{month}/#{day} - High of #{maxTemp}ºc, low of: #{minTemp}ºc "
+      text += "#{data.summary} #{humidity}% humidity\n"
+      text
+
+    text = appendText text, today
+    text = appendText text, tomorrow
+    text = appendText text, dayAfter
+    msg.send text
+
+getTemp = (c) ->
+  if env.HUBOT_WEATHER_CELSIUS
+    return c.toFixed(2) + "ºC"
+  return ((c * 1.8) + 32) + "ºF"
+
+
 module.exports = (robot) ->
-  robot.respond /forecast(?: me|for|in)?\s(.*)/, (msg) ->
-    query msg, (body, err) ->
-      return msg.send err if err

-      city = body.getElementsByTagName("city")[0]
-      return msg.send "Sorry, but I couldn't find that location." if not city or not city.getAttribute
-
-      city = city.getAttribute("data")
+  robot.respond /weather(?: me|for|in)?\s(.*)/i, (msg) ->
+    location = msg.match[1]
+    lookupAddress(msg, location, lookupWeather)

-      strings = []
-
-      strings.push "The forecast for #{city} is as follows:\n"
-      for element in body.getElementsByTagName("forecast_conditions")
-        day = element.getElementsByTagName("day_of_week")[0].getAttribute("data")
-
-        if env.HUBOT_WEATHER_CELSIUS
-          low = convertTempToCelsius(element.getElementsByTagName("low")[0].getAttribute("data")) + "ºC"
-        else
-          low = element.getElementsByTagName("low")[0].getAttribute("data") + "ºF"
-
-        if env.HUBOT_WEATHER_CELSIUS
-          high = convertTempToCelsius(element.getElementsByTagName("high")[0].getAttribute("data")) + "ºC"
-        else
-          high = element.getElementsByTagName("high")[0].getAttribute("data") + "ºF"
-
-        condition = element.getElementsByTagName("condition")[0].getAttribute("data")
-        strings.push "#{day}: #{condition} with a high of #{high} and a low of #{low}."
-
-      msg.send strings.join "\n"
-
-  robot.respond /weather(?: me|for|in)?\s(.*)/, (msg) ->
-    query msg, (body, err) ->
-      return msg.send err if err
-
-      city = body.getElementsByTagName("city")[0]
-      return msg.send "Sorry, but you didn't specify a location." if not city or not city.getAttribute
-
-      city = city.getAttribute("data")
-      currentCondition = body.getElementsByTagName("current_conditions")[0].getAttribute("data")
-      conditions = body.getElementsByTagName("current_conditions")[0].getElementsByTagName("condition")[0].getAttribute("data")
-      humidity = body.getElementsByTagName("current_conditions")[0].getElementsByTagName("humidity")[0].getAttribute("data").split(' ')[1]
-
-      if env.HUBOT_WEATHER_CELSIUS
-        temp = body.getElementsByTagName("current_conditions")[0].getElementsByTagName("temp_c")[0].getAttribute("data") + "ºC"
-      else
-        temp = body.getElementsByTagName("current_conditions")[0].getElementsByTagName("temp_f")[0].getAttribute("data") + "ºF"
-
-      msg.send "Currently in #{city} it is #{conditions} and #{temp} with a humidity of #{humidity}."
-
-  getDom = (xml) ->
-    body = jsdom.jsdom(xml)
-    throw Error("No XML data returned.") if body.getElementsByTagName("weather")[0].childNodes.length == 0
-    body
-
-  convertTempToCelsius = (f) ->
-    ((5 / 9) * (f - 32)).toFixed 0
-
-  query = (msg, cb) ->
+  robot.respond /forecast(?: me|for|in)?\s(.*)/i, (msg) ->
     location = msg.match[1]
-    msg.http("http://www.google.com/ig/api")
-      .query(weather: location)
-      .get() (err, res, body) ->
-        try
-          body = getDom body
-        catch err
-          err = "Could not fetch weather data."
-        cb(body, err)
+    lookupAddress(msg, location, lookupForecast)


Add .node extension to require path

Add .node extension to require path

--- src/scrollbar-style.coffee
+++ src/scrollbar-style.coffee
@@ -1,5 +1,5 @@
 {Behavior} = require 'emissary'
-{ScrollbarStyleObserver} = require('../build/Release/scrollbar-style-observer')
+{ScrollbarStyleObserver} = require('../build/Release/scrollbar-style-observer.node')

 observer = new ScrollbarStyleObserver -> behavior.emitValue(observer.getPreferredScrollbarStyle())
 behavior = new Behavior(observer.getPreferredScrollbarStyle())


allow nested submodule include if they all **belongs-to** the same module, fix #58

--- src/lang/extensions.coffee
+++ src/lang/extensions.coffee
@@ -448,11 +448,12 @@
       m = @lookup 'submodule', @tag
       unless m?
         throw @error "unable to resolve '#{@tag}' submodule"
-      unless @parent.tag is m['belongs-to'].tag
-        throw m.error "requested submodule '#{@tag}' not belongs-to '#{@parent.tag}'"
+
+      unless @root.tag is m['belongs-to'].tag
+        throw m.error "requested submodule '#{@tag}' not belongs-to '#{@root.tag}'"

       # defined as non-enumerable
-      Object.defineProperty m['belongs-to'], 'module', configurable: true, value: @parent
+      Object.defineProperty m['belongs-to'], 'module', configurable: true, value: @root
       for x in m.compile().elements when m.scope[x.kind] is '0..n' and x.kind isnt 'revision'
         #@debug "updating parent with #{x.kind}(#{x.tag})"
         @parent.update x


remove unnecessary copy

remove unnecessary copy

--- subtree/bokehjs/static/coffee/renderers/glyph/ray.coffee
+++ subtree/bokehjs/static/coffee/renderers/glyph/ray.coffee
@@ -36,9 +36,8 @@
     width = @plot_view.view_state.get('width')
     height = @plot_view.view_state.get('height')
     inf_len = 2 * (width + height)
-    @slength = @length[..]
-    for i in [0..@slength.length-1]
-      if @slength[i] == 0 then @slength[i] = inf_len
+    for i in [0..@length.length-1]
+      if @length[i] == 0 then @length[i] = inf_len

     ctx = @plot_view.ctx

@@ -54,13 +53,13 @@
       @glyph_props.line_properties.set(ctx, @glyph_props)
       ctx.beginPath()
       for i in [0..@sx.length-1]
-        if isNaN(@sx[i] + @sy[i] + @angle[i] + @slength[i])
+        if isNaN(@sx[i] + @sy[i] + @angle[i] + @length[i])
           continue

         ctx.translate(@sx[i], @sy[i])
         ctx.rotate(@angle[i])
         ctx.moveTo(0,  0)
-        ctx.lineTo(@slength[i], 0) # TODO handle @length in data units?
+        ctx.lineTo(@length[i], 0) # TODO handle @length in data units?
         ctx.rotate(-@angle[i])
         ctx.translate(-@sx[i], -@sy[i])

@@ -69,7 +68,7 @@
   _full_path: (ctx) ->
     if @do_stroke
       for i in [0..@sx.length-1]
-        if isNaN(@sx[i] + @sy[i] + @angle[i] + @slength[i])
+        if isNaN(@sx[i] + @sy[i] + @angle[i] + @length[i])
           continue

         ctx.translate(@sx[i], @sy[i])
@@ -77,7 +76,7 @@

         ctx.beginPath()
         ctx.moveTo(0, 0)
-        ctx.lineTo(@slength[i], 0) # TODO handle @length in data units?
+        ctx.lineTo(@length[i], 0) # TODO handle @length in data units?

         @glyph_props.line_properties.set(ctx, @data[i])
         ctx.stroke()


env var HUBOT_TF_BASEPATH; show key on request

--- src/tf.coffee
+++ src/tf.coffee
@@ -17,6 +17,8 @@
 sys = require('sys')
 exec = require('child_process').exec;

+basepath = process.env.HUBOT_TF_BASEPATH || ''
+
 module.exports = (robot) ->

   robot.respond /tf create key$/i, (msg) ->
@@ -24,12 +26,20 @@

     fn = msg.message.user.name
     fn.replace /\//, "_"
-
-    basepath = "~/"
-    fp = basepath + fn
+    fp = basepath + "/" + fn

     exec "ssh-keygen -f #{fp} -b 1024 -C tf -N ''", (error, stdout, stderr) ->
-      msg.send {room: msg.message.user.name}, "```\n#{stdout}\n```"
+      #msg.send {room: msg.message.user.name}, "```\n#{stdout}\n```"
       pubkey = fs.readFileSync("#{fp}.pub", 'utf-8').toString()
-      msg.send {room: msg.message.user.name}, "Public key: ```\n#{pubkey}\n```"
-
+      msg.send {room: msg.message.user.name}, "Public key: \n```\n#{pubkey}\n```"
+
+  robot.respond /tf show key$/i, (msg) ->
+    fn = msg.message.user.name
+    fn.replace /\//, "_"
+    fp = basepath + "/" + fn
+
+    if ! fs.accessSync("#{fp}.pub", fs.F_OK|fs.R_OK)
+      return msg.send {room: msg.message.user.name}, "No key on file!  Create one."
+
+    pubkey = fs.readFileSync("#{fp}.pub", 'utf-8').toString()
+    return msg.send {room: msg.message.user.name}, "Public key: \n```\n#{pubkey}\n```"


use better var name for text chunk

use better var name for text chunk

--- src/tf.coffee
+++ src/tf.coffee
@@ -176,7 +176,7 @@
           return msg.send {room: msg.message.user.name}, "```\n#{stdout}\n```"
         out = []
         waitms = 200
-        msg = ''
+        textchunk = ''
         for line in stdout.split "\n"
           if line.match /^\+\s/
             textchunk = out.join "\n"


simple adjustment

simple adjustment

--- src/ui/views/messages.coffee
+++ src/ui/views/messages.coffee
@@ -1,5 +1,5 @@
-moment = require 'moment'
-shell = require('electron').shell
+moment    = require 'moment'
+shell     = require('electron').shell
 urlRegexp = require 'url-regex'
 url       = require 'url'


Client: Activity: Feed: Added componentDidMount event handler to focus commentInputWidget on edit

--- client/activity/lib/components/comments/commentinputwidget.coffee
+++ client/activity/lib/components/comments/commentinputwidget.coffee
@@ -23,6 +23,12 @@
       hasValue     : @props.hasValue
       commentValue : @props.commentValue
       focusOnInput : no
+
+
+  componentDidMount: ->
+
+    textInput = ReactDOM.findDOMNode @refs.textInput
+    kd.utils.moveCaretToEnd textInput

   onFocus: (event) -> @setState focusOnInput: yes


Fixed trailing comma mistake in commit c3ddffb6761d8dc53b63b56a16c68f84c7543997.

--- client/landing/app/Applications/Home.kdapplication/AppView.coffee
+++ client/landing/app/Applications/Home.kdapplication/AppView.coffee
@@ -763,7 +763,7 @@
     @onBeforeDestroy() if e.which is 27

   putOverlay:()->
-    @$overlay = $("<div/>")
+    @$overlay = $ "<div/>",
       class : "kdoverlay"
     @$overlay.hide()
     @$overlay.appendTo "body"


activity feed: minor fix

activity feed: minor fix

--- client/app/Applications/Activity.kdapplication/AppController.coffee
+++ client/app/Applications/Activity.kdapplication/AppController.coffee
@@ -207,7 +207,7 @@
       @lastTo   = to
       @lastFrom = from
     else
-      @reachedEndOfActivities = true
+      @reachedEndOfActivities = yes

   # Store first & last cache activity timestamp.
   extractCacheTimeStamps: (cache)->


Update lastInputTime upon site and salt update.

--- src/ui.coffee
+++ src/ui.coffee
@@ -124,7 +124,6 @@
 			opt_msg += "Options also saved."

 	if not_enough_input()
-		$('#passwd').val ''
 		util.notify "Options for <b>#{site}</b> saved." if options_saved
 		return false

@@ -151,6 +150,8 @@
 	return

 exports.site_update = site_update = ->
+	# update lastInputTime here, so the delayed gen_passwd will not trigger
+	lastInputTime = new Date().getTime()
 	site_option_state = OPTION_STATE.CHANGED

 exports.site_keypress = (k) ->
@@ -169,6 +170,7 @@
 	return

 exports.ios_salt_update = ->
+	lastInputTime = new Date().getTime()
 	util.storage.set_salt $('#salt').val()

 exports.username_update = ->
@@ -231,8 +233,7 @@
 			updater: (item) ->
 				console.log "#{item} selected"
 				site_update()
-				delay_call ->
-					load_site_options gen_passwd
+				load_site_options gen_passwd
 				item
 		}
 		return


set event-function-properties before any transition happens

--- src/coffee_state_machine.coffee
+++ src/coffee_state_machine.coffee
@@ -222,6 +222,9 @@

             current_state = obj[stateAttrName]

+            if 'object' is typeof props
+                obj[key] = value for key, value of props
+
             trans = (event_fn.transitions or= {})[current_state]
             trans = null if trans? and not check_transition_callbacks(trans)

@@ -234,9 +237,6 @@
             if trans?
                 old_state = current_state

-                if 'object' is typeof props
-                    obj[key] = value for key, value of props
-
                 set_new_state(trans.to)

                 current_state = obj[stateAttrName]


synctable: write a different initialization to program around https://github.com/rethinkdb/rethinkdb/issues/5210

--- src/smc-hub/rethink.coffee
+++ src/smc-hub/rethink.coffee
@@ -3937,10 +3937,12 @@
     _dbg: (f) =>
         return (m) => winston.debug("SyncTable(table='#{@_table}',id='#{@_id}').#{f}: #{m}")

-    _init_changefeed: (cb) =>
+    # the correct official way to do this, which is randomly broken: https://github.com/rethinkdb/rethinkdb/issues/5210
+    # Use the includeInitial:true, includeStates:true
+    _init_changefeed_0: (cb) =>
         if @_closed
             return
-        dbg = @_dbg("_init_changefeed")
+        dbg = @_dbg("_init_changefeed_0")
         dbg("doing the query")
         @_query.changes(includeInitial:true, includeStates:true).run (err, feed) =>
             if err
@@ -4000,3 +4002,80 @@
                                 process.nextTick(=>@emit('change', k))   # WARNING: don't remove this promise.nextTick! See above.

+    _init_changefeed: (cb) =>
+        if @_closed
+            return
+        dbg = @_dbg("_init_changefeed")
+        value = immutable.Map()
+        init_changefeed = (cb) =>
+            if @_closed
+                dbg("synctable is closed!")
+                cb?('closed')
+                return
+            delete @_state
+            @_query.changes(includeInitial:false, includeStates:false).run (err, feed) =>
+                if err
+                    dbg("failed to start changefeed -- changes query failed: #{err}")
+                    if err.name == 'ReqlQueryLogicError'
+                        dbg('terminating')
+                        cb?(err)
+                    else
+                        dbg('will try again soon')
+                        setTimeout((=>init_changefeed(cb)), 10000*Math.random()+5000)
+                else
+                    dbg("successful initialization of feed; now start getting changes...")
+                    cb?()   # done init
+                    @_feed = feed # save to use in close
+                    feed.each (err, change) =>
+                        if err
+                            feed.close()
+                            dbg("error -- will try to recreate changefeed in a few seconds: #{err}")
+                            setTimeout(_init_changefeed, 10000*Math.random()+5000)
+                        else
+                            if change.old_val? and not change.new_val?
+                                k     = change.old_val[@_primary_key]
+                                value = value.delete(k)
+                                if @_state == 'ready'
+                                    @_value = value
+                                    # WARNING!!!!! This process.nextTick is necessary; if you don't put it here
+                                    # the feed just stops after the first thing is emitted!!!!!  This would, of
+                                    # course, lead to very subtle bugs.  I don't understand this, but it is probably
+                                    # some subtle bug/issue involving bluebird promises and node's eventemitter...
+                                    process.nextTick(=>@emit('change', k))
+                            if change.new_val?
+                                k = change.new_val[@_primary_key]
+                                value = value.set(k, immutable.fromJS(change.new_val))
+                                if @_state == 'ready'
+                                    @_value = value
+                                    process.nextTick(=>@emit('change', k))   # WARNING: don't remove this promise.nextTick! See above.
+
+        init_value = (cb) =>
+            @_query.run (err, results) =>
+                if err
+                    dbg("init_value: failed to get init value : #{err}")
+                    if err.name == 'ReqlQueryLogicError'
+                        dbg('terminating')
+                        cb(err)
+                    else
+                        dbg('init_value: will try again')
+                        setTimeout((=>init_value(cb)), 10000*Math.random()+5000)
+                else
+                    if results
+                        dbg("init_value: got #{results.length} init values")
+                        for x in results
+                            k = x[@_primary_key]
+                            value = value.set(k, immutable.fromJS(x))
+                    @_value = value
+                    @_state = 'ready'
+                    process.nextTick(=>@emit('change', k))   # WARNING: don't remove this promise.nextTick! See above.
+                    cb()
+
+        async.series([
+            (cb) =>
+                dbg('create the changefeed')
+                init_changefeed(cb)
+            (cb) =>
+                dbg('doing the query')
+                init_value(cb)
+        ], cb)
+


Remove node replacement with their sanitized children to preserve the tree structure

--- src/mentions-kinder.coffee
+++ src/mentions-kinder.coffee
@@ -349,7 +349,6 @@
       # clean all children and replace node with them
       if node.childNodes?.length > 0
         @_cleanChildNodes(node)
-        $(node).replaceWith(node.childNodes)
       # remove node
       else
         $(node).remove()


修正一处小的拼写错误

修正一处小的拼写错误

--- src/widget/list/table.coffee
+++ src/widget/list/table.coffee
@@ -116,7 +116,6 @@
 				tagName: "i"
 				class: "green checkmark icon"
 		"checkbox-column":
-			tagName: "c-checkbox"
 			tagName: "c-checkbox"
 			bind: "$default"
 		"toggle-column":


switch from exec to spawn for gulp dev

switch from exec to spawn for gulp dev

--- tasks/dev.coffee
+++ tasks/dev.coffee
@@ -2,7 +2,7 @@
 deleteLines = require 'gulp-delete-lines'
 fs = require 'fs-extra'
 path = require 'path'
-exec = require('child_process').exec
+spawn = require('child_process').spawn

 gulp.task 'removelivereload', ->
@@ -21,9 +21,7 @@
   gulp.watch './stylesheets/*.styl', ['stylus']
   gulp.watch './lib/*.coffee', ['coffee']

-  cmd = path.normalize('../node_modules/.bin/electron') + ' .'
-
-  console.log cmd
-  exec cmd, {'cwd': './dev'},(err, std, ste) ->
-    console.log err if err
+  current_process = spawn(path.normalize('../node_modules/.bin/electron'), ['.'], {'cwd': './dev'})
+  current_process.on 'close', (code) ->
+    console.log('child process exited with code ' + code);
     process.exit(0)


Fix for scrolling on pages

Fix for scrolling on pages

--- src/scripts/modules/media/editbar/modals/list/section.coffee
+++ src/scripts/modules/media/editbar/modals/list/section.coffee
@@ -17,9 +17,7 @@
         container: @itemViewContainer

       super()
-      @listenTo(@model, 'change:unit', @render)
-      @listenTo(@model.get('contents'), 'change:title', @render)
-      @listenTo(@model.get('contents'), 'add:contents', @render)
+      @listenTo(@model, 'change:unit change:changed', @render)

     onRender: () ->
       super()


Add support for Objective-C and Objective-C++


fix spec

fix spec

--- tutor/test/components/course-settings/student-id-field.spec.coffee
+++ tutor/test/components/course-settings/student-id-field.spec.coffee
@@ -37,4 +37,4 @@
       ReactTestUtils.Simulate.change(input, {target: {value: 'ONEWORLDID'}})
       ReactTestUtils.Simulate.blur(input, {})
       expect(RosterActions.saveStudentIdentifier).to.have.been
-        .calledWith(COURSE_ID, STUDENT.id)
+        .calledWith({courseId: COURSE_ID, studentId: STUDENT.id})


fix toolbar

fix toolbar

--- src/modules/toolbar.coffee
+++ src/modules/toolbar.coffee
@@ -8,12 +8,13 @@
     container: null

   @formats:
-    BUTTON: {'bold', 'italic', 'strike', 'underline'}
-    SELECT: {'align', 'background', 'color', 'font', 'size'}
+    BUTTON: { 'bold', 'italic', 'strike', 'underline' }
+    SELECT: { 'align', 'background', 'color', 'font', 'size' }

   constructor: (@quill, @editorContainer, @options) ->
     throw new Error('container required for toolbar', @options) unless @options.container?
     @container = if _.isString(@options.container) then document.querySelector(@options.container) else @options.container
+    @activeFormats = {}
     _.each(@quill.options.formats, (format) =>
       if Toolbar.formats.BUTTON[format]?
         this.initFormat(format, 'click')
@@ -26,7 +27,10 @@
   initFormat: (format, eventName) ->
     input = this._getInput(format)
     return unless input?
+    triggering = false
     DOM.addEventListener(input, eventName, =>
+      return if triggering
+      triggering = true
       value = if eventName == 'change' then input.options[input.selectedIndex].value else !DOM.hasClass(input, 'sc-active')
       @quill.focus()
       range = @quill.getSelection()
@@ -35,29 +39,37 @@
           @quill.prepareFormat(format, value)
         else
           @quill.formatText(range, format, value, 'user')
-      this.setActive(input, value)
+      this.setActive(format, value)
+      triggering = false
       return false
     )

-  setActive: (input, value) ->
+  setActive: (format, value) ->
+    if value
+      @activeFormats[format] = value
+    else
+      delete @activeFormats[format]
+    input = this._getInput(format)
+    return unless input?
     if input.tagName == 'SELECT'
-      value = '' if _.isArray(value)
-      input.value = value
+      if value
+        value = '' if _.isArray(value)
+        DOM.selectOption(input, value)
+      else
+        DOM.resetSelect(input, false)
     else
-      DOM.addClass(input, 'sc-active')
+      DOM.toggleClass(input, 'sc-active', value)

   updateActive: (range) ->
-    return
     activeFormats = this._getActive(range)
-    _.each(@container.querySelectorAll('select'), _.bind(DOM.resetSelect))
-    _.each(@container.querySelectorAll('.sc-active'), (button) =>
-      DOM.removeClass(button, 'sc-active')
+    _.each(_.keys(@activeFormats), (name) =>
+      if activeFormats[name]?
+        this.setActive(name, activeFormats[name]) if activeFormats[name] != @activeFormats[name]
+      else
+        this.setActive(name, false)
     )
-    _.each(activeFormats, (value, key) =>
-      return unless value
-      input = this._getInput(key)
-      return unless input?
-      this.setActive(input, value)
+    _.each(activeFormats, (value, name) =>
+      this.setActive(name, value) unless @activeFormats[name]?
     )

   _getInput: (format) ->
@@ -68,10 +80,13 @@

   _getActive: (range) ->
     if range?
-      contents = @quill.getContents(range)
+      if range.isCollapsed()
+        contents = @quill.getContents(range)
+      else
+        index = Math.max(0, range.start - 1)
+        contents = @quill.getContents(index, index)
       formatsArr = _.pluck(contents, 'attributes')
-      return {} unless formatsArr.length > 0
-      activeFormats = formatsArr[0]
+      activeFormats = formatsArr[0] or {}
       _.each(formatsArr.slice(1), (formats) ->
         _.each(_.keys(activeFormats), (name) ->
           if formats[name]


use $.when and $.then

use $.when and $.then

--- cf/spell.coffee
+++ cf/spell.coffee
@@ -1,5 +1,6 @@
 $().ready()

-$.getJSON("spellings.json", () ->
-  console.log("Hello World")
+words = $.getJSON("spellings.json")
+$.when(words).then (()->
+  alert("Hello World")
   )


build fix

build fix

--- test/blog.coffee
+++ test/blog.coffee
@@ -67,7 +67,7 @@
 				body 	: body
 			promise.then (result)=>
 				result.permaLink.should.equal '1900/01/test'
-				result.body.should.equal helper.formatWithCDATA(body)
+				result.body.should.equal body
 				done()

 		it 'should update the permalink when title is different', (done)->


no accept specified handled

no accept specified handled

--- vendor/assets/javascripts/attachinary/jquery.attachinary.js.coffee
+++ vendor/assets/javascripts/attachinary/jquery.attachinary.js.coffee
@@ -50,8 +50,10 @@
       options =
         maxFileSize: 10000000
         dataType: 'json'
-        acceptFileTypes: new RegExp("^#{@$input.attr('accept').split(",").join("|")}$", "i")
         headers: {"X-Requested-With": "XMLHttpRequest"}
+
+      if @$input.attr('accept')
+        options.acceptFileTypes = new RegExp("^#{@$input.attr('accept').split(",").join("|")}$", "i")

       @$input.attr('name', 'file')
       @$input.fileupload(options)


Refresh the page if the user is not logged in when joining a project

--- services/web/public/coffee/ide/connection/ConnectionManager.coffee
+++ services/web/public/coffee/ide/connection/ConnectionManager.coffee
@@ -103,6 +103,16 @@
 			@ide.socket.emit 'joinProject', {
 				project_id: @ide.project_id
 			}, (err, project, permissionsLevel, protocolVersion) =>
+				if err?
+					if err.message == "not authorized"
+						window.location = "/login?redir=#{encodeURI(window.location.pathname)}"
+					else
+						@ide.socket.disconnect()
+						@ide.showGenericMessageModal("Something went wrong connecting", """
+							Something went wrong connecting to your project. Please refresh is this continues to happen.
+						""")
+					return
+
 				if @$scope.protocolVersion? and @$scope.protocolVersion != protocolVersion
 					location.reload(true)


add snap for vdi

add snap for vdi

--- src/spec.coffee
+++ src/spec.coffee
@@ -334,6 +334,9 @@

           size: get('virtual_size')

+          # FIXME: don't know if the good way to do
+          snapshot: get('is_a_snapshot')
+
           # FIXME: SR.VDIs -> VDI instead of VDI.SR -> SR.
           SR: get('SR')


Fixed issue#268 - No space at comment on ticket bulk action.

--- app/assets/javascripts/app/controllers/ticket_overview.coffee
+++ app/assets/javascripts/app/controllers/ticket_overview.coffee
@@ -110,6 +110,10 @@
     $(window).off 'keydown.overview_navigation'

   listNavigate: (e) =>
+
+    # ignore if focus is in bulk action
+    return if $(e.target).is('textarea, input, select')
+
     if e.keyCode is 38 # up
       e.preventDefault()
       @nudge(e, -1)


Only use batch feature for agents. Fixed browser tests.

--- app/assets/javascripts/app/controllers/ticket_overview.coffee
+++ app/assets/javascripts/app/controllers/ticket_overview.coffee
@@ -21,8 +21,10 @@

   constructor: ->
     super
+    @batchSupport = @permissionCheck('ticket.agent')
     @render()

+    return if !@batchSupport
     users = [
       App.User.find(2),
       App.User.find(2),
@@ -74,6 +76,7 @@
     ))

   startDragItem: (event) =>
+    return if !@batchSupport
     @grabbedItem = $(event.currentTarget)
     offset = @grabbedItem.offset()
     @batchDragger = $(App.view('ticket_overview/batch_dragger')())
@@ -94,6 +97,7 @@
     # TODO: fire @cancelDrag on ESC

   dragItem: (event) =>
+    return if !@batchSupport
     pos = @batchDragger.data()
     threshold = 3
     x = event.pageX - pos.dx
@@ -143,6 +147,7 @@
     $.Velocity.hook @batchDragger, 'translateY', "#{y}px"

   endDragItem: (event) =>
+    return if !@batchSupport
     $(document).off 'mousemove.item'
     $(document).off 'mouseup.item'
     pos = @batchDragger.data()


Remove semicolon.

Remove semicolon.

--- app/assets/javascripts/dot_ledger/collections/index.js.coffee
+++ app/assets/javascripts/dot_ledger/collections/index.js.coffee
@@ -4,7 +4,7 @@
 DotLedger.module 'Collections', ->
   class @Base extends Backbone.Collection
     parse: (response, options)->
-      @_fetch_options_data = options.data || {};
+      @_fetch_options_data = options.data || {}
       @pagination = JSON.parse options.xhr.getResponseHeader('X-Pagination')
       @metadata = JSON.parse options.xhr.getResponseHeader('X-Metadata')
       response


[#62240086] removed blank line

[#62240086] removed blank line

--- app/assets/javascripts/trade/routes/shipments_route.js.coffee
+++ app/assets/javascripts/trade/routes/shipments_route.js.coffee
@@ -16,7 +16,6 @@
     ))

   model: (params, queryParams, transition) ->
-
     # redo the array params if we're coming from the url
     @get('selectedQueryParamNames').forEach (property) ->
       if property.type == 'array' && queryParams[property.param] == true


Only flush buffer is anything in it...

Only flush buffer is anything in it...

--- src/tail.coffee
+++ src/tail.coffee
@@ -15,7 +15,7 @@
         stream.on 'end',=>
           x = @queue.shift()
           @internalDispatcher.emit("next") if @queue.length > 0
-          if @flushAtEOF
+          if @flushAtEOF && @buffer.length > 0
             @emit("line", @buffer)
             @buffer = ''
         stream.on 'data', (data) =>


Relay messages to multiple users

Relay messages to multiple users

Closes #4

--- src/tell.coffee
+++ src/tell.coffee
@@ -8,11 +8,13 @@
 #   HUBOT_TELL_ALIASES [optional] - Comma-separated string of command aliases for "tell".
 #
 # Commands:
-#   hubot tell <username> <some message> - tell <username> <some message> next time they are present.
+#   hubot tell <recipients> <some message> - tell <recipients> <some message> next time they are present.
 #
 # Notes:
 #   Case-insensitive prefix matching is employed when matching usernames, so
 #   "foo" also matches "Foo" and "foooo".
+#   A comma-separated list of recipients can be supplied to relay the message
+#   to each of them.
 #
 # Author:
 #   christianchristensen, lorenzhs, xhochy, patcon
@@ -30,22 +32,23 @@
   commands = ["tell"].concat(config.aliases)
   commands = commands.join '|'

-  REGEX = ///(#{commands})\s+([\w.-]+):?\s+(.*)///i
+  REGEX = ///(#{commands})\s+([\w,.-]+):?\s+(.*)///i

   robot.respond REGEX, (msg) ->
     datetime = new Date()
     verb = msg.match[1]
-    username = msg.match[2]
+    recipients = msg.match[2].split(',').filter((x) -> x?.length)
     message = msg.match[3]
     room = msg.message.user.room
     tellmessage = msg.message.user.name + " @ " + datetime.toLocaleString() + " said: " + message + "\r\n"
     if not localstorage[room]?
       localstorage[room] = {}
-    if localstorage[room][username]?
-      localstorage[room][username] += tellmessage
-    else
-      localstorage[room][username] = tellmessage
-    msg.send "Ok, I'll #{verb} #{username} '#{message}'."
+    for recipient in recipients
+      if localstorage[room][recipient]?
+        localstorage[room][recipient] += tellmessage
+      else
+        localstorage[room][recipient] = tellmessage
+    msg.send "Ok, I'll #{verb} #{recipients.join ', '} '#{message}'."
     return

   # When a user enters, check if someone left them a message


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/text.coffee
+++ src/text.coffee
@@ -1,4 +1,4 @@
-Score = (I={}) ->
+Text = (I={}) ->
   Object.reverseMerge I,
     duration: 40
     font: 'VT323'


update legend position (again)

update legend position (again)

--- site/app.coffee
+++ site/app.coffee
@@ -757,8 +757,10 @@
       ctx.fillText('               due to differences in reporter types.', wx, h-24-12 * Math.max(0.7, w / 1000))
       ctx.fillText('NOTE 2: Color intensity is similar to, but not the same as, CDC’s Flu Activity Level.', wx, h-24)
     # Draw legend
+    x_offset = 32
     areas_box = $('#map_list')
-    x_offset = areas_box.position().left + areas_box.width() + 32
+    if areas_box.css('visibility') != 'hidden'
+      x_offset += areas_box.position().left + areas_box.width()
     [lw, lh] = [w/25, h/2]
     [x0, y0, x1, y1] = [x_offset, h / 10, x_offset + lw, h / 10 + lh]
     ctx.strokeStyle = '#000'


notify when sections have changed

notify when sections have changed

--- src/components/task-plan/select-topics.cjsx
+++ src/components/task-plan/select-topics.cjsx
@@ -14,6 +14,7 @@
   propTypes:
     planId: React.PropTypes.string.isRequired
     courseId: React.PropTypes.string.isRequired
+    onSectionChange: React.PropTypes.func.isRequired
     hide: React.PropTypes.func.isRequired
     selected: React.PropTypes.array

@@ -24,6 +25,7 @@

   onSectionChange: (sectionIds) ->
     TaskPlanActions.updateTopics(@props.planId, sectionIds)
+    @props.onSectionChange?(sectionIds)

   renderDialog: ->
     {courseId, planId, selected, hide, header, primary, cancel} = @props


Updated more REST tests

Updated more REST tests

--- test/rest.coffee
+++ test/rest.coffee
@@ -150,73 +150,64 @@
         assert.deepEqual data, []
         done()

+  describe 'POST', ->
+    it 'lets you submit', (done) ->
+      called = false
+      @userAgent.submit = (cName, docName, opData, options, callback) ->
+        assert.strictEqual cName, 'c'
+        assert.strictEqual docName, 'd'
+        assert.deepEqual opData, {v:5, op:[1,2,3]}
+        called = true
+        callback null, 5, []
+
+      fetch 'POST', @port, "/doc/c/d", {v:5, op:[1,2,3]}, (res, ops) =>
+        assert.strictEqual res.statusCode, 200
+        assert.deepEqual ops, []
+        assert called
+        done()
+
+    it 'POST a document with invalid JSON returns 400', (done) ->
+      fetch 'POST', @port, "/doc/c/d", 'invalid>{json', (res, data) ->
+        assert.strictEqual res.statusCode, 400
+        done()
+
+  describe 'PUT', ->
+    it 'PUT a document creates it', (done) ->
+      called = false
+      @userAgent.submit = (cName, docName, opData, options, callback) ->
+        assert.strictEqual cName, 'c'
+        assert.strictEqual docName, 'd'
+        assert.deepEqual opData, {create:{type:'simple'}}
+        called = true
+        callback null, 5, []
+
+      fetch 'PUT', @port, "/doc/c/d", {type:'simple'}, (res, data, headers) =>
+        assert.strictEqual res.statusCode, 200
+        assert.strictEqual headers['x-ot-version'], '5'
+
+        assert called
+        done()
+
+  describe 'DELETE', ->
+    it 'deletes a document', (done) ->
+      called = false
+      @userAgent.submit = (cName, docName, opData, options, callback) ->
+        assert.strictEqual cName, 'c'
+        assert.strictEqual docName, 'd'
+        assert.deepEqual opData, {del:true}
+        called = true
+        callback null, 5, []
+
+      fetch 'DELETE', @port, "/doc/c/d", null, (res, data, headers) =>
+        assert.strictEqual res.statusCode, 200
+        assert.strictEqual headers['x-ot-version'], '5'
+        assert called
+        done()
+
+
   # Tests past this line haven't been rewritten yet for the new API.

-  describe.skip 'PUT', ->
-    it 'PUT a document creates it', (done) ->
-      fetch 'PUT', @port, "/doc/c/d", {type:'simple'}, (res, data) =>
-        assert.strictEqual res.statusCode, 200
-
-        @model.getSnapshot @name, (error, doc) ->
-          meta = doc.meta
-          delete doc.meta
-          assert.deepEqual doc, {v:0, type:types.simple, snapshot:{str:''}}
-          test.ok meta
-          assert.strictEqual typeof(meta.ctime), 'number'
-          assert.strictEqual typeof(meta.mtime), 'number'
-          done()
-
-  'POST a document in the DB returns 200 OK': (test) ->
-    @model.create @name, 'simple', =>
-      fetch 'POST', @port, "/doc/#{@name}?v=0", {position: 0, text: 'Hi'}, (res, data) =>
-        assert.strictEqual res.statusCode, 200
-        assert.deepEqual data, {v:0}
-
-        @model.getSnapshot @name, (error, doc) ->
-          assert.deepEqual doc, {v:1, type:types.simple, snapshot:{str:'Hi'}, meta:{}}
-          done()
-
-  'POST a document setting the version in an HTTP header works': (test) ->
-    @model.create @name, 'simple', =>
-      fetch 'POST', @port, "/doc/#{@name}", {position: 0, text: 'Hi'}, {'X-OT-Version': 0}, (res, data) =>
-        assert.strictEqual res.statusCode, 200
-        assert.deepEqual data, {v:0}
-
-        @model.getSnapshot @name, (error, doc) ->
-          assert.deepEqual doc, {v:1, type:types.simple, snapshot:{str:'Hi'}, meta:{}}
-          done()
-
-  'POST a document with no version returns 400': (test) ->
-    fetch 'POST', @port, "/doc/#{@name}", {type:'simple'}, (res, data) ->
-      assert.strictEqual res.statusCode, 400
-      done()
-
-  'POST a document with invalid JSON returns 400': (test) ->
-    fetch 'POST', @port, "/doc/#{@name}?v=0", 'invalid>{json', (res, data) ->
-      assert.strictEqual res.statusCode, 400
-      done()
-
-  "Can't POST an op to a nonexistant document": (test) ->
-    # This was found in the wild -
-    # https://github.com/josephg/ShareJS/issues/66
-    fetch 'POST', @port, "/doc/#{@name}?v=0", {foo:'bar'}, (res, data) ->
-      assert.strictEqual res.statusCode, 404
-      done()
-
-  'DELETE deletes a document': (test) ->
-    @model.create @name, 'simple', =>
-      fetch 'DELETE', @port, "/doc/#{@name}", null, (res, data) =>
-        assert.strictEqual res.statusCode, 200
-
-        @model.getSnapshot @name, (error, doc) ->
-          test.equal doc, null
-          done()
-
-  'DELETE returns a 404 message if you delete something that doesn\'t exist': (test) ->
-    fetch 'DELETE', @port, "/doc/#{@name}", null, (res, data) ->
-      assert.strictEqual res.statusCode, 404
-      done()
-
+###
   'Cannot do anything if the server doesnt allow client connections': (test) ->
     @auth = (agent, action) ->
       assert.strictEqual action.type, 'connect'
@@ -319,4 +310,4 @@
           test.ok doc
           done()

-
+###


MultiOutput: Added the same listener of MultiInput, to hide/show elements after the languages enabled were changed. See #48123

--- src/elements/MultiOutput/MultiOutput.coffee
+++ src/elements/MultiOutput/MultiOutput.coffee
@@ -17,6 +17,10 @@
 				check: Boolean
 				default: true

+	readOpts: ->
+		super()
+		@__keyTemplates = {}
+
 	render: ->
 		super()
 		if @_showOnlyPreferredKey
@@ -26,15 +30,22 @@
 			@replace(label)
 		else
 			for key, idx in @_control.getKeys()
-				if not @_control.isEnabled(key.name)
-					continue
-
 				value = @getValue()[key.name]
 				if CUI.util.isEmpty(value)
 					continue

 				template = @__buildTemplateForKey(key)
+				@__keyTemplates[key.name] = template
+
 				@append(template)
+
+			@__updateListener = CUI.Events.listen
+				type: "multi-input-control-update"
+				node: @DOM
+				call: =>
+					@__hideShowElements()
+			@__hideShowElements()
+
 		@

 	__buildTemplateForKey: (key) ->
@@ -45,6 +56,7 @@
 			map:
 				center: true
 				right: true
+
 		template.append(label, "center")

 		button = new CUI.defaults.class.Button
@@ -58,3 +70,16 @@

 		template.append(button, "right")
 		return template
+
+	__hideShowElements: ->
+		for key, template of @__keyTemplates
+			if @_control.isEnabled(key)
+				template.show()
+			else
+				template.hide()
+
+	destroy: ->
+		@__updateListener.destroy()
+		delete @__keyTemplates
+
+		super()


Allow insert into "Find" tool panel

Allow insert into "Find" tool panel

Fixes a problem where the character was inserted in the last active editor instead of the focused "Find" tool panel

--- lib/compatible-view.coffee
+++ lib/compatible-view.coffee
@@ -18,9 +18,9 @@
     @detach()

   at: ->
-    editor = atom.workspace.activePaneItem
-    editor.insertText('@')
+    editor = atom.workspace.getActiveEditor()
+    editor.insertText('@') if editor

   backslash: ->
-    editor = atom.workspace.activePaneItem
-    editor.insertText('\\')
+    editor = atom.workspace.getActiveEditor()
+    editor.insertText('\\') if editor


Get the changed files list from async git.

--- lib/git-status-view.coffee
+++ lib/git-status-view.coffee
@@ -20,8 +20,8 @@
   populate: ->
     paths = []
     for repo in atom.project.getRepositories() when repo?
-      workingDirectory = repo.getWorkingDirectory()
-      for filePath of repo.statuses
-        filePath = path.join(workingDirectory, filePath)
-        paths.push(filePath) if fs.isFileSync(filePath)
-    @setItems(paths)
+      repo.async.getWorkingDirectory().then (workingDirectory) =>
+        for filePath of repo.async.getCachedPathStatuses()
+          filePath = path.join(workingDirectory, filePath)
+          paths.push(filePath) if fs.isFileSync(filePath)
+        @setItems(paths)


don't perform slideDown if data has loaded but it's not currently selected

--- resources/assets/coffee/_classes/beatmap-pack.coffee
+++ resources/assets/coffee/_classes/beatmap-pack.coffee
@@ -26,6 +26,7 @@
     @packBody = @el.querySelector('.js-beatmap-pack__body')
     @expander = @el.querySelector('.js-beatmap-pack-expander')
     @busy = false
+    @isCurrent = false

     $.subscribe 'beatmappack:clicked', @onClick
     $(@expander).on 'click', (event) =>
@@ -39,23 +40,25 @@
       @close()

   open: =>
+    @isCurrent = true
     return if @busy

     $(@el).addClass('accordion__item--expanded')
     if @packBody.innerHTML?.length
-      @slideDown()
+      @slideDown() if @isCurrent
     else
       @busy = true
       @getBeatmapPackItem(@packId)
       .done (data) =>
         @packBody.innerHTML = data
-        @slideDown()
+        @slideDown() if @isCurrent
       .fail (xhr) ->
         console.error(xhr)
       .always =>
         @busy = false

   close: =>
+    @isCurrent = false
     # drop shadow should change _after_ slide up animation
     $(@el.querySelector('.accordion__item-body')).slideUp(300, () =>
       $(@el).removeClass('accordion__item--expanded')


Remove dead helpers

Remove dead helpers

--- test/test.coffee
+++ test/test.coffee
@@ -22,23 +22,3 @@
   event = document.createEvent 'MouseEvents'
   event.initEvent 'click', true, true
   element.dispatchEvent event
-
-
-$(document).delegate 'a[href]', 'click', (event) ->
-  return if event.defaultPrevented || event.isDefaultPrevented?()
-  window.location = event.target.href
-  return
-
-# Target form submissions to iframe
-guid = 1
-$(document).bind 'submit', (event) ->
-  return if event.defaultPrevented || event.isDefaultPrevented?()
-  name   = "frame#{guid++}"
-  iframe = $ "<iframe id=#{name} name=#{name}>"
-  $(event.target).attr 'target', name
-  $('#qunit-fixture').append iframe
-
-if Zepto?
-  $(document).bind 'ajaxSuccess', (event, xhr) ->
-    if xhr.getResponseHeader('Content-Type') is 'application/javascript'
-      eval xhr.responseText


Add tieBreakers test

Add tieBreakers test

--- test/test.coffee
+++ test/test.coffee
@@ -12,18 +12,18 @@
 			'path/to/foo.js'
 			'path/to/bar.js'
 			'bar.js'
-			'path/anyjs/baz.js'
-			'path/anyjs/aaz.js'
+			'path/zjs/baz.js'
+			'path/zjs/aaz.js'
 			'path/to/file.js'
-			'path/anyjs/caz.js'
-			'path/anyjs/foo.js'
+			'path/zjs/caz.js'
+			'path/zjs/foo.js'
 		]

 		nativeSorted = sortable.slice().sort()

 		matchers = [
 			'path/to/file.js'
-			'path/anyjs/**/*.js'
+			'path/zjs/**/*.js'
 			'wontmatchanything'
 			/foo.js$/
 			(string) -> string.indexOf('bar') isnt -1 and string.length > 10
@@ -44,8 +44,8 @@
 		assert.equal sortable.sort(anysort matchers[0])[0], matchers[0]

 	it 'should break ties with lower matchers', ->
-		val1 = 'path/anyjs/foo.js'
-		val2 = 'path/anyjs/aaz.js'
+		val1 = 'path/zjs/foo.js'
+		val2 = 'path/zjs/aaz.js'
 		assert sortable.indexOf(val1) > sortable.indexOf(val2)
 		sortable.sort anysort matchers[1] # only the matcher they both hit
 		assert sortable.indexOf(val1) > sortable.indexOf(val2)
@@ -70,8 +70,19 @@
 			assert unmatched.length is sortable.length
 			assert.deepEqual sorted, sortable.sort()

-		it 'should properly utilize matchers', ->
+		it 'should utilize matchers', ->
 			{matched, unmatched, sorted} = anysort.splice sortable, matchers
 			assert unmatched.length is 1
 			assert.deepEqual sorted, matched.concat unmatched
 			assert.deepEqual sorted, sortable.sort anysort matchers
+
+		it 'should utilize tieBreakers', ->
+			# without tieBreakers, sortable[0] comes first
+			{matched, unmatched, sorted} = anysort.splice sortable, matchers[3]
+			assert.deepEqual matched, [sortable[0], sortable[7]]
+			# with tieBreakers, sortable[7] wins
+			{matched, unmatched, sorted} = anysort.splice sortable, matchers[3], matchers
+			assert.deepEqual matched, [sortable[7], sortable[0]]
+			# sortable[3] matches the tieBreakers, but isn't in the matched array
+			assert anysort.match matchers, sortable[3]
+			assert -1 isnt unmatched.indexOf sortable[3]


remove console.log

remove console.log

--- spec/EnumerationSpec.coffee
+++ spec/EnumerationSpec.coffee
@@ -152,7 +152,6 @@
     new Enumeration(nextEnumerationType(),{})
     list=Enumeration.list()
     length=list.length
-    console.log length
     expect(list).toEqual(jasmine.any(Array))
     list.pop
     expect(Enumeration.list().length).toBe(length)


Refactoring if validation with coffescript sintax

--- server/src/Component/Builtin/Database/MyNinja.coffee
+++ server/src/Component/Builtin/Database/MyNinja.coffee
@@ -143,10 +143,7 @@
                 primaryKeyValue = data[@_primaryKey]
                 delete data[@_primaryKey]

-                if conditions
-                    whereSql = @$.and(@$.equal(@_primaryKey, primaryKeyValue), conditions)
-                else
-                    whereSql = @$.equal(@_primaryKey, primaryKeyValue)
+                whereSql = if conditions then @$.and(@$.equal(@_primaryKey, primaryKeyValue), conditions) else @$.equal(@_primaryKey, primaryKeyValue)

                 sql = @$.update(@_table).set(data).where(whereSql).build()
                 @_mysql.query sql, [], @_dataSourceName, (error, results) =>


Reorganize csv encoding specs

Reorganize csv encoding specs

--- spec/csv-editor-spec.coffee
+++ spec/csv-editor-spec.coffee
@@ -606,17 +606,19 @@
             encodingSelect = csvEditorElement.form.encodingSelect
             encodingSelect.value = 'ISO 8859-1'

+        describe 'when the table editor is opened', ->
+          beforeEach ->
             tableEditorButton = csvEditorElement.form.openTableEditorButton
             click(tableEditorButton)

             waitsFor 'table editor created', -> tableEditor?

-        it 'uses the given encoding to open the file', ->
-          expect(tableEditor.table.getColumnValues(0)).toEqual([
-            'name',
-            'Cédric',
-            'Émile'
-          ])
+          it 'uses the given encoding to fill the table', ->
+            expect(tableEditor.table.getColumnValues(0)).toEqual([
+              'name',
+              'Cédric',
+              'Émile'
+            ])

     describe 'when the file cannot be parsed with the default', ->
       beforeEach ->


i"m gonna really enjoy this tagging thing...!

--- spec/does/reset_spec.coffee
+++ spec/does/reset_spec.coffee
@@ -8,6 +8,16 @@
 # multiple mocha suite stacks to verify the unstubbing across
 # various combinations of ancestor hooks
 #
+
+before ipso (done, should) ->
+
+    ipso.tag
+
+        GOT: should.exist
+        NOT: should.not.exist
+
+    .then done
+

 beforeEach ipso (MyClass) -> MyClass.does each_ROOT_1: ->

@@ -32,21 +42,22 @@
                 MyClass.each_INNER_1()
                 MyClass.each_INNER_2()

-        it 'no longer expects INNER functions', ipso (MyClass, should) ->
+        it 'no longer expects INNER functions', ipso (MyClass, NOT) ->

-            should.not.exist MyClass.each_INNER_1
-            should.not.exist MyClass.each_INNER_2
+            NOT MyClass.each_INNER_1
+            NOT MyClass.each_INNER_2

             MyClass.each_ROOT_1()
             MyClass.each_DESCRIBE_1()
             MyClass.each_OUTER_1()

-    it 'no longer expects OUTER functions', ipso (MyClass, should) ->
+    it 'no longer expects OUTER functions', ipso (MyClass, NOT) ->

-        should.not.exist MyClass.each_INNER_1
-        should.not.exist MyClass.each_INNER_2
-        should.not.exist MyClass.each_OUTER_1
+        NOT MyClass.each_INNER_1
+        NOT MyClass.each_INNER_2
+        NOT MyClass.each_OUTER_1

+        #NOT MyClass.each_ROOT_1
         MyClass.each_ROOT_1()
         MyClass.each_DESCRIBE_1()


Removed merge markup that I accidentally committed.

--- coffeescripts/xwing.coffee
+++ coffeescripts/xwing.coffee
@@ -1156,11 +1156,8 @@
                 card_obj[ship.modification.data.name] = null if ship.modification?.data?
         return Object.keys(card_obj).sort()

-<<<<<<< HEAD
-=======
     getNotes: ->
         @notes.val()
->>>>>>> upstream/master

 class Ship
     constructor: (args) ->


Add missing -s to function name in failing test.

--- spec/powershell-spec.coffee
+++ spec/powershell-spec.coffee
@@ -88,7 +88,7 @@

       it "should tokenize the closing double-quote as empty string", ->
         expect(tokens[1].value).toEqual "\""
-        expect(tokens[1]).toHaveScope ["punctuation.definition.string.end.powershell", "meta.empty-string.double.powershell"]
+        expect(tokens[1]).toHaveScopes ["punctuation.definition.string.end.powershell", "meta.empty-string.double.powershell"]

     describe "Variables within a string", ->
       tokens = null


Fix another test

Fix another test

--- spec/powershell-spec.coffee
+++ spec/powershell-spec.coffee
@@ -84,7 +84,6 @@

     describe "Variables within a string", ->
       tokens = null
-      expectedDollarSignScopes = ["keyword.other.variable.definition.powershell"]

       beforeEach ->
         {tokens} = grammar.tokenizeLine("\"Hi there $name `$bob\"")
@@ -98,7 +97,7 @@

       it "should tokenize the beginning of variable names as embedded punctuation", ->
         expect(tokens[2].value).toEqual "$"
-        expect(tokens[2]).toHaveScopes expectedDollarSignScopes
+        expect(tokens[2]).toHaveScopes [ "source.powershell", "string.quoted.double.powershell", "variable.other.readwrite.powershell", "punctuation.definition.variable.powershell" ]

       it "should tokenize variable names", ->
         expect(tokens[3].value).toEqual "name"


Correct indentation

Correct indentation

--- spec/status-bar-spec.coffee
+++ spec/status-bar-spec.coffee
@@ -94,12 +94,12 @@
       expect(statusBar.bufferModified.text()).toBe '*'

     it "doesn't update the buffer modified indicator for the old buffer", ->
-     oldBuffer = editor.getBuffer()
-     expect(statusBar.bufferModified.text()).toBe ''
-     rootView.open(require.resolve('fixtures/sample.txt'))
-     oldBuffer.setText("new text")
-     advanceClock(buffer.stoppedChangingDelay)
-     expect(statusBar.bufferModified.text()).toBe ''
+      oldBuffer = editor.getBuffer()
+      expect(statusBar.bufferModified.text()).toBe ''
+      rootView.open(require.resolve('fixtures/sample.txt'))
+      oldBuffer.setText("new text")
+      advanceClock(buffer.stoppedChangingDelay)
+      expect(statusBar.bufferModified.text()).toBe ''

   describe "when the associated editor's cursor position changes", ->
     it "updates the cursor position in the status bar", ->


Minor CoffeeScript usage cleanup.

Minor CoffeeScript usage cleanup.

--- src/js/jquery.hlf.tip.coffee
+++ src/js/jquery.hlf.tip.coffee
@@ -168,7 +168,7 @@
     # ❦ `constructor` keeps `$triggers` and `$context` as properties. `options`
     # is further normalized.
     constructor: (@$triggers, options, @$context) ->
-      for name, animation of options.animations when name isnt 'base'
+      for own name, animation of options.animations when name isnt 'base'
         _.defaults animation, options.animations.base

     # ❦ `init` offloads non-trivial setup to other subroutines


fix some issues pointed out in 041033a51af086b48156ff2a9dafb4675b88d99c

--- repl.coffee
+++ repl.coffee
@@ -7,9 +7,16 @@
   prompt: 'coffee> ',
   eval: (input, context, filename, cb) ->
     try
-      return cb null if /^\(\s+\)$/.test input # Empty command
-      # TODO: pass in-scope vars and avoid accidentally shadowing them by omitting those declarations
-      js = CoffeeScript.compile input, {filename, bare: yes}
+      # XXX: multiline hack
+      input = input.replace /\uFF00/g, '\n'
+      # strip node-added parens
+      input = input[1...-1]
+      # strip single-line comments
+      input = input.replace /(^|[\r\n]+)(\s*)##?(?:[^#\r\n][^\r\n]*|)($|[\r\n])/, '$1$2$3'
+      # empty command
+      return cb null if /^\s*$/.test input
+      # TODO: fix #1829: pass in-scope vars and avoid accidentally shadowing them by omitting those declarations
+      js = CoffeeScript.compile "[]=(#{input}\n)", {filename, bare: yes}
       cb null, vm.runInContext js, context, filename
     catch err
       cb err
@@ -20,8 +27,8 @@

   multiline =
     enabled: off
-    initialPrompt: '------> '
-    prompt: '......> '
+    initialPrompt: repl.prompt.replace(/^[^> ]*/, (x) -> x.replace /./g, '-')
+    prompt: repl.prompt.replace(/^[^> ]*>?/, (x) -> x.replace /./g, '.')
     buffer: ''

   # Proxy node's line listener
@@ -54,6 +61,8 @@
       rli.cursor = 0
       rli.output.cursorTo 0
       rli.output.clearLine 1
+      # XXX: multiline hack
+      multiline.buffer = multiline.buffer.replace /\n/g, '\uFF00'
       rli.emit 'line', multiline.buffer
       multiline.buffer = ''
     else


Issue #2105 refined

Issue #2105 refined

Fix updated as suggested by michaelficarra

--- repl.coffee
+++ repl.coffee
@@ -21,7 +21,6 @@
 REPL_PROMPT = 'coffee> '
 REPL_PROMPT_MULTILINE = '------> '
 REPL_PROMPT_CONTINUATION = '......> '
-REPL_CHOMP = /(\n|\r)+$/
 enableColours = no
 unless process.platform is 'win32'
   enableColours = not process.env.NODE_DISABLE_COLORS
@@ -76,7 +75,7 @@
 # Attempt to evaluate the command. If there's an exception, print it out instead
 # of exiting.
 run = (buffer) ->
-  buffer = buffer.replace REPL_CHOMP, ""
+  buffer = buffer.replace /[\r\n]+$/, ""
   if multilineMode
     backlog += "#{buffer}\n"
     repl.setPrompt REPL_PROMPT_CONTINUATION


Move { restoreScroll } spec to up.change

--- spec_app/spec/javascripts/up/fragment_spec.js.coffee
+++ spec_app/spec/javascripts/up/fragment_spec.js.coffee
@@ -271,7 +271,6 @@
               expect(result.state).toEqual('rejected')
               expect(result.value).toMatch(/Could not match target in current page and response/i)
               done()
-

       describe 'choice of target', ->

@@ -1123,6 +1122,34 @@
             next =>
               expect(@revealOptions.scrollBehavior).toEqual('auto')

+      describe 'with { restoreScroll: true } option', ->
+
+        beforeEach ->
+          up.history.config.enabled = true
+
+        it 'restores the scroll positions of all viewports around the target', asyncSpec (next) ->
+
+          $viewport = $fixture('div[up-viewport] .element').css
+            'height': '100px'
+            'width': '100px'
+            'overflow-y': 'scroll'
+
+          respond = =>
+            @lastRequest().respondWith
+              status: 200
+              contentType: 'text/html'
+              responseText: '

'
+
+          up.change('.element', url: '/foo')
+
+          next => respond()
+          next => $viewport.scrollTop(65)
+          next => up.replace('.element', '/bar')
+          next => respond()
+          next => $viewport.scrollTop(0)
+          next.await => up.replace('.element', '/foo', restoreScroll: true)
+          # No need to respond because /foo has been cached before
+          next => expect($viewport.scrollTop()).toEqual(65)

       describe 'execution of scripts', ->

@@ -1863,7 +1890,7 @@
     ##############################################################################
     ##############################################################################

-    describe 'up.replace', ->
+    describe 'up.replace()', ->

       describeCapability 'canPushState', ->

@@ -2504,39 +2531,11 @@

-        describe 'with { restoreScroll: true } option', ->
-
-          beforeEach ->
-            up.history.config.enabled = true
-
-          it 'restores the scroll positions of all viewports around the target', asyncSpec (next) ->
-
-            $viewport = $fixture('div[up-viewport] .element').css
-              'height': '100px'
-              'width': '100px'
-              'overflow-y': 'scroll'
-
-            respond = =>
-              @lastRequest().respondWith
-                status: 200
-                contentType: 'text/html'
-                responseText: '

'
-
-            up.replace('.element', '/foo')
-
-            next => respond()
-            next => $viewport.scrollTop(65)
-            next => up.replace('.element', '/bar')
-            next => respond()
-            next => $viewport.scrollTop(0)
-            next.await => up.replace('.element', '/foo', restoreScroll: true)
-            # No need to respond because /foo has been cached before
-            next => expect($viewport.scrollTop()).toEqual(65)
-
-
-        it 'uses a { failTransition } option if the request failed'
-
-    describe 'up.extract', ->
+
+
+
+
+    describe 'up.extract()', ->

       it "rejects if the selector can't be found on the current page", (done) ->
         html = '
text
'


Client: Activity: Formatting and using CollaborationComingSoonModal component for renderComingSoonModal method

--- client/activity/lib/components/channelthreadpane/index.coffee
+++ client/activity/lib/components/channelthreadpane/index.coffee
@@ -1,18 +1,18 @@
-kd                   = require 'kd'
-React                = require 'kd-react'
-KDReactorMixin       = require 'app/flux/reactormixin'
-ActivityFlux         = require 'activity/flux'
-immutable            = require 'immutable'
-classnames           = require 'classnames'
-ThreadSidebar        = require 'activity/components/threadsidebar'
-ThreadHeader         = require 'activity/components/threadheader'
-PublicChannelLink    = require 'activity/components/publicchannellink'
-ImmutableRenderMixin = require 'react-immutable-render-mixin'
-PublicChatPane       = require 'activity/components/publicchatpane'
-Link                 = require 'app/components/common/link'
-Modal                = require 'app/components/modal'
-showNotification     = require 'app/util/showNotification'
-
+kd                           = require 'kd'
+React                        = require 'kd-react'
+KDReactorMixin               = require 'app/flux/reactormixin'
+ActivityFlux                 = require 'activity/flux'
+immutable                    = require 'immutable'
+classnames                   = require 'classnames'
+ThreadSidebar                = require 'activity/components/threadsidebar'
+ThreadHeader                 = require 'activity/components/threadheader'
+PublicChannelLink            = require 'activity/components/publicchannellink'
+ImmutableRenderMixin         = require 'react-immutable-render-mixin'
+PublicChatPane               = require 'activity/components/publicchatpane'
+Link                         = require 'app/components/common/link'
+Modal                        = require 'app/components/modal'
+showNotification             = require 'app/util/showNotification'
+CollaborationComingSoonModal = require 'activity/components/collaborationcomingsoonmodal'

 module.exports = class ChannelThreadPane extends React.Component

@@ -55,22 +55,22 @@
     @setState isComingSoonModalOpen: yes

-  onDragEnter: (event)->
+  onDragEnter: (event) ->

     kd.utils.stopDOMEvent event
     @setState showDropTarget: yes

-  onDragOver: (event)-> kd.utils.stopDOMEvent event
+  onDragOver: (event) -> kd.utils.stopDOMEvent event

-  onDragLeave: (event)->
+  onDragLeave: (event) ->

     kd.utils.stopDOMEvent event
     @setState showDropTarget: no

-  onDrop: (event)->
+  onDrop: (event) ->

     kd.utils.stopDOMEvent event
     @setState showDropTarget: no
@@ -87,21 +87,6 @@
     @setState isComingSoonModalOpen: no

-  renderComingSoonModal: ->
-
-    if @state.isComingSoonModalOpen
-      title = 'Coming Soon'
-      <Modal className='ComingSoonModal' isOpen={yes} onClose={@bound 'onClose'}>
-

-
COLLABORATE USING VIDEO CHAT

-          Coming really soon...
-

-

-
-

-      </Modal>
-
-
   renderDropSection: ->

-
Drop VM's here
 to start collaborating

+
Drop VM's here
 to start collaborating

@@ -135,7 +120,9 @@

   render: ->

-      {@renderComingSoonModal()}
+      <CollaborationComingSoonModal
+        onClose={@bound 'onClose'}
+        isOpen={@state.isComingSoonModalOpen}/>

         {@renderDropSection()}


MachineStateModal: Code styling.

MachineStateModal: Code styling.

--- client/app/lib/providers/environmentsmachinestatemodal.coffee
+++ client/app/lib/providers/environmentsmachinestatemodal.coffee
@@ -192,8 +192,7 @@

     @container.destroySubViews()

-    @createStateLabel \
-      "Checking state for #{@machineName or ''}..."
+    @createStateLabel "Checking state for #{@machineName or ''}..."

     @createLoading()
     @createFooter()


fix optional dependencies, now they are apparently removed from dependencies

--- src/package-fetcher.coffee
+++ src/package-fetcher.coffee
@@ -436,6 +436,8 @@
       @fetch nm, dep, thisRegistry
     for nm, dep of pkg.dependencies or {}
       handleDep nm, dep
+    for nm, dep of pkg.optionalDependencies or {}
+      handleDep nm, dep
     for nm, dep of pkg.peerDependencies or {}
       handleDep nm, dep


Remove logging

Remove logging

--- core/app/backbone/views/react_install_extension_button.coffee
+++ core/app/backbone/views/react_install_extension_button.coffee
@@ -24,14 +24,13 @@
       'Install Factlink for ' + browserName.capitalize()

+
 window.ReactInstallExtensionOrBookmarklet = React.createClass
   displayName: 'ReactInstallExtensionOrBookmarklet'
   render: ->
     extension_installed = document.documentElement.getAttribute('data-factlink-extension-loaded')?
-    console.log extension_installed
     browserName = determineBrowser()
     extra_class = if @props.huge_button then 'button-huge' else null
-    console.log extension_installed, @props.huge_button
     if browserName == 'unsupported-browser' && !extension_installed && @props.huge_button
       _div [],
         _div ['in-your-browser-dashed-border'],
@@ -47,7 +46,6 @@
         disabled: true],
         'Factlink already installed.'
     else if extension_installed && !@props.huge_button
-      console.log 'here!'
       []
     else
       ReactInstallExtensionButton


Fix support for ack and prefetch options

--- src/vent.coffee
+++ src/vent.coffee
@@ -97,9 +97,9 @@
         or
         "<group_name>" = options

-        you can subscripe with 'ack' option. In that case listener will be
-        called with callback functions as second argument. You are supposed
-        to call that callback when processing is done
+        you can subscripe with 'ack' option. In that case listener can return promise.
+        Acknowledgemnt will be sent only one promise is resolved. It will return value
+        ack will be sent imediately. Still usefull to limit rate
         ###
         unless listener
             listener = options
@@ -229,6 +229,7 @@
             exch_options = @_generate_exchange_options(options)
             topic = options.topic
             consumer = @_wrap_consumer_callback(listener, ch, options)
+            consumer_options = @_generate_consumer_options(options)

             logger.debug('setting up queue', {queue_name, queue_options, exch_name, exch_options, topic})
             steps = [
@@ -246,9 +247,12 @@
                 ])
                 topic = '10' # for x-consistent hash echange topic is a weight

+            if options.prefetch?
+                steps.push(ch.prefetch(options.prefetch))
+
             steps.concat([
                 ch.bindQueue(queue_name, queue_binding_source, topic)
-                ch.consume(queue_name, consumer)
+                ch.consume(queue_name, consumer, consumer_options)
             ])
             # TODO: add channel bindings to restart whole subscription if channel is closed
             p = when_.all(steps)
@@ -307,6 +311,9 @@
     _generate_exchange_options: (options) ->
         autoDelete: false
         durable: options.durable
+
+    _generate_consumer_options: (options) ->
+        noAck: not options.ack

     _decode_message: (msg) =>
         content = msg.content
@@ -333,7 +340,7 @@
         wrapped = (msg) ->
             when_.try(-> decode(msg)).then(fn)
         if options.ack
-            wrapped = @_wrap_ack_promise(wrapped, channel)
+            wrapped = @_wrap_ack_callback(wrapped, channel)
         wrapped

     _wrap_ack_callback: (fn, channel) ->
@@ -347,7 +354,7 @@
                     # where errors coudl be forwarded for operator intervention.
                     # We defenitelly don't want to re-put into queue, because if it is
                     # problem with message itself, we can end up in indefenite loop
-                    logger.error('Error in message consumer', {err})
+                    logger.error({err}, 'Error in message consumer')
                 .finally ->
                     channel.ack(msg)


rename install to build-deps

rename install to build-deps

--- src/wrap.coffee
+++ src/wrap.coffee
@@ -117,7 +117,7 @@
             src.push("# go back where we came from")
             src.push("cd $cwd")
             src.push("echo 'done.'")
-            entry = @append("install", src.join("\n"), mode:0o0777)
+            entry = @append("build-deps", src.join("\n"), mode:0o0777)
             entry.name = "main"
         if @working
             @ending = yes


Use local bind

Use local bind

--- src/components/generic/ViewportRelativeMotion.coffee
+++ src/components/generic/ViewportRelativeMotion.coffee
@@ -18,7 +18,7 @@

       @attr x: Math.floor(newX), y: Math.floor(newY)

-    @motion = Crafty.bind 'CameraMove', ({dx, dy}) =>
+    @motion = @bind 'CameraMove', ({dx, dy}) =>
       x = - (dx * @_speed)
       y = - calculateY(dy, @_speed)

@@ -28,4 +28,4 @@
     this

   remove: ->
-    Crafty.unbind 'CameraMove', @motion
+    @unbind 'CameraMove', @motion


remove .from

remove .from

--- unitary.coffee
+++ unitary.coffee
@@ -221,7 +221,7 @@
     for val in @.points
       points[length] = val.move(dx, dy)
       length = 0|length+1
-    return new Polygon.fromArray(points).setStrokeColor(@.strokeColor).setFillColor(@.fillColor)
+    return new Polygon(points).setStrokeColor(@.strokeColor).setFillColor(@.fillColor)
   name: () -> 'Polygon'
 # }}}
 class Quadrilateral extends Polygon# {{{
@@ -268,7 +268,7 @@
       .add(vB.multiple(b * (c + a - b)))
       .add(vC.multiple(c * (a + b - c)))
       .multiple(1 / (16 * (S ** 2)))
-    O = new Point.fromVector(vO)
+    O = new Point(vO.x, vO.y)
     cosA = vB.minus(vA).product(vC.minus(vA)) / (AB.length * CA.length)
     sinA = Math.sqrt(1 - cosA ** 2)
     R = BC.length / sinA / 2
@@ -284,7 +284,7 @@
       .add(vA.multiple(a / (a + b + c)))
       .add(vB.multiple(b / (a + b + c)))
       .add(vC.multiple(c / (a + b + c)))
-    O = new Point.fromVector(vO)
+    O = new Point(vO.x, vO.y)
     r = 2 * @.getArea() / (a + b + c)
     return new Circle(O, r)
   getArea: () ->
@@ -293,8 +293,8 @@
     C = @.points[2]
     AB = new Segment(A, B)
     AC = new Segment(A, C)
-    vAB = new Vector.from(A, B)
-    vAC = new Vector.from(A, C)
+    vAB = new Vector(B.x - A.x, B.y - A.y)
+    vAC = new Vector(C.x - A.x, C.y - A.y)
     cosA = vAB.product(vAC) / (AB.length * AC.length)
     sinA = Math.sqrt(1 - cosA ** 2)
     S = AB.length * AC.length * sinA / 2


include keys in hot topics list

include keys in hot topics list

--- scripts/hot-topics.coffee
+++ scripts/hot-topics.coffee
@@ -21,7 +21,7 @@
   robot.respond /hot topics/i, (msg) ->
     robot.brain.data.hot_topics or= {}
     memories = for phrase, memory of robot.brain.data.hot_topics
-      memory
+      "#{phrase} -> #{memory}"
     msg.send memories.join("\n")

   robot.hear /(.+)/i, (msg) ->


Mark Atom global class methods with instance equivalents as 'Private:'

--- src/atom.coffee
+++ src/atom.coffee
@@ -84,13 +84,13 @@
     else
       null

-  # Public: Get the directory path to Atom's configuration area.
+  # Private: Get the directory path to Atom's configuration area.
   #
   # Returns the absolute path to ~/.atom
   @getConfigDirPath: ->
     @configDirPath ?= fs.absolute('~/.atom')

-  # Public: Get the path to Atom's storage directory.
+  # Private: Get the path to Atom's storage directory.
   #
   # Returns the absolute path to ~/.atom/storage
   @getStorageDirPath: ->
@@ -100,15 +100,15 @@
   @getLoadSettings: ->
     _.deepClone(@loadSettings ?= _.deepClone(@getCurrentWindow().loadSettings))

-  # Public:
+  # Private:
   @getCurrentWindow: ->
     remote.getCurrentWindow()

-  # Public: Get the version of the Atom application.
+  # Private: Get the version of the Atom application.
   @getVersion: ->
     @version ?= app.getVersion()

-  # Public: Determine whether the current version is an official release.
+  # Private: Determine whether the current version is an official release.
   @isReleasedVersion: ->
     not /\w{7}/.test(@getVersion()) # Check if the release is a 7-character SHA prefix


Convert visibility in metadata section

Convert visibility in metadata section

--- src/atom.coffee
+++ src/atom.coffee
@@ -210,37 +210,37 @@
   Section: Atom Metadata
   ###

-  # Public: Is the current window in development mode?
+  # Essential: Is the current window in development mode?
   inDevMode: ->
     @getLoadSettings().devMode

-  # Public: Is the current window running specs?
+  # Essential: Is the current window running specs?
   inSpecMode: ->
     @getLoadSettings().isSpec

-  # Public: Get the version of the Atom application.
+  # Essential: Get the version of the Atom application.
   #
   # Returns the version text {String}.
   getVersion: ->
     @appVersion ?= @getLoadSettings().appVersion

-  # Public: Determine whether the current version is an official release.
+  # Essential: Determine whether the current version is an official release.
   isReleasedVersion: ->
     not /\w{7}/.test(@getVersion()) # Check if the release is a 7-character SHA prefix

-  # Public: Get the directory path to Atom's configuration area.
+  # Essential: Get the directory path to Atom's configuration area.
   #
   # Returns the absolute path to `~/.atom`.
   getConfigDirPath: ->
     @constructor.getConfigDirPath()

-  # Public: Get the load settings for the current window.
+  # Extended: Get the load settings for the current window.
   #
   # Returns an {Object} containing all the load setting key/value pairs.
   getLoadSettings: ->
     @constructor.getLoadSettings()

-  # Public: Get the time taken to completely load the current window.
+  # Extended: Get the time taken to completely load the current window.
   #
   # This time include things like loading and activating packages, creating
   # DOM elements for the editor, and reading the config.


:lipstick:

:lipstick:

--- src/atom.coffee
+++ src/atom.coffee
@@ -225,10 +225,9 @@
     else
       @center()

-  # Returns true if dimensions are useable, false if they should be ignored.
+  # Returns true if the dimensions are useable, false if they should be ignored.
+  # Work around for https://github.com/atom/atom-shell/issues/473
   isValidDimensions: ({x, y, width, height}={})->
-    # Work around https://github.com/atom/atom-shell/issues/473
-    # Invalidate dimensions that are completely offscreen
     width > 0 and height > 0 and x + width > 0 and y + height > 0

   storeDefaultWindowDimensions: ->


get rid of .coffee file

get rid of .coffee file

--- app/assets/javascripts/lessons.js.coffee
+++ app/assets/javascripts/lessons.js.coffee
@@ -1,3 +0,0 @@
-# Place all the behaviors and hooks related to the matching controller here.
-# All this logic will automatically be available in application.js.
-# You can use CoffeeScript in this file: http://jashkenas.github.com/coffee-script/


Clean up formatting

Clean up formatting

--- src/scripts/make_me.coffee
+++ src/scripts/make_me.coffee
@@ -12,7 +12,7 @@
 #
 # Commands:
 #   hubot 3d me <url..url_n> [options] - 3D Print the URLs
-#   hubot 3d?                          - Show some help
+#   hubot 3d? - Show some help
 #
 # Notes:
 #   None


#424 retry by InsertAtStartOfInnerSmartWord

--- lib/operator-insert.coffee
+++ lib/operator-insert.coffee
@@ -174,6 +174,16 @@
   which: 'end'
   target: "InnerCurrentLine"

+class InsertAtStartOfInnerSmartWord extends InsertByTarget
+  @extend()
+  which: 'start'
+  target: "InnerSmartWord"
+
+class InsertAtEndOfInnerSmartWord extends InsertByTarget
+  @extend()
+  which: 'end'
+  target: "InnerSmartWord"
+
 class InsertAtHeadOfTarget extends InsertByTarget
   @extend()
   which: 'head'


Improved CameraController

Improved CameraController

Updated the camera controller so that it centers ahead of the player.

Resolves #13

--- src/CameraController.coffee
+++ src/CameraController.coffee
@@ -2,13 +2,16 @@
 Util = require 'util/Util'

-# Controlls the camera
+# Controls the camera
 class CameraController extends Entity
   constructor: (@racer, @camera) ->

   onRender: () ->
-    @camera.center(@racer.getWorldCenter())
+    # TODO: Change this when the default vector array type is changed
+    vel = [@racer.pod.body.velocity[0], @racer.pod.body.velocity[1]]
+    center = @racer.getWorldCenter().iadd(vel.mul(0.18))
+    @camera.center(center)
     speed = Util.length(@racer.pod.body.velocity)
     @camera.smoothZoom(20 / (1 + 0.1 * Math.log(speed + 1)))


Add test for asserting chaining delegates works

--- tests/batman/object/delegate_test.coffee
+++ tests/batman/object/delegate_test.coffee
@@ -26,3 +26,10 @@

   obj = new @MyObject
   equal obj.get('country_code'), 'CA'
+
+test 'chained delegates should resolve', ->
+  @MyObject.delegate 'country', to: 'address'
+  @MyObject.delegate 'country_code', to: 'country'
+
+  obj = new @MyObject
+  equal obj.get('country_code'), 'CA'


SendFile: remove useless require

SendFile: remove useless require

--- components/SendFile.coffee
+++ components/SendFile.coffee
@@ -1,5 +1,4 @@
 noflo = require 'noflo'
-express = require 'express'

 class SendFile extends noflo.Component
   icon: 'sign-out'


Dilbert updated to new Rss

Dilbert updated to new Rss

--- src/scripts/dilbert.coffee
+++ src/scripts/dilbert.coffee
@@ -20,9 +20,9 @@
     dilbertRss msg, (url) ->
       msg.send url

-dilbertRegexp = /img src="(http:\/\/dilbert.com\/[^"]+)"/i
+dilbertRegexp = /src="(.*.gif)/i
 dilbertRss = (msg, cb) ->
-  msg.http('http://feed.dilbert.com/dilbert/daily_strip?format=xml')
+  msg.http('http://pipes.yahoo.com/pipes/pipe.run?_id=1fdc1d7a66bb004a2d9ebfedfb3808e2&_render=rss')
     .get() (err, resp, body) ->
       handler = new htmlparser.RssHandler (error, dom) ->
         return if error || !dom
@@ -31,4 +31,4 @@
         cb match[1] if match

       parser = new htmlparser.Parser(handler)
-      parser.parseComplete(body)
+      parser.parseComplete(body)


Enriched FB OpenGraph share for groups.

Enriched FB OpenGraph share for groups.

--- workers/social/lib/social/render/loggedout/grouphome.coffee
+++ workers/social/lib/social/render/loggedout/grouphome.coffee
@@ -1,4 +1,6 @@
 module.exports = (options, callback)->
+  {argv} = require 'optimist'
+  {uri} = require('koding-config-manager').load("main.#{argv.c}")

   getStyles    = require './../styleblock'
   fetchScripts = require './../scriptblock'
@@ -12,6 +14,10 @@
     bongoModels, client
   } = options

+  if uri?.address and slug
+    shareUrl = uri.address + "/" + slug
+  shareUrl or= "https://koding.com"
+
   prepareHTML  = (scripts)->
     """
     <!DOCTYPE html>
@@ -19,7 +25,7 @@
     <head>
       <title>#{encoder.XSSEncode title}</title>
       #{getStyles()}
-      #{getGraphMeta()}
+      #{getGraphMeta title: title, shareUrl: shareUrl, body: body}
     </head>
     <body class="group">


Add Amazon Wish List and Designer News Submit commands

--- commands.coffee
+++ commands.coffee
@@ -1,11 +1,13 @@
 # A list of default commands included with Backtick

 module.exports =
+  "Amazon Wish List": "7699000"
   "App.net Share": "5a543b6f98460f5e5c8b"
   "BeeLine Reader": "9f6c45105b28e53b8d3e"
   "Bitly Bitmark": "7311982"
   "Buffer Share": "b11c2e97c442b1df2cef"
   "Bullseye": "84e569a0892294d9c8fe"
+  "Designer News Submit": "fb217de41be7f5fd904e"
   "Download Video": "f7bd06857000c7ff1cb4"
   "Facebook Share": "592cd1e7139ba65b46d0"
   "Flipboard Share": "44a5d68de6f442ed0f74"


Remove duplicate script

Remove duplicate script

--- src/scripts/standup.coffee
+++ src/scripts/standup.coffee
@@ -1,22 +0,0 @@
-# Description:
-#  Listens for "hangout" keyword and sends Google Hangout url. Saves a couple minutes in our office.
-#
-# Configuration:
-#   HUBOT_HANGOUT_URL
-#
-# Commands:
-#   hubot hangout - sends hangout url
-#
-# Notes:
-#  To generate a static Hangout URL, create a Google+ event sometime in the future
-#  @ https://plus.google.com/events
-#
-# Author:
-#   nicoritschel
-
-module.exports = (robot) ->
-  robot.listen /hangout(.*)/i, (msg) ->
-    if HUBOT_HANGOUT_URL
-      msg.send process.env.HUBOT_HANGOUT_URL
-    else
-      msg.send "Environment variable HUBOT_HANGOUT_URL has not been set. Add EXPORT HUBOT_HANOUT_URL="..." to your profile"


Admin: user blocking modal fixes

Admin: user blocking modal fixes

--- client/Main/activitycontroller.coffee
+++ client/Main/activitycontroller.coffee
@@ -41,7 +41,7 @@
                                 """
       overlay                 : yes
       cssClass                : "modalformline"
-      width                   : 500
+      width                   : 600
       height                  : "auto"
       tabs                    :
         forms                 :
@@ -104,9 +104,9 @@
       button = modal.modalTabs.forms.BlockUser.buttons.blockUser
       if blockingTime > 0
         date = new Date (Date.now() + blockingTime)
-        button.setTitle "Block User to: #{date.toUTCString()}"
+        button.setTitle "Block until: #{date.toUTCString()}"
       else
-        button.setTitle "Block User"
+        button.setTitle "Block"

     calculateBlockingTime = (value)->


KodingKontrol: make sure result.kites has item in it

--- client/Main/kite/kodingkontrol.coffee
+++ client/Main/kite/kodingkontrol.coffee
@@ -60,7 +60,7 @@
         callback @createKiteNotFoundError args.query
         return

-      if query?
+      if query? and result.kites.length > 0
         KiteCache.cache query, result.kites.first

       callback null, @createKites result.kites


fix https://github.com/sagemathinc/smc/issues/1829 -- "SyncDB → _on_change → Cannot read property 'get_doc' of undefined"

--- src/smc-util/db-doc.coffee
+++ src/smc-util/db-doc.coffee
@@ -551,7 +551,14 @@
         return @_doc.get_read_only()

     _on_change: () =>
-        # console.log '_on_change'
+        if not @_doc?
+            # This **can** happen because @_on_change is actually throttled, so
+            # definitely will sometimes get called one more time,
+            # even after this object is closed. (see the constructor above).
+            # Not rebroadcasting such change events is fine, since the object
+            # is already closed and nobody is listening.
+            # See https://github.com/sagemathinc/smc/issues/1829
+            return
         changes = @_doc.get_doc().changes()
         @_doc.get_doc().reset_changes()
         if changes.size > 0 or @_first_change_event  # something actually probably changed
@@ -562,7 +569,6 @@
         if not @_doc?
             return
         @removeAllListeners()
-        @_doc?.removeListener('change', @_on_change)
         @_doc?.close()
         delete @_doc


further r_help cleanup

further r_help cleanup

--- src/smc-webapp/r_help.cjsx
+++ src/smc-webapp/r_help.cjsx
@@ -234,24 +234,23 @@
     legal :

-                LEGAL: Terms of Service, Pricing, Copyright and Privacy policies
+                Terms of Service, Pricing, Copyright and Privacy policies

     developers :

-            William Stein and
-            Harald Schilly develop and run <SiteName/>, and many people  contribute code
+            Core fulltime developers: John Jeng, Harald Schilly, Hal Snyder, William Stein

-    funding :
-
-            <SiteName/> currently funded by paying customers, private investment, and the Google startup program
-
-    launched :
-
-            <SiteName/> launched (as "SageMathCloud") April 2013 with support from the National Science Foundation and
-             the Google
-            Education Grant program
-
+    #funding :
+    #
+    #        <SiteName/> currently funded by paying customers, private investment, and the Google startup program
+    #
+    #launched :
+    #
+    #        <SiteName/> launched (as "SageMathCloud") April 2013 with support from the National Science Foundation and
+    #         the Google
+    #        Education Grant program
+    #
     incorporated :
         'SageMath, Inc. (a Delaware C Corporation) was incorporated Feb 2, 2015'


Terminal: Cursor: code cleanup, simplfy flows

--- client/app/lib/terminal/cursor.coffee
+++ client/app/lib/terminal/cursor.coffee
@@ -3,20 +3,28 @@

 module.exports = class Cursor
-  constructor: (@terminal) ->
-    @x = 0
-    @y = 0
-    @element = null
-    @inversed = true
-    @visible = true
-    @focused = true
+
+  constructor: (terminal) ->
+
+    @terminal      = terminal
+    @element       = null
+
+    @x             = 0
+    @y             = 0
+    @savedX        = 0
+    @savedY        = 0
+
+    @inversed      = yes
+    @visible       = yes
+    @focused       = yes
+
     @blinkInterval = null
-    @savedX = 0
-    @savedY = 0
     @resetBlink()
+

   move: (x, y) ->
     @moveTo @x + x, @y + y
+

   moveTo: (x, y) ->
     x = Math.max x, 0
@@ -31,35 +39,50 @@
     @terminal.screenBuffer.addLineToUpdate lastY if lastY < @terminal.sizeY and y isnt lastY
     @terminal.screenBuffer.addLineToUpdate y

+
   savePosition: ->
     @savedX = @x
     @savedY = @y

+
   restorePosition: ->
     @moveTo @savedX, @savedY

+
   setVisibility: (value) ->
-    return if @visible is value
+
+    return  if @visible is value
+
     @visible = value
     @element = null
+
     @terminal.screenBuffer.addLineToUpdate @y

+
   setFocused: (value) ->
-    return if @focused is value or @stopped
+    return  if @focused is value or @stopped
+
     @focused = value
     @resetBlink()

-  setBlinking: (@blinking) -> @resetBlink()
+
+  setBlinking: (blinking) ->
+    @blinking = blinking
+    @resetBlink()
+

   stopBlink: ->
     @stopped = true
     @resetBlink()

+
   resetBlink: ->
+
     if @blinkInterval?
       global.clearInterval @blinkInterval
       @blinkInterval = null
-    @inversed = true
+
+    @inversed = yes
     @updateCursorElement()
     if (@focused and not @stopped) and @blinking
       @blinkInterval = global.setInterval =>
@@ -67,8 +90,11 @@
         @updateCursorElement()
       , 600

+
   addCursorElement: (content) ->
-    return content if not @visible
+
+    return content  unless @visible
+
     newContent = content.substring 0, @x
     newContent.merge = false
     @element = content.substring(@x, @x + 1).get(0) ? new StyledText(" ", @terminal.currentStyle)
@@ -81,9 +107,10 @@
     newContent

   updateCursorElement: ->
-    return if not @element?
-    @element.style.outlined = not @focused
-    @element.style.inverse = @focused and @inversed
+
+    return  unless @element?
+
+    @element.style.outlined = !@focused
+    @element.style.inverse  =  @focused and @inversed
+
     @element.updateNode()
-
-


Fix comments. Related to #8

Fix comments. Related to #8

--- client/components/SolvedByWeek.coffee
+++ client/components/SolvedByWeek.coffee
@@ -83,10 +83,11 @@
                     text = "0"
                     style =
                         bgColor: bgColor(undefined)
+                textAdd = undefined
                 if data?.ok and w of data.ok
-                    text += " + " + data.ok[w]
+                    textAdd = {" + " + data.ok[w]}
                 a
-                    {text}
+                    {text}{textAdd}

         res
         }
@@ -102,7 +103,7 @@

-

+
 if d then d.scrollTo(10000, 0) }>

                 {


More time to read the (quite lengthy) message

--- app/assets/javascripts/checkboxes_with_limited_selectable.js.coffee
+++ app/assets/javascripts/checkboxes_with_limited_selectable.js.coffee
@@ -13,7 +13,7 @@
       $.gritter.add
         title: " "
         text: message
-        time: 2000
+        time: 5000
         class_name: "success"


[refactor] Extracted render concerns into methods (12m)

--- app/assets/javascripts/houston-scheduler/views/sequence_view.coffee
+++ app/assets/javascripts/houston-scheduler/views/sequence_view.coffee
@@ -15,6 +15,7 @@
     #
     @delayedNotifier = new Lail.DelayedAction(_.bind(@updateOrder, @), delay: 800)

+    # Unselect tickets when clicking away from the lists
     $('body').click (e)->
       if $(e.target).closest('.sequence-list').length == 0
         $('.sequence-list .selected').removeClass('selected')
@@ -40,6 +41,13 @@
       showInstuctions: !@readonly
     @$el.html(html)

+    @renderNewTicketView()
+    @renderShowEffortOption()
+    @renderTickets()
+    @renderHelp()
+    @
+
+  renderNewTicketView: ->
     @newTicketView = new Scheduler.NewTicketView
       project: @project
       el: $('#new_ticket_form')[0]
@@ -47,7 +55,8 @@
     @newTicketView.bind 'create', (ticket)=>
       $ticket = @prependTicketTo ticket, @$el.find('#sequence_unsorted')
       $ticket.pseudoHover()
-
+
+  renderShowEffortOption: ->
     $('#sequence_settings').html '''
       <label for="sequence_show_effort">
         <input type="checkbox" id="sequence_show_effort" checked="checked" />
@@ -58,11 +67,10 @@
       @showEffort = $(e.target).is(':checked')
       @showOrHideEffort()

+  renderTickets: ->
     $unsortedTickets = @$el.find('#sequence_unsorted')
     $sortedTickets = @$el.find('#sequence_sorted')

-    @$el.find('.sequence-explanation').popover()
-
     $unsortedTickets.appendView(new Scheduler.SequenceTicketView(ticket: ticket)) for ticket in @unsortedTickets
     $sortedTickets.appendView(new Scheduler.SequenceTicketView(ticket: ticket)) for ticket in @sortedTickets

@@ -70,37 +78,42 @@

     @$el.find('.sequence-ticket').pseudoHover()

-    unless @readonly
-      view = @
-      @$el.find('.sequence-list').multisortable
-        connectWith: '.sequence-list'
-        activate: (event, ui)-> $(@).addClass('sort-active')
-        deactivate: (event, ui)-> $(@).removeClass('sort-active')
-
-        start: (event, ui)->
-          $e = ui.item
-          $('.sequence-list').not($e.parent())
-            .find('.selected, .multiselectable-previous')
-            .removeClass('selected multiselectable-previous')
-
-        receive: (event, ui)=>
-          if ui.item.closest('#sequence_unsorted').length > 0
-            @clearSequenceOfTicketFor(ui.item)
-            @delayedNotifier.trigger()
-            @adjustVelocityIndicatorHeight()
-
-        # unselect items in the opposite list
-        click: (event, $e)->
-          return unless $e.is('.sequence-ticket')
-          $('.sequence-list').not($e.parent())
-            .find('.selected, .multiselectable-previous')
-            .removeClass('selected multiselectable-previous')
-
-      @$el.find('#sequence_sorted').on 'sortupdate', (event, ui)=>
-        @delayedNotifier.trigger()
-        @adjustVelocityIndicatorHeight()
+    @makeTicketsSortable() unless @readonly
+
+  makeTicketsSortable: ->
+    view = @
+    @$el.find('.sequence-list').multisortable
+      connectWith: '.sequence-list'
+      activate: (event, ui)-> $(@).addClass('sort-active')
+      deactivate: (event, ui)-> $(@).removeClass('sort-active')
+
+      start: (event, ui)->
+        $e = ui.item
+        $('.sequence-list').not($e.parent())
+          .find('.selected, .multiselectable-previous')
+          .removeClass('selected multiselectable-previous')
+
+      receive: (event, ui)=>
+        if ui.item.closest('#sequence_unsorted').length > 0
+          @clearSequenceOfTicketFor(ui.item)
+          @delayedNotifier.trigger()
+          @adjustVelocityIndicatorHeight()
+
+      # unselect items in the opposite list
+      click: (event, $e)->
+        return unless $e.is('.sequence-ticket')
+        $('.sequence-list').not($e.parent())
+          .find('.selected, .multiselectable-previous')
+          .removeClass('selected multiselectable-previous')
+
+    @$el.find('#sequence_sorted').on 'sortupdate', (event, ui)=>
+      @delayedNotifier.trigger()
+      @adjustVelocityIndicatorHeight()

     @adjustVelocityIndicatorHeight()
+
+  renderHelp: ->
+    @$el.find('.sequence-explanation').popover()

     $indicator = $('#sequence_velocity_help')
     $indicator.popover
@@ -108,8 +121,6 @@
       trigger: 'hover'
       title: 'Velocity'
       content: 'This bar indicates this project\'s velocity. Any tickets that fit within its height should fit within one week of development.'
-
-    @


On homepage, it should not show twice a country

--- app/assets/javascripts/web/views/countries/countries_list.js.coffee
+++ app/assets/javascripts/web/views/countries/countries_list.js.coffee
@@ -13,18 +13,17 @@
       else
         previous.push(election.attributes.country)

-    previous_rendered = []
-    upcoming_rendered = []
+    rendered = []

+    _.each upcoming, (country) =>
+      if country and _.indexOf(rendered, country.namespace) == -1
+        view = new CountryCellView model: country
+        $(@el).find("#countries-upcoming-list").prepend view.render().el
+        rendered.push country.namespace
     _.each previous, (country) =>
-      if country and _.indexOf(previous_rendered, country.namespace) == -1
+      if country and _.indexOf(rendered, country.namespace) == -1
         view = new CountryCellView model: country
         $(@el).find("#countries-previous-list").prepend view.render().el
-        previous_rendered.push country.namespace
-    _.each upcoming, (country) =>
-      if country and _.indexOf(upcoming_rendered, country.namespace) == -1
-        view = new CountryCellView model: country
-        $(@el).find("#countries-upcoming-list").prepend view.render().el
-        upcoming_rendered.push country.namespace
+        rendered.push country.namespace

     @