18: Changed name to y-richtext

18: Changed name to y-richtext

--- lib/y-rt.coffee
+++ lib/y-rt.coffee
@@ -1,436 +0,0 @@
-_ = require 'underscore'
-misc = require './misc'
-relativeFromAbsolute = misc.relativeFromAbsolute
-absoluteFromRelative = misc.absoluteFromRelative
-Word = misc.Word
-Selection = misc.Selection
-
-# XRegExp = require('xregexp').XRegExp
-WordRegExp = /\S+\s*/g
-PreSpacesRegExp = /^\s+/
-PostSpacesRegExp = /\s+$/
-
-# Class describing the Rich Text Editor type
-#
-class YRichText extends BaseClass
-  # @property [Options] _rt the RTE object
-  # @param [String] content the initial content to set
-  constructor: (content = '')->
-    if content.constructor isnt String
-      throw new Error "Only accepts strings."
-    @_rt = {}
-    @_rt.selections = []
-    @_rt.words = []
-    @pushString content
-
-  _name: "Rich Text Editor"
-
-  _getModel: (Y, Operation) ->
-    if @_model == null
-      @_model = new Operation.MapManager(@).execute()
-      @_model.val(words, @_rt.words)
-      @_model.val(selections, @_rt.selections)
-
-
-  # @overload val()
-  #   Return the value of the Y.RichText instance as a non formatted string
-  #
-  # @overload val(content)
-  #   Set the content of the Y.RichText instance
-  #   @param content [String] Set the strings of the Y.RichText to this content
-  val: (content)->
-    if not _.isUndefined(content)
-      # reset styles when replacing content
-      @_rt.words = []
-      @_rt.style = []
-      @pushString content
-    else
-      # TODO: support breaks (br, new paragraph, …)
-      (e.word for e in @_rt.words).join('')
-
-  # Returns the word object of a word.
-  # @param index [Integer] the index of the word to return
-  getWord: (index) ->
-    if @_rt.words.length == 0 or index == @_rt.words.length
-      return (new Word '', @)
-
-    if not (0 <= index < @_rt.words.length)
-      throw new Error "Index out of bounds #{index}"
-    @_rt.words[index]
-
-  # Returns the *word objects* within boundaries
-  # @param begin [Integer] the first word the return
-  # @param end [Integer] the first word /not/ to return
-  getWords: (begin, end) ->
-    if _.isUndefined(end)
-      end = @_rt.words.length
-    if not (0 <= begin <= end <= @_rt.words.length)
-      return []
-
-    ret = @_rt.words[begin..end]
-    if ret
-      ret
-    else
-      []
-
-  # Set the content of a word
-  # @param index [Integer] the index of the word to modify
-  # @param content [String] the content to set the word to
-  # @note it pushes all selection to the end of the word
-  setWord: (index, content) ->
-    if index == @_rt.words.length
-      @_rt.words.push(new Word content, @)
-    if not (0 <= index < @_rt.words.length)
-      throw new Error "Index out of bounds"
-
-    word = @getWord(index)
-    word.word = content
-
-    return word
-
-  # Append new words at the end of the word list
-  # @param [String] content the string to append
-  pushString: (content) ->
-    preSpaces = content.match PreSpacesRegExp
-    if preSpaces isnt null
-      @_rt.words.push (new Word (preSpaces[0]), @)
-    words = content.match WordRegExp
-    if _.isArray(words)
-      for w in words
-        @_rt.words.push (new Word w, @)
-
-  # Insert words at position
-  #
-  # @param [Integer] position the position where to insert words
-  # @param [Array<String>] words the words to insert at position
-  #
-  # @return [Array<Word>] an array of word objects insertd
-  insertWords: (position, words)->
-    if not _.isNumber position
-      throw new Error "Expected a number as first parameter, got #{position}"
-    if not _.isArray words
-      throw new Error "Expected a string array as second parameter, got #{words}"
-
-    length = @_rt.words.length
-    if 0 <= position <= length
-      wordsObj = ((new Word w, @) for w in words)
-      Array.prototype.splice.apply(@_rt.words, [position, 0].concat(wordsObj))
-      # left = @_rt.words.slice(0, position)
-      # right = @_rt.words.slice(position)
-      # @_rt.words = left.concat(wordsObj).concat(right)
-
-      return wordsObj or []
-
-    else
-      throw new Error 'Index #{position} out of bound in word list'
-
-
-  # @overload deleteWords (start, end)
-  #   Delete all the words between start and end
-  #
-  #   @param [Integer] start position of first word to delete
-  #   @param [Integer] end position of last word to delete
-  #
-  # @overload deleteWords (position)
-  #   Delete the word at position
-  #
-  #   @param [Integer] position position the word to delete
-  #
-  # @note selections that where bound to any of the deleted word
-  # are moved to the beginning of the first word not deleted
-  deleteWords: (start, end) ->
-    if _.isUndefined(end)
-      end = start+1
-
-    if start <= end
-      indexStart = absoluteFromRelative start, 0, @
-      length = (@getWords 0).length
-
-      endPos = (@getWord (end-1)).word.length-1
-      indexEnd = absoluteFromRelative (end-1), endPos, @
-
-      # remove all the selections within deleted area
-      # and change the extremities of those overlapping
-      opts = {bind: false}
-      selection = new Selection indexStart, indexEnd, @, opts
-
-      selection.deleteHelper (@getWord (end))
-
-      # remove words
-      @_rt.words.splice start, (end-start)
-
-
-  # Merge two words at position
-  #
-  # @param [Integer] n position of word where to perform merge. The merge will
-  # be done with the word at right (if any)
-  merge: (index) ->
-    if 0 <= index < @getWords(0).length
-      word = @getWord index
-      # use slice to force copy of array
-      [selLeft, selRight] = [word.left.slice(), word.right.slice()]
-      for selection in  _.uniq(selLeft.concat(selRight))
-        # console.log "Merge — unbinding " + selection.print()
-        #selection.unbind()
-        @removeSel selection
-
-      word.left = word.right = []
-      # remove word at position index
-      @deleteWords index
-      # insert its content at position where is used to be
-      pos = absoluteFromRelative index, 0, @
-
-      @insert pos, word.word.trimRight()
-
-      # the new word is here
-      newWord = @getWord index
-
-      # console.log "Merge — before selLeft", selLeft
-      for selection in selLeft
-        # console.log "Merge — rebinding " + selection.print()
-        selection.rebind newWord, null
-        @pushSel selection
-      # # console.log "Merge — before selRight", selRight
-      for selection in selRight
-        # console.log "Merge — rebinding " + selection.print()
-        selection.rebind null, newWord
-        @pushSel selection
-    else
-      throw new Error "Impossible to merge"
-
-  # Delete text under selection
-  #
-  # @param [Selection] sel the selection to delete
-  #
-  deleteSel: (selection) ->
-    if not selection.isValid()
-      throw new Error "Invalid selection, got", selection
-
-    left = selection.left
-    right = selection.right
-
-    leftIndex = left.index @
-    rightIndex = right.index @
-
-    newLeft = left.word.substring 0, selection.leftPos
-    newRight = right.word.substring selection.rightPos
-
-    # delete selections inside
-    if left == right
-      wordToBound = @setWord leftIndex, (newLeft + newRight)
-      posToBound = selection.leftPos
-
-    else
-      @setWord leftIndex, newLeft
-      wordToBound = @setWord rightIndex, newRight
-
-      # delete the words in between
-      @deleteWords leftIndex+1, rightIndex
-      # merge
-      @merge leftIndex
-
-      posToBound = 0
-
-    selection.deleteHelper wordToBound, posToBound
-
-  # Insert text at position
-  #
-  # @param [Integer] position The position where to insert text
-  # @param [String] content the content to insert
-  #
-  insert: (position, content)->
-    if not (_.isNumber position)
-      throw new Error "Expected an integer as first argument"
-    if not (_.isString content)
-      throw new Error "Expected a string as second argument"
-
-    if content.length == 0
-      return
-
-    ret = relativeFromAbsolute position, @
-
-    index = ret.word   #position to work from
-    pos = ret.pos
-
-    preSpaces = content.match PreSpacesRegExp
-    currWordObj = @getWord(index)
-    currWord = currWordObj.word
-
-    # selections at right of insertion point
-    selAtRight = currWordObj.getSelections((s) -> s.leftPos >= pos or s.rightPos >= pos)
-    oldLength = currWord.length
-    selAtLeft = currWordObj.getSelections((s) -> s.leftPos < pos or s.rightPos < pos)
-
-    # move the spaces to the previous word if a pos == 0
-    if preSpaces isnt null
-      if pos == 0
-        if index == 0
-          index++
-          @insertWord 0, (new Word '', @)
-        prevWord = @getWord(index-1).word
-        prevWord += preSpaces
-        content = content.substring(preSpaces.length)
-        @setWord (index-1), prevWord
-
-    # insert the content at position
-    currWord = currWord.substring(0, pos) + content + currWord.substring(pos)
-
-    # cut the word
-    newWords = currWord.match WordRegExp or []
-    # get the pre spaces
-    tmp = currWord.match PreSpacesRegExp or ""
-    if tmp isnt null
-      newWords[0] = tmp + (newWords[0] or "")
-
-    # get the new words inserted as an array
-    tmp1 = [@setWord index, newWords[0]]
-    tmp2 = @insertWords index+1, newWords[1..]
-    wordsInserted = tmp1.concat(tmp2)
-
-    # update the positions of all selections
-    lastWord = _.last wordsInserted
-    newLength = lastWord.word.length
-    for sel in selAtRight
-      if sel.left == currWordObj
-        sel.left = lastWord
-        sel.leftPos = sel.leftPos + newLength - oldLength
-      if sel.right == currWordObj
-        sel.right = lastWord
-        sel.rightPos = sel.rightPos + newLength - oldLength
-      sel.bind sel.left, sel.right
-
-
-  # Relative jump from position
-  #
-  _jump: (position, relJump) ->
-    word = position.word
-    pos = position.pos
-
-    if jump < 0
-      jump = Math.abs(jump)
-      while jump > 0
-        if pos < jump
-          word--
-          jump -= pos
-          pos = @getWord(word).word.length - 1
-
-          if word < 0
-            return null
-        else
-          pos -= jump
-    else if jump > 0
-      while jump > 0
-        delta = pos + jump - @getWord(word).word.length + 1
-        if delta > 0
-          word++
-          jump -= delta
-          pos = 0
-
-          if word >= @getWords(0).length
-            return null
-        else
-          pos += jump
-    else
-      word = word
-      pos = pos
-    {word: word, pos: pos}
-
-  # Apply a delta to the object
-  # @see http://quilljs.com/docs/deltas/
-  #
-  delta: (deltas) ->
-    position = 0
-    for delta in deltas.ops
-      if delta.retain?
-        opts = {bind: false}
-        if delta.attributes?
-          opts.bind = true
-          opts.style = delta.attributes
-        selection = new Selection position, (position + delta.retain), @, opts
-        position += delta.retain
-
-      else if delta.delete?
-        opts = {bind: false}
-        selection = new Selection position, (position + delta.delete), @, opts
-        @deleteSel selection
-
-      else if delta.insert?
-        opts = {bind: false}
-        @insert position, delta.insert
-        if delta.attributes?
-          opts = {style: delta.attributes, bind: true}
-          end = position + delta.insert.length
-          selection = new Selection position, end, @, opts
-        position += delta.insert.length
-
-      else
-        throw new Error "Unknown operation"
-
-      if delta.attributes?
-        selectionList = @getSelections((s) -> s!=selection and s)
-        for sel in selectionList
-          selection.merge sel
-          if sel.left and sel.right # sel might have been unbound in previous merge
-            selection.split sel
-
-  # Add a  selection to the selection list
-  pushSel: (selection)->
-    # console.log "Pushing", selection, "in", @_rt.selections
-    selections = @_rt.selections
-    if !(selection in selections)
-      @_rt.selections.push selection
-
-  # @overload getSelections()
-  #   Return all the selections
-  #   @return [Array<Selection>] an array of selection
-  #
-  # @overload getSelections(filter)
-  #   Return all the selections and filter using filter
-  #   @param [Function] filter the function to use for filtering
-  #   @return [Array<Selection>] an array of selection
-  getSelections: (filter = null)->
-    # console.log "Y.RichText.getSelections", @_rt.selections
-    tmp = (if _.isFunction(filter)
-      @_rt.selections.filter(filter) or []
-    else
-      @_rt.selections or [])
-    _.uniq tmp
-
-
-  # Remove a selection from selection list
-  #
-  # @param [Selection] selection the selection to remove
-  removeSel: (selection) ->
-    index = 0
-    array = @_rt.selections
-    for el, index in array
-      if (array[index] == selection)
-        array.splice index, 1
-        break
-
-  # Only for manual use, so no efficiency research
-  garbageCollect: ->
-    sels = @_rt.selections
-    for sel, index in sels
-      for key, value of sel
-        if value == null or value == ""
-          sel.unbind()
-          @removeSel sel
-
-
-if window?
-  if window.Y?
-    window.Y.RichText = YRichText
-  else
-    throw new Error "Import first Y!"
-  window.Selection = Selection
-  window.Word = Word
-  window.relativeFromAbsolute = relativeFromAbsolute
-  window.absoluteFromRelative = absoluteFromRelative
-
-if module?
-  module.exports.YRichText = YRichText
-  module.exports.Selection = Selection
-  module.exports.Word = Word
-  module.exports.relativeFromAbsolute = relativeFromAbsolute
-  module.exports.absoluteFromRelative = absoluteFromRelative


Tests: Initial test for roundrobin

Tests: Initial test for roundrobin

--- spec/01transport.coffee
+++ spec/01transport.coffee
@@ -14,6 +14,13 @@
   'MQTT': 'mqtt://localhost'
   'AMQP': 'amqp://localhost'

+randomString = (n) ->
+  text = ""
+  possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
+  for i in [0...n]
+    idx = Math.floor Math.random()*possible.length
+    text += possible.charAt idx
+  return text

 zip = () ->
   lengthArray = (arr.length for arr in arguments)
@@ -72,6 +79,19 @@
     client.subscribeToQueue queue, ackHandler, cb

   async.map handlers, sub, callback
+
+subscribeDataNoAck = (handlers, callback) ->
+  sub = (h, cb) ->
+    [client, queue, handler] = h
+    client.subscribeToQueue queue, handler, cb
+  async.map handlers, sub, callback
+
+
+setupBindings = (broker, bindings, callback) ->
+  send = (b, cb) ->
+    broker.addBinding b, cb
+
+  async.map bindings, send, callback
 # End utils

@@ -263,12 +283,73 @@
                 chai.expect(err).to.not.exist

   describe 'Roundrobin binding', ->
-    describe 'data is ACKed', ->
-      it 'should be sent to only one consumer'
-      it 'should not be sent do deadletter queue'
-
-    describe 'data is NACKed', ->
-      it 'should be sent to deadletter queue'
+    describe 'sending ACKed message, then NACKed message', ->
+      received = null
+      beforeEach (done) ->
+        received = { worker1: [], worker2: [], deadletter: [] }
+        r = randomString '3'
+        outq =
+          sender: 'outQ-'+r
+        inq =
+          worker1: 'workerQ-'+r
+          worker2: 'workerQ-'+r
+          deadletter: 'deadletterQ-'+r
+        clientNames = Object.keys inq
+        clientNames = clientNames.concat Object.keys(outq)
+        createConnectClients address, clientNames, (err, clients) ->
+          chai.expect(err).to.not.exist
+
+          queues = []
+          for clientName, queueName of outq
+            queues.push [ clients[clientName], 'outqueue', queueName ]
+          for clientName, queueName of inq
+            queues.push [ clients[clientName], 'inqueue', queueName ]
+
+          createQueues queues, (err) ->
+            chai.expect(err).to.not.exist
+
+            bindings = [
+              { type: 'roundrobin', src: outq.sender, tgt: inq.worker1, deadletter: inq.deadletter }
+            ]
+            setupBindings broker, bindings, (err) ->
+              chai.expect(err).to.not.exist
+
+              # Setup queue subscribers
+              ackFunc = (data) ->
+                return 'nackMessage' if data.foo == 'nack'
+                return 'ackMessage'
+              onReceives =
+                worker1: (msg) ->
+                  console.log 'worker1 got', msg.data
+                  received.worker1.push msg.data
+                  clients.worker1[ackFunc(msg.data)] msg, () ->
+                worker2: (msg) ->
+                  console.log 'worker2 got', msg.data
+                  received.worker2.push msg.data
+                  clients.worker2[ackFunc(msg.data)] msg, () ->
+                deadletter: (msg) ->
+                  console.log 'deadletter got', msg.data
+                  received.deadletter.push msg.data
+                  clients.deadletter.ackMessage msg, () ->
+                  done()
+              handlers = []
+              for name in Object.keys inq
+                handlers.push [ clients[name], inq[name], onReceives[name] ]
+              subscribeDataNoAck handlers, (err) ->
+                chai.expect(err).to.not.exist
+
+                packets = [
+                  [ clients.sender, outq.sender, {foo: 'ack'} ]
+                  [ clients.sender, outq.sender, {foo: 'nack'} ]
+                ]
+                sendPackets packets, (err) ->
+                  chai.expect(err).to.not.exist
+
+      it 'each message is only sent to one worker', () ->
+        workerData = received.worker1.concat received.worker2
+        chai.expect(workerData).to.have.length 2
+      it 'only NACKed message is sent to deadletter', ->
+        chai.expect(received.deadletter).to.eql [ { foo: 'nack'} ]

 describe 'Transport', ->
   Object.keys(transports).forEach (type) =>


Fix specs in config

Fix specs in config

--- spec/config-spec.coffee
+++ spec/config-spec.coffee
@@ -164,13 +164,19 @@
       expect(observeHandler).toHaveBeenCalledWith atom.config.get("foo.bar.baz"), {previous: ['a', 'b', 'c']}

   describe ".getPositiveInt(keyPath, defaultValue)", ->
-    it "returns the proper current or default value", ->
+    it "returns the proper coerced value", ->
       atom.config.set('editor.preferredLineLength', 0)
-      expect(atom.config.getPositiveInt('editor.preferredLineLength', 80)).toBe 80
+      expect(atom.config.getPositiveInt('editor.preferredLineLength', 80)).toBe 1
+
+    it "returns the proper coerced value", ->
       atom.config.set('editor.preferredLineLength', -1234)
-      expect(atom.config.getPositiveInt('editor.preferredLineLength', 80)).toBe 80
+      expect(atom.config.getPositiveInt('editor.preferredLineLength', 80)).toBe 1
+
+    it "returns the default value when a string is passed in", ->
       atom.config.set('editor.preferredLineLength', 'abcd')
       expect(atom.config.getPositiveInt('editor.preferredLineLength', 80)).toBe 80
+
+    it "returns the default value when null is passed in", ->
       atom.config.set('editor.preferredLineLength', null)
       expect(atom.config.getPositiveInt('editor.preferredLineLength', 80)).toBe 80

@@ -452,18 +458,15 @@

         atom.config.setSchema('foo.bar', schema)

-        expect(atom.config.schema).toEqual
+        expect(atom.config.getSchema('foo')).toEqual
           type: 'object'
           properties:
-            foo:
+            bar:
               type: 'object'
               properties:
-                bar:
-                  type: 'object'
-                  properties:
-                    anInt:
-                      type: 'integer'
-                      default: 12
+                anInt:
+                  type: 'integer'
+                  default: 12

     describe '.getSchema(keyPath)', ->
       schema =


Fix unit test bug

Fix unit test bug

--- spec/helper-spec.coffee
+++ spec/helper-spec.coffee
@@ -16,7 +16,7 @@
       atom.packages.activatePackage('language-coffee-script')

     waitsForPromise ->
-      atom.project.open('helper-sample.coffee').then (o) ->
+      atom.workspace.open('helper-sample.coffee').then (o) ->
         editor = o

     runs ->


Don't let users create anonymous graded discussions in UI

closes VICE-2360
flag=discussion_anonymity

Test Plan:
 - turn on redesign and anonymity ff
 - try to create an anonymous graded discussion
 > should get error on save

Change-Id: I4c7c62e66d06906bc6bbef5950b6d29c09eb4998
Reviewed-on: https://gerrit.instructure.com/c/canvas-lms/+/281212
Tested-by: Service Cloud Jenkins <9144042a601061f88f1e1d7a1753ea3e2972119d@instructure.com>
Reviewed-by: Omar Soto-Fortuño <2ca100155bc0669318a7e6f3850629c7667465e1@instructure.com>
QA-Review: Omar Soto-Fortuño <2ca100155bc0669318a7e6f3850629c7667465e1@instructure.com>
Product-Review: Omar Soto-Fortuño <2ca100155bc0669318a7e6f3850629c7667465e1@instructure.com>

--- ui/features/discussion_topic_edit/backbone/views/EditView.coffee
+++ ui/features/discussion_topic_edit/backbone/views/EditView.coffee
@@ -472,6 +472,8 @@
   validateBeforeSave: (data, errors) =>
     if data.delay_posting == "0"
       data.delayed_post_at = null
+    if data.anonymous_state != 'full_anonymity' && data.anonymous_state != 'partial_anonymity'
+      data.anonymous_state = null
     if @isTopic() && data.set_assignment is '1'
       if @assignmentGroupSelector?
         errors = @assignmentGroupSelector.validateBeforeSave(data, errors)
@@ -481,6 +483,8 @@
       errors = @dueDateOverrideView.validateBeforeSave(validateBeforeSaveData, errors)
       errors = @_validatePointsPossible(data, errors)
       errors = @_validateTitle(data, errors)
+      if data.anonymous_state != null
+        errors['anonymous_state'] = [{message: I18n.t("You are not allowed to create an anonymous graded discussion")}]
     else
       @model.set 'assignment', @model.createAssignment(set_assignment: false)


Add spec for assigning new state properties via Marker::setRange

--- spec/marker-spec.coffee
+++ spec/marker-spec.coffee
@@ -216,3 +216,26 @@
           oldState: {}, newState: {}
           bufferChanged: false
         }]
+
+      it "allows new properties to be assigned to the state", ->
+        marker.setRange([[0, 1], [0, 2]], foo: 1)
+        expect(changes).toEqual [{
+          oldHeadPosition: [0, 9], newHeadPosition: [0, 2]
+          oldTailPosition: [0, 6], newTailPosition: [0, 1]
+          hadTail: true, hasTail: true
+          wasValid: true, isValid: true,
+          oldState: {}, newState: {foo: 1}
+          bufferChanged: false
+        }]
+
+        changes = []
+        marker.setRange([[0, 3], [0, 6]], bar: 2)
+        expect(marker.getState()).toEqual {foo: 1, bar: 2}
+        expect(changes).toEqual [{
+          oldHeadPosition: [0, 2], newHeadPosition: [0, 6]
+          oldTailPosition: [0, 1], newTailPosition: [0, 3]
+          hadTail: true, hasTail: true
+          wasValid: true, isValid: true,
+          oldState: {foo: 1}, newState: {foo: 1, bar: 2}
+          bufferChanged: false
+        }]


Use grammars from registry

Use grammars from registry

--- spec/syntax-spec.coffee
+++ spec/syntax-spec.coffee
@@ -15,7 +15,7 @@
       expect(atom.syntax.selectGrammar(filePath).name).not.toBe 'Ruby'
       atom.syntax.setGrammarOverrideForPath(filePath, 'source.ruby')
       syntax2 = atom.deserializers.deserialize(atom.syntax.serialize())
-      syntax2.addGrammar(grammar) for grammar in atom.syntax.grammars when grammar isnt atom.syntax.nullGrammar
+      syntax2.addGrammar(grammar) for grammar in atom.syntax.registry.grammars when grammar isnt atom.syntax.registry.nullGrammar
       expect(syntax2.selectGrammar(filePath).name).toBe 'Ruby'

   describe ".selectGrammar(filePath)", ->


update hostConnected on disconnect

update hostConnected on disconnect

--- bin/comCenter.coffee
+++ bin/comCenter.coffee
@@ -63,6 +63,9 @@
             if @now.group?
                 nowjs.getGroup(@now.group).now.receiveMessage({fromUser:"server", message:"#{@now.name} has left the session.", messageType:'serverMessage'})

+            if groupData[@now.group].hostClientId == @user.clientId
+                groupData[@now.group].hostConnected == false
+

             #remove the client from any rooms he's currently in
             nowjs.getGroup(@now.group).removeUser(@user.clientId)
@@ -70,7 +73,7 @@
         )

         @everyone.now.joinGroup = (groupName) ->
-            console.log('groupname: '+groupName)
+            console.log('groupname: ' + groupName)
             #remove the client from any rooms he's currently in
             nowjs.getGroup(@now.group).removeUser(@user.clientId)

@@ -132,8 +135,6 @@
                 @now.receiveMessage({message:message,fromUser:fromUser,messageType:'privateMessage'})

-
-
         @everyone.now.updateGroupData = (data) ->
             #data should be an object.  Update groupData with the data object

@@ -141,7 +142,10 @@
                 groupData[@now.group][key] = val

         @everyone.now.connectHost = ->
+            #Update info on the host for the session
             groupData[@now.group].hostConnected = true
+            groupData[@now.group].hostClientId = @user.clientId
+

             functionInfo = {functionName:'hostIsConnected',args:groupData[@now.group]}


Don't trigger delete event on move to avoid switching to root document


Fix discussion module

Fix discussion module

It worked in Chrome because Chrome appears to magically infer some missing
arguments to methods. This is the second time this happened, should be more
careful about it in the future, because it causes confusion when features
work for some students but not others.

--- lms/static/coffee/src/discussion/discussion_module_view.coffee
+++ lms/static/coffee/src/discussion/discussion_module_view.coffee
@@ -56,12 +56,12 @@
         url: url
         type: "GET"
         dataType: 'json'
-        success: (response, textStatus, jqXHR) => @renderDiscussion(event, response, textStatus, discussionId)
+        success: (response, textStatus, jqXHR) => @renderDiscussion($elem, response, textStatus, discussionId)

-    renderDiscussion: (event, response, textStatus, discussionId) =>
+    renderDiscussion: ($elem, response, textStatus, discussionId) =>
       window.user = new DiscussionUser(response.user_info)
       Content.loadContentInfos(response.annotated_content_info)
-      $(event.target).html("Hide Discussion")
+      $elem.html("Hide Discussion")
       @discussion = new Discussion()
       @discussion.reset(response.discussion_data, {silent: false})
       $discussion = $(Mustache.render $("script#_inline_discussion").html(), {'threads':response.discussion_data, 'discussionId': discussionId})


Crop url for href with length > 100

Crop url for href with length > 100

--- lib/asciidoc-preview-view.coffee
+++ lib/asciidoc-preview-view.coffee
@@ -133,7 +133,9 @@
       if hrefLink = link.attr('href')
         do(hrefLink) ->
           link.on 'mouseover', (e) ->
-            atom.workspaceView.statusBar?.prependRight("#{hrefLink}")
+            # TODO Use constant
+            cropUrl = if (hrefLink.length > 100) then hrefLink.substr(0, 97).concat('...')  else hrefLink
+            atom.workspaceView.statusBar?.prependRight("#{cropUrl}")
           link.on 'mouseleave', (e) ->
             $('#linkUrl').remove()
         continue if not hrefLink.match(/^#/)


cassandra: put another reconnect in -- I was only catching 1 of two cases.

--- salvus/cassandra.coffee
+++ salvus/cassandra.coffee
@@ -669,7 +669,7 @@
                     # The 'any of its parents' is because often when the server is loaded it rejects requests sometimes
                     # with "no permissions. ... any of its parents".
                     if error? and ("#{error}".indexOf("peration timed out") != -1 or "#{error}".indexOf("any of its parents") != -1)
-                        winston.error("... so probably re-doing query after re-connecting")
+                        winston.error("... so (probably) re-doing query after reconnecting")
                         @connect () =>
                             c(error)
                     else
@@ -685,10 +685,11 @@

         f = (c) =>
             failed = () =>
-                m = "query #{query}, params=#{misc.to_json(vals).slice(0,1024)}, timed out with no response at all after #{@query_timeout_s} seconds -- likely retrying"
+                m = "query #{query}, params=#{misc.to_json(vals).slice(0,1024)}, timed out with no response at all after #{@query_timeout_s} seconds -- likely retrying after reconnecting"
                 winston.error(m)
-                c(m)
-                c = undefined # ensure only called once
+                @connect () =>
+                    c(m)
+                    c = undefined # ensure only called once
             _timer = setTimeout(failed, 1000*@query_timeout_s)
             g (err) =>
                 clearTimeout(_timer)


Map json fence to json extension

Map json fence to json extension

--- lib/markdown-preview-view.coffee
+++ lib/markdown-preview-view.coffee
@@ -15,6 +15,7 @@
   'java': 'java'
   'javascript': 'js'
   'js': 'js'
+  'json': 'json'
   'mustache': 'mustache'
   'python': 'py'
   'rb': 'rb'


Added word alignment to binary format

Added word alignment to binary format

--- src/goo/loaders/handlers/MeshDataHandler.coffee
+++ src/goo/loaders/handlers/MeshDataHandler.coffee
@@ -21,17 +21,13 @@
 	_getTypedArray = (bindata, pointer)->
 		[start, length, format] = pointer
 		if format == 'float32'
-			#new Float32Array(bindata, start, length) Inexplicably doesn't work
-			new Float32Array(bindata.slice(start, start+length*4))
+			new Float32Array(bindata, start, length)
 		else if format == 'uint8'
-			#new Uint8Array(bindata, start, length)
-			new Uint8Array(bindata.slice(start, start+length))
+			new Uint8Array(bindata, start, length)
 		else if format == 'uint16'
-			#new Uint16Array(bindata, start, length)
-			new Uint16Array(bindata.slice(start, start+length*2))
+			new Uint16Array(bindata, start, length)
 		else if format == 'uint32'
-			#new Uint32Array(bindata, start, length)
-			new Uint32Array(bindata.slice(start, start+length*4))
+			new Uint32Array(bindata, start, length)
 		else
 			throw new Error("Binary format #{format} is not supported")


use ActivityLogger in stash drop

use ActivityLogger in stash drop

--- lib/models/git-stash-drop.coffee
+++ lib/models/git-stash-drop.coffee
@@ -1,11 +1,8 @@
-git = require '../git'
-notifier = require '../notifier'
-OutputViewManager = require '../output-view-manager'
+git = require('../git-es').default
+ActivityLogger = require('../activity-logger').default

 module.exports = (repo) ->
   cwd = repo.getWorkingDirectory()
-  git.cmd(['stash', 'drop'], {cwd}, color: true)
-  .then (msg) ->
-    OutputViewManager.getView().showContent(msg) if msg isnt ''
-  .catch (msg) ->
-    notifier.addInfo msg
+  git(['stash', 'drop'], {cwd, color: true})
+  .then (result) ->
+    ActivityLogger.record(Object.assign({message: 'Drop stash'}, result))


P1 DONE: for all spec #270

P1 DONE: for all spec #270

--- spec/spec-helper.coffee
+++ spec/spec-helper.coffee
@@ -1,4 +1,4 @@
-SPEC_ENSURE_LEVEL = 0
+SPEC_ENSURE_LEVEL = 1
 # -------------------------
 _ = require 'underscore-plus'
 {Range, Point} = require 'atom'


Don’t bind window:close in spec helper

Don’t bind window:close in spec helper

--- spec/spec-helper.coffee
+++ spec/spec-helper.coffee
@@ -27,8 +27,6 @@

 fixturePackagesPath = path.resolve(__dirname, './fixtures/packages')
 atom.packages.packageDirPaths.unshift(fixturePackagesPath)
-
-window.addEventListener 'core:close', -> window.close()

 document.querySelector('html').style.overflow = 'auto'
 document.body.style.overflow = 'auto'


use `links` instead of `drilldown_uri` for external linking

--- lib/repliers/query_runner.coffee
+++ lib/repliers/query_runner.coffee
@@ -52,8 +52,9 @@

     renderField = (f, row) =>
       d = row[f.name]
-      if d.drilldown_uri && (f.is_measure || f.measure)
-        "<#{@replyContext.looker.url}#{d.drilldown_uri}|#{renderString(d)}>"
+      drill = d.links?[0]
+      if drill && drill.type == "measure_default"
+        "<#{@replyContext.looker.url}#{drill.url}|#{renderString(d)}>"
       else if d? && d.value != null
         renderString(d)
       else


Fix Meteor support

Fix Meteor support

--- src/TimeZoneDate.coffee
+++ src/TimeZoneDate.coffee
@@ -1,7 +1,7 @@
-isNode = module?.exports?
+isNode = !Package?
 # Using a different variable name for moment to avoid re-declaring it in CoffeeScript and shadowing
 # the global variable.
-momentLib = if isNode then require('moment-timezone') else moment
+_moment = if isNode then require('moment-timezone') else moment

 # Extending Date ensures `instanceof Date` is true and all methods are inherited.
 class TimeZoneDate extends Date
@@ -15,16 +15,16 @@
     if args.length == 1
       args = args[0]
     if timeZone
-      @_moment = momentLib.tz(args, timeZone)
+      @_moment = _moment.tz(args, timeZone)
       @_timeZone = timeZone
     else
-      @_moment = momentLib(args)
+      @_moment = _moment(args)

   _fromUtc: ->
-    @_moment = momentLib.tz(@_utcMoment, @_timeZone)
+    @_moment = _moment.tz(@_utcMoment, @_timeZone)
     @_moment
   _fromMomentArg: (arg) ->
-    @_moment = momentLib.tz(arg, @_timeZone)
+    @_moment = _moment.tz(arg, @_timeZone)
     @_getForSetter()
   _getUtc: -> @_utcMoment ?= @_moment.clone().utc()
   _getForSetter: ->
@@ -62,7 +62,7 @@
   setMinutes: (value) -> @_getForSetter().minutes(value)
   setMonth: (value) -> @_getForSetter().month(value)
   setSeconds: (value) -> @_getForSetter().seconds(value)
-  setTime: (value) -> @_fromMomentArg(momentLib.unix(value / 1000))
+  setTime: (value) -> @_fromMomentArg(_moment.unix(value / 1000))
   setUTCDate: (value) -> @_fromUtc @_getUtc().date(value)
   setUTCFullYear: (value) -> @_fromUtc @_getUtc().year(value)
   setUTCHours: (value) -> @_fromUtc @_getUtc().hours(value)


Feedback with userId

Feedback with userId

--- src/packages/carpool-service/client/CarpoolServiceClient.coffee
+++ src/packages/carpool-service/client/CarpoolServiceClient.coffee
@@ -13,7 +13,7 @@
       preInitQueue.start()

   saveFeedback: (message)->
-    Feedback.insert({text:message});
+    Feedback.insert({text:message, userId: Meteor.userId()});

   sendMessage: (to, message)->
     Meteor.call("sendMessage", Meteor.userId(), to, message)


Use document.documentElement.clientWidth/Height to calculate dimensions.

Fixes motif/company#5059.


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/engine_selector.coffee
+++ test/engine_selector.coffee
@@ -30,7 +30,7 @@
     else if index is 2
       equals obj, obj3

-  equals
+  equals index

 test "#find", ->
   engine = Engine()


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/engine_selector.coffee
+++ test/engine_selector.coffee
@@ -22,7 +22,7 @@

   engine.each (obj, index) ->
     if index is 0
-
+      obj1

 test "#find", ->
   engine = Engine()


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/engine_selector.coffee
+++ test/engine_selector.coffee
@@ -4,7 +4,7 @@
   engine = Engine()

   engine.add
-    class:
+    class: "Awe"
     x: 0
     y:


add pane sizing

add pane sizing

--- home/.atom/keymap.cson
+++ home/.atom/keymap.cson
@@ -60,6 +60,7 @@
   'r': 'pane:show-previous-item'
   'l': 'pane:show-next-item'

+
   # Word Navigation
   'b': 'editor:move-to-beginning-of-word'
   'm': 'editor:move-to-end-of-word'
@@ -120,6 +121,10 @@
   'alt-shift-c': 'window:move-active-item-to-pane-above'
   'alt-shift-t': 'window:move-active-item-to-pane-below'

+  # pane sizing
+  'ctrl-n': 'pane:increase-size'
+  'ctrl-h': 'pane:decrease-size'
+
   # Multiple Cursors (adding / subtracting)
   'ctrl-shift-c':   'editor:add-selection-above'
   'ctrl-shift-t': 'editor:add-selection-below'


Remove logging

Remove logging

--- test/spec/range_spec.coffee
+++ test/spec/range_spec.coffee
@@ -146,7 +146,6 @@
         range = new Range.BrowserRange(sel.getRangeAt(0))
         norm  = range.normalize(fix())

-        console.log(textInNormedRange(norm), sel.expectation)
         assert.equal(textInNormedRange(norm), sel.expectation)

     for i in [0...testData.length]


add tv channel controll function

add tv channel controll function


aircon command has changed

aircon command has changed

--- scripts/caesabot.coffee
+++ scripts/caesabot.coffee
@@ -97,7 +97,7 @@
         else if sw is "dry"
           postIRKit msg, AIRCON_DRY, "除湿モードでエアコンつけたよ"
         else if sw is "hot"
-          postIRKit msg, AIRCON_DRY, "暖房モードでエアコンつけたよ"
+          postIRKit msg, AIRCON_HOT, "暖房モードでエアコンつけたよ"
         else if sw is "off"
           postIRKit msg, AIRCON_OFF, "エアコン消しといたよ"
         else


use videoHeight and videoWidth if available Fall back to `naturalHeight` and `naturalWidth` then 0.

--- app/components/frame-viewer.cjsx
+++ app/components/frame-viewer.cjsx
@@ -143,10 +143,12 @@
     scrubber.value = player.currentTime

   handleLoad: (e) ->
+    width = e.target.videoWidth ? e.target.naturalWidth
+    height = e.target.videoHeight ? e.target.naturalHeight
     @setState
       loading: false
       frameDimensions:
-        width: e.target.naturalWidth
-        height: e.target.naturalHeight
+        width: width ? 0
+        height: height ? 0

     @props.onLoad? arguments...


Fix tests

Fix tests

--- test/tests/LayerTest.coffee
+++ test/tests/LayerTest.coffee
@@ -446,37 +446,15 @@

 			simulate.mouseover(layer._element)

-		it "should not emit click event if a parent draggable was moved", ->
+
+		it "should not emit click event if a parent draggable was moved on draggable", ->

 			clicked = false

 			layerA = new Layer
-			layerB = new Layer superLayer:layerA

 			layerA.draggable.enabled = true
-			layerA.draggable._correctedLayerStartPoint = {x:10, y:10}
-			layerA.draggable.offset.should.eql {x:-10, y:-10}
-
-			layerB.on Events.Click, ->
-				clicked = true
-
-			layerB.emit(Events.Click)
-			clicked.should.equal false
-
-			layerA.draggable.enabled = false
-
-			layerB.emit(Events.Click)
-			clicked.should.equal true
-
-		it "should not emit click event if a parent draggable was moved on draggable", ->
-
-			clicked = false
-
-			layerA = new Layer
-
-			layerA.draggable.enabled = true
-			layerA.draggable._correctedLayerStartPoint = {x:10, y:10}
-			layerA.draggable.offset.should.eql {x:-10, y:-10}
+			layerA.draggable._isMoving = true

 			layerA.on Events.Click, ->
 				clicked = true


Add rubocop linter config

Add rubocop linter config

--- .atom/config.cson
+++ .atom/config.cson
@@ -16,19 +16,22 @@
     showOnStartup: false
   "atom-beautify":
     _analyticsUserId: "1d4703af-c0bf-4e61-8894-f8865aaabf2b"
-  linter: {}
+  linter:
+    showErrorPanel: false
   tabs:
     showIcons: true
   "tree-view": {}
-".ini.source":
+".git-rebase.text":
+  editor:
+    preferredLineLength: 120
+".git-config.source":
   editor:
     preferredLineLength: 120
 ".git-commit.text":
   editor:
     preferredLineLength: 120
-".git-config.source":
+".ini.source":
   editor:
     preferredLineLength: 120
-".git-rebase.text":
-  editor:
-    preferredLineLength: 120
+"linter-rubocop":
+  executablePath: "/usr/local/var/rbenv/versions/2.2.0/bin/rubocop"


Forgot to actually use the parameter

Forgot to actually use the parameter

--- resources/assets/coffee/osu_common.coffee
+++ resources/assets/coffee/osu_common.coffee
@@ -183,8 +183,8 @@
     options ?= {}

     if precision?
-      options.minimumFractionDigits = 2
-      options.maximumFractionDigits = 2
+      options.minimumFractionDigits = precision
+      options.maximumFractionDigits = precision

     number.toLocaleString locale ? currentLocale, options


Avoid page flashing upon reload

Avoid page flashing upon reload

It's probably removing a bit too much

--- resources/assets/coffee/osu_common.coffee
+++ resources/assets/coffee/osu_common.coffee
@@ -84,6 +84,7 @@

   reloadPage: (fallback, forceReload) ->
     $(document).off '.ujsHideLoadingOverlay'
+    Turbolinks.clearCache()
     Turbolinks.visit document.location.href, action: 'replace'


Also forward move

Also forward move

--- framer/Components/FlowComponent.coffee
+++ framer/Components/FlowComponent.coffee
@@ -266,6 +266,7 @@

 		# But only the actual scroll events
 		for event in [
+			Events.Move,
 			Events.ScrollStart,
 			Events.ScrollMove,
 			Events.ScrollEnd,


fix minor spelling issues

fix minor spelling issues

--- library/semantic/platings.coffee
+++ library/semantic/platings.coffee
@@ -44,12 +44,12 @@
     # Once inherited from, the inheritee is not abstract anymore.
     @abstract yes

-    # This method is invoked once the `success` event goes off in
-    # the sercvice. This event could be fired after the component
+    # This method is invoked once the `successful` event goes off
+    # at a service. This event could be fired after the component
     # receives a response after the form submission, if it was a
     # success. This implementation hides the actualy form, shows
     # the generic acknowledgement memo regarding succeeded form.
-    respondedWithSuccess: @awaiting "success", ->
+    respondedWithSuccess: @awaiting "successful", ->
         this.paragraph.text "The information you entered in
         to the form has been successfully submitted. Please
         press the okay button to close this window and carry


Corrects unit test

Corrects unit test

--- test/util.spec.coffee
+++ test/util.spec.coffee
@@ -833,9 +833,9 @@
         expect(spy.calls.length).to.equal(options.maxRetries + 1)
         done()

-    it 'retries errors with status code 4xx with retry-after header', (done) ->
+    it 'retries errors with status code 429', (done) ->
       app = (req, resp) ->
-        resp.writeHead(400, {'retry-after': 1})
+        resp.writeHead(429, {})
         resp.write('FOOBAR')
         resp.end()
       sendRequest (err, data) ->


Avoid duplicate role changes in "Switch to Partial Member"

--- client/author.coffee
+++ client/author.coffee
@@ -40,6 +40,7 @@
     roots =
       messagesBy(group, username).map (message) -> message2root message
     if roots.length
+      roots = _.uniq roots.sort(), true
       console.log "Switching #{username}'s access to the following roots: #{roots.join ', '}"
       for root in roots
         for role in roles


Use Meteor.setTimeout instead of setTimeout

--- client/import.coffee
+++ client/import.coffee
@@ -151,7 +151,7 @@
     e.preventDefault()

     DRAGGING_OVER_DOM = true
-    setTimeout () ->
+    Meteor.setTimeout ->
       DRAGGING_OVER_DOM = false
     , 5


fix #28

fix #28

--- client/shared.coffee
+++ client/shared.coffee
@@ -204,6 +204,32 @@
   SPAN null

+dom.AUTOSIZEBOX = ->
+  TEXTAREA
+    ref: 'textbox'
+    rows: 1
+    placeholder: @props.placeholder
+    onKeyDown: (e) =>@props.onKeyDown?(e)
+    onChange: (e) => @props.onChange?(e)
+    className: @props.className
+    style: @props.style
+    value: @props.value
+    resize: false  # infinite loop can be triggered if you resize manually,
+                   # and then trigger auto resize by typing
+
+resizebox = (target) ->
+  while(target.rows > 1 && target.scrollHeight < target.offsetHeight)
+    target.rows--
+  while(target.scrollHeight > target.offsetHeight)
+    target.rows++
+    if target.rows > 10000
+      console.error 'Infinite loop detected in AUTOSIZEBOX. Exiting.'
+      return
+
+dom.AUTOSIZEBOX.up      = -> resizebox(@refs.textbox.getDOMNode())
+dom.AUTOSIZEBOX.refresh = -> resizebox(@refs.textbox.getDOMNode())
+
+
 # Auto growing text area.
 # Transfers props to a TEXTAREA.
 dom.GROWING_TEXTAREA = ->


changed method name

changed method name

--- jasmine-harness.coffee
+++ jasmine-harness.coffee
@@ -1,10 +1,10 @@
-expose = (obj) ->
+attach = (obj) ->
   window[key] = obj[key] for key of obj

 jasmine = require('./spec/lib/jasmine.js')

 # attach jasmine to window
-expose jasmine
+attach jasmine

 html = require './spec/lib/jasmine-html.js'
 tap = require './spec/lib/jasmine.tap_reporter.js'


filter out non ascii stuff

filter out non ascii stuff

--- client/tweets.coffee
+++ client/tweets.coffee
@@ -12,6 +12,7 @@
         replace(/http\:\/\/twitter.com\/.*/, '')
       o.title = el.find("title").text().
         replace( /\#nowplaying/, '').
-        replace(/http\:\/\/t.co\/.*/, '')
+        replace(/http\:\/\/t.co\/.*/, '').
+        replace(/[^\w\s]/gi, '')
       o


make troll harder to trigger

make troll harder to trigger

--- scripts/troll-me.coffee
+++ scripts/troll-me.coffee
@@ -17,5 +17,6 @@
 """

 module.exports = (robot) ->
-  robot.hear /.*\btroll.*/i, (msg) ->
+  robot.respond /troll me/i, (msg) ->
     msg.send troll
+    msg.send "Ugh."


remove debug alerts

remove debug alerts

--- lib/resources/jquery.motion-hybrid.coffee
+++ lib/resources/jquery.motion-hybrid.coffee
@@ -25,12 +25,9 @@
     window.location = target.attr('href') # want to simulate real click but target.get(0).click() sometimes needs to be fired twice to work..?

   @waitForJqueryAndDom: ->
-    #alert 'finding jquery'
     if window.$
-      #alert 'found'
       jQuery -> document.location.href = 'motionhybrid://ready'
     else
-      #alert 'waiting'
       setTimeout MotionHybrid.waitForJqueryAndDom, 100

 MotionHybrid.waitForJqueryAndDom()


Correct url to endpoint

Correct url to endpoint

--- src/api/settings.coffee
+++ src/api/settings.coffee
@@ -38,7 +38,7 @@
       completedEvent: 'user.receive.statusUpdate'

     'course.*.send.registration':
-      url: 'endrollment_change'
+      url: 'api/enrollment_changes'
       method: 'POST'
       useCredentials: true
       baseUrl: process?.env?.BASE_URL


Remove a console.log

Remove a console.log

--- app/models/classification.coffee
+++ app/models/classification.coffee
@@ -51,7 +51,6 @@
   send: ->
     unless @subject.metadata.tutorial or @subject.metadata.empty
       @constructor.sentThisSession += 1
-      console.log 'Sent this session', @constructor.sentThisSession

     @trigger 'send'
     Api.post @url(), @toJSON(), arguments...


Added +withdraw function

Added +withdraw function

--- bot.iced
+++ bot.iced
@@ -190,6 +190,55 @@
 					when "add"
 						message += "\n\tType: add, Amount: #{item.amount}, Status: #{item.status}"
 			bot.sendMessage chatterID, message
+		when "+withdraw"
+			await checkIfRegistered chatterID, defer(registered, user)
+			if registered is undefined
+				return bot.sendMessage chatterID, "The database ran into an error"
+			unless registered
+				return bot.sendMessage chatterID, "You must register before you can add funds. Do this by sending '+register'."
+
+			address = message.split(" ")[1]
+			unless address?
+				return bot.sendMessage chatterID, "Missing address. Notation for +withdraw is '+withdraw <ADDRESS> <AMOUNT|all> doge'."
+			amount = message.split(" ")[2]
+			unless amount?
+				return bot.sendMessage chatterID, "Missing amount. Notation for +withdraw is '+withdraw <ADDRESS> <AMOUNT|all> doge'."
+
+			if amount.toLowerCase() is "all"
+				amount = user.funds
+			else
+				amount = parseInt amount, 10
+				if isNaN(amount)
+					return bot.sendMessage chatterID, "Invalid DOGE to withdraw"
+				if amount > user.funds - 1
+					return bot.sendMessage chatterID, "You can't withdraw that many DOGE (remember that there is a 1 DOGE transaction fee from the network)"
+			amount -= 1 # 1 DOGE network transaction fee
+
+			payload = {amount, destination: address}
+			options =
+				"method": "POST"
+				"url": "https://moolah.ch/api/merchant/send"
+				"form":
+					"guid": moolah.guid
+					"api_key": moolah.api_key
+					"payload": payload
+			requester options, (error, response, body) ->
+				unless !error and response.statusCode is 200
+					console.error "#{Date.now().toString()} - #{error}, #{response}, #{body}"
+					return bot.sendMessage chatterID, "Moolah ran into an error processing your request"
+				try
+					body = JSON.parse body
+				catch e
+					return bot.sendMessage chatterID, "Moolah returned invalid JSON"
+				if body.status is "failure"
+					# Something prevented Moolah from sending the money
+					console.error "#{Date.now().toString()} - Withdrawl error: #{body.reason}"
+					return bot.sendMessage chatterID, "Moolah couldn't withdraw your funds. Stated reason: '#{body.reason}'."
+				await Users_collection.update {id: chatterID}, {$inc: {funds: -(amount + 1)}}, {w:1}, defer(err)
+				if err
+					console.error err
+					return bot.sendMessage chatterID, "The database ran into an error"
+				bot.sendMessage chatterID, "#{body.amount} DOGE sent to #{body.destination} successfully"

 bot.on "friend", (steamID, Relationship) ->
 	if pendingInvites.indexOf(steamID) isnt -1


jupyter: ensure newlines in tracebacks e.g., in R kernel;

f2 <- function() stop('deep error')
throw <- function() f2()
'this line is run / displayed'
throw()

--- src/smc-webapp/jupyter/cell-output-message.cjsx
+++ src/smc-webapp/jupyter/cell-output-message.cjsx
@@ -230,6 +230,8 @@
         v = []
         n = 0
         @props.message.get('traceback').forEach (x) ->
+            if not misc.endswith(x, '\n')
+                x += '\n'
             v.push(<Ansi key={n}>{x}</Ansi>)
             n += 1
             return


fixed gridline color mixing on transparent background

--- src/viz/types/helpers/graph/includes/svg.coffee
+++ src/viz/types/helpers/graph/includes/svg.coffee
@@ -48,7 +48,7 @@

         if visible and (!log or Math.abs(d).toString().charAt(0) is "1")
           color
-        else if grid
+        else if grid and vars.axes.background.color isnt "transparent"
           mix(color, vars.axes.background.color, 0.4, 1)
         else
           mix(color, vars.background.value, 0.4, 1)


Added final batch of mappings to make a complete set

--- src/articleData.coffee
+++ src/articleData.coffee
@@ -45,9 +45,21 @@

     # Query
-    addMappings data, "query-10002762fc3e"  ,  { is: "Query",   title: "Perform Validation on the Server", xref: []}
-    addMappings data, "query-20002762fc3e"  ,  { is: "Query",   title: "Validate All Input"              , xref: []}
-    addMappings data, "query-30002762fc3e"  ,  { is: "Query",   title: "Use White-list Validation"       , xref: []}
+    addMappings data, "query-10002762fc3e"  ,  { is: "Query",   title: "Perform Validation on the Server", xref: ['xref-31002762fc3e'
+                                                                                'xref-32002762fc3e'
+                                                                                'xref-33002762fc3e'
+                                                                                'xref-34002762fc3e'
+                                                                                'xref-35002762fc3e'
+                                                                                'xref-36002762fc3e'
+                                                                                'xref-37002762fc3e']}
+    addMappings data, "query-20002762fc3e"  ,  { is: "Query",   title: "Validate All Input"              , xref: ['xref-38002762fc3e'
+                                                                                'xref-39002762fc3e'
+                                                                                'xref-41002762fc3e']}
+
+    addMappings data, "query-30002762fc3e"  ,  { is: "Query",   title: "Use White-list Validation"       , xref: ['xref-42002762fc3e'
+                                                                                'xref-43002762fc3e'
+                                                                                'xref-44002762fc3e'
+                                                                                'xref-45002762fc3e']}

@@ -158,10 +170,23 @@
     addMappings data, "xref-27002762fc3e"   ,  { is: 'XRef',    target: 'article-46d6939abe45' , weight: '3' }
     addMappings data, "xref-28002762fc3e"   ,  { is: 'XRef',    target: 'article-46d6939abe45' , weight: '1' }
     addMappings data, "xref-29002762fc3e"   ,  { is: 'XRef',    target: 'article-46d6939abe45' , weight: '3' }
-#    addMappings data, "xref-30002762fc3e"   ,  { is: 'XRef',    target: 'article-' , weight: '3' }
-
-    #addMapping(data, "12wewe3", "is an", "aaaaa12356")
-    #console.log(data)
+
+    addMappings data, "xref-31002762fc3e"   ,  { is: 'XRef',    target: 'article-e7ed2762fc3e' , weight: '1' }
+    addMappings data, "xref-32002762fc3e"   ,  { is: 'XRef',    target: 'article-d5bc580df781' , weight: '1' }
+    addMappings data, "xref-33002762fc3e"   ,  { is: 'XRef',    target: 'article-9771b8ed3eda' , weight: '3' }
+    addMappings data, "xref-34002762fc3e"   ,  { is: 'XRef',    target: 'article-1106d793193b' , weight: '3' }
+    addMappings data, "xref-35002762fc3e"   ,  { is: 'XRef',    target: 'article-3e15eef3a23c' , weight: '6' }
+    addMappings data, "xref-36002762fc3e"   ,  { is: 'XRef',    target: 'article-9f8b44a5b27d' , weight: '6' }
+    addMappings data, "xref-37002762fc3e"   ,  { is: 'XRef',    target: 'article-46d6939abe45' , weight: '9' }
+
+    addMappings data, "xref-38002762fc3e"   ,  { is: 'XRef',    target: 'article-e7ed2762fc3e' , weight: '1' }
+    addMappings data, "xref-39002762fc3e"   ,  { is: 'XRef',    target: 'article-d5bc580df781' , weight: '1' }
+    addMappings data, "xref-41002762fc3e"   ,  { is: 'XRef',    target: 'article-9771b8ed3eda' , weight: '3' }
+    addMappings data, "xref-42002762fc3e"   ,  { is: 'XRef',    target: 'article-1106d793193b' , weight: '3' }
+    addMappings data, "xref-43002762fc3e"   ,  { is: 'XRef',    target: 'article-3e15eef3a23c' , weight: '6' }
+    addMappings data, "xref-44002762fc3e"   ,  { is: 'XRef',    target: 'article-9f8b44a5b27d' , weight: '6' }
+    addMappings data, "xref-45002762fc3e"   ,  { is: 'XRef',    target: 'article-46d6939abe45' , weight: '9' }
+
     db.put data , callback

 module.exports = addData


Expand args passed to 'trigger' in mock TitaniumView

--- ti/src/ui/views/base.coffee
+++ ti/src/ui/views/base.coffee
@@ -12,7 +12,7 @@
     @off name, event

   fireEvent: (name, args...) ->
-    @trigger name, args
+    @trigger name, args...

   add: (view) ->
     @children.push view


cleanup

cleanup

--- apps/articles/client/show.coffee
+++ apps/articles/client/show.coffee
@@ -22,7 +22,6 @@
     @setupSlideshow()
     @renderArtworks()
     @breakCaptions()
-    window.view = this
     if $(@article.get 'lead_paragraph').text().trim() is ''
       @$('#articles-lead-paragraph').hide()


fixes artist meta tests

fixes artist meta tests

--- apps/artist_old/test/meta.coffee
+++ apps/artist_old/test/meta.coffee
@@ -6,7 +6,7 @@

 describe 'Meta tags', ->
   before ->
-    @file = "#{process.cwd()}/apps/artist/templates/meta.jade"
+    @file = "#{process.cwd()}/apps/artist_old/templates/meta.jade"
     @sd =
       APP_URL: 'http://localhost:5000'
       CANONICAL_MOBILE_URL: 'http://m.localhost:5000'


Don't log start time when running specs

Don't log start time when running specs

--- src/browser/main.coffee
+++ src/browser/main.coffee
@@ -62,7 +62,7 @@
       AtomApplication = require './atom-application'

     AtomApplication.open(args)
-    console.log("App load time: #{Date.now() - startTime}ms")
+    console.log("App load time: #{Date.now() - startTime}ms") unless args.test

 global.devResourcePath = path.join(app.getHomeDir(), 'github', 'atom')


Comment out nslog for now

Comment out nslog for now

--- src/browser/main.coffee
+++ src/browser/main.coffee
@@ -8,7 +8,8 @@
 module = require 'module'
 path = require 'path'
 optimist = require 'optimist'
-nslog = require 'nslog'
+# TODO: NSLog is missing .lib on windows
+#nslog = require 'nslog'
 dialog = require 'dialog'

 console.log = (args...) ->


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/camera_fade.coffee
+++ src/camera_fade.coffee
@@ -10,8 +10,7 @@
 ###
 Camera.Fade = (I, self) ->
   Object.reverseMerge I,
-    shakeIntensity: 20
-    shakeCooldown: 0
+    alpha

   defaultParams =
     duration: 30


Run association tests under a custom namespace rather than running a Batman app

--- tests/batman/model/associations_test.coffee
+++ tests/batman/model/associations_test.coffee
@@ -1,7 +1,9 @@
 {TestStorageAdapter, AsyncTestStorageAdapter} = if typeof require isnt 'undefined' then require './model_helper' else window
 helpers = if typeof require is 'undefined' then window.viewHelpers else require '../view/view_helper'

-asyncTest "Associations support custom model scopes", 2, ->
+QUnit.module "Associations"
+
+asyncTest "support custom model namespaces", 2, ->
   namespace = {}
   class namespace.Store extends Batman.Model

@@ -18,28 +20,51 @@
     equal store.get('id'), 3
     QUnit.start()

+asyncTest "work with model classes that haven't been loaded yet", ->
+  namespace = this
+  class @Blog extends Batman.Model
+    @encode 'id', 'name'
+    @hasOne 'customer', namespace
+  blogAdapter = new AsyncTestStorageAdapter @Blog
+  blogAdapter.storage = 'blogs1': {name: "Blog One", id: 1}
+  @Blog.persist blogAdapter
+
+  setTimeout (=>
+    class @Customer extends Batman.Model
+      @encode 'id', 'name'
+    customerAdapter = new AsyncTestStorageAdapter @Customer
+    customerAdapter.storage =
+      'customer1': {name: "Customer One", id: 1, blog_id: 1}
+    @Customer.persist customerAdapter
+
+    @Blog.find 1, (err, blog) =>
+      customer = blog.get 'customer'
+      ok customer instanceof @Customer
+      equal customer.get('id'), 1
+      equal customer.get('name'), 'Customer One'
+      QUnit.start()
+  ), ASYNC_TEST_DELAY
+
 QUnit.module "belongsTo Associations"
   setup: ->
-    @App = Batman.currentApp = {}
-
-    class @App.Store extends Batman.Model
-      @encode 'id', 'name'
-    @storeAdapter = new AsyncTestStorageAdapter @App.Store
+    namespace = this
+    class @Store extends Batman.Model
+      @encode 'id', 'name'
+    @storeAdapter = new AsyncTestStorageAdapter @Store
     @storeAdapter.storage =
       'stores1': {name: "Store One", id: 1}
       'stores2': {name: "Store Two", id: 2, product: {id:3, name:"JSON Product"}}
-    @App.Store.persist @storeAdapter
-
-    class @App.Product extends Batman.Model
-      @encode 'id', 'name'
-      @belongsTo 'store'
-    @productAdapter = new AsyncTestStorageAdapter @App.Product
+    @Store.persist @storeAdapter
+
+    class @Product extends Batman.Model
+      @encode 'id', 'name'
+      @belongsTo 'store', namespace
+    @productAdapter = new AsyncTestStorageAdapter @Product
     @productAdapter.storage = 'products1': {name: "Product One", id: 1, store_id: 1}
-    @App.Product.persist @productAdapter
-
+    @Product.persist @productAdapter

 asyncTest "belongsTo yields the related model when toJSON is called", 1, ->
-  @App.Product.find 1, (err, product) =>
+  @Product.find 1, (err, product) =>
     productJSON = product.toJSON()
     storeJSON = product.get('store').toJSON()
     # store will encode its product
@@ -49,15 +74,15 @@
     QUnit.start()

 asyncTest "belongsTo associations are loaded via ID", 2, ->
-  @App.Product.find 1, (err, product) =>
+  @Product.find 1, (err, product) =>
     store = product.get 'store'
-    ok store instanceof @App.Store
+    ok store instanceof @Store
     equal store.id, 1
     QUnit.start()

 asyncTest "belongsTo associations are saved", 5, ->
-  store = new @App.Store name: 'Zellers'
-  product = new @App.Product name: 'Gizmo'
+  store = new @Store name: 'Zellers'
+  product = new @Product name: 'Gizmo'
   product.set 'store', store

   productSaveSpy = spyOn product, 'save'
@@ -73,7 +98,7 @@
     QUnit.start()

 asyncTest "belongsTo associations render", 1, ->
-  @App.Product.find 1, (err, product) ->
+  @Product.find 1, (err, product) ->
     source = ''
     context = Batman(product: product)
     helpers.render source, context, (node) =>
@@ -82,70 +107,45 @@

 QUnit.module "hasOne Associations"
   setup: ->
-    @App = Batman.currentApp = {}
-
-    class @App.Store extends Batman.Model
-      @encode 'id', 'name'
-      @hasOne 'product'
-    @storeAdapter = new AsyncTestStorageAdapter @App.Store
+    namespace = this
+    class @Store extends Batman.Model
+      @encode 'id', 'name'
+      @hasOne 'product', namespace
+    @storeAdapter = new AsyncTestStorageAdapter @Store
     @storeAdapter.storage =
       'stores1': {name: "Store One", id: 1}
       'stores2': {name: "Store Two", id: 2, product: {id:3, name:"JSON Product"}}
-    @App.Store.persist @storeAdapter
-
-    class @App.Product extends Batman.Model
-      @encode 'id', 'name'
-    @productAdapter = new AsyncTestStorageAdapter @App.Product
+    @Store.persist @storeAdapter
+
+    class @Product extends Batman.Model
+      @encode 'id', 'name'
+    @productAdapter = new AsyncTestStorageAdapter @Product
     @productAdapter.storage = 'products1': {name: "Product One", id: 1, store_id: 1}
-    @App.Product.persist @productAdapter
-
-asyncTest "should work with model classes that haven't been loaded yet", ->
-  class @App.Blog extends Batman.Model
-    @encode 'id', 'name'
-    @hasOne 'customer'
-  blogAdapter = new AsyncTestStorageAdapter @App.Blog
-  blogAdapter.storage = 'blogs1': {name: "Blog One", id: 1}
-  @App.Blog.persist blogAdapter
-
-  setTimeout (=>
-    class @App.Customer extends Batman.Model
-      @encode 'id', 'name'
-    customerAdapter = new AsyncTestStorageAdapter @App.Customer
-    customerAdapter.storage =
-      'customer1': {name: "Customer One", id: 1, blog_id: 1}
-    @App.Customer.persist customerAdapter
-
-    @App.Blog.find 1, (err, blog) =>
-      customer = blog.get 'customer'
-      ok customer instanceof @App.Customer
-      equal customer.get('id'), 1
-      equal customer.get('name'), 'Customer One'
-      QUnit.start()
-  ), ASYNC_TEST_DELAY
+    @Product.persist @productAdapter

 asyncTest "hasOne yields the related model when toJSON is called", 1, ->
-  @App.Store.find 1, (err, store) =>
+  @Store.find 1, (err, store) =>
     deepEqual store.toJSON().product, @productAdapter.storage['products1']
     QUnit.start()

 asyncTest "hasOne associations are loaded via ID", 2, ->
-  @App.Store.find 1, (err, store) =>
+  @Store.find 1, (err, store) =>
     product = store.get 'product'
-    ok product instanceof @App.Product
+    ok product instanceof @Product
     equal product.id, 1
     QUnit.start()

 asyncTest "hasOne associations are loaded via JSON", 3, ->
-  @App.Store.find 2, (err, store) =>
+  @Store.find 2, (err, store) =>
     product = store.get 'product'
-    ok product instanceof @App.Product
+    ok product instanceof @Product
     equal product.get('id'), 3
     equal product.get('name'), "JSON Product"
     QUnit.start()

 asyncTest "hasOne associations are saved", 4, ->
-  store = new @App.Store name: 'Zellers'
-  product = new @App.Product name: 'Gizmo'
+  store = new @Store name: 'Zellers'
+  product = new @Product name: 'Gizmo'
   store.set 'product', product

   storeSaveSpy = spyOn store, 'save'
@@ -161,15 +161,15 @@
     QUnit.start()

 asyncTest "hasOne associations can be destroyed safely", 2, ->
-  @App.Store.find 1, (err, store) =>
-    @App.Product.find 1, (err, product) ->
+  @Store.find 1, (err, store) =>
+    @Product.find 1, (err, product) ->
       store.destroy()
       equal product.get('store_id'), undefined
       equal product._batman.attributes['store'], undefined
       QUnit.start()

 asyncTest "hasOne models can save while related records are loading", 1, ->
-  @App.Store.find 1, (err, store) ->
+  @Store.find 1, (err, store) ->
     product = store.get 'product'
     product._batman.state = 'loading'
     store.save (err, savedStore) ->
@@ -177,7 +177,7 @@
       QUnit.start()

 asyncTest "hasOne associations render", 1, ->
-  @App.Store.find 1, (err, store) ->
+  @Store.find 1, (err, store) ->
     source = ''
     context = Batman(store: store)
     helpers.render source, context, (node) ->
@@ -186,21 +186,21 @@

 QUnit.module "hasMany Associations"
   setup: ->
-    @App = Batman.currentApp = {}
-
-    class @App.Store extends Batman.Model
-      @encode 'id', 'name'
-      @hasMany 'products'
-    @storeAdapter = new AsyncTestStorageAdapter @App.Store
+    namespace = this
+
+    class @Store extends Batman.Model
+      @encode 'id', 'name'
+      @hasMany 'products', namespace
+    @storeAdapter = new AsyncTestStorageAdapter @Store
     @storeAdapter.storage =
       'stores1': {name: "Store One", id: 1}
-    @App.Store.persist @storeAdapter
-
-    class @App.Product extends Batman.Model
+    @Store.persist @storeAdapter
+
+    class @Product extends Batman.Model
       @encode 'id', 'name', 'store_id'
-      @belongsTo 'store'
-      @hasMany 'productVariants'
-    @productAdapter = new AsyncTestStorageAdapter @App.Product
+      @belongsTo 'store', namespace
+      @hasMany 'productVariants', namespace
+    @productAdapter = new AsyncTestStorageAdapter @Product
     @productAdapter.storage =
       'products1': {name: "Product One", id: 1, store_id: 1}
       'products2': {name: "Product Two", id: 2, store_id: 1}
@@ -213,20 +213,20 @@
           productvariants6: {price:60,product_id:3}
         }
       }
-    @App.Product.persist @productAdapter
-
-    class @App.ProductVariant extends Batman.Model
+    @Product.persist @productAdapter
+
+    class @ProductVariant extends Batman.Model
       @encode 'price'
-      @belongsTo 'product'
-    variantAdapter = new AsyncTestStorageAdapter @App.ProductVariant
-    @App.ProductVariant.persist variantAdapter
+      @belongsTo 'product', namespace
+    variantAdapter = new AsyncTestStorageAdapter @ProductVariant
+    @ProductVariant.persist variantAdapter

 asyncTest "hasMany associations are loaded", 6, ->
-  @App.Store.find 1, (err, store) =>
+  @Store.find 1, (err, store) =>
     products = store.get 'products'
     trackedIds = {1: no, 2: no, 3: no}
     products.forEach (product) =>
-      ok product instanceof @App.Product
+      ok product instanceof @Product
       trackedIds[product.id] = true
     equal trackedIds[1], yes
     equal trackedIds[2], yes
@@ -234,9 +234,9 @@
     QUnit.start()

 asyncTest "hasMany associations are saved via the parent model", 4, ->
-  store = new @App.Store name: 'Zellers'
-  product1 = new @App.Product name: 'Gizmo'
-  product2 = new @App.Product name: 'Gadget'
+  store = new @Store name: 'Zellers'
+  product1 = new @Product name: 'Gizmo'
+  product2 = new @Product name: 'Gadget'
   store.set 'products', new Batman.Set(product1, product2)

   storeSaveSpy = spyOn store, 'save'
@@ -252,8 +252,8 @@
     QUnit.start()

 asyncTest "hasMany associations are saved via the child model", 2, ->
-  @App.Store.find 1, (err, store) =>
-    product = new @App.Product name: 'Gizmo'
+  @Store.find 1, (err, store) =>
+    product = new @Product name: 'Gizmo'
     product.set 'store', store
     product.save (err, savedProduct) ->
       equal savedProduct.get('store_id'), store.id
@@ -262,7 +262,7 @@
       QUnit.start()

 asyncTest "hasMany associations can be destroyed safely", 6, ->
-  @App.Store.find 1, (err, store) =>
+  @Store.find 1, (err, store) =>
     products = store.get('products')
     store.destroy()
     products.forEach (product) =>
@@ -271,20 +271,20 @@
     QUnit.start()

 asyncTest "hasMany association can be loaded from JSON data", 12, ->
-  @App.Product.find 3, (err, product) =>
+  @Product.find 3, (err, product) =>
     variants = product.get('productVariants')
     ok variants instanceof Batman.Set
     equal variants.length, 2

     variant5 = variants.toArray()[0]
-    ok variant5 instanceof @App.ProductVariant
+    ok variant5 instanceof @ProductVariant
     equal variant5.id, 5
     equal variant5.get('price'), 50
     equal variant5.get('product_id'), 3
     equal variant5.get('product'), product

     variant6 = variants.toArray()[1]
-    ok variant6 instanceof @App.ProductVariant
+    ok variant6 instanceof @ProductVariant
     equal variant6.id, 6
     equal variant6.get('price'), 60
     equal variant6.get('product_id'), 3
@@ -293,7 +293,7 @@
     QUnit.start()

 asyncTest "hasMany associations render", 3, ->
-  @App.Store.find 1, (err, store) ->
+  @Store.find 1, (err, store) ->
     source = '

'
     context = Batman(store: store)
     helpers.render source, context, (node) ->


Add bug counts to chart legend

Add bug counts to chart legend

--- src/client/game.coffee
+++ src/client/game.coffee
@@ -12,11 +12,13 @@
   legend = new Plottable.Component.Legend(colorScale)

   plots = bug_stats.map (stat) ->
+    last_stat = stat[stat.length - 1]
+    name      = last_stat.count + ' ' + last_stat.name
     return new Plottable.Plot.Line(xScale, yScale)
                       .addDataset(stat)
                       .project("x", "iteration", xScale)
                       .project("y", "count", yScale)
-                      .project("stroke", colorScale.scale(stat[0].name))
+                      .project("stroke", colorScale.scale(name))
                       .project("stroke-width", 1)

   gridlines = new Plottable.Component.Gridlines(xScale, yScale)


slow down that animation!

slow down that animation!

--- app/assets/javascripts/antiqua/pages/repositories_page.js.coffee
+++ app/assets/javascripts/antiqua/pages/repositories_page.js.coffee
@@ -20,7 +20,9 @@
       @renderLoader $archive_button
       $window = $ 'html,body'
       $scroll_to = $ "#repo-#{ repository_id }-header"
-      $window.animate scrollTop: $scroll_to.offset().top - $window.offset().top + $window.scrollTop() - 50
+      $window.animate
+        scrollTop: $scroll_to.offset().top - $window.offset().top + $window.scrollTop() - 50
+      , 1000

   renderArchives: ( repository ) ->
     if repository.archives.length > 0


ClampBounds docs

ClampBounds docs

--- src/clamp_bounds.coffee
+++ src/clamp_bounds.coffee
@@ -18,7 +18,7 @@
   # his position back inside the viewport.
   player.update()

-  # x, y position is based on the c
+  # x, y position is based on the center point
   player.I.x
   # => 2.5
 </pre></code>


renamed a helper to be more descriptive; implemented /Logout endpoint

--- core/KDRouter.coffee
+++ core/KDRouter.coffee
@@ -1,7 +1,4 @@
 class KDRouter extends KDObject
-
-  @stayOpen = (fn)->
-

   listenerKey = 'ಠ_ಠ'

@@ -55,7 +52,7 @@
     console.trace()
     log "The route #{route} was not found!"

-  routeWithoutThisEdge =(route, i)->
+  routeWithoutEdgeAtIndex =(route, i)->
     "/#{route.slice(0, i).concat(route.slice i + 1).join '/'}"

   addRoute:(route, listener)->
@@ -67,7 +64,7 @@
       len = edge.length-1
       if '?' is edge.substr len # then this is an "optional edge".
         # recursively alias this route without this optional edge:
-        @addRoute routeWithoutThisEdge(route, i), listener
+        @addRoute routeWithoutEdgeAtIndex(route, i), listener
         edge = edge.substr 0, len # get rid of the "?" from the route
       if /^:/.test edge
         node[':'] or= name: edge.substr 1


fixed refresh problem on develop slug

fixed refresh problem on develop slug

--- core/KDRouter.coffee
+++ core/KDRouter.coffee
@@ -58,6 +58,8 @@

   getTitle:(path)-> path

+  getCurrentPath:-> @currentPath
+
   handleNotFound:(route)->
     console.trace()
     log "The route #{route} was not found!"
@@ -95,12 +97,12 @@

     {shouldPushState, replaceState, state} = options
     shouldPushState ?= yes
-
+
     objRef = createObjectRef state

     node = @tree
     params = {}
-
+
     isRooted = '/' is frag[0]

     frag = frag.split '/'


Remove unneeded test

Remove unneeded test

--- tests/unit/initializers/storage-test.coffee
+++ tests/unit/initializers/storage-test.coffee
@@ -1,19 +0,0 @@
-`import Ember from 'ember'`
-`import { initialize } from 'travis/initializers/storage'`
-
-container = null
-application = null
-
-module 'StorageInitializer',
-  setup: ->
-    Ember.run ->
-      application = Ember.Application.create()
-      container = application.__container__
-      application.deferReadiness()
-
-# Replace this with your real tests.
-test 'it works', ->
-  initialize container, application
-
-  # you would normally confirm the results of the initializer here
-  ok true


Removed unused function

Removed unused function

--- app/assets/javascripts/components/about_item.js.jsx.coffee
+++ app/assets/javascripts/components/about_item.js.jsx.coffee
@@ -12,9 +12,6 @@
     description: this.props.description
     order: this.props.order

-  rawMarkup: ->
-    { __html: marked(this.state.description, {sanitize: true}) }
-
   #I am using the raw html here because one could create anchors inside
   #about description and in order to this work it was necessary to output
   #the raw value of this property.


use jquery.each instead of _.each as it necessitates an unessary use of underscore library

--- app/assets/javascripts/hanuman/conditional-logic.js.coffee
+++ app/assets/javascripts/hanuman/conditional-logic.js.coffee
@@ -103,7 +103,7 @@

   checkConditionsAndHideShow: (conditions, ancestorId, $ruleElement, $container, inRepeater, matchType) ->
     conditionMetTracker = []
-    _.each conditions, (condition) ->
+    $.each conditions, (index, condition) ->
       $conditionElement = $("[data-question-id=" + condition.question_id + "]").find('.form-control')
       if inRepeater
         $conditionElement = $container.closest(".form-container-repeater").find("[data-question-id=" + condition.question_id + "]").find('.form-control')


Fixed missing click event

Fixed missing click event

--- app/assets/javascripts/light_mobile/flash_errors.js.coffee
+++ app/assets/javascripts/light_mobile/flash_errors.js.coffee
@@ -1,5 +1,5 @@
 $ ->
-  $(".mobile-flash-ok").click (e) ->
+  $(".mobile-button-flash-ok").click (e) ->
     e.preventDefault()
     parent_flash = $(this).parent(".mobile-flash")
     parent_flash.remove()


Ensures randomised thang clusters do not overlap each other

--- app/views/editor/level/modals/TerrainRandomizeModal.coffee
+++ app/views/editor/level/modals/TerrainRandomizeModal.coffee
@@ -142,7 +142,6 @@
 module.exports = class TerrainRandomizeModal extends ModalView
   id: 'terrain-randomize-modal'
   template: template
-  thangs = []
   events:
     'click .choose-option': 'onRandomize'

@@ -167,6 +166,7 @@
     preset = presets[presetName]
     presetSize = presetSizes[presetSize]
     @thangs = []
+    @rects = []
     @randomizeFloor preset, presetSize
     @randomizeBorder preset, presetSize, preset.borderNoise
     @randomizeDecorations preset, presetSize
@@ -233,12 +233,15 @@
         if @['build'+name] isnt undefined
           @['build'+name](preset, presetSize, decoration)
           continue
-        rect = {
-          'x':_.random(decoration.width/2 + preset.borderSize/2 + thangSizes.borderSize.x, presetSize.x - decoration.width/2 - preset.borderSize/2 - thangSizes.borderSize.x),
-          'y':_.random(decoration.height/2 + preset.borderSize/2 + thangSizes.borderSize.y, presetSize.y - decoration.height/2 - preset.borderSize/2 - thangSizes.borderSize.y)
-          'width':decoration.width
-          'height':decoration.height
-        }
+        while true
+          rect = {
+            'x':_.random(decoration.width/2 + preset.borderSize/2 + thangSizes.borderSize.x, presetSize.x - decoration.width/2 - preset.borderSize/2 - thangSizes.borderSize.x),
+            'y':_.random(decoration.height/2 + preset.borderSize/2 + thangSizes.borderSize.y, presetSize.y - decoration.height/2 - preset.borderSize/2 - thangSizes.borderSize.y)
+            'width':decoration.width
+            'height':decoration.height
+          }
+          break if @addRect rect
+
         for cluster, range of decoration.clusters
           for i in _.range(_.random(range[0], range[1]))
             while not @addThang {
@@ -253,13 +256,20 @@

   buildRoom: (preset, presetSize, room) ->
     if presetSize is presetSizes['small'] then sizeFactor = 1 else sizeFactor = 2
-    rect = {
-      'width':sizeFactor * (room.width[0] + preset.borderSize * _.random(0, (room.width[1] - room.width[0])/preset.borderSize))
-      'height':sizeFactor * (room.height[0] + preset.borderSize * _.random(0, (room.height[1] - room.height[0])/preset.borderSize))
-    }
-    roomThickness = _.random(room.thickness[0], room.thickness[1])
-    rect.x = _.random(rect.width/2 + preset.borderSize * (roomThickness+2), presetSize.x - rect.width/2 - preset.borderSize * (roomThickness+2))
-    rect.y = _.random(rect.height/2 + preset.borderSize * (roomThickness+2), presetSize.y - rect.height/2 - preset.borderSize * (roomThickness+2))
+    while true
+      rect = {
+        'width':sizeFactor * (room.width[0] + preset.borderSize * _.random(0, (room.width[1] - room.width[0])/preset.borderSize))
+        'height':sizeFactor * (room.height[0] + preset.borderSize * _.random(0, (room.height[1] - room.height[0])/preset.borderSize))
+      }
+      roomThickness = _.random(room.thickness[0], room.thickness[1])
+      rect.x = _.random(rect.width/2 + preset.borderSize * (roomThickness+1), presetSize.x - rect.width/2 - preset.borderSize * (roomThickness+1))
+      rect.y = _.random(rect.height/2 + preset.borderSize * (roomThickness+1), presetSize.y - rect.height/2 - preset.borderSize * (roomThickness+1))
+      break if @addRect {
+        'x': rect.x
+        'y': rect.y
+        'width': rect.width + 2 * roomThickness * preset.borderSize
+        'height': rect.height + 2 * roomThickness * preset.borderSize
+      }

     xRange = _.range(rect.x - rect.width/2 + preset.borderSize, rect.x + rect.width/2, preset.borderSize)
     topDoor = _.random(1) > 0.5
@@ -316,7 +326,7 @@
         }

   addThang: (thang) ->
-    if @falseCount > 20
+    if @falseCount > 100
       console.log 'infinite loop', thang
       @falseCount = 0
       return true
@@ -329,6 +339,18 @@
     @thangs.push thang
     true

+  addRect: (rect) ->
+    if @falseCount > 100
+      console.log 'infinite loop', rect
+      @falseCount = 0
+      return true
+    for existingRect in @rects
+      if Math.abs(existingRect.x - rect.x) <= rect.width/2 + existingRect.width/2 and Math.abs(existingRect.y - rect.y) <= rect.height/2 + existingRect.height/2
+        @falseCount++
+        return false
+    @rects.push rect
+    true
+
   getRandomThang: (thangList) ->
     return thangList[_.random(0, thangList.length-1)]


Add spec about destructuring assignment

Add spec about destructuring assignment

Single variable assignment is not working.

--- spec/Parser-spec.coffee
+++ spec/Parser-spec.coffee
@@ -210,6 +210,8 @@
       { a: { b: c } } = obj
       func = ({ a: { b: c } }) ->
         a = b = c = 2
+      func = ({ a }) ->
+        a 1
       """
       expectNoRefs parser, new Range([1, 2], [1, 3])
       expectNoRefs parser, new Range([1, 7], [1, 8])
@@ -231,6 +233,10 @@
         new Range([3, 10], [3, 11])
       expectEqualRefs parser, new Range([3, 10], [3, 11]),
         new Range([2, 18], [2, 19])
+      expectEqualRefs parser, new Range([4, 10], [4, 11]),
+        new Range([5, 2], [5, 3])
+      expectEqualRefs parser, new Range([5, 2], [5, 3]),
+        new Range([4, 10], [4, 11])

     it 'should recognize the scope of variable', ->


Extended coverage of environmental requirements

--- spec/environment.coffee
+++ spec/environment.coffee
@@ -6,7 +6,17 @@
         expect(typeof location).toBe('object')
         expect(typeof location.hash).toBe('string')

-    it 'provides the onHashChange event', ->
+    it 'allows changing location.hash', ->
+        location.hash = ''
+        expect(location.hash).not.toEqual('#foo')
+        location.hash = 'foo'
+        expect(location.hash).toEqual('#foo')
+
+    it 'ignores leading hash symbols when writing to location.hash', ->
+        location.hash = '#bar'
+        expect(location.hash).toEqual('#bar')
+
+    it 'provides the "onHashChange" event', ->
         expect('onhashchange' of window).toBe(true)


Fixed test

Fixed test

--- spec/router_test.coffee
+++ spec/router_test.coffee
@@ -94,7 +94,7 @@
       window_open("/index.html", (w) -> w.router.initDone; eq(["/index.html", "1"], [text(w, "#txt"), text(w, "#routerRenderCount")])).
       test()
     it "should handle click to /foo without reloading page", zhain().
-      window_open("/", (w) -> w.router.initDone; eq("No content!", text(w, "#txt"))).
+      window_open("/", (w) -> w.router.initDone; eq("No clicks!", text(w, "#txt"))).
       click("#pageSame", (w) -> eq("Ok!", text(w, "#pageSame"))).
       click("#link_foo", (w) -> eq(["Ok!", "/foo", "1"], [text(w, "#pageSame"), text(w, "#txt"), text(w, "#routerRenderCount")])).
       test()


Remove deprecated call from spec helper

Remove deprecated call from spec helper

--- spec/spec-helper.coffee
+++ spec/spec-helper.coffee
@@ -303,7 +303,7 @@
   event.result

 $.fn.enableKeymap = ->
-  @on 'keydown', (e) => atom.keymaps.handleKeyEvent(e)
+  @on 'keydown', (e) -> atom.keymaps.handleKeyboardEvent(e.originalEvent)

 $.fn.attachToDom = ->
   @appendTo($('#jasmine-content'))


DRY: generalized keyUp/DownEvent method so InputElement can route those events with minimal code overlap

--- src/art/engine/core/canvas_element.coffee
+++ src/art/engine/core/canvas_element.coffee
@@ -411,10 +411,16 @@
   queueKeyEvents: (type, newEventFunction) ->
     @pointerEventManager.queueKeyEvents type, newEventFunction

+  keyDownEvent: (keyboardEvent) ->
+    @pointerEventManager.queueKeyEvents "keyDown",  -> new KeyEvent "keyDown",  keyboardEvent
+    @pointerEventManager.queueKeyEvents "keyPress", -> new KeyEvent "keyPress", keyboardEvent
+
+  keyUpEvent: (keyboardEvent) ->
+    @pointerEventManager.queueKeyEvents "keyUp",    -> new KeyEvent "keyUp",  keyboardEvent
+
   _attachKeypressListeners: ->
     @_domListener @_canvas, "keydown", (keyboardEvent) =>
-      @queueKeyEvents "keyDown",  -> new KeyEvent "keyDown",  keyboardEvent
-      @queueKeyEvents "keyPress", -> new KeyEvent "keyPress", keyboardEvent
+      @keyDownEvent keyboardEvent

       # HACK
       # Our event handlers don't happen immeidately. They are queued.
@@ -425,7 +431,7 @@
         keyboardEvent.preventDefault()

     @_domListener @_canvas, "keyup", (keyboardEvent) =>
-      @queueKeyEvents "keyUp", -> new KeyEvent "keyUp", keyboardEvent
+      @keyUpEvent keyboardEvent

   _enableHtmlFocusOnCanvas: ->
     @_canvas.tabIndex = "0"


Fix linter error

Fix linter error

--- build/tasks/codesign-task.coffee
+++ build/tasks/codesign-task.coffee
@@ -63,7 +63,8 @@
       url: sourceUrl
       headers: {
         'User-Agent': 'Atom Signing Key build task',
-        'Accept': 'application/vnd.github.VERSION.raw' }
+        'Accept': 'application/vnd.github.VERSION.raw'
+      }
     }
     request(options)
       .pipe(fs.createWriteStream(targetPath))


coordinator: Better errors in findQueue

coordinator: Better errors in findQueue

--- src/coordinator.coffee
+++ src/coordinator.coffee
@@ -40,7 +40,10 @@
   part = participants[partIdByRole] if not part?
   throw new Error "No participant info found for '#{partId}'" if not part?
   for port in part[dir]
-    return port.queue if port.id == portName
+    if port.id == portName
+      throw new Error "Queue for #{dir} #{portName} missing in #{JSON.stringify(part)}" if not port.queue
+      return port.queue
+  throw new Error "No matching port found for #{portName} in #{JSON.stringify(part)}"

 connectionFromBinding = (participants, binding) ->
   byRole = {}


the backing data is now in the same format that the C64 uses

--- web-editor/app.coffee
+++ web-editor/app.coffee
@@ -35,6 +35,15 @@

 scale = 3 # number of on-screen pixels to each C64 pixel

+# Rather than have structures that reflect character and sprite geometries, the
+# backing data is kept as a single Array of byte ( each represented as a Number
+# between 0 and 255), 16384 entries long ( for 256 64-byte sprites), in much
+# the same format as the C64 itself uses.  This is because a single character
+# set or sprite sheet may contain both hi-res and multi-color elements with no
+# record of which is which, so this tool could not possibly export the data
+# unless it was already in the format expected by the C64.
+#
+data = []
 character_set = null
 editor = null
 macro = null
@@ -45,17 +54,23 @@

 class Character

-  constructor: ->
-    @data = ( [0,0,0,0,0,0,0,0] for row in [0..7] )
+  constructor: ( @code ) ->
     @canvas = elm 'canvas', width:8*scale, height:8*scale
     @context = @canvas.getContext '2d'
     @image_data = @context.createImageData  @canvas.width, @canvas.height

   pixel_at: ( row, column ) ->
-    @data[ row][ column]
-
+    address = 8 * @code + row
+    mask = 1 << (7 - column)
+    if 0 < ( (data[address] || 0) & mask ) then 1 else 0
+
+  # @param  color  0 or 1
   set_pixel: ( row, column, color ) ->
-    @data[ row][ column] = color
+    address = 8 * @code + row
+    if pixel is 1
+      data[address] |= (1 << column)
+    else
+      data[address] &|= ~(1 << column)

   render: =>
     for y in [0..@image_data.height - 1]
@@ -89,7 +104,7 @@
       tr = $( elm 'tr', {})
       for column in [0..31]
         character_code = 32 * row + column
-        character = new Character()
+        character = new Character character_code
         @characters[ character_code] = character
         td = elm 'td', id:"c#{character_code}", title:@in_hex(character_code), character.canvas
         tr.append  td
@@ -111,23 +126,11 @@

   import_from: ( encoded_text) ->
     data = Base64::decoded  encoded_text.replace(/[\r\n]/g, '')
-    $.each @characters, ( code, character ) ->
-      for row in [0..7]
-        for column in [0..7]
-          mask = 1 << (7 - column)
-          character.data[ row][ column] = if 0 < (data[ 8*code + row ] & mask) then 1 else 0
-      character.render()
+    character_set.render()
     editor.render()
     macro.render()

   export: ->
-    data = []
-    for character in @characters
-      for row in character.data
-        byte = 0
-        for bit in [0..7]
-          byte |= (1 << bit) if 1 == row[ 7 - bit ]
-        data.push  byte
     "cat <<. | base64 -d > charset.bin\n"+ Base64::encoded( data )+ "\n.\n"

   in_hex: ( number ) ->


Only re-read .eslintrc when auto indent enabled.

--- lib/auto-indent.coffee
+++ lib/auto-indent.coffee
@@ -70,7 +70,9 @@
       @eslintrcFilename = new File(@eslintrcFilename)
       @getEslintrcOptions(@eslintrcFilename.getPath())
       # watch eslintrc for changes
-      @disposables.add @eslintrcFilename.onDidChange => @getEslintrcOptions(@eslintrcFilename.getPath())
+      @disposables.add @eslintrcFilename.onDidChange =>
+        return unless @autoJsx
+        @getEslintrcOptions(@eslintrcFilename.getPath())

   destroy: () ->
     @disposables.dispose()


Fix lint issues.

Fix lint issues.

--- lib/auto-indent.coffee
+++ lib/auto-indent.coffee
@@ -93,7 +93,7 @@
       cursorPositions = @editor.getCursorBufferPositions()
       if cursorPositions.length is @multipleCursorTrigger
         @multipleCursorTrigger = 1
-        bufferRow = 0;
+        bufferRow = 0
         for cursorPosition in cursorPositions
           if cursorPosition.row > bufferRow then bufferRow = cursorPosition.row
       else
@@ -171,7 +171,7 @@
               hardTabsFound

           if isFirstTokenOfLine
-              firstTagInLineIndentation =  tagIndentation
+            firstTagInLineIndentation =  tagIndentation

           # big switch statement follows for each token. If the line is reformated
           # then we recalculate the new position.
@@ -239,7 +239,7 @@
               stackOfTokensStillOpen.push idxOfToken
               idxOfToken++
               isInsideTag = true
-              break;
+              break

             # tags ending </tag>
             when JSXTAG_CLOSE
@@ -264,7 +264,7 @@
               if parentTokenIdx >=0 then tokenStack[parentTokenIdx].termsThisTagIdx = idxOfToken
               idxOfToken++
               isInsideTag = false
-              break;
+              break

             # tags ending />
             when JSXTAG_SELFCLOSE_END
@@ -298,7 +298,7 @@
                 tokenStack[parentTokenIdx].termsThisTagIdx = idxOfToken
               idxOfToken++
               isInsideTag = false
-              break;
+              break

             # tags ending >
             when JSXTAG_CLOSE_ATTRS
@@ -329,7 +329,7 @@
               if parentTokenIdx >= 0 then tokenStack[parentTokenIdx].termsThisTagsAttributesIdx = idxOfToken
               idxOfToken++
               isInsideTag = false
-              break;
+              break

             # embeded expression start { but not inside a tag
             when JSXBRACE_OPEN
@@ -361,7 +361,7 @@

               stackOfTokensStillOpen.push idxOfToken
               idxOfToken++
-              break;
+              break

             # embeded expression end } but not inside a tag
             when JSXBRACE_CLOSE
@@ -386,7 +386,7 @@
                 parentTokenIdx: parentTokenIdx         # ptr to <tag
               if parentTokenIdx >=0 then tokenStack[parentTokenIdx].termsThisTagIdx = idxOfToken
               idxOfToken++
-              break;
+              break

             # Javascript brace Start {
             when BRACE_OPEN
@@ -430,7 +430,7 @@

               stackOfTokensStillOpen.push idxOfToken
               idxOfToken++
-              break;
+              break

             # Javascript brace End }
             when BRACE_CLOSE
@@ -456,7 +456,7 @@
                   parentTokenIdx: parentTokenIdx         # ptr to <tag
                 if parentTokenIdx >=0 then tokenStack[parentTokenIdx].termsThisTagIdx = idxOfToken
                 idxOfToken++
-              break;
+              break

       if idxOfToken and not tokenOnThisLine and  row isnt range.end.row
         if not isInsideTag


Added pending state to spec helper.

Added pending state to spec helper.

--- spec/compiler.coffee
+++ spec/compiler.coffee
@@ -7,13 +7,16 @@
 {expect} = chai

-parse = (source, expectation) ->
-  result = null
+parse = (source, expectation, pending) ->
+  itFn = if pending then xit else it
+
   describe source, ->
-    it 'should do something', ->
+    result = null
+
+    itFn 'should do something', ->
       result = parser.parse source
       expect(result).to.be.an 'array'
-    it 'should match expected', ->
+    itFn 'should match expected', ->
       expect(result).to.eql expectation


Codo opts are in the grunt task now

Codo opts are in the grunt task now

All tests are now included in the lint check

#12 Investigate move to Jasmine

--- grunt/coffeelint.coffee
+++ grunt/coffeelint.coffee
@@ -28,7 +28,7 @@
   sourcesAndTests: [
     'src/*.coffee'
     'src/**/*.coffee'
-    'test/unit/*.coffee'
-    'test/unit/**/*.coffee'
+    'test/*.coffee'
+    'test/**/*.coffee'
   ]
 }


show service name

show service name

--- imports/ui/pages/InstanceDetailPage.cjsx
+++ imports/ui/pages/InstanceDetailPage.cjsx
@@ -136,7 +136,7 @@

               <Anchor reverse={true} href={@props.serviceLinks[name]} target='_blank'
                 icon={<Icons.Base.Link style={width:20} />}
-                label={{name}} />
+                label={{service.name}} />

               <Box flex={true} justify='end' direction='row'>
                 <Anchor reverse={true} target='_blank'


engine data module

engine data module

--- src/engine_data.coffee
+++ src/engine_data.coffee
@@ -1,5 +1,5 @@
 ###*
-The Delay module provides methods to trigger events after a number of steps have passed.
+The Data module provides methods to trigger events after a number of steps have passed.

 @name Delay
 @fieldOf Engine


fix subpages without response variables

fix subpages without response variables

--- lib/codesharing.coffee
+++ lib/codesharing.coffee
@@ -50,20 +50,25 @@
 # Converts map of variables and array of functions to executable Javascript
 # code
 isolatedCodeFrom = (vars, execs, context) ->
+
+
   code = "(function(){\n"

-  # Declare local variables
-  variableNames = (name for name, _ of vars).join(", ")
-  code += "var #{ removeTrailingComma variableNames };\n"
-
-  # Set local and context variables
-  for name, variable of vars
-    code += "#{ name } = this.#{ name } = #{ codeFrom variable };\n"
-
-  # Create immediately functions from the function array
-
-  for fn in execs
-    code += executableFrom fn, context
+  # variables are not always set when using reponses
+  if vars?
+    # Declare local variables
+    variableNames = (name for name, _ of vars).join(", ")
+
+    code += "var #{ removeTrailingComma variableNames };\n"
+
+    # Set local and context variables
+    for name, variable of vars
+      code += "#{ name } = this.#{ name } = #{ codeFrom variable };\n"
+
+  if execs?
+    # Create immediately functions from the function array
+    for fn in execs
+      code += executableFrom fn, context

   # Set context of this closure. ie. "this"
   code += "}).call(#{ context });\n"
@@ -208,7 +213,8 @@
   # Works like app.share but for only this one response
   responseShare = (name, value) ->
     # "this" is the response object
-    localVars = this.localVars ?= {}
+    this.localVars ?= {}
+    localVars = this.localVars
     if typeof name is "object"
       for k, v of name
         localVars[k] = v


Decluttered constructors in finger tree code.

--- lib/finger_tree.coffee
+++ lib/finger_tree.coffee
@@ -76,9 +76,7 @@

     # A node.
     class Node2
-      constructor: ->
-        [@a, @b] = arguments
-        @v = norm @a, @b
+      constructor: (@a, @b) -> @v = norm @a, @b

       reduceLeft:  (z, op) -> op(op(z, @a), @b)
       reduceRight: (op, z) -> op(@a, op(@b, z))
@@ -86,9 +84,7 @@
       measure: -> @v

     class Node3
-      constructor: ->
-        [@a, @b, @c] = arguments
-        @v = norm @a, @b, @c
+      constructor: (@a, @b, @c) -> @v = norm @a, @b, @c

       reduceLeft:  (z, op) -> op(op(op(z, @a), @b), @c)
       reduceRight: (op, z) -> op(@a, op(@b, op(@c, z)))
@@ -98,7 +94,7 @@

     # A digit in a finger tree.
     class Digit1
-      constructor: -> [@a] = arguments
+      constructor: (@a) ->

       reduceLeft:  (z, op) -> op(z, @a)
       reduceRight: (op, z) -> op(@a, z)
@@ -117,7 +113,7 @@
       split: (p, i) -> [Empty, @a, Empty]

     class Digit2
-      constructor: -> [@a, @b] = arguments
+      constructor: (@a, @b) ->

       reduceLeft:  (z, op) -> op(op(z, @a), @b)
       reduceRight: (op, z) -> op(@a, op(@b, z))
@@ -142,7 +138,7 @@
           [new Digit1(@a), @b, Empty]

     class Digit3
-      constructor: -> [@a, @b, @c] = arguments
+      constructor: (@a, @b, @c) ->

       reduceLeft:  (z, op) -> op(op(op(z, @a), @b), @c)
       reduceRight: (op, z) -> op(@a, op(@b, op(@c, z)))
@@ -170,7 +166,7 @@
           [new Digit2(@a, @b), @c, Empty]

     class Digit4
-      constructor: -> [@a, @b, @c, @d] = arguments
+      constructor: (@a, @b, @c, @d) ->

       reduceLeft:  (z, op) -> op(op(op(op(z, @a), @b), @c), @d)
       reduceRight: (op, z) -> op(@a, op(@b, op(@c, op(@d, z))))
@@ -221,7 +217,7 @@

     # A finger tree with a single element.
     class Single
-      constructor: (a) -> @a = a
+      constructor: (@a) ->

       isEmpty: -> false

@@ -252,10 +248,7 @@
       asTree = (s) -> s.reduceLeft Empty, (a, b) -> a.before b
       asSeq  = (s) -> s.reduceRight ((a, b) -> Sequence.conj a, -> b), null

-      constructor: (left, mid, right) ->
-        @l = left
-        @m = -> val = mid(); (@m = -> val)()
-        @r = right
+      constructor: (@l, m, @r) -> @m = -> val = m(); (@m = -> val)()

       isEmpty: -> false


revise to optimization friendly version, and handle variations better

--- lib/get-options.coffee
+++ lib/get-options.coffee
@@ -1,37 +1,41 @@
-
 flatten = require 'array-flatten'

-module.exports = (args...) ->
+# buildChain(someFn, someFn2)
+# buildChain([ someFn, someFn2 ], [moreFns, moreFns2])
+# buildChain({ array:[ someFn, someFn2 ] })
+module.exports = () ->

-  arg1 = args?[0]
+  # optimization friendly method
+  args = new Array arguments.length
+  args[i] = arguments[i] for i in [0...arguments.length]

-  array =
-    # buildChain([ someFn, someFn2 ], [moreFns, moreFns2])
-    if Array.isArray arg1 then flatten args
+  # include any array args into the main array. one big happy array.
+  args = flatten args

-    # buildChain(someFn, someFn2)
-    else if typeof arg1 is 'function' then args
+  # we either have an array of functions, or, an "options" object at [0]
+  array = switch typeof args[0]

-    # buildChain({ array:[ someFn, someFn2 ] })
-    else if Array.isArray arg1?.array then arg1.array
+    # if the first arg is a function then we must not have received
+    # an options object. So, use the array we already created.
+    when 'function' then args

-    # otherwise start with an empty
+    # if it's an object then use the `array` property, or []
+    when 'object'
+      options = args[0]
+      args[0].array ? []
+
+    # otherwise, make a brand new empty array
     else []

   # validate array's contents: must be functions
   for element, index in array
-    unless 'function' is typeof(element)
+    unless 'function' is typeof element
       return error:'Elements must be functions', element:element, index:index

-  # we have the array so, put those in an options object.
-  # mark we validated the array
-  options = array:array, __validated:true
-
-  # if we were provided an options object to start,
-  # then select properties we care about, only if they exist.
-  if typeof arg1 is 'object'
-    options.base  = arg1.base if arg1?.base?
-    options.props = arg1.props if arg1?.props?
-    options.buildContext = arg1.buildContext if arg1?.buildContext?
-
-  return options
+  # # specify all the options properties
+  # this is a marker telling the Chain constructor we've validated these
+  __validated:true
+  array: array
+  base : options?.base
+  props: options?.props
+  buildContext: options?.buildContext


Save user's phone number to redis

Save user's phone number to redis

--- src/fleepClient.coffee
+++ src/fleepClient.coffee
@@ -126,6 +126,9 @@

       if not user.email? and event.email?
         user.email = event.email
+
+      if not user.phone_nr? and event.phone_nr?.length
+        user.phone = event.phone_nr

       return


Fix coding standard errors

Fix coding standard errors

--- src/fleepClient.coffee
+++ src/fleepClient.coffee
@@ -151,9 +151,10 @@

     @handleMessage event

-  # Determines whether a particular message in a particular message has been seen
+  # Determines whether a particular message in
+  # a particular message has been seen
   isMessageSeen: (conversation_id, message_nr) =>
-    seen = @getKnownConversations()?[conversation_id]?['last_message_nr'] >= message_nr
+    @getKnownConversations()?[conversation_id]?['last_message_nr'] >= message_nr

   # Parses an incoming message and passes it to Hubot
   # Message is a Fleep event response object
@@ -240,9 +241,9 @@
       unless resp.conversations? and resp.conversations.length
         return

-      for conversation in resp.conversations
-        unless @getKnownConversations()?[conversation.conversation_id]?
-          @saveConversation conversation.conversation_id, conversation.last_message_nr
+      for conv in resp.conversations
+        unless @getKnownConversations()?[conv.conversation_id]?
+          @saveConversation conv.conversation_id, conv.last_message_nr
       @robot.logger.debug 'Conversation list synced'
       cb()