Handle 'JavaScript with JSX' grammar with node

--- lib/grammars.coffee
+++ lib/grammars.coffee
@@ -378,6 +378,14 @@
       command: "node"
       args: (context) -> [context.filepath]

+  'JavaScript with JSX':
+    "Selection Based":
+      command: "node"
+      args: (context)  -> ['-e', context.getCode()]
+    "File Based":
+      command: "node"
+      args: (context) -> [context.filepath]
+
   "JavaScript for Automation (JXA)":
     "Selection Based":
       command: "osascript"


Refactored collision node in hash_map.

Refactored collision node in hash_map.

--- lib/hash_map.coffee
+++ lib/hash_map.coffee
@@ -138,22 +138,21 @@
     @size = @bucket.length

   each: (func) ->
-    for [key, value] in @bucket
-      func([key, value])
+    for item in @bucket
+      func(item)

   get: (shift, hash, key) ->
-    pair[1] if (pair = this.getEntry(key))?
+    pair[1] if (pair = _.detect @bucket, (pair) -> util.equal(pair[0], key))?

   with: (shift, hash, leaf) ->
     if hash != @hash
       new BitmapIndexedNode().with(shift, @hash, this).with(shift, hash, leaf)
     else
-      newBucket = _.without(@bucket, this.getEntry(leaf.key))
-                   .concat([[leaf.key, leaf.value]])
+      newBucket = this.bucketWithout(leaf.key).concat([[leaf.key, leaf.value]])
       new CollisionNode(hash, newBucket)

   without: (shift, hash, key) ->
-    newBucket = _.without(@bucket, this.getEntry(key))
+    newBucket = this.bucketWithout key
     if newBucket.length < 2
       new LeafNode(hash, _.first(newBucket)...)
     else
@@ -161,7 +160,8 @@

   toString: -> "CollisionNode(#{@bucket.join(", ")})"

-  getEntry: (key) -> _.detect @bucket, (pair) -> util.equal(pair[0], key)
+  bucketWithout: (key) ->
+    item for item in @bucket when not util.equal(item[0], key)

 # A sparse interior node using a bitmap to indicate which of the


:fire: Remove all the subs from MessageRegistry

--- lib/messages.coffee
+++ lib/messages.coffee
@@ -1,20 +1,15 @@
 Helpers = require('./helpers')
-{CompositeDisposable, Emitter} = require 'atom'
+{Emitter} = require 'atom'

 class MessageRegistry
   constructor: (@linter)->
     @messages = new Map()
     @emitter = new Emitter
-    @subscriptions = new CompositeDisposable
-    @subscriptions.add atom.workspace.onDidChangeActivePaneItem =>
-      @messages.forEach (messages) => @classifyMessages(messages)
-      @emitter.emit 'did-classify'

   set: (linter, messages) ->
     Helpers.validateMessages(messages)
     @classifyMessages(messages)
     @messages.set(linter, messages)
-    @emitter.emit 'did-classify'
     @emitter.emit 'did-change', @messages

   delete: (linter) ->
@@ -27,9 +22,6 @@
   onDidChange: (callback) ->
     return @emitter.on 'did-change', callback

-  onDidClassify: (callback) ->
-    return @emitter.on 'did-classify', callback
-
   classifyMessages: (messages)->
     isProject = @linter.state.scope is 'Project'
     activeFile = atom.workspace.getActiveTextEditor()?.getPath()
@@ -41,7 +33,6 @@

   destroy: ->
     @messages.clear()
-    @subscriptions.dispose()
     @emitter.dispose()


Improve parenting code: remove extra data when root becomes nonroot; keep track of old position in MessagesParents; store oldParent id not contents!; detect and skip no-op cases; fix $pop -> $pull

--- lib/messages.coffee
+++ lib/messages.coffee
@@ -288,6 +288,12 @@
   message.title.trim().length == 0 and
   message.body.trim().length == 0

+_noLongerRoot = (message) ->
+  Messages.update message,
+    $unset:
+      submessageCount: ''
+      submessageLastUpdate: ''
+
 _submessagesChanged = (root) ->
   return unless root?
   Messages.update root,
@@ -367,9 +373,11 @@
     root = null
   if canEdit(child) and canPost group, parent
     cmsg = Messages.findOne child
+    oldIndex = null
     if oldParent
       oldParent = findMessageParent child
       if oldParent?
+        oldIndex = oldParent.children.indexOf child
         Messages.update oldParent._id,
           $pop: children: child
     if parent?
@@ -402,6 +410,7 @@
           $set: root: root ? child  ## actually must be root
         ,
           multi: true
+        _noLongerRoot child if root?
       _submessagesChanged cmsg.root     ## old root
       _submessagesChanged root ? child  ## new root
     doc =
@@ -409,6 +418,7 @@
       parent: parent
       position: position
       oldParent: oldParent
+      oldIndex: oldIndex
     if importing
       #cmsg = Messages.findOne child
       doc.updator = cmsg.creator


remove unused

remove unused

--- lib/operator.coffee
+++ lib/operator.coffee
@@ -1,4 +1,3 @@
-LineEndingRegExp = /(?:\n|\r\n)$/
 _ = require 'underscore-plus'
 {Point, Range, Disposable} = require 'atom'


Switched sig and nonceStore parameters for easier access

--- lib/provider.coffee
+++ lib/provider.coffee
@@ -8,7 +8,7 @@
   # Used as accessor to request parameters
   body: {}

-  constructor: (consumer_key, consumer_secret, signature_method=(new HMAC_SHA1()), nonceStore ) ->
+  constructor: (consumer_key, consumer_secret, nonceStore, signature_method=(new HMAC_SHA1()) ) ->

     if typeof consumer_key is 'undefined' or consumer_key is null
       throw new Error 'Must specify consumer_key'


Start date was removed in 2014 but stray reference

--- lib/reporter.coffee
+++ lib/reporter.coffee
@@ -117,7 +117,7 @@
     @consentedParams: ->
       memUse = process.memoryUsage()
       {
-        cd1: startDate if startDate = localStorage.getItem('metrics.sd')
+        # cd1: was start date, removed
         cd2: getOsArch()
         cm1: memUse.heapUsed >> 20 # Convert bytes to megabytes
         cm2: Math.round((memUse.heapUsed / memUse.heapTotal) * 100)


Refactored Sortable and got rid of all loops except one.

--- lib/sortable.coffee
+++ lib/sortable.coffee
@@ -4,15 +4,21 @@
 # execution time O(log n) per operation for adding an element and O(n)
 # for extracting a sorted result.
 #
-# The recursive helper functions merge(), addSeg() and mergeAll() from
-# the original algorithm have been replaced with iterative
-# versions to avoid stack overflow.
-#
 # A simple nested-pairs list of arrays is used instead of a list of
-# lists to represent the data .
+# lists to represent the data. As a consequence, the recursive helper
+# function merge() has been replaced by an iterative version for
+# efficiency reasons.
 #
 # Copyright (c) 2010 Olaf Delgado-Friedrichs (odf@github.com)
 # --------------------------------------------------------------------
+
+
+if typeof(require) != 'undefined'
+  require.paths.unshift('#{__dirname}/../lib')
+  pazy = require('trampoline')
+
+recur = pazy.recur
+resolve = pazy.resolve

 class Sortable
@@ -23,29 +29,35 @@
       [0, -> null]

   plus: ->
-    s = this
-    for x in arguments
-      t = s
-      s = new Sortable(s.less, s.size + 1, -> newSegments(x, t))
-    s
+    less = @less
+
+    addSeg = (seg, segs, bits) ->
+      if bits % 2 > 0
+        recur -> addSeg(merge(less, seg, segs[0]), segs[1], bits >> 1)
+      else
+        [seg, segs]
+
+    step = (s, args) ->
+      if args.length > 0
+        [x, a...] = args
+        newSegs = -> resolve addSeg([x], s._segs(), s.size)
+        recur -> step(new Sortable(less, s.size + 1, newSegs), a)
+      else
+        s
+
+    resolve step(this, arguments)

   sort: ->
-    [buf, segs] = [[], @_segs()]
-    while segs?
-      [buf, segs] = [merge(@less, buf, segs[0]), segs[1]]
-    buf
+    less = @less
+    step = (buf, segs) ->
+      if segs? then recur -> step(merge(less, buf, segs[0]), segs[1]) else buf
+    resolve step([], @_segs())

   merge = (less, xs, ys) ->
     [buf, ix, iy, lx, ly] = [[], 0, 0, xs.length, ys.length]
     while ix < lx and iy < ly
       if less(xs[ix], ys[iy]) then buf.push(xs[ix++]) else buf.push(ys[iy++])
     buf.concat(xs[ix..], ys[iy..])
-
-  newSegments = (x, t) ->
-    [seg, less, segs, bits] = [[x], t.less, t._segs(), t.size]
-    while bits % 2 > 0
-      [seg, segs, bits] = [merge(less, seg, segs[0]), segs[1], bits >> 1]
-    [seg, segs]

 # --------------------------------------------------------------------


--- src/jqt.coffee
+++ src/jqt.coffee
@@ -191,15 +191,27 @@

       return

-    @goBack = =>
+    @goBack = (toPage) =>
       # Error checking
       console.warn "History is empty." if customHistory.length < 1
       if customHistory.length is 1
         console.warn "You are on the first panel."
         window.history.go -1
+
+      # Go back an arbitrary number of internal pages.
+      if /^#.+/.test toPage
+        end = 0
+
+        for h, i in customHistory
+          if h.hash == toPage
+            end = i
+            break
+
+        customHistory.splice 1, end - 1
+
       from = customHistory[0]
       to = customHistory[1]
-
+
       return unless from? and to?

       if doNavigation(from.page, to.page, from.animation, true)


CoffeeScript is not JavaScript

CoffeeScript is not JavaScript

--- lib/wakatime.coffee
+++ lib/wakatime.coffee
@@ -10,7 +10,7 @@
 fs = require 'fs'
 os = require 'os'
 path = require 'path'
-process = require('child_process');
+process = require 'child_process'
 request = require 'request'

 module.exports =


Max bounds & min zoom.

Max bounds & min zoom.

--- src/map.coffee
+++ src/map.coffee
@@ -45,19 +45,20 @@
             url = "http://geoserver.hel.fi/mapproxy/wmts/osm-sm/etrs_tm35fin/{z}/{x}/{y}.png"
             opts =
                 maxZoom: 15
-                minZoom: 0
+                minZoom: 8
                 continuousWorld: true
                 tms: false

             map_layer = new L.TileLayer url, opts

             layer_control = null
-
+60°07′N 024°26′E
         map = new L.Map el,
             crs: crs
             continuusWorld: true
             worldCopyJump: false
             zoomControl: false
+            maxBounds: L.latLngBounds L.latLng(60, 24.5), L.latLng(60.5, 25.25)
             layers: [map_layer]

         map.setView [60.171944, 24.941389], 10


conservation instance in the stage

conservation instance in the stage

Former-commit-id: d786d12c26c63b39414f3851a9889765461e5fc3 [formerly b83f12edf79e4854236f486af88a0d03cf45e82b]
Former-commit-id: a67972f0a8558363570233a2d2c71e085c805ed9

--- src/msa.coffee
+++ src/msa.coffee
@@ -43,10 +43,9 @@
     # load seqs and add subviews
     @seqs = new SeqCollection data.seqs

-    # populate it and init the models
+    # populate it and init the global models
     @g.config = new Config data.conf
     @g.columns = new Columns() # for action on the columns like hiding
-    @g.columns.calcConservation @seqs
     @g.colorscheme = new Colorator()
     @g.selcol = new SelCol [],{g:@g}
     @g.vis = new Visibility data.vis


Add phantomArgs option

Add phantomArgs option

--- src/pdf.coffee
+++ src/pdf.coffee
@@ -69,7 +69,7 @@

   exec: (callback) ->
-    child = childprocess.spawn(@options.phantomPath, [@script])
+    child = childprocess.spawn(@options.phantomPath, [@script].concat(@options.phantomArgs))
     stdout = []
     stderr = []


Only look for attribute IDs in expressions

Currently, built-in nodes have human-readable IDs like "Node", so
0fb273c caused crashes when these human-readable IDs occurred in
expression strings. Since no /attributes/ have human-readable IDs,
this change is all that is needed to prevent this problem.

--- src/View/ExpressionCode.coffee
+++ src/View/ExpressionCode.coffee
@@ -114,9 +114,10 @@
     if attribute.exprString != newExprString
       newReferences = {}
       for own id, node of project.editingElementNodesById()
-        isUsed = (newExprString.indexOf(id) != -1)
-        if isUsed
-          newReferences[id] = node
+        if node.isVariantOf(Model.Attribute)
+          isUsed = (newExprString.indexOf(id) != -1)
+          if isUsed
+            newReferences[id] = node
       attribute.setExpression(newExprString, newReferences)


Fixed merge conflict

Fixed merge conflict

--- src/hubot-cah.coffee
+++ src/hubot-cah.coffee
@@ -37,7 +37,7 @@
 helpSummary += "\ncah hand - List cards in your hand"
 helpSummary += "\ncah play <#> <#> ... - Indicate white cards to be submitted as an answer, applied to black card blanks in order"
 helpSummary += "\ncah answers - List the submitted white cards once all have been submitted"
-helpSummary += "\ncah answers! - List the submitted white cards anytime (czar only)"
+# helpSummary += "\ncah answers! - List the submitted white cards anytime (czar only)"
 helpSummary += "\ncah choose <#> - Choose a winning answer (czar only)"
 helpSummary += "\ncah status - Display summary of current game"
 helpSummary += "\ncah skip - Discard current black card and assign a new Card Czar"
@@ -191,7 +191,8 @@
   if !db.czar?
     return "Waiting for players."
   else
-    return "*#{db.blackCard}* [#{db.czar}, #{db.answers.length}/#{db.activePlayers.length-1}]"
+
+    return "*#{db.blackCard}* [#{db.czar}, #{db.answers.length}/#{db.activePlayers.length - 1}]"

 # @param res: message object
 # @return name of message sender
@@ -231,8 +232,8 @@
       else
         res.send responseString
     else
-      res.reply "NOPE, not everyone has responded yet! (#{status})\n(Czars can use 'cah answers!' to see answers early)"
-
+      res.reply "NOPE, not everyone has responded yet! (#{status})"
+
   # combo hear and respond, prepends ^ to hear regex
   # good for allowing same commands in room and DM
   robot.hearspond = (regex, cb) ->
@@ -339,10 +340,10 @@
       res.reply "Submission accepted."

-  robot.hearspond /cah answers$/i, (res) ->
+  robot.hear /^cah answers$/i, (res) ->
     show_answers res

-  robot.hearspond /cah answers!/i, (res) ->
+  robot.hear /^cah answers!/i, (res) ->
     if (sender(res) != db.czar)
       res.reply "Whoa easy only the czar can force out the answers"
       return


have gmap canvas retrieve ranges from the frame (#6257)

--- bokehjs/src/coffee/models/plots/gmap_plot_canvas.coffee
+++ bokehjs/src/coffee/models/plots/gmap_plot_canvas.coffee
@@ -135,8 +135,8 @@

   _set_bokeh_ranges: () =>
     [proj_xstart, proj_xend, proj_ystart, proj_yend] = @_get_projected_bounds()
-    @x_range.setv({start: proj_xstart, end: proj_xend})
-    @y_range.setv({start: proj_ystart, end: proj_yend})
+    @frame.x_range.setv({start: proj_xstart, end: proj_xend})
+    @frame.y_range.setv({start: proj_ystart, end: proj_yend})

   _update_center: (fld) ->
     c = @map.getCenter().toJSON()


Cleanup: unnecessary scroll fires

Cleanup: unnecessary scroll fires

--- core/KDScrollView.coffee
+++ core/KDScrollView.coffee
@@ -77,8 +77,6 @@
       direction = if event._delta.delta > 0 then "up" else "down"
       @_scrollUponVelocity event._delta.delta,direction
       return no
-    # return (KD.getSingleton "windowController").scrollingEnabled
-    KD.getSingleton("windowController").emit "ScrollHappened", this, event

   # scroll:(event)->
   #   if @getOptions().ownScrollBars


Activity: create previous link taken to own method

--- client/Social/Activity/views/privatemessage/pane.coffee
+++ client/Social/Activity/views/privatemessage/pane.coffee
@@ -10,11 +10,6 @@

     super options, data

-    @listPreviousLink = new ReplyPreviousLink
-      delegate : @listController
-      click    : @bound 'listPreviousReplies'
-      linkCopy : 'Show previous replies'
-    , data

     # To keep track of who are the shown participants
     # This way we are preventing to be duplicates
@@ -22,6 +17,7 @@
     # once.
     @participantMap = {}

+    @createPreviousLink()
     @createParticipantsView()
     @createAddParticipantForm()

@@ -269,6 +265,15 @@
       origin    : participant

     @participantMap[participant._id] = yes
+
+
+  createPreviousLink: ->
+
+    @listPreviousLink = new ReplyPreviousLink
+      delegate : @listController
+      click    : @bound 'listPreviousReplies'
+      linkCopy : 'Show previous replies'
+    , @getData()

   createParticipantsView : ->


KodingClient: modify  button options according to given message

--- client/app/MainApp/maincontroller/maincontroller.coffee
+++ client/app/MainApp/maincontroller/maincontroller.coffee
@@ -79,23 +79,49 @@

         k.connect()

-        showErrorModal = (err, callback)->
+        showErrorModal = (message, callback)->
           modal = new KDBlockingModalView
-            title        : "Want to try again?"
-            content      : "
#{err.message}
"
+            title        : "Kite Registration"
+            content      : "
#{message}
"
+            height       : "auto"
+            overlay      : yes
+            buttons      : {}
+
+          Retry      =
+            style    : "modal-clean-gray"
+            callback : ->
+              modal.destroy()
+              callback?()
+
+          Cancel     =
+            style    : "modal-clean-red"
+            callback : ->
+              modal.destroy()
+              clear()
+
+          Ok         =
+            style    : "modal-clean-gray"
+            callback : ->
+              modal.destroy()
+              clear()
+
+          if /^Authentication\ already\ established/.test message
+            modal.setButtons {Ok}, yes
+          else
+            modal.setButtons {Retry, Cancel}, yes
+
+        showSuccessfulModal = (message, callback)->
+          modal = new KDBlockingModalView
+            title        : "Kite Registration"
+            content      : "
#{message}
"
             height       : "auto"
             overlay      : yes
             buttons      :
-              Retry      :
-                style    : "modal-clean-gray"
+              Ok         :
+                style    : "modal-clean-green"
                 callback : ->
                   modal.destroy()
                   callback?()
-              Cancel      :
-                style    : "modal-clean-red"
-                callback : ->
-                  modal.destroy()
-                  clear()

         handleInfo = (err, result)=>
           KD.remote.api.JKodingKey.registerHostnameAndKey {


sidebar: cast botchannels to array if necessary

--- client/app/lib/activity/sidebar/activitysideview.coffee
+++ client/app/lib/activity/sidebar/activitysideview.coffee
@@ -102,6 +102,9 @@

     if isFeatureEnabled('botchannel') and dataPath is 'privateMessages'
       botChannels = socialapi.getPrefetchedData 'bot'
+
+      botChannels = [botChannels]  unless Array.isArray botChannels
+
       items = botChannels.concat items

     if items.length


payment: remove pointless code

payment: remove pointless code

Well, since Number#toFixed() returns a string, and String does not override #toLocaleString(), calling #toLocaleString() is completely pointless because it will just defer to #toString, and we're already dealing with a string

--- client/landing/app/MainApp/payment/vmproductview.coffee
+++ client/landing/app/MainApp/payment/vmproductview.coffee
@@ -3,7 +3,7 @@
     super options, data

   formatCurrency: (cents) ->
-    "#{ ( cents / 100 ).toFixed(2).toLocaleString currency: 'USD' }"
+    "#{ ( cents / 100 ).toFixed(2) }"

   pistachio: ->
     """


fixes the help bug, but puts me back a bit

--- lib/bebopt.coffee
+++ lib/bebopt.coffee
@@ -20,18 +20,19 @@
 util      = require 'util'
 clone     = require 'clone'
 deepEqual = require './deep-equal'
+basename  = require('path').basename

 class Bebopt
   constructor: (@app) ->
-    @app ?= 'bebopt'
-    @_long = {}
-    @_short = {}
+    @app ?= basename(process.argv[1])
+    @_options = []
     @_parent = null
     # XXX for the people; lol, that is this is a protected copy of `argv'
     @_raw = process.argv
     @_cooked = []
     @_eaten = {}
     @usage = undefined
+    @_help = []

   _beatError: (parent, name) ->
     switch parent
@@ -47,11 +48,11 @@
         return false

   _makeOpt: (name) ->
-    _op = name.replace(/^.*?([:]*)$/, '$1')
+    new_op = name.replace(/^.*?([:]*)$/, '$1')
     name = name.replace(/^(.*?)[:]*$/, '$1')
-    if _op is '::'
+    if new_op is '::'
       op = 'optarg'
-    else if op is ':'
+    else if new_op is ':'
       op = 'arg'
     else
       op = 'flag'
@@ -60,7 +61,8 @@
       name: name
     }

-  # a bit of black magic ;)
+  # creates a function as a prototype of `context'.
+  # can be used to mass-produce class methods
   `function _makeFun(context, name, cb) {
     context.prototype[name] = cb;
     return context;
@@ -69,25 +71,38 @@
   [ 'shortBeat', 'longBeat' ].forEach((funcName) =>
     listName = funcName.replace(/Beat/, '')
     return _makeFun(@, funcName, (_name, fn) ->
-      # namespace err.. context injection ;)
       self = @
+      # determine the type of OPTION, based on the OPTION's name
       { op, name } = self._makeOpt(_name)
       self._beatError(listName, name)

       if fn is undefined
         self._parent.type ?= op
-        self["_#{listName}"][name] = self._parent
-        self["_#{listName}"][name].names.push(name)
-        self["_#{listName}"][name].list.push(listName)
-      else
-        self["_#{listName}"][name] =
+        child = clone(self._parent)
+        # give child a pointer to its parent
+        child.parent = self._parent
+        child.parent.index = child.index
+
+        ++child.index # increment child.index again to set it to its own index
+        child.name = name
+        child.list = listName
+        # give parent a pointer to its child
+        self._parent.child = child
+        self._options.push(child)
+      else
+        option =
           cb: fn
           type: op
-          names: []
-          list: []
-        self["_#{listName}"][name].names.push(name)
-        self["_#{listName}"][name].list.push(listName)
-      self._parent = self["_#{listName}"][name]
+          name: name
+          list: listName
+          child: null
+          parent: null
+        self._options.push(option)
+        index = self._options.length - 1
+        option = self._options.pop()
+        option.index = index
+        self._options.push(option)
+      self._parent = self._options.slice(-1)[0]
       return self))

   help: (text) =>
@@ -98,29 +113,44 @@
     @_parent = null
     return @

+  _parentOrChildUsage: (opt) =>
+    if typeof opt.usage isnt 'string'
+      if opt.parent is null
+        if opt.child is null
+          return ''
+        else
+          return @_options[opt.child.index].usage
+      else
+        return @_options[opt.parent.index].usage
+    else
+      return opt.usage
+
+  _makeHelp: () =>
+    @_options.forEach((opt) =>
+      dashes = if opt.list is 'short' then '-' else '--'
+      usage = @_parentOrChildUsage(opt)
+      if opt.child is null
+        if opt.parent is null
+          @_help.push("  #{dashes}#{opt.name}#{usage}")
+      else
+        if opt.parent is null
+          if dashes is '--'
+            @_help.push("  -#{opt.child.name}, #{dashes}#{opt.name}#{usage}")
+          else
+            @_help.push("  #{dashes}#{opt.name}, --#{opt.child.name}#{usage}")
+    )
+    @_log(@)
+
   printHelp: (fn) =>
     usage = if @usage is undefined then "Usage: #{@app}" else @usage
     if fn is undefined
       console.error(usage)
+      @_help.forEach((txt) ->
+        console.error(txt))
     else
       fn(usage)
-
-    _long = Object.keys(@_long).map((key) ->
-
-    _options = _long.concat(Object.keys(@_short))
-    @_cooked.forEach((food) ->
-      if food.list[0] isnt 'short'
-        food.names = food.names.reverse()
-        food.list = food.list.reverse()
-      food.names = food.names.map((name, ind) ->
-        dashes = if food.list[ind] is 'short' then '-' else '--'
-        name = "#{dashes}#{clone(name)}"
-        return name)
-      names = food.names.join(', ')
-      if fn is undefined
-        console.error("  #{names}#{food.usage}")
-      else
-        fn("  #{names}#{food.usage}"))
+      @_help.forEach((txt) ->
+        fn(txt))

   usage: (text) =>
     @usage = text
@@ -294,25 +324,22 @@
   _clean: () =>
     Object.keys(@).forEach((key) =>
       if /^_/.test(key)
-        switch key
-          when '_runCallbacks'
-            null
-          when '_log'
-            null
-          when '_raw'
-            null
-          when '_cooked'
-            null
-          when '_eaten'
-            null
-          else
-            delete this[key])
+        [
+          '_runCallbacks',
+          '_log',
+          '_raw',
+          '_cooked',
+          '_eaten',
+          '_help'
+        ].forEach((target) =>
+          if key isnt target
+            delete @[key]))
     return @

   parse: () =>
+    @_makeHelp()
     @_gather()
     @_resolveOpts()
-    @_log(@)
-    @_runCallbacks()
+    #@_runCallbacks()

 module.exports = Bebopt


Some code improvements

Some code improvements

--- lib/motions/general-motions.coffee
+++ lib/motions/general-motions.coffee
@@ -383,18 +383,15 @@
   previousFirstScreenRow: 0
   currentFirstScreenRow: 0

-  constructor: (@editor, @vimState) ->
-    super(@editor, @vimState)
-
   select: (count, options) ->
-    final_destination = @scrollScreen(count)
+    finalDestination = @scrollScreen(count)
     super(count, options)
-    @editor.setScrollTop(final_destination)
+    @editor.setScrollTop(finalDestination)

   execute: (count) ->
-    final_destination = @scrollScreen(count)
+    finalDestination = @scrollScreen(count)
     super(count)
-    @editor.setScrollTop(final_destination)
+    @editor.setScrollTop(finalDestination)

   moveCursor: (cursor, count=1) ->
     cursor.setScreenPosition([@getDestinationRow(count), 0])


Added more button mappings

Added more button mappings


Handle when host app already has a catch-all route (in iron-router) (#223)

--- router.coffee
+++ router.coffee
@@ -80,12 +80,33 @@

   routeAll: (routes) ->
     @routes = routes
+
+    # --------------------------------------------------------------------------
+    # IRON ROUTER
+
     if Package['iron:router']
       # Fast Render
       if Meteor.isServer
         routes.forEach (route) ->
           if route.fastRender
             FastRender.route route.path, route.fastRender
+
+      catchAll = _.findWhere(Package['iron:router'].Router.routes, _path: '/(.*)')
+      if catchAll
+        # If app has already defined a catch-all route, prepend our logic to its
+        # before hook
+
+        catchAllHook = catchAll.options.onBeforeAction
+        return catchAll.options.onBeforeAction = ->
+          template = Blog.Router.getTemplate()
+          if template
+            if Blog.settings.blogLayoutTemplate
+              @layout Blog.settings.blogLayoutTemplate
+            @render template
+          else if catchAllHook
+            catchAllHook.call @
+          else
+            @next()

       Package['iron:router'].Router.route '/(.*)',
         onBeforeAction: ->
@@ -99,6 +120,8 @@
         action: ->
           @next()

+    # --------------------------------------------------------------------------
+    # FLOW ROUTER

     else if Package['kadira:flow-router']
       Package['kadira:flow-router'].FlowRouter.route '/:any*',


array comprehension expression change

array comprehension expression change

--- react-and-coffeescript/client/code/app/entry.coffee
+++ react-and-coffeescript/client/code/app/entry.coffee
@@ -39,13 +39,8 @@

     componentDidMount: ->
         window.addEventListener 'con---sole', (a) =>
-            cc 'we heard', a.detail
-            # line_text = a.detail.reduce (acc, i) ->
-            #     acc + i.toString()
-            # , ''
-            line_text = [a.detail]
             @setState
-                logs: [@state.logs..., line_text]
+                logs: [@state.logs..., a.detail]

     render: ->
         div


update

update

--- src/main/coffeescript/nz/SceneBattleStart.coffee
+++ src/main/coffeescript/nz/SceneBattleStart.coffee
@@ -25,24 +25,23 @@

   loadMap: ->
     unless phina.asset.AssetManager.get('json',@mapName)
-      @one 'enterframe', ->
-        assets = json:{}
-        assets.json[@mapName] = "data/#{@mapName}.json"
-        @loadAsset assets, @loadCharacter
+      assets = json:{}
+      assets.json[@mapName] = "data/#{@mapName}.json"
+      @loadAsset assets, @loadCharacter
     else
       @loadCharacter()
     return

   loadCharacter: ->
     unless phina.asset.AssetManager.get('json','character_001')
-      @one 'enterframe', ->
-        assets = json:{}
-        assets.json['character_001'] = "data/character_001.json"
-        @loadAsset assets, ->
-          console.log 'end'
+      assets = json:{}
+      assets.json['character_001'] = "data/character_001.json"
+      @loadAsset assets, ->
+        console.log 'end'
     return

   loadAsset: (assets,cb) ->
-    @app.pushScene phina.game.LoadingScene assets: assets
     if cb?
       @one 'resume', cb
+    @one 'enterframe', ->
+      @app.pushScene phina.game.LoadingScene assets: assets


moving og tag remove/append under one if block

--- src/scripts/helpers/backbone/views/base.coffee
+++ src/scripts/helpers/backbone/views/base.coffee
@@ -95,22 +95,21 @@
       return data

     addOpenGraphMetaTags: () ->
-      url = window.location.href
-      title = @pageTitle
       summary = @summary?() or @summary
-      image = location.origin + '/images/social/logo.png'
-      head = $('head')

-      $('meta[property="og:url"]').remove()
-      head.append("<meta property=\"og:url\" content=\"#{url}\">")
-      if title isnt undefined
+      if summary isnt undefined
+        url = window.location.href
+        title = document.title
+        image = location.origin + '/images/social/logo.png'
+        head = $('head')
+        $('meta[property="og:url"]').remove()
+        head.append("<meta property=\"og:url\" content=\"#{url}\">")
         $('meta[property="og:title"]').remove()
         head.append("<meta property=\"og:title\" content=\"#{title}\">")
-      if summary isnt undefined
         $('meta[property="og:description"]').remove()
         head.append("<meta property=\"og:description\" content=\"#{summary}\">")
-      $('meta[property="og:image"]').remove()
-      head.append("<meta property=\"og:image\" content=\"#{image}\">")
+        $('meta[property="og:image"]').remove()
+        head.append("<meta property=\"og:image\" content=\"#{image}\">")

     _render: () ->
       _.each @regions, (region) -> region.empty()


do not rerender header after it's loaded

--- src/scripts/modules/media/header/header.coffee
+++ src/scripts/modules/media/header/header.coffee
@@ -78,7 +78,7 @@
       @mediaBody = options.mediaBody

       @listenTo(@model, 'change:downloads change:buyLink change:title change:active', @render)
-      @listenTo(@model, 'change:currentPage change:currentPage.active change:currentPage.loaded', @render)
+      @listenTo(@model, 'change:currentPage change:currentPage.active', @render)
       @listenTo(@model, 'change:abstract change:currentPage.abstract change:currentPage.chapter', @render)
       @listenTo(session, 'change', @render)
       @listenTo(@model, 'change:currentPage.editable change:currentPage.canPublish', @render)
@@ -133,3 +133,4 @@
         toFocus.setAttribute('tabindex', '-1')
         toFocus.focus()
         toFocus.removeAttribute('tabindex')
+        window.pageWasChangedWithKeyboard = false


make it so migrate can open project even if messed up

--- salvus/compute.coffee
+++ salvus/compute.coffee
@@ -793,11 +793,15 @@
     # open project files on some node
     open: (opts) =>
         opts = defaults opts,
+            ignore_recv_errors : false
             cb     : required
         @dbg("open")()
+        args = [@assigned]
+        if opts.ignore_recv_errors
+            args.push('--ignore_recv_errors')
         @_action
             action : "open"
-            args   : [@assigned]
+            args   : args
             cb     : opts.cb

     # start local_hub daemon running (must be opened somewhere)
@@ -878,6 +882,7 @@

     ensure_opened_or_running: (opts) =>
         opts = defaults opts,
+            ignore_recv_errors : false
             cb     : required   # cb(err, state='opened' or 'running')
         state = undefined
         dbg = @dbg("ensure_opened_or_running")
@@ -904,6 +909,7 @@
                 else if state == 'closed'
                     dbg("opening")
                     @open
+                        ignore_recv_errors : opts.ignore_recv_errors
                         cb : (err) =>
                             if err
                                 cb(err)
@@ -912,6 +918,10 @@
                                     dbg("it opened")
                                     state = 'opened'
                                     cb()
+                                    # also fire off this, which will check if project hasn't yet
+                                    # been migrated successfully, and if so run one safe
+                                    # rsync --update (so it won't overwrite newer files)
+                                    @migrate_update_if_never_before()
                 else
                     cb("bug -- state=#{state} should be stable but isn't known")
         ], (err) => opts.cb(err, state))
@@ -1416,6 +1426,7 @@
                 @set_quotas(quotas)
         ], (err) => opts.cb(err))

+    # delete this once it has been run on all projects
     migrate_update_if_never_before: (opts) =>
         opts = defaults opts,
             subdir : false
@@ -1430,7 +1441,7 @@
                     where : {project_id : @project_id}
                     columns : ['migrated']
                     cb      : (err, result) =>
-                        dbg("got #{err}, #{misc.to_safe_str(result)}")
+                        dbg("got err=#{err}, result=#{misc.to_safe_str(result)}")
                         if err
                             cb(err)
                         else
@@ -1441,7 +1452,9 @@
                     cb()
                 else
                     dbg("not migrated so migrating after first opening")
-                    @ensure_opened_or_running(cb:cb)
+                    @ensure_opened_or_running
+                        ignore_recv_errors : true
+                        cb : cb
             (cb) =>
                 if migrated
                     cb()
@@ -1730,7 +1743,7 @@
                     # the time the project was assigned to this node, which is the first
                     # argument to open.  We then remove that argument.
                     assigned = opts.args[0]
-                    opts.args = []
+                    opts.args.shift()
                 state_info = STATES[state]
                 if not state_info?
                     err = "bug / internal error -- unknown state '#{misc.to_json(state)}'"


Fixes issue where destructured assignment params were incorrectly identified as duplicates.

--- nodes.coffee
+++ nodes.coffee
@@ -1278,7 +1278,7 @@
     for obj in name.objects
       # * assignments within destructured parameters `{foo:bar}`
       if obj instanceof Assign
-        names.push obj.variable.base.value
+        names.push obj.value.base.value
       # * splats within destructured parameters `[xs...]`
       else if obj instanceof Splat
         names.push obj.name.unwrap().value


better printing for OpNodes with running with --nodes

--- nodes.coffee
+++ nodes.coffee
@@ -111,9 +111,10 @@

   # `toString` representation of the node, for inspecting the parse tree.
   # This is what `coffee --nodes` prints out.
-  toString: (idt) ->
+  toString: (idt, override) ->
     idt: or ''
-    '\n' + idt + @class + (child.toString(idt + TAB) for child in @collectChildren()).join('')
+    children: (child.toString idt + TAB for child in @collectChildren()).join('')
+    '\n' + idt + (override or @class) + children

   eachChild: (func) ->
     return unless @children
@@ -1014,6 +1015,9 @@
   isChainable: ->
     indexOf(@CHAINABLE, @operator) >= 0

+  toString: (idt) ->
+    super(idt, @class + ' ' + @operator)
+
   compileNode: (o) ->
     o.operation: true
     return @compileChain(o)      if @isChainable() and @first.unwrap() instanceof OpNode and @first.unwrap().isChainable()


merging in gfxmonk's cleanup to how children of nodes are determined. Removing some (so far) unused portions.

--- nodes.coffee
+++ nodes.coffee
@@ -25,7 +25,7 @@
   (klass::is_pure_statement: -> true) if only

 children: (klass, child_attrs...) ->
-  klass::_children_attributes: child_attrs
+  klass::children_attributes: child_attrs

 #### BaseNode

@@ -96,11 +96,11 @@
   # scope boundaries.
   contains: (block) ->
     contains: false
-    @_traverse_children false, (node, attr, index) =>
+    @traverse_children false, (node) ->
       if block(node)
         contains: true
         return false
-    return contains
+    contains

   # Is this node of a certain type, or does it contain the type?
   contains_type: (type) ->
@@ -112,7 +112,7 @@
     @is_pure_statement() or @contains (n) -> n.is_pure_statement()

   # Perform an in-order traversal of the AST. Crosses scope boundaries.
-  traverse: (block) -> @_traverse_children true, block
+  traverse: (block) -> @traverse_children true, block

   # `toString` representation of the node, for inspecting the parse tree.
   # This is what `coffee --nodes` prints out.
@@ -121,28 +121,20 @@
     '\n' + idt + @constructor.name + (child.toString(idt + TAB) for child in @children()).join('')

   children: ->
-    _children: []
-    @_each_child (child) -> _children.push(child)
-    _children
-
-  _each_child: (func) ->
-    for attr in @_children_attributes
-      child_collection: this[attr]
-      continue unless child_collection?
-      if child_collection instanceof Array
-        i: 0
-        for child in child_collection
-          result: func(child, attr, i)
-          return if result is false
-          i += 1
-      else
-        func(child_collection, attr)
-
-  _traverse_children: (cross_scope, func) ->
-    return unless @_children_attributes
-    @_each_child (child, attr, i) ->
+    nodes: []
+    @each_child (node) -> nodes.push node
+    nodes
+
+  each_child: (func) ->
+    for attr in @children_attributes when this[attr]
+      for child in flatten [this[attr]]
+        return if func(child) is false
+
+  traverse_children: (cross_scope, func) ->
+    return unless @children_attributes
+    @each_child (child) ->
       func.apply(this, arguments)
-      child._traverse_children(cross_scope, func) if child instanceof BaseNode
+      child.traverse_children(cross_scope, func) if child instanceof BaseNode

   # Default implementations of the common node identification methods. Nodes
   # will override these with custom logic, if needed.
@@ -179,10 +171,6 @@
   # Is this an empty block of code?
   empty: ->
     @expressions.length is 0
-
-  # Make a copy of this node.
-  copy: ->
-    new Expressions @Expressions.slice()

   # An Expressions node does not return its entire body, rather it
   # ensures that the final expression is returned.
@@ -853,9 +841,9 @@
   top_sensitive: ->
     true

-  # Short-circuit _traverse_children method to prevent it from crossing scope boundaries
+  # Short-circuit traverse_children method to prevent it from crossing scope boundaries
   # unless cross_scope is true
-  _traverse_children: (cross_scope, func) -> super(cross_scope, func) if cross_scope
+  traverse_children: (cross_scope, func) -> super(cross_scope, func) if cross_scope

   toString: (idt) ->
     idt: or ''


Added deferred tests for reading/writing UTF-8 and C-strings

--- spec/byte-buffer-test.coffee
+++ spec/byte-buffer-test.coffee
@@ -119,6 +119,12 @@
     expect(b.readShort(ByteBuffer.BIG_ENDIAN)).toEqual(-128)
     expect(b.readUnsignedShort(ByteBuffer.LITTLE_ENDIAN)).toEqual(128)

+  it '//can read and write UTF-8 strings', ->
+    # TODO: Assertions
+
+  it '//can read and write NULL-terminated C-strings', ->
+    # TODO: Assertions
+
   it 'can skip bytes', ->
     b = new ByteBuffer(4)
     expect(b.skip().index).toEqual(1)


removed extra parens from `in`/`return` compilations

--- nodes.coffee
+++ nodes.coffee
@@ -296,7 +296,7 @@
     expr = ''
     if @expression
       o.asStatement = true if @expression.isStatement(o)
-      expr = ' ' + @expression.compile(o)
+      expr = ' ' + @expression.compileBare o
     "#{@tab}return#{expr};"

 #### Value
@@ -1252,23 +1252,23 @@
   constructor: (@object, @array) ->
     super()

-  isArray: ->
-    @array instanceof Value and @array.isArray()
-
-  compileNode: (o) ->
-    if @isArray() then @compileOrTest o else @compileLoopTest o
+  compileNode: (o) ->
+    code = if @array instanceof Value and @array.isArray()
+      @compileOrTest o
+    else
+      @compileLoopTest o
+    if @parenthetical then code else "(#{code})"

   compileOrTest: (o) ->
-    [obj1, obj2] = @object.compileReference o, precompile: yes
+    [sub, ref] = @object.compileReference o, precompile: yes
     tests = for item, i in @array.base.objects
-      "#{ if i then obj2 else obj1 } === #{ item.compile o }"
-    "(#{ tests.join ' || ' })"
+      "#{ if i then ref else sub } === #{ item.compile o }"
+    tests.join ' || '

   compileLoopTest: (o) ->
-    [obj1, obj2] = @object.compileReference merge(o, top: yes), precompile: yes
-    prefix = if obj1 isnt obj2 then "#{obj1}, " else ''
-    code = "#{prefix}#{utility 'indexOf'}.call(#{@array.compile o}, #{obj2}) >= 0"
-    if @parenthetical then code else "(#{code})"
+    [sub, ref] = @object.compileReference merge(o, top: yes), precompile: yes
+    code = utility('indexOf') + ".call(#{ @array.compile o }, #{ref}) >= 0"
+    if sub is ref then code else sub + ', ' + code

 #### Try


issue #2333: removed gratuitous test, refactored

--- nodes.coffee
+++ nodes.coffee
@@ -807,18 +807,17 @@
       if prop?
         propName = prop.unwrapAll().value.toString()
         isDuplicate = (x) ->
-          propName is x or +propName is +x or
-          do ->
-            ex = ep = null
-            unless /^[a-z$_]/i.test x
-              ex = eval x
-              return true if propName is ex
-            unless /^[a-z$_]/i.test propName
-              ep = eval propName
-              return true if x is ep
-            if ex? and ep?
-              return true if ex is ep
-            false
+          return true if propName is x
+          ex = ep = null
+          unless /^[a-z$_]/i.test x
+            ex = eval x
+            return true if propName is ex
+          unless /^[a-z$_]/i.test propName
+            ep = eval propName
+            return true if x is ep
+          if ex? and ep?
+            return true if ex is ep
+          false
         if any propNames, isDuplicate
           throw SyntaxError "multiple object literal properties named \"#{propName}\""
         propNames.push propName


optimizing comprehensions source references

--- nodes.coffee
+++ nodes.coffee
@@ -1429,13 +1429,8 @@
       sourcePart  = source.compileVariables(o)
       forPart     = source.compile merge o, index: ivar, step: @step
     else
-      svar = @source.compile o
-      if IDENTIFIER.test(svar) and scope.check(svar, immediate: on)
-        sourcePart = ''
-      else
-        ref = scope.freeVariable 'ref'
-        sourcePart = "#{ref} = #{svar};"
-        svar = ref
+      [sourcePart, svar] = @source.compileReference merge(o, top: yes), precompile: yes
+      sourcePart = if sourcePart is svar then '' else "#{sourcePart};"
       namePart = if @pattern
         new AssignNode(@name, literal "#{svar}[#{ivar}]").compile merge o, top: on
       else if name


Add spec for first match exceeding end of range in backwardsScanInRange

--- spec/text-buffer-spec.coffee
+++ spec/text-buffer-spec.coffee
@@ -1816,7 +1816,7 @@
           expect(ranges[1]).toEqual [[6,6], [6,9]]

       describe "when the portion of the match within the range does not matches the regex", ->
-        it "calls the iterator with the truncated match", ->
+        it "does not call the iterator with the truncated match", ->
           matches = []
           ranges = []
           buffer.scanInRange /cu(r*)e/g, [[4,0], [6,9]], ({match, range}) ->
@@ -1931,6 +1931,41 @@

         expect(matches[0][0]).toBe ""
         expect(ranges[0]).toEqual [[0,0], [0,0]]
+
+    describe "when the first regex match exceeds the end of the range", ->
+      describe "when the portion of the match within the range also matches the regex", ->
+        it "calls the iterator with the truncated match", ->
+          matches = []
+          ranges = []
+          buffer.backwardsScanInRange /cu(r*)/g, [[4,0], [6,9]], ({match, range}) ->
+            matches.push(match)
+            ranges.push(range)
+
+          expect(matches.length).toBe 2
+          expect(ranges.length).toBe 2
+
+          expect(matches[0][0]).toBe 'cur'
+          expect(matches[0][1]).toBe 'r'
+          expect(ranges[0]).toEqual [[6,6], [6,9]]
+
+          expect(matches[1][0]).toBe 'curr'
+          expect(matches[1][1]).toBe 'rr'
+          expect(ranges[1]).toEqual [[5,6], [5,10]]
+
+      describe "when the portion of the match within the range does not matches the regex", ->
+        it "does not call the iterator with the truncated match", ->
+          matches = []
+          ranges = []
+          buffer.backwardsScanInRange /cu(r*)e/g, [[4,0], [6,9]], ({match, range}) ->
+            matches.push(match)
+            ranges.push(range)
+
+          expect(matches.length).toBe 1
+          expect(ranges.length).toBe 1
+
+          expect(matches[0][0]).toBe 'curre'
+          expect(matches[0][1]).toBe 'rr'
+          expect(ranges[0]).toEqual [[5,6], [5,11]]

     describe "when the iterator calls the 'replace' control function with a replacement string", ->
       it "replaces each occurrence of the regex match with the string", ->


#188 fix spec

#188 fix spec

--- spec/text-object-spec.coffee
+++ spec/text-object-spec.coffee
@@ -652,7 +652,7 @@
         111]00{555}
         """
     describe "inner", ->
-      ffit "select forwarding range within enclosed range(if exists)", ->
+      it "select forwarding range within enclosed range(if exists)", ->
         set cursor: [2, 0]
         keystroke 'v'
         ensure ';', selectedText: "222"
@@ -666,7 +666,13 @@
         ensure ':', selectedText: '"222"'
         ensure ':', selectedText: "{333}"
         ensure ':', selectedText: "(\n444()444\n)"
-        ensure ':', selectedText: "()"
+        ensure ':', selectedText: """
+        [11
+        11"222"11{333}11(
+        444()444
+        )
+        111]
+        """

   xdescribe "Tag", ->
     describe "inner-tag", ->


Test cookie expiration.

Test cookie expiration.

--HG--
branch : qunit

--- src/js/collection-editor-tests.coffee
+++ src/js/collection-editor-tests.coffee
@@ -19,6 +19,15 @@
   set_cookie 'delete_cookie_test', 'test value', 60
   delete_cookie 'delete_cookie_test'
   equal( get_cookie('delete_cookie_test'), null )
+
+asyncTest "cookies set by set_cookie should expire", ->
+  expect(2)
+  set_cookie 'expire_cookie_test', 'test value', 1
+  equal( get_cookie('expire_cookie_test'), 'test value' )
+  setTimeout ->
+    equal( get_cookie('expire_cookie_test'), null )
+    start()
+  , 1001

 module "access token cookies",
   setup: ->


fix an issue where background image wouldn't show on new windows

--- lib/config.coffee
+++ lib/config.coffee
@@ -117,11 +117,11 @@
       applyFontWeight(atom.config.get('isotope-ui.fontWeight'))
       applyCompactness()
       applyTreeColor()
+      applyBackgroundColor()
       applyBackgroundGradient()
       applyBackgroundImage()
       applyGutterStyle()
       applyTooltipContrast()
-      applyBackgroundColor()

     # run when configs change


Create new model method insertOrUpsert based on _id

--- packages/rocketchat-lib/server/models/_Base.coffee
+++ packages/rocketchat-lib/server/models/_Base.coffee
@@ -15,6 +15,17 @@

 	insert: ->
 		return @model.insert.apply @model, arguments
+
+	insertOrUpsert: (args...) ->
+		if args[0]?._id?
+			_id = args[0]._id
+			delete args[0]._id
+			args.unshift
+				_id: _id
+			@model.upsert.apply @model, args
+			return _id
+		else
+			return @model.insert.apply @model, args

 	update: ->
 		return @model.update.apply @model, arguments


Journal: Expose as public API on noflo.

Journal: Expose as public API on noflo.

--- src/lib/NoFlo.coffee
+++ src/lib/NoFlo.coffee
@@ -15,6 +15,13 @@
 # [Graph](Graph.html) is used for instantiating FBP graph definitions.
 exports.graph = require('./Graph')
 exports.Graph = exports.graph.Graph
+
+# ### Graph journal
+#
+# Journal is used for keeping track of graph changes
+exports.journal = require('./Journal')
+exports.Journal = exports.journal.Journal
+
 # ## Network interface
 #
 # [Network](Network.html) is used for running NoFlo graphs.


Make coffee commands work (hopefully)

Make coffee commands work (hopefully)

--- scripts/coffee.coffee
+++ scripts/coffee.coffee
@@ -10,11 +10,9 @@
 #   hubot fresh pot - Announce that a pot is finished
 #   hubot fp - Alias for: hubot fresh pot
 #   hubot dibs - Reserve your spot in line for a current brew
-#   hubot :coffee: balance [user] - Check your coffee balance (defaults to you)
-#   hubot :coffee: top [n] - See the top coffee balances (defaults to 10)
-#   hubot :coffee: bottom [n] - See the bottom coffee balances (defaults to 10)
-
-
+#   hubot coffee balance [user] - Check your coffee balance (defaults to you)
+#   hubot coffee top [n] - See the top coffee balances (defaults to 10)
+#   hubot coffee bottom [n] - See the bottom coffee balances (defaults to 10)

 # water = 3/16 * cups
 # scoops = .75 * cups + 2
@@ -63,7 +61,7 @@

     msg.send "You need #{scoops} level scoops and #{water} L of water."

-  robot.respond /(?::coffee:|☕) balance(?:\s+@?([^ ]+)\s*)?$/i, (msg) ->
+  robot.respond /coffee balance(?:\s+@?([^ ]+)\s*)?$/i, (msg) ->
     clientName = msg.match[1] || msg.message.user.name
     client = robot.brain.usersForRawFuzzyName(clientName)[0]

@@ -76,13 +74,13 @@
       return
     msg.send coffeeconomy.accountFormatter coffeeconomy.account client

-  robot.respond /(?::coffee:|☕) top(?:\s+(\d+))?$/i, (msg) ->
+  robot.respond /coffee top(?:\s+(\d+))?$/i, (msg) ->
     limit = parseInt msg.match[1]?.trim() || 10
     count = 1
     msg.send "These are the top :coffee: accounts:\n" +
         (coffeeconomy.topListEntryFormatter count++, account for account in coffeeconomy.top limit).join("\n")

-  robot.respond /(?::coffee:|☕) bottom(?:\s+(\d+))?$/i, (msg) ->
+  robot.respond /coffee bottom(?:\s+(\d+))?$/i, (msg) ->
     limit = parseInt msg.match[1]?.trim() || 10
     total = coffeeconomy.length()


added files button for mobile view and got rid of columns for the row with buttons and avatars to deal with spacing issues

--- src/smc-webapp/editor_chat.cjsx
+++ src/smc-webapp/editor_chat.cjsx
@@ -988,23 +988,23 @@
         ##########################################
             <Grid>
                 <Row style={marginBottom:'5px'}>
-                    <Col xs={3} style={padding:'0px'}>
-                        <UsersViewing
-                              file_use_id = {@props.file_use_id}
-                              file_use    = {@props.file_use}
-                              account_id  = {@props.account_id}
-                              user_map    = {@props.user_map} />
-                    </Col>
-                    <Col xs={9} style={padding:'2px', textAlign:'right'}>
-                        <ButtonGroup>
-                            <Button onClick={@show_timetravel} bsStyle='info'>
-                                <Icon name='history'/> TimeTravel
-                            </Button>
-                            <Button onClick={@scroll_to_bottom}>
-                                <Icon name='arrow-down'/> Scroll to Bottom
-                            </Button>
-                        </ButtonGroup>
-                    </Col>
+                    <ButtonGroup>
+                        <Button className='smc-small-only'
+                            onClick={@show_files}>
+                            <Icon name='toggle-up'/> Files
+                        </Button>
+                        <Button onClick={@show_timetravel} bsStyle='info'>
+                            <Icon name='history'/> TimeTravel
+                        </Button>
+                        <Button onClick={@scroll_to_bottom}>
+                            <Icon name='arrow-down'/> Scroll to Bottom
+                        </Button>
+                    </ButtonGroup>
+                    <UsersViewing
+                          file_use_id = {@props.file_use_id}
+                          file_use    = {@props.file_use}
+                          account_id  = {@props.account_id}
+                          user_map    = {@props.user_map} />
                 </Row>
                 <Row>
                     <Col md={12} style={padding:'0px 2px 0px 2px'}>


Typo fix

Typo fix

--- src/lib/nodejs/ComponentLoader.coffee
+++ src/lib/nodejs/ComponentLoader.coffee
@@ -153,7 +153,7 @@
   getSource: (name, callback) ->
     unless @ready
       @listComponents =>
-        @getSource packageId, name, callback
+        @getSource name, callback
       return

     component = @components[name]


use spawn instead of buffered process.

use spawn instead of buffered process.

--- lib/cscope.coffee
+++ lib/cscope.coffee
@@ -2,6 +2,7 @@
 ResultSetModel = require './models/result-set-model'
 fs = require 'fs'
 path = require 'path'
+spawn = require('child_process').spawn

 module.exports = CscopeCommands =
   getSourceFiles: (project, exts) ->
@@ -53,17 +54,24 @@

   runCommand: (command, args, options = {}) ->
     process = new Promise (resolve, reject) =>
+      complete = false
       output = ''
-      try
-        new BufferedProcess
-          command: command
-          args: args
-          options: options
-          stdout: (data) -> output += data.toString()
-          stderr: (data) -> reject {success: false, message: "At " + options.cwd + ": " + data.toString()}
-          exit: (code) -> resolve output
-      catch
-        reject "Couldn't find cscope"
+      child = spawn command, args, options
+      child.stdout.on 'data', (data) =>
+        output += data.toString()
+      child.stderr.on 'data', (data) =>
+        if complete then return
+        complete = true
+        reject data.toString()
+
+      child.on 'error', (err) =>
+        if complete then return
+        complete = true
+        reject err.toString()
+      child.on 'close', (code) =>
+        if complete then return
+        complete = true
+        if code != 0 then reject code else resolve output
     return process

   runCscopeCommand: (num, keyword, cwd) ->
@@ -93,4 +101,4 @@
       .catch (data) ->
         reject data

-    return motherSwear+    return motherSwear


Remove requires to prep for consolidation into work-app

--- src/src.coffee
+++ src/src.coffee
@@ -1,5 +1,3 @@
-require 'appirio-tech-ng-ui-components'
-require 'appirio-tech-ng-api-services'
 require './scripts/ap-file-upload.module'

 require './scripts/file.controller'


Made formatJavascript take the active editor's tablength

Before, the global preference tab length was used, which caused
some annoyances when a user would set tab length by using init
scripts or had changed them otherwise separate from the global
config.

--- lib/format.coffee
+++ lib/format.coffee
@@ -42,7 +42,7 @@
   formatJavascript: (editor) ->
     settings = atom.config.getSettings().editor
     opts = {
-      indent_size: settings.tabLength,
+      indent_size: editor.getTabLength(),
       wrap_line_length: settings.preferredLineLength
     }


Code cleanup

Code cleanup

--- lib/format.coffee
+++ lib/format.coffee
@@ -56,20 +56,19 @@
         mainCursor.setBufferPosition(currentCursorPosition)
         currentPosition = currentCursorPosition

-      whitespaceText = textBuffer.getTextInRange([[0, 0], currentPosition])
+      textuntilCursor = textBuffer.getTextInRange([[0, 0], currentPosition])

-      nonWhitespaceCharacters = whitespaceText.match(nonWhitespaceRegex)
-      whitespaceCharacterCount = whitespaceText.match(whitespaceRegex)
+      nonWhitespaceCharacters = textuntilCursor.match(nonWhitespaceRegex)
+      whitespaceCharacters = textuntilCursor.match(whitespaceRegex)

-      whitespaceCharacterCount = if whitespaceCharacterCount then whitespaceCharacterCount.length else 0
       nonWhitespaceCharacters = if nonWhitespaceCharacters then nonWhitespaceCharacters.length else 0
+      whitespaceCharacters = if whitespaceCharacters then whitespaceCharacters.length else 0

       @formatJavascript(editor)

-      nonWhitespaceCount = 0
       text = editor.getText()

-      newCursorPosition = textBuffer.positionForCharacterIndex(nonWhitespaceCharacters + whitespaceCharacterCount);
+      newCursorPosition = textBuffer.positionForCharacterIndex(nonWhitespaceCharacters + whitespaceCharacters);

       mainCursor.setBufferPosition(newCursorPosition)


遅刻時の反応メッセージを追加

遅刻時の反応メッセージを追加

--- scripts/secret.coffee
+++ scripts/secret.coffee
@@ -1,6 +1,6 @@
 module.exports = (robot) ->
   robot.hear /遅刻します|ちこくします|遅刻しそう|ちこくしそう|遅れます|おくれます|おくれそう|遅れそう|まにあわな|間に合わな|間にあわな/, (msg) ->
-    messages = ["は？", "え？", "本気で言ってんの？", "3分間待ってやる"]
+    messages = ["は？", "え？", "本気で言ってんの？", "3分間待ってやる", "ボードゲームの提供、誠にありがとうございます。"]
     index = Math.floor(Math.random() * messages.length)
     message = messages[index]
     msg.send "@#{msg.envelope.user.name} #{message}"


:art: use isRepeat convention

:art: use isRepeat convention

--- lib/motion.coffee
+++ lib/motion.coffee
@@ -482,7 +482,7 @@
   offset: 0

   initialize: ->
-    @focusInput() unless @instanceof('RepeatFind')
+    @focusInput() unless @isRepeated()

   isBackwards: ->
     @backwards
@@ -492,7 +492,7 @@
     {start, end} = @editor.bufferRangeForBufferRow(cursorPoint.row)

     offset   = if @isBackwards() then @offset else -@offset
-    unOffset = -offset * @instanceof('RepeatFind')
+    unOffset = -offset * @isRepeated()
     if @isBackwards()
       scanRange = [start, cursorPoint.translate([0, unOffset])]
       method    = 'backwardsScanInBufferRange'
@@ -511,7 +511,7 @@
   moveCursor: (cursor) ->
     if point = @find(cursor)
       cursor.setBufferPosition(point)
-    unless @instanceof('RepeatFind')
+    unless @isRepeated()
       globalState.currentFind = this

 # keymap: F
@@ -542,6 +542,8 @@

 class RepeatFind extends Find
   @extend()
+  repeated: true
+
   initialize: ->
     unless findObj = globalState.currentFind
       @abort()


add another die command

add another die command

--- plugins/die.coffee
+++ plugins/die.coffee
@@ -9,3 +9,6 @@
   init: (bot, config) ->
     bot.msg ///^#{config.global.prefix}die///i, (nick, channel) ->
       process.exit()
+
+    bot.msg ///^greenman go home$///i, (nick, channel) ->
+      process.exit()


update => updated

update => updated

--- SingularityUI/app/collections/RequestTasks.coffee
+++ SingularityUI/app/collections/RequestTasks.coffee
@@ -10,7 +10,7 @@
         _.each tasks, (task) ->
             task.id = task.taskDetails.id
             task.name = task.id
-            task.updateAtHuman = if task.updateAt? then moment(task.updateAt).from() else ''
+            task.updatedAtHuman = if task.updatedAt? then moment(task.updatedAt).from() else ''
             task.createdAtHuman = if task.createdAt? then moment(task.createdAt).from() else ''
             task.lastStatus = task.lastStatus ? ''


Oops, missed one!

Oops, missed one!

--- lib/object.coffee
+++ lib/object.coffee
@@ -19,7 +19,7 @@
             '(' + object + ')'

         else if object instanceof PDFReference
-            return object.toString()
+            object.toString()

         else if object instanceof Date
             '(D:' + pad(object.getUTCFullYear(), 4) +


modified

modified

--- sedata.coffee
+++ sedata.coffee
@@ -5,6 +5,7 @@
 hist = require("./src/json126")
 qsa = require("./src/htsc")
 qsort = require("./src/sortsina")
+JisiluData = require('./src/JisiluData')

 module.exports =
   forex: sinaforex
@@ -14,3 +15,4 @@
   histdwm: hist
   qsa: qsa
   qsort: qsort
+  JisiluData:JisiluData


修复 markdown 表格链接编译错误

修复 markdown 表格链接编译错误

--- lib/parser.coffee
+++ lib/parser.coffee
@@ -3,9 +3,9 @@
 articleRender = new Renderer()

 articleRender.heading = (text, level) ->
-  (if level < 2 then '
' else '')
-  + "<h#{level}>#{text}</h#{level}>"
-  + (if level < 2 then '
' else '')
+  (if level < 2 then '
' else '') +
+  "<h#{level}>#{text}</h#{level}>" +
+  (if level < 2 then '
' else '')

 articleRender.table = (header, body) ->
   '
\
@@ -16,9 +16,11 @@

'

 articleRender.link = (href, title, text) ->
-  '#{text}"
+  '#{text}"

 summaryRender.heading = -> ''

@@ -31,9 +33,9 @@
     node.title = s.a[0]._
     node.link = s.a[0].href[0].replace(/\.(md|markdown)$/i, '')
   if s.ul
-    if s.ul[0] && s.ul[0].li
+    if s.ul[0]?.li
       list = s.ul[0].li
-    else if s.ul && s.ul.li
+    else if s.ul?.li
       list = s.ul.li
     node.list = list.map((a) -> summaryParser({}, a))
   node


Make the tab embed script SSL-friendly.

Make the tab embed script SSL-friendly.

--- src/main/webapp/coffeescripts-hidden/load.coffee
+++ src/main/webapp/coffeescripts-hidden/load.coffee
@@ -78,7 +78,7 @@

 withJQueryLoaded = (callback) ->
   if ! jQuery?
-    loadScript("http://ajax.googleapis.com/ajax/libs/jquery/" + jqVersion + "/jquery.min.js", callback)
+    loadScript("//ajax.googleapis.com/ajax/libs/jquery/" + jqVersion + "/jquery.min.js", callback)
   else if ! /^1.[678]/.test(jQuery.fn.jquery)
     console?.error("AnchorTab is disabled because you are using an unsupported version of jQuery.")

@@ -103,7 +103,7 @@
   $input.attr('disabled', 'disabled')

   email = $input.val()
-  submissionUri = "http://" + apiDomain + "/api/v1/embed/" + tabId + "/submit"
+  submissionUri = "//" + apiDomain + "/api/v1/embed/" + tabId + "/submit"

   $.ajax
     url: submissionUri
@@ -147,9 +147,13 @@
   # Load the Anchor Tab stylesheet.
   atStyleSheet =
     $("<link />")
-      .attr("href", "http://" + resourcesDomain + "/stylesheets/tab.css")
       .attr("rel", "stylesheet")
       .attr("type", "text/css")
+
+  if document.location.protocol == "https:"
+    atStyleSheet.attr("href", "//" + secureResourcesDomain + "/stylesheets/tab.css")
+  else
+    atStyleSheet.attr("href", "//" + resourcesDomain + "/stylesheets/tab.css")

   $("head").append atStyleSheet

@@ -232,7 +236,7 @@
   # then we'll have to refactor some of this code, I suppose.
   $ = jQuery # In case we're in compability mode.
   tabId = $("#anchortab-loader").data('tab-id')
-  tabJson = "http://" + apiDomain + "/api/v1/embed/" + tabId
+  tabJson = "//" + apiDomain + "/api/v1/embed/" + tabId

   $.ajax
     url: tabJson


added other exts

added other exts

--- server.coffee
+++ server.coffee
@@ -16,7 +16,7 @@

   # This is for debugging purposes, it allows you to view the raw files as plain text.
   app.use (request, response, next) ->
-    if /\.(coffee|jade|sass)$/.test request.url
+    if /\.(coffee|jade|sass|scss|styl|less)$/.test request.url
       response.set 'Content-Type', 'text/plain'

     next()


Keep state

Keep state

--- server.coffee
+++ server.coffee
@@ -32,17 +32,20 @@
   console.log('socket_id '+ socket_id)
   console.log(data)

+shapes = {}
+
 pipe.channel('game1').on 'event:created', (socketID, data) ->
+  shapes[data.id] = {id: data.id, x:data.x, y:data.y, rotation: data.rotation, color: data.color, fixed: true}
   pipe.channel('game1').trigger('created', data)

 pipe.channel('game1').on 'event:moved', (socketID, data) ->
+  shapes[data.id].x = data.x
+  shapes[data.id].y = data.y
+  shapes[data.id].rotation = data.rotation
   pipe.channel('game1').trigger('moved', data)

+pipe.sockets.on 'open', (socket_id) ->
+  pipe.channel('game1').trigger('start', shapes)
+
 # when a new person connects ->
-  # send current state
-
-# created ->
-  # update current state
-
-# move ->
-  # update current state+  # send current state


Some people can only use 3 letter tools

Some people can only use 3 letter tools

--- lib/routes.coffee
+++ lib/routes.coffee
@@ -9,6 +9,9 @@
 ## Application routes
 app.get '/dickbutt', (request, response) ->
   response.redirect('/static/db.jpg')
+
+app.get '/dkb', (request, response) ->
+  response.redirect('/dickbutt')

 app.get '/', (request, response) ->
   Comic.latest (err, comic) =>


consistent whitespace

consistent whitespace

--- lib/scroll.coffee
+++ lib/scroll.coffee
@@ -23,7 +23,6 @@
     lastScreenRow = @editorView.getLastVisibleScreenRow() - @scrolloff
     @editorView.scrollToBufferPosition([lastScreenRow + count, 0])

-
 class ScrollUp extends Scroll
   constructor: (@editorView, @editor, @scrolloff) ->
     super(@editorView, @editor)
@@ -42,5 +41,4 @@
     firstScreenRow = @editorView.getFirstVisibleScreenRow() + @scrolloff
     @editorView.scrollToBufferPosition([firstScreenRow - count, 0])

-
 module.exports = { ScrollDown, ScrollUp }


Fix for #1304 ... but disallows an alternative syntax.

--- rewriter.coffee
+++ rewriter.coffee
@@ -103,7 +103,7 @@
       (tag in ['TERMINATOR', 'OUTDENT'] and
         not (two?[0] is ':' or one?[0] is '@' and three?[0] is ':')) or
         (tag is ',' and one and
-          one[0] not in ['IDENTIFIER', 'NUMBER', 'STRING', '@', 'TERMINATOR', 'OUTDENT'])
+          one[0] not in ['IDENTIFIER', 'NUMBER', 'STRING', '@', 'TERMINATOR', 'OUTDENT', '->', '=>'])
     action = (token, i) ->
       tok = ['}', '}', token[2]]
       tok.generated = yes


Fixing issue #694. Destructuring assignment as first line of implicitly called block -- regression.

--- rewriter.coffee
+++ rewriter.coffee
@@ -173,8 +173,9 @@
         condition = (token, i) ->
           return yes if not seenSingle and token.fromThen
           seenSingle = yes if token[0] in ['IF', 'ELSE', 'UNLESS', '->', '=>']
+          post = @tokens[i + 1]
           (not token.generated and @tokens[i - 1][0] isnt ',' and include(IMPLICIT_END, token[0]) and
-            not (token[0] is 'INDENT' and (include(IMPLICIT_BLOCK, @tag(i - 1)) or @tag(i - 2) is 'CLASS' or @tag(i + 1) is '{'))) or
+            not (token[0] is 'INDENT' and (include(IMPLICIT_BLOCK, @tag(i - 1)) or @tag(i - 2) is 'CLASS' or (post and post.generated and post[0] is '{')))) or
             token[0] is 'PROPERTY_ACCESS' and @tag(i - 1) is 'OUTDENT'
         action = (token, i) ->
           idx = if token[0] is 'OUTDENT' then i + 1 else i


Revert "Add "select-with-keyboard" functionality to route-details-typeahead. Improve usability. Closes #215."

This reverts commit c83e3a0b37a21f5e5ec824f7684140cb45c5c375.

--- src/views/route-settings.coffee
+++ src/views/route-settings.coffee
@@ -83,7 +83,6 @@
             event.preventDefault()
             $('#route-details').toggleClass('settings-open')
             $('.bootstrap-datetimepicker-widget').hide()
-            $('#route-details').trigger "shown"

     class TransportModeControlsView extends base.SMItemView
         template: 'transport-mode-controls'
@@ -233,9 +232,6 @@
             options.templates.empty = (ctx) -> jade.template 'typeahead-no-results', ctx
             @$searchEl.typeahead null, [options]

-            @$searchEl.on 'keyup', (e) =>
-                $('.tt-suggestion:first-child').trigger('click') if e.keyCode is 13
-
             selectAddress = (event, match) =>
                 @commit = true
                 switch $(event.currentTarget).attr 'data-endpoint'
@@ -250,9 +246,8 @@
                 $inputEl: @$searchEl
                 selectionCallback: selectAddress

-            # Focus on search-elem when #route-details has been opened
-            $('#route-details').on "shown", =>
-                @$searchEl.attr('tabindex', -1).focus()
+            # # TODO figure out why focus doesn't work
+            @$searchEl.focus()

         _locationNameAndLocking: (object) ->
             name: @model.getEndpointName object


periodically pull rss feeds

periodically pull rss feeds

--- server/startup.coffee
+++ server/startup.coffee
@@ -128,36 +128,34 @@
         startDate: moment().subtract(2, 'days').toDate()
         endDate: new Date()
       )
+
+      feeds = Feeds.find(rss: true).fetch()
+      for feed in feeds
+        console.log "Reading feed: " + feed.url
+        response = HTTP.get(feed.url)
+        $xml = cheerio.load(response.content, xml: xml: true)
+        rssItems = Array.from($xml('item').map (idx)->
+          $item = $xml('item').eq(idx)
+          {
+            title: $item.find('title').text()
+            link: $item.find('link').text()
+            pubDate: $item.find('pubDate').text()
+          }
+        )
+        rssItems.forEach (item) ->
+          if not Articles.findOne(url: item.link)
+            # Normalize post for display/subscription
+            normalizedPost =
+              url: item.link
+              addedDate: new Date()
+              publishDate: new Date(item.pubDate)
+              publishDateTZ: "UTC"
+              title: item.title
+              reviewed: false
+              feedId: feed._id
+            console.log "Adding post: " + item.link
+            Articles.insert(normalizedPost)
     , 5 * 60 * 60 * 1000
-
-
-    #for feed in Feeds.findOne(promedId: post.feedId)?._id
-    feeds = Feeds.find(rss: true).fetch()
-    for feed in feeds
-      console.log "Reading feed: " + feed.url
-      response = HTTP.get(feed.url)
-      $xml = cheerio.load(response.content, xml: xml: true)
-      rssItems = Array.from($xml('item').map (idx)->
-        $item = $xml('item').eq(idx)
-        {
-          title: $item.find('title').text()
-          link: $item.find('link').text()
-          pubDate: $item.find('pubDate').text()
-        }
-      )
-      rssItems.forEach (item) ->
-        if not Articles.findOne(url: item.link)
-          # Normalize post for display/subscription
-          normalizedPost =
-            url: item.link
-            addedDate: new Date()
-            publishDate: new Date(item.pubDate)
-            publishDateTZ: "UTC"
-            title: item.title
-            reviewed: false
-            feedId: feed._id
-          console.log "Adding post: " + item.link
-          Articles.insert(normalizedPost)

     console.log "Syncing structured data feeds"
     syncStructuredFeeds()


Default generator adds jquery dependency

--- src/steroids/generators/resources/Default.coffee
+++ src/steroids/generators/resources/Default.coffee
@@ -56,6 +56,8 @@

     @addFile path.join("app", "views", "#{@options.name}", "index.html"), "index.html.template"
     @addFile path.join("app", "views", "#{@options.name}", "show.html"), "show.html.template"
+
+    @addBowerDependency "jquery", "1.9.1"

     util.log "Command completed successfully."


改进DropDown的定位算法

改进DropDown的定位算法

--- src/widget/edit/dropdown.coffee
+++ src/widget/edit/dropdown.coffee
@@ -366,17 +366,18 @@
 		clientWidth = document.body.offsetWidth
 		clientHeight = document.body.clientHeight
 		bottomSpace = clientHeight - rect.top - dropdownDom.clientHeight
-		height=0
+
 		if bottomSpace >= boxHeight
 			direction = "down"
 		else
 			topSpace = rect.top
 			if topSpace > bottomSpace
 				direction = "up"
-#				height = topSpace
+				if boxHeight > topSpace then height = topSpace
 			else
 				direction = "down"
-#				height = bottomSpace
+				if boxHeight > bottomSpace then height = bottomSpace
+
 		if direction == "down"
 			top = rect.top + dropdownDom.clientHeight
 		else
@@ -390,11 +391,12 @@

 		if height then $dom.css("height", height)
 		$dom
-		.removeClass(if direction == "down" then "direction-up" else "direction-down").addClass("direction-" + direction)
-		.toggleClass("x-over", boxWidth > dropdownDom.offsetWidth)
-		.css("left", left).css("top", top)
-		.css("min-width", dropdownDom.offsetWidth)
-		.css("max-width",document.body.clientWidth).css("min-height",boxHeight)
+			.removeClass(if direction == "down" then "direction-up" else "direction-down").addClass("direction-" + direction)
+			.toggleClass("x-over", boxWidth > dropdownDom.offsetWidth)
+			.css("left", left).css("top", top)
+			.css("min-width", dropdownDom.offsetWidth)
+			.css("max-width",document.body.clientWidth)
+
 		$dom.css({
 			zIndex: cola.floatWidget.zIndex()
 		})


fix incorrect path for contentful config require

--- root/app.coffee
+++ root/app.coffee
@@ -2,7 +2,7 @@
 rupture      = require 'rupture'
 autoprefixer = require 'autoprefixer-stylus'
 contentful   = require 'roots-contentful'
-config       = require 'contentful_config'
+config       = require './contentful'

 module.exports =
   ignores: ['readme.md', '**/layout.*', '**/_*', '.gitignore', 'contentful.coffee']


fixed mirror axes

fixed mirror axes

--- src/viz/types/helpers/graph/includes/axes.coffee
+++ src/viz/types/helpers/graph/includes/axes.coffee
@@ -41,9 +41,6 @@
       # calculate scale
       vars[axis].scale.viz = getScale vars, axis, range

-      # Add buffer to scale if it needs it
-      buffer vars, axis, opts.buffer if opts.buffer and axis isnt vars.axes.discrete
-
       # store axis domain
       vars[axis].domain.viz = range

@@ -51,9 +48,17 @@

   # Mirror axes, if applicable
   if vars.axes.mirror.value
-    domains = vars.y.domain.viz.concat(vars.x.domain.viz)
-    vars.x.domain.viz = d3.extent(domains)
-    vars.y.domain.viz = d3.extent(domains).reverse()
+    domains = d3.extent vars.y.domain.viz.concat(vars.x.domain.viz)
+    vars.x.domain.viz = domains
+    vars.x.scale.viz.domain(domains)
+    domains = domains.slice().reverse()
+    vars.y.domain.viz = domains
+    vars.y.scale.viz.domain(domains)
+
+  # Add buffer to scale if it needs it
+  if opts.buffer
+    for axis in axes
+      buffer vars, axis, opts.buffer if axis isnt vars.axes.discrete

   return


Fixed: modal error was not properly initialized

--- client/modal.coffee
+++ client/modal.coffee
@@ -75,7 +75,11 @@
     new Modal {title, content, yes: yesMsg, no:noMsg, cb}

 Modal.error = (text, cb) ->
-    new ModalView t("modal error"), text, t("modal ok"), false, cb
-
+    new Modal
+        title: t 'modal error'
+        content: text
+        yes: t 'modal ok'
+        no: false
+        cb: cb

 module.exports = Modal


Add fittext to asset pipeline

Add fittext to asset pipeline

--- docs/source/javascripts/application.js.coffee
+++ docs/source/javascripts/application.js.coffee
@@ -1 +1,3 @@
-#= require "vendor/jquery-1.7.1.min"+#= require "vendor/jquery-1.7.1.min"
+
+#= require "vendor/plugins/jquery.fittext.js"


debounce actual search instead?

debounce actual search instead?

--- resources/assets/coffee/react/beatmaps/main.coffee
+++ resources/assets/coffee/react/beatmaps/main.coffee
@@ -52,6 +52,8 @@
   constructor: (props) ->
     super props

+    @debouncedSearch = _.debounce @search, 500
+
     prevState = JSON.parse(props.container.dataset.reactState ? '{}')

     @state = prevState.state unless _.isEmpty(prevState)
@@ -90,7 +92,7 @@

   componentDidMount: =>
     $(document).on 'beatmap:load_more.beatmaps', @loadMore
-    $(document).on 'beatmap:search:start.beatmaps', @search
+    $(document).on 'beatmap:search:start.beatmaps', @debouncedSearch
     $(document).on 'beatmap:search:filtered.beatmaps', @updateFilters
     $(document).on 'turbolinks:before-cache.beatmaps', @saveState
     $(window).on 'resize.beatmaps', @updateColumnCount
@@ -100,13 +102,12 @@
     return if _.isEqual(prevState.filters, @state.filters)

     $(document).trigger 'beatmap:search:start'
-    url = encodeURI laroute.route('beatmapsets.index', BeatmapsetFilter.queryParamsFromFilters(@state.filters))
-    Turbolinks.controller.pushHistoryWithLocationAndRestorationIdentifier url, Turbolinks.uuid()

   componentWillUnmount: =>
     $(document).off '.beatmaps'
     $(window).off '.beatmaps'
+    @debouncedSearch.cancel
     @xhr?.abort()

@@ -250,6 +251,9 @@
   search: =>
     @xhr?.abort()

+    url = encodeURI laroute.route('beatmapsets.index', BeatmapsetFilter.queryParamsFromFilters(@state.filters))
+    Turbolinks.controller.pushHistoryWithLocationAndRestorationIdentifier url, Turbolinks.uuid()
+
     return Promise.resolve() if @isSupporterMissing()

     @setState loading: true


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/function_extensions.coffee
+++ src/function_extensions.coffee
@@ -1,4 +1,4 @@
-Function::once = (function)- >
+Function::once = (function)->
   _.once = function(func) {
     var ran = false, memo;
     return function() {


Fix some js stuff

Fix some js stuff

--- sli/databrowser/app/assets/javascripts/entities.js.coffee
+++ sli/databrowser/app/assets/javascripts/entities.js.coffee
@@ -3,11 +3,10 @@
 # You can use CoffeeScript in this file: http://jashkenas.github.com/coffee-script/
 table = null
 jQuery ->
-  if (!$.fn.dataTable().fnTables(true))
-    table = $('#simple-table').dataTable(
-      "bLengthChange": false,
-      "bRetrieve": true,
-      "bPaginate": false)
+  table = $('#simple-table').dataTable(
+    "bLengthChange": false,
+    "bRetrieve": true,
+    "bPaginate": false)
   $('#simple-table tbody tr').click ->
     details = $(@)
     console.log details


Commented out IP intelligence since I think it's rate limited...

--- src/scripts/search-virus-total.coffee
+++ src/scripts/search-virus-total.coffee
@@ -65,27 +65,31 @@
           msg.send "VirusTotal URL Analysis: #{vt_json.verbose_msg}"

-  robot.respond /virustotal ip (.*)/i, (msg) ->
-    ip = msg.match[1].toLowerCase()
+  #robot.respond /virustotal ip (.*)/i, (msg) ->
+    #ip = msg.match[1].toLowerCase()

-    robot.http(vt_ip_report_url)
-      .query("apikey": VIRUS_TOTAL_API_KEY, "ip": ip)
-      .get() (err, res, body) ->
+    #robot.http(vt_ip_report_url)
+      #.query("apikey": VIRUS_TOTAL_API_KEY, "ip": ip)
+      #.get() (err, res, body) ->
         #vt_json = JSON.parse(body)
-        msg.send body

         #if vt_json.response_code == 1

-          # summary = """ VirusTotal IP Result: #{vt_json.resource}
+          #console.log(vt_json.resolutions)
+          # for resolution in vt_json.resolutions
+          #   console.log("#{resolution}")
+
+          #console.log(vt_json.detected_urls)
+          # for detected_url in vt_json.detected_urls
+          #   console.log("#{detected_url}")
+
+          # summary = """ VirusTotal IP Result: #{ip}
           # - Scanned at: #{vt_json.scan_date}
           # - Results:    #{vt_json.positives}/#{vt_json.total}
           # - Link:       #{vt_json.permalink}
           # """

-          # for resolution of vt_json.resolutions
-          #   console.log("- #{resolution.last_resolved} : #{resolution.hostname}")
-
-        #vt_json
+          #msg.send summary

         #else
           #msg.send vt_json.verbose_msg


enhance doc.image(src) to support both src=binary data and src=filename


Nodejs Web Server Tests: remove unused require statements

--- servers/lib/server/handlers/createteam.test.coffee
+++ servers/lib/server/handlers/createteam.test.coffee
@@ -4,11 +4,9 @@

 { hostname }                        = KONFIG
 { Relationship }                    = require 'jraphical'
-{ hat
-  daisy
+{ daisy
   expect
   request
-  querystring
   convertToArray
   generateRandomEmail
   generateRandomString
@@ -16,9 +14,7 @@
   generateRandomUsername }          = require '../../../testhelper'
 { testCsrfToken }                   = require '../../../testhelper/handler'
 { generateRegisterRequestParams }   = require '../../../testhelper/handler/registerhelper'
-
-{ generateCreateTeamRequestBody
-  generateCreateTeamRequestParams } = require '../../../testhelper/handler/teamhelper'
+{ generateCreateTeamRequestParams } = require '../../../testhelper/handler/teamhelper'

 reservedTeamDomains = require '../../../../workers/social/lib/social/models/user/reservedteamdomains'


changed getprofilehandlerhelper require path

--- servers/lib/server/handlers/getprofile.test.coffee
+++ servers/lib/server/handlers/getprofile.test.coffee
@@ -12,8 +12,8 @@
   generateRandomEmail
   generateRandomString } = require '../../../testhelper'

-{ generateGetProfileRequestParams
-  generateRegisterRequestParams } = RegisterHandlerHelper
+{ generateRegisterRequestParams }   = RegisterHandlerHelper
+{ generateGetProfileRequestParams } = require '../../../testhelper/getprofilehandlerhelper'

 # begin tests
 describe 'server.handlers.getprofile', ->


Preserve whitespace in syntax quote to expand macros with indentation

--- src/js/app/compilers/teascript/compiler.coffee
+++ src/js/app/compilers/teascript/compiler.coffee
@@ -2520,7 +2520,7 @@
     jsNoop()

 ms['`'] = ms_quote = (ctx, call) ->
-  expression = firstOrCall _arguments call
+  expression = firstOrCall call
   if (_arguments call).length > 1
     labelDelimeters call, 'const'

@@ -2588,9 +2588,11 @@
   else
     (jsCall 'constantToSource', [expressionCompile ctx, expression])

-firstOrCall = (args) ->
+firstOrCall = (expression) ->
+  args = _arguments expression
   if args.length > 1
-    call_ args[0], args[1...]
+    opIndex = expression.indexOf args[0]
+    call_ expression[opIndex], expression[opIndex + 1...-1]
   else
     args[0]

@@ -5742,9 +5744,14 @@

 expandCall = (moduleName, call) ->
   {modules, ctx} = contextWithDependencies moduleDependencies moduleName
-  expanded = callMacroExpand ctx, call
-  if not isTranslated expanded
-    expanded
+  try
+    expanded = callMacroExpand ctx, call
+    if not isTranslated expanded
+      expanded
+  catch e
+    console.error "Error in expandCall"
+    console.error e
+    null

 importsFor = (moduleSet) ->
   lookupDefinitions = (name) ->


Fixed order of compiling wheres

Fixed order of compiling wheres

--- src/js/app/compilers/teascript/compiler.coffee
+++ src/js/app/compilers/teascript/compiler.coffee
@@ -2349,7 +2349,7 @@
       jsNoop()

 findDefinitionsIncludingDeps = (ctx, names) ->
-  findDefinitions ctx, setToArray (findDeps ctx) names
+  findDefinitions ctx, reverse setToArray (findDeps ctx) names

 findDefinitions = (ctx, names) ->
   auxiliaries = ctx.auxiliaries()


Renamed List to Array and added Immutable.Stack as List

--- src/js/app/compilers/teascript/compiler.coffee
+++ src/js/app/compilers/teascript/compiler.coffee
@@ -780,7 +780,7 @@
     elemType = ctx.freshTypeVariable star
     # TODO use (Seq c e) instead of (Array e)
     form.tea = new Constrained (concatMap _constraints, (tea for {tea} in elems)),
-      new TypeApp listType, elemType
+      new TypeApp arrayType, elemType

     for elem in elems
       unify ctx, elem.tea.type,
@@ -799,8 +799,8 @@
     # expressionCompile ctx, arrayToConses elems
     # result =>         "[#{listOf map _compiled, elems}]"

-    compiledItems = uniformCollectionCompile ctx, form, elems, listType
-    assignCompile ctx, form, (irList compiledItems)
+    compiledItems = uniformCollectionCompile ctx, form, elems, arrayType
+    assignCompile ctx, form, (irArray compiledItems)

 isSplat = (expression) ->
   (isAtom expression) and (_symbol expression)[...2] is '..'
@@ -1612,7 +1612,7 @@
     macroName = ctx.definitionName()
     if ctx.macros()[macroName]
       return malformed call, "Macro with this name already defined"
-    if not isTypeAnnotation type
+    if not type or not isTypeAnnotation type
       return malformed call, "Type annotation required"

     # Register type
@@ -1620,7 +1620,11 @@
     paramNames = map _symbol, params
     ctx.declare macroName,
       arity: paramNames
-      type: quantifyUnbound ctx, typeConstrainedCompile ctx, type
+      type: type = quantifyUnbound ctx, typeConstrainedCompile ctx, type
+    call.tea = type
+
+    if not rest.length > 0
+      return malformed call, "Macro body missing"

     #macroFn = transform call
     compiledMacro = translateToJs translateIr ctx,
@@ -1658,7 +1662,7 @@
     assignCompile ctx, call, (jsBinary "===", compiledA, compiledB)

 ms.Set = ms_Set = (ctx, call) ->
-  if hashset = ctx.assignTo()
+  if ctx.assignTo()
     # TODO:
     throw new Error "matching on sets not supported yet"
   else
@@ -1666,10 +1670,19 @@
     compiledItems = uniformCollectionCompile ctx, call, items, hashsetType
     assignCompile ctx, call, (irSet compiledItems)

+ms.List = ms_List = (ctx, call) ->
+  if ctx.assignTo()
+    # TODO:
+    throw new Error "matching on lists not supported yet"
+  else
+    items = _arguments call
+    compiledItems = uniformCollectionCompile ctx, call, items, listType
+    assignCompile ctx, call, (irList compiledItems)
+
 ms.Map = ms_Map = (ctx, call) ->
-  if hashset = ctx.assignTo()
+  if ctx.assignTo()
     # TODO:
-    throw new Error "matching on sets not supported yet"
+    throw new Error "matching on maps not supported yet"
   else
     args = _arguments call
     if args.length % 2 != 0
@@ -2215,12 +2228,18 @@
       body: translateIr ctx, body)])

+irArray = (items) ->
+  {ir: irArrayTranslate, items}
+
+irArrayTranslate = (ctx, {items}) ->
+  (jsCall "Immutable.List.of", (translateIr ctx, items))
+
+
 irList = (items) ->
   {ir: irListTranslate, items}

 irListTranslate = (ctx, {items}) ->
-  (jsCall "Immutable.List.of", (translateIr ctx, items))
-
+  (jsCall "Immutable.Stack.of", (translateIr ctx, items))

 irMap = (keys, elems) ->
@@ -2252,7 +2271,7 @@
 isCustomCollectionType = ({type}) ->
   helpContext = new Context
   newVar = -> helpContext.freshTypeVariable star
-  (toMatchTypes (applyKindFn listType, newVar()), type) or
+  (toMatchTypes (applyKindFn arrayType, newVar()), type) or
     (toMatchTypes (applyKindFn hashmapType, newVar(), newVar()), type) or
     (toMatchTypes (applyKindFn hashsetType, newVar()), type)

@@ -3015,7 +3034,7 @@
 builtInTypeNames = ->
   arrayToMap map (({name, kind}) -> [name, kind]), [
     arrowType
-    listType
+    arrayType
     hashmapType
     hashsetType
     stringType
@@ -3624,6 +3643,7 @@

 star = '*'
 arrowType = new TypeConstr 'Fn', kindFn 2
+arrayType = new TypeConstr 'Array', kindFn 1
 listType = new TypeConstr 'List', kindFn 1
 hashmapType = new TypeConstr 'Map', kindFn 2
 hashsetType = new TypeConstr 'Set', kindFn 1
@@ -4208,7 +4228,7 @@
   'seq splice in match'
   """
     & (macro [what to]
-      (: (Fn a (List a) (List a)))
+      (: (Fn a (Array a) (Array a)))
       (Js.call (Js.access to "unshift") {what}))

     map (fn [what to]
@@ -4513,10 +4533,10 @@
         (# Whether the bag contains no elements.)))

     list-elem? (macro [what in]
-      (: (Fn item (List item) Bool))
+      (: (Fn item (Array item) Bool))
       (Js.call (Js.access in "contains") {what}))

-    collection-list (instance (Collection List)
+    collection-list (instance (Collection Array)
       elem? (fn [what in]
         (list-elem? what in)))
   """
@@ -4529,10 +4549,10 @@
         (: (Fn (ce e) e))))

     list-first (macro [in]
-      (: (Fn (List item) item))
+      (: (Fn (Array item) item))
       (Js.call (Js.access in "first") {}))

-    list-collection (instance (Collection List a)
+    list-collection (instance (Collection Array a)
       first (fn [in]
         (list-first in)))
   """
@@ -4546,10 +4566,10 @@
         (: (Fn ce e))))

     list-first (macro [in]
-      (: (Fn (List item) item))
+      (: (Fn (Array item) item))
       (Js.call (Js.access in "first") {}))

-    list-collection (instance (Collection (List a) a)
+    list-collection (instance (Collection (Array a) a)
       first (fn [in]
         (list-first in)))
   """


fix(tests): use regex matcher instead of hard-coded result (#260)

* WIP(tests): fix tests

* fix(tests): use regex matcher instead of hard-coded result

--- src/spec/integration/gzip.spec.coffee
+++ src/spec/integration/gzip.spec.coffee
@@ -43,9 +43,9 @@
       gzipReqHeader = results.http.request.headers['accept-encoding']
       gzipResHeader = results.http.response.headers['content-encoding']
       expect(gzipReqHeader).toBeTruthy()
-      expect(gzipReqHeader).toBe 'gzip'
+      expect(gzipReqHeader).toMatch /gzip/
       expect(gzipResHeader).toBeTruthy()
-      expect(gzipResHeader).toBe 'gzip'
+      expect(gzipResHeader).toMatch /gzip/
       done()
     .catch (error) -> done(_.prettify(error))
   , 20000 # 20 sec


Master: Return immediately if there are no group sources to hand off

In `sendHandoffData`, we weren't properly signalling if we had no group
sources to transfer. This would cause our original process to never exit.

--- src/streammachine/master/index.coffee
+++ src/streammachine/master/index.coffee
@@ -313,6 +313,12 @@

             sg = @stream_groups[ msg.key ]

+            if sg._stream.sources.length == 0
+                # no sources to send... return right away
+                fFunc()
+                return cb null
+
+            # if we have sources, count up to make sure we return them all
             af = _.after sg._stream.sources.length, =>
                 fFunc()
                 cb null


fix error when key that doesn't exist

fix error when key that doesn't exist

Former-commit-id: d85e8cb9506623c33c3bdd2826953ff131920978 [formerly 1a41ebc289ef10429bce64e6c4abc38ac926ea13] [formerly 8066cc0a88fea0887e45b342ace744d2a9d25523 [formerly 9cf95eb78ac0449b5e890ce7b44a47ea3283be82]] [formerly bd13b92056f195f7f2a4a8d15e9439f4e256ebda [formerly 667b2def2b39cf0231ade14c38df8f74d09f6fee] [formerly 53a4faf60e5a93381186da589dfb92ba57919cd9 [formerly 53a4faf60e5a93381186da589dfb92ba57919cd9 [formerly ed08e346f9cc750d4b065754a1912c819e817f3c]]]]
Former-commit-id: 32c33d201a7de2b29c7b848cf4494731482a39bb [formerly 2400bc4a84174e6fbdae1524e04338c623978a75] [formerly 373e2f08a8fa7b62e50a83b9d975678d2dde3189 [formerly ffed36791a1f81a69d1ca7b863dd90a8edb21238]]
Former-commit-id: 5be12dcd6a348decbdc0f92d007dedc489321f75 [formerly ed8089834c7009ec1fcc45d478e2e2c88b90f7da]
Former-commit-id: 9c80e482b7041fe3dbf55978a17994c950f33a1e
Former-commit-id: 8c47df5c262cf6e2726b8faaaa4f45528e85353b

--- @client/activerest.coffee
+++ @client/activerest.coffee
@@ -211,9 +211,11 @@
       # Add getters / setters
       # Need to do this every access in case new properties were added
       # to this key since the last access
-      for prop in _.keys ActiveREST.fetch(key)
-        if !getters_setters.hasOwnProperty prop
-          @_addGetterSetter getters_setters, prop, key, @
+      activeREST_object = ActiveREST.fetch(key)
+      if activeREST_object
+        for prop in _.keys activeREST_object
+          if !getters_setters.hasOwnProperty prop
+            @_addGetterSetter getters_setters, prop, key, @

       getters_setters


Fix help (Again)

Fix help (Again)

--- modules/help.coffee
+++ modules/help.coffee
@@ -36,7 +36,7 @@
         text: 'Made by TheBITLINK#3141'
     }

-  filtersCommandFunction: (msg,args)=>
+  filtersCommandFunction: (msg,args,d)=>
     pfx = d.data.prefix or @prefix
     msg.reply """
     To learn more about audio filters, check this link: \


adding quad trees for collisions

adding quad trees for collisions

--- src/quad_tree.coffee
+++ src/quad_tree.coffee
@@ -66,6 +66,13 @@
       return index

     self =
+      clear: ->
+        I.children.clear()
+
+        I.nodes.each (node) ->
+          node.clear()
+
+        I.nodes.clear()

       insert: (item) ->
         index = findIndex(item)
@@ -90,8 +97,48 @@

         return I.children

+      subdivide: ->
+        depth = I.depth + 1
+
+        bx = I.bounds.x
+        by = I.bounds.y
+
+        b_w_h = (I.bounds.width / 2).floor()
+        b_h_h = (I.bounds.height / 2).floor()
+        bx_b_w_h = bx + b_w_h
+        by_b_h_h = by + b_h_h
+
+        I.nodes[TOP_LEFT] = Node
+          bounds:
+            x: bx
+            y: by
+            width: b_w_h
+            height: b_h_h
+          depth: depth

-
+        I.nodes[TOP_RIGHT] = Node
+          bounds:
+            x: bx_b_w_h
+            y: by
+            width: b_w_h
+            height: b_h_h
+          depth: depth
+
+        I.nodes[BOTTOM_LEFT] = Node
+          bounds:
+            x: bx
+            y: by_b_h_h
+            width: b_w_h
+            height: b_h_h
+          depth: depth
+
+        I.nodes[BOTTOM_RIGHT] = Node
+          bounds:
+            x: bx_b_w_h
+            y: by_b_h_h
+            width: b_w_h
+            height: b_h_h
+          depth: depth

     self


Use jQuery instead of $.

Use jQuery instead of $.

--- src/rehydrate.coffee
+++ src/rehydrate.coffee
@@ -35,14 +35,14 @@
   controlClass = Control.getClass className
   # Extract properties. Compound properties ( those defined in children )
   # will get removed from the control content at this point.
-  lowerCaseProperties = $.extend(
+  lowerCaseProperties = jQuery.extend(
     {},
     getPropertiesFromAttributes( element ),
     getCompoundPropertiesFromChildren( element )
   )
   properties = restorePropertyCase controlClass, lowerCaseProperties
   # Create the control.
-  $( element ).control controlClass, properties
+  jQuery( element ).control controlClass, properties

 ###
@@ -59,7 +59,7 @@
   # Remove any data properties we've processed.
   # Removing during the loop above would complicate things, since
   # we also want to process the properties in the order they're defined.
-  $element = $ element
+  $element = jQuery element
   for key of properties
     $element.removeAttr "data-" + key
   properties
@@ -70,12 +70,12 @@
 ###
 getCompoundPropertiesFromChildren = ( element ) ->
   properties = {}
-  $compoundElements = Control( element ).children().filter( "[data-property]" )
+  $compoundElements = Control( element ).children().filter "[data-property]"
   for $compound in $compoundElements.each()
     propertyName = $compound.attr "data-property"
     if propertyName isnt "control" # data-control means a control, not a property.
       $value = $compound.content()
-      properties[propertyName] = $value
+      properties[ propertyName ] = $value
       # Detach before removing so we preserve subcontrols.
       $value.detach() if $value instanceof jQuery
   $compoundElements.remove()
@@ -88,14 +88,14 @@
 found in the control class are dropped.
 ###
 restorePropertyCase = ( controlClass, properties ) ->
-  return properties  if $.isEmptyObject( properties )
-  map = classPropertyNameMap( controlClass )
+  return properties if jQuery.isEmptyObject properties
+  map = classPropertyNameMap controlClass
   result = {}
   for propertyName of properties
-    mixedCaseName = map[propertyName.toLowerCase()]
-    result[mixedCaseName] = properties[propertyName]  if mixedCaseName
+    mixedCaseName = map[ propertyName.toLowerCase() ]
+    result[ mixedCaseName ] = properties[ propertyName ] if mixedCaseName
   result
-
+

 ###
 Cached maps for property names in rehydrated control classes. See below.
@@ -109,14 +109,14 @@
 ###
 classPropertyNameMap = ( controlClass ) ->
   className = controlClass.className
-  unless propertyNameMaps[className]
+  unless propertyNameMaps[ className ]
     map = {}
     # Use the names on the control class' prototype for reference.
     for mixedCaseName of controlClass::
       lowerCaseName = mixedCaseName.toLowerCase()
-      map[lowerCaseName] = mixedCaseName
-    propertyNameMaps[className] = map
-  propertyNameMaps[className]
+      map[ lowerCaseName ] = mixedCaseName
+    propertyNameMaps[ className ] = map
+  propertyNameMaps[ className ]

 ###
@@ -125,5 +125,5 @@
 page automatically rehydrated on load.
 ###
 jQuery ->
-  $body = Control( "body" )
-  $body.rehydrate() if $body.data( "create-controls" )+  $body = Control "body"
+  $body.rehydrate() if $body.data "create-controls"


fix translation test

fix translation test

--- test/xlform/translations.tests.coffee
+++ test/xlform/translations.tests.coffee
@@ -130,5 +130,5 @@
         translations: ["L2", null]
       )
     # run()
-    expect(run).toThrow('translations need to be reordered')
+    expect(run).toThrow('There is an unnamed translation in your form definition')


Move suggestion highlighting into its own method

--- lib/suggestion-list-element.coffee
+++ lib/suggestion-list-element.coffee
@@ -132,35 +132,7 @@
       li.appendChild(wordSpan)
       wordSpan.className = 'word'

-    replacement = text
-    snippetCompletions = []
-    if _.isString(snippet)
-      replacement = snippet.replace @snippetRegex, (match, snippetText) =>
-        snippetCompletions.push "#{snippetText}"
-        @snippetMarkerChar
-
-    # highlight the prefix
-    displayHtml = ''
-    wordIndex = 0
-    lastWordIndex = 0
-    for ch, i in replacementPrefix
-      while wordIndex < replacement.length and replacement[wordIndex].toLowerCase() isnt ch.toLowerCase()
-        wordIndex += 1
-
-      break if wordIndex >= replacement.length
-      preChar = replacement.substring(lastWordIndex, wordIndex)
-      highlightedChar = "#{replacement[wordIndex]}"
-      displayHtml = "#{displayHtml}#{preChar}#{highlightedChar}"
-      wordIndex += 1
-      lastWordIndex = wordIndex
-    displayHtml += replacement.substring(lastWordIndex)
-
-    if snippetCompletions.length
-      completionIndex = 0
-      displayHtml = displayHtml.replace @snippetMarkerRegex, (match, snippetText) ->
-        snippetCompletions[completionIndex++]
-
-    wordSpan.innerHTML = displayHtml
+    wordSpan.innerHTML = @getHighlightedHTML(text, snippet, replacementPrefix)

     labelSpan = li.childNodes[1]
     hasRightLabel = rightLabel or rightLabelHTML
@@ -177,6 +149,50 @@
     else
       labelSpan?.remove()

+  getHighlightedHTML: (text, snippet, replacementPrefix) ->
+    # 1. Pull the snippets out, replacing with placeholder
+    # 2. Highlight relevant characters
+    # 3. Place snippet HTML back at the placeholders
+
+    # Pull out snippet
+    # e.g. replacementPrefix: 'a', snippet: 'abc(${d}, ${e})f'
+    # ->   replacement: 'abc(|, |)f'
+    replacement = text
+    snippetCompletions = []
+    if _.isString(snippet)
+      replacement = snippet.replace @snippetRegex, (match, snippetText) =>
+        snippetCompletions.push "#{snippetText}"
+        @snippetMarkerChar
+
+    # Add spans for replacement prefix
+    # e.g. replacement: 'abc(|, |)f'
+    # ->   highlightedHTML: 'abc(|, |)f'
+    highlightedHTML = ''
+    wordIndex = 0
+    lastWordIndex = 0
+    for ch, i in replacementPrefix
+      while wordIndex < replacement.length and replacement[wordIndex].toLowerCase() isnt ch.toLowerCase()
+        wordIndex += 1
+
+      break if wordIndex >= replacement.length
+      preChar = replacement.substring(lastWordIndex, wordIndex)
+      highlightedChar = "#{replacement[wordIndex]}"
+      highlightedHTML = "#{highlightedHTML}#{preChar}#{highlightedChar}"
+      wordIndex += 1
+      lastWordIndex = wordIndex
+
+    highlightedHTML += replacement.substring(lastWordIndex)
+
+    # Place the snippets back at the placeholders
+    # e.g. highlightedHTML: 'abc(|, |)f'
+    # ->   highlightedHTML: 'abc(d, e)f'
+    if snippetCompletions.length
+      completionIndex = 0
+      highlightedHTML = highlightedHTML.replace @snippetMarkerRegex, (match, snippetText) ->
+        snippetCompletions[completionIndex++]
+
+    highlightedHTML
+
   dispose: ->
     @subscriptions.dispose()
     @parentNode?.removeChild(this)