Filled a leaf with inserts.

Filled a leaf with inserts.

--- src/lib/strata_.coffee
+++ src/lib/strata_.coffee
@@ -64,21 +64,28 @@
   # TODO: Reading through the code, this does not take this into account, and
   # will problably send items that belong in the least value tree into the three
   # that follows it. I'll clean this up with unit testing.
-  find: (tier, low, extractor, sought, _) ->
+  find: (tier, _) ->
     size = tier.addresses.length
-    high = size - 1
-    while low < high
+    if tier.leaf
+      [ low, high, offset ] = [ 0, size - 1, 0 ]
+    else if size is 1
+      return 0
+    else
+      [ low, high, offset ] = [ 1, size - 1, 1 ]
+    { comparator, fields, io } = @
+    loop
       mid = (low + high) >>> 1
-      compare = @comparator sought, extractor @_record tier, mid, _
+      compare = comparator fields, io.key(tier, mid, _)
+      if compare is 0
+        break
       if compare > 0
         low = mid + 1
       else
-        high = mid
-    if low < size
-      while low != 0 && @comparator(sought, @extractor tier.record(low - 1)) == 0
-        low--
-      return low
-    return low - 1
+        high = mid - 1
+    if compare is 0
+      while mid != 0 && comparator(fields, io.key(tier, mid - 1, _)) == 0
+        mid--
+    return mid

   mutate: (_) ->
     address = 0
@@ -96,25 +103,25 @@
     @[@operation.method].call this, parent, child, _

   insertSorted: (parent, child, _) ->
-    branch = @find child, 1, @_branchExtractor, @fields, _
+    branch = @find child, _
     leaves = @io.lock true, child.addresses[branch], true, _

     addresses = leaves.addresses

     # Insert the object value sorted.
     for i in [0...addresses.length]
-      before = leaf.record(i)
-      if @comparator(mutation.fields, @extract before) <= 0
+      key = @io.key(leaves, i, _)
+      if @comparator(@fields, key) <= 0
         break

     address = @io.writeInsert leaves, @object, i
     addresses.splice i, 0, address

   get: (parent, child, _) ->
-    branch = @find child, 1, @_branchExtractor, @fields, _
+    branch = @find child, _
     leaves = @io.lock true, child.addresses[branch], true, _
-    address = @find leaves, 0, @extractor, @fields, _
-    leaves.objects[address]
+    address = @find leaves, _
+    leaves.cache[leaves.addresses[address]]

 class Level
   # Construct a new level. The `exclusive` pararameter determines how this level
@@ -159,7 +166,7 @@
 extractor = (a) -> a

 class IO
-  constructor: (@directory) ->
+  constructor: (@directory, @extractor) ->
     @cache          = {}
     @head           = { address: -1 }
     @head.next      = @head
@@ -210,7 +217,7 @@

   lock: (exclusive, address, leaf, callback) ->
     if not tier = @cache[address]
-      tier = @link({ leaf, address, objects: {}, addresses: [], locks: [[]] })
+      tier = @link({ leaf, address, cache: {}, addresses: [], locks: [[]] })
     locks = tier.locks
     if tier.loaded
       lock = callback
@@ -255,14 +262,14 @@

   allocateBranches: (penultimate) ->
     address = @nextAddress++
-    tier = @_link({ penultimate, address, addresses: [], objects: {} })
+    tier = @_link({ penultimate, address, addresses: [], cache: {} })
     @cache[address] = tier

   allocateLeaves: (_) ->
     address = @nextAddress++
     filename = @filename address
     fs.writeFile filename, "", "utf8", _
-    @cache[address] = @_link({ leaves: true, address, objects: [] })
+    @cache[address] = @_link({ leaves: true, address, cache: [] })

   readLeaves: (tier, _) ->
     filename = @filename tier.address
@@ -278,7 +285,7 @@
     eol       = stat.size
     splices   = []
     addresses = []
-    objects   = {}
+    cache     = {}
     buffer    = new Buffer(1024)
     while end
       end     = eol
@@ -303,7 +310,7 @@
           else
             splices.push [ index, start + read ]
             if index > 0
-              objects[start + read] = record.shift()
+              cache[start + read] = record.shift()
       eol = start + eos
     splices.reverse()
     for splice in splices
@@ -312,7 +319,52 @@
         addresses.splice(index - 1, 0, address)
       else
         addresses.splice(-(index + 1), 1)
-    tier = extend tier, { addresses, objects }
+    tier = extend tier, { addresses, cache }
+
+  object: (tier, index, _) ->
+    address = tier.addresses[index]
+    if not object = tier.cache[address]
+      if not tier.in
+        filename = @filename tier.address
+        tier.in = fd.open filename, "r", _
+      if not tier.position
+        filename = @filename tier.address
+        tier.position = fs.stat(filename, _).size
+      length = 1024
+      loop
+        buffer = new Buffer(length)
+        read = fs.read tier.in, buffer, 0, buffer.length, address, _
+        for offset in [0...read]
+          if buffer[offset] is 0xA0
+            json = JSON.parse buffer.toString("utf8", 0, offset + 1)
+            break
+        if length > tier.position - address
+          throw new Error "cannot find end of record."
+        length *= 2
+      object = tier.cache[address] = json.pop()
+    object
+
+  key: (tier, index, _) ->
+    if tier.leaf
+      @extractor @object tier, index, _
+    else
+      address = tier.addresses[index]
+      if not key = tier.cache[address]
+        # Here's something to consdier. The only time we lock the leaf is during
+        # descent, to read in the key.
+        #
+        # If this leaf tier is locked exclusively, we are not the ones holding the
+        # lock, because that would mean that we are locking the inner tier, we
+        # would already have traversed the inner tier, the key would be in the
+        # cache.
+        #
+        # If this leaf tier has an exclusive lock pending, it is not a lock that
+        # we are waiting for, so we can append a shared lock and wait for multiple
+        # exclusive and shared locks to clear.
+        leaf  = @lock false, address, true, _
+        key   = tier.cache[address] = @extractor @object leaf, 0, _
+        @release leaf
+      key

   # TODO: Move link to head always, to maintain MRU.
   readBranches: (tier, _) ->
@@ -332,19 +384,29 @@
     fs.writeFile filename, serialized, "utf8", _
     tier.addresses.pop()

-  _writeJSON: (tier, _) ->
+  _drain: (tier, callback) ->
+    tier.out.once "drain", -> callback()
+
+  _writeJSON: (tier, object, _) ->
     if not tier.out
       filename = @filename tier.address
-      tier.position = fs.stat(filename, _).size
       tier.out = fs.createWriteStream filename,
         flags: "a"
         encoding: "utf8"
         mode: 0666
-    json            = JSON.stringify record
+    if not tier.position
+      filename = @filename tier.address
+      tier.position = fs.stat(filename, _).size
+
+    json            = JSON.stringify object
     position        = tier.position
     tier.position  += Buffer.byteLength(json, "utf8") + 1
-    tier.write(json)
-    tier.write("\n")
+
+    buffered = tier.out.write(json)
+    buffered = tier.out.write("\n") && buffered
+
+    @_drain(tier, _) if buffered
+
     position

   writeInsert: (tier, object, index, _) ->
@@ -365,7 +427,7 @@
       comparator: comparator
       extractor: extractor
     @_options       = extend defaults, options
-    @_io            = new IO options.directory
+    @_io            = new IO options.directory, @_options.extractor

   create: (_) -> @_io.create(_)


Track map bounds changing

Track map bounds changing

--- site/index.coffee
+++ site/index.coffee
@@ -55,6 +55,12 @@
   zoom: 6
   # Satellite view is much nicer for visualising antenna masts
   mapTypeId: google.maps.MapTypeId.SATELLITE
+
+track_bounds_changed = -> mpq.track "Bounds Changed"
+throttled_track_bounds_changed = _.throttle track_bounds_changed, 1000
+
+google.maps.event.addListener map, 'bounds_changed', ->
+  throttled_track_bounds_changed()

 $("a#add_custom_search").click ->
   q = prompt "Enter Company Name"


Fix several issues.

Fix several issues.

--- src/coffee/orderstatussync.coffee
+++ src/coffee/orderstatussync.coffee
@@ -18,7 +18,7 @@

   getOrders: (rest) ->
     deferred = Q.defer()
-    rest.GET "orders?limit=0", (error, response, body) ->
+    rest.GET "/orders?limit=0", (error, response, body) ->
       if error
         deferred.reject "Error on fetching orders: " + error
       else if response.statusCode != 200
@@ -30,9 +30,8 @@

   run: (callback) ->
     throw new Error 'Callback must be a function!' unless _.isFunction callback
-
-    initMatcher().then (fromIndex2toIndex) =>
-      process(fromIndex2toIndex).then (msg) =>
+    @initMatcher().then (fromIndex2toIndex) =>
+      @process(fromIndex2toIndex).then (msg) =>
         @returnResult true, msg, callback
       .fail (msg) =>
         @returnResult false, msg, callback
@@ -47,11 +46,12 @@

   initMatcher: () ->
     deferred = Q.defer()
-    Q.all([@getOrders(@restFrom), @getOrders(@restTo)]).then (ordersFrom, ordersTo) =>
-      @orderFrom = orderFrom
-      @ordersTo = orderTo
+    Q.all([@getOrders(@restFrom), @getOrders(@restTo)]).then ([ordersFrom, ordersTo]) =>
+      @ordersFrom = ordersFrom
+      @ordersTo = ordersTo
       fromIndex2toIndex = {}
-      for oTo,i in ordersTo
+      for oTo,i in @ordersTo
+        continue if oTo.exportedInfo
         for expoInfo,j in oTo.exportedInfo
           if not expoInfo.externalId
             fromIndex2toIndex[i] = j


Config: enable gowebserver

Config: enable gowebserver

--- config/main.dev.coffee
+++ config/main.dev.coffee
@@ -276,7 +276,6 @@
         command         : "#{GOBIN}/watcher -run koding/go-webserver -c #{configName}"
       nginx             :
         locations       : [ location: "~^/IDE/.*" ]
-        disableLocation : !!disabledFeatures.teams
       healthCheckURL    : "http://localhost:#{KONFIG.gowebserver.port}/healthCheck"
       versionURL        : "http://localhost:#{KONFIG.gowebserver.port}/version"


remove _id and password from user details GET api endpoint

--- code/models/user.coffee
+++ code/models/user.coffee
@@ -21,8 +21,9 @@
     @save callback

 userSchema.methods.objectify = ->
-  # :todo: In future we might want to remove some stuff
-  # from this object, like the password hash
-  @toObject()
+  result = @toObject()
+  delete result._id
+  delete result.password
+  return result

 module.exports = mongoose.model 'User', userSchema


改进router，添加对:param?模式url匹配的支持

改进router，添加对:param?模式url匹配的支持

--- src/core/router.coffee
+++ src/core/router.coffee
@@ -38,11 +38,17 @@
 		routerDef.pathParts = pathParts = []
 		if path
 			hasVariable = false
-			for part in path.split(/[\/\?\#]/)
+			for part in path.split("/")
 				if part.charCodeAt(0) == 58 # `:`
+					optional = part.charCodeAt(part.length - 1) == 63 # `?`
+					if optional
+						variable = part.substring(1, part.length - 1)
+					else
+						variable = part.substring(1)
 					hasVariable = true
 					pathParts.push({
-						variable: part.substring(1)
+						variable: variable
+						optional: optional
 					})
 				else
 					pathParts.push(part)
@@ -86,8 +92,8 @@

 	pathParts = if path then path.split(/[\/\?\#]/) else []
 	for routerDef in routerRegistry.elements
-		if routerDef.pathParts.length != pathParts.length
-			continue
+		gap = routerDef.pathParts.length - pathParts.length
+		if 1 < gap < 0 then continue

 		matching = true
 		param = {}
@@ -98,6 +104,9 @@
 					matching = false
 					break
 			else
+				if i >= pathParts.length and not defPart.optional
+					matching = false
+					break
 				param[defPart.variable] = pathParts[i]
 		if matching then break


Fix #6

Fix #6

--- src/main/window.coffee
+++ src/main/window.coffee
@@ -58,15 +58,17 @@
       properties: ['openFile']
     , (filenames) =>
       return unless filenames?[0]?
-      @start filenames[0]
+      @filename = filenames[0]
+      @start @filename

   onToggleDevToolsRequested: =>
     return unless @browserWindow.isFocused()
     @browserWindow.toggleDevTools()

   onReloadRequested: =>
-    return unless @browserWindow.isFocused()
-    @browserWindow.reload()
+    return unless @browserWindow.isFocused() and @filename?
+    @render ''
+    @start @filename

   registerBounds: ->
     @storage.set 'bounds', @browserWindow.getBounds(), (err) =>


Add show command

Add show command

- Visual representation of memes with code and description on image

--- src/memes.coffee
+++ src/memes.coffee
@@ -9,6 +9,7 @@
 #
 # Commands:
 #   hubot meme list - List of memes and their codes
+#   hubot meme show - Show all memes and their codes
 #   hubot meme me (meme) "top message" "bottom message"
 #
 # Author:
@@ -62,6 +63,10 @@
     memelist += "#{code} - #{meme}\n" for code, meme of memes
     msg.send memelist

+  robot.respond /meme show/i, (msg) ->
+    for code, meme of memes
+      msg.send "http://memegen.link/#{code}/#{escape(code)}/#{escape(meme)}.jpg"
+
   robot.respond /meme me (\w+) (\"[^"]+\") (\"[^"]+\")/i, (msg) ->

     meme = if checkCode(msg.match[1].toLowerCase(), memes) then msg.match[1].toLowerCase() else 'doge'


fix(model): fixed model.export incorrectly exporting multicontainers

--- src/model.coffee
+++ src/model.coffee
@@ -145,6 +145,12 @@
       if options.container?
         for name, settings of options.container
           # move to container
+          unless Array.isArray self[name]()
+            data[name] = self[name]().export()
+          else
+            data[name] = []
+            data[name] = self[name]().map (e) -> e.export()
+
           if datahandler
             field = name.toLowerCase()
             field = settings.field if settings.field?
@@ -152,13 +158,8 @@
             datahandler = settings.datahandler if     settings.datahandler?
             datahandler = new JSONHandler()    unless settings.datahandler?

-            data[field] = datahandler.save self[name]()
-          else
-            unless Array.isArray self[name]()
-              data[name] = self[name]().export()
-            else
-              data[name] = []
-              data[name][index] = x.export() for x, index in self[name]()
+            data[field] = datahandler.save data[name]
+            delete data[name] if name != field

       return data


Improve support for reflective methods ending with literal blocks

--- src/nodes.coffee
+++ src/nodes.coffee
@@ -1352,7 +1352,6 @@
       lecode = locationDataToSource @locationData
       lecode = lecode
       .replace(/[\"\\]/g, (((x)->"\\"+x)))
-      .replace(/(\n[ \t]*)*$/, "")
       .replace(/\n/g, "\\n")
       wrapper = new Code [new Param new Literal "x"], new Block [(new Assign(new Value(new Literal("x"), [new Access(new Literal("coffee"))]), new Literal('"'+lecode+'"'))), new Literal("x")]
       @pure = true


Returns are determined now by the scheme parser.

--- src/nodes.coffee
+++ src/nodes.coffee
@@ -346,7 +346,7 @@
     if expr and expr not instanceof Return then expr.compile o, level else super o, level

   compileNode: (o) ->
-    @tab + "(return #{[" #{@expression.compile o, LEVEL_PAREN}" if @expression]})"
+    @tab + "#{[" #{@expression.compile o, LEVEL_PAREN}" if @expression]}"

 #### Value


Jump to text when clicking change icon as well

--- services/web/public/coffee/ide/review-panel/directives/changeEntry.coffee
+++ services/web/public/coffee/ide/review-panel/directives/changeEntry.coffee
@@ -18,7 +18,7 @@
 			scope.needsCollapsing = false

 			element.on "click", (e) ->
-				if $(e.target).is('.rp-entry, .rp-entry-description, .rp-entry-body')
+				if $(e.target).is('.rp-entry, .rp-entry-description, .rp-entry-body, .rp-entry-action-icon i')
 					scope.onBodyClick()

 			scope.toggleCollapse = () ->


Media: added init related state checks.

Media: added init related state checks.

--- coffee/src/media.coffee
+++ coffee/src/media.coffee
@@ -124,10 +124,12 @@
   # State checks -- returns true if media is in a particular state
   isInState: (state) => @mediaState() is state

+  isIniting: => @isInState 'init'
   isReady: => @isInState 'ready'
   isPlaying: => @isInState 'play'
   isPaused: => @isInState 'pause'
   ended: => @isInState 'end'
+  canPlay: => not @isIniting()

   # Seek to/return playback offset.


fixing map container not found

fixing map container not found

--- app/assets/javascripts/models/beacon.js.coffee
+++ app/assets/javascripts/models/beacon.js.coffee
@@ -23,7 +23,8 @@
 #      zoomControlOpt:
 #        position: 'RIGHT_BOTTOM'
 #    )
-     @map = new MicelloMap('rrVDMQe2qjBRuJP5dXv59kAeK77SD7',@mapDom)
+     if @mapDom.length > 0
+      @map = new MicelloMap('rrVDMQe2qjBRuJP5dXv59kAeK77SD7',@mapDom)

   isActive: ->
     @map != null


Select: Add cui-select-menu class to menu.

--- src/elements/Select/Select.coffee
+++ src/elements/Select/Select.coffee
@@ -162,7 +162,7 @@
 			# 	console.debug "adjust", adjust, item_top, vp.dim_element.viewportTop, vp.layer_pos.top, DOM.getDimensions(active_item).viewportTop
 			# 	vp.layer_pos.top = vp.layer_pos.top + adjust
 			# 	vp.layer_pos.left += 20
-
+			class: "cui-select-menu"
 			onDeactivate: (btn, item, idx, flags) =>
 				if flags.prior_activate
 					return


Use correct path to read file. Closes #7

--- src/coffee/run.coffee
+++ src/coffee/run.coffee
@@ -43,7 +43,7 @@
   generatedFiles = fs.readdirSync(path)
   for file in generatedFiles
     # TODO: filter only *.json files
-    zip.file("product-type-json/#{file}", fs.readFileSync(file))
+    zip.file("product-type-json/#{file}", fs.readFileSync("#{path}/#{file}"))
   buffer = zip.generate type: 'nodebuffer'
   fs.writeFileSync "#{path}/#{filename}.zip", buffer


Fix default model override in collection

--- src/collection.coffee
+++ src/collection.coffee
@@ -51,10 +51,10 @@
   # Init

   constructor: (models, options) ->
-    @model = require('./model')
+    super
+
+    @model ?= require('./model')
     @storageManager = require('./storage-manager').get()
-
-    super

     @firstFetchOptions = Collection.pickFetchOptions(options) if options
     @setLoaded false


point

point

--- src/point.coffee
+++ src/point.coffee
@@ -527,7 +527,6 @@

   @name fromAngle
   @fieldOf Point
-
   @param {Number} angle The angle in radians
   @returns {Point} The point on the unit circle.
   ###
@@ -540,16 +539,15 @@
   that the dude standing at p1 will need to face to look at p2.

-     p1 = Point(0, 0)
-     p2 = Point(7, 3)
-
-     Point.direction(p1, p2)
-  => 0.40489178628508343
+  p1 = Point(0, 0)
+  p2 = Point(7, 3)
+
+  Point.direction(p1, p2)
+  # => 0.40489178628508343

   @name direction
   @fieldOf Point
-
   @param {Point} p1 The starting point.
   @param {Point} p2 The ending point.
   @returns {Number} The direction from p1 to p2 in radians.
@@ -563,7 +561,6 @@
   ###*
   @name ZERO
   @fieldOf Point
-
   @returns {Point} The point (0, 0)
   ###
   Point.ZERO = Point()


Fix: apt.cson: missing """

Fix: apt.cson: missing """

--- snippets/apt.cson
+++ snippets/apt.cson
@@ -44,4 +44,4 @@
         pkg: "$2"
         state: present
         update_cache: yes;
-
+    """


Fix newline removal bug with +/- buttons

--- src/controller.coffee
+++ src/controller.coffee
@@ -2210,7 +2210,14 @@
 Editor::populateBlock = (block, string) ->
   newBlock = @mode.parse(string, wrapAtRoot: false).start.next.container
   if newBlock
+    # Find the first token before the block
+    # that will still be around after the
+    # block has been removed
     location = block.start.prev
+    while location?.type is 'newline' and not (
+          location.prev?.type is 'indentStart' and
+          location.prev.container.end is block.end.next)
+      location = location.prev
     @spliceOut block
     @spliceIn newBlock, location
     return true


Add support for marking blocks by row, column as well

--- src/controller.coffee
+++ src/controller.coffee
@@ -497,6 +497,13 @@
       else
         delete @markedLines[line]

+    for id, info of @markedBlocks
+      if @inTree info.model
+        path = @getHighlightPath info.model, info.style
+        path.draw @highlightCtx
+      else
+        delete @markedLines[line]
+
     for id, info of @extraMarks
       if @inTree info.model
         path = @getHighlightPath info.model, info.style
@@ -3341,6 +3348,7 @@

   hook 'populate', 0, ->
     @markedLines = {}
+    @markedBlocks = {}; @markedBlockKey = 0
     @extraMarks = {}

   Editor::getHighlightPath = (model, style) ->
@@ -3363,13 +3371,43 @@

     @redrawMain()

+  Editor::markBlock = (line, col, style) ->
+    parent = @tree.getBlockOnLine line; head = parent.start
+    # Find the necessary indent for this line
+    chars = 0
+    until parent is @tree
+      if parent.type is 'indent'
+        chars += parent.prefix.length
+      parent = parent.parent
+    until (chars >= col and head.type is 'blockStart') or head.type is 'newline'
+      chars += head.stringify().length
+      head = head.next
+
+    if head.type is 'newline'
+      return false
+
+    key = @markedBlockKey++
+
+    @markedBlocks[key] = {
+      model: head.container
+      style: style
+    }
+
+    @redrawMain()
+
+    return key
+
+  Editor::unmarkBlock = (key) ->
+    delete @markedBlocks[key]
+    return true
+
   Editor::unmarkLine = (line) ->
     delete @markedLines[line]

     @redrawMain()

-  Editor::clearLineMarks = (tag) ->
-    @markedLines = {}
+  Editor::clearLineMarks = ->
+    @markedLines = @markedBlocks = {}

     @redrawMain()


Remove extraneous debug stuff

Remove extraneous debug stuff

--- src/controller.coffee
+++ src/controller.coffee
@@ -651,7 +651,6 @@
     head = head.prev

   if head.type is 'newline'
-    console.log 'splicing out extra lines'
     @spliceOut new model.List head, tail

 # UNDO STACK SUPPORT
@@ -683,7 +682,6 @@
     @undoStack.push operation
     return operation
   else
-    console.log 'wasn\'t in tree', node

 Editor::spliceIn = (node, location) ->
   container = location.container ? location.parent
@@ -860,7 +858,6 @@

     # Signify that we are now dragging a block.
     @draggingBlock = @clickedBlock
-    console.log 'set to', @draggingBlock, @inTree(@draggingBlock), @clickedBlock, @inTree(@clickedBlock)

     # Our dragging offset must be computed using the canvas on which this block
     # is rendered.
@@ -876,7 +873,6 @@
       if 'function' is typeof expansion then expansion = expansion()
       if (expansion) then expansion = parseBlock(@mode, expansion)
       @draggingBlock = (expansion or @draggingBlock).clone()
-      console.log 'set to reparsed', @draggingBLock

     else
       # Find the line on the block that we have
@@ -1709,7 +1705,6 @@
 #   -> Reparses function(a, b) {} with two paremeters.
 #   -> Finsihed.
 Editor::reparse = (list, recovery, originalTrigger = list) ->
-  debugger
   # Don't reparse sockets. When we reparse sockets,
   # reparse them first, then try reparsing their parent and
   # make sure everything checks out.


Remove runaway print

Remove runaway print

--- src/quest.coffee
+++ src/quest.coffee
@@ -117,7 +117,6 @@
   # it.
   registerPlugins: ->
     for plugin in @plugins
-      console.log path
       unless _.startsWith(plugin, '/') or not _.startsWith(plugin, '.')
         plugin = path.join(@questPath, plugin)
       require(plugin)(@)


Fix wrong target

Fix wrong target

event.target is the triggering element whereas in there it should be the delegate target.

--- resources/assets/coffee/tooltip-default.coffee
+++ resources/assets/coffee/tooltip-default.coffee
@@ -20,13 +20,13 @@
     $(document).on 'mouseover', '[title]', @onMouseOver

   onMouseOver: (event) =>
-    return if event.target._tooltip
+    return if event.currentTarget._tooltip

-    event.target._tooltip = true
+    event.currentTarget._tooltip = true

-    title = event.target.getAttribute 'title'
+    title = event.currentTarget.getAttribute 'title'

-    at = event.target.getAttribute('data-tooltip-position') || 'top center'
+    at = event.currentTarget.getAttribute('data-tooltip-position') || 'top center'

     my = switch at
       when 'top center' then 'bottom center'
@@ -51,7 +51,7 @@
           width: 10
           height: 8

-    event.target.setAttribute 'data-orig-title', title
-    event.target.removeAttribute 'title'
+    event.currentTarget.setAttribute 'data-orig-title', title
+    event.currentTarget.removeAttribute 'title'

-    $(event.target).qtip options, event
+    $(event.currentTarget).qtip options, event


Set carousel timer to 15 seconds

Set carousel timer to 15 seconds

--- scripts/modules/featured-books/featured-books.coffee
+++ scripts/modules/featured-books/featured-books.coffee
@@ -57,4 +57,4 @@

         # Start the carousel
         if @_carousel then clearInterval(@_carousel)
-        @_carousel = setInterval(nextFeatured, 3000)
+        @_carousel = setInterval(nextFeatured, 15000)


Refactor the date range control to extend generic range control

--- src/coffee/cilantro/ui/field/controls/date.coffee
+++ src/coffee/cilantro/ui/field/controls/date.coffee
@@ -1,120 +1,41 @@
 define [
-    'underscore'
-    '../../controls'
-    '../../button'
-    'tpl!templates/controls/range-input.html'
-], (_, controls, button, templates...) ->
+    './range'
+], (range) ->

-    templates = _.object ['range'], templates
-
-    class DateControl extends controls.Control
-        template: templates.range
-
+    class DateControl extends range.RangeControl
+        # Add the change event from the datepickers to existing range events
         events: ->
             'changeDate .datepicker': 'change'
-            'keyup .datepicker': 'change'
-            'change .btn-select': 'change'
-            'click .range-help-button': 'toggleHelpText'
-
-        initialize: (options) ->
-            @model = options.model
-
-        ui:
-            inOutSelect: '.btn-select'
-            lowerBound: '.range-from'
-            upperBound: '.range-to'

         onRender: ->
-            @inOutSelect = new button.ButtonSelect
-                collection: [
-                    value: 'between'
-                    label: 'between'
-                    selected: true
-                ,
-                    value: 'not_between'
-                    label: 'not between'
-                ]
-
-            @inOutSelect.render().$el.prependTo(@$el)
-            @$('.help-block').hide()
+            # Initialize the datepickers and make them close automatically
             @ui.lowerBound.datepicker({'autoclose': true})
             @ui.upperBound.datepicker({'autoclose': true})

-            @set(@context)
+        # Convert the lower bound to a date for use in the getValue() method
+        getLowerBoundValue: ->
+            return @ui.lowerBound.datepicker('getFormattedDate')

-        toggleHelpText: (event) ->
-            @$('.help-block').toggle()
-            event.preventDefault()
+        # Convert the upper bound to a date for use in the getValue() method
+        getUpperBoundValue: ->
+            return @ui.upperBound.datepicker('getFormattedDate')

-        isBetweenSelected: ->
-            return @inOutSelect.getSelection() == 'between'
+        parseMinStat: (value) ->
+            return @parseMaxStat(value)
+        parseMaxState: (value) ->
+            return value.replace(/-/g, "/")

-        getField: ->
-            return @model.id
+        # We need to override the default behavior here so that the value is
+        # applied to the datepicker rather than the textbox. The datepicker
+        # will handle the updating of the textbox.
+        setLowerBoundValue: (value) ->
+            @ui.lowerBound.datepicker('setDate', new Date(value))

-        getOperator: ->
-            from = @ui.lowerBound.val()
-            to = @ui.upperBound.val()
-            operator = ''
+        # We need to override the default behavior here so that the value is
+        # applied to the datepicker rather than the textbox. The datepicker
+        # will handle the updating of the textbox.
+        setUpperBoundValue: (value) ->
+            @ui.upperBound.datepicker('setDate', new Date(value))

-            if from != "" and to != ""
-                operator = if @isBetweenSelected() then 'range' else '-range'
-            else if from != ""
-                operator = if @isBetweenSelected() then 'gte' else 'lte'
-            else if to != ""
-                operator = if @isBetweenSelected() then 'lte' else 'gte'
-            else
-                operator = if @isBetweenSelected() then 'range' else '-range'
-
-            return operator
-
-        getValue: ->
-            from_text = @ui.lowerBound.val()
-            from_date = @ui.lowerBound.data('datepicker').getFormattedDate()
-
-            to_text = @ui.upperBound.val()
-            to_date = @ui.upperBound.data('datepicker').getFormattedDate()
-
-            value = []
-
-            if from_text != "" and to_text != ""
-                value = [from_date, to_date]
-            else if from_text != ""
-                value = from_date
-            else if to_text != ""
-                value = to_date
-            # If both fields are emtpy, return null to invalidate this range
-            else
-                value = null
-
-            return value
-
-        setOperator: (operator) ->
-            @operator = operator
-
-            if operator == '-range'
-                @inOutSelect.setSelection("not_between")
-            else
-                @inOutSelect.setSelection("between")
-
-        setValue: (value) ->
-            switch @operator
-                when 'range', '-range'
-                    if value[0]?
-                        @ui.lowerBound.data('datepicker').setDate(
-                            new Date(value[0]))
-                    else
-                        @ui.lowerBound.val(@minLowerBound ? '')
-                    if value[1]?
-                        @ui.upperBound.data('datepicker').setDate(
-                            new Date(value[1]))
-                    else
-                        @ui.upperBound.val(@maxUpperBound ? '')
-                when 'gte'
-                    @ui.lowerBound.data('datepicker').setDate(
-                        new Date(value))
-                when 'lte'
-                    @ui.upperBound.data('datepicker').setDate(
-                        new Date(value))

     {DateControl}


delete old code

delete old code

--- src/javascript/game/metroid_clone_delegate.coffee
+++ src/javascript/game/metroid_clone_delegate.coffee
@@ -199,59 +199,12 @@
       else
         throw new Error("WTF Transforms.selectAction returned #{action.toJS()}")

-    # # ------------------------------------------------------------------------
-    # adminState = @adminState # -->
-    # stateHistory = @stateHistory
-    #
-    # gameState0 = @gameState # -->
-    # gameMachine = @gameMachine # -->
-    # gameControlMappings = GameControlMappings # -->
-    # defaultInput = @defaultInput # -->
-    # # controllerEvents -->
-    # # ------------------------------------------------------------------------
-    #
-    # gameState1 = null
-    # events = null
-    # if adminState.get('paused')
-    #   if adminState.get('replay_forward')
-    #     stateHistory = ImmRingBuffer.forward(stateHistory)
-    #     gameState1 = ImmRingBuffer.read(stateHistory)
-    #
-    #   else if adminState.get('replay_back')
-    #     stateHistory = ImmRingBuffer.backward(stateHistory)
-    #     gameState1 = ImmRingBuffer.read(stateHistory)
-    #
-    #   else if adminState.get('step_forward')
-    #     # paused, step forward one nominal time slice. 17 =~ 16.666
-    #     dt = 17
-    #
-    #   else
-    #     # paused. no change.
-    #     gameState1 = ImmRingBuffer.read(stateHistory)
-    #
-    # if !gameState1
-    #   gameInput = defaultInput
-    #     .set('dt', dt)
-    #     .set('controllers', @_mapControllerEvents(controllerEvents,gameControlMappings))
-    #
-    #   [gameState1,events] = gameMachine.update2(gameState0,gameInput)
-    #
-    #   stateHistory = ImmRingBuffer.add(stateHistory, gameState1)
-    #
-    # # ------------------------------------------------------------------------
-    # @stateHistory = stateHistory # <---
-    # @gameState = gameState1 # <--
-    # # events # <--
-    # # ------------------------------------------------------------------------

     # (maybe) Switch levels based on game events
     switchLevel = (level,machine) =>
       @gameState = @_getInitialState(level)
-
       @gameMachine = machine
       @stateHistory = ImmRingBuffer.add(ImmRingBuffer.clear(@stateHistory), @gameState)
-      # @adminState.update('stateHistory', (h) ->
-      #   ImmRingBuffer.add(ImmRingBuffer.clear(h), @gameState))

     if events? and events.size > 0
       if e = events.find((e) -> e.get('name') == 'StartNewGame')


Add an additonal test, courtesy @matehat

--- test/classes.coffee
+++ test/classes.coffee
@@ -754,3 +754,10 @@

   b = (new B).b
   eq b(), 2
+
+
+test "#2791: bound function with destructured argument", ->
+  class Foo
+    method: ({a}) => 'Bar'
+
+  eq (new Foo).method({a: 'Bar'}), 'Bar'


Add missing tomdoc comments to functions

--- src/robot.coffee
+++ src/robot.coffee
@@ -18,7 +18,7 @@
     @enableSlash = false

     Adapter = require "#{adapterPath}/#{adapter}"
-    @adapter = new Adapter(@)
+    @adapter = new Adapter @

   # Public: Adds a Listener that attempts to match incoming messages based on
   # a Regex.
@@ -129,6 +129,7 @@
         continue if !line.match('-')
         @commands.push line[2..line.length]

+  # Public: Get an Array of User objects stored in the brain.
   users: ->
     @brain.data.users

@@ -149,10 +150,16 @@
         result = @brain.data.users[k]
     result

+  # Public: Run Hubot using the loaded adapter.
+  #
+  # Returns nothing.
   run: ->
     @adapter.run()

 class Robot.Adapter
+  # An adapter is a specific interface to a chat source for robots.
+  #
+  # robot - A Robot instance.
   constructor: (@robot) ->

   # Public: Raw method for sending data back to the chat source.  Extend this.
@@ -183,9 +190,15 @@
   close: ->
     @robot.brain.close()

+  # Public: Dispatch a received message to the robot.
+  #
+  # message - A TextMessage instance of the received message.
+  #
+  # Returns nothing.
   receive: (message) ->
     @robot.receive message

+  # Public: Get an Array of User objects stored in the brain.
   users: ->
     @robot.users

@@ -249,14 +262,25 @@

     @resetSaveInterval 5

+  # Emits the 'save' event so that 'brain' scripts can handle persisting.
+  #
+  # Returns nothing.
   save: ->
     @emit 'save', @data

+  # Emits the 'close' event so that 'brain' scripts can handle closing.
+  #
+  # Returns nothing.
   close: ->
     clearInterval @saveInterval
     @save()
     @emit 'close'

+  # Reset the interval between save function calls.
+  #
+  # seconds - An Integer of seconds between saves.
+  #
+  # Returns nothing.
   resetSaveInterval: (seconds) ->
     clearInterval @saveInterval if @saveInterval
     @saveInterval = setInterval =>


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/durable.coffee
+++ test/durable.coffee
@@ -1,15 +1,19 @@
-test "Durable objects become inactive after their duration", ->
+module("Durable")
+
+test "objects become inactive after their duration", ->
   obj = GameObject
     duration: 5
-
+
   obj.include(Durable)
-
+
   5.times ->
     obj.update()
-
+
   equals obj.I.active, true, "object is active until duration is exceeded"
-
+
   6.times ->
     obj.update()

   equals obj.I.active, false, "object is inactive after duration"
+
+module()


setup: Support envvars for broker URL

setup: Support envvars for broker URL

--- src/setup.coffee
+++ src/setup.coffee
@@ -44,7 +44,13 @@

   return bindings

+exports.normalizeOptions = normalize = (options) ->
+  options.broker = process.env['MSGFLO_BROKER'] if not options.broker
+  options.broker = process.env['CLOUDAMQP_URL'] if not options.broker
+  return options
+
 exports.bindings = setupBindings = (options, callback) ->
+  options = normalize options
   common.readGraph options.graphfile, (err, graph) ->
     return callback err if err
     bindings = graphBindings graph


Remove relative paths for imports and python

--- src/skype.coffee
+++ src/skype.coffee
@@ -1,8 +1,4 @@
-Readline = require 'readline'
-
-Robot   = require("hubot").Robot
-Adapter = require("hubot").Adapter
-{TextMessage,EnterMessage,LeaveMessage,CatchAllMessage} = require '../../../src/message'
+{Robot, Adapter, TextMessage, EnterMessage, LeaveMessage, CatchAllMessage} = require "hubot"

 class SkypeAdapter extends Adapter
   send: (user, strings...) ->
@@ -24,7 +20,7 @@
     if (process.platform == 'win32')
         py = 'C:/Python27/python.exe'
     else
-        py = './python'
+        py = 'python'
     @skype = require('child_process').spawn(py, [pyScriptPath])
     @skype.stdout.on 'data', (data) =>
         decoded = JSON.parse(data.toString())


:memo: Fix docstring

:memo: Fix docstring

--- src/path-filter.coffee
+++ src/path-filter.coffee
@@ -57,7 +57,7 @@
   isDirectoryAccepted: (filepath) ->
     return false if @isPathExcluded('directory', filepath) is true

-    # An matching explicit local inclusion will override the global exclusions
+    # A matching explicit local inclusion will override the global exclusions
     # Other than this, the logic is the same between file and directory matching.
     return true if @inclusions['directory']?.length && @isPathIncluded('directory', filepath)


Extend highlight span to be entire width of tree

Closes #219

--- src/packages/tree-view/lib/tree-view.coffee
+++ src/packages/tree-view/lib/tree-view.coffee
@@ -56,6 +56,7 @@
   afterAttach: (onDom) ->
     @focus() if @focusAfterAttach
     @scrollTop(@scrollTopAfterAttach) if @scrollTopAfterAttach > 0
+    @find('.selected > .highlight').width(@treeViewList[0].scrollWidth)

   serialize: ->
     directoryExpansionStates: @root?.serializeEntryExpansionStates()
@@ -304,10 +305,11 @@
     entry = entry.view() unless entry instanceof View
     @selectedPath = entry.getPath()
     @deselect()
+    entry.children('.highlight').width(@treeViewList[0].scrollWidth)
     entry.addClass('selected')

   deselect: ->
-    @treeViewList.find('.selected').removeClass('selected')
+    @treeViewList.find('.selected').removeClass('selected').children('.highlight').width('')

   scrollTop: (top) ->
     if top


Remove unused require

Remove unused require

--- src/packages/tree-view/src/file-view.coffee
+++ src/packages/tree-view/src/file-view.coffee
@@ -1,4 +1,4 @@
-{View, $$} = require 'space-pen'
+{View} = require 'space-pen'
 $ = require 'jquery'
 Git = require 'git'
 fs = require 'fs'


Remove unneded focus() call

Remove unneded focus() call

focus() is called from attach()

--- src/packages/tree-view/src/tree-view.coffee
+++ src/packages/tree-view/src/tree-view.coffee
@@ -135,7 +135,6 @@

   revealActiveFile: ->
     @attach()
-    @focus()

     return unless activeFilePath = @rootView.getActiveEditor()?.getPath()


docs: Cleaned up incorrect docstrings

docs: Cleaned up incorrect docstrings

--- client/app/lib/util/openIdeByMachine.coffee
+++ client/app/lib/util/openIdeByMachine.coffee
@@ -19,7 +19,7 @@
   machineId  = machine._id

   # First, try to get the already loaded IDE. If it exists, we
-  # don't need to redirect the user.
+  # don't need to listen for the app creation events below.
   ideController = getIdeByMachine machine
   if ideController
     router.handleRoute "/IDE/#{machine.slug}"
@@ -44,14 +44,6 @@
     # When the app is first created, it has no mounted machine. We
     # need the mounted machine, to compare with the `machine` given to
     # fetchIdeByMachine.
-    #
-    # TODO: Find a way to check if the controller is already, ready.
-    # Otherwise we risk waiting for a ready event, on something that's
-    # already ready.
-    #
-    # TODO: A timeout is also needed. If for whatever reason `ready`
-    # will not be fired, the timeout will ensure that the callback
-    # doesn't hang in nomans land.
     controller.once 'ready', ->
       unless controller.mountedMachine?._id is machineId
         return callback new Error 'IDEApp being shown does not belong to


Code lint

Code lint

--- client/app/views/config_applications.coffee
+++ client/app/views/config_applications.coffee
@@ -90,7 +90,21 @@
         if not(@devices.length is 0)
             @$el.find('.title-device').after @deviceList.$el
         else
-            @$el.find('.title-device').after "

#{t 'status no device'}

#{t 'mobile app promo'}
#{t 'download apk'}
"
+            @$el.find('.title-device').after """
+

+
#{t 'status no device'}

+
#{t 'mobile app promo'}

+
+
+    #{t 'download apk'}
+
+
+
+
+
"
+            """

     fetch: =>


implementation can do that

implementation can do that

--- src/notice/capsule/capsule.coffee
+++ src/notice/capsule/capsule.coffee
@@ -89,7 +89,3 @@
                 Object.defineProperty @, key,
                     writable: not opts.protected

-
-        # save:    ->
-        # refresh: ->
-


more explicitness

more explicitness

--- src/openpgp/packet/signature.iced
+++ src/openpgp/packet/signature.iced
@@ -92,13 +92,15 @@
   verify : (data_packets, cb) ->
     await @_verify data_packets, defer err
     for p in @unhashed_subpackets when (not err? and (s = p.to_sig())?)
-      if s.type is C.sig_types.primary_binding
+      if s.type isnt C.sig_types.primary_binding
+        err = new Error "unknown subpacket signature type: #{s.type}"
+      else if data_packets.length isnt 1
+        err = new Error "Needed 1 data packet for a primary_binding signature"
+      else
         subkey = data_packets[0]
         s.primary = @primary
         s.key = subkey.key
-        await s._verify data_packets, defer err
-      else
-        err = new Error "unknown subpacket signature type: #{s.type}"
+        await s._verify [ subkey ], defer err
     cb err

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


Using wrapper around lstat to preserve the corresponding filename in the closure

--- src/server/application_manager/index.coffee
+++ src/server/application_manager/index.coffee
@@ -19,14 +19,14 @@
     load : (paths) ->
         for path in paths
             path = Path.resolve process.cwd(), path
-            Fs.lstat path, (err, stats) =>
+            @friendlyLstat path, (err, stats) =>
                 throw err if err
                 throw new Error "Path " + path + " not found" if not stats
                 #Check for symlink
                 if stats.isFile()
-                    @addFile path
+                    @addFile stats.filename
                 else if stats.isDirectory()
-                    @walk path
+                    @walk stats.filename

     # Adds an html application to the application manager
     addFile : (path) ->
@@ -36,19 +36,19 @@
         @add opts

+    friendlyLstat : (filename, cb) ->
+        Fs.lstat filename, (err, stats) ->
+            stats.filename = filename
+            if err then cb err
+            else cb err, stats
+
     #Walks a path recursively and finds all CloudBrowser applications
     walk : (path) =>
         outstandingReadDir = @barrier.add()

-        friendlyLstat = (filename, cb) ->
-            Fs.lstat filename, (err, stats) ->
-                stats.filename = filename
-                if err then cb err
-                else cb err, stats
-
         statDirEntry = (filename) =>
             outstandingLstat = @barrier.add()
-            friendlyLstat filename, (err, stats) =>
+            @friendlyLstat filename, (err, stats) =>
                 if err then throw err
                 if stats.isDirectory()
                     @walk stats.filename


Just drawing entities

Just drawing entities

--- public/app/game.coffee
+++ public/app/game.coffee
@@ -1,28 +1,28 @@
 define ['jquery', 'easel', 'EventEmitter', 'cs!block', 'cs!input', 'cs!entity'], ($, $e, EventEmitter, Block, Input, Entity) ->
     class Game extends EventEmitter
         constructor: (@screen) ->
-            setInterval (=> console.log "FPS: #{1000 / window.elapsed }"), 3000
+            #setInterval (=> console.log "FPS: #{1000 / window.elapsed }"), 3000

             window.$game = @
             $e.Ticker.addListener @

-            @player = new Entity("player")
             @blocks = {}
             @entities = [
-                @player,
+                new Entity("player", 0, 0)
                 new Entity("square", 200, 200)
             ]

             @camera = {x: 0, y: 0}
             @input = new Input()

+            @once "ready", =>
+                @screen.stage.addChildAt (e.avatar for e in @entities)..., 1
+
         ready: () =>
             @emit "ready", @

         load: (cb) ->
             @once "ready", cb if cb
-
-            @init_player()

             # TODO: Compute block index from position
             block = {x: 0, y: 0}
@@ -34,34 +34,14 @@
                 new Block data, (b) =>
                     @blocks[block.x] ?= {}
                     @blocks[block.x][block.y] = b
-                    @screen.stage.addChildAt b.container, @screen.stage.getChildIndex(@player.avatar)
+                    @screen.stage.addChild b.container
+                    @screen.stage.setChildIndex b.container, 0
+                    @ready()
             .error =>
                 console.log "Failed to fetch block."

-            @ready()
-
-        init_player: () =>
-            @screen.stage.addChild @player.avatar
-
-            do (speed = 64) =>
-                @input.on "key:down[Up]", => @player.v.y = speed
-                @input.on "key:up[Up]", => @player.v.y = 0
-
-                @input.on "key:down[Down]", => @player.v.y = -speed
-                @input.on "key:up[Down]", => @player.v.y = 0
-
-                @input.on "key:down[Left]", => @player.v.x = -speed
-                @input.on "key:up[Left]", => @player.v.x = 0
-
-                @input.on "key:down[Right]", => @player.v.x = speed
-                @input.on "key:up[Right]", => @player.v.x = 0
-
         tick: (elapsed, paused) =>
             window.elapsed = elapsed
-
-            # Chase the player with the camera
-            @camera.x = @player.p.x
-            @camera.y = @player.p.y

             # Update entities
             for entity in @entities


Add support for placeholder replacement

Add support for placeholder replacement

--- proxy.coffee
+++ proxy.coffee
@@ -12,7 +12,7 @@

 start = (config) ->
   match = (url, route) ->
-    new RegExp(route).test(url)
+    new RegExp(route).exec(url)

   debug = (args...) ->
     if config.debug
@@ -23,8 +23,12 @@
     for path, dest of config.routes
       debug "Trying #{ path }"

-      if match(req.url, path)
+      if matches = match(req.url, path)
+        for component, i in matches when i > 0
+          dest = dest.replace new RegExp("\\$#{ i }", 'g'), component
+
         debug "#{ path } matches, sending to #{ dest }".green
+
         return dest

     debug "No match!".red


feat(diagnostic): shut down after message received

--- src/diagnostic.coffee
+++ src/diagnostic.coffee
@@ -70,5 +70,8 @@
     symphony.readDatafeed(response.id)
   .then (response) =>
     logger.info "Received '#{msg.message}'" for msg in response when msg.v2messageType = 'V2Message'
+    if argv.runOffline?
+      nock.close()
+    process.exit(0)
   .fail (err) =>
     logger.error "Failed to receive a message: #{err}"


Removing unneccessary log

Removing unneccessary log

--- src/jolokiasrv.coffee
+++ src/jolokiasrv.coffee
@@ -210,7 +210,6 @@
    * @return {Object} The merged template
   ###
   merge_parent_templates: (template) =>
-    console.log ''
     mappings = @templates[template].mappings
     inherits = @templates[template].inherits
     if inherits == null or inherits == undefined


make log message consistent

make log message consistent

--- bokehjs/src/coffee/renderer/glyph/glyph.coffee
+++ bokehjs/src/coffee/renderer/glyph/glyph.coffee
@@ -267,7 +267,7 @@
         if @_hit_rect?
           return @_hit_rect(geometry)
         if not @_rect_hit_warned?
-          console.log "'rect' selection not avaliable on renderer"
+          console.log "WARNING: 'rect' selection not avaliable on renderer"
           @_rect_hit_warned = true
         return null


Fix two minor bugs with time tracking

Fix two minor bugs with time tracking

--- src/track.coffee
+++ src/track.coffee
@@ -186,6 +186,7 @@
 exports.start = (task, time = "", note = "") ->
     options = parseOptions task, time, note
     cb = trackHistory
+    messageType = "start"
     cb = startTimer if time.match /^\+?\d+(\.|:)\d+$/
     timer.create options, cb

@@ -197,7 +198,7 @@

 # Resume a stopped timer
 exports.resume = (entry) ->
-    task = getHistoryEntry entry if entry else file.history().chrono.pop()
+    task = if not entry then file.history().chrono.pop() else getHistoryEntry entry
     checkTimer task, startTimer


pass correct dataType to sorting header

pass correct dataType to sorting header

--- tutor/src/components/scores/assignment-header.cjsx
+++ tutor/src/components/scores/assignment-header.cjsx
@@ -59,6 +59,7 @@
         sortKey={columnIndex}
         sortState={sort}
         onSort={onSort}
+        dataType='score'
       >

Progress

       </SortingHeader>
@@ -68,7 +69,7 @@
       <SortingHeader
         type={heading.type}
         sortKey={columnIndex}
-        dataType={dataType}
+        dataType='score'
         sortState={sort}
         onSort={onSort}
       >
@@ -77,7 +78,7 @@
       <SortingHeader
         type={heading.type}
         sortKey={columnIndex}
-        dataType={dataType}
+        dataType='completed'
         sortState={sort}
         onSort={onSort}
       >


GameObject.Effect

GameObject.Effect

--- src/tween.coffee
+++ src/tween.coffee
@@ -21,8 +21,8 @@
         delete I.activeTweens[property]
       else
         f = Easing[data.easing](data.start, data.end)
-        t = (I.age + elapsedTime
-        I[property] = f((t- data.startTime) / data.duration)
+
+        I[property] = f((t - data.startTime) / data.duration)

   ###*
   Modify the object's properties over time.


Rename msg => message.

Rename msg => message.

--- src/types.coffee
+++ src/types.coffee
@@ -32,12 +32,12 @@
 # @param thing {object}       The object to test.
 # @param prototype {function} The prototype that the
 #                             test is against.
-# @param [msg] {string}       An optional error message
+# @param [message] {string}   An optional error message
 #                             to set on the thrown Error.
 #
-verifyInstance = (thing, prototype, msg) ->
+verifyInstance = (thing, prototype, message) ->
   if isInstance(thing, prototype) is false
-    throw new Error msg || 'Invalid type'
+    throw new Error message || 'Invalid type'

 ##
 # Public function `types.isInstance`.
@@ -62,13 +62,13 @@
 # Throws an exception unless something is a non-null,
 # non-array object.
 #
-# @param thing          The thing to test.
-# @param [msg] {string} An optional error message
-#                       to set on the thrown Error.
-#
-verifyObject = (thing, msg) ->
+# @param thing              The thing to test.
+# @param [message] {string} An optional error message
+#                           to set on the thrown Error.
+#
+verifyObject = (thing, message) ->
   if isObject(thing) is false
-    throw new Error msg || 'Invalid object'
+    throw new Error message || 'Invalid object'

 ##
 # Public function `types.isObject`.
@@ -88,13 +88,13 @@
 #
 # Throws an exception unless something is an array.
 #
-# @param thing          The thing to test.
-# @param [msg] {string} An optional error message
-#                       to set on the thrown Error.
-#
-verifyArray = (thing, msg) ->
+# @param thing              The thing to test.
+# @param [message] {string} An optional error message
+#                           to set on the thrown Error.
+#
+verifyArray = (thing, message) ->
   if isArray(thing) is false
-    throw new Error msg || 'Invalid array'
+    throw new Error message || 'Invalid array'

 ##
 # Public function `types.isArray`.
@@ -113,13 +113,13 @@
 #
 # Throws an exception unless something is function.
 #
-# @param thing          The thing to test.
-# @param [msg] {string} An optional error message
-#                       to set on the thrown Error.
-#
-verifyFunction = (thing, msg) ->
+# @param thing              The thing to test.
+# @param [message] {string} An optional error message
+#                           to set on the thrown Error.
+#
+verifyFunction = (thing, message) ->
   if isFunction(thing) is false
-    throw new Error msg || 'Invalid function'
+    throw new Error message || 'Invalid function'

 ##
 # Public function `types.isFunction`.
@@ -138,13 +138,13 @@
 #
 # Throws an exception unless something is a non-empty string.
 #
-# @param thing          The thing to test.
-# @param [msg] {string} An optional error message
-#                       to set on the thrown Error.
-#
-verifyUnemptyString = (thing, msg) ->
+# @param thing              The thing to test.
+# @param [message] {string} An optional error message
+#                           to set on the thrown Error.
+#
+verifyUnemptyString = (thing, message) ->
   if isUnemptyString(thing) is false
-    throw new Error msg || 'Invalid string'
+    throw new Error message || 'Invalid string'

 ##
 # Public function `types.isUnemptyString`.
@@ -163,13 +163,13 @@
 #
 # Throws an exception unless something is a string.
 #
-# @param thing          The thing to test.
-# @param [msg] {string} An optional error message
-#                       to set on the thrown Error.
-#
-verifyString = (thing, msg) ->
+# @param thing              The thing to test.
+# @param [message] {string} An optional error message
+#                           to set on the thrown Error.
+#
+verifyString = (thing, message) ->
   if isString(thing) is false
-    throw new Error msg || 'Invalid string'
+    throw new Error message || 'Invalid string'

 ##
 # Public function `types.isString`.


Only export list and readFile methods

Only export list and readFile methods

--- src/ls-archive.coffee
+++ src/ls-archive.coffee
@@ -1,100 +1,100 @@
 fs = require 'fs'
 path = require 'path'

-module.exports =
-  list: (archivePath, callback) ->
-    switch path.extname(archivePath)
-      when '.tar' then @listTar(archivePath, callback)
-      when '.gz' then @listGzip(archivePath, callback)
-      when '.zip' then @listZip(archivePath, callback)
-      else callback([])
+listZip = (archivePath, callback) ->
+  unzip = require 'unzip'
+  paths = []
+  fileStream = fs.createReadStream(archivePath)
+  zipStream = fileStream.pipe(unzip.Parse())
+  zipStream.on 'entry', (entry) ->
+    paths.push(entry.path)
+    entry.autodrain()
+  zipStream.on 'close', -> callback(paths)

-  listZip: (archivePath, callback) ->
-    unzip = require 'unzip'
-    paths = []
-    fileStream = fs.createReadStream(archivePath)
-    zipStream = fileStream.pipe(unzip.Parse())
-    zipStream.on 'entry', (entry) ->
-      paths.push(entry.path)
-      entry.autodrain()
-    zipStream.on 'close', -> callback(paths)
+listGzip = (archivePath, callback) ->
+  if path.extname(path.basename(archivePath, '.gz')) isnt '.tar'
+    callback([])
+    return

-  listGzip: (archivePath, callback) ->
-    if path.extname(path.basename(archivePath, '.gz')) isnt '.tar'
-      callback([])
-      return
+  zlib = require 'zlib'
+  gzipStream = fs.createReadStream(archivePath).pipe(zlib.createGunzip())
+  readTarStream(gzipStream, callback)

-    zlib = require 'zlib'
-    gzipStream = fs.createReadStream(archivePath).pipe(zlib.createGunzip())
-    @readTarStream(gzipStream, callback)
+listTar = (archivePath, callback) ->
+  fileStream = fs.createReadStream(archivePath)
+  readTarStream(fileStream, callback)

-  listTar: (archivePath, callback) ->
-    fileStream = fs.createReadStream(archivePath)
-    @readTarStream(fileStream, callback)
+readTarStream = (inputStream, callback) ->
+  tar = require 'tar'
+  paths = []
+  tarStream = inputStream.pipe(tar.Parse())
+  tarStream.on 'entry', (entry) -> paths.push(entry.props.path)
+  tarStream.on 'end', -> callback(paths)

-  readTarStream: (inputStream, callback) ->
-    tar = require 'tar'
-    paths = []
-    tarStream = inputStream.pipe(tar.Parse())
-    tarStream.on 'entry', (entry) -> paths.push(entry.props.path)
-    tarStream.on 'end', -> callback(paths)
+createReadStream = (archivePath, callback) ->
+  fileStream = fs.createReadStream(archivePath)
+  fileStream.on 'error', (error) -> callback(error)
+  fileStream

-  createReadStream: (archivePath, callback) ->
-    fileStream = fs.createReadStream(archivePath)
-    fileStream.on 'error', (error) -> callback(error)
-    fileStream
-
-  readFile: (archivePath, filePath, callback) ->
-    switch path.extname(archivePath)
-      when '.tar' then @readFileFromTar(archivePath, filePath, callback)
-      when '.gz' then @readFileFromGzip(archivePath, filePath, callback)
-      when '.zip' then @readFileFromZip(archivePath, filePath, callback)
-
-  readFileFromZip: (archivePath, filePath, callback) ->
-    unzip = require 'unzip'
-    fileStream = @createReadStream(archivePath, callback)
-    filePathFound = false
-    zipStream = fileStream.pipe(unzip.Parse())
-    zipStream.on 'entry', (entry) ->
-      if filePath is entry.path
-        contents = []
-        entry.on 'data', (data) -> contents.push(data)
-        entry.on 'end', ->
-          filePathFound = true
-          callback(null, Buffer.concat(contents).toString())
-      else
-        entry.autodrain()
-    zipStream.on 'close', ->
-      unless filePathFound
-        callback(new Error("#{filePath} does not exist in the archive: #{archivePath}"))
-
-  readFileFromGzip: (archivePath, filePath, callback) ->
-    if path.extname(path.basename(archivePath, '.gz')) isnt '.tar'
-      callback(null, '')
-      return
-
-    zlib = require 'zlib'
-    fileStream = @createReadStream(archivePath, callback)
-    gzipStream = fileStream.pipe(zlib.createGunzip())
-    @readFileFromTarStream(gzipStream, archivePath, filePath, callback)
-
-  readFileFromTar: (archivePath, filePath, callback) ->
-    fileStream = @createReadStream(archivePath, callback)
-    @readFileFromTarStream(fileStream, archivePath, filePath, callback)
-
-  readFileFromTarStream: (inputStream, archivePath, filePath, callback) ->
-    tar = require 'tar'
-    tarStream = inputStream.pipe(tar.Parse())
-
-    filePathFound = false
-    tarStream.on 'entry', (entry) ->
-      return unless filePath is entry.props.path
-
+readFileFromZip = (archivePath, filePath, callback) ->
+  unzip = require 'unzip'
+  fileStream = createReadStream(archivePath, callback)
+  filePathFound = false
+  zipStream = fileStream.pipe(unzip.Parse())
+  zipStream.on 'entry', (entry) ->
+    if filePath is entry.path
       contents = []
       entry.on 'data', (data) -> contents.push(data)
       entry.on 'end', ->
         filePathFound = true
         callback(null, Buffer.concat(contents).toString())
-    tarStream.on 'end', ->
-      unless filePathFound
-        callback(new Error("#{filePath} does not exist in the archive: #{archivePath}"))
+    else
+      entry.autodrain()
+  zipStream.on 'close', ->
+    unless filePathFound
+      callback(new Error("#{filePath} does not exist in the archive: #{archivePath}"))
+
+readFileFromGzip = (archivePath, filePath, callback) ->
+  if path.extname(path.basename(archivePath, '.gz')) isnt '.tar'
+    callback(null, '')
+    return
+
+  zlib = require 'zlib'
+  fileStream = createReadStream(archivePath, callback)
+  gzipStream = fileStream.pipe(zlib.createGunzip())
+  readFileFromTarStream(gzipStream, archivePath, filePath, callback)
+
+readFileFromTar = (archivePath, filePath, callback) ->
+  fileStream = createReadStream(archivePath, callback)
+  readFileFromTarStream(fileStream, archivePath, filePath, callback)
+
+readFileFromTarStream = (inputStream, archivePath, filePath, callback) ->
+  tar = require 'tar'
+  tarStream = inputStream.pipe(tar.Parse())
+
+  filePathFound = false
+  tarStream.on 'entry', (entry) ->
+    return unless filePath is entry.props.path
+
+    contents = []
+    entry.on 'data', (data) -> contents.push(data)
+    entry.on 'end', ->
+      filePathFound = true
+      callback(null, Buffer.concat(contents).toString())
+  tarStream.on 'end', ->
+    unless filePathFound
+      callback(new Error("#{filePath} does not exist in the archive: #{archivePath}"))
+
+module.exports =
+  list: (archivePath, callback) ->
+    switch path.extname(archivePath)
+      when '.tar' then listTar(archivePath, callback)
+      when '.gz' then listGzip(archivePath, callback)
+      when '.zip' then listZip(archivePath, callback)
+      else callback([])
+
+  readFile: (archivePath, filePath, callback) ->
+    switch path.extname(archivePath)
+      when '.tar' then readFileFromTar(archivePath, filePath, callback)
+      when '.gz' then readFileFromGzip(archivePath, filePath, callback)
+      when '.zip' then readFileFromZip(archivePath, filePath, callback)


Fixed head-on collision behaviour

Fixed head-on collision behaviour

--- src/walls.coffee
+++ src/walls.coffee
@@ -35,12 +35,14 @@

     extend: (segment) ->
       if not segment.startpoint.eq(@endpoint)
-        throw Error('IllegalExtendException')
+        console.error("Illegal extend")
+        #throw Error('IllegalExtendException')
       @endpoint = segment.endpoint

     truncate: (segment) ->
       if segment.orientation == @orientation
-        throw Error('SameOrientationTruncateException')
+        console.error("Same orientation truncate")
+        #throw Error('SameOrientationTruncateException')
       if segment.orientation == VERTICAL
         @endpoint.x = segment.startpoint.x
       else
@@ -48,7 +50,8 @@

     intersection_with: (segment) ->
       if segment.orientation == @orientation
-        throw new Error('The segments must have different orientations')
+        console.error "Same orientation intersection check"
+        #throw new Error('The segments must have different orientations')

       intersection = fabric.Intersection.intersectLineLine(@startpoint, @endpoint, segment.startpoint, segment.endpoint)
       return [this, segment, intersection.points[0]] if intersection.status == "Intersection"
@@ -63,7 +66,10 @@
       last_wall = @most_recent_walls[player_no]
       if last_wall? and segment.orientation == last_wall.orientation
         last_wall.extend(segment)
+        console.log "Walls: ", @walls[segment.orientation]
+        @most_recent_walls[player_no] = last_wall
       else
+        console.log "Walls 2: ", @walls[segment.orientation]
         if last_wall?
           last_wall.truncate(segment)
         @most_recent_walls[player_no] = segment
@@ -72,7 +78,7 @@
     removePlayer: (player) ->
       console.log "Removing player!"
       for wall in @allWalls()
-        if wall.player == player and @walls[wall.orientation].indexOf(wall) != -1
+        if wall.player == player
           console.log "Wall gone!"
           @walls[wall.orientation].splice(@walls[wall.orientation].indexOf(wall), 1)

@@ -85,24 +91,19 @@

       walls_to_search = @walls[orientation_to_search]

-      searched = []
+      for wall2 in _.without(@most_recent_walls, segment, null)
+        #console.log "Wall2: ", wall2
+        if segment.orientation == wall2.orientation and segment.player != wall2.player
+          if (segment.orientation == HORIZONTAL and segment.startpoint.y == wall2.startpoint.y)
+            overlap = _.min([_.max([segment.startpoint.x, segment.endpoint.x]), _.max([wall2.startpoint.x, wall2.endpoint.x])]) - _.max([_.min([wall2.startpoint.x, wall2.endpoint.x]), _.min([segment.startpoint.x, segment.endpoint.x])])
+          else if (segment.orientation == VERTICAL and segment.startpoint.x == wall2.startpoint.x)
+            overlap = _.min([_.max([segment.startpoint.y, segment.endpoint.y]), _.max([wall2.startpoint.y, wall2.endpoint.y])]) - _.max([_.min([wall2.startpoint.y, wall2.endpoint.y]), _.min([segment.startpoint.y, segment.endpoint.y])])

-      for wall in _.without(@most_recent_walls, null)
-        #console.log "Wall: ", wall
-        for wall2 in _.without(@most_recent_walls, wall, null, searched...)
-          #console.log "Wall2: ", wall2
-          if wall.orientation == wall2.orientation
-            if (wall.orientation == HORIZONTAL and wall.startpoint.y == wall2.startpoint.y)
-              overlap = _.min([_.max([wall.startpoint.x, wall.endpoint.x]), _.max([wall2.startpoint.x, wall2.endpoint.x])]) - _.max([_.min([wall2.startpoint.x, wall2.endpoint.x]), _.min([wall.startpoint.x, wall.endpoint.x])])
-            else if (wall.orientation == VERTICAL and wall.startpoint.x == wall2.startpoint.x)
-              overlap = _.min([_.max([wall.startpoint.y, wall.endpoint.y]), _.max([wall2.startpoint.y, wall2.endpoint.y])]) - _.max([_.min([wall2.startpoint.y, wall2.endpoint.y]), _.min([wall.startpoint.y, wall.endpoint.y])])
-
-            #console.log "Overlap: ", overlap
-            if overlap > 0
-              collisions.push([wall, wall2, overlap])
-              collisions.push([wall2, wall, overlap])
-
-        searched.push(wall)
+          #console.log "Overlap: ", overlap
+          if overlap > 0
+            collisions.push([segment, wall2, overlap])
+            collisions.push([wall2, segment, overlap])
+            console.log "Collisionsa: ", segment, wall2

       for wall in walls_to_search
         continue if wall.endpoint.x == segment.player.lastPos().x and wall.endpoint.y == segment.player.lastPos().y


y u no work remote

y u no work remote

--- src/server/server.iced
+++ src/server/server.iced
@@ -15,7 +15,7 @@
 responseTime	= require "response-time"
 pkg						= require "../package"
 favicon				= require "serve-favicon"
-#envi          = require "./environment-helper"
+envi          = require "./environment-helper"
 app						= express()

 app.set "port", process.env.PORT or 3000


Move specs for destruction of old element to up.change

--- spec_app/spec/javascripts/up/fragment_spec.js.coffee
+++ spec_app/spec/javascripts/up/fragment_spec.js.coffee
@@ -958,6 +958,134 @@
               expect(text0).toEqual('')
               expect(text1).toEqual('')

+      describe 'destruction of old element', ->
+
+        # up.extract
+        it 'emits an up:fragment:destroyed event on the former parent element after the element has been removed from the DOM', (done) ->
+          $parent = $fixture('.parent')
+          $element = $parent.affix('.element.v1').text('v1')
+          expect($element).toBeAttached()
+
+          spy = jasmine.createSpy('event listener')
+          $parent[0].addEventListener 'up:fragment:destroyed', (event) ->
+            spy(event.target, event.fragment, up.specUtil.isDetached($element))
+
+          extractDone = up.change('.element', document: '
v2
')
+
+          extractDone.then ->
+            expect(spy).toHaveBeenCalledWith($parent[0], $element[0], true)
+            done()
+
+        it 'calls destructors on the old element', asyncSpec (next) ->
+          destructor = jasmine.createSpy('destructor')
+          up.$compiler '.container', ($element) ->
+            -> destructor($element.text())
+          $container = $fixture('.container').text('old text')
+          up.hello($container)
+          up.change('.container', document: '
new text
')
+
+          next =>
+            expect('.container').toHaveText('new text')
+            expect(destructor).toHaveBeenCalledWith('old text')
+
+        it 'calls destructors on the old element after a { transition }', (done) ->
+          destructor = jasmine.createSpy('destructor')
+          up.$compiler '.container', ($element) ->
+            -> destructor($element.text())
+          $container = $fixture('.container').text('old text')
+          up.hello($container)
+
+          up.change('.container', document: '
new text
', transition: 'cross-fade', duration: 100)
+
+          u.timer 50, =>
+            expect(destructor).not.toHaveBeenCalled()
+
+          u.timer 220, =>
+            expect('.container').toHaveText('new text')
+            expect(destructor).toHaveBeenCalledWith('old text')
+            done()
+
+        it 'calls destructors when the replaced element is a singleton element like  (bugfix)', asyncSpec (next) ->
+          # shouldSwapElementsDirectly() is true for body, but can't have the example replace the Jasmine test runner UI
+          up.element.knife.mock('isSingleton').and.callFake (element) -> e.matches(element, '.container')
+          destructor = jasmine.createSpy('destructor')
+          up.$compiler '.container', -> destructor
+          $container = $fixture('.container')
+          up.hello($container)
+          up.change('.container', document: '
new text
')
+
+          next =>
+            expect('.container').toHaveText('new text')
+            expect(destructor).toHaveBeenCalled()
+
+        it 'marks the old element as .up-destroying before destructors', (done) ->
+          destructor = jasmine.createSpy('destructor')
+          up.$compiler '.container', ($element) ->
+            -> destructor($element.text(), $element.is('.up-destroying'))
+          $container = $fixture('.container').text('old text')
+          up.hello($container)
+
+          extractDone = up.change('.container', document: '
new text
')
+
+          extractDone.then ->
+            expect('.container').toHaveText('new text')
+            expect(destructor).toHaveBeenCalledWith('old text', true)
+            done()
+
+        it 'marks the old element as .up-destroying before destructors after a { transition }', (done) ->
+          destructor = jasmine.createSpy('destructor')
+          up.$compiler '.container', ($element) ->
+            -> destructor($element.text(), $element.is('.up-destroying'))
+          $container = $fixture('.container').text('old text')
+          up.hello($container)
+
+          extractDone = up.change('.container',
+            document: '
new text
',
+            transition: 'cross-fade',
+            duration: 100
+          )
+
+          extractDone.then ->
+            expect('.container').toHaveText('new text')
+            expect(destructor).toHaveBeenCalledWith('old text', true)
+            done()
+
+        it 'calls destructors while the element is still attached to the DOM, so destructors see ancestry and events bubble up', asyncSpec (next) ->
+          spy = jasmine.createSpy('parent spy')
+          up.$compiler '.element', ($element) ->
+            return -> spy($element.text(), $element.parent())
+
+          $parent = $fixture('.parent')
+          $element = $parent.affix('.element').text('old text')
+          up.hello($element)
+
+          up.change('.element', document: '
new text
')
+
+          next =>
+            expect(spy).toHaveBeenCalledWith('old text', $parent)
+
+        it 'calls destructors while the element is still attached to the DOM when also using a { transition }', (done) ->
+          spy = jasmine.createSpy('parent spy')
+          up.$compiler '.element', ($element) ->
+            return ->
+              # We must seek .parent in our ancestry, because our direct parent() is an .up-bounds container
+              spy($element.text(), $element.closest('.parent'))
+
+          $parent = $fixture('.parent')
+          $element = $parent.affix('.element').text('old text')
+          up.hello($element)
+
+          extractDone = up.change('.element',
+            document: '
new text
',
+            transition: 'cross-fade',
+            duration: 30
+          )
+
+          extractDone.then ->
+            expect(spy).toHaveBeenCalledWith('old text', $parent)
+            done()
+
+
       describeCapability 'canCustomElements', ->

         describe 'custom elements', ->
@@ -1683,7 +1811,7 @@

     describe 'up.extract', ->

-      it 'Updates a selector on the current page with the same selector from the given HTML string', asyncSpec (next) ->
+      it 'updates a selector on the current page with the same selector from the given HTML string', asyncSpec (next) ->

         $fixture('.before').text('old-before')
         $fixture('.middle').text('old-middle')
@@ -1771,124 +1899,6 @@
           expect(document.activeElement).toBe(input)

-      # up.extract
-      it 'emits an up:fragment:destroyed event on the former parent element after the element has been removed from the DOM', (done) ->
-        $parent = $fixture('.parent')
-        $element = $parent.affix('.element.v1').text('v1')
-        expect($element).toBeAttached()
-
-        spy = jasmine.createSpy('event listener')
-        $parent[0].addEventListener 'up:fragment:destroyed', (event) ->
-          spy(event.target, event.fragment, up.specUtil.isDetached($element))
-
-        extractDone = up.extract('.element', '
v2
')
-
-        extractDone.then ->
-          expect(spy).toHaveBeenCalledWith($parent[0], $element[0], true)
-          done()
-
-      describe 'cleaning up', ->
-
-        it 'calls destructors on the old element', asyncSpec (next) ->
-          destructor = jasmine.createSpy('destructor')
-          up.$compiler '.container', ($element) ->
-            -> destructor($element.text())
-          $container = $fixture('.container').text('old text')
-          up.hello($container)
-          up.extract('.container', '
new text
')
-
-          next =>
-            expect('.container').toHaveText('new text')
-            expect(destructor).toHaveBeenCalledWith('old text')
-
-        it 'calls destructors on the old element after a { transition }', (done) ->
-          destructor = jasmine.createSpy('destructor')
-          up.$compiler '.container', ($element) ->
-            -> destructor($element.text())
-          $container = $fixture('.container').text('old text')
-          up.hello($container)
-
-          up.extract('.container', '
new text
', transition: 'cross-fade', duration: 100)
-
-          u.timer 50, =>
-            expect(destructor).not.toHaveBeenCalled()
-
-          u.timer 220, =>
-            expect('.container').toHaveText('new text')
-            expect(destructor).toHaveBeenCalledWith('old text')
-            done()
-
-        it 'calls destructors when the replaced element is a singleton element like  (bugfix)', asyncSpec (next) ->
-          # shouldSwapElementsDirectly() is true for body, but can't have the example replace the Jasmine test runner UI
-          up.element.knife.mock('isSingleton').and.callFake (element) -> e.matches(element, '.container')
-          destructor = jasmine.createSpy('destructor')
-          up.$compiler '.container', -> destructor
-          $container = $fixture('.container')
-          up.hello($container)
-          up.extract('.container', '
new text
')
-
-          next =>
-            expect('.container').toHaveText('new text')
-            expect(destructor).toHaveBeenCalled()
-
-        it 'marks the old element as .up-destroying before destructors', (done) ->
-          destructor = jasmine.createSpy('destructor')
-          up.$compiler '.container', ($element) ->
-            -> destructor($element.text(), $element.is('.up-destroying'))
-          $container = $fixture('.container').text('old text')
-          up.hello($container)
-
-          extractDone = up.extract('.container', '
new text
')
-
-          extractDone.then ->
-            expect('.container').toHaveText('new text')
-            expect(destructor).toHaveBeenCalledWith('old text', true)
-            done()
-
-        it 'marks the old element as .up-destroying before destructors after a { transition }', (done) ->
-          destructor = jasmine.createSpy('destructor')
-          up.$compiler '.container', ($element) ->
-            -> destructor($element.text(), $element.is('.up-destroying'))
-          $container = $fixture('.container').text('old text')
-          up.hello($container)
-
-          extractDone = up.extract('.container', '
new text
', transition: 'cross-fade', duration: 100)
-
-          extractDone.then ->
-            expect('.container').toHaveText('new text')
-            expect(destructor).toHaveBeenCalledWith('old text', true)
-            done()
-
-        it 'calls destructors while the element is still attached to the DOM, so destructors see ancestry and events bubble up', asyncSpec (next) ->
-          spy = jasmine.createSpy('parent spy')
-          up.$compiler '.element', ($element) ->
-            return -> spy($element.text(), $element.parent())
-
-          $parent = $fixture('.parent')
-          $element = $parent.affix('.element').text('old text')
-          up.hello($element)
-
-          up.extract '.element', '
new text
'
-
-          next =>
-            expect(spy).toHaveBeenCalledWith('old text', $parent)
-
-        it 'calls destructors while the element is still attached to the DOM when also using a { transition }', (done) ->
-          spy = jasmine.createSpy('parent spy')
-          up.$compiler '.element', ($element) ->
-            return ->
-              # We must seek .parent in our ancestry, because our direct parent() is an .up-bounds container
-              spy($element.text(), $element.closest('.parent'))
-
-          $parent = $fixture('.parent')
-          $element = $parent.affix('.element').text('old text')
-          up.hello($element)
-
-          extractDone = up.extract('.element', '
new text
', transition: 'cross-fade', duration: 30)
-
-          extractDone.then ->
-            expect(spy).toHaveBeenCalledWith('old text', $parent)
-            done()

       describe 'with { transition } option', ->


fix reserse to _reserse in TableView.coffee

--- src/intermediate/TableView.coffee
+++ src/intermediate/TableView.coffee
@@ -1,7 +1,7 @@
 class @TableView
   constructor: (@$el, @model) ->
     @_data = @model.getData()
-    @reverse = false
+    @_reverse = false
     @$tbody = @$el.querySelector('.js-tbody')
     @$sortBtns = @$el.querySelectorAll('.js-btn-sort')
     @render()


Add dgeni Filter, replace line-breaks with <br>-Tag (JSON doesn't accpet visuell line-breaks)

--- docs/config/index.coffee
+++ docs/config/index.coffee
@@ -14,14 +14,11 @@
 .processor(require('./processors/pages-data'))

 .config (templateEngine) ->
-
   linebreak =
     name: 'linebreak'
     process:  (string) ->
-      string = string.replace /(?:\r\n|\r|\n)/g, '
'
-
-  console.log templateEngine.filters.push linebreak
-
+      return string.replace /(?:\r\n|\r|\n)/g, '
' if string
+  templateEngine.filters.push linebreak
   return

 .config (log, readFilesProcessor, templateFinder, computePathsProcessor, writeFilesProcessor) ->


Fix typo

Fix typo

--- views/components/settings/parts/plugin-config.cjsx
+++ views/components/settings/parts/plugin-config.cjsx
@@ -18,10 +18,13 @@
   plugin = require filePath
   packageData = {}
   try
-    fs.readJsonSync path.join filePath, 'package.json'
+    packageData = fs.readJsonSync path.join filePath, 'package.json'
   catch error
     if env.process.DEBUG? then console.log error
-  plugin.packageName = packageData.name || plugin.name
+  if packageData?.name?
+    plugin.packageName =  packageData.name
+  else
+    plugin.packageName = plugin.name
   plugin.priority = 10000 unless plugin.priority?
   plugin
 plugins = _.sortBy(plugins, 'priority')


Cancel stopped changing timeout when destroyed

--- src/text-buffer.coffee
+++ src/text-buffer.coffee
@@ -69,6 +69,7 @@

   destroy: ->
     unless @destroyed
+      @cancelStoppedChangingTimeout()
       @file?.off()
       @unsubscribe()
       @destroyed = true
@@ -647,8 +648,11 @@
     else
       text

+  cancelStoppedChangingTimeout: ->
+    clearTimeout(@stoppedChangingTimeout) if @stoppedChangingTimeout
+
   scheduleModifiedEvents: ->
-    clearTimeout(@stoppedChangingTimeout) if @stoppedChangingTimeout
+    @cancelStoppedChangingTimeout()
     stoppedChangingCallback = =>
       @stoppedChangingTimeout = null
       modifiedStatus = @isModified()


:memo: Document `destroyFoldsContainingBufferRange`

...and correct docs for `destroyFoldsIntersectingBufferRange`.

--- src/text-editor.coffee
+++ src/text-editor.coffee
@@ -2774,13 +2774,14 @@
   destroyFoldWithId: (id) ->
     @displayBuffer.destroyFoldWithId(id)

-  # Remove any {Fold}s found that intersect the given buffer row.
+  # Remove any {Fold}s found that intersect the given buffer range.
   destroyFoldsIntersectingBufferRange: (bufferRange) ->
     @destroyFoldsContainingBufferRange(bufferRange)

     for row in [bufferRange.end.row..bufferRange.start.row]
       fold.destroy() for fold in @displayBuffer.foldsStartingAtBufferRow(row)

+  # Remove any {Fold}s found that contain the given buffer range.
   destroyFoldsContainingBufferRange: (bufferRange) ->
     @unfoldBufferRow(bufferRange.start.row)
     @unfoldBufferRow(bufferRange.end.row)


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/text_effect.coffee
+++ src/text_effect.coffee
@@ -20,7 +20,7 @@
 ###

 ###*
-The default draw
+The default <code>draw< method

 @name update
 @methodOf TextEffect#


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/text_effect.coffee
+++ src/text_effect.coffee
@@ -1,5 +1,6 @@
 ###*
-The Text Effect
+The Sprite class provides a way to load images for use in games.
+

 By default, images are loaded asynchronously. A proxy object is
 returned immediately. Even though it has a draw method it will not


add examples of replyAsync usage to the inline doc

--- src/rivescript.coffee
+++ src/rivescript.coffee
@@ -768,6 +768,28 @@
   #
   # Asyncronous version of reply. Use replyAsync if at least one of the subroutines
   # used with <call> tag returns a promise
+  #
+  # Example: using promises
+  #
+  # ```javascript
+  # rs.replyAsync(user, message).then(function(reply) {
+  #   console.log("Bot>", reply);
+  # }).catch(function(error) {
+  #   console.error("Error: ", error);
+  # });
+  # ```
+  #
+  # Example: using the callback
+  #
+  # ```javascript
+  # rs.replyAsync(username, msg, this, function(error, reply) {
+  #   if (!error) {
+  #     console.log("Bot>", reply);
+  #   } else {
+  #     console.error("Error: ", error);
+  #   }
+  # });
+  # ```
   ##
   replyAsync: (user, msg, scope, callback) ->
     reply = @brain.reply(user, msg, scope, true)


clean up element serialiser

clean up element serialiser

--- src/serialiser.coffee
+++ src/serialiser.coffee
@@ -129,9 +129,11 @@
       serialisedChildren[serialisedChildren.length-1] += accumulatedWhitespace
       accumulatedWhitespace = ''

-    prefix = 'React.createElement('
-    prefix += if HTML_ELEMENTS[node.value]? then @domObject+'.' else ''
-    prefix+node.value+', '+serialisedChildren.join(', ')+')'
+    if HTML_ELEMENTS[node.value]?
+      prefix = @domObject+'.'
+    else
+      prefix = ''
+    "React.createElement(#{prefix}#{node.value}, #{serialisedChildren.join(', ')})"

   CJSX_ESC: (node) ->
     childrenSerialised = node.children


Increased speed, damage, cost

Increased speed, damage, cost

--- src/bullet.coffee
+++ src/bullet.coffee
@@ -7,9 +7,9 @@
       x: vx
       y: vy
     @owner = id
-    @speed = 100
-    @damage = 10
-    @cost = 5
+    @speed = 125
+    @damage = 20
+    @cost = 10
     @size = 1

 exports.Bullet = Bullet


Make like shorter

Make like shorter

--- src/bundle.coffee
+++ src/bundle.coffee
@@ -32,7 +32,8 @@
   # Ugh. Replace with .tar.gz, if we can get platform support
   zip = exec "zip -r bundle.zip .", cwd: tmpdir, (err) ->
     if err
-      return callback new Error "zip process exited with code #{err.code}"
+      message = "zip process exited with code #{err.code}"
+      return callback new Error message

     process.nextTick ->
       console.log chalk.grey 'bundle path:', bundlePath


Allow host option to be set via constructor function

--- src/client.coffee
+++ src/client.coffee
@@ -8,7 +8,7 @@
     @_projectId = opts.projectId || 0
     @_projectKey = opts.projectKey || ''

-    @_host = 'https://api.airbrake.io'
+    @_host = opts.host || 'https://api.airbrake.io'

     @_processor = null
     @_reporters = []


Check for errors. Fixes #14

Check for errors. Fixes #14

--- src/client.coffee
+++ src/client.coffee
@@ -65,7 +65,7 @@
             @reconnect()

     _onInitialLoad: (data, headers) =>
-        if data
+        if data && not data.error
             @teams = data.teams
             @logger.debug 'Found '+Object.keys(@teams).length+' teams.'
             for t in @teams
@@ -83,7 +83,7 @@
             @emit 'error', { msg: 'failed to load teams.' }

     _onProfiles: (data, headers) =>
-        if data
+        if data && not data.error
             @users = data
             @logger.debug 'Found '+Object.keys(@users).length+' profiles.'
             @emit 'profilesLoaded', { profiles: @users }
@@ -92,7 +92,7 @@
             @emit 'error', { msg: 'failed to load profiles'}

     _onChannels: (data, headers) =>
-        if data
+        if data && not data.error
             @channels = data.members
             @logger.debug 'Found '+Object.keys(@channels).length+' channels.'
             @channel_details = data.channels


Fix for associated resources needing queries to instantiate

--- src/client.coffee
+++ src/client.coffee
@@ -54,24 +54,16 @@
   create_directory: (mappings, constructors) ->
     for name, mapping of mappings
       do (name, mapping) =>
-        {url, path, template} = mapping
-        # TODO: use a JSON schema for this sort of thing
-        if !(url || path || template)
+        {url, query} = mapping
+        if !(url || query)
           throw new Error "Mapping lacks a url, path, or template field"
-        if constructor = constructors[mapping.resource]
-          if mapping.url && !mapping.query
-            # The API has provided a URL for this resource, so we do not have to
-            # generate it.  This is the expected case for directories coming
-            # from a Patchboard Server.
-            url = mapping.url
-            @resources[name] = new constructor(null, url: url)
-
-          else if mapping.path && !mapping.query
-            url = @generate_url(mapping)
-            @resources[name] = new constructor(null, url: url)
-          else
-            @resources[name] = (params={}) ->
-              new constructor(params)
+        if constructor = constructors[name]
+          if mapping.url
+            if mapping.query
+              @resources[name] = (params={}) ->
+                new constructor(params)
+            else
+              @resources[name] = new constructor(null, url: mapping.url)
         else
           throw new Error "No constructor for '#{name}'"

@@ -82,6 +74,9 @@
   generate_url: (mapping, params) ->
     url = @api.service_url
     if template = mapping.template
+      # this should never be needed when the API is served by a
+      # Patchboard Server.  Including it for client-side only
+      # uses, such as the GitHub API.
       parts = template.split("/")
       out = []
       for part in parts
@@ -96,6 +91,7 @@
           out.push(part)
       path = out.join("/")
     else if mapping.path
+      # Ditto above comment.
       path = mapping.path
     else if mapping.url
       url = mapping.url
@@ -143,14 +139,21 @@
   resource_constructor: ({type, mapping, definition}) ->
     client = @
     constructor = (params, data={}) ->
-      if mapping && params && !data.url
-        data.url = client.generate_url(mapping, params)
+
+      if mapping
+        {url, path, template, query} = mapping
+        url ||= data.url
+        data.url = client.generate_url({url, path, template, query}, params)
+
       for key, value of data
         @[key] = value
       return @

     constructor.prototype._actions = {}
     constructor.prototype.resource_type = type
+    if mapping?.query
+      constructor.query = mapping.query
+
     # Hide the Patchboard client from such things as console.log
     Object.defineProperty constructor.prototype, "patchboard_client",
       value: @
@@ -195,7 +198,20 @@
   decorate: (schema, data) ->
     if name = schema.id?.split("#")[1]
       if constructor = @resource_constructors[name]
-        data = new constructor(null, data)
+        d = data
+        if constructor.query
+          # Some resources require query parameters to instantiate.
+          # For these, we've stuck the query definition onto the
+          # constructor.  For these cases, we substitute a simple
+          # function for the property.
+          # In usage, this looks like:
+          #   user.repository(name: "patchboard").update(content, callback)
+          # TODO: this approach short circuits any further decoration
+          # of resources inside the present resource.  Try to fix.
+          data = (params) ->
+            new constructor params, d
+        else
+          data = new constructor(null, d)
     return @_decorate(schema, data) || data


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/color2.coffee
+++ src/color2.coffee
@@ -140,7 +140,7 @@
         hue = saturation = 0
       else
         delta = max - min
-        saturation = (if lightness > 0.5 then delta / (2 - max - min) else delta / (max + min))
+        saturation = (if lightness > 0.5 then delta / (2 - delta) else delta / (max + min))

         switch max
           when @r then hue = (@g - @b) / delta + (if @g < @b then 6 else 0)


Update node version to v0.11.10.

Update node version to v0.11.10.

--- src/config.coffee
+++ src/config.coffee
@@ -24,7 +24,7 @@
     process.env.ATOM_PACKAGES_URL ? 'https://www.atom.io/api/packages'

   getNodeVersion: ->
-    process.env.ATOM_NODE_VERSION ? '0.11.9'
+    process.env.ATOM_NODE_VERSION ? '0.11.10'

   getNodeArch: ->
     if process.platform is 'darwin' then 'x64' else 'ia32'


fix default config

fix default config

--- src/config.coffee
+++ src/config.coffee
@@ -1,3 +1,3 @@
 define (require) ->
-  require 'settings'
+  settings = require 'settings'
   console.warn 'using default configuration'


Pass a message to the errors thrown by validators

--- src/config.coffee
+++ src/config.coffee
@@ -402,13 +402,13 @@
   'integer':
     coercion: (value, schema) ->
       value = parseInt(value)
-      throw new Error() if isNaN(value)
+      throw new Error('Value cannot be coerced into an int') if isNaN(value)
       value

   'number':
     coercion: (value, schema) ->
       value = parseFloat(value)
-      throw new Error() if isNaN(value)
+      throw new Error('Value cannot be coerced into a number') if isNaN(value)
       value

   'boolean':
@@ -421,18 +421,18 @@

   'string':
     coercion: (value, schema) ->
-      throw new Error() if typeof value isnt 'string'
+      throw new Error('Value must be a string') if typeof value isnt 'string'
       value

   'null':
     # null sort of isnt supported. It will just unset in this case
     coercion: (value, schema) ->
-      throw new Error() unless value == null
+      throw new Error('Value must be an object') unless value == null
       value

   'object':
     coercion: (value, schema) ->
-      throw new Error() if typeof value isnt 'object'
+      throw new Error('Value must be an object') if typeof value isnt 'object'
       return value unless schema.properties?
       for prop, childSchema of schema.properties
         value[prop] = @executeSchemaValidators(value[prop], childSchema) if prop of value
@@ -440,7 +440,7 @@

   'array':
     coercion: (value, schema) ->
-      throw new Error() unless Array.isArray(value)
+      throw new Error('Value must be an array') unless Array.isArray(value)
       itemSchema = schema.items
       if itemSchema?
         @executeSchemaValidators(item, itemSchema) for item in value