:art:

:art:

--- src/patch.coffee
+++ src/patch.coffee
@@ -1,4 +1,5 @@
 Point = require "./point"
+last = (array) -> array[array.length - 1]

 BRANCHING_THRESHOLD = 3

@@ -6,25 +7,16 @@
   constructor: (@children) ->
     @calculateExtent()

-  splice: (childIndex, newChildren, extentToCut) ->
-    oldChild = @children[childIndex]
-
-    if newChildren?
-      @children.splice(childIndex, 1, newChildren...)
-      childIndex = @children.indexOf(oldChild)
-
-    if previousChild = @children[childIndex - 1]
-      if previousChild.shouldMergeRightNeighbor(@children[childIndex])
-        @children[childIndex].mergeLeftNeighbor(previousChild)
-        @children.splice(childIndex - 1, 1)
-        childIndex--
+  splice: (childIndex, splitChildren) ->
+    spliceChild = @children[childIndex]
+
+    if splitChildren?
+      @children.splice(childIndex, 1, splitChildren...)
+      childIndex += splitChildren.indexOf(spliceChild)

     i = childIndex
-    while i < @children.length - 1
-      child = @children[i]
-      nextChild = @children[i + 1]
-      if child.shouldMergeRightNeighbor(nextChild)
-        child.mergeRightNeighbor(nextChild)
+    while (child = @children[i]) and (nextChild = @children[i + 1])
+      if child.merge(nextChild)
         @children.splice(i + 1, 1)
       else
         i++
@@ -38,26 +30,38 @@
         childIndex -= splitIndex

     {inputOffset, outputOffset} = @calculateExtent(childIndex)
-    {splitNodes, inputOffset, outputOffset, extentToCut, childIndex}
+    {splitNodes, inputOffset, outputOffset, childIndex}

   cut: (extentToCut, cutIndex=0) ->
-    originalExtentToCut = extentToCut
-
     inputCut = Point.zero()
-    while (child = @children[cutIndex]) and extentToCut.isPositive()
+    totalExtentToCut = extentToCut
+    while extentToCut.isPositive() and (child = @children[cutIndex])
       if extentToCut.compare(child.outputExtent) >= 0
         @children.splice(cutIndex, 1)
         inputCut = inputCut.traverse(child.inputExtent)
         extentToCut = extentToCut.traversalFrom(child.outputExtent)
       else
-        {extentToCut, inputCut: childInputCut} = child.cut(extentToCut, 0)
-        inputCut = inputCut.traverse(childInputCut)
-
+        result = child.cut(extentToCut, 0)
+        inputCut = inputCut.traverse(result.inputCut)
+        extentToCut = result.extentToCut
     if cutIndex is 0
       @inputExtent = @inputExtent.traversalFrom(inputCut)
-      @outputExtent = @outputExtent.traversalFrom(originalExtentToCut).sanitizeNegatives()
-
+      @outputExtent = @outputExtent.traversalFrom(totalExtentToCut)
     {extentToCut, inputCut}
+
+  merge: (rightNeighbor) ->
+    totalChildCount = @children.length + rightNeighbor.children.length
+    shouldMerge =
+      (totalChildCount <= BRANCHING_THRESHOLD) or
+      ((totalChildCount is BRANCHING_THRESHOLD + 1) and
+        last(@children).merge(rightNeighbor.children[0]) and
+        rightNeighbor.children.shift())
+
+    if shouldMerge
+      @inputExtent = @inputExtent.traverse(rightNeighbor.inputExtent)
+      @outputExtent = @outputExtent.traverse(rightNeighbor.outputExtent)
+      @children.push(rightNeighbor.children...)
+      true

   calculateExtent: (childIndex) ->
     result = {inputOffset: null, outputOffset: null}
@@ -71,24 +75,6 @@
       @outputExtent = @outputExtent.traverse(child.outputExtent)
     result

-  mergeLeftNeighbor: (leftNeighbor) ->
-    leftNeighbor.mergeRightNeighbor(this)
-    {@children, @inputExtent, @outputExtent} = leftNeighbor
-
-  mergeRightNeighbor: (rightNeighbor) ->
-    if last(@children).shouldMergeRightNeighbor(rightNeighbor.children[0])
-      last(@children).mergeRightNeighbor(rightNeighbor.children[0])
-      rightNeighbor.children.shift()
-    @outputExtent = @outputExtent.traverse(rightNeighbor.outputExtent)
-    @inputExtent = @inputExtent.traverse(rightNeighbor.inputExtent)
-    @children.push(rightNeighbor.children...)
-
-  shouldMergeRightNeighbor: (rightNeighbor) ->
-    newChildCount = @children.length + rightNeighbor.children.length
-    if @children[@children.length - 1].shouldMergeRightNeighbor(rightNeighbor.children[0])
-      newChildCount--
-    newChildCount <= BRANCHING_THRESHOLD
-
   toString: (indentLevel=0) ->
     indent = ""
     indent += " " for i in [0...indentLevel] by 1
@@ -102,63 +88,57 @@
   constructor: (@inputExtent, @outputExtent, @content) ->

   splice: (outputOffset, spliceOldExtent, spliceNewExtent, spliceContent) ->
+    splitNodes = null
     spliceOldEnd = outputOffset.traverse(spliceOldExtent)
     extentAfterSplice = @outputExtent.traversalFrom(spliceOldEnd).sanitizeNegatives()

-    if @content? or spliceContent is ""
+    if @content? or spliceNewExtent.isZero()
       @content = @content.slice(0, outputOffset.column) +
         spliceContent +
         @content.slice(spliceOldEnd.column) if @content?
       @outputExtent = outputOffset
         .traverse(spliceNewExtent)
         .traverse(extentAfterSplice)
-
-      splitNodes = null
       outputOffset = outputOffset.traverse(spliceNewExtent)
       inputOffset = Point.min(outputOffset, @inputExtent)
+
     else
       splitNodes = []
       if outputOffset.isPositive()
         splitNodes.push(new Leaf(outputOffset, outputOffset, null))
         @inputExtent = @inputExtent.traversalFrom(outputOffset).sanitizeNegatives()
+      @content = spliceContent
+      @outputExtent = spliceNewExtent
       splitNodes.push(this)
       if extentAfterSplice.isPositive()
         splitNodes.push(new Leaf(extentAfterSplice, extentAfterSplice, null))
         @inputExtent = spliceOldExtent
-
-      @content = spliceContent if spliceContent.length > 0
-      @outputExtent = spliceNewExtent
       outputOffset = @outputExtent
-      inputOffset = Point.min(outputOffset, @inputExtent)
+      inputOffset = @inputExtent

     {splitNodes, inputOffset, outputOffset}

   cut: (extentToCut) ->
+    inputCut = Point.min(extentToCut, @inputExtent)
     @content = @content?.slice(extentToCut.column) ? null
     @outputExtent = @outputExtent.traversalFrom(extentToCut)
-    if extentToCut.compare(@inputExtent) >= 0
-      inputCut = @inputExtent
-      @inputExtent = Point.zero()
-    else
-      inputCut = extentToCut
-      @inputExtent = @inputExtent.traversalFrom(extentToCut)
+    @inputExtent = @inputExtent.traversalFrom(inputCut)
     {inputCut, extentToCut: Point.zero()}

-  mergeLeftNeighbor: (leftNeighbor) ->
-    leftNeighbor.mergeRightNeighbor(this)
-    {@content, @outputExtent, @inputExtent} = leftNeighbor
-
-  mergeRightNeighbor: (rightNeighbor) ->
-    if @outputExtent.isZero()
-      @content = null
-    @content = @content + rightNeighbor.content if @content? and rightNeighbor.content?
-    @outputExtent = @outputExtent.traverse(rightNeighbor.outputExtent)
-    @inputExtent = @inputExtent.traverse(rightNeighbor.inputExtent)
-
-  shouldMergeRightNeighbor: (rightNeighbor) ->
-    (@inputExtent.isZero() and @outputExtent.isZero()) or
-      (rightNeighbor.inputExtent.isZero() and rightNeighbor.outputExtent.isZero()) or
-      (@content? is rightNeighbor.content?)
+  merge: (rightNeighbor) ->
+    shouldMerge =
+      (@content? is rightNeighbor.content?) or
+      (@inputExtent.isZero() and @outputExtent.isZero()) or
+      (rightNeighbor.inputExtent.isZero() and rightNeighbor.outputExtent.isZero())
+
+    if shouldMerge
+      @content = null if @outputExtent.isZero()
+      rightNeighbor.content = null if rightNeighbor.outputExtent.isZero()
+      if @content? and rightNeighbor.content?
+        @content = @content + rightNeighbor.content
+      @outputExtent = @outputExtent.traverse(rightNeighbor.outputExtent)
+      @inputExtent = @inputExtent.traverse(rightNeighbor.inputExtent)
+      true

   toString: (indentLevel=0) ->
     indent = ""
@@ -169,32 +149,23 @@
     else
       "#{indent}Leaf #{@inputExtent} #{@outputExtent}"

-last = (array) -> array[array.length - 1]
-
 class PatchIterator
   constructor: (@patch) ->
-    @stack = []
+    @path = []
     @descendToLeftmostLeaf(@patch.rootNode)

-  toString: ->
-    "[PatchIterator\n" +
-      @stack
-        .map ({node, inputOffset, outputOffset, childIndex}) ->
-          "  inputOffset:#{inputOffset}, outputOffset:#{outputOffset}, childIndex:#{childIndex}"
-        .join("\n") + "]"
-
   next: ->
-    while (entry = last(@stack)) and (entry.outputOffset.compare(entry.node.outputExtent) is 0)
-      @stack.pop()
-      if parentEntry = last(@stack)
+    while (entry = last(@path)) and (entry.outputOffset.compare(entry.node.outputExtent) is 0)
+      @path.pop()
+      if parentEntry = last(@path)
         parentEntry.childIndex++
         parentEntry.inputOffset = parentEntry.inputOffset.traverse(entry.inputOffset)
         parentEntry.outputOffset = parentEntry.outputOffset.traverse(entry.outputOffset)
         if nextChild = parentEntry.node.children[parentEntry.childIndex]
           @descendToLeftmostLeaf(nextChild)
-          entry = last(@stack)
-      else
-        @stack.push(entry)
+          entry = last(@path)
+      else
+        @path.push(entry)
         return {value: null, done: true}

     value = entry.node.content?.slice(entry.outputOffset.column) ? null
@@ -203,7 +174,7 @@
     {value, done: false}

   seek: (targetOutputOffset) ->
-    @stack.length = 0
+    @path.length = 0

     childInputStart = Point.zero()
     childOutputStart = Point.zero()
@@ -221,7 +192,7 @@
           if childOutputEnd.compare(targetOutputOffset) >= 0
             inputOffset = childInputStart
             outputOffset = childOutputStart
-            @stack.push({node, childIndex, inputOffset, outputOffset})
+            @path.push({node, childIndex, inputOffset, outputOffset})
             targetOutputOffset = targetOutputOffset.traversalFrom(childOutputStart)
             node = child
             break
@@ -229,64 +200,81 @@
         inputOffset = Point.min(targetOutputOffset, node.inputExtent)
         outputOffset = targetOutputOffset
         childIndex = null
-        @stack.push({node, inputOffset, outputOffset, childIndex})
+        @path.push({node, inputOffset, outputOffset, childIndex})
         node = null

     this

   splice: (oldOutputExtent, newOutputExtent, newContent) ->
-    newStack = []
+    newPath = []
     splitNodes = null
     extentToCut = null
     previousNode = null

-    for {node, outputOffset, childIndex} in @stack by -1
+    for {node, outputOffset, childIndex} in @path by -1
       if node instanceof Leaf
+
+        # Determine how far the splice extends beyond this leaf. Subsequent
+        # nodes will need to be shrunk or removed to make room for the splice.
         extentToCut = outputOffset
           .traverse(oldOutputExtent)
           .traversalFrom(node.outputExtent)
-          .sanitizeNegatives()
-
-        {splitNodes, inputOffset, outputOffset} =
-          node.splice(outputOffset, oldOutputExtent, newOutputExtent, newContent)
-      else
-        if extentToCut.isPositive()
-          {extentToCut, inputCut} = node.cut(extentToCut, childIndex + 1)
-          for newEntry, i in newStack
-            newEntry.node.inputExtent = newEntry.node.inputExtent.traverse(inputCut)
-
-        {splitNodes, inputOffset, outputOffset, childIndex} =
-          node.splice(childIndex, splitNodes, extentToCut)
-
-      newStack.unshift({node, inputOffset, outputOffset, childIndex})
+
+        # Insert the new content into the leaf.
+        {splitNodes, inputOffset, outputOffset} = node.splice(
+          outputOffset,
+          oldOutputExtent,
+          newOutputExtent,
+          newContent
+        )
+      else
+
+        # Shrink or remove any subsequent nodes that fall within the splice,
+        # transferring the removed input extent onto the spliced nodes.
+        {extentToCut, inputCut} = node.cut(extentToCut, childIndex + 1)
+        for entry in newPath
+          entry.node.inputExtent = entry.node.inputExtent.traverse(inputCut)
+
+        # If the spliced child node has split, insert the split-off children.
+        {splitNodes, inputOffset, outputOffset, childIndex} = node.splice(
+          childIndex,
+          splitNodes
+        )
+
       previousNode = node
-
+      newPath.unshift({node, inputOffset, outputOffset, childIndex})
+
+    # If the root node has split, create a new root node and add an entry
+    # for it at the beginning of the iterator's path.
     if splitNodes?
-      node = new Node([previousNode])
-      {inputOffset, outputOffset, childIndex} = node.splice(0, splitNodes, Point.zero())
-      newStack.unshift({node, inputOffset, outputOffset, childIndex})
-      @patch.rootNode = node
-
-    @stack = newStack
-
+      node = @patch.rootNode = new Node([previousNode])
+      {inputOffset, outputOffset, childIndex} = node.splice(0, splitNodes)
+      newPath.unshift({node, inputOffset, outputOffset, childIndex})
+
+    # If the root node's children have all merged, remove the root node.
     while @patch.rootNode.children?.length is 1
       @patch.rootNode = @patch.rootNode.children[0]
-      @stack.shift()
-
-    leafEntry = last(@stack)
-    leafEntry.inputOffset = Point.min(leafEntry.outputOffset, leafEntry.node.inputExtent)
-
+      newPath.shift()
+
+    # Adjust the input offset into the leaf node, since its input-extent may have been
+    # updated as subsequent nodes within the slice were cut.
+    leafEntry = last(newPath)
+    leafEntry.inputOffset = Point.max(
+      leafEntry.inputOffset,
+      Point.min(leafEntry.outputOffset, leafEntry.node.inputExtent)
+    )
+    @path = newPath
     return

   getOutputPosition: ->
     result = Point.zero()
-    for entry in @stack
+    for entry in @path
       result = result.traverse(entry.outputOffset)
     result

   getInputPosition: ->
     result = Point.zero()
-    for {node, inputOffset, outputOffset} in @stack
+    for {node, inputOffset, outputOffset} in @path
       if node instanceof Leaf and outputOffset.isEqual(node.outputExtent)
         result = result.traverse(node.inputExtent)
       else
@@ -294,14 +282,19 @@
     result

   descendToLeftmostLeaf: (node) ->
-    while node
+    loop
       entry = {node, outputOffset: Point.zero(), inputOffset: Point.zero(), childIndex: null}
-      @stack.push(entry)
+      @path.push(entry)
       if node.children?
         entry.childIndex = 0
         node = node.children[0]
       else
-        node = null
+        break
+
+  toString: ->
+    entries = for {node, inputOffset, outputOffset, childIndex} in @path
+      "  {inputOffset:#{inputOffset}, outputOffset:#{outputOffset}, childIndex:#{childIndex}}"
+    "[PatchIterator\n#{entries.join("\n")}]"

 module.exports =
 class Patch


if options was not setted, use default value.

--- coffee/controllers/optionCtrl.coffee
+++ coffee/controllers/optionCtrl.coffee
@@ -1,13 +1,14 @@
 timeTracker.controller 'OptionCtrl', ($scope, $timeout, $message, option, analytics) ->

-  $scope.options = { reportUsage: true }
+  DEFAULT_OPTION = { reportUsage: true }
+  $scope.options = {}

   ###
    restore option, and start watching options.
   ###
   option.getOptions (options) ->
-    $scope.options = options or {}
+    $scope.options = options or DEFAULT_OPTION
     analytics.setPermittion $scope.options.reportUsage

     # start watch changing.


Minor.

Minor.

--- src/local.coffee
+++ src/local.coffee
@@ -137,7 +137,7 @@
         buf = new Buffer(10)
         buf.write "\u0005\u0000\u0000\u0001", 0, 4, "binary"
         buf.write "\u0000\u0000\u0000\u0000", 4, 4, "binary"
-        buf.writeInt16BE remotePort, 8
+        buf.writeUInt16BE remotePort, 8
         connection.write buf
         # connect to remote server
         # ws = new WebSocket aServer, protocol: "binary"


make local.coffee more coffeescriptish

make local.coffee more coffeescriptish

--- src/local.coffee
+++ src/local.coffee
@@ -2,15 +2,15 @@

 Spine.Model.Local =
   extended: ->
-    this.change(this.saveLocal)
-    this.fetch(this.loadLocal)
+    @change @saveLocal
+    @fetch @loadLocal

   saveLocal: ->
-    result = JSON.stringify(this)
+    result = JSON.stringify(@)
     localStorage[@className] = result

   loadLocal: ->
     result = localStorage[@className]
-    this.refresh(result or [], clear: true)
+    @refresh(result or [], clear: true)

 module?.exports = Spine.Model.Local


Try get audit api tests to pass on travis

--- test/integration/auditAPITests.coffee
+++ test/integration/auditAPITests.coffee
@@ -222,7 +222,7 @@
                       newAudits[1].participantObjectIdentification.length.should.be.exactly 1
                       newAudits[1].participantObjectIdentification[0].participantObjectID.should.be.exactly "https://localhost:8080/audits/#{result._id}"
                     done()
-                , 10 * global.testTimeoutFactor
+                , 100 * global.testTimeoutFactor

       it "should NOT generate an 'audit log used' audit when using basic (default) representation", (done) ->
         audit = new Audit auditData
@@ -328,7 +328,7 @@
                       newAudits[1].participantObjectIdentification.length.should.be.exactly 1
                       newAudits[1].participantObjectIdentification[0].participantObjectID.should.be.exactly "https://localhost:8080/audits/#{result._id}"
                     done()
-                , 10 * global.testTimeoutFactor
+                , 100 * global.testTimeoutFactor

     describe "*getAuditsFilterOptions", ->


point

point

--- src/point.coffee
+++ src/point.coffee
@@ -274,26 +274,25 @@
     treats the point as though it is a vector from the origin to (x, y).

-       point = Point(2, 3).norm()
-
-       point.x
-    => 0.5547001962252291
-
-       point.y
-    => 0.8320502943378437
-
-       anotherPoint = Point(2, 3).norm(2)
-
-       anotherPoint.x
-    => 1.1094003924504583
-
-       anotherPoint.y
-    => 1.6641005886756874
+    point = Point(2, 3).norm()
+
+    point.x
+    # => 0.5547001962252291
+
+    point.y
+    # => 0.8320502943378437
+
+    anotherPoint = Point(2, 3).norm(2)
+
+    anotherPoint.x
+    # => 1.1094003924504583
+
+    anotherPoint.y
+    # => 1.6641005886756874

     @name norm
     @methodOf Point#
-
     @returns {Point} The unit vector pointing in the same direction as this vector.
     ###
     norm: (length=1.0) ->


Modify version to 0.1.3

Modify version to 0.1.3

--- lib/js2coffee.coffee
+++ lib/js2coffee.coffee
@@ -782,7 +782,7 @@
 # ## Exports

 @Js2coffee = exports =
-  version: '0.1.2'
+  version: '0.1.3'
   build: buildCoffee
   UnsupportedError: UnsupportedError


Adding parent check for text node comparison. per https://github.com/openannotation/xpath-range/issues/3

--- src/range.coffee
+++ src/range.coffee
@@ -379,15 +379,21 @@
     # commonAncestorContainer and bail out.

     contains =
-      if not document.compareDocumentPosition?
-        # IE
+      if document.compareDocumentPosition?
+        # Everyone else
+        (a, b) -> a.compareDocumentPosition(b) & Node.DOCUMENT_POSITION_CONTAINED_BY
+
+      else
+        # Newer IE
         (a, b) -> a.contains(b)
+
+    $(range.startContainer).parents().each ->
+      if range.endContainer.nodeType == Node.TEXT_NODE
+        endContainer = range.endContainer.parentNode
       else
-        # Everyone else
-        (a, b) -> a.compareDocumentPosition(b) & 16
-
-    $(range.startContainer).parents().each ->
-      if contains(this, range.endContainer)
+        endContainer = range.endContainer
+
+      if contains(this, endContainer)
         range.commonAncestorContainer = this
         return false


SQL support

SQL support

--- lib/languages.coffee
+++ lib/languages.coffee
@@ -100,6 +100,11 @@
     pygmentsLexer:     'sh'
     singleLineComment: ['#']

+  SQL:
+    nameMatchers:      ['.sql']
+    pygmentsLexer:     'sql'
+    singleLineComment: ['--']
+
   YAML:
     nameMatchers:      ['.yml', '.yaml']
     pygmentsLexer:     'yaml'


Removes url conversion.

Removes url conversion.

--- src/slack.coffee
+++ src/slack.coffee
@@ -74,10 +74,6 @@
       hubot_msg = req.param('text')

       if hubot_msg
-        # Make links back into html
-        hubot_msg = hubot_msg.replace(/<((\bhttp)[^|]+)(\|(.*))+>/g, '$4')
-        hubot_msg = hubot_msg.replace(/<((\bhttp)(.*))?>/g, '$1')
-
         # Unescape
         hubot_msg = hubot_msg.replace(/&/g, '&')
         hubot_msg = hubot_msg.replace(/</g, '<')
@@ -162,4 +158,4 @@
         callback err

 exports.use = (robot) ->
-  new Slack robot+  new Slack robot


Default to array instead of object

Default to array instead of object

--- src/stars.coffee
+++ src/stars.coffee
@@ -45,7 +45,7 @@
         @requestStarredPackages(requestSettings, callback)

   requestStarredPackages: (requestSettings, callback) ->
-    request.get requestSettings, (error, response, body={}) ->
+    request.get requestSettings, (error, response, body=[]) ->
       if error?
         callback(error)
       else if response.statusCode is 200


Remove unused cloneDeep function

Remove unused cloneDeep function

--- src/store.coffee
+++ src/store.coffee
@@ -25,31 +25,6 @@
 #			# Do things..
 #			@changed.dispatch()
 ###
-
-cloneDeep = (obj) ->
-	return obj unless _.isObject(obj) or _.isArray(obj)
-	if window?
-		return obj if obj instanceof window.Element # Don't clone DOMnodes
-	if obj instanceof Date
-		return new Date(obj.getTime())
-	newObj = null
-	if _.isObject(obj) and not _.isArray(obj)
-		newObj = {}
-		if obj.clone? and typeof obj.clone is 'function'
-			newObj = obj.clone()
-		else
-			for own key, val of obj
-				if _.isObject(val) or _.isArray(val)
-					newObj[key] = cloneDeep(val)
-				else
-					newObj[key] = val
-	else
-		newObj = []
-		if obj.clone? and typeof obj.clone is 'function'
-			newObj = obj.clone()
-		else
-			newObj.push cloneDeep(val) for val in obj
-	return newObj

 module.exports = class Store


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/tween.coffee
+++ src/tween.coffee
@@ -14,7 +14,7 @@
   # Add events and methods here
   self.bind "update", ->
     for property, data of I.activeTweens
-      I[property] = Easing[data.]
+      I[property] = Easing[data.easing]

   tween: (duration, properties) ->
     for property, target of properties


Update comment.

Update comment.

--- src/types.coffee
+++ src/types.coffee
@@ -149,7 +149,8 @@
 ##
 # Public function `types.isUnemptyString`.
 #
-# Throws an exception unless something is a non-empty string.
+# Returns `true` if something is a non-empty string, `false`
+# otherwise.
 #
 # @param thing          The thing to test.
 #


1) Cleaned up unify.coffee a bit

1) Cleaned up unify.coffee a bit

--- src/unify.coffee
+++ src/unify.coffee
@@ -1,11 +1,8 @@
 # utils
-log=(o)->console.log o
-dir=(o)->console.dir o
-len=(o)-> o.length
-if typeof exports == 'undefined' then window.JSUnify={}
-extern=(name, o)->if typeof exports == 'undefined' then window.JSUnify[name] = o else exports[name] = o
-if typeof exports == 'undefined' then window.JSUnify.internal={} else exports.internal = {}
-internal=(name, o)->if typeof exports == 'undefined' then window.JSUnify.internal[name] = o else exports.internal[name] = o
+if typeof module == 'undefined' then window.JSUnify={}
+extern=(name, o)->if typeof module == 'undefined' then window.JSUnify[name] = o else module.exports[name] = o
+if typeof module == 'undefined' then window.JSUnify.internal={} else module.exports.internal = {}
+internal=(name, o)->if typeof module == 'undefined' then window.JSUnify.internal[name] = o else module.exports.internal[name] = o
 str=(o)->
     if typeof o == "undefined"
         return "undefined"
@@ -13,11 +10,6 @@
         return "null"
     else
        return o.toString()
-mergeJson=()->
-    ret = {}
-    for arg in arguments
-        ret[name] = value for name, value of arg
-    return ret

 # type testing functions
 isundef=(o) -> typeof o == "undefined"
@@ -66,8 +58,7 @@
         else
             @name = name
     isHiddenVar: () -> isHiddenVar @name
-    toString: () -> "Var(#{ toJson @name })"
-Var=(name)->new Variable(name)
+    toString: () -> "variable(#{ toJson @name })"

 class Tin
     constructor: (name, node, varlist) ->
@@ -79,37 +70,32 @@
         t = this
         t = t.varlist while t.varlist instanceof Tin
         return t
-#    isfree:()->!@node?
     isfree: () ->
         t = @end_of_chain()
         return t.node == null and t.varlist == null
     isHiddenVar: () -> isHiddenVar @name
     toString:() -> "new Tin(#{ toJson @name }, #{ toJson @node }, #{ toJson @varlist})"
-
     get: (var_name) ->
         vartin = @varlist[var_name]
         if vartin != null and vartin != undefined
             vartin = vartin.end_of_chain()
-
         if not vartin?
             throw "Variable #{var_name} not in this tin"
         else if not vartin.node? or vartin.node == null
-            return new Var(vartin.name)
+            return new Variable(vartin.name)
         else if vartin.node instanceof Box
             return unboxit(vartin.node,vartin.varlist)
-        else if vartin.node instanceof Var
+        else if vartin.node instanceof Variable
             return unboxit(vartin.node,vartin.varlist)
         else if isarray(vartin.node)
             return ( unboxit(n,vartin.varlist) for n in vartin.node )
         else
             throw "Unknown type in get"
-
     get_all: () ->
         j = {}
         for key of @varlist
             j[key] = @get(key) if !isHiddenVar key
         return j
-
     unparse: () ->
         unboxit @node

@@ -146,8 +132,6 @@
         return tree.value
     else if tree instanceof Variable
         if varlist != undefined
-            # log varlist
-            # log tree
             try
                 tin = get_tin(varlist,tree)
             catch error # Is unbound
@@ -165,7 +149,7 @@
     return new Tin( null, tree, tinlist )

 get_tin = (varlist,node) ->
-    throw "Node must be a Var to get_tin" if not node instanceof Variable
+    throw "Node must be a Variable to get_tin" if not node instanceof Variable
     return varlist[node.name] if varlist?[node.name]?
     throw "Couldn't find node #{node.name} in varlist #{varlist}"

@@ -175,6 +159,7 @@
     t.node = node
     t.varlist = varlist
     changes.push( () -> t.node = null; t.varlist = null; t.chainlength = 1 )
+
 bind_tins = (t1,t2,changes) ->
     if not t1.isfree() and not t2.isfree()
         return false
@@ -233,10 +218,10 @@
  # export functions so they are visible outside of this file
  extern "parse", parse
  extern "unify", unify
- extern "Var", Var
+ extern "variable", (name)->new Variable(name)
+ extern "rollback", rollback
  internal "Tin", Tin
  internal "Box", Box
  internal "DictFlag", DictFlag
  internal "toJson", toJson
  internal "Variable", Variable
- extern "rollback", rollback


Cut overly long descriptions. TODO: add read more link.

--- src/views.coffee
+++ src/views.coffee
@@ -435,6 +435,12 @@
         render: ->
             embedded = @embedded
             data = @model.toJSON()
+            description = data.description
+            MAX_LENGTH = 20
+            if description
+                words = description.split /[ ]+/
+                if words.length > MAX_LENGTH + 1
+                    data.description = words[0..MAX_LENGTH].join(' ') + '…'
             template_string = jade.template 'details', 'data': data, 'isEmbedded': embedded
             @el.innerHTML = template_string
             @set_max_height()


* add coffeecup as one of default assets.

--- src/zappa.coffee
+++ src/zappa.coffee
@@ -12,9 +12,16 @@
 uuid = require 'node-uuid'
 express = require 'express'
 socketio = require 'socket.io'
+uglify = require 'uglify-js'
+minify = (js) ->
+  ast = uglify.parser.parse(js)
+  ast = uglify.uglify.ast_mangle(ast)
+  ast = uglify.uglify.ast_squeeze(ast)
+  uglify.uglify.gen_code(ast)
+
 jquery = fs.readFileSync(__dirname + '/../vendor/jquery-1.6.4.min.js').toString()
 sammy = fs.readFileSync(__dirname + '/../vendor/sammy-0.7.0.min.js').toString()
-uglify = require 'uglify-js'
+coffeecup = fs.readFileSync( __dirname+ '/../node_modules/coffeecup/lib/coffeecup.js').toString()

 # Soft dependencies:
 jsdom = null
@@ -38,12 +45,6 @@
     } return -1; };
 """.replace /\n/g, ''

-minify = (js) ->
-  ast = uglify.parser.parse(js)
-  ast = uglify.uglify.ast_mangle(ast)
-  ast = uglify.uglify.ast_squeeze(ast)
-  uglify.uglify.gen_code(ast)
-
 # Shallow copy attributes from `sources` (array of objects) to `recipient`.
 # Does NOT overwrite attributes already present in `recipient`.
 copy_data_to = (recipient, sources) ->
@@ -209,6 +210,7 @@
               when '/zappa/zappa.js' then send client
               when '/zappa/jquery.js' then send jquery
               when '/zappa/sammy.js' then send sammy
+              when '/zappa/coffeecup.js' then send coffeecup
               else next()

     use = (name, arg = null) ->


Removed `@require`, it won't work. Need to find something else for plugins.

#38

--- src/zappa.coffee
+++ src/zappa.coffee
@@ -285,10 +285,6 @@
   context.include = (p) ->
     sub = if typeof p is 'string' then require path.join(real_root, p) else p
     sub.include.apply(context, [context])
-
-  context.require = (p,args...) ->
-    sub = if p[0] is '.' then require path.join(real_root, p) else require p
-    sub.plugin.apply context, [{context,route},args...]

   apply_helpers = (ctx) ->
     for name, helper of helpers


Remove simulator V1

Remove simulator V1

--- lib/simulator.coffee
+++ lib/simulator.coffee
@@ -1,366 +0,0 @@
-"use strict"
-
-## class Ship
-class Ship
-  constructor: (@owner, @id, @position, @equipment, @nowHP, @maxHP) ->
-    # owner = enum ShipOwner
-    # id = int : api_id of api_start2.api_mst_ship
-    # position = int : Position of ship in fleet
-    # equipment = [int, ...] : api_id of api_start2.api_mst_slotitem
-    # nowHP = int
-    # maxHP = int
-
-# enum ShipOwner
-ShipOwner =
-  Ours: "ShipOwner.Ours"
-  Enemy: "ShipOwner.Enemy"
-
-## class Attack
-class Attack
-  constructor: (@type, @fromShip, @toShip,
-                @maxHP, @fromHP, @toHP,
-                @damage, @hit, @useItem = NaN) ->
-    # type = enum AttackType
-    # fromShip = class Ship
-    # toShip = class Ship
-    # maxHP = int : Max HP of @toShip
-    # nowHP = int : HP of @toShip after the attack.
-    # damage = [int, ...]
-    # hit = [enum HitType, ...]
-    # useItem = int : api_id of api_start2.api_mst_slotitem
-    #           NaN : if didnt used any item
-    # Assert: @damage.length == @hit.length
-
-# enum AttackType
-# Day:   api_req_sortie/battle .api_hougeki[1-3].api_at_type
-# Night: api_req_battle_midnight/battle .api_hougeki.api_sp_list
-AttackType =
-  Normal: 0               # 通常攻撃, @damage.length == 1
-  Laser: 1                # レーザー攻撃, @damage.length == ?
-  Double: 2               # 連撃, @damage.length == 2
-  Primary_Secondary_CI: 3 # カットイン(主砲/副砲), @damage.length == 1
-  Primary_Radar_CI: 4     # カットイン(主砲/電探), @damage.length == 1
-  Primary_AP_CI: 5        # カットイン(主砲/徹甲), @damage.length == 1
-  Primary_Primary_CI: 6   # カットイン(主砲/主砲), @damage.length == 1
-  Primary_Torpedo_CI: 7   # カットイン(主砲/魚雷), @damage.length == 2
-  Torpedo_Torpedo_CI: 8   # カットイン(魚雷/魚雷), @damage.length == 2
-
-# enum HitType
-HitType =
-  Miss: 0
-  Hit: 1
-  Critical: 2
-
-## class Stage
-class Stage
-  constructor: (@type, @detail, @kouku) ->
-    # type = enum StageType
-    # detail = list of class Attack
-
-# enum StageType
-StageType =
-  AerialCombat: "AerialCombat",
-  TorpedoSalvo: "TorpedoSalvo",
-  Shelling: "Shelling",
-  Support: "Support"
-
-
-# Map from api id of attack type to AttackType
-# 特殊砲撃 0=通常, 1=レーザー攻撃, 2=連撃, 3=カットイン(主砲/副砲), 4=カットイン(主砲/電探), 5=カットイン(主砲/徹甲), 6=カットイン(主砲/主砲)
-AttackTypeMap =
-  0: AttackType.Normal
-  1: AttackType.Laser
-  2: AttackType.Double
-  3: AttackType.Primary_Secondary_CI
-  4: AttackType.Primary_Radar_CI
-  5: AttackType.Primary_AP_CI
-  6: AttackType.Primary_Primary_CI
-# Map from api id of night attack type to day attack type id
-# 夜戦攻撃種別 0=通常攻撃, 1=連撃, 2=カットイン(主砲/魚雷), 3=カットイン(魚雷/魚雷), 4=カットイン(主砲/副砲), 5=カットイン(主砲/主砲)
-NightAttackTypeMap =
-  0: AttackType.Normal
-  1: AttackType.Double
-  2: AttackType.Primary_Torpedo_CI
-  3: AttackType.Torpedo_Torpedo_CI
-  4: AttackType.Primary_Secondary_CI
-  5: AttackType.Primary_Primary_CI
-
-
-checkRepairItem = (sortieShip) ->
-  if sortieShip.nowHP <= 0 && sortieShip.equipment?
-    for id, i in sortieShip.equipment
-      if id == 42
-        sortieShip.nowHP = Math.floor(sortieShip.maxHP / 5)
-        return 42
-      else if id == 43
-        sortieShip.nowHP = sortieShip.maxHP
-        return 43
-  return NaN
-
-simulateAerialCombat = (sortieShip, enemyShip, kouku) ->
-  list = []
-  return list unless kouku?
-  if kouku.api_edam?
-    for damage, i in kouku.api_edam
-      continue if (kouku.api_ebak_flag[i] <= 0 && kouku.api_erai_flag[i] <= 0) || i == 0
-      damage = Math.floor(damage)
-      dmg = []
-      dmg.push damage
-      critical = []
-      critical.push if kouku.api_ecl_flag[i] == 1 then HitType.Critical else if damage == 0 then HitType.Miss else HitType.Hit
-      fromHP = enemyShip[i - 1].nowHP
-      enemyShip[i - 1].nowHP -= damage
-      list.push new Attack AttackType.Normal, null, enemyShip[i - 1], enemyShip[i - 1].maxHP, fromHP, enemyShip[i - 1].nowHP, dmg, critical
-  if kouku.api_fdam?
-    for damage, i in kouku.api_fdam
-      continue if (kouku.api_fbak_flag[i] <= 0 && kouku.api_frai_flag[i] <= 0) || i == 0
-      damage = Math.floor(damage)
-      dmg = []
-      dmg.push damage
-      critical = []
-      critical.push if kouku.api_fcl_flag[i] == 1 then HitType.Critical else if damage == 0 then HitType.Miss else HitType.Hit
-      fromHP = sortieShip[i - 1].nowHP
-      sortieShip[i - 1].nowHP -= damage
-      useItem = checkRepairItem sortieShip[i - 1]
-      list.push new Attack AttackType.Normal, null, sortieShip[i - 1], sortieShip[i - 1].maxHP, fromHP, sortieShip[i - 1].nowHP, dmg, critical, useItem
-  list
-
-simulateSupportFire = (enemyShip, support) ->
-  list = []
-  if support.api_support_airatack?
-    for damage, i in support.api_support_airatack.api_stage3.api_edam
-      continue unless 1 <= i <= 6
-      damage = Math.floor(damage)
-      dmg = []
-      dmg.push damage
-      critical = []
-      if support.api_support_airatack.api_stage3.api_ecl_flag[i] == 1
-        critical.push HitType.Critical
-      else if support.api_support_airatack.api_stage3.api_ecl_flag[i] == 0 && damage != 0
-        critical.push HitType.Hit
-      else
-        critical.push HitType.Miss
-      fromHP = enemyShip[i - 1].nowHP
-      enemyShip[i - 1].nowHP -= damage
-      list.push new Attack AttackType.Normal, null, enemyShip[i - 1], enemyShip[i - 1].maxHP, fromHP, enemyShip[i - 1].nowHP, dmg, critical
-  else if support.api_support_hourai?
-    for damage, i in support.api_support_hourai.api_damage
-      continue unless 1 <= i <= 6
-      damage = Math.floor(damage)
-      dmg = []
-      dmg.push damage
-      critical = []
-      if support.api_support_hourai.api_cl_list[i] == 2
-        critical.push HitType.Critical
-      else if support.api_support_hourai.api_cl_list[i] == 1
-        critical.push HitType.Hit
-      else
-        critical.push HitType.Miss
-      fromHP = enemyShip[i - 1].nowHP
-      enemyShip[i - 1].nowHP -= damage
-      list.push new Attack AttackType.Normal, null, enemyShip[i - 1], enemyShip[i - 1].maxHP, fromHP, enemyShip[i - 1].nowHP, dmg, critical
-  list
-
-simulateTorpedoSalvo = (sortieShip, enemyShip, raigeki) ->
-  list = []
-  # 雷撃ターゲット
-  for target, i in raigeki.api_frai
-    continue if target <= 0
-    damage = Math.floor(raigeki.api_fydam[i])
-    dmg = []
-    dmg.push damage
-    # クリティカルフラグ 0=ミス, 1=命中, 2=クリティカル
-    hit = []
-    api_hit = raigeki.api_fcl[i]
-    if api_hit == 2
-      hit.push HitType.Critical
-    else if api_hit == 1
-      hit.push HitType.Hit
-    else
-      hit.push HitType.Miss
-    fromHP = enemyShip[target - 1].nowHP
-    enemyShip[target - 1].nowHP -= damage
-    list.push new Attack AttackType.Normal, sortieShip[i - 1], enemyShip[target - 1], enemyShip[target - 1].maxHP, fromHP, enemyShip[target - 1].nowHP, dmg, hit
-  # 雷撃ターゲット
-  for target, i in raigeki.api_erai
-    continue if target <= 0
-    damage = Math.floor(raigeki.api_eydam[i])
-    dmg = []
-    dmg.push damage
-    # api_cl_list		：クリティカルフラグ 0=ミス, 1=命中, 2=クリティカル
-    hit = []
-    api_hit = raigeki.api_ecl[i]
-    if api_hit == 2
-      hit.push HitType.Critical
-    else if api_hit == 1
-      hit.push HitType.Hit
-    else
-      hit.push HitType.Miss
-    fromHP = sortieShip[target - 1].nowHP
-    sortieShip[target - 1].nowHP -= damage
-    useItem = checkRepairItem sortieShip[target - 1]
-    list.push new Attack AttackType.Normal, enemyShip[i - 1], sortieShip[target - 1], sortieShip[target - 1].maxHP, fromHP, sortieShip[target - 1].nowHP, dmg, hit, useItem
-  list
-
-simulateShelling = (sortieShip, enemyShip, hougeki, isNight) ->
-  list = []
-  # 砲撃戦行動順
-  for damageFrom, i in hougeki.api_at_list
-    continue if damageFrom == -1
-    damageFrom -= 1
-    dmg = []
-    totalDamage = 0
-    for damage, j in hougeki.api_damage[i]
-      damage = Math.floor(damage)
-      dmg.push damage
-      totalDamage += damage
-    critical = []
-    for crt, j in hougeki.api_cl_list[i]
-      if crt == 2
-        critical.push HitType.Critical
-      else if crt == 1
-        critical.push HitType.Hit
-      else
-        critical.push HitType.Miss
-    target = hougeki.api_df_list[i][0] - 1
-    attackType = 0
-    if !isNight
-      attackType = AttackTypeMap[hougeki.api_at_type[i]]
-    else
-      attackType = NightAttackTypeMap[hougeki.api_sp_list[i]]
-    if target < 6
-      fromHP = sortieShip[target].nowHP
-      sortieShip[target].nowHP -= totalDamage
-      useItem = checkRepairItem sortieShip[target]
-      list.push new Attack attackType, enemyShip[damageFrom - 6], sortieShip[target], sortieShip[target].maxHP, fromHP, sortieShip[target].nowHP, dmg, critical, useItem
-    else
-      fromHP = enemyShip[target - 6].nowHP
-      enemyShip[target - 6].nowHP -= totalDamage
-      list.push new Attack attackType, sortieShip[damageFrom], enemyShip[target - 6], enemyShip[target - 6].maxHP, fromHP, enemyShip[target - 6].nowHP, dmg, critical
-  list
-
-# req needs the api_data name
-simulate = (req) ->
-    isCombined = req.poi_is_combined
-    isWater = !req.poi_is_carrier
-    # Initialization of sortieShip
-    sortieShip = []
-    enemyShip = []
-    combinedShip = []
-    sortiePos = 0
-    sortiePos = req.api_deck_id - 1 if !isCombined
-    for i in [0..5]
-      sortieShip.push new Ship ShipOwner.Ours, req.poi_sortie_fleet[i], i + 1, req.poi_sortie_equipment[i], req.api_nowhps[i + 1], req.api_maxhps[i + 1]
-      enemyShip.push new Ship ShipOwner.Enemy, req.api_ship_ke[i + 1], i + 1, null, req.api_nowhps[i + 7], req.api_maxhps[i + 7]
-      if isCombined
-        combinedShip.push new Ship ShipOwner.Ours, req.poi_combined_fleet[i], i + 1, req.poi_combined_equipment[i], req.api_nowhps_combined[i + 1], req.api_maxhps_combined[i + 1]
-    landBaseProgress = []
-    sortieProgress = []
-
-    # Land base air attack
-    if req.api_air_base_attack?
-      for api_data in req.api_air_base_attack
-        tempList = simulateAerialCombat sortieShip, enemyShip, api_data.api_stage3
-        landBaseProgress.push new Stage StageType.LandBaseAerialCombat, tempList, api_data
-
-    # Air battle
-    tempList = []
-    if req.api_kouku?
-      if req.api_kouku.api_stage3?
-        tempList = tempList.concat simulateAerialCombat sortieShip, enemyShip, req.api_kouku.api_stage3
-      if req.api_kouku.api_stage3_combined?
-        tempList = tempList.concat simulateAerialCombat combinedShip, enemyShip, req.api_kouku.api_stage3_combined
-    tempList = null if tempList.length == 0
-    sortieProgress.push new Stage StageType.AerialCombat, tempList, req.api_kouku
-
-    # Second air battle
-    tempList = []
-    if req.api_kouku2?
-      if req.api_kouku2.api_stage3?
-        tempList = tempList.concat simulateAerialCombat sortieShip, enemyShip, req.api_kouku2.api_stage3
-      if req.api_kouku2.api_stage3_combined?
-        tempList = tempList.concat simulateAerialCombat combinedShip, enemyShip, req.api_kouku2.api_stage3_combined
-    tempList = null if tempList.length == 0
-    sortieProgress.push new Stage StageType.AerialCombat, tempList, req.api_kouku2
-
-    # Support battle
-    tempList = []
-    if req.api_support_info?
-      tempList = tempList.concat simulateSupportFire enemyShip, req.api_support_info
-    tempList = null if tempList.length == 0
-    sortieProgress.push new Stage StageType.Support, tempList
-
-    # Opening battle
-    tempList = []
-    if req.api_opening_atack?
-      if isCombined
-        tempList = tempList.concat simulateTorpedoSalvo combinedShip, enemyShip, req.api_opening_atack
-      else
-        tempList = tempList.concat simulateTorpedoSalvo sortieShip, enemyShip, req.api_opening_atack
-    tempList = null if tempList.length == 0
-    sortieProgress.push new Stage StageType.TorpedoSalvo, tempList
-
-    # First hougeki battle
-    tempList = []
-    if req.api_hougeki1?
-      if isCombined && !isWater
-        tempList = tempList.concat simulateShelling combinedShip, enemyShip, req.api_hougeki1, false
-      else
-        tempList = tempList.concat simulateShelling sortieShip, enemyShip, req.api_hougeki1, false
-    tempList = null if tempList.length == 0
-    sortieProgress.push new Stage StageType.Shelling, tempList
-
-    # Combined fleet raigeki
-    tempList = []
-    if req.api_raigeki? && isCombined && !isWater
-      tempList = tempList.concat simulateTorpedoSalvo combinedShip, enemyShip, req.api_raigeki
-    tempList = null if tempList.length == 0
-    sortieProgress.push new Stage StageType.TorpedoSalvo, tempList
-
-    # Second hougeki battle
-    tempList = []
-    if req.api_hougeki2?
-      tempList = tempList.concat simulateShelling sortieShip, enemyShip, req.api_hougeki2, false
-    tempList = null if tempList.length == 0
-    sortieProgress.push new Stage StageType.Shelling, tempList
-
-    # Combined hougeki battle
-    tempList = []
-    if req.api_hougeki3?
-      if isCombined && isWater
-        tempList = tempList.concat simulateShelling combinedShip, enemyShip, req.api_hougeki3, false
-      else
-        tempList = tempList.concat simulateShelling sortieShip, enemyShip, req.api_hougeki3, false
-    tempList = null if tempList.length == 0
-    sortieProgress.push new Stage StageType.Shelling, tempList
-
-    # Raigeki battle
-    tempList = []
-    if req.api_raigeki?
-      if isCombined
-        if isWater
-          tempList = tempList.concat simulateTorpedoSalvo combinedShip, enemyShip, req.api_raigeki
-      else
-        tempList = tempList.concat simulateTorpedoSalvo sortieShip, enemyShip, req.api_raigeki
-    tempList = null if tempList.length == 0
-    sortieProgress.push new Stage StageType.TorpedoSalvo, tempList
-
-    # Night battle
-    tempList = []
-    if req.api_hougeki?
-      if isCombined
-        tempList = tempList.concat simulateShelling combinedShip, enemyShip, req.api_hougeki, true
-      else
-        tempList = tempList.concat simulateShelling sortieShip, enemyShip, req.api_hougeki, true
-    tempList = null if tempList.length == 0
-    sortieProgress.push new Stage StageType.Shelling, tempList
-
-    return [sortieProgress, landBaseProgress]
-
-
-module.exports = {
-  Ship, ShipOwner
-  Attack, AttackType, HitType
-  Stage, StageType
-  simulate
-}


Package detection stub

Package detection stub

--- lib/spark-dev.coffee
+++ lib/spark-dev.coffee
@@ -550,3 +550,7 @@
     @wifiCredentialsView.show(ssid, security)

   tryFlashUsb: -> @projectRequired =>
+    if !atom.commands.registeredCommands['spark-dev-dfu-util:flash-usb']
+      # TODO: Ask for installation
+    else
+      atom.workspaceView.trigger 'spark-dev-dfu-util:flash-usb'


Call open on workspace instead of workspace view

--- lib/tree-view.coffee
+++ lib/tree-view.coffee
@@ -452,7 +452,7 @@
       @selectEntryForPath(createdPath)
       false
     dialog.on 'file-created', (event, createdPath) ->
-      atom.workspaceView.open(createdPath)
+      atom.workspace.open(createdPath)
       false
     dialog.attach()


#PR use utility function to determine cur mode

--- lib/vim-state.coffee
+++ lib/vim-state.coffee
@@ -40,9 +40,9 @@
     @subscriptions.add @editor.onDidChangeSelectionRange _.debounce(=>
       return unless @editor?
       if @editor.getSelections().every((selection) -> selection.isEmpty())
-        @activateNormalMode() if @mode is 'visual'
+        @activateNormalMode() if @isVisualMode()
       else
-        @activateVisualMode('characterwise') if @mode is 'normal'
+        @activateVisualMode('characterwise') if @isNormalMode()
     , 100)

     @subscriptions.add @editor.onDidChangeCursorPosition ({cursor}) => @ensureCursorIsWithinLine(cursor)
@@ -227,7 +227,7 @@

       for operation in operations
         # Motions in visual mode perform their selections.
-        if @mode is 'visual' and _.isFunction(operation.select)
+        if @isVisualMode() and _.isFunction(operation.select)
         # if @mode is 'visual' and (operation instanceof Motions.Motion or operation instanceof TextObjects.TextObject)
           # console.log 'visu', operation.constructor.name
           # console.log 'type', operation.isMotion()
@@ -244,11 +244,7 @@

         # If we've received an operator in visual mode, mark the current
         # selection as the motion to operate on.
-        if @mode is 'visual' and operation.isOperator?()
-          # console.log "--"
-          # console.log operation.constructor.name
-          # console.log operation.isOperator()
-          # console.log "--"
+        if @isVisualMode() and operation.isOperator?()
           @operationsQueue.push(new TextObjects.CurrentSelection(@editor, this))

         @processOperations()
@@ -280,7 +276,7 @@
       return

     unless @tailOperation().isComplete()
-      if @mode is 'normal' and @tailOperation().isOperator?()
+      if @isNormalMode() and @tailOperation().isOperator?()
         @activateOperatorPendingMode()
       return

@@ -491,7 +487,7 @@
       @replaceModeUndoListener = null

   deactivateVisualMode: ->
-    return unless @mode is 'visual'
+    return unless @isVisualMode()
     for selection in @editor.getSelections()
       selection.cursor.moveLeft() unless (selection.isEmpty() or selection.isReversed())

@@ -514,7 +510,7 @@
     #  * activate-blockwise-visual-mode
     #  * activate-characterwise-visual-mode
     #  * activate-linewise-visual-mode
-    if @mode is 'visual'
+    if @isVisualMode()
       if @submode is type
         @activateNormalMode()
         return
@@ -662,6 +658,10 @@
     else
       @operationsQueue.length > 0

+  isVisualMode: -> @mode is 'visual'
+  isNormalMode: -> @mode is 'normal'
+  isInsertMode: -> @mode is 'insert'
+
   updateStatusBar: ->
     @statusBarManager.update(@mode, @submode)


Fix show more discussions on group page functionality

--- lineman/app/components/group_page/discussions_card/discussions_card.coffee
+++ lineman/app/components/group_page/discussions_card/discussions_card.coffee
@@ -15,7 +15,7 @@
         per:      $scope.perPage
       $scope.loaded += $scope.perPage
       Records.discussions.fetchByGroup(options).then (data) ->
-        if (data.discussions or []).length < $scope.perPage?
+        if (data.discussions or []).length < $scope.perPage
           $scope.canLoadMoreDiscussions = false

     LoadingService.applyLoadingFunction $scope, 'loadMore'


Count matching quotes with "howmany".

Count matching quotes with "howmany".

--- src/quote.coffee
+++ src/quote.coffee
@@ -47,26 +47,39 @@
       msg.reply "Just a moment, the quotes aren't loaded yet."
       false

+  queryFrom = (msg) ->
+    words = msg.match[1].trim().split /\s+/
+    _.filter words, (part) -> part.length > 0
+
+  quotesMatching = (query) ->
+    if query.length > 0
+      _.filter quotes, (quote) ->
+        _.every query, (q) -> quote.indexOf(q) isnt -1
+    else
+      quotes
+
   # Perform the initial load.
   reloadThen ->

   robot.respond /quote(.*)/i, (msg) ->
     return unless isLoaded(msg)

-    query = msg.match[1].trim().split /\s+/
-    query = _.filter query, (part) -> part.length > 0
-
-    potential = quotes
-
-    if query.length > 0
-      potential = _.filter potential, (quote) ->
-        _.every query, (q) -> quote.indexOf(q) isnt -1
+    potential = quotesMatching queryFrom msg

     if potential.length > 0
       chosen = _.random potential.length - 1
       msg.send potential[chosen]
     else
       msg.send "That wasn't notable enough to quote. Try harder."
+
+  robot.respond /howmany(.*)/i, (msg) ->
+    return unless isLoaded(msg)
+
+    matches = quotesMatching queryFrom msg
+
+    qstr = msg.match[1] or ' everything'
+    msg.reply "There are #{matches.length} quotes about#{qstr}."
+

   robot.respond /reload quotes$/i, (msg) ->
     reloadThen (err) ->


fix(web): Fix manifest dependency

fix(web): Fix manifest dependency

--- apps/actor-web/gulpfile.coffee
+++ apps/actor-web/gulpfile.coffee
@@ -107,7 +107,7 @@
     .pipe gulp.dest './dist/'
     .pipe connect.reload()

-gulp.task 'manifest', ->
+gulp.task 'manifest-prod', ['assets', 'browserify', 'sass', 'html', 'usemin', 'push']
   gulp.src ['./dist/**/*']
     .pipe manifest {
       hash: true,
@@ -125,11 +125,11 @@
     root: ['./dist/', './']
     livereload: true

-gulp.task 'build', ['assets', 'browserify', 'sass', 'html', 'usemin', 'push', 'manifest']
+gulp.task 'build', ['manifest-prod']

-gulp.task 'build:dev', ['assets', 'browserify:watchify', 'sass', 'html', 'push', 'manifest']
+gulp.task 'build:dev', ['assets', 'browserify:watchify', 'sass', 'html', 'push']

-gulp.task 'build:gwt', ['assets', 'browserify', 'sass', 'usemin', 'push', 'manifest']
+gulp.task 'build:gwt', ['assets', 'browserify', 'sass', 'usemin', 'push']

 gulp.task 'dev', ['build:dev', 'server', 'watch']


increase plan publishing timeout

increase plan publishing timeout

--- test-integration/helpers/ui/calendar.coffee
+++ test-integration/helpers/ui/calendar.coffee
@@ -3,7 +3,7 @@
 {TestHelper} = require './test-element'
 {PeriodReviewTab} = require './items'

-PUBLISHING_TIMEOUT = 60 * 1000 # Wait up to a minute for publishing to complete.
+PUBLISHING_TIMEOUT = 2 * 60 * 1000 # Wait up to 2 minutes for publishing to complete.

 COMMON_ELEMENTS =
   forecastLink:


fixed that KDButtonView does not consider options.title

--- components/modals/KDModalView.coffee
+++ components/modals/KDModalView.coffee
@@ -163,8 +163,8 @@

   createButton:(title,buttonOptions)->

-    buttonOptions.title    = title
-    buttonOptions.delegate = @
+    buttonOptions.title    or= title
+    buttonOptions.delegate   = @
     itemClass = buttonOptions.itemClass
     delete buttonOptions.itemClass
     @buttonHolder.addSubView button = new (itemClass or KDButtonView) buttonOptions


improve relative time display

improve relative time display

--- demo/demo.coffee
+++ demo/demo.coffee
@@ -5,10 +5,10 @@

   relativeTime = (timeAgo) ->
     diff = moment.utc(TimeSync.serverTime() - timeAgo)
-    time = diff.format(" H:mm:ss")
+    time = diff.format("H:mm:ss")
     days = +diff.format("DDD") - 1
-    ago = (if days then days + "d" else "") + time
-    return new Date(timeAgo).toLocaleString() + " -" + ago + " ago"
+    ago = (if days then days + "d " else "") + time
+    return new Date(timeAgo).toLocaleString() + " - " + ago + " ago"

   Template.login.loggedIn = -> Meteor.userId()


Remove unused class variable

Remove unused class variable

--- src/robot.coffee
+++ src/robot.coffee
@@ -41,7 +41,6 @@
     @Response     = Response
     @commands     = []
     @listeners    = []
-    @enableSlash  = false
     @logger       = new Log process.env.HUBOT_LOG_LEVEL or 'info'

     @parseVersion()


Alternate specification for react_example asets

--- desktop/assets/react_example.coffee
+++ desktop/assets/react_example.coffee
@@ -1,10 +1,7 @@
 require('backbone').$ = $

 routes =
-  '''
-  /react-example/
-  ''': require('../apps/react_example/client.js').default()
+  '/react-example': require('../apps/react_example/client.js').default()

-for paths, init of routes
-  for path in paths.split('\n')
-    $(init) if location.pathname.match path
+for path, init of routes
+  $(init) if location.pathname.match path


remove unnecessary escape sequence

remove unnecessary escape sequence

--- src/spawn.coffee
+++ src/spawn.coffee
@@ -6,7 +6,7 @@
   error

 module.exports = (name, command, port, logPath, logHandle, spawnOpts) ->
-  command = command.replace '\%port\%', port
+  command = command.replace '%port%', port
   args = command.split(' ')
   cmd = args[0]
   args = args.slice(1)


Added yet another test

Added yet another test

--- test/args.coffee
+++ test/args.coffee
@@ -102,10 +102,23 @@
                 webp[method]()
               webp[name]()
               write(webp, 'out.json').then (data) ->
-                Object.keys(data).should.have.length 4
                 data.should.have.keys key, '_', '__', 'o'
                 data[key].should.be.ok

+    describe 'convention', ->
+
+      it 'should send -preset first', ->
+        filename = Math.random().toString(36)
+        cmd = Math.random().toString(36)
+        webp = new Webp filename
+        webp.quality 123
+        webp.preset cmd
+        webp.size 456
+        write(webp, 'out.json').then (data) ->
+          data.preset.should.be.equal cmd
+          data.__[0].should.be.equal filename
+          data.__[1].should.be.equal '-preset'
+          data.__[2].should.be.equal cmd

     after (done) ->
       context.spawn = spawn


remove method complexity metric for now

remove method complexity metric for now

--- test/clog.coffee
+++ test/clog.coffee
@@ -15,12 +15,8 @@
   describe "#report", ->
     cases = fixturePath("case")
     ifs = fixturePath("nested_ifs")
-    methods = fixturePath("methods")

-    scores = JSON.parse clog.report [cases, ifs, methods]
-
-    it "returns a list of methods", ->
-      assert.ok(scores[methods].methods.length is 7)
+    scores = JSON.parse clog.report [cases, ifs]

     it "returns token compexity", ->
       assert.equal(scores[cases].complexity, 29)


more tests for function and undefined

more tests for function and undefined

--- test/main.coffee
+++ test/main.coffee
@@ -79,4 +79,26 @@

       out = ton.parse str
       out.should.eql o
+      done()
+
+  describe 'undefined', ->
+    it 'should be correct', (done) ->
+      o = test: undefined
+      str = ton.stringify o
+      expected = '{"test":undefined}'
+      should.exist str
+      str.should.equal expected
+
+      out = ton.parse str
+      out.should.eql o
+      done()
+
+  describe 'Function', ->
+    it 'should be correct', (done) ->
+      o = test: (a, b) -> a+b
+      str = ton.stringify o
+      should.exist str
+
+      out = ton.parse str
+      out.test.toString().should.equal o.test.toString()
       done()


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/text.coffee
+++ test/text.coffee
@@ -19,7 +19,6 @@

   text.update()

-
   equals text.I.alpha, 1

 module()


namespaced Movable

namespaced Movable

--- test/text.coffee
+++ test/text.coffee
@@ -3,13 +3,13 @@
 test "text moves up vertically", ->
   text = TextEffect()

-  text.update()
+  text.update(1)

   equals text.I.x, 0
   equals text.I.y, -1

   4.times ->
-    text.update()
+    text.update(1)

   equals text.I.x, 0
   equals text.I.y, -5


Disable sauce.

Disable sauce.

--- test/util.coffee
+++ test/util.coffee
@@ -24,6 +24,7 @@
     ]
     caps['tunnel-identifier'] = process.env.TRAVIS_JOB_NUMBER

+  if process.env.SAUCE_CONNECT
     opts =
       desiredCapabilities: caps
       logLevel: logLevel


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/state.coffee
+++ src/state.coffee
@@ -7,7 +7,7 @@
       return initialState

     transition: (toState) ->
-      return currentState = toState
+      currentState.trigger(toState)

   self.attrAccessor('currentState')


refactor

refactor

--- src/tools.coffee
+++ src/tools.coffee
@@ -1,5 +1,4 @@
-# this should be stored as a hash, not a list (look at how it's used!)
-# should also sort these alphabetically...
+# should sort these alphabetically to make it easier to read...
 reservedWords = [
   # actual keywords
   'break',        'case',         'catch',        'continue',     'debugger'
@@ -16,6 +15,7 @@
   'native',       'final',        'transient',    'float',        'package'
   'goto',         'private'
 ]
+

@@ -50,12 +50,16 @@
 # Write a test for it and fix it
 exports.isValidIdentifier = (name) ->
   name?.toString().match(/^[_a-zA-Z][_a-zA-Z0-9]*$/) &&
-  name not in reservedWords
+  !exports.isReservedWord(name)

-exports.isReservedWord = (name) ->
-  name in reservedWords
+exports.isReservedWord = do ->
+  reservedWordsHash = reservedWords.reduce (acc, word) ->
+    acc[word] = 1
+    acc
+  , {}
+  (name) -> !!reservedWordsHash[name]


Added rudimentary tweening, wut!

Added rudimentary tweening, wut!

--- src/tween.coffee
+++ src/tween.coffee
@@ -28,9 +28,9 @@

   player = GameObject()

-  player.tween 5
-    x:
-    y:
+  player.tween 5,
+    x: 50
+    y: 50
   # => This causes the sprite to flicker between full opacity
   # => and 50% opacity every 3 frames for 30 frames


parseCommand now handles string and array based commands

--- src/utils.coffee
+++ src/utils.coffee
@@ -93,25 +93,40 @@
   if typeof callback isnt 'function'
     callback = (v) -> v

-  matches = command.match Config.REPLACE_SETTING_RE
+  if 'string' isnt typeof command and 'array' isnt toType command
+    throw new Error "Bad invocation of parseCommand: (command: [String or Array], settings: Object, callback: Function)"

-  if matches
-    matches.forEach (settingToReplace) ->
-      # Remove leading and trailing '%'
-      lookupSetting = settingToReplace[1...-1]
+  # To preserve same type for caller, we will return a string if we were given
+  # a string and an array for an array.
+  if 'string' is typeof command
+    isString = true
+    commands = [ command ]
+  else
+    isString = false
+    commands = command

-      # See what we have in our dictionary and let the calling function be notified.
-      # Callback can update settingValue if needed.
-      settingValue = callback resolve settings, lookupSetting
+  results = commands.map (command) ->
+    matches = command.match Config.REPLACE_SETTING_RE

-      if settingValue
-        logging.debug "Found token to lookup #{lookupSetting}: #{settingValue}"
-      else
-        throw new Error "Setting '#{lookupSetting}' not found for command '#{command}!'"
+    if matches
+      matches.forEach (settingToReplace) ->
+        # Remove leading and trailing '%'
+        lookupSetting = settingToReplace[1...-1]

-      command = command.replace settingToReplace, settingValue
+        # See what we have in our dictionary and let the calling function be notified.
+        # Callback can update settingValue if needed.
+        settingValue = callback resolve settings, lookupSetting

-  command
+        if settingValue
+          logging.debug "Found token to lookup #{lookupSetting}: #{settingValue}"
+        else
+          throw new Error "Setting '#{lookupSetting}' not found for command '#{command}!'"
+
+        command = command.replace settingToReplace, settingValue
+
+    command
+
+  if isString then results[0] else results

 # Adapted from https://stackoverflow.com/questions/586182/how-do-i-insert-an-item-into-an-array-at-a-specific-index
 # What:


Fixing file extension detection

Fixing file extension detection

There were issues if there were multiple periods in the name

--- src/video.coffee
+++ src/video.coffee
@@ -9,7 +9,7 @@
 	return !!video?.canPlayType?(mime(url)).replace 'no', ''

 # Get the mimetupe of a video url given it's file extension
-mime = (url) -> switch url.match(/\.(\w+)/)?[1]
+mime = (url) -> switch url.match(/\.(\w+)$/)?[1]
 	when 'mp4' then 'video/mp4'
 	when 'webm' then 'video/webm'
 	when 'ogg' then 'video/ogg'


Fix typo

Fix typo

--- packages/rocketchat-channel-settings/server/functions/saveRoomTopic.coffee
+++ packages/rocketchat-channel-settings/server/functions/saveRoomTopic.coffee
@@ -6,7 +6,7 @@

 	update = RocketChat.models.Rooms.setTopicById(rid, roomTopic)

-	if udpate and sendMessage
+	if update and sendMessage
 		RocketChat.models.Messages.createRoomSettingsChangedWithTypeRoomIdMessageAndUser 'room_changed_topic', rid, roomTopic, user

 	return update


'done' callback bug with error parameter fixed

--- structure.coffee
+++ structure.coffee
@@ -44,10 +44,11 @@
 			return

 		@_callbackCalled = true
-		logger.onTestEnd @

 		if err
 			stack.fail err
+
+		logger.onTestEnd @

 		# call after functions
 		if @fulfilled


Move the selected illustration on tool select Change the active rectangle (for zooming etc.) when we select a pinpoint tool.

--- overrides.coffee
+++ overrides.coffee
@@ -137,7 +137,15 @@
   current_tool?.el.classList.remove 'selected'
   current_tool = (tool for tool in ms.tools when tool.outline?.el is e.target)[0]
   current_tool?.el.classList.add 'selected'
-
+
+ms.addEvent 'marking-surface:tool:select', ({detail}) ->
+  pinpoint = detail[0] if detail[0].mark._taskIndex is 2
+  current_tool?.el.classList.remove 'selected' if pinpoint?
+  for tool in ms.tools when tool.mark._taskIndex is 1
+    rectangle = tool.mark
+    if pinpoint?.mark.inside rectangle
+      current_tool = tool
+      current_tool?.el.classList.add 'selected'

 ms.on 'marking-surface:add-tool', (tool) ->
   {label} = decisionTree.currentTask.getChoice() ? ''


use relative photoUrl instead of prepending url

--- www/js/model.coffee
+++ www/js/model.coffee
@@ -71,8 +71,6 @@
 				_.each ['updatedAt', 'createdAt'], (field) ->
 					if ret[field]
 						ret[field] = new Date Date.parse ret[field]
-				if ret.photoUrl
-					ret.photoUrl = "#{env.server.app.urlRoot}/" + ret.photoUrl
 				return ret

 			post: ->
@@ -158,9 +156,6 @@
 				if ret.createdBy and typeof ret.createdBy == 'object'
 					ret.createdBy = new User ret.createdBy

-				if ret.photoUrl
-					ret.photoUrl = "#{env.server.app.urlRoot}/" + ret.photoUrl
-
 				return ret

 			exit: ->


fix Onetime token handler path

fix Onetime token handler path

--- services/web/app/coffee/Features/PasswordReset/PasswordResetHandler.coffee
+++ services/web/app/coffee/Features/PasswordReset/PasswordResetHandler.coffee
@@ -1,7 +1,7 @@
 settings = require("settings-sharelatex")
 async = require("async")
 UserGetter = require("../User/UserGetter")
-OneTimeTokenHandler = require("./OneTimeTokenHandler")
+OneTimeTokenHandler = require("../Security/OneTimeTokenHandler")
 EmailHandler = require("../Email/EmailHandler")
 AuthenticationManager = require("../Authentication/AuthenticationManager")
 logger = require("logger-sharelatex")


Remove event handlers when editor is removed

--- index.coffee
+++ index.coffee
@@ -21,7 +21,8 @@
     rootView.eachEditor (editor) => @subscribeToEditor(editor) if editor.attached

   subscribeToEditor: (editor) ->
-    editor.on 'cursor:moved', => @updateMatch(editor)
+    editor.on 'cursor:moved.bracket-matcher', => @updateMatch(editor)
+    editor.on 'editor:will-be-removed', => editor.off('.bracket-matcher')

   createView: (editor, bufferPosition) ->
     pixelPosition = editor.pixelPositionForBufferPosition(bufferPosition)


Fixes #1 (Intervals are now consistent)

Fixes #1 (Intervals are now consistent)

--- index.coffee
+++ index.coffee
@@ -71,7 +71,6 @@
     else
       @nonCarryReadCount += 1

-    timeStampBeforeReading = (new Date).getTime()
     @_readExpectedBytesOrRemainingBytes bytesToRead, (bytes)=>
       if bytes is null
         'pass'
@@ -80,15 +79,18 @@
         if @nonCarryReadCount is 9
           @emit 'progress', @bytesReadTotal

-        timeStampAfterReading = (new Date).getTime()
-        timeDiff = timeStampAfterReading - timeStampBeforeReading
+        timeDiff = (new Date).getTime() - (@copyStartTimeStamp + (@stepCount * 100))
         timeToWait = (if timeDiff > 100 then 0 else (100 - timeDiff))
+        @stepCount += 1

         fn = =>
           @_readingLoopStep()
         setTimeout fn, timeToWait

   copy: ->
+
+    @copyStartTimeStamp = (new Date).getTime()
+
     @_gatherInputFileStats =>
       @_calculateUpperLimits()

@@ -97,6 +99,7 @@

       @nonCarryReadCount = 0
       @bytesReadTotal = 0
+      @stepCount = 0

       @readStream.once 'readable', =>
         @_readingLoopStep()


Add debris when blocks are mined. Closes GH-15

Requires https://github.com/deathcap/voxel-debris/commit/62aed07ea56e9a56c442b00c572cc4a6e30e3fca

--- index.coffee
+++ index.coffee
@@ -170,16 +170,16 @@
     #game.setBlock target.voxel, 0
     # TODO: other interactions depending on item (ex: click button, check target.sub; or other interactive blocks)

-  mine.on 'break', (goner) ->
-    #game.explode goner # TODO: update voxel-debris for latest voxel-engine, doesn't pass materials?
-    game.setBlock goner, 0
-
   # block interaction: left/right-click to break/place blocks, uses raytracing
   game.currentMaterial = 1

   debris = game.plugins.load 'debris', {power: 1.5}
   debris.on 'collect', (item) ->
     console.log 'collect', item
+
+  mine.on 'break', (pos) =>
+    debris(pos, 2) # TODO: pass material
+    #game.setBlock pos, 0

   gui = new datgui.GUI()
   console.log 'gui',gui


Remove references to player.

Remove references to player.

--- index.coffee
+++ index.coffee
@@ -24,14 +24,11 @@
   vec: 'vecx'

 save = ->
-  localForage.setItem (md5 player.game), player.core.serialize() if player
+  localForage.setItem (md5 retro.game), retro.save if retro.running

 stop = ->
   retro.stop()
   save()
-  player.core.unload_game()
-  player.core.deinit()
-  player = null

 window.setInterval save, 10000


cancel session after put

cancel session after put

--- index.coffee
+++ index.coffee
@@ -178,7 +178,7 @@
   wf.push (hooks, callback) ->
     fs.readFile options.localpath, {encoding: null}, (err, data) ->
       logger.error err if err
-      logger.debug 'file length', data.length
+      logger.info 'FILE',  options.localpath, 'LENGTH', data.length
       hooks.buffs = data
       callback()
   wf.push (hooks, callback) ->
@@ -192,20 +192,35 @@
         body: hooks.buffs.slice frag.rangeF, frag.size
       logger.trace '%j', _.omit par, 'body'
       request par, (err, resp, body) ->
-        return logger.error err if err
-        logger.warn body if body.error
-        logger.debug body
-        fin null, body
+        logger.error err if err
+        logger.debug 'put fragment', resp.statusCode, resp.headers if resp
+        if err or body?.error
+          logger.warn body
+        else
+          logger.debug body
+        request hooks.session.uploadUrl, (err, resp, status) ->
+          logger.debug 'status', status
+          fin null, body
     size = +options.size * Math.pow(2, 20)
     list = _.range hooks.buffs.length / size
     list = _.map list, (i) ->
-      rangeF: f = i*size
+      rangeF: f = i * size
       size: s = Math.min size, hooks.buffs.length - f
       rangeT: f + s - 1
     async.mapLimit list, 1, iter, (err, arr) ->
-      hooks.item = _.find arr, (e) -> e.id and e.name
-      callback()
-  wf.exec (err, item) ->
+      hooks.item = _.find arr, (e) -> e and e.id and e.name
+      callback()
+  wf.push (hooks, callback) ->
+    par =
+      uri: hooks.session.uploadUrl
+      headers: {}
+      method: 'DELETE'
+    logger.trace '%j', par
+    request par, (err, resp) ->
+      logger.error err if err
+      logger.debug 'cancel session', resp.statusCode, resp.headers if resp
+      callback null
+  wf.exec (err, hooks) ->
     logger.info 'put success', hooks.item

@@ -240,7 +255,7 @@
       cmdShowList par if name is 'list'

   program.command 'put <localpath>'
-    .description 'show list/drive'
+    .description 'upload file'
     .option '-p --path [name]', 'item id/name'
     .option '--size [number]', 'split file by ?MB before put'
     .action (name, options) ->


if settings have been closed allow it to be opened again

--- index.coffee
+++ index.coffee
@@ -18,12 +18,6 @@

 settings = new Configstore(package_json.name)

-onClosed = ->
-  # dereference the window
-  # for multiple windows store them in an array
-  mainWindow = null
-  return
-
 checkWhich = ->
   which kdu_compress, (err, path) ->
     if err #open a window with instructions on installing kakadu binaries
@@ -42,7 +36,8 @@
       icon: './app/images/image-image.png')
     win.setMenu(null)
     win.loadURL("file://#{__dirname}/app/views/index.html")
-    win.on 'closed', onClosed
+    win.on 'closed', ->
+      win = null
     # win.output_dir = settings.get('output_dir')
     win.settings = settings
     win
@@ -67,7 +62,8 @@
   win.setMenu(null)
   win.loadURL("file://#{__dirname}/app/views/settings.html")
   win.settings = settings
-  # win.on 'closed', something
+  win.on 'closed', ->
+    settingsWindow = null
   win

 # report crashes to the Electron project
@@ -110,7 +106,7 @@
   checkWhich()

 ipc_main.on 'open-settings', (event, arg) ->
-  if !settingsWindow?
+  if not settingsWindow?
     settingsWindow = openSettings()

 ipc_main.on 'open-mainwindow-if-not', (event, arg) ->


Backwards compatibility.

Backwards compatibility.

Ensure that client works with API without 'include' parameter
for services and service ancestors.

--- src/app.coffee
+++ src/app.coffee
@@ -98,10 +98,11 @@
                 @units.reset []
                 @search_results.set []

-            ancestor = @services.find (s) ->
-                s.id in service.get('ancestors')
-            if ancestor?
-                @removeService(ancestor)
+            if service.has('ancestors')
+                ancestor = @services.find (s) ->
+                    s.id in service.get('ancestors')
+                if ancestor?
+                    @removeService(ancestor)
             @services.add(service)

             unit_list = new models.UnitList pageSize: PAGE_SIZE


Make session details window work for all sessions

--- views/map.coffee
+++ views/map.coffee
@@ -17,6 +17,8 @@
     scaledSize: new google.maps.Size(25, 38),
     anchor: new google.maps.Point(12.5, 38)
   }
+  sessionDetailsWindow = initializeSessionDetailsWindow()
+
   $.get '/sessions', (sessions) ->
     for session in sessions
       marker = new google.maps.Marker(
@@ -24,94 +26,98 @@
         map: map,
         title: session.user_last_name,
         icon: icon
-        anchorPoint: new google.maps.Point(0, -38)
+        anchorPoint: new google.maps.Point(0, -38),
+        session: session
       )
-      marker.addListener('click', -> sessionDetailsWindow(session).open(map, marker))
+      google.maps.event.addListener marker, 'click', ->
+        sessionDetailsWindow.setContent(sessionDetails(this.session))
+        sessionDetailsWindow.open(map, this)
+
     setTimeout(loadSessions, 30000)

-@sessionDetailsWindow = (session) ->
-  contentString = "
-

-
#{session.user_name}

-

-

-
#{session.description}

-
at #{session.spot.name}

+@initializeSessionDetailsWindow = ->
+  new google.maps.InfoWindow(
+    content: 'Loading...',
+    maxWidth: 400
+  )
+
+@sessionDetails = (session) ->
+  "

+
#{session.user_name}

+

+

+
#{session.description}

+
at #{session.spot.name}

+

+
#{session.session_finished}

+
" +
+    (if session.picture? then "
+

+
+
"
+    else '') + "
+

+

+

+

+          #{session.highest_jump.toFixed(1)}m

-
#{session.session_finished}

-
" +
-      (if session.picture? then "
-

-
-
"
-      else '') + "
-

-

-

-

-            #{session.highest_jump.toFixed(1)}m
-

-          Highest Jump
+        Highest Jump
+

+

+

+          #{session.max_airtime}

-

-

-            #{session.max_airtime}
-

-          Max Airtime
+        Max Airtime
+

+

+

+          #{session.max_crash_power.toFixed(1)}G

-

-

-            #{session.max_crash_power.toFixed(1)}G
-

-          Landing
-

-

-

-

-

-            #{session.total_height.toFixed(1)}m
-

-          Total Height
-

-

-

-            #{session.total_airtime}
-

-          Total Airtime
-

-

-

-            #{session.duration}
-

-          Session Duration
-

-

-

-

-

-            #{session.likes}
-

-          Likes
-

-

-

-            #{session.comments}
-

-          Comments
-

-

-

-            #{session.number_of_jumps}
-

-          Jumps
-

+        Landing

-  "
-  new google.maps.InfoWindow(
-    content: contentString,
-    maxWidth: 400
-  )
+

+

+

+          #{session.total_height.toFixed(1)}m
+

+        Total Height
+

+

+

+          #{session.total_airtime}
+

+        Total Airtime
+

+

+

+          #{session.duration}
+

+        Session Duration
+

+

+

+

+

+          #{session.likes}
+

+        Likes
+

+

+

+          #{session.comments}
+

+        Comments
+

+

+

+          #{session.number_of_jumps}
+

+        Jumps
+

+

+
"

 @setDayNightCycleOverlay = (map) ->
   nite.init(map)


removed bunk setTimeout and instead call _add_attribution() in _map_data()

--- bokehjs/src/coffee/renderer/tile/tile_renderer.coffee
+++ bokehjs/src/coffee/renderer/tile/tile_renderer.coffee
@@ -64,6 +64,7 @@
     @y_range.set('start', new_extent[1])
     @x_range.set('end', new_extent[2])
     @y_range.set('end', new_extent[3])
+    @_add_attribution()

   _on_tile_load: (e) =>
     tile_data = e.target.tile_data
@@ -141,9 +142,6 @@

     @prefetch_timer = setTimeout(@_prefetch_tiles, 500)

-    if not @attribution_added
-      setTimeout(@_add_attribution, 1000)
-      @attribution_added = true

   _draw_tile: (tile_key) ->
     tile_obj = @mget('tile_source').tiles[tile_key]


Adding header

Adding header

--- src/bob.coffee
+++ src/bob.coffee
@@ -1,3 +1,9 @@
+# Description:
+#   Adds Bits of Binary for image URLs
+#
+# Author:
+#   nickcaballero
+
 meld = require 'meld'
 request = require 'request'
 crypto = require 'crypto'


Free payment plan fix

Free payment plan fix

--- client/Pricing/AppView.coffee
+++ client/Pricing/AppView.coffee
@@ -209,9 +209,10 @@
       inProcess = yes

       isCurrentPlan =
-        options.planTitle    is @state.currentPlan and
-        options.planInterval is @state.currentPlanInterval and
-        'expired'          isnt @state.subscriptionState
+        options.planTitle     is @state.currentPlan and
+        (options.planInterval is @state.currentPlanInterval or
+        options.planTitle     is PaymentConstants.planTitle.FREE) and
+        'expired'             isnt @state.subscriptionState

       return KD.showError "That's already your current plan."  if isCurrentPlan


Fix grammar in documentation for host option.

--- src/cli.coffee
+++ src/cli.coffee
@@ -25,7 +25,7 @@
   .option('--config [path]', 'path to fontello config. defaults to ./config')
   .option('--css [path]', 'path to css directory (optional). if provided, --font option is expected.')
   .option('--font [path]', 'path to font directory (optional). if provided, --css option is expected.')
-  .option('--host [host]', 'address to fontello instance (optional).')
+  .option('--host [host]', 'address of fontello instance (optional).')

 program
   .command('install')


formatting

formatting

--- src/cli.coffee
+++ src/cli.coffee
@@ -21,12 +21,8 @@
   watch process.cwd(), (changedPath) ->
     changedPath = path.relative process.cwd(), changedPath

-    # Just the one instance thanks
     return if commandRunning
-
-    # Ignore the deletes
     return unless path.existsSync changedPath
-
     return unless passesGlobFilters path, only

     logInfo "changed: #{changedPath} "


String interpolation instead of concatenation

--- src/cli.coffee
+++ src/cli.coffee
@@ -85,4 +85,4 @@

 # Expose APIs.
 exports.commands = {}
-CLI.require __dirname + '/cli/db'+CLI.require "#{__dirname}/cli/db"


Pend the pending

Pend the pending

--- apps/galleries_institutions2/components/primary_carousel/test/fetch.coffee
+++ apps/galleries_institutions2/components/primary_carousel/test/fetch.coffee
@@ -2,7 +2,7 @@
 Backbone = require 'backbone'
 PrimaryCarousel = require '../fetch'

-describe 'PrimaryCarousel', ->
+xdescribe 'PrimaryCarousel', ->
   describe '#fetch', ->
     beforeEach ->
       sinon.stub Backbone, 'sync'


Remove `hash: true` until its implemented

--- docs/welcome/coffee/welcome.coffee
+++ docs/welcome/coffee/welcome.coffee
@@ -64,8 +64,7 @@
         shepherd.next()
     ]

-  shepherd.start
-    hash: true
+  shepherd.start()

 completeShepherd = ->
   $('body').addClass('shepherd-completed')


fix one space and missed one TIE translation

--- coffeescripts/cards-de.coffee
+++ coffeescripts/cards-de.coffee
@@ -204,7 +204,7 @@
             name: "Pilot der Gold-Staffel"
         "Academy Pilot":
             ship: "TIE-Jäger"
-           name: "Pilot der Akademie"
+            name: "Pilot der Akademie"
         "Obsidian Squadron Pilot":
             ship: "TIE-Jäger"
             name: "Pilot der Obsidian-Staffel"
@@ -574,7 +574,8 @@
             text: """Nachdem du ein Manöver ausgeführt hast, darfst du 2 angedockte Schiffe absetzen."""
         '"Scourge"':
             ship: "TIE-Jäger"
-            text: """When attacking a defender that has 1 or more Damage cards, roll 1 additional attack die."""
+            name: "Geissel"
+            text: """Sobald du einen Verteidiger angreifst, der 1 oder mehr Schadenskarten hat, wirf 1 zusätzlichen Angriffswürfel."""
         "The Inquisitor":
             text: """When attacking with your primary weapon at Range 2-3, treat the range of the attack as Range 1."""
         "Zuckuss":


Fix ES translation. (dupe fix of #328)

Fix ES translation. (dupe fix of #328)

--- coffeescripts/cards-es.coffee
+++ coffeescripts/cards-es.coffee
@@ -1512,6 +1512,7 @@
             text: '''%GOZANTIONLY%%LINEBREAK%Once per round, after you acquire a target lock, you may remove 1 focus, evade, or blue target lock token from that ship.'''
         'Black One':
             text: '''After you perform a boost or barrel roll action, you may remove 1 enemy target lock from a friendly ship at Range 1.  You cannot equip this card if your pilot skill is "6" or lower.'''
+            ship: "T-70 Ala-X"
         'Millennium Falcon (TFA)':
             text: '''After you execute a 3-speed bank maneuver (%BANKLEFT% or %BANKRIGHT%), if you are not touching another ship and you are not stressed, you may receive 1 stress token to rotate your ship 180°.'''


improved thingsAt to return results sorted by nearness

--- evolution.coffee
+++ evolution.coffee
@@ -162,14 +162,17 @@
             ret.push v if returns
     ret
   # find all things overlapping (minus appendages) overlapping a given point
+  # sorted nearest to farthest
   thingsAt: ( x, y ) ->
     c = @cellAt( x, y )
-    test = (t) -> t.radius > Math.sqrt( (t.x - x)**2 + (t.y - y )**2 )
-    ret = []
-    ret.push t for t in c.inhabitants when test t
+    ar = []
+    f = (t) ->
+      d = Math.sqrt( (t.x - x)**2 + (t.y - y )**2 )
+      ar.push [ t, d ] if d < t.radius
+    f(t) for t in c.inhabitants
     for other in c.neighbors when other[1] <= @maxRadius
-      ret.push t for t in other[0].inhabitants when test t
-    ret
+      f(t) for t in other[0].inhabitants
+    i[0] for i in ar.sort (a,b) -> a[1] - b[1]
   cellAt: ( x, y ) -> @cells[ x // @cellWidth ][ y // @cellWidth ]
   # place a thing in the appropriate cell
   place: (thing, onlyMovingIn) ->


clear modal window forms when closing

clear modal window forms when closing

--- app/assets/javascripts/admin/admin_in_place_editor.js.coffee
+++ app/assets/javascripts/admin/admin_in_place_editor.js.coffee
@@ -21,14 +21,17 @@
     $('.modal .modal-footer .save-button').click () ->
       $(@).closest('.modal').find('form').submit()
     $('.modal').on 'hidden', () =>
-      @clearModalForm($(@))
+      $('.modal.hide.fade').each((idx, element) =>
+        @clearModalForm($(element))
+      )
     @initModals()
     @initSearchTypeahead()
     $("[rel='tooltip']").tooltip()

-  clearModalForm: (modal) ->
-    form = modal.find('form')
-    form[0].reset() if form.length > 0
+  clearModalForm: (modalElement) ->
+    modalElement.find('form').each(() ->
+      @reset()
+    )

   initForm: () ->
     $(".datepicker").datepicker


When opening an entry, close any other open entries.

This is a WIP and currently breaks "open all entries" mode.

--- app/assets/javascripts/ng-services/ng-OpenEntrySvc.js.coffee
+++ app/assets/javascripts/ng-services/ng-OpenEntrySvc.js.coffee
@@ -15,6 +15,8 @@
       $rootScope.open_entries.push entry
     # Otherwise there is at most a single open entry.
     else
+      for e in $rootScope.open_entries
+        animationsSvc.close_entry e
       $rootScope.open_entries = [entry]

     animationsSvc.open_entry entry


announcement: handle announcement events for showing unread count

--- client/Social/Activity/sidebar/activitysidebar.coffee
+++ client/Social/Activity/sidebar/activitysidebar.coffee
@@ -267,12 +267,16 @@
   addItem: (data, index) ->

     listController = @getListController data.typeConstant
-
-    if item = @getItemByData data
-      listController.moveItemToIndex item, index  if index?
-      return item
-
-    item = listController.addItem data, index
+    item = @getItemByData data
+
+    # add the new topic item in sidebar
+    return listController.addItem data, index  unless item
+
+    # since announcement is fixed in sidebar no need to add/move it
+    return item  if data.typeConstant is 'announcement'
+
+    # move the channel to the given index
+    listController.moveItemToIndex item, index  if index?

     return item


Add window and incognito options for createTab.

Examples:

    # Just create new windows
    map X createTab window
    map X createTab incognito

    # Create windows with URLs
    map X createTab window https://developer.chrome.com/home https://github.com/philc/vimium
    map X createTab window URL1 URL2 URL3

`2X` creates two new windows, each with all of the indicated URLs.

Fixes #2825.

--- background_scripts/main.coffee
+++ background_scripts/main.coffee
@@ -188,13 +188,20 @@
             [if request.tab.incognito then "chrome://newtab" else chrome.runtime.getURL newTabUrl]
           else
             [newTabUrl]
-    urls = request.urls[..].reverse()
-    do openNextUrl = (request) ->
-      if 0 < urls.length
-        TabOperations.openUrlInNewTab (extend request, {url: urls.pop()}), (tab) ->
-          openNextUrl extend request, {tab, tabId: tab.id}
-      else
-        callback request
+    if request.registryEntry.options.incognito or request.registryEntry.options.window
+      windowConfig =
+        url: request.urls
+        focused: true
+        incognito: request.registryEntry.options.incognito ? false
+      chrome.windows.create windowConfig, -> callback request
+    else
+      urls = request.urls[..].reverse()
+      do openNextUrl = (request) ->
+        if 0 < urls.length
+          TabOperations.openUrlInNewTab (extend request, {url: urls.pop()}), (tab) ->
+            openNextUrl extend request, {tab, tabId: tab.id}
+        else
+          callback request
   duplicateTab: mkRepeatCommand (request, callback) ->
     chrome.tabs.duplicate request.tabId, (tab) -> callback extend request, {tab, tabId: tab.id}
   moveTabToNewWindow: ({count, tab}) ->


Remove unused line

Remove unused line

--- core/app/backbone/views/comments/react_comment.coffee
+++ core/app/backbone/views/comments/react_comment.coffee
@@ -103,7 +103,6 @@

 window.ReactOpinionatedAvatar = React.createClass
   displayName: "ReactOpinionatedAvatar"
-  changeOptions: 'add remove reset sort' + ' change'

   propTypes:
     user: React.PropTypes.instanceOf(User).isRequired


Moved COLORPICKER from GENERATOR

Moved COLORPICKER from GENERATOR

--- src/DOM.coffee
+++ src/DOM.coffee
@@ -28,13 +28,12 @@
             'RULE_DROPDOWN':'wolfcage-generator-select-input',
             'RULE_GENERATE_BUTTON':'wolfcage-generator-generate-button',
             'COLORPICKER_BUTTON':'wolfcage-generator-colorpicker-button',
-            'COLORPICKER_CONTAINER':'wolfcage-colorpicker',
-            'COLORPICKER_ACTIVE':'wolfcage-colorpicker-active',
-            'COLORPICKER_BORDER':'wolfcage-colorpicker-border',
-            'COLORPICKER_INACTIVE':'wolfcage-colorpicker-inactive',
-            'COLORPICKER_ACTIVE_HEX':'wolfcage-colorpicker-active-hex',
-            'COLORPICKER_BORDER_HEX':'wolfcage-colorpicker-border-hex',
-            'COLORPICKER_INACTIVE_HEX':'wolfcage-colorpicker-inactive-hex'
+        },
+        'COLORPICKER':{
+            'CONTAINER':'wolfcage-colorpicker',
+            'ACTIVE_HEX':'wolfcage-colorpicker-active-hex',
+            'BORDER_HEX':'wolfcage-colorpicker-border-hex',
+            'INACTIVE_HEX':'wolfcage-colorpicker-inactive-hex'
         },
         'TABS':{
             'CONTAINER':'wolfcage-tab-container'


Added missing / at the end of url to match DS

I "fixed" the url used so that they match what is declared in the routes
of the data-system.

(I also replaced hard spaces by "normal" spaces.)

--- src/api.coffee
+++ src/api.coffee
@@ -201,14 +201,14 @@

     # Public: Create a Sharing and send request to all the targets
-    # Warning : Your app need to ask the 'Sharing' permission
+    # Warning : Your app need to ask the 'Sharing' permission
     # data - {Object}
     #   :rules - [rule], composed of id and docType fields
     #   :targets - [target], composed of recipientUrl fields
     #   :continuous - {boolean}, sync sharing
     #   :desc - {String}, human-readable description
     # callback - {Function}({Error} err)
-    #
+    #
     # Returns null
     createSharing: (data, callback) ->
         client.post "services/sharing/", data, (err, res, body) ->
@@ -216,12 +216,12 @@

     # Public: Answer a Sharing request
-    # Warning : Your app need to ask the 'Sharing' permission
+    # Warning : Your app need to ask the 'Sharing' permission
     # data - {Object}
     #   :id - {String}, id of the recipient's Sharing document
     #   :accepted - {boolean}
     # callback - {Function}({Error} err)
-    #
+    #
     # Returns null
     answerSharing: (data, callback) ->
         client.post "services/sharing/sendAnswer", data, (err, res, body) ->
@@ -229,23 +229,23 @@

     # Public: Revoke a Sharing from the sharer side
-    # Warning : Your app need to ask the 'Sharing' permission
+    # Warning : Your app need to ask the 'Sharing' permission
     # id - {String}, id of the sharer's Sharing document, also called shareID
     # callback - {Function}({Error} err)
-    #
+    #
     # Returns null
     revokeSharingFromSharer: (id, callback) ->
-        path = "services/sharing/sharer/#{id}"
+        path = "services/sharing/sharer/#{id}/"
         client.post path, {}, (err, res, body) ->
             callback err, body

     # Public: Revoke a target from the sharer side
-    # Warning : Your app need to ask the 'Sharing' permission
+    # Warning : Your app need to ask the 'Sharing' permission
     # id - {String}, id of the sharer's Sharing document, also called shareID
     # target - {String}, url of the target
     # callback - {Function}({Error} err)
-    #
+    #
     # Returns null
     revokeSharingTargetFromSharer: (id, target, callback) ->
         path = "services/sharing/sharer/#{id}/#{target}/"
@@ -254,13 +254,13 @@

     # Public: Revoke a Sharing from the recipient side
-    # Warning : Your app need to ask the 'Sharing' permission
+    # Warning : Your app need to ask the 'Sharing' permission
     # id - {String}, id of the recipient's Sharing document
     # callback - {Function}({Error} err)
-    #
+    #
     # Returns null
     revokeSharingFromRecipient: (id, callback) ->
-        path = "services/sharing/target/#{id}"
+        path = "services/sharing/target/#{id}/"
         client.post path, {}, (err, res, body) ->
             callback err, body


Bugfix.

Bugfix.

--- src/app.coffee
+++ src/app.coffee
@@ -339,6 +339,7 @@
             sm.resolveImmediately()

         setRadiusFilter: (radius) ->
+            @services.reset []
             @units.reset []
             @units.clearFilters()
             @units.overrideComparatorKeys = [


Fix portal double removal

Fix portal double removal

portal gets unmounted after navigation but it's already been removed
before navigation. It still needs to be able to be removed if anything
causes it to unmount before navigation.

--- resources/assets/coffee/react/_components/notification-banner.coffee
+++ resources/assets/coffee/react/_components/notification-banner.coffee
@@ -38,7 +38,8 @@

   removePortalContainer: =>
-    notificationBanners[0].removeChild @portalContainer
+    if @portalContainer.offsetParent?
+      notificationBanners[0].removeChild @portalContainer

   createPortalContainer: =>


render people even on team error

render people even on team error

--- controllers/teams.coffee
+++ controllers/teams.coffee
@@ -73,7 +73,7 @@
     if req.team.errors
       req.team.people (err, people) ->
         return next err if err
-        res.render2 'teams/edit', team: req.team, people: req.people
+        res.render2 'teams/edit', team: req.team, people: people
     else
       res.redirect "/teams/#{req.team.id}"
   null


Better error reporting

Better error reporting

--- src/gpg.coffee
+++ src/gpg.coffee
@@ -43,7 +43,7 @@
          newUsers.push user
          next()
       else
-         next new Error 'missing public key'
+        next new Error "missing public key #{JSON.stringify user}"
     ), (err) ->
       if err then return cb err
       cb null, newUsers


Remove common/ticking from main.coffee

Remove common/ticking from main.coffee

--- bokehjs/src/coffee/main.coffee
+++ bokehjs/src/coffee/main.coffee
@@ -30,7 +30,6 @@
   Bokeh.Random = require("common/random")
   Bokeh.safebind = require("common/safebind")
   Bokeh.SVGColors = require("common/svg_colors")
-  Bokeh.ticking = require("common/ticking")
   Bokeh.ViewState = require("common/view_state")

   # mappers


keep the log when copying PlayerStates

keep the log when copying PlayerStates

--- gameState.coffee
+++ gameState.coffee
@@ -238,6 +238,7 @@
     other.setAside = @setAside.slice(0)
     other.moatProtected = @moatProtected
     other.ai = @ai
+    other.log = @log
     other.turnsTaken = @turnsTaken
     other


Use different channels for each test as they might not have been removed from the previous test

--- test/test.coffee
+++ test/test.coffee
@@ -6,34 +6,34 @@

 test "it should create subscritions for action-cable-subscription elements in the dom", 2, (assert)->
   withContent """
-    <action-cable-subscription channel="ChatChannel"></action-cable-subscription>
+    <action-cable-subscription channel="Chat5Channel"></action-cable-subscription>
   """
-  equal window.cable.subscriptions.findAll("{\"channel\":\"ChatChannel\"}").length, 1
+  equal window.cable.subscriptions.findAll("{\"channel\":\"Chat5Channel\"}").length, 1

   withContent ""
   done = assert.async()
   requestAnimationFrame(->
-    equal window.cable.subscriptions.findAll("{\"channel\":\"ChatChannel\"}").length, 0
+    equal window.cable.subscriptions.findAll("{\"channel\":\"Chat5Channel\"}").length, 0
     done()
   ,100)

 test "it should trigger events on the elements for messages received through the channel", ->
   withContent """
-    <action-cable-subscription id='event-test' channel='ChatChannel'>
+    <action-cable-subscription id='event-test' channel='Chat4Channel'>
   """
   $('#event-test').on "cable:received", (e, data)->
     deepEqual data, {"message": "Hello"}

-  window.cable.subscriptions.notify("{\"channel\":\"ChatChannel\"}","received", {"message": "Hello"})
+  window.cable.subscriptions.notify("{\"channel\":\"Chat4Channel\"}","received", {"message": "Hello"})

 test "it should perform actions when elements with data-cable-action attributes are clicked", ->
   withContent """
-    <action-cable-subscription id='action-test' channel='ChatChannel'>
+    <action-cable-subscription id='action-test' channel='Chat3Channel'>
       Action
     </action-cable-subscription>
   """

-  subscription = window.cable.subscriptions.findAll("{\"channel\":\"ChatChannel\"}")[0]
+  subscription = window.cable.subscriptions.findAll("{\"channel\":\"Chat3Channel\"}")[0]
   subscription.perform = (action, data)->
     equal action, 'doAction'

@@ -42,11 +42,11 @@
 test "it should perform actions when 'cable:perform' events are triggered on the element", (assert)->
   done = assert.async()
   withContent """
-    <action-cable-subscription id='action-test' channel='ChatChannel'>
+    <action-cable-subscription id='action-test' channel='Chat2Channel'>
     </action-cable-subscription>
   """

-  subscription = window.cable.subscriptions.findAll("{\"channel\":\"ChatChannel\"}")[0]
+  subscription = window.cable.subscriptions.findAll("{\"channel\":\"Chat2Channel\"}")[0]
   subscription.perform = (action, data)->
     equal action, 'doAction'
     done()
@@ -57,9 +57,9 @@
 test "it should trigger events on the on the element that caused the subscription to be created", ->
   expect 2
   withContent """
-    <action-cable-subscription id='test1' channel='RoomChannel'>
+    <action-cable-subscription id='test1' channel='Room1Channel'>
     </action-cable-subscription>
-    <action-cable-subscription id='test2' channel='MentionChannel'>
+    <action-cable-subscription id='test2' channel='Mention1Channel'>
     </action-cable-subscription>
   """
   $('#test1').on "cable:received", (e, data)->
@@ -68,6 +68,6 @@
   $('#test2').on "cable:received", (e, data)->
     equal data, 2

-  window.cable.subscriptions.notify("{\"channel\":\"RoomChannel\"}","received", 1)
-  window.cable.subscriptions.notify("{\"channel\":\"MentionChannel\"}","received", 2)
+  window.cable.subscriptions.notify("{\"channel\":\"Room1Channel\"}","received", 1)
+  window.cable.subscriptions.notify("{\"channel\":\"Mention1Channel\"}","received", 2)


change HUBOT_IRC_USERNAME? to HUBOT_IRC_USERNAME

--- src/irc.coffee
+++ src/irc.coffee
@@ -61,7 +61,7 @@
       unflood:  process.env.HUBOT_IRC_UNFLOOD?
       debug:    process.env.HUBOT_IRC_DEBUG?
       usessl:   process.env.HUBOT_IRC_USESSL?
-      userName: process.env.HUBOT_IRC_USERNAME?
+      userName: process.env.HUBOT_IRC_USERNAME

     client_options =
       userName: options.userName,


removed obsolete file

removed obsolete file

--- tests/run.coffee
+++ tests/run.coffee
@@ -1,20 +0,0 @@
-# A small subset of the run.js written in coffeescript
-
-do (casper) ->
-  steps = 0
-
-  casper.options.onStepComplete = -> steps++
-
-  casper.start "tests/site/index.html", ->
-    @test.assertTitle "CasperJS test index", "Casper.start() casper can start itself an open an url"
-    @test.assertEquals @fetchText("ul li"), "onetwothree", "Casper.fetchText() can retrieves text contents"
-    @click "a[href=\"test.html\"]"
-
-  casper.then ->
-    @test.assertTitle "CasperJS test target", "Casper.click() casper can click on a text link"
-    @click "a[href=\"form.html\"]"
-
-  casper.run ->
-    @test.assertEquals steps, 3, "Casper.options.onStepComplete() is called on step complete"
-    @options.onStepComplete = null
-    @test.done()


remove tabs

remove tabs

--- src/wow.coffee
+++ src/wow.coffee
@@ -202,9 +202,9 @@
     box.style.visibility = 'visible' for box in @boxes

   resetAnimation: (event) ->
-  	if event.type.toLowerCase().indexOf('animationend') >= 0
-  		target = event.target || event.srcElement
-  		target.className = target.className.replace(@config.animateClass, '').trim()
+    if event.type.toLowerCase().indexOf('animationend') >= 0
+      target = event.target || event.srcElement
+      target.className = target.className.replace(@config.animateClass, '').trim()

   customStyle: (box, hidden, duration, delay, iteration) ->
     @cacheAnimationName(box) if hidden


add missing tag api attr `writable` to builder

--- src/xml.coffee
+++ src/xml.coffee
@@ -305,6 +305,7 @@
         # states
         @closed = no
         @isempty = yes
+        @writable = true
         # defaults
         @opts.pretty ?= off
         @level = @opts.level ? -1
@@ -334,6 +335,7 @@

     end: () =>
         @closed = yes
+        @writable = no
         @emit 'close', this # tag api
         @emit 'end'
         this


set level default to 0 for Tag

set level default to 0 for Tag

--- src/xml.coffee
+++ src/xml.coffee
@@ -94,7 +94,7 @@
     constructor: ->
         [@name, @attrs, children, opts] = parse_args arguments...
         @pretty = opts.pretty ? off
-        @level = opts.level
+        @level = opts.level ? 0
         @builder = opts.builder #or new Builder # inheritence
         @buffer = [] # after this tag all children emitted data
         @pending = [] # no open child tag


Updating gruntfile with config check

Updating gruntfile with config check

--- gruntfile.coffee
+++ gruntfile.coffee
@@ -1,7 +1,7 @@
 module.exports = (grunt) ->

   # Project configuration.
-  grunt.initConfig
+  config =
     pkg: grunt.file.readJSON 'package.json'
     coffee:
       compile:
@@ -59,11 +59,14 @@
       files: ['src/*.coffee', 'src/features/*.coffee', 'src/builder/*.coffee', 'src/css/*.css']
       tasks: ['coffee', 'concat', 'karma', 'uglify', 'cssmin', 'compress']

+  # Do we have credentials?
+  if grunt.file.exists('aws-credentials.json')
+
     # Load aws credentials,
-    aws: grunt.file.readJSON 'aws-credentials.json'
+    config.aws = grunt.file.readJSON 'aws-credentials.json'

     # Rename files.
-    copy:
+    config.copy =
       aws:
         files: [
           {flatten: true, src: 'dist/gzip/moltin.min.js', dest: 'dist/gzip/v1', filter: 'isFile'}
@@ -71,7 +74,7 @@
         ]

     # Upload files to s3.
-    aws_s3:
+    config.aws_s3 =
       options:
         accessKeyId: '<%= aws.access %>'
         secretAccessKey: '<%= aws.secret %>'
@@ -91,6 +94,9 @@
           {dest: '/', exclude: 'builder', flipExclude: true, action: 'delete'}
         ]

+  # Initialize grunt.
+  grunt.initConfig config
+
   # These plugins provide necessary tasks.
   grunt.loadNpmTasks 'grunt-contrib-coffee'
   grunt.loadNpmTasks 'grunt-contrib-uglify'


Fixed one possible null exeption in event manager

--- js/foundation/eventmanager/eventmanager.coffee
+++ js/foundation/eventmanager/eventmanager.coffee
@@ -766,6 +766,7 @@
         continue
       continue if _newActive != null and _viewId == _newActive.viewId
       _ctrl = @_views[_viewId]
+      continue unless _ctrl?
       if _ctrl.isDead and !@isProduction
         console.warn('trying to deactivate dead control!')
       _ctrl.active = false