activity feed: comment out unused old member bucket view classes

--- client/landing/app/Applications/Activity.kdapplication/views/newmemberbucket.coffee
+++ client/landing/app/Applications/Activity.kdapplication/views/newmemberbucket.coffee
@@ -9,159 +9,159 @@

     super

-class NewMemberBucketView extends JView
+#class NewMemberBucketView extends JView

-  constructor: (options = {}, data)->
+#  constructor: (options = {}, data)->

-    options.cssClass = "new-member"
+#    options.cssClass = "new-member"

-    super
+#    super

-    @group = new NewMemberLinkGroup (totalCount : @getData().count), @getData().anchors
+#    @group = new NewMemberLinkGroup (totalCount : @getData().count), @getData().anchors

-    @lastFetchedDate      = null
-    @lastFetchedItemCount = 0
-    @isUserViewCreated    = no
-    @listViews            = []
-    @isLoading            = no
+#    @lastFetchedDate      = null
+#    @lastFetchedItemCount = 0
+#    @isUserViewCreated    = no
+#    @listViews            = []
+#    @isLoading            = no

-    @group.on "moreLinkClicked", =>
+#    @group.on "moreLinkClicked", =>

-      return warn "not yet implemented, @facet fix pls!!!"
+#      return warn "not yet implemented, @facet fix pls!!!"

-      return if @isLoading or @getData().count <= @lastFetchedItemCount and @showMore
-      @isLoading = yes
-      @groupLoader = new KDLoaderView
-        cssClass : "new-members-loader"
-        size     :
-          width  : 24
+#      return if @isLoading or @getData().count <= @lastFetchedItemCount and @showMore
+#      @isLoading = yes
+#      @groupLoader = new KDLoaderView
+#        cssClass : "new-members-loader"
+#        size     :
+#          width  : 24

-      @addSubView @groupLoader
-      @groupLoader.show()
-      @showMore?.hide()
+#      @addSubView @groupLoader
+#      @groupLoader.show()
+#      @showMore?.hide()

-      count = 10
+#      count = 10

-      if not @isUserViewCreated
-        @createCloseButton()
-        @isUserViewCreated = yes
+#      if not @isUserViewCreated
+#        @createCloseButton()
+#        @isUserViewCreated = yes

-      selector    =
-        type      : { $in : [options.type or= 'CNewMemberBucketActivity'] }
-        createdAt :
-          $gt     : @lastFetchedDate or @getData().createdAtTimestamps[0]
-          $lt     : @getData().createdAtTimestamps[1]
+#      selector    =
+#        type      : { $in : [options.type or= 'CNewMemberBucketActivity'] }
+#        createdAt :
+#          $gt     : @lastFetchedDate or @getData().createdAtTimestamps[0]
+#          $lt     : @getData().createdAtTimestamps[1]

-      options     =
-        limit     : count
+#      options     =
+#        limit     : count

-      KD.getSingleton("appManager").tell "Activity", "fetch", selector, options, (teasers, activities) =>
-        @getData().anchors = @getData().anchors.concat teasers.map (item)-> item.anchor
-        @group.setData @getData().anchors
+#      KD.getSingleton("appManager").tell "Activity", "fetch", selector, options, (teasers, activities) =>
+#        @getData().anchors = @getData().anchors.concat teasers.map (item)-> item.anchor
+#        @group.setData @getData().anchors

-        items = @getData().anchors.slice @lastFetchedItemCount, @lastFetchedItemCount + count
+#        items = @getData().anchors.slice @lastFetchedItemCount, @lastFetchedItemCount + count

-        newMembersList = new KDListViewController
-          view        : new NewMemberList
-          wrapper     : no
-          scrollView  : no
-        , items: items
+#        newMembersList = new KDListViewController
+#          view        : new NewMemberList
+#          wrapper     : no
+#          scrollView  : no
+#        , items: items

-        @listViews.push newMembersList
-        @addSubView newMembersList.getView()
-        @lastFetchedItemCount = @lastFetchedItemCount + count
-        @showMore.hide() if @getData().count <= @lastFetchedItemCount and @showMore
-        @lastFetchedDate = activities.last.createdAt
-        @groupLoader.destroy()
-        @createShowMore count
-        @isLoading = no
+#        @listViews.push newMembersList
+#        @addSubView newMembersList.getView()
+#        @lastFetchedItemCount = @lastFetchedItemCount + count
+#        @showMore.hide() if @getData().count <= @lastFetchedItemCount and @showMore
+#        @lastFetchedDate = activities.last.createdAt
+#        @groupLoader.destroy()
+#        @createShowMore count
+#        @isLoading = no

-  createCloseButton: ->
-    @addSubView @closeButton = new KDView
-      cssClass : "close-button"
-      partial  : "Close"
-      click    : =>
-        list.getView().destroy() for list in @listViews
-        @closeButton.destroy()
-        @showMore?.destroy()
-        @groupLoader?.destroy()
-        @lastFetchedItemCount = 0
-        @isUserViewCreated    = no
-        @isLoading            = no
-        @lastFetchedDate      = null
-        @listViews            = []
+#  createCloseButton: ->
+#    @addSubView @closeButton = new KDView
+#      cssClass : "close-button"
+#      partial  : "Close"
+#      click    : =>
+#        list.getView().destroy() for list in @listViews
+#        @closeButton.destroy()
+#        @showMore?.destroy()
+#        @groupLoader?.destroy()
+#        @lastFetchedItemCount = 0
+#        @isUserViewCreated    = no
+#        @isLoading            = no
+#        @lastFetchedDate      = null
+#        @listViews            = []

-  createShowMore: (count) ->
-    return if @getData().count < @lastFetchedItemCount
-    @addSubView @showMore = new KDView
-      cssClass : "show-more"
-      partial  : "Show More"
-      click    : =>
-        @group.emit "moreLinkClicked"
-        @showMore.hide() if @getData().count <= @lastFetchedItemCount + count
+#  createShowMore: (count) ->
+#    return if @getData().count < @lastFetchedItemCount
+#    @addSubView @showMore = new KDView
+#      cssClass : "show-more"
+#      partial  : "Show More"
+#      click    : =>
+#        @group.emit "moreLinkClicked"
+#        @showMore.hide() if @getData().count <= @lastFetchedItemCount + count

-  pistachio:->
-    """
-
-
-    {{> @group}}
-    """
+#  pistachio:->
+#    """
+#
+#
+#    {{> @group}}
+#    """

-class NewMemberList extends KDListView
-  constructor: (options = {}, data) ->
-    options.tagName   = "ul"
-    options.cssClass  = "activity-new-members"
-    options.itemClass = NewMemberListItem
+#class NewMemberList extends KDListView
+#  constructor: (options = {}, data) ->
+#    options.tagName   = "ul"
+#    options.cssClass  = "activity-new-members"
+#    options.itemClass = NewMemberListItem

-    super options, data
+#    super options, data

-class NewMemberLinkGroup extends LinkGroup
+#class NewMemberLinkGroup extends LinkGroup

-  constructor:(options = {}, data)->
+#  constructor:(options = {}, data)->

-    options.suffix or= " became a member."
-    super options, data
+#    options.suffix or= " became a member."
+#    super options, data

-  createMoreLink:->
+#  createMoreLink:->

-    @more.destroy() if @more
-    {totalCount, group} = @getOptions()
-    @more = new KDCustomHTMLView
-      tagName     : "a"
-      cssClass    : "more"
-      partial     : "#{totalCount-3} more"
-      attributes  :
-        href      : "#"
-        title     : "Click to view..."
-      click       : (e)=>
-        @emit "moreLinkClicked"
+#    @more.destroy() if @more
+#    {totalCount, group} = @getOptions()
+#    @more = new KDCustomHTMLView
+#      tagName     : "a"
+#      cssClass    : "more"
+#      partial     : "#{totalCount-3} more"
+#      attributes  :
+#        href      : "#"
+#        title     : "Click to view..."
+#      click       : (e)=>
+#        @emit "moreLinkClicked"

-# OLD
+## OLD

-class NewMemberBucketItemView extends KDView
+#class NewMemberBucketItemView extends KDView

-  constructor:(options,data)->
-    options = $.extend options,
-      cssClass : "new-member"
-    super options,data
+#  constructor:(options,data)->
+#    options = $.extend options,
+#      cssClass : "new-member"
+#    super options,data

-    @anchor = new ProfileLinkView origin: data.anchor
+#    @anchor = new ProfileLinkView origin: data.anchor

-  render:->
+#  render:->

-  addCommentBox:->
+#  addCommentBox:->

-  viewAppended:->
-    @setTemplate @pistachio()
-    @template.update()
+#  viewAppended:->
+#    @setTemplate @pistachio()
+#    @template.update()

-  pistachio:->
-    """
-
-    {{> @anchor}}
-    became a member.
-    """
+#  pistachio:->
+#    """
+#
+#    {{> @anchor}}
+#    became a member.
+#    """


groups webhook: add space between edit link & actual link; make link title shorter

--- client/landing/app/Applications/Groups.kdapplication/Views/groupswebhookview.coffee
+++ client/landing/app/Applications/Groups.kdapplication/Views/groupswebhookview.coffee
@@ -5,10 +5,10 @@
     @setClass 'webhook'
     @editLink = new CustomLinkView
       href    : '#'
-      title   : 'Edit webhook'
+      title   : 'Edit'
       click   : (event)=>
         event.preventDefault()
         @emit 'WebhookEditRequested'

   pistachio:->
-    "{.endpoint{#(webhookEndpoint)}}{{> @editLink}}"+    "{.endpoint{#(webhookEndpoint)}} {{> @editLink}}"


Workspace: Fixed updating terminal references on firebase node.

--- client/landing/app/CommonViews/workspace/views/workspacefloatingpanelauncher.coffee
+++ client/landing/app/CommonViews/workspace/views/workspacefloatingpanelauncher.coffee
@@ -106,11 +106,12 @@
       delegate   : @panel
       sessionKey : @sessionKeys.terminal

-    @terminalPane.webterm.on "WebTermConnected", =>
-      @keysRef.child("terminal").set
-        key    : @terminalPane.remote.session
-        host   : KD.nick()
-        vmName : KD.getSingleton("vmController").defaultVmName
+    if @workspace.amIHost()
+      @terminalPane.webterm.on "WebTermConnected", =>
+        @keysRef.child("terminal").set
+          key    : @terminalPane.remote.session
+          host   : KD.nick()
+          vmName : KD.getSingleton("vmController").defaultVmName

   createPreviewPane: ->
     @container.addSubView @preview = new KDView


Try without Sausage

Try without Sausage


animate tidy

animate tidy

--- tree2.coffee
+++ tree2.coffee
@@ -592,7 +592,7 @@
   tidy: (tidy, tree, tree_pos) ->
     rel_pos = []

-    getBounds = (node) ->
+    calcBounds = (node) ->
       width = node.radius * 2
       height = node.radius * 2

@@ -608,7 +608,7 @@
         children_width = 0
         children_max_height = 0
         for c, idx in children
-          cb = getBounds(c)
+          cb = calcBounds(c)
           children_bounds[idx] = cb

           children_width += cb.width
@@ -624,7 +624,6 @@
           rel_pos.push (
             parent: node
             child: c
-            child_bounds: children_bounds[idx]
             pos: ( x: x + children_bounds[idx].width/2, y:
                       default_node_radius * 3 )
           )
@@ -633,21 +632,33 @@
       width: width + default_node_radius
       height: height + default_node_radius

-    getBounds(tree)
-
-    tree.x = tree_pos.x
-    tree.y = tree_pos.y
+    calcBounds(tree)
+
+    duration = .1
+    anims = [ new NodeAnimation(tree, tree_pos, duration) ]
+
+    # rel_pos forms a post-order traversal, so by taking it backwards
+    # we will always have the new position for the root computed before we
+    # visit its children
+    moved_nodes = [tree]
+    new_positions = [tree_pos]

     for i in [rel_pos.length-1 ..0] by -1
       rp = rel_pos[i]
       c = rp.child
       p = rp.parent
-      cb = rp.child_bounds
       pos = rp.pos
-      console.log("child: #{c.x},#{c.y} bounds: #{cb.width},#{cb.height} parent: #{p.x},#{p.y} offset #{pos.x},#{pos.y}")
-
-      c.x = p.x + pos.x
-      c.y = p.y + pos.y
+
+      pidx = moved_nodes.indexOf(p)
+      ppos = new_positions[pidx]
+
+      newpos = (x: ppos.x + pos.x, y: ppos.y + pos.y)
+      new_positions.push(newpos)
+      moved_nodes.push(c)
+
+      anims.push(new NodeAnimation(c, newpos, duration))
+
+    @addAnimations([new CollectionAnimation(anims, -1)])

 class HuffmanNodeCollection extends NodeCollection
   constructor: (shape) ->


Server restart should not trigger browser reload. When you work on the server side, you don't want the browser to refresh all the time.

--- src/server.coffee
+++ src/server.coffee
@@ -89,17 +89,8 @@
       logging.info 'Restarting the application server...'
     else
       # Start the server in a child process
-      child = exports.child = spawn 'node', ['server/server.js'], {cwd: process.cwd()}
+      child = exports.child = spawn 'node', ['server/server.js'], {stdio: 'inherit'}
       child.shouldRestart = false
-
-      child.stdout.on 'data', (data) ->
-        console.log data.toString()
-        if /Quit the server with CONTROL-C/.test(data.toString())
-          reloadBrowser()
-
-      child.stderr.on 'data', (data) ->
-        if data.toString().length > 1
-          logging.error data

       child.on 'exit', (code) =>
         exports.child = null


close bug with url

close bug with url

--- src/sirius.coffee
+++ src/sirius.coffee
@@ -209,6 +209,7 @@

     @data = params['data'] || null

+
   # @param e [EventObject|null] - event object if it's a mouse\key events, and `null` when it's url change event
   # @param args [Array<Any>] - arguments, used only for url changes events
   #
@@ -230,6 +231,7 @@
         @action.apply(null, merge)
         @after()
     else
+      args = [].concat.apply([], args)
       if @guard
         if @guard.apply(null, args)
           @before()
@@ -254,24 +256,21 @@
     current = prev = window.location.hash

     for url, action of routes when url.indexOf("#") != 0 && url.toString() != "404"
-      do (url, action) =>
-        handler = if Sirius.Utils.is_function(action)
-          action
-        else
-          (e) ->
-            (new Sirius.ControlFlow(action)).handle_event(e)
-
-        z = url.match(/^([a-zA-Z:]+)(\s+)?(.*)?/)
-        event_name = z[1]
-        selector   = z[3] || document #when it a custom event: 'custom:event' for example
-        Sirius.Application.adapter.bind(selector, event_name, handler)
+      handler = if Sirius.Utils.is_function(action)
+        action
+      else
+        (e) -> (new Sirius.ControlFlow(action)).handle_event(e)
+
+      z = url.match(/^([a-zA-Z:]+)(\s+)?(.*)?/)
+      event_name = z[1]
+      selector   = z[3] || document #when it a custom event: 'custom:event' for example
+      Sirius.Application.adapter.bind(selector, event_name, handler)

     # for cache change obj[k, v] to array [[k,v]]
-    array_of_routes = for url, action of routes when url.toString() != "404"
-      do (url, action) ->
-        url    = new Sirius.RoutePart(url)
-        action = if Sirius.Utils.is_function(action) then action else new Sirius.ControlFlow(action)
-        [url, action]
+    array_of_routes = for url, action of routes when url.indexOf("#") == 0 && url.toString() != "404"
+      url    = new Sirius.RoutePart(url)
+      action = if Sirius.Utils.is_function(action) then action else new Sirius.ControlFlow(action)
+      [url, action]

     window.onhashchange = (e) =>
       prev = current
@@ -283,17 +282,17 @@

       #call first matched function
       for part in array_of_routes
-        do(part) =>
-          f = part[0]
-          r = f.match(current)
-          if r && !result
-            result = true
-            z = part[1]
-            if z.handle_event
-              z.handle_event(null, f.args)
-            else
-              z.apply(null, f.args)
-            return
+        f = part[0]
+        r = f.match(current)
+        if r && !result
+          result = true
+          flow = part[1]
+
+          if flow.handle_event
+            flow.handle_event(null, f.args)
+          else
+            flow.apply(null, f.args)
+          return

       #when no results, then call 404 or empty function
       if !result


improve tokenize function

improve tokenize function

--- src/string.coffee
+++ src/string.coffee
@@ -28,17 +28,20 @@

     tokenize: (string) ->

-        # make lowercase
-        # replace umlauts
-        # split into words (split by whitespace)
-        # remove non alphanumeric chars
+        string = string.toLowerCase()
+
+        # replace - and _ by whitespace to later break words on it
+        string = string.replace /[\-_]/g, ' '
+
+        string = module.exports.replaceUmlauts string
+
+        # split into words
+        words = _s.words string
+
+        words = words.map module.exports.removeNonAlphanumeric
+
         # remove empty words
+        words = words.filter (word) -> word isnt ''
+
         # remove duplicates
-
-        lowerCase = string.toLowerCase().replace /[\-_]/g, ' '
-
-        words = _s.words module.exports.replaceUmlauts lowerCase
-
-        processed = words.map module.exports.removeNonAlphanumeric
-
-        _.uniq processed.filter (word) -> word isnt ''
+        _.uniq words


simpler tester api

simpler tester api

--- src/tester.coffee
+++ src/tester.coffee
@@ -12,6 +12,10 @@
   lets.unshift 'message'
   lets.join ','

+getUnbound = (formula_src) ->
+  formula = parser.parse formula_src
+  evaluator.unbound formula
+
 test = (formula_src, csv_src, report) ->
   formula = parser.parse formula_src
   results = []
@@ -22,6 +26,11 @@
     report results
   )

+testJson = (formula_src, json, report) ->
+  formula = parser.parse formula_src
+  data.actual = evaluator.evaluate formula, data for data in json
+  report json
+
 getFailures = (results) ->
   failures = []
   for data, index in results
@@ -31,6 +40,8 @@

 module.exports = {
   getTemplate: getTemplate
+  getUnbound: getUnbound
   test: test
+  testJson: testJson
   getFailures: getFailures
 }


error handling

error handling

--- src/trello.coffee
+++ src/trello.coffee
@@ -59,7 +59,7 @@
           msg.send "* #{list.name}" for list in data unless err and data.length == 0
         break
     if found_board == false
-      msg.reply "I couldn't find a board named: #{msg.envelope.room}." unless id
+      msg.reply "I couldn't find a board named: #{msg.envelope.room}."

 createCard = (msg, list_name, cardName) ->
   msg.reply "Sure thing boss. I'll create that card for you."
@@ -80,7 +80,7 @@
                 msg.reply "OK, I created that card for you. You can see it here: #{data.url}" unless err
               break
           if found_list == false
-            msg.reply "I couldn't find a list named: #{list_name}." unless id
+            msg.reply "I couldn't find a list named: #{list_name}."
         break

 showCards = (msg, list_name) ->
@@ -104,7 +104,7 @@
                 msg.reply "No cards are currently in the #{data.name} list." if data.cards.length == 0 and !err
               break
           if found_list == false
-            msg.reply "I couldn't find a list named: #{list_name}." unless id
+            msg.reply "I couldn't find a list named: #{list_name}."
         break

 moveCard = (msg, card_id, list_name) ->
@@ -125,7 +125,7 @@
                 msg.reply "Yep, ok, I moved that card to #{list_name}." unless err
             break;
           if found_list == false
-            msg.reply "I couldn't find a list named: #{list_name}." unless id
+            msg.reply "I couldn't find a list named: #{list_name}."
       break

 module.exports = (robot) ->


Fix up tests after WordPress move

Fix up tests after WordPress move

--- test/unit/index.coffee
+++ test/unit/index.coffee
@@ -64,33 +64,11 @@
       .expect(/ScraperWiki Enterprise Agreement/)
       .end(done)

-  it '/contact/', (done) ->
-    request(app)
-      .get('/contact/')
-      .expect(200)
-      .expect(/Contact Us/)
-      .end(done)
-
-  it '/about/', (done) ->
-    request(app)
-      .get('/about/')
-      .expect(200)
-      .expect(/About ScraperWiki/)
-      .end(done)
-
-  it '/professional/', (done) ->
-    request(app)
-      .get('/professional/')
-      .expect(200)
-      .expect(/Professional Services/)
-      .expect(/Get in touch/)
-      .end(done)
-
   it '/', (done) ->
     request(app)
       .get('/')
       .expect(200)
-      .expect(/Liberate your data/)
+      .expect(/placeholder for the internal home page/)
       .end(done)

   it '/login/', (done) ->
@@ -106,6 +84,7 @@
   xit 'Should display a 404 error', (done) ->
     request(app)
       .get('/xxsdsdsdsd')
+      .expect(404)
       .expect(/Not Found/)
       .end(done)

@@ -117,3 +96,5 @@
       .end (err, res) ->
         (spy.calledWith process.env['USER']).should.be.true
         done()
+
+


hid the chat handler on the right top, it was breaking the scrollbars

--- client/app/MainApp/maincontroller/mainviews/mainview.coffee
+++ client/app/MainApp/maincontroller/mainviews/mainview.coffee
@@ -176,7 +176,8 @@

   createChatPanel:->
     @addSubView @chatPanel   = new MainChatPanel
-    @addSubView @chatHandler = new MainChatHandler
+    # @addSubView @chatHandler = new MainChatHandler
+    @chatHandler = new MainChatHandler

   setStickyNotification:->
     # sticky = KD.getSingleton('windowController')?.stickyNotification


home/billing: add space between texts

home/billing: add space between texts

--- client/component-lab/TrialChargeInfo/TrialChargeInfo.coffee
+++ client/component-lab/TrialChargeInfo/TrialChargeInfo.coffee
@@ -55,7 +55,7 @@
       </Col>
       <Col xs={7} className={textStyles.right}>
         <Label size="small">
-          Monthly charge after trial:
+          Monthly charge after trial:
           <strong>${formatNumber pricePerSeat, 2}</strong> per user
         </Label>
       </Col>


new fb handle: s/kodingcom/koding/g

new fb handle: s/kodingcom/koding/g

--- client/Main/CommonViews/footerview.coffee
+++ client/Main/CommonViews/footerview.coffee
@@ -38,7 +38,7 @@

COMMUNITY

         ACTIVITY
         KODING BLOG
-        MEETUPS
+        MEETUPS
         TESTIMONIALS
         BRAND GUIDELINES
         COPYRIGHT/DMCA GUIDELINES
@@ -59,7 +59,7 @@
       <address>#{(new Date).getFullYear()} © Koding, Inc. 358 Brannan Street, San Francisco, CA, 94107</address>

-        TWITTER · FACEBOOK
+        TWITTER · FACEBOOK

     """


WorkspaceSettingsModal: Fix delete workspace.

--- client/Main/workspacesettingspopup.coffee
+++ client/Main/workspacesettingspopup.coffee
@@ -34,20 +34,22 @@
       callback : =>
         @deleteButton.showLoader()

-        KD.remote.api.JWorkspace.deleteById workspace.id, (err)=>
+        KD.remote.api.JWorkspace.deleteById workspace.getId(), (err)=>

           return  if KD.showError err
-
-          { machineUId, rootPath } = workspace.data
+
+          { machineUId, rootPath } = workspace
+          { router, appManager   } = KD.singletons
+
           if deleteRelatedFiles
-            KD.getSingleton('appManager').tell 'IDE', "deleteWorkspaceRootFolder", machineUId, rootPath
+            methodName = 'deleteWorkspaceRootFolder'
+            appManager.tell 'IDE', methodName, machineUId, rootPath

           navItem.destroy()
           @destroy()

-          KD.singletons
-            .router.handleRoute "/IDE/#{workspace.machineLabel}/my-workspace"
-
+          router.handleRoute "/IDE/#{workspace.machineLabel}/my-workspace"
+
     @buttonContainer.addSubView field = new KDCustomHTMLView
         tagName : 'li'
         cssClass : 'delete-files'


Teamwork: Fixed http content warning.

Teamwork: Fixed http content warning.

--- client/Workspace/panes/previewpane.coffee
+++ client/Workspace/panes/previewpane.coffee
@@ -18,15 +18,15 @@

     @container.addSubView @previewer = new PreviewerView viewerOptions

-    @container.addSubView @secureInfo = new KDCustomHTMLView
+    @createSecureWarning()  unless $.cookie("kdproxy-usehttp") is "1"
+
+  createSecureWarning: ->
+    @secureInfo = new KDCustomHTMLView
       tagName  : "div"
       cssClass : "tw-browser-splash"
       partial  : """

You can only preview links starting with "https".
 """

-    @createSecureWarning()  unless $.cookie("kdproxy-usehttp") is "1"
-
-  createSecureWarning: ->
     infoText   = new KDCustomHTMLView
       tagName  : "span"
       partial  : "In order to view non-secure (http) content you can "
@@ -52,6 +52,7 @@
     @secureInfo.addSubView httpLink
     @secureInfo.addSubView separator
     @secureInfo.addSubView infoLink
+    @container.addSubView  @secureInfo

   useHttp: ->
     KD.getSingleton("router").handleRoute "/Activity"


Login: login overlay to take you back instead of activity

--- client/app/MainApp/login/loginview.coffee
+++ client/app/MainApp/login/loginview.coffee
@@ -16,7 +16,6 @@

     handler =(route, event)=>
       stop event
-      log route
       @getSingleton('router').handleRoute route, {entryPoint}

     homeHandler       = handler.bind null, '/'
@@ -303,9 +302,7 @@

   click:(event)->
     if $(event.target).is('.login-screen')
-      @hide =>
-        {entryPoint} = KD.config
-        @getSingleton('router').handleRoute "/Activity", {entryPoint}
+      @hide @getSingleton('router').bound "back"

   animateToForm: (name)->


Update Cypher query syntax to be compatible.

--- community/browser/app/scripts/services/GraphExplorer.coffee
+++ community/browser/app/scripts/services/GraphExplorer.coffee
@@ -43,7 +43,7 @@
         exploreNeighbours: (node) ->
           q = $q.defer()
           Cypher.transaction()
-          .commit("START a = node(#{node.id}) MATCH (a)-[r]-() RETURN r;")
+          .commit("MATCH (a)-[r]-() WHERE id(a)= #{node.id} RETURN r;")
           .then(q.resolve)
           q.promise

@@ -55,8 +55,9 @@
           ids = nodes.map((node) -> node.id)
           Cypher.transaction()
           .commit("""
-            START a = node(#{ids.join(',')}), b = node(#{ids.join(',')})
-            MATCH a -[r]-> b RETURN r;"""
+            MATCH a -[r]-> b WHERE id(a) IN[#{ids.join(',')}]
+            AND id(b) IN[#{ids.join(',')}]
+            RETURN r;"""
           )
           .then(q.resolve)
           q.promise


update MongoManager tests

update MongoManager tests

--- services/track-changes/test/unit/coffee/MongoManager/MongoManagerTests.coffee
+++ services/track-changes/test/unit/coffee/MongoManager/MongoManagerTests.coffee
@@ -97,184 +97,6 @@
 				@callback.calledWith(null, null, @update.v_end).should.equal true

-	# describe "getDocUpdates", ->
-	# 	beforeEach ->
-	# 		@results = [
-	# 			{foo: "mock-update", v: 56, doc_id: 100, project_id: 1},
-	# 			{foo: "mock-update", v: 55, doc_id: 100, project_id: 1},
-	# 			{pack: [ {foo: "mock-update", v: 54, doc_id: 100, project_id: 1},
-	# 				{foo: "mock-update", v: 53, doc_id: 100, project_id: 1},
-	# 				{foo: "mock-update", v: 52, doc_id: 100, project_id: 1} ]
-	# 				, v: 52, doc_id: 100, project_id: 1},
-	# 			{foo: "mock-update", v: 42, doc_id: 100, project_id: 1},
-	# 			{foo: "mock-update", v: 41, doc_id: 100, project_id: 1}
-	# 		]
-	# 		@updates_between = [
-	# 			{foo: "mock-update", v: 55, doc_id: 100, project_id: 1},
-	# 			{foo: "mock-update", v: 54, doc_id: 100, project_id: 1},
-	# 			{foo: "mock-update", v: 53, doc_id: 100, project_id: 1},
-	# 			{foo: "mock-update", v: 52, doc_id: 100, project_id: 1},
-	# 			{foo: "mock-update", v: 42, doc_id: 100, project_id: 1}
-	# 		]
-	# 		@updates_after = [
-	# 			{foo: "mock-update", v: 56, doc_id: 100, project_id: 1},
-	# 			{foo: "mock-update", v: 55, doc_id: 100, project_id: 1},
-	# 			{foo: "mock-update", v: 54, doc_id: 100, project_id: 1},
-	# 			{foo: "mock-update", v: 53, doc_id: 100, project_id: 1},
-	# 			{foo: "mock-update", v: 52, doc_id: 100, project_id: 1},
-	# 			{foo: "mock-update", v: 42, doc_id: 100, project_id: 1}
-	# 		]
-	# 		@db.docHistory = {}
-	# 		@db.docHistory.find = sinon.stub().returns @db.docHistory
-	# 		@db.docHistory.sort = sinon.stub().returns @db.docHistory
-	# 		@db.docHistory.limit = sinon.stub().returns @db.docHistory
-	# 		@db.docHistory.toArray = sinon.stub().callsArgWith(0, null, @results)
-
-	# 		@from = 42
-	# 		@to   = 55
-
-	# 	describe "with a to version", ->
-	# 		beforeEach ->
-	# 			@MongoManager.getDocUpdates @doc_id, from: @from, to: @to, @callback
-
-	# 		it "should find the all updates between the to and from versions", ->
-	# 			@db.docHistory.find
-	# 				.calledWith({
-	# 					doc_id: ObjectId(@doc_id)
-	# 					v: { $gte: @from, $lte: @to }
-	# 				})
-	# 				.should.equal true
-
-	# 		it "should sort in descending version order", ->
-	# 			@db.docHistory.sort
-	# 				.calledWith("v": -1)
-	# 				.should.equal true
-
-	# 		#it "should not limit the results", ->
-	# 		#	@db.docHistory.limit
-	# 		#		.called.should.equal false
-
-	# 		it "should call the call back with the results", ->
-	# 			@callback.calledWith(null, @updates_between).should.equal true
-
-	# 	describe "without a to version", ->
-	# 		beforeEach ->
-	# 			@MongoManager.getDocUpdates @doc_id, from: @from, @callback
-
-	# 		it "should find the all updates after the from version", ->
-	# 			@db.docHistory.find
-	# 				.calledWith({
-	# 					doc_id: ObjectId(@doc_id)
-	# 					v: { $gte: @from }
-	# 				})
-	# 				.should.equal true
-
-	# 		it "should call the call back with the updates", ->
-	# 			@callback.calledWith(null, @updates_after).should.equal true
-
-	# 	describe "with a limit", ->
-	# 		beforeEach ->
-	# 			@MongoManager.getDocUpdates @doc_id, from: @from, limit: @limit = 10, @callback
-
-	# 		it "should limit the results", ->
-	# 			@db.docHistory.limit
-	# 				.calledWith(@limit)
-	# 				.should.equal true
-
-
-	# describe "getDocUpdates", ->
-	# 	beforeEach ->
-	# 		@results = [
-	# 			{foo: "mock-update", v: 56, meta: {end_ts: 110}, doc_id: 100, project_id: 1},
-	# 			{foo: "mock-update", v: 55, meta: {end_ts: 100}, doc_id: 100, project_id: 1},
-	# 			{pack: [
-	# 				{foo: "mock-update", v: 54, meta: {end_ts: 99}, doc_id: 300, project_id: 1},
-	# 				{foo: "mock-update", v: 53, meta: {end_ts: 98}, doc_id: 300, project_id: 1},
-	# 				{foo: "mock-update", v: 52, meta: {end_ts: 97}, doc_id: 300, project_id: 1}	]
-	# 				, v: 52, meta: {end_ts: 100}, doc_id: 300, project_id: 1},
-	# 			{pack: [
-	# 				{foo: "mock-update", v: 54, meta: {end_ts: 103}, doc_id: 200, project_id: 1},
-	# 				{foo: "mock-update", v: 53, meta: {end_ts: 101}, doc_id: 200, project_id: 1},
-	# 				{foo: "mock-update", v: 52, meta: {end_ts: 99}, doc_id: 200, project_id: 1}	]
-	# 				, v: 52, meta: {end_ts: 103}, doc_id: 200, project_id: 1},
-	# 			{foo: "mock-update", v: 42, meta:{end_ts: 90}, doc_id: 100, project_id: 1}
-	# 		]
-	# 		@updates_before = [
-	# 			{foo: "mock-update", v: 55, meta: {end_ts: 100}, doc_id: 100, project_id: 1},
-	# 			{foo: "mock-update", v: 52, meta: {end_ts: 99}, doc_id: 200, project_id: 1},
-	# 			{foo: "mock-update", v: 54, meta: {end_ts: 99}, doc_id: 300, project_id: 1},
-	# 			{foo: "mock-update", v: 53, meta: {end_ts: 98}, doc_id: 300, project_id: 1},
-	# 			{foo: "mock-update", v: 52, meta: {end_ts: 97}, doc_id: 300, project_id: 1},
-	# 			{foo: "mock-update", v: 42, meta: {end_ts: 90}, doc_id: 100, project_id: 1},
-	# 		]
-	# 		@updates_all = [
-	# 			{foo: "mock-update", v: 56, meta: {end_ts: 110}, doc_id: 100, project_id: 1},
-	# 			{foo: "mock-update", v: 54, meta: {end_ts: 103}, doc_id: 200, project_id: 1},
-	# 			{foo: "mock-update", v: 53, meta: {end_ts: 101}, doc_id: 200, project_id: 1},
-	# 			{foo: "mock-update", v: 55, meta: {end_ts: 100}, doc_id: 100, project_id: 1},
-	# 			{foo: "mock-update", v: 52, meta: {end_ts: 99}, doc_id: 200, project_id: 1},
-	# 			{foo: "mock-update", v: 54, meta: {end_ts: 99}, doc_id: 300, project_id: 1},
-	# 			{foo: "mock-update", v: 53, meta: {end_ts: 98}, doc_id: 300, project_id: 1},
-	# 			{foo: "mock-update", v: 52, meta: {end_ts: 97}, doc_id: 300, project_id: 1},
-	# 			{foo: "mock-update", v: 42, meta: {end_ts: 90}, doc_id: 100, project_id: 1}
-	# 		]
-
-
-	# 		@db.docHistory = {}
-	# 		@db.docHistory.find = sinon.stub().returns @db.docHistory
-	# 		@db.docHistory.sort = sinon.stub().returns @db.docHistory
-	# 		@db.docHistory.limit = sinon.stub().returns @db.docHistory
-	# 		@db.docHistory.toArray = sinon.stub().callsArgWith(0, null, @results)
-
-	# 		@before = 101
-
-	# 	describe "with a before timestamp", ->
-	# 		beforeEach ->
-	# 			@MongoManager.getProjectUpdates @project_id, before: @before, @callback
-
-	# 		it "should find the all updates before the timestamp", ->
-	# 			@db.docHistory.find
-	# 				.calledWith({
-	# 					project_id: ObjectId(@project_id)
-	# 					"meta.end_ts": { $lt: @before }
-	# 				})
-	# 				.should.equal true
-
-	# 		it "should sort in descending version order", ->
-	# 			@db.docHistory.sort
-	# 				.calledWith("meta.end_ts": -1)
-	# 				.should.equal true
-
-	# 		it "should not limit the results", ->
-	# 			@db.docHistory.limit
-	# 				.called.should.equal false
-
-	# 		it "should call the call back with the updates", ->
-	# 			@callback.calledWith(null, @updates_before).should.equal true
-
-	# 	describe "without a before timestamp", ->
-	# 		beforeEach ->
-	# 			@MongoManager.getProjectUpdates @project_id, {}, @callback
-
-	# 		it "should find the all updates", ->
-	# 			@db.docHistory.find
-	# 				.calledWith({
-	# 					project_id: ObjectId(@project_id)
-	# 				})
-	# 				.should.equal true
-
-	# 		it "should call the call back with the updates", ->
-	# 			@callback.calledWith(null, @updates_all).should.equal true
-
-	# 	describe "with a limit", ->
-	# 		beforeEach ->
-	# 			@MongoManager.getProjectUpdates @project_id, before: @before, limit: @limit = 10, @callback
-
-	# 		it "should limit the results", ->
-	# 			@db.docHistory.limit
-	# 				.calledWith(@limit)
-	# 				.should.equal true
-
 	describe "backportProjectId", ->
 		beforeEach ->
 			@db.docHistory =
@@ -332,83 +154,3 @@
 		it "should call the callback", ->
 			@callback.called.should.equal true

-	# describe "getDocChangesCount", ->
-	# 	beforeEach ->
-	# 		@db.docHistory =
-	# 			count: sinon.stub().callsArg(1)
-	# 		@MongoManager.getDocChangesCount @doc_id, @callback
-
-	# 	it "should return if there is any doc changes", ->
-	# 		@db.docHistory.count
-	# 			.calledWith({
-	# 				doc_id: ObjectId(@doc_id)
-	# 			})
-	# 			.should.equal true
-
-	# 	it "should call the callback", ->
-	# 		@callback.called.should.equal true
-
-	# describe "getArchivedDocStatus", ->
-	# 	beforeEach ->
-	# 		@db.docHistoryStats =
-	# 			findOne: sinon.stub().callsArg(2)
-	# 		@MongoManager.getArchivedDocStatus @doc_id, @callback
-
-	# 	it "should return if there is any archived doc changes", ->
-	# 		@db.docHistoryStats.findOne
-	# 			.calledWith({
-	# 				doc_id: ObjectId(@doc_id)
-	# 				inS3: {$exists: true}
-	# 			})
-	# 			.should.equal true
-
-	# 	it "should call the callback", ->
-	# 		@callback.called.should.equal true
-
-	# describe "markDocHistoryAsArchived", ->
-	# 	beforeEach ->
-	# 		@update = { _id: ObjectId(), op: "op", meta: "meta", v: "v"}
-	# 		@db.docHistoryStats =
-	# 			update: sinon.stub().callsArg(3)
-	# 		@db.docHistory =
-	# 			remove: sinon.stub().callsArg(1)
-	# 		@MongoManager.markDocHistoryAsArchived @doc_id, @update.v, @callback
-
-	# 	it "should update doc status with inS3 flag", ->
-	# 		@db.docHistoryStats.update
-	# 			.calledWith({
-	# 				doc_id: ObjectId(@doc_id)
-	# 			},{
-	# 				$set : { inS3 : true }
-	# 			})
-	# 			.should.equal true
-
-	# 	it "should remove any other doc changes before last update", ->
-	# 		@db.docHistory.remove
-	# 			.calledWith({
-	# 				doc_id: ObjectId(@doc_id)
-	# 				v: { $lte : @update.v }
-	# 				expiresAt: {$exists : false}
-	# 			})
-	# 			.should.equal true
-
-	# 	it "should call the callback", ->
-	# 		@callback.called.should.equal true
-
-	# describe "markDocHistoryAsUnarchived", ->
-	# 	beforeEach ->
-	# 		@db.docHistoryStats =
-	# 			update: sinon.stub().callsArg(2)
-	# 		@MongoManager.markDocHistoryAsUnarchived @doc_id, @callback
-
-	# 	it "should remove any doc changes inS3 flag", ->
-	# 		@db.docHistoryStats.update
-	# 			.calledWith({
-	# 				doc_id: ObjectId(@doc_id)
-	# 			},{
-	# 				$unset : { inS3 : true, lastVersion: true }
-	# 			})
-	# 			.should.equal true
-
-	# 	it "should call the callback", ->
-	# 		@callback.called.should.equal true


change notifications email top header to the grey we use on the site

--- services/web/app/coffee/Features/Email/Layouts/NotificationEmailLayout.coffee
+++ services/web/app/coffee/Features/Email/Layouts/NotificationEmailLayout.coffee
@@ -311,7 +311,7 @@

-
+


whitespace update

whitespace update

--- src/vector.coffee
+++ src/vector.coffee
@@ -12,6 +12,4 @@
     return -result if @y < 0
     return  result

-
-
 module.exports = Vector


fixing issue with break statements and for loops and closures and the value of 'this'

--- nodes.coffee
+++ nodes.coffee
@@ -1024,7 +1024,8 @@
         for_part:   "$ivar = 0, $lvar = ${svar}.length; $ivar < $lvar; $step_part"
     set_result:     if rvar then @idt() + rvar + ' = []; ' else @idt()
     return_result:  rvar or ''
-    body:           ClosureNode.wrap(body, true) if top_level and @contains (n) -> n instanceof CodeNode
+    if top_level and not @contains((n) -> n.is_pure_statement()) and @contains((n) -> n instanceof CodeNode)
+      body:         ClosureNode.wrap(body, true)
     body:           PushNode.wrap(rvar, body) unless top_level
     if o.returns
       return_result: 'return ' + return_result


Fixes #494: Forcing named classes in CoffeeScript (named functions) ... even in IE.

--- nodes.coffee
+++ nodes.coffee
@@ -928,6 +928,7 @@
     @ensureConstructor name
     @body.spaced = yes
     @body.expressions.unshift @ctor unless @ctor instanceof Code
+    @body.expressions.unshift new Literal "#{name}.name = '#{name}'" if decl
     @body.expressions.push lname
     @addBoundFunctions o


fix broken simplenum regex for rangenodes...


Removing accidental superfluous temp variables.

--- nodes.coffee
+++ nodes.coffee
@@ -1425,7 +1425,7 @@
     rvar          = scope.freeVariable 'result' unless topLevel
     ivar          = if range then name else index
     ivar          = scope.freeVariable 'i' if not ivar or codeInBody
-    nvar          = scope.freeVariable 'i' if name and not range
+    nvar          = scope.freeVariable 'i' if name and not range and codeInBody
     varPart       = ''
     guardPart     = ''
     body          = Expressions.wrap([@body])


More guns

More guns

--- app/js/world.coffee
+++ app/js/world.coffee
@@ -270,10 +270,15 @@
     super e
     if e.keyCode is 32 or e.keyCode is 40 # space or down
       v = Vec.scale @ship.orientation, 5
-      @fireMissile @ship.tip(), Vec.add @ship.velocity, v
+      @fireMissile @ship.tip(), Vec.add(@ship.velocity, v), 3
     if e.keyCode is 88 # x
-      v = Vec.scale @ship.orientation, 3
+      v = Vec.scale @ship.orientation, 5
       @fireMissile @ship.tip(), Vec.add(@ship.velocity, v), 5, true
+    if e.keyCode is 90 # z
+      for angle in [-Math.PI/8, 0, Math.PI/8]
+        v = Rotation.add @ship.orientation, Rotation.fromAngle angle
+        v = Vec.scale v, 5
+        @fireMissile @ship.tip(), Vec.add(@ship.velocity, v), 3

   collisions: (contacts) ->

@@ -291,7 +296,7 @@
         for i in [0..num]
           direction = Rotation.fromAngle Utils.random() * Math.PI * 2
           velocity = Vec.scale direction, 2.5 + Utils.random() * 2.5
-          @fireMissile contact.particle.position, velocity
+          @fireMissile contact.particle.position, velocity, 3

       @explosionAt particle.position
       @removeBody contact.body
@@ -339,7 +344,7 @@
         color: color
         fade: true

-  fireMissile: (position, velocity, size = 2, spawnMore = false) ->
+  fireMissile: (position, velocity, size, spawnMore = false) ->
     missile = new Particle
       lifespan: 1
       position: position


Generate particle contacts before resolving polygon contacts

--- app/js/world.coffee
+++ app/js/world.coffee
@@ -75,6 +75,7 @@
     @postIntegrate()

     @contacts = @narrowPhaseCollisions @broadPhaseCollisions()
+    @particleContacts = @generateParticleContacts()

     if @contacts.length > 0
       for n in [1..@contacts.length*2] # loop contacts * 2 times
@@ -96,8 +97,6 @@

         break if worstSepV > 0
         worst.resolveVelocity dt
-
-    @particleContacts = @generateParticleContacts()

     # post-process collisions:
     @particleCollisions @particleContacts


Handle physics when only one body is present

--- app/js/world.coffee
+++ app/js/world.coffee
@@ -70,6 +70,7 @@
   # (memoized?) AABBs to build quadtree, then iterate and compare bounding boxes
   # for overlap.
   broadPhaseCollisions: ->
+    return [] if @bodies.length < 2
     pairs = []
     for i in [0..(@bodies.length-2)]
       for j in [(i+1)..(@bodies.length-1)]


Update find.coffee

Update find.coffee

Fixed #950

--- lib/find.coffee
+++ lib/find.coffee
@@ -107,7 +107,7 @@
         panelToToggle.show()
         postToggleAction?()

-    atom.commands.add '.editor:not(.mini)',
+    @subscriptions.add atom.commands.add '.editor:not(.mini)',
       'find-and-replace:select-next': (event) ->
         selectNextObjectForEditorElement(this).findAndSelectNext()
       'find-and-replace:select-all': (event) ->


added support for add font as Uint8Array

--- lib/font.coffee
+++ lib/font.coffee
@@ -33,6 +33,11 @@
       @font = TTFFont.fromBuffer src, family
       @subset = new Subset @font
       @registerTTF()
+
+    else if src instanceof Uint8Array
+      @font = TTFFont.fromBuffer (new Buffer src), family
+      @subset = new Subset @font
+      @registerTTF()

     else
       throw new Error 'Not a supported font format or standard PDF font.'


Logger: Create a global Logger called "SystemLogger"

--- packages/rocketchat-logger/server.coffee
+++ packages/rocketchat-logger/server.coffee
@@ -254,6 +254,10 @@

 		return

+
+@SystemLogger = new Logger 'System'
+
+
 processString = (string, date) ->
 	if string[0] is '{'
 		try


minor cson2json command line update

minor cson2json command line update

--- package.cson
+++ package.cson
@@ -148,7 +148,7 @@
 #=====================
     cson2json: '

-        npm run cson2json package.cson > package.json
+        node ./node_modules/cson/bin/cson2json package.cson > package.json
         &&
         node readium-build-tools/optimizePackageJsonScripts.js
     '


After 10 seconds timeout show failed to fetch DB list message and provide a link for retry

--- client/app/Applications/Account.kdapplication/account/accSettingsDevDatabases.coffee
+++ client/app/Applications/Account.kdapplication/account/accSettingsDevDatabases.coffee
@@ -64,16 +64,29 @@

   loadItems:(callback)->

-    @_loaderCount = 2
+    dbTypes = ['mysql', 'mongo']
+    @_loaderCount = dbTypes.length
     @showLazyLoader no

     hideLoaderWhenFinished = =>
       @_loaderCount--
       @hideLazyLoader() if @_loaderCount is 0

-    for dbtype in ['mysql', 'mongo']
+    setTimeout =>
+      for i in dbTypes
+        hideLoaderWhenFinished()
+      @scrollView.addSubView timeout = new KDCustomHTMLView
+        cssClass : "lazy-loader"
+        partial  : "Fetching database list failed. Retry"
+        click    : (ins, event)=>
+          if $(event.target).is "a"
+            @loadItems()
+            timeout.destroy()
+    , 10000
+
+    for dbtype in dbTypes
       @talkToKite
-        method      : @commands[dbtype].fetch
+        method    : @commands[dbtype].fetch
         withArgs  :
           dbUser  : KD.whoami().profile.nickname
       , (err, response)=>
@@ -412,9 +425,6 @@
   #   @modal.destroy()

-
-
-
 class AccountDatabaseListItem extends KDListItemView
   constructor:(options = {},data)->


change util to attackLogger to better describe purpose and free up the util variable

--- lib/http.coffee
+++ lib/http.coffee
@@ -11,7 +11,7 @@
 async = require 'async'

 # Custom Files
-util = require './util'
+attackLogger = require './attackLogger'
 config = require './config'
 blacklist = require './blacklist'

@@ -85,6 +85,6 @@
     module.run req, res
     call()

-  async.forEach http.payloads, kill, -> util.logHTTP module, req
+  async.forEach http.payloads, kill, -> attackLogger.logHTTP module, req

 module.exports = http


Document  variable usage

Document  variable usage

In the Executable Path option, document the usage of the
 variable and give an example

--- lib/init.coffee
+++ lib/init.coffee
@@ -17,7 +17,11 @@
       title: 'Executable Path'
       type: 'string'
       default: 'python3'
-      description: "Path to the executable of python."
+      description: '''Path to the executable of python.
+      An optional `$PROJECT_NAME` variable can be used to resolve the path
+      dynamically depending of the current project's name. For example:
+      `/home/user/.virtualenvs/$PROJECT_NAME/bin/python`.
+      '''
       order: 1
     ignoreFiles:
       type: 'string'


Write files

Write files

--- app/siteshot.coffee
+++ app/siteshot.coffee
@@ -14,7 +14,7 @@
       url = require 'url'

       # Read sitemap.xml
-      parseString @fs.readFileSync(@config.sitemap), (err, result) ->
+      parseString @fs.readFileSync(@config.sitemap), (err, result) =>
         if err?
           throw err
         else
@@ -22,20 +22,24 @@
           routes = _.flatten(_.pluck result.urlset.url, 'loc')

           # Async page loading
-          phantom.create (ph) ->
+          phantom.create (ph) =>
             # Load each route in headless server webkit
-            pageLoad = (route) ->
-              ph.createPage (page) ->
-                page.open route, (status) ->
+            pageLoad = (route) =>
+              ph.createPage (page) =>
+                page.open route, (status) =>
                   if status is 'success'
                     # Get page HTML
-                    page.evaluate (-> document.documentElement.outerHTML), (res) ->
-                      # Set html prefix name
-                      snapPath = if url.parse(route).path is '/'
-                        '/index.html'
+                    page.evaluate (-> document.documentElement.outerHTML), (res) =>
+                      # Set snapshot name
+                      snapPrefix = if url.parse(route).path is '/'
+                        '/index'
                       else
-                        "#{url.parse(route).path}.html"
-                      console.log snapPath
+                        url.parse(route).path
+
+                      # Write snapshot file
+                      @fs.writeFile "#{@config.snapshotDir}#{snapPrefix}.html", res, (err) =>
+                        if err?
+                          throw err

             # Get async each page
             async.each routes, pageLoad, (err) ->


Fix inbox conversation participants (#4765)

* Fix inbox conversation participants
Check user ID against conversation.participants when rendering the link to the sender.

* explicitly cast user IDs to int

--- app/talk/inbox.cjsx
+++ app/talk/inbox.cjsx
@@ -36,7 +36,7 @@
   componentWillMount: ->
     apiClient
       .type 'users'
-      .get @props.conversation.links.users.filter (userId) => userId isnt @props.user.id
+      .get @props.conversation.participant_ids.filter (userId) => userId isnt parseInt(@props.user.id)
       .then (users) =>
         @setState {users}


commit #2

commit #2

--- lib/main.coffee
+++ lib/main.coffee
@@ -59,10 +59,10 @@
   executeFile = (path) ->
     path = "open #{path}" if process.platform == 'darwin'
     exec path, (error, stdout, stderr) ->
-      console.log "stdout: #{stdout}"
-      console.log "stderr: #{stderr}"
+      # console.log "stdout: #{stdout}"
+      # console.log "stderr: #{stderr}"
       if error?
-        console.log "exec error: #{error}"
+        # console.log "exec error: #{error}"
         webContents.send "error","Beim ausführen von #{path}."
       else
         webContents.send "info","Datei #{path} wird ausgeführt."
@@ -78,7 +78,7 @@
     item.on 'done', (e, state) ->
       if (state == "completed")
         webContents.send "info", "Die Datei #{item.getFilename()} wurde erfolgreich heruntergeladen und wird geöffnet."
-        console.log("Download successfully");
+        # console.log("Download successfully");
         executeFile "#{downloadFolder}/#{item.getFilename()}"
         event.preventDefault();
       else


payment: filter "undefined" from address

we're not really using address2 at the moment.  We should only show address2 if it's defined

--- client/app/Applications/Dashboard.kdapplication/views/billingmethodview.coffee
+++ client/app/Applications/Dashboard.kdapplication/views/billingmethodview.coffee
@@ -22,10 +22,11 @@
     if billingInfo
       { cardFirstName, cardLastName, cardNumber, cardMonth, cardYear
         cardType, address1, address2, city, state, zip } = billingInfo
+      address = [address1, address2].filter(Boolean).join ' '
       """

#{cardFirstName} #{cardLastName}

#{cardNumber} - #{cardMonth}/#{cardYear} (#{cardType})

-
#{address1} #{address2}

+
#{address}

#{city} #{state} #{zip}

       """
     else "Enter billing information"


Use new keybinding

Use new keybinding

--- keymaps/codewars.cson
+++ keymaps/codewars.cson
@@ -8,14 +8,5 @@
 # For more detailed documentation see
 # https://atom.io/docs/latest/behind-atom-keymaps-in-depth

-# 'atom-workspace':
-#   'ctrl-alt-c': 'codewars:toggle'
-
-'.platform-darwin, .platform-darwin atom-workspace':
-  'cmd-shift-c': 'codewars:toggle'
-
-'.platform-win32, .platform-win32 atom-workspace':
-  'ctrl-shift-c': 'codewars:toggle'
-
-'.platform-linux, .platform-linux atom-workspace':
-  'ctrl-shift-c': 'codewars:toggle'
+'atom-workspace':
+  'alt-shift-c': 'codewars:toggle'


atom: vcs coloring for tabs and gutter

atom: vcs coloring for tabs and gutter

--- atom/config.cson
+++ atom/config.cson
@@ -53,6 +53,8 @@
 		softTabs: false
 		softWrap: true
 		softWrapHangingIndent: 1
+	"git-diff":
+		showIconsInEditorGutter: true
 	linter: {}
 	"linter-jscs":
 		preset: "jquery"
@@ -68,7 +70,8 @@
 		applications: "Finder,FireFox,Google Chrome,TextEdit"
 	"seti-syntax":
 		themeColor: "Green"
-	tabs: {}
+	tabs:
+		enableVcsColoring: true
 	"vim-mode":
 		useClipboardAsDefaultRegister: false
 		wrapLeftRightMotion: true


Treeview setting of atom

Treeview setting of atom

--- atom/config.cson
+++ atom/config.cson
@@ -27,4 +27,8 @@
     showInfoMessages: true
     statusBar: "Show error of the selected line"
     showHighlighting: false
-  "tree-view": {}
+  "tree-view":
+    hideIgnoredNames: true
+    hideVcsIgnoredFiles: true
+  "linter-rubocop": {}
+  "fuzzy-finder": {}


Atom package config

Atom package config

--- atom/config.cson
+++ atom/config.cson
@@ -3,7 +3,9 @@
     createFileInstantly: true
     defaultInputValue: "Project root"
     fuzzyMatch: true
-  "aligner-scss": {}
+  "aligner-scss":
+    "!-enabled": true
+    "{-enabled": true
   "atom-beautify":
     elm:
       beautify_on_save: true
@@ -65,6 +67,8 @@
       "one-dark-ui"
       "an-old-hope-syntax"
     ]
+  "dash-ui":
+    themeColor: "Orange"
   editor:
     fontFamily: "Monoid HalfTight"
     fontSize: 15
@@ -84,21 +88,33 @@
     cacheDirectory: "/var/folders/pk/gw0kxksn1fx0747r3b7f_5t00000gn/T/elmjutsu26927V4w6JyuRQCfp"
     globalAutocompleteEnabled: true
     hyperclickEnabled: true
+    inferExpectedTypeAtCursorOnTheFly: true
+    showScopeBar: true
     showSidekick: true
+    sidekickPosition: "right"
+    sidekickSize: 300
   "exception-reporting":
     userId: "c251c093-269f-474d-bbb6-7fd145722626"
+  "file-icons":
+    coloured: false
+    onChanges: true
   hasklig:
     fontWeight: "regular"
+  "hey-pane": {}
   "language-elm":
     autocompleteEnabled: false
   linter:
     disabledProviders: [
       "RuboCop"
     ]
+  "linter-elm-make":
+    showInferredTypeAnnotations: true
+    useDatatips: true
   "linter-js-standard": {}
   "linter-rubocop": {}
   "linter-ui-default":
-    panelHeight: 69
+    panelHeight: 179
+    showPanel: true
   minimap:
     plugins:
       "highlight-selected": true
@@ -106,17 +122,24 @@
   "one-light-ui": {}
   "opened-files":
     removeOnClose: true
-  "package-sync": {}
+  "package-sync":
+    createOnChange: true
+    forceOverwrite: true
   "prettier-atom":
     formatOnSaveOptions: {}
   "rails-transporter":
     controllerSpecType: "features"
+  "scroll-through-time":
+    direction: "right"
   "standard-formatter":
     formatOnSave: true
+  tabs:
+    enableVcsColoring: true
   "tree-view":
     alwaysOpenExisting: true
     autoReveal: true
-    hideVcsIgnoredFiles: true
+  "vim-mode-plus":
+    notifiedCoffeeScriptNoLongerSupportedToExtendVMP: true
   welcome:
     showOnStartup: false
   zentabs:


Remove Remember Window settings from main config

--- atom/config.cson
+++ atom/config.cson
@@ -47,11 +47,7 @@
   'hideIgnoredNames': true
 'autosave': {}
 'remember-window':
-  'y': 22
-  'width': 1509
-  'height': 1237
-  'x': 570
-  'treeWidth': 338
+  'storeOutsideMainConfig': true
 'tabs-to-spaces':
   'onSave': 'untabify'
 'git-diff':


Update config.cson for Atom

Update config.cson for Atom

--- atom/config.cson
+++ atom/config.cson
@@ -11,6 +11,9 @@
       "terminal"
       "merge-conflicts"
       "terminal-panel"
+      "git-projects"
+      "outdoor-theme"
+      "error-status"
     ]
     destroyEmptyPanes: false
     themes: [
@@ -41,6 +44,11 @@
     layoutMode: "Compact"
   "autocomplete-plus":
     enableAutoActivation: false
+  "git-projects":
+    rootPath: "~/github;~/src"
+    maxDepth: 1
+  "outdoor-theme":
+    lightLevelToSwitch: 5904360
 ".git-commit.text":
   editor:
     preferredLineLength: 72


Add Atom Elmjutsu next/previous usage keybindings

--- atom/keymap.cson
+++ atom/keymap.cson
@@ -53,6 +53,8 @@
 'atom-text-editor:not([mini])[data-grammar^="source elm"]':
   'ctrl-shift-d': 'elmjutsu:go-to-definition'
   'ctrl-r': 'elmjutsu:go-to-symbol'
+  'alt-j': 'elmjutsu:go-to-next-usage'
+  'alt-k': 'elmjutsu:go-to-previous-usage'
   'alt-i': 'elmjutsu:infer-type'
   'alt-l': 'elmjutsu:lift-to-let'
   'alt-shift-l': 'elmjutsu:surround-with-let'


Exposing the driver

Exposing the driver

--- src/chalcogen.coffee
+++ src/chalcogen.coffee
@@ -2,8 +2,10 @@
 localOpra = require './runner-localOpra'
 saucelabs = require './runner-saucelabs'
 browserstack = require './runner-browserstack'
+driver = require './driver'

 exports.runOpra = localOpra.run
 exports.runSaucelabs = saucelabs.run
 exports.runLocal = local.run
 exports.runBrowserstack = browserstack.run
+exports.driver = driver


KDView: if containment provided in the options, get the object width and height before drag action to prevent function calls while dragging.

--- src/core/view.coffee
+++ src/core/view.coffee
@@ -720,7 +720,10 @@
       dragState = @dragState

       if options.containment
+
         dragState.containment = {}
+        dragState.containment.m = w: @getWidth(), h: @getHeight()
+
         {view} = options.containment

         bounds = if 'string' is typeof view
@@ -809,7 +812,7 @@
       newY = if targetPosY is 'top'  then dragMeta.top  + dragRelPos.y else dragMeta.bottom - dragRelPos.y

       if containment
-        m  = w: @getWidth(), h: @getHeight()  # My sizes
+        m  = containment.m                    # My sizes
         p  = containment.viewBounds           # Containment's sizes
         cp = containment.padding              # Containment paddings
         if newX <= cp.left then newX = cp.left


Message for some tests

Message for some tests

Sometime we don't know which test was failing

#6 Classify and objectify query handlers

--- test/basic.coffee
+++ test/basic.coffee
@@ -36,13 +36,13 @@
   dht1.listen ->
     dht2._sendFindNode '127.0.0.1:' + dht1.address().port, targetNodeId, (err, res) ->
       t.error err
-      t.deepEqual res.id, dht1.nodeId
+      t.deepEqual res.id, dht1.nodeId, "same nodeid"
       t.deepEqual res.nodes.map((node) ->
         node.addr
       ), [
         '255.255.255.255:6969'
         '127.0.0.1:' + dht2.address().port
-      ]
+      ], "same nodes"
       dht1.destroy()
       dht2.destroy()


test/first: remove buffering to make test more robust

The old implementation could have passed the test if the size of the input was
less than the highWaterMark. Read the comment in the test to find out why.

--- test/first.coffee
+++ test/first.coffee
@@ -29,8 +29,16 @@
     LIMIT = 5
     input = [0..100]
     seen = 0
-    _(input).stream().each(-> seen++).first(LIMIT).run (err, result) ->
-      assert.ifError err
-      assert.equal seen, input.length
-      assert.equal result.length, LIMIT
-      done()
+    _(input).stream()
+      # Ensure there's no buffering, otherwise the objects could get past the
+      # each() but be buffered before first() and the test would still past.
+      # Without buffers, we guarantee that every object that goes through
+      # each() goes through first().
+      .defaults(objectMode: true, highWaterMark: 0)
+      .each(-> seen++)
+      .first(LIMIT)
+      .run (err, result) ->
+        assert.ifError err
+        assert.equal seen, input.length
+        assert.equal result.length, LIMIT
+        done()


tests

tests

--- test/index.coffee
+++ test/index.coffee
@@ -120,6 +120,9 @@
       instance.push 'e', 'f'
       assert.equal instance.get('c'), 'd'

+  describe "#insert", ->
+
+
   describe "#update", ->
     it "updates the value of an existing key", ->
       instance.push 'a', 1


remove empty file

remove empty file


Chain callbacks.

Chain callbacks.

--- test/index.coffee
+++ test/index.coffee
@@ -1078,14 +1078,9 @@
       period: SparseReport.DAY

     before (done) ->
-      counter = 0
-      increment = (err, data) ->
-        throw err if err
-        counter++
-        if counter == 2
-          done()
-      Report.remove {}, increment
-      MonthReport.remove {}, increment
+      Report.remove {}, (err) ->
+        throw err if err
+        MonthReport.remove {}, done

     it "should find nothing when empty", (done) ->
       Report.get 'empty', (err, doc) ->


More tests

More tests

Signed-off-by: Ian Macalinao <08370e385f4778cad8fe504ec5445edd3d45bd9a@gmail.com>

--- test/items.coffee
+++ test/items.coffee
@@ -77,6 +77,10 @@
           items.find(where name: "Hextech Gunblade", ap: lt: 66).should.eql [items.list["Hextech Gunblade"]]
           items.find(where name: "Hextech Gunblade", ap: lt: 65).should.eql []

+        it "should work with multiple query filters applied at once", ->
+          items.find(where name: "Hextech Gunblade", ap: lt: 66, gt: 54).should.eql [items.list["Hextech Gunblade"]]
+          items.find(where name: "Hextech Gunblade", ap: lt: 69, gt: 65).should.eql []
+
       describe "withStats", ->

         it "should not include items without the stat", ->


test: update for new Clever demo data

test: update for new Clever demo data

--- test/query.coffee
+++ test/query.coffee
@@ -162,22 +162,22 @@

     it 'exists true with where works', (done) ->
       @clever.School.find().where('name').exists(true).count().exec (err, count) ->
-        assert.equal count, 3
+        assert.equal count, 4
         done()

     it 'exists without args works', (done) ->
       @clever.School.find().where('name').exists().count().exec (err, count) ->
-        assert.equal count, 3
+        assert.equal count, 4
         done()

     it 'exists true works', (done) ->
       @clever.School.find().exists('name', true).count().exec (err, count) ->
-        assert.equal count, 3
+        assert.equal count, 4
         done()

     it 'exists path works', (done) ->
       @clever.School.find().exists('name').count().exec (err, count) ->
-        assert.equal count, 3
+        assert.equal count, 4
         done()

     it 'exists false with where works', (done) ->


js state machine

js state machine

--- test/state.coffee
+++ test/state.coffee
@@ -2,5 +2,5 @@
   callback = Callback {}, "Test Machine", ->
     console.log "testing"

-  equals callback, ! null, "callback shouldn't be null"
+  equals callback != null, true, "callback shouldn't be null"


Express you makes me has a sad

Express you makes me has a sad

--- server/http/middleware/jugglingdb.coffee
+++ server/http/middleware/jugglingdb.coffee
@@ -25,6 +25,9 @@
 	app = http._app # Yeah, this is hackish. NFG
 	root = schema.root
 	routes = {}
+
+	# Gross...
+	app._usedRouter = true

 	for name, Model of schema.models


Remove outdated info

Remove outdated info

--- server/materials/data/level_about.coffee
+++ server/materials/data/level_about.coffee
@@ -1086,7 +1086,7 @@
         module20927_39(),
         label("
Очные занятия
\nПодробная информация
"),
         module16024_1(),
-        label("
Лицей 40
\n
Только для учеников лицея 40
\n
Занятия проходят по средам в 14:30 КЦ.
\n
\n
Новичкам (кто не ходил ни разу и не решал задач заочно) прежде чем приходить на занятие, необходимо зарегистрироваться на сайте и решить хотя бы одну задачу. Исключение — занятие 25 сентября, туда можно приходить всем новичкам вообще.
\n
Те, кто уже был хотя бы один раз, или решил в курсе хотя бы одну задачу заочно, могут приходить каждую среду.
\n
\nПравила получения зачета\n
ННГУ
\n
Только для школьников\n

Занятия проходят по воскресеньям в ННГУ ил. Лобачевского (пр. Гагарина, 23), корп. 2, ауд. 110 и 114.
\n
Берите с собой паспорт или свидетельство о рождении. Если вас не пускает охрана, звоните мне.
\n
\n
Проходит два занятия: с 14.00 до 15.30 и с 15.30 до 17.00.
\n\n
Новичкам (кто не ходил ни разу и не решал задач заочно) прежде чем приходить на занятие, необходимо зарегистрироваться на сайте и решить хотя бы одну задачу. Исключение — занятия для новичков, которые объявляются особо (обычно второе занятие года), туда можно приходить всем новичкам вообще.
\n
Те, кто уже был хотя бы один раз, или решил в курсе хотя бы одну задачу заочно, могут приходить каждое воскресенье на любое из двух занятий.
\n
Чтобы вам точно хватило компьютеров, зарегистрируйтесь на занятие. Без регистрации приходить можно, но если компьютеров хватать не будет, то преимущество будет у тех, кто записался. Каждую неделю надо регистрироваться заново.
\n
Регистрироваться на два занятия нельзя, но те, кто записались на 14.00, могут остаться на второе занятие, если будут места. Те, кто со своими ноутбуками, тоже могут приходить на оба занятия.
\n
\n
\n\n
\n
Контакты
\nПетр Андреевич Калинин\n
\n
e-mail: petr@kalinin.nnov.ru
\n
https://vk.com/petr.kalinin*
\n
+7(910)794-32-07**
\n
Телеграм: +7(910)794-32-07
\n
\nТелеграм:\n
\n
Канал с важными объявлениями, в первую очередь про очные занятия. Рекомендую всем подписаться. (Если ссылка на t.me заблокирована, просто в телеграмме поищите канал «algoprog_news».)
\n\n
\n
Мои контактные данные можно свободно распространять по любым вопросам, связанным с работой курса или с олимпиадным программированием в Нижегородской области вообще.
\n
Если у вас есть какие-то проблемы с работой в курсе, какие-то вопросы и т.п., сразу пишите мне!
\n
* Политика добавления в друзья во вКонтакте: я не возражаю, если вы добавите меня в друзья, но я буду добавлять только тех, кто уже давно и успешно занимается.
\n
** Пожалуйста, звоните по телефону только в экстренных случаях. Если ваш вопрос не очень срочный и может подождать несколько часов, напишите мне любым указанным выше способом — во вКонтакте, в Телеграм или на email.
\n
\n
\n\n
Полезные ссылки
\nСсылка на установщик Free Pascal\n
\nУстановщики питона: сам Python и Wing IDE 101\n
\n\nКлавиатурный тренажер на скорость набора программистского текста.  Ориентировочные результаты: 150 символов/минуту — довольно неплохо, 200 символов/минуту — очень круто. Подробнее.\n\n
\n\n
Текущие олимпиады и т.п.
"),
+        label("
Лицей 40
\n
Только для учеников лицея 40
\n
Занятия проходят по средам в 14:30 КЦ.
\n
\n
Новичкам (кто не ходил ни разу и не решал задач заочно) прежде чем приходить на занятие, необходимо зарегистрироваться на сайте и решить хотя бы одну задачу. Исключение — занятия для новичков, которые объявляются особо (обычно второе занятие года), туда можно приходить всем новичкам вообще.
\n
Те, кто уже был хотя бы один раз, или решил в курсе хотя бы одну задачу заочно, могут приходить каждую среду.
\n
\nПравила получения зачета\n
ННГУ
\n
Только для школьников\n

Занятия проходят по воскресеньям в ННГУ ил. Лобачевского (пр. Гагарина, 23), корп. 2, ауд. 110 и 114.
\n
Берите с собой паспорт или свидетельство о рождении. Если вас не пускает охрана, звоните мне.
\n
\n
Проходит два занятия: с 14.00 до 15.30 и с 15.30 до 17.00.
\n\n
Новичкам (кто не ходил ни разу и не решал задач заочно) прежде чем приходить на занятие, необходимо зарегистрироваться на сайте и решить хотя бы одну задачу. Исключение — занятия для новичков, которые объявляются особо (обычно второе занятие года), туда можно приходить всем новичкам вообще.
\n
Те, кто уже был хотя бы один раз, или решил в курсе хотя бы одну задачу заочно, могут приходить каждое воскресенье на любое из двух занятий.
\n
Чтобы вам точно хватило компьютеров, зарегистрируйтесь на занятие. Без регистрации приходить можно, но если компьютеров хватать не будет, то преимущество будет у тех, кто записался. Каждую неделю надо регистрироваться заново.
\n
Регистрироваться на два занятия нельзя, но те, кто записались на 14.00, могут остаться на второе занятие, если будут места. Те, кто со своими ноутбуками, тоже могут приходить на оба занятия.
\n
\n
\n\n
\n
Контакты
\nПетр Андреевич Калинин\n
\n
e-mail: petr@kalinin.nnov.ru
\n
https://vk.com/petr.kalinin*
\n
+7(910)794-32-07**
\n
Телеграм: +7(910)794-32-07
\n
\nТелеграм:\n
\n
Канал с важными объявлениями, в первую очередь про очные занятия. Рекомендую всем подписаться. (Если ссылка на t.me заблокирована, просто в телеграмме поищите канал «algoprog_news».)
\n\n
\n
Мои контактные данные можно свободно распространять по любым вопросам, связанным с работой курса или с олимпиадным программированием в Нижегородской области вообще.
\n
Если у вас есть какие-то проблемы с работой в курсе, какие-то вопросы и т.п., сразу пишите мне!
\n
* Политика добавления в друзья во вКонтакте: я не возражаю, если вы добавите меня в друзья, но я буду добавлять только тех, кто уже давно и успешно занимается.
\n
** Пожалуйста, звоните по телефону только в экстренных случаях. Если ваш вопрос не очень срочный и может подождать несколько часов, напишите мне любым указанным выше способом — во вКонтакте, в Телеграм или на email.
\n
\n
\n\n
Полезные ссылки
\nСсылка на установщик Free Pascal\n
\nУстановщики питона: сам Python и Wing IDE 101\n
\n\nКлавиатурный тренажер на скорость набора программистского текста.  Ориентировочные результаты: 150 символов/минуту — довольно неплохо, 200 символов/минуту — очень круто. Подробнее.\n\n
\n\n
Текущие олимпиады и т.п.
"),
         module16024_3(),
         module16024_5(),
         module16024_7(),


Tests double server close fixes.

Tests double server close fixes.

--- test/tests.coffee
+++ test/tests.coffee
@@ -64,10 +64,10 @@

   it 'should integrate with an existing io', (done) ->
     io = socketIO port
-    chatServer = new ChatService { io : io }
+    chatServer1 = new ChatService { io : io }
     socket1 = ioClient.connect url1, makeParams(user1)
     socket1.on 'loginConfirmed', (u) ->
-      chatServer.close()
+      chatServer1.close()
       io.close()
       done()

@@ -79,12 +79,12 @@
       done()

   it 'should disconnect on server shutdown', (done) ->
-    chatServer = new ChatService { port : port }
+    chatServer1 = new ChatService { port : port }
     socket1 = ioClient.connect url1, makeParams(user1)
     socket1.on 'loginConfirmed', ->
       socket1.on 'disconnect', () ->
         done()
-      chatServer.close()
+      chatServer1.close()
       chatServer = null

   it 'should reject empty user query', (done) ->
@@ -121,7 +121,7 @@
     r = chatServer.roomManager.getRoom 'roomName'
     expect(r).eql(room)
     expect(r.whitelist.get user1).ok
-    chatServer.close done
+    done()

   it 'should support multiple sockets per user', (done) ->
     chatServer = new ChatService { port : port }


remove log line

remove log line

--- imports/ui/meteor/storage.cjsx
+++ imports/ui/meteor/storage.cjsx
@@ -13,7 +13,6 @@
     usage[instance.meta.storageBucket] ?= []
     usage[instance.meta.storageBucket].push instance.name
   buckets: StorageBuckets?.find({name: {$regex: props.filter or '', $options: 'i'}}, sort: name: 1).map (bucket) ->
-    console.log "==========SIZE=#{bucket.size}"
     _.extend bucket,
       size: pretty bucket.size
       usedBy: usage[bucket.name]


Draggable now broadcasts to interested objects that a draggable has been lifted/dropped.

--- src/draggable.coffee
+++ src/draggable.coffee
@@ -205,6 +205,9 @@
       # Mark the drag as having started
       @dragStarted = true

+      # Broadcast to interested subscribers that this droppable is now in the air
+      @broadcast('start', e)
+
     handleDrag: (e) ->
       @scheduleDrag =>
         # Call any user-supplied drag callback
@@ -228,6 +231,9 @@

       # Call any user-supplied stop callback
       @getConfig().stop?(e)
+
+      # Broadcast to interested subscribers that this droppable has been dropped
+      @broadcast('stop', e)

     #
     # Validators
@@ -248,6 +254,16 @@
     #
     # Helpers
     #
+
+    broadcast: (type, originalEvent) ->
+      # Synthesize a new event with this type
+      event = new jQuery.Event(type)
+
+      # Attach the original mouse event to it
+      event.originalEvent = originalEvent
+
+      # Broadcast!
+      $(jQuery.draggable::).trigger(event, @)

     cancelAnyScheduledDrag: ->
       return unless @scheduledDragId


Fix invalid key for monthly active users

--- src/esa-slack.coffee
+++ src/esa-slack.coffee
@@ -71,7 +71,7 @@
         { title: 'Stars', value: stats.stars }
         { title: 'Daily Active Users', value: stats.daily_active_users }
         { title: 'Weekly Active Users', value: stats.weekly_active_users }
-        { title: 'Monthly Active Users', value: stats.montly_active_users }
+        { title: 'Monthly Active Users', value: stats.monthly_active_users }
       ]
       content.fields = fields.map (item, i) ->
         item.short = true


testing atom.project import

testing atom.project import

--- lib/cargo-view.coffee
+++ lib/cargo-view.coffee
@@ -20,7 +20,7 @@
       @div class: 'message', outlet: 'message'

   initialize: ->
-    @projectPath ?= _.first(atom.workspace.getActivePaneItem().project.getPaths())
+    @projectPath ?= _.first(atom.project.getPaths())
     process.chdir(@projectPath)

     @commandSubscription = atom.commands.add 'atom-workspace',


@rspeer I've never written coffee code before so please go over this, especially how i implemented the card drawing for the reaction to being attacked. I've gotta get going for now, but I'll give this some more thought once I've got time to pour over where the ai.choose code is implemented.

--- cards.coffee
+++ cards.coffee
@@ -2229,6 +2229,33 @@
       state.log("...putting #{order} back on the deck.")
       state.current.draw = order.concat(state.current.draw)
       state.current.setAside = []
+}
+
+# Secret Chamber
+# Initial code by Jorbles
+# This is far from optimal, but I believe it does what the card
+# is supposed to do without breaking any rules. I may have to come
+# back to this when my coffee skills are stronger. And I have a
+# greater understanding of how discards are decided. Ideally, the
+# code for discards should be different depending on the type of
+# attack and the total money already in hand.
+
+makeCard "Secret Chamber", action, {
+  cost: 2
+	isReaction: true
+
+	playEffect: (state) ->
+    		discarded = state.allowDiscard(state.current, Infinity)
+    		state.log("...getting +$#{discarded.length} from the Secret Chamber.")
+    		state.current.coins += discarded.length
+
+	reactToAttack: (state, player) ->
+		state.drawCards(player, 2) #is this correct?
+		#there is probably a better way to write this, but i couldn't find how AIs decide what to discard from Militia, Goons, etc.
+    card = state.current.ai.choose('putOnDeck', state, state.current.hand)
+    state.doPutOnDeck(state.current, card)
+    card = state.current.ai.choose('putOnDeck', state, state.current.hand)
+		state.doPutOnDeck(state.current, card)
 }

 makeCard 'Shanty Town', action, {


Refactor specs gulp task

Refactor specs gulp task

--- gulp/specs.coffee
+++ gulp/specs.coffee
@@ -5,6 +5,6 @@
   gulp.task 'specs', ->
     gulp.src [
       'src/spec_setup.coffee'
-      'src/*.coffee'
+      'src/**/*.coffee'
     ]
     .pipe mocha()


remove auto refresh to try balancing load from MAG queries


is not sy.

is not sy.

--- scripts/google-images.coffee
+++ scripts/google-images.coffee
@@ -34,7 +34,7 @@
   robot.respond /(iy|is) (.*)/i, (msg) ->
     exSerchType = ""
     if msg.match[1] == "iy" then exSerchType = "野球"
-    else if msg.match[1] == "sy" then exSerchType = "サッカー"
+    else if msg.match[1] == "is" then exSerchType = "サッカー"
     imageMe msg, msg.match[2] + " " + exSerchType, (url) ->
       msg.send url


don't save dispatcher options beyond the constructor

--- lib/dispatcher.coffee
+++ lib/dispatcher.coffee
@@ -11,16 +11,19 @@
 class Dispatcher

   # Initializer
-  constructor: (@options) ->
+  constructor: (options) ->
     @deps = {}
-    @_db = db @options.db_index
-    @_req = db @options.db_index
-    @_res = db @options.db_index
-    @_test_mode = @options.test_mode
-    @_verbose = @options.verbose
-    @_idle_timeout = @options.idle_timeout ? (if @_test_mode then 500 else 10000)
-    @_audit_stream = @options.audit
-    @_control_channel = _('control').namespace @options.db_index
+    @_test_mode = options.test_mode
+    @_verbose = options.verbose
+    @_idle_timeout = options.idle_timeout ? (if @_test_mode then 500 else 10000)
+    @_audit_stream = options.audit
+    {db_index} = options
+    @_db = db db_index
+    @_req = db db_index
+    @_res = db db_index
+    @_control_channel = _('control').namespace db_index
+    @_responses_channel = _('responses').namespace db_index
+    @_requests_channel = _('requests').namespace db_index
     @_count = {}
     @_state = {}
     @_cycles = {}
@@ -30,8 +33,8 @@
   listen: ->
     @_req.on 'message', (ch, str) => @_requested str
     @_res.on 'message', (ch, str) => @_responded str
-    @_req.subscribe _('requests').namespace(@options.db_index)
-    @_res.subscribe _('responses').namespace(@options.db_index)
+    @_req.subscribe @_responses_channel
+    @_res.subscribe @_responses_channel

   # Send quit signals to the work queues.
   quit: ->


Add the default metadata read on the model with convention => modelName.property in the

--- app/lib/view_helper.coffee
+++ app/lib/view_helper.coffee
@@ -43,8 +43,13 @@
 # Get the metadatad for a property.
 getMetadataFor = (property, context)->
   metadata = {}
-  if context? and context['metadatas']?
-    md = context['metadatas'][property]
+  if context?
+    if context['metadatas']?
+      md = context['metadatas'][property]
+    if not md?
+      domainOfModel = require('./domains-definition')[context['modelName']];
+      if domainOfModel?
+        md = {metadata : domainOfModel[property]}
     if md?
       # First copy the "globals" metadata
       _.extend(metadata, md.metadata) if md.metadata?


Rename pet name categories

Rename pet name categories

--- app/lib/world/names.coffee
+++ app/lib/world/names.coffee
@@ -145,21 +145,15 @@
     'Ofgar'
     'Randall'
   ]
-  'Raven': [
+  'Raven Pet': [
     # Animal
-    'Baltimore'
-    'Columbia'
-    'Dawnstar'
     'Nevermore'
   ]
-  'Cougar': [
+  'Cougar Pet': [
     # Animal
-    'Guenhwyvar'
     'Kitty'
-    'Shasta'
-    'Simbia'
-  ]
-  'Frog': [
+  ]
+  'Frog Pet': [
     # Animal
     'Bighead'
     'Bufo'


Fix request sended before join

Fix request sended before join

--- app/src/m2fa/client.coffee
+++ app/src/m2fa/client.coffee
@@ -47,15 +47,15 @@
     @ws.onclose = _.bind(@_onClose,@)

   _leaveRoom: () ->
-    @_send JSON.stringify(type: 'leave')
+    @ws.send JSON.stringify(type: 'leave')
     @ws.close()
     @ws = null
     @_connectionPromise = null
     @emit 'm2fa.room.left'

   _onOpen: (e) ->
-    @_send JSON.stringify(type: 'join', room: @pairingId)
-    @_send JSON.stringify(type: 'repeat')
+    @ws.send JSON.stringify(type: 'join', room: @pairingId)
+    @ws.send JSON.stringify(type: 'repeat')
     @emit 'm2fa.room.joined'

   _onMessage: (e) ->
@@ -87,7 +87,7 @@

   # Sent by mobile clients to request chrome application to repeat their 'request' message.
   _onRepeat: (data) ->
-    @_send(@_lastRequest)
+    @ws.send(@_lastRequest)

   # Sent by mobile clients to indicate the chrome application that one client is able to handle the 'request' message.
   _onAccept: (data) ->


Simplified DSL processing

Simplified DSL processing

--- lib/json_utils.coffee
+++ lib/json_utils.coffee
@@ -72,34 +72,16 @@
         return callback(err) if err
         callback(null, result)

-  @renderJSONDSL = (model, template, options, callback) ->
-    (callback = options; options = {}) if arguments.length is 3
-
-    queue = new Queue()
+  @renderJSONDSL = (model, dsl, options, callback) ->
+    (callback = options; options = {}) if arguments.length is 3
+
+    queue = new Queue(1)
     result = {}
-    if template.$select
-      queue.defer (callback) ->
-        JSONUtils.renderJSONKeys model, template.$select, options, (err, related_json) ->
-          return callback(err) if err
-          result = related_json
-          callback()
-
-    for key, args of template
-      continue if key is '$select'
-
+    for key, args of dsl
       do (key, args) ->
         field = if args.key then args.key else key
-        relation = model.relation(field)
-
-        # full_name:      'name'
-        if _.isString(args)
-          queue.defer (callback) ->
-            JSONUtils.renderJSONKey model, args, options, (err, value) ->
-              return callback(err) if err
-              result[key] = value
-              callback()
-
-        else if relation
+
+        if relation = model.relation(field)
           # classroom:      {$select: ['id', 'name']}                     -> dsl
           # a_class:        {key: 'classroom', $select: ['id', 'name']}   -> dsl
           # classroom:      {$query: {year: '2012'}}                      -> query
@@ -115,44 +97,47 @@
 #                JSONUtils.renderJSONFunction related_model, key, args, options, (err, json) ->
 #                  result[key] = json
 #                  callback()
+            if args.$query
+              query = args.$query
+            else if args.$count
+              query = args
+            else if args.template
+              if _.isObject(args.template) and not _.isFunction(args.template)
+                query = args.template
+              else
+                template = args.template
+            else
+              template = _.extend({}, args)
+              delete template['key']

             # query
-            if args.$query
-              return callback("Template $query provided for a field that isn't a relation: #{field}") unless relation
-              relation.cursor(model, field, args.$query).toJSON (err, value) ->
+            if query
+              relation.cursor(model, field, query).toJSON (err, value) ->
                 return callback(err) if err
                 result[key] = value
                 callback()

             # template
-            else if args.template
-              return callback("Template provided for a field that isn't a relation: #{field}") unless relation
-              if _.isObject(args.template) and not _.isFunction(args.template)
-                relation.cursor(model, field, args.template).toJSON (err, value) ->
-                  return callback(err) if err
-                  result[key] = value
+            else
+              model.get field, (err, related_model) ->
+                JSONUtils.renderJSON related_model, template, options, (err, json) ->
+                  result[key] = json
                   callback()

-              else
-                model.get field, (err, related_model) ->
-                  render_args = _.extend({}, args)
-                  delete render_args['key']; delete render_args['template']
-                  JSONUtils.renderJSON related_model, args.template, render_args, (err, json) ->
-                    result[key] = json
-                    callback()
-
-            # dsl
-            else
-              return callback("Template provided for a field that isn't a relation: #{field}") unless relation
-              model.get field, (err, related_model) ->
-                return callback(err) if err
-                # Clone the args so we don't clobber the key if we're using it for more than one model
-                render_args = _.extend({}, args)
-                delete render_args['key']
-                JSONUtils.renderJSON related_model, render_args, (err, value) ->
-                  return callback(err) if err
-                  result[key] = value
-                  callback()
+        else if key is '$select'
+          queue.defer (callback) ->
+            JSONUtils.renderJSONKeys model, args, options, (err, related_json) ->
+              return callback(err) if err
+              _.extend(result, related_json)
+              callback()
+
+        # full_name:      'name'
+        else if _.isString(args)
+          queue.defer (callback) ->
+            JSONUtils.renderJSONKey model, args, options, (err, value) ->
+              return callback(err) if err
+              result[key] = value
+              callback()

         # can_delete: (photo, options, callback) ->
         else if _.isFunction(args)
@@ -169,7 +154,8 @@
             model[args.fn].apply(model, fn_args)

         else
-          throw "Unknown DSL action: #{key} #{util.inspect(args)}"
+          console.trace "Unknown DSL action: #{key}: #{util.inspect(args)}"
+          return callback(new Error "Unknown DSL action: #{key}: #{util.inspect(args)}")

     queue.await (err) ->
       return callback(err) if err


:shirt: Fix linter warning

:shirt: Fix linter warning

- Unused variable decorate.

--- lib/jumpy-view.coffee
+++ lib/jumpy-view.coffee
@@ -285,7 +285,7 @@
         marker = editor.markScreenRange range, invalidate: 'never'
         beacon = document.createElement 'span'
         beacon.classList.add 'beacon'
-        decoration = editor.decorateMarker marker,
+        editor.decorateMarker marker,
             item: beacon,
             type: 'overlay'
         setTimeout ->


Refactor patterns matching Jison Lex regexes

--- grammars/jisonlex.cson
+++ grammars/jisonlex.cson
@@ -50,34 +50,32 @@
       {include: "source.jison#comments"}

       {
-        begin: "^(?i)([a-z_][a-z0-9_-]*)(?=\\s|$)"
-        beginCaptures:
-          1:
-            name: "entity.name.function.jisonlex"
-        end: "$"
-        name: "meta.definition.jisonlex"
+        name:  "meta.definition.jisonlex"
+        begin: "\\b[\\p{Alpha}_](?:[\\w-]*\\w)?\\b"
+        end:   "$"
+        beginCaptures: 0: name: "entity.name.variable.jisonlex"
         patterns: [
           {include: "source.jison#comments"}
           {
-            include: "#regexp"
-          }
-        ]
-      }
-      {
-        begin: "^(%[sx])(?=\\s|$)"
-        beginCaptures:
-          1:
-            name: "punctuation.definition.start-condition.jisonlex"
-        end: "$"
-        name: "meta.start-condition.jisonlex"
+            name:  "string.regexp.jisonlex"
+            begin: "(?=\\S)"
+            end:   "(?=\\s)"
+            patterns: [include: "#regexp"]
+          }
+        ]
+      },{
+        name:  "meta.start-condition.jisonlex"
+        begin: "%[sx]\\b"
+        end:   "$"
+        beginCaptures: 0: name: "keyword.other.start-condition.jisonlex"
         patterns: [
           {include: "source.jison#comments"}
           {
-            match: "(?i)[a-z_][a-z0-9_-]*"
-          }
-          {
+            name:  "entity.name.function.jisonlex"
+            match: "\\b[\\p{Alpha}_](?:[\\w-]*\\w)?\\b"
+          },{
+            name:  "invalid.illegal.jisonlex"
             match: "\\S"
-            name: "invalid.illegal.jisonlex"
           }
         ]
       }
@@ -93,40 +91,51 @@
     patterns: [
       {include: "source.jison#comments"}
       {
-        begin: "^(?!$)"
-        end: "$"
-        name: "meta.rule.jisonlex"
-        patterns: [
-          {
-            begin: "(?i)^(<(?:(?:[a-z_][a-z0-9_-]*,)*[a-z_][a-z0-9_-]|\\*)>)?(?:(<<EOF>>)(\\s*))?(?=\\S)"
-            beginCaptures:
-              1:
-                name: "keyword.other.start-condition.jisonlex"
-              2:
-                name: "keyword.operator.eof.jisonlex"
-              3:
-                name: "invalid.illegal.regexp.jisonlex"
-            comment: "rule pattern"
-            end: "(?=\\s)|$"
-            patterns: [
-              {
-                include: "#regexp"
-              }
-            ]
-          },{
-            name:  "meta.rule.action.jisonlex"
-            begin: "(?=%\\{)"
-            end:   "(?<=%\\})"
-            patterns: [include: "source.jison#user_code_blocks"]
-          },{
-            begin: "(?=\\S)"
-            comment: "TODO: eol should override embedded scopes"
-            end: "$"
-            name: "meta.rule.action.jisonlex"
-            patterns: [
-              {include: "source.js"}
-            ]
-          }
+        name:  "meta.start-conditions.jisonlex"
+        begin: "(?:^|(?<=%\\}))<(?!<EOF>>)"
+        end:   ">"
+        beginCaptures: 0: name: "punctuation.definition.start-conditions.begin.jisonlex"
+        endCaptures:   0: name: "punctuation.definition.start-conditions.end.jisonlex"
+        patterns: [
+          {
+            name:  "keyword.other.jisonlex"
+            match: "\\bINITIAL\\b"
+          },{
+            name:  "entity.name.function.jisonlex"
+            match: "\\b[\\p{Alpha}_](?:[\\w-]*\\w)?\\b"
+          },{
+            name:  "punctuation.separator.start-condition.jisonlex"
+            match: ","
+          },{
+            name:  "keyword.other.any-start-condition.jisonlex"
+            match: "(?<=<)\\*(?=>)"
+          },{
+            name:  "invalid.illegal.jisonlex"
+            match: "."
+          }
+        ]
+      },{
+        name:  "meta.rule.action.jisonlex"
+        begin: "(?=%\\{)"
+        end:   "(?<=%\\})"
+        patterns: [include: "source.jison#user_code_blocks"]
+      },{
+        name:  "string.regexp.jisonlex"
+        begin: "(?:^|(?<=>|%\\}))(?=\\S)"
+        end:   "(?=\\s|%\\{)"
+        patterns: [include: "#regexp"]
+      },{
+        name:  "meta.rule.action.jisonlex"
+        begin: "(?=\\S)"
+        end:   "((//).*)?(?=$)"
+        endCaptures:
+          1: name: "comment.line.double-slash.js"
+          2: name: "punctuation.definition.comment.js"
+        # TODO: See the arrow (->) action in the Jison grammar for what’s going
+        # on with the duplicated JavaScript comment patterns.
+        patterns: [
+          {include: "source.jison#include_declarations"}
+          {include: "source.js"}
         ]
       }
     ]
@@ -136,138 +145,132 @@
       {include: "source.js"}
     ]

-  re_escape:
-    match: "\\\\(?i:[0-9]{1,3}|x[0-9a-f]{1,2}|.)"
-    name: "constant.character.escape.jisonlex"
+
+  name_uses:
+    patterns: [
+      {
+        name:  "variable.other.jisonlex"
+        match: "\\{[\\p{Alpha}_](?:[\\w-]*\\w)?\\}"
+      }
+    ]
+
+  # TODO: Can the regexp patterns be replaced with language-regexp
+  # (https://github.com/Alhadis/language-regexp) and some injections?
   regexp:
-    begin: "\\G(?=\\S)(\\^)?"
-    captures:
-      1:
-        name: "keyword.control.anchor.regexp.jisonlex"
-    end: "(\\$)?(?:(?=\\s)|$)"
-    name: "string.regexp.jisonlex"
-    patterns: [
-      {
-        include: "#subregexp"
-      }
-    ]
-  subregexp:
-    patterns: [
-      {
-        include: "#re_escape"
-      }
-      {
-        begin: "(\\[)(\\^)?-?"
+    patterns: [
+      # Comments are allowed in Jison Lex regex patterns.
+      {include: "source.jison#comments"}
+      # These patterns are supported by JavaScript.
+      {
+        name:  "keyword.other.character-class.any.regexp.jisonlex"
+        match: "\\."
+      },{
+        name:  "keyword.other.anchor.word-boundary.regexp.jisonlex"
+        match: "\\\\b"
+      },{
+        name:  "keyword.other.anchor.non-word-boundary.regexp.jisonlex"
+        match: "\\\\B"
+      },{
+        name:  "keyword.other.anchor.start-of-input.regexp.jisonlex"
+        match: "\\^"
+      },{
+        name:  "keyword.other.anchor.end-of-input.regexp.jisonlex"
+        match: "\\$"
+      },{
+        name:  "keyword.other.back-reference.regexp.jisonlex"
+        match: "\\\\[1-9]\\d*"
+      },{
+        name:  "keyword.operator.quantifier.regexp.jisonlex"
+        match: "(?:[+*?]|\\{(?:\\d+(?:,(?:\\d+)?)?|,\\d+)\\})\\??"
+      },{
+        name:  "keyword.operator.alternation.regexp.jisonlex"
+        match: "\\|"
+      },{
+        name:  "meta.non-capturing.group.regexp.jisonlex"
+        begin: "\\(\\?:"
+        end:   "\\)"
+        beginCaptures: 0: name: "punctuation.definition.group.begin.regexp.jisonlex"
+        endCaptures:   0: name: "punctuation.definition.group.end.regexp.jisonlex"
+        patterns: [include: "#regexp"]
+      },{
+        name:  "meta.lookahead.assertion.regexp.jisonlex"
+        begin: "\\(\\?="
+        end:   "\\)"
+        beginCaptures: 0: name: "punctuation.definition.group.begin.regexp.jisonlex"
+        endCaptures:   0: name: "punctuation.definition.group.end.regexp.jisonlex"
+        patterns: [include: "#regexp"]
+      },{
+        name:  "meta.negative.lookahead.assertion.regexp.jisonlex"
+        begin: "\\(\\?!"
+        end:   "\\)"
+        beginCaptures: 0: name: "punctuation.definition.group.begin.regexp.jisonlex"
+        endCaptures:   0: name: "punctuation.definition.group.end.regexp.jisonlex"
+        patterns: [include: "#regexp"]
+      },{
+        name:  "meta.group.regexp.jisonlex"
+        begin: "\\("
+        end:   "\\)"
+        beginCaptures: 0: name: "punctuation.definition.group.begin.regexp.jisonlex"
+        endCaptures:   0: name: "punctuation.definition.group.end.regexp.jisonlex"
+        patterns: [include: "#regexp"]
+      },{
+        name:  "constant.other.character-class.set.regexp.jisonlex"
+        begin: "(\\[)(\\^)?"
+        end:   "\\]"
         beginCaptures:
-          1:
-            name: "punctuation.definition.character-class.set.jisonlex"
-          2:
-            name: "keyword.operator.negation.regexp.jisonlex"
-        end: "-?(\\])"
+          1: name: "punctuation.definition.character-class.begin.regexp.jisonlex"
+          2: name: "keyword.operator.negation.regexp.jisonlex"
         endCaptures:
-          1:
-            name: "punctuation.terminator.character-class.set.jisonlex"
-        name: "constant.other.character-class.set.jisonlex"
-        patterns: [
-          {
-            include: "#re_escape"
-          }
-          {
-            captures:
-              1:
-                name: "invalid.illegal.regexp.jisonlex"
-            match: "\\[:(?:(?:alnum|alpha|blank|cntrl|x?digit|graph|lower|print|punct|space|upper)|(.*?)):\\]"
-            name: "constant.other.character-class.set.jisonlex"
-          }
-        ]
-      }
-      {
-        match: "(?i){[a-z_][a-z0-9_-]*}"
-        name: "variable.other.jisonlex"
-      }
-      {
-        begin: "\\{"
-        end: "\\}"
-        name: "keyword.operator.quantifier.regexp.jisonlex"
-        patterns: [
-          {
-            match: "(?<=\\{)[0-9]*(?:,[0-9]*)?(?=\\})"
-          }
-          {
-            comment: "{3} counts should only have digit[,digit]"
-            match: "[^}]"
-            name: "invalid.illegal.regexp.jisonlex"
-          }
-        ]
-      }
-      {
-        begin: "\""
-        end: "\""
-        name: "string.quoted.double.regexp.jisonlex"
-        patterns: [
-          {
-            include: "#re_escape"
-          }
-        ]
-      }
-      {
-        begin: "([*+?])(?=[*+?])"
-        beginCaptures:
-          1:
-            name: "keyword.operator.quantifier.regexp.jisonlex"
-        comment: "make ** or +? or other combinations illegal"
-        end: "(?=[^*+?])"
-        patterns: [
-          {
-            match: "."
-            name: "invalid.illegal.regexp.jisonlex"
-          }
-        ]
-      }
-      {
-        match: "[*+?]"
-        name: "keyword.operator.quantifier.regexp.jisonlex"
-      }
-      {
-        comment: "<<EOF>> is handled in the rule pattern"
+          0: name: "punctuation.definition.character-class.end.regexp.jisonlex"
+        patterns: [
+          {include: "#name_uses"}
+          {include: "#regexp_character_class"}
+        ]
+      }
+      {include: "#regexp_character_class"}
+      # These patterns are specific to Jison Lex.
+      {include: "#name_uses"}
+      {include: "source.jison#quoted_strings"}
+      {
+        name:  "keyword.other.eof.regexp.jisonlex"
         match: "<<EOF>>"
-        name: "invalid.illegal.regexp.jisonlex"
-      }
-      {
-        begin: "(\\()"
-        beginCaptures:
-          1:
-            name: "punctuation.definition.group.regexp.jisonlex"
-        end: "(\\))|(?=\\s)|$(?#end on whitespace because regex does)"
-        endCaptures:
-          1:
-            name: "punctuation.terminator.group.regexp.jisonlex"
-        name: "meta.group.regexp.jisonlex"
-        patterns: [
-          {
-            match: "/"
-            name: "invalid.illegal.regexp.jisonlex"
-          }
-          {
-            include: "#subregexp"
-          }
-        ]
-      }
-      {
-        begin: "(/)"
-        beginCaptures:
-          1:
-            name: "keyword.operator.trailing-match.regexp.jisonlex"
-        comment: "detection of multiple trailing contexts"
-        end: "(?=\\s)|$"
-        patterns: [
-          {
-            match: "/|\\$(?!\\S)"
-            name: "invalid.illegal.regexp.jisonlex"
-          }
-          {
-            include: "#subregexp"
-          }
-        ]
-      }
-    ]
+      },{
+        name:  "keyword.operator.negative.lookahead.regexp.jisonlex"
+        match: "/!"
+      },{
+        name:  "keyword.operator.lookahead.regexp.jisonlex"
+        match: "/"
+      }
+    ]
+
+  regexp_character_class:
+    patterns: [
+      # Character classes from
+      # https://github.com/atom/language-javascript/blob/master/grammars/regular%20expressions%20(javascript).cson
+      # and
+      # https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp
+      {
+        name:  "constant.character.escape.character-class.word.regexp.jisonlex"
+        match: "\\\\w"
+      },{
+        name:  "constant.character.escape.character-class.non-word.regexp.jisonlex"
+        match: "\\\\W"
+      },{
+        name:  "constant.character.escape.character-class.space.regexp.jisonlex"
+        match: "\\\\s"
+      },{
+        name:  "constant.character.escape.character-class.non-space.regexp.jisonlex"
+        match: "\\\\S"
+      },{
+        name:  "constant.character.escape.character-class.digit.regexp.jisonlex"
+        match: "\\\\d"
+      },{
+        name:  "constant.character.escape.character-class.non-digit.regexp.jisonlex"
+        match: "\\\\D"
+      },{
+        name:  "constant.character.escape.character-class.control.regexp.jisonlex"
+        match: "\\\\c[A-Z]"
+      }
+      # https://github.com/atom/language-javascript/blob/master/grammars/javascript.cson
+      {include: "source.js#string_escapes"}
+    ]


Fixing checkout controller spec

Fixing checkout controller spec

--- spec/javascripts/unit/darkswarm/controllers/checkout/checkout_controller_spec.js.coffee
+++ spec/javascripts/unit/darkswarm/controllers/checkout/checkout_controller_spec.js.coffee
@@ -3,7 +3,7 @@
   scope = null
   Checkout = null
   CurrentUser = null
-  CurrentHubMock =
+  CurrentHubMock =
     hub:
       id: 1
   storage = null
@@ -13,9 +13,9 @@
     angular.module('Darkswarm').value('user', {})
     angular.module('Darkswarm').value('currentHub', {id: 1})
     module ($provide)->
-      $provide.value "CurrentHub", CurrentHubMock
+      $provide.value "CurrentHub", CurrentHubMock
       null
-    Checkout =
+    Checkout =
       submit: ->
       navigate: ->
       bindFieldsToLocalStorage: ->
@@ -25,17 +25,18 @@
         user_id: 1
       secrets:
         card_number: "this is a secret"
-
+
   describe "with user", ->
     beforeEach ->
       inject ($controller, $rootScope, _storage_) ->
         storage = _storage_
         spyOn(storage, "bind").andCallThrough()
-        scope = $rootScope.$new()
-        ctrl = $controller 'CheckoutCtrl', {$scope: scope, Checkout: Checkout, CurrentUser: {}}
+        scope = $rootScope.$new()
+        CurrentUser = { id: 1 }
+        ctrl = $controller 'CheckoutCtrl', {$scope: scope, Checkout: Checkout, CurrentUser: CurrentUser }

     describe "submitting", ->
-      event =
+      event =
         preventDefault: ->

       beforeEach ->
@@ -57,14 +58,15 @@

     describe "Local storage", ->
       it "binds to localStorage when given a scope", ->
-        prefix = "order_#{scope.order.id}#{CurrentUser?.id}#{CurrentHubMock.hub.id}"
+        prefix = "order_#{scope.order.id}#{CurrentUser.id or ""}#{CurrentHubMock.hub.id}"
+        console.log prefix
         field = scope.fieldsToBind[0]
         expect(storage.bind).toHaveBeenCalledWith(scope, "Checkout.order.#{field}", {storeName: "#{prefix}_#{field}"})
         expect(storage.bind).toHaveBeenCalledWith(scope, "Checkout.ship_address_same_as_billing", {storeName: "#{prefix}_sameasbilling", defaultValue: true})

       it "it can retrieve data from localstorage", ->
-        prefix = "order_#{scope.order.id}#{CurrentUser?.id}#{CurrentHubMock.hub.id}"
-        expect(localStorage.getItem("#{prefix}_email")).toMatch "public"
+        prefix = "order_#{scope.order.id}#{CurrentUser.id or ""}#{CurrentHubMock.hub.id}"
+        expect(localStorage.getItem("#{prefix}_email")).toMatch "public"

       it "does not store secrets in local storage", ->
         Checkout.secrets =
@@ -76,8 +78,8 @@
   describe "without user", ->
     beforeEach ->
       inject ($controller, $rootScope) ->
-        scope = $rootScope.$new()
-        ctrl = $controller 'CheckoutCtrl', {$scope: scope, Checkout: Checkout, CurrentUser: undefined}
+        scope = $rootScope.$new()
+        ctrl = $controller 'CheckoutCtrl', {$scope: scope, Checkout: Checkout, CurrentUser: {}}

     it "is disabled", ->
       expect(scope.enabled).toEqual false


need to destructure the #bash() method

need to destructure the #bash() method

--- kites/sharedHosting/api.coffee
+++ kites/sharedHosting/api.coffee
@@ -13,7 +13,7 @@
 Kite      = require 'kite-amqp'
 mkdirp    = require 'mkdirp'
 coffee    = require 'coffee-script'
-bash      = require 'koding-bash-user-glue'
+{bash}    = require 'koding-bash-user-glue'

 createTmpDir = require './createtmpdir'