Add comments to all methods and reorder some method definitions

--- src/mentions-kinder.coffee
+++ src/mentions-kinder.coffee
@@ -1,5 +1,5 @@
 ###
-  Base class
+  MentionsKinder base class
 ###
 class MentionsKinder
   KEY = { RETURN: 13, ESC: 27 }
@@ -24,7 +24,7 @@

     deserializeRegex: /\[(.)(.+?)\]\((\w+):(.+?)\)/g

-
+  # default trigger options
   triggerDefaultOptions:
     autocompleter: Autocompleter
     formatter: (data)->
@@ -39,6 +39,7 @@
       "[#{data.trigger}#{data.name}](#{data.triggerOptions.triggerName}:#{data.value})"

   # le constructor
+  # setup mentions kinder
   constructor: (element, options)->
     @_ensureInput(element)
     @_buildOptions(options)
@@ -48,43 +49,26 @@
     # trigger focus, because contenteditable divs doesn't support html5 autofocus
     @$editable.focus() if @$editable.attr('autofocus')

-  handleInput: (e)=>
-    charCode = e.charCode || e.which || e.keyCode
-    char = String.fromCharCode(charCode)
-    if !@isAutocompleting() && @trigger[char]
-      e.preventDefault()
-      @startAutocomplete(char)
-
-    # don't allow newline in singleline input
-    if charCode == KEY.RETURN && !@multiline
-      e.preventDefault()
-      if @submitOnEnter
-        @$form.submit()
-
-  handleKeyup: (e)=>
-    if @isAutocompleting()
-      @updateAutocomplete()
-
-    switch e.keyCode
-      # abort if escape is pressed
-      when KEY.ESC
-        @abortAutocomplete()
-
-    # abort if the cursor left the temp mention
-    if @isAutocompleting() && !@_isCaretInTempMention()
-      @abortAutocomplete()
-
-    # set plaintext to our hidden field
-    @populateInput()
-
-  handlePaste: =>
-    # defer, content is not in yet
-    setTimeout(@cleanEditable, 0)
-
+  # serialize text and set it to the hidden field
+  populateInput: =>
+    val = @serializeEditable()
+    @$input?.val(val)
+
+  # serialize the editable element
+  serializeEditable: ->
+    @_serializeNode(@$editable[0]).join('')
+
+  # Deserialize the original input into the editable
+  deserializeFromInput: =>
+    @$editable.html @_deserialize(@$originalInput.val())
+
+  # Helper method
   # un**** our editable after paste or other ****
   cleanEditable: =>
-    @cleanChildNodes(@$editable[0])
-
+    @_cleanChildNodes(@$editable[0])
+
+  # start autocompletion for trigger char
+  # Create new temp mention, create autompleter and set caret correctly
   startAutocomplete: (triggerChar)->
     @_current = {
       trigger: triggerChar,
@@ -106,6 +90,7 @@
     @_current.autocompleter.always(@populateInput)
     @_current.autocompleter.search('')

+  # update autocompletion
   updateAutocomplete: ->
     text = @_current.$tempMention.text()
     triggerLength = @_current.trigger.length
@@ -117,6 +102,7 @@
     else
       @abortAutocomplete()

+  # check if there is an autocompletion started
   isAutocompleting: ->
     if @_current?
       if $.contains(@$editable, @_current.$tempMention)
@@ -128,10 +114,13 @@
     else
       false

-
+  # abort the autocompletion
   abortAutocomplete: ->
     @isAutocompleting() && @_current.autocompleter.abort()

+  # Event handler
+  # autocompleter done
+  # Create valid mention and add it to the editable
   handleAutocompleteDone: (data)=>
     # add current trigger state to data to allow access from formatters and serializers
     data = $.extend({}, @_current, data)
@@ -150,6 +139,9 @@

     @_current = null

+  # Event handler
+  # autocomplete fail
+  # No mention created, transform temp mention to text node
   handleAutocompleteFail: =>
     # convert to text
     textNode = document.createTextNode(@_current.$tempMention.text())
@@ -159,10 +151,59 @@

     @_current = null

+  # Event handler
+  # keypress
+  # Invoke autocompleter if possible trigger char found
+  # Check enter key in single line inputs to trigger form submit (default single line input behaviour)
+  handleInput: (e)=>
+    charCode = e.charCode || e.which || e.keyCode
+    char = String.fromCharCode(charCode)
+    if !@isAutocompleting() && @trigger[char]
+      e.preventDefault()
+      @startAutocomplete(char)
+
+    # don't allow newline in singleline input
+    if charCode == KEY.RETURN && !@multiline
+      e.preventDefault()
+      @$form.submit() if @submitOnEnter
+
+  # Event handler
+  # keyup
+  # Update or cancel autocompleter
+  # Update hidden field with serialized text
+  handleKeyup: (e)=>
+    if @isAutocompleting()
+      @updateAutocomplete()
+
+    switch e.keyCode
+      # abort if escape is pressed
+      when KEY.ESC
+        @abortAutocomplete()
+
+    # abort if the cursor left the temp mention
+    if @isAutocompleting() && !@_isCaretInTempMention()
+      @abortAutocomplete()
+
+    # set plaintext to our hidden field
+    @populateInput()
+
+  # Event handler
+  # paste
+  # Cleanup editable
+  handlePaste: =>
+    # defer, content is not in yet
+    setTimeout(@cleanEditable, 0)
+
+  # Event handler
+  # (form) reset
+  # reset editable
   handleReset: =>
     @$editable.empty().blur()
     @$input.val(@$originalInput.val())

+  # Event handler
+  # (form) reset, blur, focus
+  # show/hide the placeholder
   handlePlaceholder: (e)=>
     if e.type == 'focus'
       @$placeholder?.detach()
@@ -170,6 +211,9 @@
       if @_strip(@serializeEditable()) == ''
         @$editable.empty().append(@$placeholder)

+  # Event handler
+  # click
+  # delete mention on click
   handleDelete: (e)=>
     e.preventDefault()
     $currentMentionNode = $(e.target).parents('.mention')
@@ -178,112 +222,7 @@
       @_setCaretToStartOf(nextNode) if nextNode
       $currentMentionNode.remove()

-  populateInput: =>
-    val = @serializeEditable()
-    @$input?.val(val)
-
-  serializeEditable: ->
-    @serializeNode(@$editable[0]).join('')
-
-  serializeNode: (parentNode)->
-    textNodes = []
-    for node in parentNode.childNodes
-      # is text node, append text
-      if node.nodeType == 3 # nodeType 3 is a text node
-        textNodes.push node.data
-      # is br node, append newline
-      else if node.nodeName.toUpperCase() == 'BR'
-        textNodes.push "\n"
-      # is mention, append serializedMention
-      else if serializedMention = $(node).attr('serialized-mention')
-        textNodes.push serializedMention
-      # is p or div, append newline and serialize children
-      else if node.nodeName.toUpperCase() == 'P' || node.nodeName.toUpperCase() == 'DIV'
-        # add newline only if first child is not a br-node, prevent endless new line duplicating
-        textNodes.push("\n") unless node.childNodes[0]?.nodeName?.toUpperCase() == 'BR'
-        textNodes = textNodes.concat @serializeNode(node)
-      # is any other tag, serialize children
-      else
-        textNodes = textNodes.concat @serializeNode(node)
-    textNodes
-
-  # if we remove items from nodeList it is updated live, that results in missed nodes
-  # therefore we save the node references in an array and iterate over that
-  cloneReferences = (nodes)->
-    for node in nodes
-      node
-
-  # iterate over child nodes and clean them
-  cleanChildNodes: (parentNode)->
-    for node in cloneReferences(parentNode.childNodes)
-      @cleanNode(node)
-
-    true
-
-  # clean a single node
-  # recurses into child nodes
-  # TODO keep <br>'s
-  cleanNode: (node)->
-    # dont clean text nodes or mention nodes
-    if node.nodeType == 3
-      # do nothing
-    else if node.nodeName == 'BR'
-      $(node).replaceWith(' ') unless @multiline # clean breaks in single line inputs
-    else if $(node).attr('serialized-mention')
-      $(node).attr('contenteditable', false) # ensure contenteditable is set after paste
-    else
-      # clean all children and replace node with them
-      if node.childNodes?.length > 0
-        @cleanChildNodes(node)
-        $(node).replaceWith(node.childNodes)
-      # remove node
-      else
-        $(node).remove()
-
-    true
-
-  deserializeFromInput: =>
-    @$editable.html @_deserialize(@$originalInput.val())
-
-  _deserialize: (text)->
-    result = []
-    regex = @options.deserializeRegex
-    #console.log "START", text
-    pointer = 0
-
-    # deserialize magic
-    loop
-      match = regex.exec(text)
-      if match
-        # add text before matched token
-        unless match.index == 0
-          #console.log "LOOP substring from #{pointer} to #{match.index}", text.substring(pointer, match.index)
-          @_deserializeText.call(result, text.substring(pointer, match.index))
-          #result.push document.createTextNode(text.substring(pointer, match.index))
-        pointer = regex.lastIndex
-        # deserialize matched token
-        #console.log "LOOP deserialize", match[0]
-        result.push @options.deserialize.apply(@, match)
-      else
-        unless pointer == text.length
-          #console.log "LAST substring from #{pointer} to #{text.length}", text.substring(pointer, text.length)
-          lastText = text.substring(pointer, text.length)
-          @_deserializeText.call(result, lastText) unless lastText == ''
-          #result.push document.createTextNode(lastText)  unless lastText == ''
-        break
-    # return array of nodes
-    #console.log "RESULT", result, "\n\n"
-    result
-
-  # Split text into lines and add br-nodes to keep line breaks
-  # musst be called with array as context (this), for example see _deserialize a few lines above
-  _deserializeText: (text)->
-    #console.log "_deserializeText", text
-    lines = text.split("\n")
-    for line, i in lines
-      @push document.createTextNode(line)
-      @push document.createElement('br') unless i == lines.length - 1
-
+  # Check if $.mentionsKinder is seting up to the correct element
   _ensureInput: (element)->
     @$originalInput = $(element)
     unless @$originalInput.is('input[type=text],textarea')
@@ -291,6 +230,7 @@

     @multiline = @$originalInput.is('textarea')

+  # Create default flavoured option objects
   _buildOptions: (options)->
     # build options
     @options = $.extend {}, @defaultOptions, options
@@ -329,6 +269,7 @@
     # return
     undefined

+  # Bind all necessary events
   _setupEvents: ->
     # editable events
     @$editable.bind 'keypress', @handleInput
@@ -345,6 +286,107 @@
       @$form.on('reset', @handleReset)
       @$form.on('reset', @handlePlaceholder)

+  # if we remove items from nodeList it is updated live, that results in missed nodes
+  # therefore we save the node references in an array and iterate over that
+  cloneReferences = (nodes)->
+    for node in nodes
+      node
+
+  # Helper method to clean cloned nodes
+  # iterate over child nodes and clean them
+  _cleanChildNodes: (parentNode)->
+    for node in cloneReferences(parentNode.childNodes)
+      @_cleanNode(node)
+
+    true
+
+  # Helper method to clean a single node
+  # recurses into child nodes
+  _cleanNode: (node)->
+    # dont clean text nodes or mention nodes
+    if node.nodeType == 3
+      # do nothing
+    else if node.nodeName == 'BR'
+      $(node).replaceWith(' ') unless @multiline # clean breaks in single line inputs
+    else if $(node).attr('serialized-mention')
+      $(node).attr('contenteditable', false) # ensure contenteditable is set after paste
+    else
+      # clean all children and replace node with them
+      if node.childNodes?.length > 0
+        @_cleanChildNodes(node)
+        $(node).replaceWith(node.childNodes)
+      # remove node
+      else
+        $(node).remove()
+
+    true
+
+  # Helper Method toserialize a node, support for different node types
+  _serializeNode: (parentNode)->
+    textNodes = []
+    for node in parentNode.childNodes
+      # is text node, append text
+      if node.nodeType == 3 # nodeType 3 is a text node
+        textNodes.push node.data
+        # is br node, append newline
+      else if node.nodeName.toUpperCase() == 'BR'
+        textNodes.push "\n"
+        # is mention, append serializedMention
+      else if serializedMention = $(node).attr('serialized-mention')
+        textNodes.push serializedMention
+        # is p or div, append newline and serialize children
+      else if node.nodeName.toUpperCase() == 'P' || node.nodeName.toUpperCase() == 'DIV'
+        # add newline only if first child is not a br-node, prevent endless new line duplicating
+        textNodes.push("\n") unless node.childNodes[0]?.nodeName?.toUpperCase() == 'BR'
+        textNodes = textNodes.concat @_serializeNode(node)
+        # is any other tag, serialize children
+      else
+        textNodes = textNodes.concat @_serializeNode(node)
+
+    textNodes
+
+  # Helper Method to deserialize given text into nodes
+  _deserialize: (text)->
+    result = []
+    regex = @options.deserializeRegex
+    #console.log "START", text
+    pointer = 0
+
+    # deserialize magic
+    loop
+      match = regex.exec(text)
+      if match
+        # add text before matched token
+        unless match.index == 0
+          #console.log "LOOP substring from #{pointer} to #{match.index}", text.substring(pointer, match.index)
+          @_deserializeText.call(result, text.substring(pointer, match.index))
+          #result.push document.createTextNode(text.substring(pointer, match.index))
+        pointer = regex.lastIndex
+        # deserialize matched token
+        #console.log "LOOP deserialize", match[0]
+        result.push @options.deserialize.apply(@, match)
+      else
+        unless pointer == text.length
+          #console.log "LAST substring from #{pointer} to #{text.length}", text.substring(pointer, text.length)
+          lastText = text.substring(pointer, text.length)
+          @_deserializeText.call(result, lastText) unless lastText == ''
+          #result.push document.createTextNode(lastText)  unless lastText == ''
+        break
+    # return array of nodes
+    #console.log "RESULT", result, "\n\n"
+    result
+
+  # Helper method for _deserialize
+  # Split text into lines and add br-nodes to keep line breaks
+  # musst be called with array as context (this), for example see _deserialize a few lines above
+  _deserializeText: (text)->
+    #console.log "_deserializeText", text
+    lines = text.split("\n")
+    for line, i in lines
+      @push document.createTextNode(line)
+      @push document.createElement('br') unless i == lines.length - 1
+
+  # Helper method to create a proper rangy selection
   _prepareSetCaretTo: (node)->
     selection = rangy.getSelection()
     range = selection.getRangeAt(0)
@@ -352,17 +394,21 @@
     selection.setSingleRange(range)
     selection

+  # Collapse selection to the end
   _setCaretToEndOf: (node)->
     @_prepareSetCaretTo(node).collapseToEnd()

+  # Collapse selection to the beginning
   _setCaretToStartOf: (node)->
     @_prepareSetCaretTo(node).collapseToStart()

+  # Helper method to check if caret is in the current temp mention
   _isCaretInTempMention: ->
     if @isAutocompleting()
       range = rangy.getSelection().getRangeAt(0)
       range?.compareNode(@_current.$tempMention.get(0)) == range.NODE_BEFORE_AND_AFTER

+  # Helper method to strip whitespaces from start/end of text
   _strip: (text)->
     text.replace(/^\s*(.*?)\s*$/gm, '$1')


replace magic number 3 with TEXT_NODE

replace magic number 3 with TEXT_NODE

--- src/mentions-kinder.coffee
+++ src/mentions-kinder.coffee
@@ -13,6 +13,7 @@
 ###
 class @MentionsKinder
   KEY = { RETURN: 13, ESC: 27 }
+  TEXT_NODE = 3
   # default options, exposed under $.mentionsKinder.defaultOptions
   defaultOptions:
     trigger:
@@ -379,7 +380,7 @@
   # recurses into child nodes
   _cleanNode: (node)->
     # dont clean text nodes or mention nodes
-    if node.nodeType == 3
+    if node.nodeType == TEXT_NODE
       # do nothing
     else if node.nodeName.toUpperCase() == 'BR'
       $(node).replaceWith(' ') unless @multiline # clean breaks in single line inputs
@@ -401,7 +402,7 @@
     textNodes = []
     for node in parentNode.childNodes
       # is text node, append text
-      if node.nodeType == 3 # nodeType 3 is a text node
+      if node.nodeType == TEXT_NODE
         textNodes.push node.data
         # is br node, append newline
       else if node.nodeName.toUpperCase() == 'BR'


strip the phrase default from tool output when changing toolchain

--- lib/toolchain-view.coffee
+++ lib/toolchain-view.coffee
@@ -34,7 +34,7 @@
       [responseSuccess, responseError] = ["", ""]
       @runCommandOut(
         @toolBinPath
-        ['default', item]
+        ['default', item.replace('(default)','')]
         stderr = (data) -> responseError += data.toString()
         stdout = (data) -> responseSuccess += data.toString()
         exit = (code) => callback(item, code, responseSuccess, responseError)


Removed non-existent parameter from test description.

--- test/unit/add-hooks-test.coffee
+++ test/unit/add-hooks-test.coffee
@@ -15,7 +15,7 @@
   'path': pathStub
 }

-describe 'addHooks(hooks, pattern, callback)', () ->
+describe 'addHooks(hooks, pattern)', () ->

   transactions = {}


Windows multi-line copy/paste fix

Windows multi-line copy/paste fix

--- lib/views/terminal.coffee
+++ lib/views/terminal.coffee
@@ -76,7 +76,7 @@
           if event.which == 67 && event.shiftKey && event.ctrlKey
             Clipboard.writeText(getSelection().toString())
           else if event.which == 86 && event.shiftKey && event.ctrlKey
-            ipc.send 'terminal-data', Clipboard.readText()
+            ipc.send 'terminal-data', Clipboard.readText().replace(/\n/g, "\r")
           else if event.altKey
             console.log 'Saved from alt key disaster!'
           else


Fixed viewInBrowser

Fixed viewInBrowser

--- lib/zombie/browser.coffee
+++ lib/zombie/browser.coffee
@@ -784,7 +784,7 @@
   #
   # Views the current document in a real Web browser.  Uses the default system browser on OS X, BSD and Linux.  Probably
   # errors on Windows.
-  @viewInBrowser = (browser)->
+  viewInBrowser: (browser)->
     require("./bcat").bcat @html()

   # ### browser.lastRequest => HTTPRequest


Added fix for mac OSX changing double quotes into non-standard characters

--- src/memes.coffee
+++ src/memes.coffee
@@ -89,11 +89,9 @@
     for code, meme of memes
       msg.send "http://memegen.link/#{code}/#{escape(code)}/#{escape(meme)}.jpg"

-  robot.respond /meme me (\w+) (\"[^"]+\") (\"[^"]+\")/i, (msg) ->
-
+  robot.respond /meme me (\w+) ([\"\“\”][^\"\“\”]+[\"\“\”]) ([\"\“\”][^\"\“\”]+[\"\“\”])/i, (msg) ->
     meme = if checkCode(msg.match[1].toLowerCase(), memes) then msg.match[1].toLowerCase() else 'doge'
-    top    = msg.match[2].replace(/"/g, '').trim().replace(/\s+/g, '-')
-    bottom = msg.match[3].replace(/"/g, '').trim().replace(/\s+/g, '-')
+    top    = msg.match[2].replace(/"|“|”/g, '').trim().replace(/\s+/g, '-')
+    bottom = msg.match[3].replace(/"|“|”/g, '').trim().replace(/\s+/g, '-')

     msg.send "http://memegen.link/#{meme}/#{escape(top)}/#{escape(bottom)}.jpg"
-


atom config update

atom config update

--- dotfiles/atom/config.cson
+++ dotfiles/atom/config.cson
@@ -41,6 +41,6 @@
     'microBreakLengthInSeconds': 30
     'microBreakIntervalInSeconds': 1200
   'linter':
-    'showStatusBarWhenCursorIsInErrorRange': true
+    'statusBar': 'Show error if the cursor is in range'
   'atom-beautify':
     '_analyticsUserId': 'bcb0826f-cb01-4339-bb49-4f4de6116860'


<debug> Correcting proper call to nested async function.

--- tests/controllers/sensor.coffee
+++ tests/controllers/sensor.coffee
@@ -171,16 +171,16 @@
     describe 'When we delete a Sensor (DELETE /sensors/:id) and its Driver doesn\'t allow it', ->

         sensor = fixturesSensor.supportedSensor1
-
+
         before (done) ->
-        	sensor.driverId = store.driver.id
-        	helpers.createSensor(sensor)
-	        done null
+            sensor.driverId = store.driver.id
+            helpers.createSensor(sensor) () ->
+                store.sensorId = helpers.getInStore('sensor').id
+                done null

         # @todo Modify "remove" function of driver so that it returns an error: before helpers.updateDriver ...

         it 'should allow requests', (done) ->
-            store.sensorId = helpers.getInStore('sensor').id
             @client.del "sensors/#{store.sensorId}", done

         it 'should return an error', ->


websockets service emits a connection.authenticated event for authenticated websockets connections

--- websockets/websockets.coffee
+++ websockets/websockets.coffee
@@ -22,6 +22,7 @@
             # if the client isn't authenticated, do not continue
             unless socket.user then return
             # register the user
+            @emit 'connection.authenticated', socket
             @users[socket.user.id] = {} if not @users[socket.user.id]
             @users[socket.user.id][socket.id] = socket
             # register the team


Implemented multi component route matching tests.

--- tests/match-routes-tests.coffee
+++ tests/match-routes-tests.coffee
@@ -29,6 +29,8 @@

 	# Route Matching

+	## Default
+
 	it 'matches the default route', () ->
 		router.go '/'
 		expect(router.current().component).toBe('default')
@@ -44,6 +46,8 @@
 	it 'matches the default route with a hash and a query string', () ->
 		router.go '/#hash?foo=bar'
 		expect(router.current().component).toBe('default')
+
+	## Single component

 	it 'matches a single component route', () ->
 		router.go '/foo'
@@ -61,17 +65,39 @@
 		router.go '/foo#hash?foo=bar'
 		expect(router.current().component).toBe('single')

-	it 'matches a multi component route', () -> expect(true).toBe(false)
-	it 'matches a multi component route with a query string', () -> expect(true).toBe(false)
-	it 'matches a multi component matches the default route with a hash', () -> expect(true).toBe(false)
+	## Multiple components
+
+	it 'matches a multi component route', () ->
+		router.go '/foo/bar'
+		expect(router.current().component).toBe('multi')
+
+	it 'matches a multi component route with a query string', () ->
+		router.go '/foo/bar?foo=bar'
+		expect(router.current().component).toBe('multi')
+
+	it 'matches a multi component matches the default route with a hash', () ->
+		router.go '/foo/bar#hash'
+		expect(router.current().component).toBe('multi')
+
+	it 'matches a multi component matches the default route with a hash and a query string', () ->
+		router.go '/foo/bar#hash?foo=bar'
+		expect(router.current().component).toBe('multi')
+
+	## Single parameter

 	it 'matches a route with a parameter', () -> expect(true).toBe(false)
 	it 'matches a route with a parameter and a query string', () -> expect(true).toBe(false)
 	it 'matches a route with a parameter and a hash', () -> expect(true).toBe(false)
+	it 'matches a route with a parameter and a hash and a query string', () -> expect(true).toBe(false)
+
+	## Multiple parameters

 	it 'matches a route with multiple parameters', () -> expect(true).toBe(false)
 	it 'matches a route with multiple parameters and a query string', () -> expect(true).toBe(false)
 	it 'matches a route with multiple parameters and a hash', () -> expect(true).toBe(false)
+	it 'matches a route with a parameter and a hash and a query string', () -> expect(true).toBe(false)
+
+	## Unknown route

 	it 'sets current to null if unable to match a route', () ->
 		router.go '/unknown'


resolve path correct. refs #61

resolve path correct. refs #61

--- lib/atom-ternjs-reference.coffee
+++ lib/atom-ternjs-reference.coffee
@@ -49,7 +49,7 @@
       @references = data
       for ref in data.refs
         ref.file = ref.file.replace(/^.\//, '')
-        ref.file = path.resolve(__dirname, ref.file)
+        ref.file = path.resolve(atom.project.relativizePath(@manager.server.rootPath)[0], ref.file)
       data.refs = _.uniq(data.refs, (item) =>
         JSON.stringify item
       )


Fix repl for Node 0.11.12.

Fix repl for Node 0.11.12.

Node changed their repl so that it inherits from readline.Interface.
This means that `prompt` is now the rli function and not the original
prompt string.  This may be a little hacky, but I figure it would give
someone a start if they want to do a better fix.

The commit that changed this in Node is joyent/node@3ae0b17c76f693dd2e68a46f78c7dc7f595b33c6

This bug was mentioned in Issue #3395.

--- repl.coffee
+++ repl.coffee
@@ -39,11 +39,13 @@

 addMultilineHandler = (repl) ->
   {rli, inputStream, outputStream} = repl
+  # Node 0.11.12 changed API, prompt is now _prompt.
+  origPrompt = repl._prompt ? repl.prompt

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

   # Proxy node's line listener
@@ -55,6 +57,7 @@
       rli.setPrompt multiline.prompt
       rli.prompt true
     else
+      rli.setPrompt origPrompt
       nodeLineListener cmd
     return

@@ -65,7 +68,7 @@
       # allow arbitrarily switching between modes any time before multiple lines are entered
       unless multiline.buffer.match /\n/
         multiline.enabled = not multiline.enabled
-        rli.setPrompt repl.prompt
+        rli.setPrompt origPrompt
         rli.prompt true
         return
       # no-op unless the current line is empty


Remove comments, oops

Remove comments, oops

--- src/octonode/client.coffee
+++ src/octonode/client.coffee
@@ -71,9 +71,6 @@
       query = {}
       query.page     = pageOrQuery if pageOrQuery?
       query.per_page = per_page if per_page?
-      #query =
-      #  page: pageOrQuery if pageOrQuery?
-      #  per_page: per_page if per_page?
     if typeof @token == 'string'
       query.access_token = @token
     else if typeof @token == 'object' and @token.id


:lipstick: Use _.pluck

:lipstick: Use _.pluck

--- lib/markdown-preview-view.coffee
+++ lib/markdown-preview-view.coffee
@@ -166,7 +166,7 @@
       codeBlock.empty()

       for tokens in grammar.tokenizeLines(text).slice(0, -1)
-        lineText = (token.value for token in tokens).join('')
+        lineText = _.pluck(tokens, 'value').join('')
         htmlEolInvisibles = ''
         codeBlock.append(EditorView.buildLineHtml({tokens, text: lineText, htmlEolInvisibles}))


Fix usage of SMTPTesting through CLI

Fix usage of SMTPTesting through CLI

--- tests/smtp-testing/index.coffee
+++ tests/smtp-testing/index.coffee
@@ -37,5 +37,5 @@
     smtpServer.listen parseInt(port), done

 unless module.parent
-    port = process.argv[1] or 587
+    port = process.argv[2] or 587
     SMTPTesting.init port, -> console.log arguments


Handle EIO open errors

Handle EIO open errors

--- src/workspace.coffee
+++ src/workspace.coffee
@@ -442,7 +442,7 @@
         when 'EACCES'
           atom.notifications.addWarning("Permission denied '#{error.path}'")
           return Q()
-        when 'EPERM', 'EBUSY', 'ENXIO'
+        when 'EPERM', 'EBUSY', 'ENXIO', 'EIO'
           atom.notifications.addWarning("Unable to open '#{error.path}'", detail: error.message)
           return Q()
         else


Remove extra word in comment in src/nodes.coffee (#5158)

--- src/nodes.coffee
+++ src/nodes.coffee
@@ -207,7 +207,7 @@

   # Occasionally it may be useful to make an expression behave as if it was 'hoisted', whereby the
   # result of the expression is available before its location in the source, but the expression's
-  # variable scope corresponds the source position. This is used extensively to deal with executable
+  # variable scope corresponds to the source position. This is used extensively to deal with executable
   # class bodies in classes.
   #
   # Calling this method mutates the node, proxying the `compileNode` and `compileToFragments`


Remove redundant class.

Remove redundant class.

--- lib/packet-tabular-result.coffee
+++ lib/packet-tabular-result.coffee
@@ -1,12 +0,0 @@
-Packet = require('./packet').Packet
-
-class TabularResultPacket extends Packet
-  constructor: (buffer) ->
-    super(buffer)
-
-  payloadString: (indent) ->
-    indent ||= ''
-
-    ''
-
-exports.TabularResultPacket = TabularResultPacket


Adding note to IE browsers

Adding note to IE browsers

--- tests/www/utils/browsers.coffee
+++ tests/www/utils/browsers.coffee
@@ -132,6 +132,9 @@
   # ----------------------------------------------------------------------------

   # WIN XP
+
+  # # IE's prior to 10 are waiting for some fixes on router
+
   # ie6_winxp:
   #   local: false
   #   browserName: 'internet explorer '


Adds missing renamed variable needed for previous commit

--- lib/simple-drag-drop-text.coffee
+++ lib/simple-drag-drop-text.coffee
@@ -28,8 +28,8 @@
         return

       @linesSubs?.dispose()
-      @lines = atom.views.getView(@editor)
-      @lines = @lines.querySelector '.lines'
+      @views = atom.views.getView(@editor)
+      @lines = @views.querySelector '.lines'
       @linesSubs = new SubAtom
       @linesSubs.add @lines, 'mousedown', (e) => @mousedown e
       @linesSubs.add @lines, 'mousemove', (e) =>


fix ChangeDetector.setValue

fix ChangeDetector.setValue


fix(citylist): do better filtering

fix(citylist): do better filtering

--- src/reducers/search.coffee
+++ src/reducers/search.coffee
@@ -16,8 +16,10 @@
   cities     : []

 isCity = (x) ->
-  (x.class is 'place'    and (x.type is 'city' or x.type is 'village')) or
-  (x.class is 'boundary' and x.type is 'administrative')
+  (
+    (x.class is 'place'    and (x.type is 'city' or x.type is 'village')) or
+    (x.class is 'boundary' and x.type is 'administrative')
+  ) and (a = x.address)? and (a.city? or a.county? or a.village?)

 module.exports = (state=initialState, action={}) ->


添加了注释

添加了注释

--- app/assets/javascripts/bank_train/modal.coffee
+++ app/assets/javascripts/bank_train/modal.coffee
@@ -9,11 +9,12 @@
   set_body: (body)->
     @$body_ele.html(body)

-  #移除样式
+  #移除样式 和提示信息
   set_remove_css:(class_css)->
     $(class_css).removeClass("has-error")
     $(class_css+" span").remove();

+  #添加样式 和提示信息
   set_add_css: (css,msg_val)->
     @set_remove_css(css)
     $(css).addClass("has-error")


Set tooltips for new added labels

Set tooltips for new added labels

--- app/assets/javascripts/labels_select.js.coffee
+++ app/assets/javascripts/labels_select.js.coffee
@@ -34,7 +34,7 @@
         labelHTMLTemplate = _.template(
             '<% _.each(labels, function(label){ %>
             <a href="<%= ["",issueURLSplit[1], issueURLSplit[2],""].join("/") %>issues?label_name=<%= label.title %>">
-            <span class="label color-label" style="background-color: <%= label.color %>;">
+            <span class="label has-tooltip color-label" title="<%= label.description %>" style="background-color: <%= label.color %>;">
             <%= label.title %>

@@ -165,6 +165,8 @@
             .html(template)
           $sidebarCollapsedValue.text(labelCount)

+          $('.has-tooltip', $value).tooltip(container: 'body')
+
           $value
             .find('a')
             .each((i) ->


Hotfix: Jump to top of result list on navigation link click, for #594

--- app/assets/javascripts/search/presenter.coffee
+++ app/assets/javascripts/search/presenter.coffee
@@ -161,6 +161,7 @@
     @model.save changes, true
     @sendMainSearch()
     @stopEvent event
+    window.scrollTo(0, 0)

   handleFilterChange: (event) =>
     val = $(event.target).val()


BUG 2437 - The taskboard edit lightbox have the button (CREATE in place of SAVE)

--- app/coffee/modules/taskboard/lightboxes.coffee
+++ app/coffee/modules/taskboard/lightboxes.coffee
@@ -40,7 +40,7 @@
             $scope.isNew = true

             # Update texts for creation
-            $el.find(".button-green span").html("Create") #TODO: i18n
+            $el.find(".button-green").html("Create") #TODO: i18n
             $el.find(".title").html("New task  ") #TODO: i18n
             $el.find(".tag-input").val("")

@@ -51,7 +51,7 @@
             $scope.isNew = false

             # Update texts for edition
-            $el.find(".button-green span").html("Save") #TODO: i18n
+            $el.find(".button-green").html("Save") #TODO: i18n
             $el.find(".title").html("Edit task  ") #TODO: i18n
             $el.find(".tag-input").val("")


remove invalid test from request spec

remove invalid test from request spec

--- modules/request/test/spec.coffee
+++ modules/request/test/spec.coffee
@@ -145,18 +145,6 @@
       hx.request 'test.file', cb

       requests[0].respond(404)
-
-    # Is this a valid test?
-    # it 'should deal with a response having content but no status', (done) ->
-    #   cb = (error, result, source, index) ->
-    #     should.not.exist(error)
-    #     should.exist(result)
-    #     should.not.exist(result.status)
-    #     result.responseText.should.equal('Some text')
-    #     done()
-
-    #   hx.request 'test.file', cb
-    #   requests[0].respond(undefined, undefined, 'Some text')

     it 'should format the response data correctly when one is passed in', ->


ComputeProvider: updateGroupResourceUsage: send notification on limit exceeded

--- workers/social/lib/social/models/computeproviders/computeprovider.coffee
+++ workers/social/lib/social/models/computeproviders/computeprovider.coffee
@@ -451,6 +451,7 @@

   @updateGroupResourceUsage = (options, callback) ->

+    { notifyAdmins } = require '../notify'
     { group, instanceCount, instanceOnly, change, details } = options

     return callback null  if group.slug is 'koding'
@@ -466,8 +467,13 @@
         then "Stack: #{template.title} - #{template._id}"
         else ''

-        KodingLogger.warn group, \
-          "#{user} failed to create #{provider}#{item} due to plan limitations. #{template}"
+        message = "
+          #{user} failed to create #{provider}#{item} due to
+          plan limitations. #{template}
+        "
+
+        KodingLogger.warn group, message
+        notifyAdmins group, 'MemberWarning', message

       callback err


Fixes checking that the code exists

Fixes checking that the code exists

--- src/memes.coffee
+++ src/memes.coffee
@@ -44,10 +44,8 @@
   "chosen": "You Were the Chosen One!",
   "10guy": "[10] Guy"

-getCode = (meme, meemes) ->
-  for code, meme of memes
-    return code if meme.toLowerCase() is meme.toLowerCase()
-
+checkCode = (meme, memes) ->
+  return meme of memes

 module.exports = (robot) ->

@@ -58,11 +56,11 @@
       msg.send "#{code} - #{meme}"

   robot.respond /meme me (\w+) (\"[^"]+\") (\"[^"]+\")/i, (msg) ->
-    meme = if msg.match[1] isnt undefined then getCode(msg.match[0], memes) else 'doge'
-    top = msg.match[2].replace(/"/g, '').replace(/\s+/g, '-').toLowerCase()
-    bottom = msg.match[3].replace(/"/g, '').replace(/\s+/g, '-').toLowerCase()
+
+    meme   = if msg.match[1] isnt undefined && checkCode(msg.match[1], memes) else 'doge'
+    top    = msg.match[2].replace(/"/g, '').trim().replace(/\s+/g, '-')
+    bottom = msg.match[3].replace(/"/g, '').trim().replace(/\s+/g, '-')

     msg.send "http://memegen.link/#{meme}/#{top}/#{bottom}.jpg"
-    #meme = if msg.match[0] isnt undefined then getCode(msg.match[0], memes) else 'doge'
-    #top = "\"#{msg.match[1]?.trim()}\""
-    #bottom = "\"#{msg.match[2]?.trim()}\""
+
+


Fix glaringly obvious bug

Fix glaringly obvious bug

--- plugins/github-issues/index.coffee
+++ plugins/github-issues/index.coffee
@@ -17,7 +17,7 @@

     # Authenticate the request
     if config.oauthToken
-      req.set("Authorization", "token #{config.oauth_token}")
+      req.set("Authorization", "token #{config.oauthToken}")
     else
       req.auth(config.username, config.password)


Neater numberToken function.

Neater numberToken function.

--- src/lexer.coffee
+++ src/lexer.coffee
@@ -133,14 +133,11 @@
   numberToken: ->
     return 0 unless match = NUMBER.exec @chunk
     number = match[0]
-    numlen = number.length
-    # Now, since it is not JavaScript-descended, if it is binary, we will have
-    # to massage it into something similar and then hand it over. May the user
-    # forgive us.
-    is_binary = /0b([01]+)/.exec number
-    number = (parseInt is_binary[1], 2).toString() if is_binary
+    lexedLength = number.length
+    if is_binary = /0b([01]+)/.exec number
+        number = (parseInt is_binary[1], 2).toString()
     @token 'NUMBER', number
-    numlen
+    lexedLength

   # Matches strings, including multi-line strings. Ensures that quotation marks
   # are balanced within the string's contents, and within nested interpolations.


add another constructor of Polyline

add another constructor of Polyline

allow pass x1, y1, x2, y2 ...

--- src/shapes/Polyline.coffee
+++ src/shapes/Polyline.coffee
@@ -5,6 +5,12 @@
 	constructor: (@vertices = []) ->
 		super()
 		@type = 'Polyline'
+
+		if arguments.length > 1
+			vertices = []
+			for i in [0 ... arguments.length / 2]
+				vertices.push new Bu.Point arguments[i * 2], arguments[i * 2 + 1]
+			@vertices = vertices

 		@lines = []
 		@length = 0


storage: further polish

storage: further polish

--- src/smc-hub/storage.coffee
+++ src/smc-hub/storage.coffee
@@ -548,8 +548,11 @@

 db = require('smc-hub/rethink').rethinkdb(hosts:['db0'],pool:1); s = require('smc-hub/storage');

-# make sure everything from 2 years ago (or older) has a backup
-s.save_BUP_age(database:db, min_age_m:2 * 60*24*365, age_m:1e8, time_since_last_backup_m:1e8, cb:(e)->console.log("DONE",e))
+# make sure everything not touched in 2 years has a backup as recorded in the database
+s.save_BUP_age(database:db, threads:2, min_age_m:2 * 60*24*365, age_m:1e8, time_since_last_backup_m:1e8, cb:(e)->console.log("DONE",e))
+
+# make sure everything not touched in 2 years has a backup on the local /bups disk if it exists in /projects
+s.save_BUP_age(threads:2, local:true, database:db, min_age_m:2 * 60*24*365, age_m:1e8, time_since_last_backup_m:1e8, cb:(e)->console.log("DONE",e))

 # make sure everything modified in the last week has at least one backup made within
 # the last day (if it was backed up after last edited, it won't be backed up again)
@@ -563,6 +566,7 @@
         threads   : 1
         time_since_last_backup_m : undefined  # if given, only backup projects for which it has been at least this long since they were backed up
         local     : false      # if true, backs up *every* project on this host for which no backup exists in the /bups directory.
+        limit     : undefined  # backup at most this many
         cb        : required
     if process.env.USER != 'root'
         opts.cb("must be root")
@@ -573,7 +577,7 @@
     dbg("age_m=#{opts.age_m}; min_age_m=#{opts.min_age_m}; time_since_last_backup_m=#{opts.time_since_last_backup_m}")
     async.series([
         (cb) ->
-            if opts.time_since_last_backup_m?
+            if opts.time_since_last_backup_m? or opts.local?
                 opts.database.recent_projects
                     age_m     : opts.age_m
                     min_age_m : opts.min_age_m
@@ -586,10 +590,12 @@
                             projects = []
                             cutoff = misc.minutes_ago(opts.time_since_last_backup_m)
                             for x in v
+                                if opts.limit? and projects.length >= opts.limit
+                                    break
                                 if x.storage?.host != hostname
                                     # only consider projects on this VM
                                     continue
-                                if opts.local and not fs.existsSync("/bups/#{x.project_id}")
+                                if opts.local and not fs.existsSync("/bups/#{x.project_id}") and fs.existsSync("/projects/#{x.project_id}")
                                     projects.push(x.project_id)
                                     continue
                                 if x.last_backup? and x.last_edited? and x.last_backup >= x.last_edited
@@ -607,6 +613,8 @@
                         projects = v
                         cb(err)
         (cb) ->
+            if opts.limit?
+                projects = projects.slice(0, opts.limit)
             dbg("making backup of #{projects.length} projects")
             save_BUP_many
                 database : opts.database


remove disconnect from project, now that it is factored out

--- src/smc-util/client.coffee
+++ src/smc-util/client.coffee
@@ -833,17 +833,6 @@
             error_event : true
             cb          : opts.cb

-    disconnect_from_project: (opts) =>
-        opts = defaults opts,
-            project_id : required
-            cb         : undefined
-        @call
-            allow_post  : true
-            message     : message.disconnect_from_project(project_id: opts.project_id)
-            error_event : true
-            cb          : opts.cb
-
-
     get_user_auth_token: (opts) =>
         opts = defaults opts,
             account_id : required


add a comment about share link for later

--- src/smc-webapp/r_help.cjsx
+++ src/smc-webapp/r_help.cjsx
@@ -139,6 +139,9 @@
             </span>

     render: ->
+        # TODO: it would be nice to change the share link to
+        #     https://cocalc.com/share/7561f68d-3d97-4530-b97e-68af2fb4ed13/stats.html
+        # but this seems stale for some reason sometimes.
         <Col sm={12} md={6}>
             <h3>
                 <Icon name='dashboard' /> Statistics


change the message slightly for when a project is not on a members only server

--- src/smc-webapp/r_misc.cjsx
+++ src/smc-webapp/r_misc.cjsx
@@ -869,9 +869,10 @@
     <Alert bsStyle='warning' style={marginTop:'10px'}>

Warning: this project is running on a free server

-            Projects running on free servers compete for resources with a large number of other free users.
-            They are often much slower than projects on members-only servers.
-            <Space/>
+            Projects running on free servers compete for resources with a large number of other free projects.
+            The free servers are randomly rebooted frequently,
+            and are often much more heavily loaded than members-only servers.
+

             {suggestion}

     </Alert>


fix id accessor in domain licence join page

--- services/web/app/coffee/Features/Subscription/DomainLicenceController.coffee
+++ services/web/app/coffee/Features/Subscription/DomainLicenceController.coffee
@@ -18,9 +18,9 @@

 		jobs =
 			partOfGroup: (cb)->
-				SubscriptionGroupHandler.isUserPartOfGroup user.id, licence.group_subscription_id, cb
+				SubscriptionGroupHandler.isUserPartOfGroup user._id, licence.group_subscription_id, cb
 			subscription: (cb)->
-				SubscriptionLocator.getUsersSubscription user.id, cb
+				SubscriptionLocator.getUsersSubscription user._id, cb

 		async.series jobs, (err, results)->
 			{ partOfGroup, subscription } = results


webapp/wizard: search, cat1 sorting, highlighting, etc.

--- src/smc-webapp/wizard.cjsx
+++ src/smc-webapp/wizard.cjsx
@@ -53,9 +53,9 @@
 class WizardStore extends Store

 class WizardActions extends Actions
-    get: (key) =>
+    get: (key) ->
         @redux.getStore(@name).get(key)
-    set: (update) =>
+    set: (update) ->
         @setState(update)
     show: (lang='sage') =>
         @set
@@ -63,19 +63,25 @@
             lang: lang
     reset: ->
         @set
-            cat0: null
-            cat1: null
-            cat2: null
-            catlist0 : []
-            catlist1 : []
-            catlist2 : []
-    hide: () ->
+            cat0       : null
+            cat1       : null
+            cat2       : null
+            catlist0   : []
+            catlist1   : []
+            catlist2   : []
+            code       : ''
+            descr      : ''
+            hits       : []
+            search_str : null
+            search_sel : null
+            cat1_top   : ["Intro", "Tutorial", "Help"]
+    hide: =>
         @set(show: false)
-    init: (lang='sage') =>
+    init: (lang='sage') ->
         @reset()
         @show(lang=lang)
         @load_data()
-    init_data: (data) =>
+    init_data: (data) ->
         @set(data: data)
         nav_entries = []
         for key in _.keys(data)
@@ -88,7 +94,7 @@
                 nav_entries.push(entry)
         @set(nav_entries: nav_entries)
         @select_lang(@get('lang'))
-    insert: (cb) =>
+    insert: (cb) ->
         # this is the essential task of the wizard:
         # call the callback with the selected code snippet
         cb
@@ -96,7 +102,7 @@
             lang  : @get('lang')
             title : 'title'
             descr : 'description'
-    load_data: () =>
+    load_data: () ->
         if not DATA?
             require.ensure [], =>
                 # DATA is a global variable!
@@ -104,20 +110,70 @@
                 @init_data(DATA)
         else
             @init_data(DATA)
-    select_lang: (lang) =>
+    data_lang: () ->
+        @get('data').get(@get('lang'))
+    select_lang: (lang) ->
         @reset()
+        catlist0 = @get('data').get(lang).keySeq().toArray()
         @set
             lang     : lang
-            catlist0 : @get('data').get(lang).keySeq().toArray()
-    set_selected_category: (level, selected, idx) =>
-        console.log 'category_selection', level, selected, idx
-        lang = @get('data').get(@get('lang'))
+            catlist0 : catlist0
+        if catlist0.length == 1
+            @set_selected_category(0, catlist0[0], 0)
+    search: (search_str) ->
+        @reset()
+        if not search_str? or search_str.length == 0
+            @select_lang(@get('lang'))
+            return
+        @set(search_str : search_str)
+        str = search_str.toLowerCase()
+        hits = []
+        data_lang = @data_lang()
+        EnoughResultsException = {}
+        try
+            data_lang.forEach (data1, lvl1) ->
+                data1.forEach (data2, lvl2) ->
+                    data2.forEach (entry, lvl3) ->
+                        title = entry.get(0)
+                        descr = entry.getIn([1, 1])
+                        inTitle = title.toLowerCase().indexOf(str)
+                        inDescr = descr.toLowerCase().indexOf(str)
+                        if inTitle != -1 or inDescr != -1
+                            hits.push([lvl1, lvl2, lvl3, title, descr, inDescr])
+                            if hits.length >= 30
+                                throw EnoughResultsException
+        catch ex
+            if ex isnt EnoughResultsException
+                throw ex
+        @set
+            hits: hits
+
+    search_selected: (lvl1, lvl2, lvl3, idx) ->
+        doc = @data_lang().getIn([lvl1, lvl2, lvl3])
+        @show_doc(doc)
+        @set(search_sel : idx)
+
+    show_doc: (doc) ->
+        @set
+            code  : doc.getIn([1, 0])
+            descr : doc.getIn([1, 1])
+
+    cat1_sort: (a, b) =>
+        # ordering operator, such that some entries are in front
+        top = @get('cat1_top')
+        ord = (el) ->
+            i = top.reverse().indexOf(el)
+            return 1 - i
+        return ord(a) - ord(b) or a > b
+
+    set_selected_category: (level, selected, idx) ->
+        lang = @data_lang()
         switch level
             when 0, 1
                 @set(code: '', descr: '', cat2 : null)
         switch level
             when 0
-                catlist1 = lang.get(selected).keySeq().toArray()
+                catlist1 = lang.get(selected).keySeq().toArray().sort(@cat1_sort)
                 @set
                     cat0     : selected
                     cat1     : null
@@ -141,20 +197,21 @@
                 doc  = lang.getIn([cat0, cat1, idx])
                 @set
                     cat2  : idx
-                    code  : doc.getIn([1, 0])
-                    descr : doc.getIn([1, 1])
+                @show_doc(doc)

 WizardHeader = rclass
     displayName : 'WizardHeader'
     propTypes:
         actions     : rtypes.object
         nav_entries : rtypes.array
+        search_str  : rtypes.string
         lang        : rtypes.string.isRequired
     langSelect: (key) ->
-        #console.log 'language selected', key
         @props.actions.select_lang(key)
-    search: () ->
-        console.log 'search:', @refs.search.getValue()
+    search: (evt) ->
+        evt.preventDefault()
+        evt.stopPropagation()
+        @props.actions.search(evt.target.value)
     render_nav : ->
         entries = @props.nav_entries
         if entries?
@@ -177,6 +234,7 @@
                        type='text'
                        className='smc-wizard-search'
                        placeholder='Search'
+                       value={@props.search_str}
                        onChange={@search}  />
             </Col>
         </Row>
@@ -184,42 +242,92 @@
 WizardBody = rclass
     displayName : 'WizardBody'
     propTypes:
-        actions  : rtypes.object
-        data     : rtypes.object
-        lang     : rtypes.string.isRequired
-        code     : rtypes.string
-        descr    : rtypes.string
-        cat0     : rtypes.string
-        cat1     : rtypes.string
-        cat2     : rtypes.number
-        catlist0 : rtypes.arrayOf(rtypes.string)
-        catlist1 : rtypes.arrayOf(rtypes.string)
-        catlist2 : rtypes.arrayOf(rtypes.string)
+        actions    : rtypes.object
+        data       : rtypes.object
+        lang       : rtypes.string.isRequired
+        code       : rtypes.string
+        descr      : rtypes.string
+        cat0       : rtypes.string
+        cat1       : rtypes.string
+        cat2       : rtypes.number
+        catlist0   : rtypes.arrayOf(rtypes.string)
+        catlist1   : rtypes.arrayOf(rtypes.string)
+        catlist2   : rtypes.arrayOf(rtypes.string)
+        search_str : rtypes.string
+        search_sel : rtypes.number
+        hits       : rtypes.arrayOf(rtypes.array)
+
+    componentWillMount: ->
+        @scrollTo0 = _.debounce (() -> $(@refs.list_0).find('.active').scrollintoview()), 50
+        @scrollTo1 = _.debounce (() -> $(@refs.list_1).find('.active').scrollintoview()), 50
+        @scrollTo2 = _.debounce (() -> $(@refs.list_2).find('.active').scrollintoview()), 50
+
+    componentDidUpdate: (props, state) ->
+        @scrollTo0()
+        @scrollTo1()
+        @scrollTo2()
+
     category_selection: (level, selected, idx) ->
         @props.actions.set_selected_category(level, selected, idx)
+
     category_list: (level) ->
         cat  = @props["cat#{level}"]
         list = @props["catlist#{level}"]
         if not list?
             list = []
         # don't use ListGroup & ListGroupItem with onClick, because then there are div/buttons (instead of ul/li) and layout is f'up
-

+

             {list.map (name, idx) =>
-                click  = @category_selection.bind(this, level, name, idx)
+                click  = @category_selection.bind(@, level, name, idx)
                 # level 0 and 1 by name, level 2 by index
                 comp   = if level == 2 then idx else name
                 active = if comp == cat then 'active' else ''
-
{name}

+
{name}

             }

+
+    search_result_selection: (lvl1, lvl2, lvl3, idx) ->
+        @props.actions.search_selected(lvl1, lvl2, lvl3, idx)
+
+    render_search_results : ->
+        ss = @props.search_str
+

+            {@props.hits.map (hit, idx) =>
+                [lvl1, lvl2, lvl3, title, descr, inDescr] = hit
+                click = @search_result_selection.bind(@, lvl1, lvl2, lvl3, idx)
+                title_hl = title.replace(new RegExp(ss, "gi"), "#{ss}")
+                if inDescr != -1
+                    i = Math.max(0, inDescr-30)
+                    j = Math.min(descr.length, inDescr+30+ss.length)
+                    t = descr[inDescr...inDescr+ss.length]
+                    snippet = descr[i..j].replace(new RegExp(ss, "gi"), "#{t}")
+                    if i > 0
+                        snippet = '...' + snippet
+                    if j < descr.length
+                        snippet = snippet + '...'
+                active = if @props.search_sel == idx then 'active' else ''
+

+                    {lvl1} → {lvl2} →
+                    {' '}{ if snippet?.length > 0}
+

+            }
+

+    render_top : ->
+        searching = @props.search_str?.length > 0
+        if searching
+            <Row>
+                <Col sm={12}>{@render_search_results()}</Col>
+            </Row>
+        else
+            <Row>
+                <Col sm={3}>{@category_list(0)}</Col>
+                <Col sm={3}>{@category_list(1)}</Col>
+                <Col sm={6}>{@category_list(2)}</Col>
+            </Row>
     render : ->
         if @props.data?
             <Modal.Body className='modal-body'>
-                <Row>
-                    <Col sm={3}>{@category_list(0)}</Col>
-                    <Col sm={3}>{@category_list(1)}</Col>
-                    <Col sm={6}>{@category_list(2)}</Col>
-                </Row>
+                {@render_top()}
                 <Row>
                     <Col sm={6}>

{@props.code}

@@ -251,6 +359,9 @@
             cat0        : rtypes.string
             cat1        : rtypes.string
             cat2        : rtypes.number
+            search_str  : rtypes.string
+            search_sel  : rtypes.number
+            hits        : rtypes.arrayOf(rtypes.array)

     propTypes :
         cb      : rtypes.func
@@ -265,7 +376,10 @@
     render : ->
         <Modal show={@props.show} onHide={@close} bsSize="large" className="smc-wizard">
             <Modal.Header closeButton className='modal-header'>
-               <WizardHeader actions={@props.actions} lang={@props.lang} nav_entries={@props.nav_entries} />
+               <WizardHeader actions     = {@props.actions}
+                             lang        = {@props.lang}
+                             search_str  = {@props.search_str}
+                             nav_entries = {@props.nav_entries} />
             </Modal.Header>

             <WizardBody actions    = {@props.actions}
@@ -278,6 +392,9 @@
                         catlist0   = {@props.catlist0}
                         catlist1   = {@props.catlist1}
                         catlist2   = {@props.catlist2}
+                        search_str = {@props.search_str}
+                        search_sel = {@props.search_sel}
+                        hits       = {@props.hits}
                         data       = {@props.data} />

             <Modal.Footer>


initial rewrite route testing

initial rewrite route testing

--- vendor/assets/javascripts/chai-backbone.js.coffee
+++ vendor/assets/javascripts/chai-backbone.js.coffee
@@ -56,58 +56,60 @@
     action.before?(this) for action in definedActions
     val() # execute the 'when'
     action.after?(this) for action in definedActions
+
+  # Verify if a url fragment is routed to a certain method on the router
+  # Options:
+  # - you can consider multiple routers to test routing priorities
+  # - you can indicate expected arguments to test url extractions
+  #
+  # Examples:
+  #
+  #   class MyRouter extends Backbone.Router
+  #     routes:
+  #       "home/:page/:other": "homeAction"
+  #
+  #   myRouter = new MyRouter
+  #
+  #   "home/stuff/thing".should.route_to(myRouter, "homeAction")
+  #   "home/stuff/thing".should.route_to(myRouter, "homeAction", arguments: ["stuff", "thing"])
+  #   "home/stuff/thing".should.route_to(myRouter, "homeAction", consider: [otherRouterWithPossiblyConflictingRoute])
+  #
+  chai.Assertion.addMethod 'route_to', (router, methodName, options = {}) ->
+    # move possible active Backbone history out of the way temporary
+    current_history = Backbone.history
+
+    # reset history to clear active routes
+    Backbone.history = new Backbone.History
+
+    spy = sinon.spy router, methodName # spy on our expected method call
+    router._bindRoutes() # inject router routes into our history
+    if options.considering? # if multiple routers are provided load their routes aswell
+      consideredRouter._bindRoutes() for consideredRouter in options.considering
+
+    # manually set the root option to prevent calling Backbone.history.start() which is global
+    Backbone.history.options =
+      root: '/'
+
+    route = flag(this, 'object')
+    # fire our route to test
+    Backbone.history.loadUrl route
+
+    # set back our history. The spy should have our collected info now
+    Backbone.history = current_history
+    # restore the router method
+    router[methodName].restore()
+
+    # now assert if everything went according to spec
+    @assert spy.calledOnce,
+      "expected '#{route}' to route to #{methodName}",
+      "expected '#{route}' not to route to #{methodName}"
+
+    # verify arguments if they were provided
+    if options.arguments?
+      @assert spy.calledWith(options.arguments...),
+        "expected '#{methodName}' to be called with #{inspect options.arguments}, but was called with #{inspect spy.args[0]} instead",
+        "expected '#{methodName}' not to be called with #{inspect options.arguments}, but was"
+
 )

-  ## Verify if a url fragment is routed to a certain method on the router
-  ## Options:
-  ## - you can consider multiple routers to test routing priorities
-  ## - you can indicate expected arguments to test url extractions
-  ##
-  ## Examples:
-  ##
-  ##   class MyRouter extends Backbone.Router
-  ##     routes:
-  ##       "home/:page/:other": "homeAction"
-  ##
-  ##   myRouter = new MyRouter
-  ##
-  ##   "home/stuff/thing".should.route_to(myRouter, "homeAction")
-  ##   "home/stuff/thing".should.route_to(myRouter, "homeAction", arguments: ["stuff", "thing"])
-  ##   "home/stuff/thing".should.route_to(myRouter, "homeAction", consider: [otherRouterWithPossiblyConflictingRoute])
-  ##
-  #route_to: (router, methodName, options = {}) ->
-    ## move possible active Backbone history out of the way temporary
-    #current_history = Backbone.history

-    ## reset history to clear active routes
-    #Backbone.history = new Backbone.History
-
-    #spy = sinon.spy router, methodName # spy on our expected method call
-    #router._bindRoutes() # inject router routes into our history
-    #if options.considering? # if multiple routers are provided load their routes aswell
-      #consideredRouter._bindRoutes() for consideredRouter in options.considering
-
-    ## manually set the root option to prevent calling Backbone.history.start() which is global
-    #Backbone.history.options =
-      #root: '/'
-
-    ## fire our route to test
-    #Backbone.history.loadUrl @obj
-
-    ## set back our history. The spy should have our collected info now
-    #Backbone.history = current_history
-    ## restore the router method
-    #router[methodName].restore()
-
-    ## now assert if everything went according to spec
-    #@assert spy.calledOnce,
-      #"expected '#{@obj}' to route to #{methodName}",
-      #"expected '#{@obj}' not to route to #{methodName}"
-
-    ## verify arguments if they were provided
-    #if options.arguments?
-      #@assert spy.calledWith(options.arguments...),
-        #"expected '#{methodName}' to be called with #{chai.inspect options.arguments}, but was called with #{chai.inspect spy.args[0]} instead",
-        #"expected '#{methodName}' not to be called with #{chai.inspect options.arguments}, but was"
-
-


ignore lots of random node types that i learned about from fuzz testing

--- src/nodes.coffee
+++ src/nodes.coffee
@@ -50,8 +50,10 @@
       when 'listViews'
         view = factory.build tag
         @listViews[view.fullName] = view
-      when 'searchLayouts'
-        # TODO: something
+      # TODO: something
+      when 'searchLayouts', 'namedFilters', 'validationRules', 'webLinks', 'enableEnhancedLookup', 'startsWith', 'fieldSets'
+        ''
+      when 'recordTypeTrackFeedHistory', 'recordTypeTrackHistory', 'recordTypes', 'customSettingsType', 'customSettingsVisibility'
         ''

     # regular members
@@ -59,6 +61,8 @@
         @label = value
       when 'pluralLabel'
         @pluralLabel = value
+      when 'description'
+        @description = value
       when 'enableActivities'
         @enableActivities = value is 'true'
       when 'enableFeeds'
@@ -87,22 +91,41 @@
         @trackHistory = value is 'true'

     # common
+      when 'scale'
+        @scale = value
+      when 'precision'
+        @precision = value
+      when 'description'
+        @description = value
+      when 'inlineHelpText'
+        @inlineHelpText = value
       when 'externalId'
         @externalId = value is 'true'
       when 'required'
         @required = value is 'true'
       when 'unique'
         @unique = value is 'true'
+      when 'formula'
+        @formula = value is 'true'
+      when 'formulaTreatBlanksAs'
+        @formulaTreatBlanksAs = value is 'true'

     # others
       when 'caseSensitive'
         @caseSensitive = value is 'true'
       when 'defaultValue'
         @defaultValue = value
+      when 'trackFeedHistory'
+        @trackFeedHistory = value is 'true'
       when 'picklist'
         @picklist = new Picklist tag
       when 'visibleLines'
         @visibleLines = parseInt value, 10
+        # TODO: something
+      when 'deleteConstraint', 'referenceTo', 'relationshipLabel', 'relationshipName', 'relationshipOrder', 'writeRequiresMasterRead'
+          ''
+      when 'summarizedField', 'summaryFilterItems', 'summaryForeignKey', 'summaryOperation', 'displayFormat'
+          ''
       else throw new Error "unknown field #{field} on field object"

 class DeploymentStatus
@@ -152,6 +175,9 @@
         @label = value
       when 'filterScope'
         @filterScope = value
+      # TODO: something
+      when 'columns', 'filters', 'sharedTo', 'booleanFilter'
+        ''
       else throw new Error "unknown field #{field} on list view object"

 module.exports = factory


Only double qoute strings now

Only double qoute strings now

--- src/nodes.coffee
+++ src/nodes.coffee
@@ -614,6 +614,10 @@
       "\"#{@value}\""
     else
       @value
+
+    if underscore.isString(code) and [m, stringContent] = code.match(/^'(.*)'$/) or no
+      code = "\"#{stringContent.replace(/\\'/g, "'").replace(/"/g, '\\"')}\""
+
     answer = if @isStatement() then "#{@tab}#{code};" else code
     [@makeCode answer]


smarter exports on associative arrays

smarter exports on associative arrays

--- src/nodes.coffee
+++ src/nodes.coffee
@@ -55,6 +55,9 @@
 # being requested by the surrounding function), information about the current
 # scope, and indentation level.
 exports.Base = class Base
+
+  # hack for .match
+  @isNodeClass: yes

   compile: (o, lvl) ->
     fragmentsToText @compileToFragments o, lvl
@@ -221,11 +224,11 @@
       return no if e == "match failed"
       throw e

-  # a pattern *must* be a node at outermost
+  # a pattern *must* be a node or node class at outermost
   match: (parts) ->
-    for pattern, i in parts when pattern instanceof Base
+    for pattern, i in parts when isMatchPattern pattern
       if captures = @isa pattern
-        for fn in parts[i+1..] when fn not instanceof Base
+        for fn in parts[i+1..] when not isMatchPattern fn
           return fn(captures)
         return no  # pattern followed by no functions

@@ -238,6 +241,8 @@
   matchedAttributes: []

 # Pattern matching utilities
+isMatchPattern = (p) -> p instanceof Base or p.isNodeClass
+
 exports.matchNode = matchNode = (pattern, node, match) ->
   if pattern instanceof MatchCapture
     match[pattern.name] = node
@@ -257,7 +262,7 @@
     for [p, e] in underscore.zip(pattern, node)
       matchNode(p, e, match)

-  else if pattern instanceof Base.constructor and node instanceof pattern
+  else if underscore.isFunction(pattern) and node instanceof pattern
     return yes

   # TODO: `Several` for mapping pattern matching over array
@@ -364,6 +369,10 @@
       else if top
         node.front = true
         fragments = node.compileToFragments o
+        if @exportAll then node.match [  # add assignment in here
+          Class, Code, () =>
+            fragments.unshift @makeCode "export "
+        ]
         unless node.isStatement o
           fragments.unshift @makeCode "#{@tab}"
           fragments.push @makeCode ";"
@@ -434,9 +443,9 @@
           bodyExprs.push bodyExprs.pop().match [
             new Value(new Obj(M("members"))), ({members}) =>
               @exportAll = yes
-              for renaming in members when renaming
+              for renaming in members
                 continue unless renaming instanceof Assign            # ignore { A }, because should `export A` on def
-                continue unless renaming.variable.isa renaming.value  # ignore { A: A }, as above
+                continue if renaming.variable.isa renaming.value      # ignore { A: A }, as above
                 delete renaming.context                               # use = instead of :
                 renaming


Convert space to tab in numbo.coffee

Convert space to tab in numbo.coffee

--- src/numbo.coffee
+++ src/numbo.coffee
@@ -21,66 +21,57 @@

 class Numbo

-  tools =
-    parse99: parse99
-    trimWhitespace: trimWhitespace
-    parseCents: parseCents
-    speakByDigit: speakByDigit
-    check: check
-    normalize: normalize
-    adjustDecimal: adjustDecimal
-    splitNum: splitNum
-    splitInt: splitInt
-  tools:
-    trimWhitespace: tools.trimWhitespace
-    parse99: tools.parse99
-    parseCent: tools.parseCents #todo to be depreciated
-    parseCents: tools.parseCents #todo to be depreciated
-    speakByDigit: tools.speakByDigit
-    check: tools.check
-    validate: tools.check
-    normalize: tools.normalize
-    normalise: tools.normalize
-    adjustDecimal: tools.adjustDecimal
-    splitNum: tools.splitNum
-    splitInt: tools.splitInt
+	tools =
+		parse99: parse99
+		trimWhitespace: trimWhitespace
+		parseCents: parseCents
+		speakByDigit: speakByDigit
+		check: check
+		normalize: normalize
+		adjustDecimal: adjustDecimal
+		splitNum: splitNum
+		splitInt: splitInt
+	tools:
+		trimWhitespace: tools.trimWhitespace
+		parse99: tools.parse99
+		parseCent: tools.parseCents #todo to be depreciated
+		parseCents: tools.parseCents #todo to be depreciated
+		speakByDigit: tools.speakByDigit
+		check: tools.check
+		validate: tools.check
+		normalize: tools.normalize
+		normalise: tools.normalize
+		adjustDecimal: tools.adjustDecimal
+		splitNum: tools.splitNum
+		splitInt: tools.splitInt

-  convert_enUS = enUS
-  enUS: convert_enUS
+	convert_enUS = enUS
+	enUS: convert_enUS

-  #   #####
-  #  #     #  ####  #    # #    # ###### #####  #####
-  #  #       #    # ##   # #    # #      #    #   #
-  #  #       #    # # #  # #    # #####  #    #   #
-  #  #       #    # #  # # #    # #      #####    #
-  #  #     # #    # #   ##  #  #  #      #   #    #
-  #   #####   ####  #    #   ##   ###### #    #   #
+	convert: (input, options...) ->
+		# `input` can be string or number
+		# `options` can be language ('zhTW') and style ('number', 'amount' or 'cheque')
+		if options.length is 0 # not provide any option
+			convert_enUS(input)
+		else
+			zeroCent = false #todo: temp setup
+			parsedOptions = parseOptions(options)
+			otherOptions = parsedOptions.otherOptions

-
-  convert: (input, options...) ->
-    # `input` can be string or number
-    # `options` can be language ('zhTW') and style ('number', 'amount' or 'cheque')
-    if options.length is 0 # not provide any option
-      convert_enUS(input)
-    else
-      zeroCent = false #todo: temp setup
-      parsedOptions = parseOptions(options)
-      otherOptions = parsedOptions.otherOptions
-
-      # Skip and return null if any error occurs
-      if parsedOptions.error is true then null
-      # Currently, otherOptions is not allowed yet.
-      else if otherOptions.length > 0 and otherOptions.toString() isnt 'zeroCent'
-        #todo: the condition about 'zeroCent' should be done in parseOption()
-        console.log 'Error: Invalid option. Possibly more than one template is selected. Or, some option(s) are parsed into [otherOptions], but it is not allowed in current version yet.'
-        null
-      else
-        # execute convert() according to options provided
-        if otherOptions.toString() is 'zeroCent'
-          #todo: should be done in parseOption()
-          zeroCent = true
-        @[parsedOptions.plugin](input, otherOptions.template, zeroCent) #todo: temp setup for zeroCent
+			# Skip and return null if any error occurs
+			if parsedOptions.error is true then null
+			# Currently, otherOptions is not allowed yet.
+			else if otherOptions.length > 0 and otherOptions.toString() isnt 'zeroCent'
+				#todo: the condition about 'zeroCent' should be done in parseOption()
+				console.log 'Error: Invalid option. Possibly more than one template is selected. Or, some option(s) are parsed into [otherOptions], but it is not allowed in current version yet.'
+				null
+			else
+				# execute convert() according to options provided
+				if otherOptions.toString() is 'zeroCent'
+					#todo: should be done in parseOption()
+					zeroCent = true
+				@[parsedOptions.plugin](input, otherOptions.template, zeroCent) #todo: temp setup for zeroCent

 numbo = new Numbo()


+ add configuation setting stores. + add order stores command to show all stores setting.

--- src/order.coffee
+++ src/order.coffee
@@ -5,14 +5,15 @@
 #
 #
 # Configuration:
-#
+#	HUBOT_ORDER_MENU_STORE_INFO=[name1,phone1,Http:\\link1;name2,phone2,Http:\\link2]
 #
 # Commands:
-#   hubot order <food> <note> $<money> - make an order
-#   hubot order <food> <note> $<money> for @someone - make an order for someone
-#   hubot order my - show your order
+#	hubot order stores - show all store info.
+#   hubot order <food> <note> $<money> - make an order.
+#   hubot order <food> <note> $<money> for @someone - make an order for someone.
+#   hubot order my - show your order.
 #   hubot order all - show all orders and calculate total money needed.
-#	hubot order reset - reset all orders
+#	hubot order reset - reset all orders.
 #
 # Author:
 #   Ghost.Yang
@@ -28,7 +29,30 @@
 		toString: () ->
 			"#{@name} - #{@food} #{@note} $#{@money}"

+	class Store
+		constructor: (options) ->
+			{@name, @phone, @link} = options
+
+		toString: () ->
+			"#{@name} #{@phone} - #{@link}"
+
+	unless process.env.HUBOT_ORDER_MENU_STORE_INFO?
+		robot.logger.warning 'The HUBOT_ORDER_MENU_STORE_INFO environment variable not set'
+
+	Stores = []
+	if process.env.HUBOT_ORDER_MENU_STORE_INFO?
+		rawStoreInfos = process.env.HUBOT_ORDER_MENU_STORE_INFO.split ';'
+		for rawStoreInfo in rawStoreInfos
+			robot.logger.debug rawStoreInfo
+			storeInfos = rawStoreInfo.split ','
+			robot.logger.debug storeInfos
+			Stores.push (new Store name:storeInfos[0], phone:storeInfos[1], link:storeInfos[2])
+
 	robot.brain.data['orders'] = []
+
+	robot.respond /order stores/i, (msg) ->
+		msg.reply 'The HUBOT_ORDER_MENU_STORE_INFO environment variable not set' unless Stores.length
+		msg.reply store for store in Stores

 	robot.respond /order\s+(\S*)\s+((\S*\s+)+)?\$(\S*)(\s+for\s+@?(\S*))?/i, (msg) ->
 		#robot.logger.debug Util.inspect(msg)


add log statement for refresh_all_overlays

--- src/app.coffee
+++ src/app.coffee
@@ -98,6 +98,7 @@
                 resp color: color, opac: it.opac

     refresh_all_overlays: ->
+        console.log 'Refreshing all overlays!'
         @storage.get @essentials, (it) =>
             color = K.choose_color it
             chrome.tabs.query {}, (tabs) ->


Fix bug which caused langing page not to show.

--- src/app.coffee
+++ src/app.coffee
@@ -78,7 +78,11 @@
         Backbone.history.fragment == ''

     addBackgroundLayerAsBodyClass = =>
-        $('body').removeClass().addClass 'maplayer-' + p13n.get('map_background_layer')
+        $body = $('body')
+        isLanding = $('body').hasClass 'landing'
+        $body.removeClass().addClass 'maplayer-' + p13n.get('map_background_layer')
+        if isLanding
+            $body.addClass 'landing'

     class AppControl extends BaseControl
         initialize: (appModels) ->
@@ -555,8 +559,7 @@
         $('html').attr 'lang', p13n.getLanguage()
         app.start()
         if isFrontPage()
-            if p13n.get('first_visit')
-                $('body').addClass 'landing'
+            $('body').addClass 'landing'
         addBackgroundLayerAsBodyClass()
         p13n.setVisited()
         uservoice.init(p13n.getLanguage())


Server: disable brute force check based on IP

--- workers/social/lib/social/models/log/index.coffee
+++ workers/social/lib/social/models/log/index.coffee
@@ -80,13 +80,6 @@

     daisy queue = [
       ->
-        return queue.next() unless ip
-        # do we have 5 failed login attempts from this ip...
-        JLog.some {ip : ip}, {limit : TRY_LIMIT_FOR_BLOCKING, sort: createdAt: -1}, (err, results)->
-          checkRestrictions err, results, (res)->
-            unless res then return callback res
-            queue.next()
-      ->
         return queue.next() unless username
         JLog.some {username : username}, {limit : TRY_LIMIT_FOR_BLOCKING, sort: createdAt: -1}, (err, results)->
           checkRestrictions err, results, (res)->


WorkspaceModel: Sanitize layout option before saving.

--- workers/social/lib/social/models/workspace.coffee
+++ workers/social/lib/social/models/workspace.coffee
@@ -40,8 +40,11 @@
     data.originId = client.connection.delegate._id
     data.slug     = slugify data.name?.toLowerCase()

-    {name, slug, machineUId, rootPath,
-     originId, layout, machineLabel} = data
+    {name, slug, machineUId, rootPath, originId, machineLabel} = data
+
+    # we don't support saving layout for now, i will set it to empty object
+    # to prevent storing any kind of data in it. -- acetz!
+    data.layout = {}

     generateUniqueName { originId, name }, (err, res)->


include externals for now

include externals for now

--- src/aui.coffee
+++ src/aui.coffee
@@ -1,6 +1,6 @@
 optional = require
-$ = window.jQuery
-React = window.React
+$ = window.jQuery or require 'jquery'
+React = window.React or require 'react'

 ## class <Aui/>
 # Aui class is the main wrapper class


fix #388

fix #388

--- src/bot.coffee
+++ src/bot.coffee
@@ -217,8 +217,8 @@
       id: user.id
       name: user.name
       real_name: user.real_name
-      email_address: user.profile.email
       slack: {}
+    newUser.email_address = user.profile.email unless not user.profile
     for key, value of user
       # don't store the SlackClient, because it'd cause a circular reference
       # (it contains users and channels), and because it has sensitive information like the token


random init

random init

--- src/cli.coffee
+++ src/cli.coffee
@@ -44,24 +44,26 @@
       else if xd < 0
         @moveRight()

-e = (ch, co, x, y) ->
+random = (n) -> Math.floor Math.random() * n + 1
+rr = -> random rows-2
+rc = -> random cols
+
+e = (ch, co) ->
   en = new Entity ch, co
-  en.x = x
-  en.y = y
+  en.x = rc()
+  en.y = rr()
   en

-me = e '¤', 'cyan', 15, 15
-
+me = e '¤', 'cyan'
 me.die = ->
   message 'Game over, you died :(', 'red'
   game_over = yes

-entities = [
-  e '¥', 'red', 7, 5
-  e '¥', 'red', 1, 15
-  e '¥', 'red', 27, 25
-  e '¥', 'red', 14, 17
-]
+entities = []
+
+num_enemies = 4
+for i in [0...num_enemies]
+  entities.push e '¥', 'red'

 checkForCollision = (left, right) ->
   if left.x is right.x and left.y is right.y


should not use document in case used in iframe

--- src/dom.coffee
+++ src/dom.coffee
@@ -145,8 +145,8 @@
       ScribeDOM.removeClass(node, className)

   triggerEvent: (elem, eventName, bubble, cancels) ->
-    if document.createEvent
-      evt = document.createEvent("HTMLEvents")
+    if elem.ownerDocument.createEvent
+      evt = elem.ownerDocument.createEvent("HTMLEvents")
       evt.initEvent(eventName, bubble, cancels)
       elem.dispatchEvent(evt)
     else


Add support for `onClick` property.

Add support for `onClick` property.

--- src/index.cjsx
+++ src/index.cjsx
@@ -120,6 +120,8 @@
               .style("opacity", 1/1e6)
               .each("end", -> tooltip.style("visibility", "hidden"))
         )
+    if @props.onClick
+      bar.on "click", (d, i) => @props.onClick(d, i)

     if @props.xAxis
       xAxis = d3.svg.axis()


check for existence of header_ext

check for existence of header_ext

--- src/jwt.coffee
+++ src/jwt.coffee
@@ -84,7 +84,7 @@
 #
 #
 #
-module.exports.encode = (claim, key, algorithm = "HS256", header) ->
+module.exports.encode = (claim, key, algorithm = "HS256", header_ext) ->

   jwa_provider  = jwa.provider algorithm
   throw new Error "Algorithm #{algorithm} is not yet supported." unless jwa_provider
@@ -95,8 +95,9 @@
     typ: 'JWT'
     alg: algorithm

-  for key, val of header
-    header[key] = val
+  if header_ext?
+    for key, val of header_ext
+      header[key] = val

   #create segments, all segment should be base64 string
   segments = []


Remove commented out code.

Remove commented out code.

--- src/run.coffee
+++ src/run.coffee
@@ -6,14 +6,4 @@
       process.exit 0
     .then null, (error) ->
       console.error error.message
-      process.exit 1
-
-# setDefault = (name, value) ->
-#   getConfig(config).then (config) ->
-#     config.defaults[name] = value
-#     writeConfig config, next
-#
-# writeConfig = (config) ->
-#   @findConfigFilePath.then (path) ->
-#     @log "Writing config to #{path}."
-#     writeFile path, JSON.stringify config+      process.exit 1


cleanup old code

cleanup old code

--- src/xml.coffee
+++ src/xml.coffee
@@ -94,7 +94,7 @@

     TagInstance = @builder?.Tag ? Tag
     newtag = new TagInstance name, attrs, null, opts
-    callback = ((tag) -> tag.children children, opts) if children?
+    callback = ((tag) -> tag.children children) if children?
     add_tag.call this, newtag, callback
     return newtag # hopefully this is still the same after the approval

@@ -122,9 +122,9 @@
         @isempty = yes
         @isselfclosing = no
         @content = ""
-        @children children, opts
         @$tag = sync_tag
         @tag = new_tag
+        @children children

     attr: (key, value) =>
         if typeof key is 'string'


Fix: notify users when someone talks in channel

--- packages/rocketchat-lib/server/sendMessage.coffee
+++ packages/rocketchat-lib/server/sendMessage.coffee
@@ -102,7 +102,7 @@
 			# only subscriptions to the same room
 			rid: message.rid
 			# only the ones that have not been alerted yet
-			alert: false
+			alert: { $ne: true }
 			# not the msg owner
 			'u._id':
 				$ne: message.u._id


Update styles

Update styles

--- .atom/config.cson
+++ .atom/config.cson
@@ -13,7 +13,7 @@
       "one-light-syntax"
     ]
   editor:
-    fontSize: 8
+    fontSize: 2
     showInvisibles: true
   "exception-reporting":
     userId: ""
@@ -32,7 +32,7 @@
   "linter-hlint":
     hlintExecutablePath: "/home/jk/.local/bin/hlint"
   "linter-ui-default":
-    panelHeight: 252
+    panelHeight: 231
     showPanel: true
   minimap:
     plugins:


atom: update config

atom: update config

--- .atom/config.cson
+++ .atom/config.cson
@@ -40,6 +40,9 @@
       "minimap"
       "pretty-json"
       "tool-bar-main"
+    ]
+    packagesWithKeymapsDisabled: [
+      "git-time-machine"
     ]
     projectHome: "~/dev"
     themes: [


Adjust disabled atom packages.

Adjust disabled atom packages.

--- .atom/config.cson
+++ .atom/config.cson
@@ -1,10 +1,14 @@
 "*":
+  "atom-python-virtualenv": {}
   "autocomplete-python":
     useKite: false
   core:
     destroyEmptyPanes: false
     disabledPackages: [
       "linter"
+      "whitespace"
+      "welcome"
+      "metrics"
     ]
     openEmptyEditorOnStart: false
     restorePreviousWindowsOnStart: "always"


atom: update conf

atom: update conf

--- .atom/config.cson
+++ .atom/config.cson
@@ -52,12 +52,14 @@
   jumpy: {}
   whitespace:
     ignoreWhitespaceOnCurrentLine: false
+  docblockr: {}
+  emmet: {}
 ".source.ts.tsx":
+  editor:
+    preferredLineLength: 100
+".source.ts":
   editor:
     preferredLineLength: 100
 ".diff.source":
   editor:
     preferredLineLength: 100
-".source.ts":
-  editor:
-    preferredLineLength: 100


atom: use f from jumpy in vim-mode

atom: use f from jumpy in vim-mode

--- .atom/keymap.cson
+++ .atom/keymap.cson
@@ -28,3 +28,6 @@
 '.fuzzy-finder atom-text-editor[mini]':
   'cmd-|': 'pane:split-right'
   'cmd-\\': 'pane:split-down'
+
+'atom-text-editor:not(.mini).vim-mode:not(.insert-mode):not(.jumpy-jump-mode)':
+  'f': 'jumpy:toggle'


Added admin management page to the navbar.

--- picoCTF-web/web/coffee/navbar.coffee
+++ picoCTF-web/web/coffee/navbar.coffee
@@ -39,7 +39,7 @@
   Team: "/team"
   Chat: "/chat"
   Scoreboard: "/scoreboard"
-  About:
+  About:
     About: "/about"
     News: "/news"
   Account:
@@ -52,6 +52,9 @@
   News: "/news"
   Scoreboard: "/scoreboard"
   Login: "/login"
+
+adminLoggedIn =
+  Management: "/management"

 loadNavbar = (renderNavbarLinks, renderNestedNavbarLinks) ->

@@ -77,6 +80,10 @@
             navbarLayout.links = userLoggedIn
          else
             navbarLayout.links = userLoggedInNoCompetition
+
+        if data.data["admin"]
+           $.extend navbarLayout.links, adminLoggedIn
+
     $("#navbar-links").html renderNavbarLinks(navbarLayout)
     $("#navbar-item-logout").on("click", logout)


Inactive flash icon

Inactive flash icon


moved CLI test teardown directly onto fixtures group;

--- test/cli_test.coffee
+++ test/cli_test.coffee
@@ -37,19 +37,6 @@
   #{md.version}

 """
-
-# Configuration
-# -------------
-
-exports.tearDown = (callback) ->
-  return do callback unless fs.existsSync OUTPUT_DIR
-
-  fs.readdirSync(OUTPUT_DIR).forEach (file) ->
-    fs.unlinkSync path.join OUTPUT_DIR, file
-
-  fs.rmdirSync OUTPUT_DIR
-
-  do callback

 # Helpers
 # -------
@@ -108,7 +95,17 @@
       path.basename file, HTML_EXT
   )

-  tests = {}
+  tests =
+    tearDown:(callback) ->
+      return do callback unless fs.existsSync OUTPUT_DIR
+
+      fs.readdirSync(OUTPUT_DIR).forEach (file) ->
+        fs.unlinkSync path.join OUTPUT_DIR, file
+
+      fs.rmdirSync OUTPUT_DIR
+
+      do callback
+
   tests[fixture] = testFixture fixture for fixture in fixtures
   tests


placeholders for avatar and cover images

--- app/assets/javascripts/components/user_profile/header/form.js.coffee
+++ app/assets/javascripts/components/user_profile/header/form.js.coffee
@@ -20,6 +20,18 @@

   cancel: ->
     @props.onClose()
+
+  _get_avatar: ->
+    if _.isBlank(@state.avatar_url)
+      (span style: {fontSize: 30}, className: "input__photo input__photo_circle fa fa-user")
+    else
+      (img className: "input__photo input__photo_square", src: @state.avatar_url)
+
+  _get_cover: ->
+    if _.isBlank(@state.cover_url)
+      (span style: {fontSize: 30}, className: "input__photo input__photo_circle fa fa-user")
+    else
+      (img className: "input__photo input__photo_square", src: @state.cover_url)

   render: ->
     errors = if @state.errors
@@ -36,7 +48,7 @@
               (div className: "col-xs-12 col-md-6",
                 (div className: "form-group",
                   (label className: "input__title", 'Photo')
-                  (span style: {fontSize: 30}, className: "input__photo input__photo_circle fa fa-user")
+                  @_get_cover()
                   (button type: "button", className: "btn_grey btn btn-primary", 'Upload')
                   (button type: "button", className: "btn_link btn btn-link",    'Remove'))

@@ -56,7 +68,7 @@
               (div className: "col-xs-12 col-md-6",
                 (div className: "form-group",
                   (label className: "input__title", 'Cover')
-                  (img className: "input__photo input__photo_square")
+                  @_get_cover()
                   (button type: "button", className: "btn_grey btn btn-primary", 'Upload')
                   (button type: "button", className: "btn_link btn btn-link",    'Remove'))


Removed unneeded stuff.

Removed unneeded stuff.

--- app/assets/javascripts/web/views/sidebar/conversation_view.js.coffee
+++ app/assets/javascripts/web/views/sidebar/conversation_view.js.coffee
@@ -4,14 +4,10 @@
   classNames: ['sidebar-conversation']
   tagName: 'li'

-  didInsertElement: (topic) ->
-    console.log "foo"
-
   classNameBindings: ['conversationTypeClass']

   conversationTypeClass: (->
-    console.log this
-    "conversation-topic-#{@model.get('topic').get('type')}" if @model
+    "conversation-topic-#{@model.get('topic').get('type')}"
   ).property("conversationTypeClass")

 )


remove sauce mocha workaround

remove sauce mocha workaround

--- test/graphite.coffee
+++ test/graphite.coffee
@@ -49,5 +49,5 @@
     it "doesn't accept a single object", ->
       expect(-> graphite.args_to_params args: [{}]).to.throwException()

-    #xit "doesn't accept a single array", ->
-      #expect(-> graphite.args_to_params args: [[]]).to.throwException()
+    xit "doesn't accept a single array", ->
+      expect(-> graphite.args_to_params args: [[]]).to.throwException()


getLength func bug fixed

getLength func bug fixed


add pending test

add pending test

--- test/ssh_keys.coffee
+++ test/ssh_keys.coffee
@@ -81,6 +81,8 @@
           SSHKey.findOne {name: 'tlevine@motorsag'}, (err, key) ->
             should.exist key
             done()
+
+        it "overwrites the box's authorized_keys file"

     describe 'when the apikey is invalid', ->
       before ->


Remove old codes comment

Remove old codes comment

--- src/browser/Application.coffee
+++ src/browser/Application.coffee
@@ -23,7 +23,6 @@
         @_options       = options
         @packageJson    = require("../package.json")

-        # @createWindow(options)
         new AppWindow assign {}, options,
             url     : "file://#{__dirname}/../renderer/index.html"

@@ -41,40 +40,3 @@
         ipc.on "open", (e, options) ->
             options ?= @_opts
             new AppWindow(options)
-
-
-# {EventEmitter}      = require "events"
-#
-# module.exports = class AppWindow extends BrowserWindow
-#     _opts           : null
-#
-#     constructor     : (options = {}) ->
-#         global.app = @
-#
-#         @_opts = options
-#
-#
-#         new AppWindow(options)
-#
-#     addWindow       : (options = {}) ->
-#         appWindow = new AppWindow
-#             width   : 800
-#             height  : 1000
-#             url     : "file://#{__dirname}/../renderer/index.html"
-#
-#
-#         appWindow.on "closed", =>
-#             index = @_windows.indexOf appWindow
-#             @_windows.splice index, 1
-#             appWindow = null
-#
-#         menu = new AppMenu(pkg: @packageJson)
-#         menu.attachToWindow appWindow
-#
-#         appWindow.on "closed", =>
-#             @removeWindow appWindow
-#
-
-#
-#         @_windows.push appWindow
-#         return


added comments

added comments

--- src/core/views/internal.coffee
+++ src/core/views/internal.coffee
@@ -63,11 +63,19 @@
     ###

     get: (key) ->
+
+      # try to find the value in this view
       ret = super(key)
+
+      # value doesn't exist? check the parent
       if not ret
         ret = @_parent?.get(key)
+
+        # value exists? set to this view so we don't have to check
+        # the parents anymore - also fixes any issue when setting a value
         if ret
           @set key, ret
+
       ret


update dreprecated option dataTable

update dreprecated option dataTable

--- BackofficeBundle/Resources/public/coffee/table/TableviewCollectionView.coffee
+++ BackofficeBundle/Resources/public/coffee/table/TableviewCollectionView.coffee
@@ -65,14 +65,14 @@
       serverSide: true
       displayStart: displayStart
       pageLength: pageLength
-      bAutoWidth: false
       orderCellsTop: true
       processing: true
-      sPaginationType: "input_full"
-      sDom: "<'dt-toolbar'<'col-xs-12 col-sm-6'f><'col-sm-5 col-xs-6 hidden-xs'C><'col-xs-12 col-sm-1 hidden-xs'l>>"+
+      autowidth: false
+      pagingType: "input_full"
+      dom: "<'dt-toolbar'<'col-xs-12 col-sm-6'f><'col-sm-5 col-xs-6 hidden-xs'C><'col-xs-12 col-sm-1 hidden-xs'l>>"+
                     "t"+
                     "<'dt-toolbar-footer'<'col-sm-6 col-xs-12 hidden-xs'i><'col-sm-6 col-xs-12'p>>"
-      oLanguage:
+      language:
         sLengthMenu: "_MENU_"
         sSearch: " "
         sInfo: "Showing _START_ to _END_ of _TOTAL_ entries",
@@ -99,9 +99,6 @@
       ]
       order: [@options.order]
     )
-
-    api = @options.table.api()
-
     return
   processingData : (e, seggings, processing) ->
     if processing


series 2 domains in sample data for column


fixtypo

fixtypo

--- core/router/account.coffee
+++ core/router/account.coffee
@@ -7,12 +7,12 @@
     signup: (req, res) ->
       Account.authenticate req.token, (account) ->
         res.render 'signup',
-          user: account
+          account: account

     login: (req, res) ->
       Account.authenticate req.token, (account) ->
         res.render 'login',
-          user: account
+          account: account

   post:
     signup: (req, res) ->


Add helpful error message if dredd hooks do not modify the transactions in test mode

--- src/hooks-worker-client.coffee
+++ src/hooks-worker-client.coffee
@@ -299,7 +299,9 @@
       # https://github.com/apiaryio/dredd-hooks-template/blob/master/features/execution_order.feature
       if process.env['TEST_DREDD_HOOKS_HANDLER_ORDER'] == "true"
         console.error 'FOR TESTING ONLY'
-        for mod, index in transactions[0]['hooks_modifications']
+        unless mods = transactions[0]?.hooks_modifications?
+          throw new Error "Hooks must modify transaction.hooks_modifications"
+        for mod, index in mods
           console.error "#{index} #{mod}"
         console.error 'FOR TESTING ONLY'


Fix  some errors in node.coffee

Fix  some errors in node.coffee

--- src/public/library/node.coffee
+++ src/public/library/node.coffee
@@ -46,7 +46,7 @@

 		broadcastTimeout = 4000 # Wait for return messages after a node broadcasts that it has a token
 		tokenThreshhold = 1
-		superNodeSwitchThreshhold = 0.8 # Scaler
+		superNodeSwitchThreshhold = 0.75 # Scaler: from 0 to 1. More is easier swtiching

 		# Constructs a new app.
 		#
@@ -120,6 +120,7 @@
 		# @param peer [Peer] the peer that disconnects
 		#
 		_onPeerDisconnect: ( peer ) =>
+			console.log peer
 			if peer is @getParent()
 				candidates = _(@getPeers()).filter( ( p ) -> p.isSuperNode )
 				@_pickParent(candidates)
@@ -290,16 +291,19 @@
 		# @param superNode [boolean] SuperNode state
 		#
 		setSuperNode: ( superNode = true ) =>
-			@server.emit('setSuperNode', superNode)
-			@trigger('setSuperNode', superNode) # App is listening
-			@broadcast('peer.setSuperNode', @id, superNode)
-			@isSuperNode = superNode
-			if superNode
-				@_triggerStaySuperNodeTimeout()
-			else
-				for sibling in @getSiblings()
-					@removeSibling(sibling)
-				@_pickParent()
+			if superNode is not @isSuperNode
+				if not superNode and @getSiblings().length is 0
+					return
+				@server.emit('setSuperNode', superNode)
+				@trigger('setSuperNode', superNode) # App is listening
+				@broadcast('peer.setSuperNode', @id, superNode)
+				@isSuperNode = superNode
+				if superNode
+					@_triggerStaySuperNodeTimeout()
+				else
+					for sibling in @getSiblings()
+						@removeSibling(sibling)
+					@_pickParent()

 		_triggerStaySuperNodeTimeout: () =>
 			if @getChildren().length is 0
@@ -581,7 +585,7 @@
 		# @param tokenString [String] A serialised token
 		#
 		_onTokenRequestCandidate: ( peer, tokenString ) =>
-			if(@isSuperNode)
+			if @isSuperNode
 				token = Token.deserialize(tokenString)
 				bestCandidateDistance = null
 				for child in @getChildren()
@@ -599,31 +603,39 @@
 		# @param nodeId [String] Node id of the candidate
 		#
 		_onTokenCandidate: ( peer, distance, nodeId ) =>
-			unless @token.candidates?
-				@token.candidates = new Array()
-			candidate = new Object()
-			candidate.distance = distance
-			candidate.nodeId = nodeId
-			@token.candidates.push(candidate)
+			if @token?
+				unless @token.candidates?
+					@token.candidates = new Array()
+				candidate = new Object()
+				candidate.distance = distance
+				candidate.nodeId = nodeId
+				@token.candidates.push(candidate)

 		# Picks a new owner of the token. If the new owner is self, then it becomes a supernode
 		#
 		# @return[Node] Return a new owner of the token
 		#
 		_pickNewTokenOwner: ( ) ->
-			bestCandidateDistance = null
-			for candidate in @token.candidates ? []
-				if !bestCandidateDistance? or candidate.distance < bestCandidateDistance
-					bestCandidateDistance = candidate.distance
-					bestCandidate = candidate
-
-			console.log "Best Candidate is " + bestCandidate.nodeId + " with distance " + bestCandidateDistance
-			if bestCandidate
-				if bestCandidate.nodeId is @id
-					@setSuperNode(true)
+			if @token?
+				bestCandidateDistance = null
+				for candidate in @token.candidates ? []
+					if !bestCandidateDistance? or candidate.distance < bestCandidateDistance
+						bestCandidateDistance = candidate.distance
+						bestCandidate = candidate
+
+				if bestCandidate
+					console.log "Best Candidate is " + bestCandidate.nodeId + " with distance " + bestCandidateDistance
+					if bestCandidate.nodeId is @id
+						if not @isSuperNode
+							@setSuperNode(true)
+					else
+						@emitTo(bestCandidate.nodeId,'token.add', @token.serialize())
+						@token = null
+						if @isSuperNode
+							@setSuperNode(false)
 				else
-					@emitTo(bestCandidate.nodeId,'token.add', @token.serialize())
-					@token = null
+					if not @isSuperNode
+						@setSuperNode(true)

 		# Applies Vivaldi alghoritm. Calculates the coordinates of a node
 		#
@@ -644,8 +656,7 @@
 		_lookForBetterSupernode: () =>
 			siblings = @getSiblings()
 			children = @getChildren()
-			childrenLength = children.length
-			if @isSuperNode and siblings.length > 0 and childrenLength > 0
+			if @isSuperNode and siblings.length > 0 and children.length > 0
 				for child in children
 					bestCandidateDistance = null
 					for parent in siblings


Added further logging and error handling to diagnostic HTTP consumption

--- src/scripts/diagnostics.coffee
+++ src/scripts/diagnostics.coffee
@@ -117,20 +117,30 @@
         Organization.generateReport(start, end)
         .catch(
           (err) ->
-            Logger.log "POSTing to #{response_url}"
-            robot.http(response_url)
-            .header('Content-Type', 'application/json')
-            .post({
-              "text": "Failed to produce a salary report"
-            })
+            Logger.errorToSlack "Failed to produce a salary report", err
+            Logger.log "POSTing to #{response_url}"
+            payload =
+              text: 'Failed to produce a salary report'
+            robot.http(response_url)
+            .header('Content-Type', 'application/json')
+            .post(JSON.stringify(payload))
         )
         .done(
           (numberDone) ->
-            robot.http(response_url)
-            .header('Content-Type', 'application/json')
-            .post({
-                "text": "Salary report generated for #{numberDone} employees"
-            })
+            Logger.log "Payroll has been generated"
+            Logger.log "POSTing to #{response_url}"
+            payload =
+              text: "Salary report generated for #{numberDone} employees"
+            robot.http(response_url)
+            .header('Content-Type', 'application/json')
+            .post(JSON.stringify(payload)) (err, response, body) ->
+              if err
+                response.send "Encountered an error :( #{err}"
+                return
+              if res.statusCode isnt 200
+                response.send "Request didn't come back HTTP 200 :("
+                return
+              Logger.log body
         )
         res.status 200
         res.json {
@@ -156,21 +166,29 @@
         .catch(
           (err) ->
             Logger.errorToSlack "Failed to resync", err
-            robot.http(response_url)
-            .header('Content-Type', 'application/json')
-            .post({
-              "text": "Failed to resync"
-            })
+            Logger.log "POSTing to #{response_url}"
+            payload =
+              text: 'Failed to resync'
+            robot.http(response_url)
+            .header('Content-Type', 'application/json')
+            .post(JSON.stringify(payload))
         )
         .done(
           (status) ->
             Logger.log "Options have been re-loaded"
             Logger.log "POSTing to #{response_url}"
-            robot.http(response_url)
-            .header('Content-Type', 'application/json')
-            .post({
-              "text": "Re-synced with spreadsheet"
-            })
+            payload =
+              text: 'Re-synced with spreadsheet'
+            robot.http(response_url)
+            .header('Content-Type', 'application/json')
+            .post(JSON.stringify(payload)) (err, response, body) ->
+              if err
+                response.send "Encountered an error :( #{err}"
+                return
+              if res.statusCode isnt 200
+                response.send "Request didn't come back HTTP 200 :("
+                return
+              Logger.log body
         )
         res.status 200
         res.json {


payments: WIP

payments: WIP

--- client/app/Applications/Dashboard.kdapplication/AppController.coffee
+++ client/app/Applications/Dashboard.kdapplication/AppController.coffee
@@ -107,11 +107,14 @@

   showBillingInfoModal: (type, group, callback) ->
     @fetchBillingInfo group, (err, billing) =>
-      billing = {}  if err or not billing?
+      paymentController = KD.getSingleton "paymentController"
+
+      if billingInfo?
+        paymentController.showBillingInfoModal type, billing, callback

-      paymentController = KD.getSingleton "paymentController"
-      paymentController.showBillingInfoModal type, billing, callback
-
+      else
+        console.log "we need to decide whether to use the user's billing info"
+
   fetchBillingInfo: (group, callback = (->)) ->
     group.fetchBillingInfo (err, billing) -> callback err, billing