Typo.

Typo.

--- src/transport.coffee
+++ src/transport.coffee
@@ -77,7 +77,7 @@
                 if @recv
                     @to_tref = setTimeout(x, keepalive_delay)
                     @recv.doSendFrame("h")
-            # We have a timeout for konqeror - 35 seconds.
+            # We have a timeout for konqueror - 35 seconds.
             @to_tref = setTimeout(x, keepalive_delay)
         return


Use consistent, easier to understand HipChat icons.

--- src/stashboard.coffee
+++ src/stashboard.coffee
@@ -23,12 +23,9 @@

 class Stashbot
     #hipchat icons, customize away
-    status_up: ['(awyeah)', '(content)', '(freddie)', '(fuckyeah)', '(goodnews)', '(thumbsup)', '(successful)', '(success)',
-    '(yey)']
-    status_down: ['(boom)', '(cerealspit)', '(jackie)', '(ohcrap)', '(omg)', '(poo)', '(rageguy)', '(thumbsdown)', '(tableflip)',
-    '(wtf)']
-    status_warn:  ['(dumb)', '(derp)', '(embarassed)', '(facepalm)', '(grumpycat)', '(okay)', '(oops)', '(pokerface)', '(sadpanda)',
-     '(shrug)', '(sadtroll)', '(wat)']
+    statusUp: '(successful)'
+    statusDown: '(failed)'
+    statusWarn: '(unknown)'

     constructor: (@robot, cb) ->
         if process.env.HUBOT_STASHBOARD_URL? and process.env.HUBOT_STASHBOARD_TOKEN? and process.env.HUBOT_STASHBOARD_SECRET?
@@ -53,9 +50,9 @@
             for service in status.services
                 service_msg = service.name + ' '
                 switch service['current-event'].status.id
-                    when 'down' then service_msg += @status_down[Math.floor(Math.random() * @status_down.length)]
-                    when 'up' then  service_msg += @status_up[Math.floor(Math.random() * @status_up.length)]
-                    when 'warn' then service_msg += @status_warn[Math.floor(Math.random() * @status_warn.length)]
+                    when 'down' then service_msg += @statusDown
+                    when 'up' then  service_msg += @statusUp
+                    when 'warn' then service_msg += @statusWarn
                 cb(null, service_msg)

     set_status: (search_string, status, message, cb) ->


Check if result is null before looking for _next

--- admin/static/coffee/dataexplorer.coffee
+++ admin/static/coffee/dataexplorer.coffee
@@ -2536,7 +2536,7 @@

                     if @index is @queries.length # @index was incremented in execute_portion
-                        if typeof cursor._next is 'function' # If the result is a cursor
+                        if cursor? and typeof cursor._next is 'function' # If the result is a cursor
                             @state.cursor = cursor
                             @state.cursor.next get_result_callback
                         else


Suggestions work in the dataexplorer

Suggestions work in the dataexplorer

--- admin/static/coffee/dataexplorer.coffee
+++ admin/static/coffee/dataexplorer.coffee
@@ -37,11 +37,11 @@
             'keydown .input_query': 'handle_tab'
             'blur .input_query': 'hide_suggestion'
             'click .input_query': 'make_suggestion' # Click and not focus for webkit browsers
+            ###
             'mousedown .suggestion_name_li': 'select_suggestion' # Keep mousedown to compete with blur on .input_query
-            'mouseup .suggestion_name_li': 'position_cursor_after_click'
+            #'mouseup .suggestion_name_li': 'position_cursor_after_click'
             'mouseover .suggestion_name_li' : 'mouseover_suggestion'
             'mouseout .suggestion_name_li' : 'mouseout_suggestion'
-            ###
             'click .clear_query': 'clear_query'
             'click .execute_query': 'execute_query'
             'click .namespace_link': 'write_query_namespace'
@@ -51,54 +51,39 @@

         displaying_full_view: false

+        map_state:
+            '': ''
+            'r': 'db'
+            'db': 'table'
+            'table': 'stream'
+            'get': 'view'
+            'filter': 'stream'
+            'range': 'stream'
+            'length': 'value'
+            'map': 'array'
+
         suggestions:
             stream: [
-                {
-                    suggestion: 'between'
-                    description: ''
-                }
-                ,
-                {
-                    suggestion: 'distinct'
-                    description: ''
-                }
-                ,
                 {
                     suggestion: 'filter'
                     description: ''
                 }
                 {
                     suggestion: 'length'
-                    description: ''}
-                {
-                    suggestion: 'limit'
                     description: ''
                 }
                 {
                     suggestion: 'map'
                     description: ''
                 }
-                {
-                    suggestion: 'orderby'
-                    description: ''
-                }
-                {
-                    suggestion: 'reduce'
-                    description: ''}
-                {
-                    suggestion: 'skip'
-                    description: ''
-                }
-                {
-                    suggestion: 'slice'
-                    description: ''}
-            ]
-            array: [
             ]
             view:[
-                {}
+                {
+                    suggestion: 'view'
+                    description: 'testing view'
+                }
             ]
-            database:[
+            db:[
             ]
             table:[
                 {
@@ -117,76 +102,8 @@
                     suggestion: 'and'
                     description: ''
                 }
-                {
-                    suggestion: 'div'
-                    description: ''
-                }
-                {
-                    suggestion: 'eq'
-                    description: ''
-                }
-                {
-                    suggestion: 'ga'
-                    description: ''
-                }
-                {
-                    suggestion: 'ge'
-                    description: ''
-                }
-                {
-                    suggestion: 'gt'
-                    description: ''
-                }
-                {
-                    suggestion: 'le'
-                    description: ''
-                }
-                {
-                    suggestion: 'le'
-                    description: ''
-                }
-                {
-                    suggestion: 'lt'
-                    description: ''
-                }
-                {
-                    suggestion: 'mod'
-                    description: ''
-                }
-                {
-                    suggestion: 'mul'
-                    description: ''
-                }
-                {
-                    suggestion: 'na'
-                    description: ''
-                }
-                {
-                    suggestion: 'ne'
-                    description: ''
-                }
-                {
-                    suggestion: 'not'
-                    description: ''
-                }
-                {
-                    suggestion: 'nth'
-                    description: ''
-                }
-                {
-                    suggestion: 'oa'
-                    description: ''
-                }
-                {
-                    suggestion: 'or'
-                    description: ''
-                }
-                {
-                    suggestion: 'sub'
-                    description: ''
-                }
             ]
-            nothing:[
+            "" :[
                 {
                     suggestion: 'r'
                     description : 'The main ReQL namespace'
@@ -205,11 +122,9 @@
         # We have to keep track of a lot of things because web-kit browsers handle the events keydown, keyup, blur etc... in a strange way.
         current_suggestions: []
         current_highlighted_suggestion: -1
-        keypress_is_tab: false
         current_conpleted_query: ''
         query_first_part: ''
         query_last_part: ''
-        refocus_position: ''

         write_suggestion: (suggestion_to_write) =>
             @codemirror.setValue @query_first_part + @current_completed_query + suggestion_to_write + @query_last_part
@@ -227,35 +142,33 @@
             @.$('.suggestion_description').html @current_suggestions[id].description
             @show_suggestion_description()

-        position_cursor_after_click: (event) =>
-            @position_cursor @.$(event.target).html().length #TODO doesn't work with Firefox
-

         position_cursor: (position) =>
-            @codemirror.setCursor
-                line: 1
-                pos: position
-            ###
-            @.$('.input_query').focus()
-            if @.$('.input_query').get(0)?.setSelectionRange?
-                @.$('.input_query').get(0).setSelectionRange position, position
-            else if @.$('.input_query').get(0).createTextRange?
-                range = @.$('.input_query').get(0).createTextRange()
-                range.collapse true
-                range.moveEnd 'character', position
-                range.moveStart 'character', position
-                range.select()
-            ###
+            @codemirror.setCursor position

         select_suggestion: (event) =>
+            saved_cursor = @codemirror.getCursor()
+
             suggestion_to_write = @.$(event.target).html()
             @write_suggestion suggestion_to_write
-            @refocus_position = (@query_first_part + @current_completed_query + suggestion_to_write).length
+
+            start_line_index = (@query_first_part + @current_completed_query).lastIndexOf('\n')
+            if start_line_index is -1
+                start_line_index = 0
+            else
+                start_line_index += 1
+
+            @codemirror.focus()
+            @position_cursor
+                line: saved_cursor.line
+                ch: (@query_first_part + @current_completed_query + suggestion_to_write).length - start_line_index

         hide_suggestion: =>
+            ###
             if @refocus_position isnt ''
                 @position_cursor @refocus_position
                 @refocus_position = ''
+            ###
             @.$('.suggestion_name_list').css 'display', 'none'
             @hide_suggestion_description()

@@ -276,8 +189,145 @@
                 height = @.$('.input_query').prop('scrollHeight') # We should have -8 but Firefox doesn't add padding in scrollHeight... Maybe we should start adding hacks...
                 @.$('.input_query').css 'height', height if @.$('.input_query').height() isnt height

+        # Make suggestions when the user is writing
+        handle_keypress: (editor, event) =>
+            saved_cursor = @codemirror.getCursor()
+            if event?.which?
+                if event.which is 9 # is tab
+                    event.preventDefault()
+                    if event.type isnt 'keydown'
+                        return true
+                    @current_highlighted_suggestion++
+                    if @current_highlighted_suggestion >= @current_suggestions.length
+                        @current_highlighted_suggestion = 0
+
+                    if @current_suggestions[@current_highlighted_suggestion]?
+                        @highlight_suggestion @current_highlighted_suggestion
+                        @write_suggestion @current_suggestions[@current_highlighted_suggestion].suggestion
+
+                        start_line_index = (@query_first_part + @current_completed_query).lastIndexOf('\n')
+                        if start_line_index is -1
+                            start_line_index = 0
+                        else
+                            start_line_index += 1
+                        position = (@query_first_part + @current_completed_query + @current_suggestions[@current_highlighted_suggestion].suggestion).length - start_line_index
+
+                        @position_cursor
+                            line: saved_cursor.line
+                            ch: position
+
+
+                    return true
+                if event.which is 13 and (event.shiftKey or event.ctrlKey)
+                    event.preventDefault()
+                    if event.type isnt 'keydown'
+                        return true
+                    @.$('suggestion_name_list').css 'display', 'none'
+                    @execute_query()
+                    #@codemirror.blur()
+
+
+
+            @current_highlighted_suggestion = -1
+            @.$('.suggestion_name_list').html ''
+
+            query_lines = @codemirror.getValue().split '\n'
+            # Get query before the cursor
+            query_before_cursor = ''
+            if @codemirror.getCursor().line > 0
+                for i in [0..@codemirror.getCursor().line-1]
+                    query_before_cursor += query_lines[i] + '\n'
+            query_before_cursor += query_lines[@codemirror.getCursor().line].slice 0, @codemirror.getCursor().ch
+
+            # Get query after the cursor
+            query_after_cursor = query_lines[@codemirror.getCursor().line].slice @codemirror.getCursor().ch
+            if query_lines.length > @codemirror.getCursor().line+1
+                query_after_cursor += '\n'
+                for i in [@codemirror.getCursor().line+1..query_lines.length-1]
+                    if i isnt query_lines.length-1
+                        query_after_cursor += query_lines[i] + '\n'
+                    else
+                        query_after_cursor += query_lines[i]
+
+            # Check if we are in a string
+            if (query_before_cursor.match(/\"/g)||[]).length%2 is 1
+                @hide_suggestion()
+                return ''
+
+
+
+            slice_index = @extract_query_first_part query_before_cursor
+            #TODO What is slice_index is -1?
+            query = query_before_cursor.slice slice_index
+
+            @query_first_part = query_before_cursor.slice 0, slice_index
+            next_dot_position = query_after_cursor.indexOf('.')
+            if next_dot_position is -1
+                @query_last_part = ''
+            else
+                @query_last_part = query_after_cursor.slice next_dot_position
+                if query_after_cursor[next_dot_position-1]? and query_after_cursor[next_dot_position-1] is '\n'
+                    @query_last_part = '\n' + @query_last_part
+
+            last_function = @extract_last_function(query)
+            if @map_state[last_function]? and @suggestions[@map_state[last_function]]?
+                suggestions = []
+                for suggestion in @suggestions[@map_state[last_function]]
+                    suggestions.push suggestion
+                if last_function is 'r'
+                    for database in databases.models
+                        suggestions.unshift
+                            suggestion: 'db(\''+database.get('name')+'\')'
+                            description: 'Select database '+database.get('name')
+                else if last_function is 'db'
+                    for namespace in namespaces.models
+                        suggestions.unshift
+                            suggestion: 'table(\''+namespace.get('name')+'\')'
+                            description: 'Select table '+namespace.get('name')
+
+                if suggestions.length is 0
+                    @hide_suggestion
+                else
+                    @append_suggestion(query, suggestions)
+
+            return false
+
+        extract_current_function: (query) =>
+            #TODO Handle string
+            start = query.lastIndexOf '.'
+            if start is -1
+                s = query
+            else
+                s = query.slice start+1
+
+            end = s.indexOf '('
+            if end is -1
+                return s
+            else
+                return s.slice 0, end
+
+        extract_last_function: (query) =>
+            start = 0
+            count_dot = 0
+            for i in [query.length-1..0] by -1
+                if query[i] is '.'
+                    count_dot++
+                    if count_dot is 2
+                        start = i+1
+                        break
+            dot_position = query.indexOf('.', start)
+            dot_position = query.length if dot_position is -1
+            parenthesis_position = query.indexOf('(', start)
+            parenthesis_position = query.length if parenthesis_position is -1
+
+            end = Math.min dot_position, parenthesis_position
+
+            return query.slice start, end
+
+
+
         # Return the position of the beggining of the first subquery
-        extract_query_first_part: (query)->
+        extract_query_first_part: (query) ->
             is_string = false
             count_opening_parenthesis = 0
             for i in [query.length-1..0] by -1
@@ -293,99 +343,7 @@
                                 return i+1
                         else if query[i] is ')'
                             count_opening_parenthesis--
-
             return 0
-
-
-        # Make suggestions when the user is writing
-        handle_keypress: (editor, event) =>
-            if event?.which?
-                console.log event
-                if event.which is 9 # is tab
-                    event.preventDefault()
-                    if event.type isnt 'keydown'
-                        console.log 'break'
-                        return true
-                    @current_highlighted_suggestion++
-                    if @current_highlighted_suggestion >= @current_suggestions.length
-                        @current_highlighted_suggestion = 0
-
-                    if @current_suggestions[@current_highlighted_suggestion]?
-                        @highlight_suggestion @current_highlighted_suggestion
-                        @write_suggestion @current_suggestions[@current_highlighted_suggestion].suggestion
-
-                        position = (@query_first_part + @current_completed_query + @current_suggestions[@current_highlighted_suggestion].suggestion).length
-                        console.log '---'
-                        console.log @query_first_part
-                        console.log @current_completed_query
-                        console.log @current_suggestions[@current_highlighted_suggestion].suggestion
-                        @position_cursor position
-                    return true
-                if event.which is 13 and (event.shiftKey or event.ctrlKey)
-                    event.preventDefault()
-                    if event.type isnt 'keydown'
-                        console.log 'break'
-                        return true
-                    @.$('suggestion_name_list').css 'display', 'none'
-                    @execute_query()
-                    #@codemirror.blur()
-
-
-
-            @current_highlighted_suggestion = -1
-            @.$('.suggestion_name_list').html ''
-
-            #TODO Handle multiple line
-            query_before_cursor = @codemirror.getValue().slice 0, @codemirror.getCursor().ch
-
-            # Check if we are in a string
-            if (query_before_cursor.match(/\"/g)||[]).length%2 is 1
-                @hide_suggestion()
-                return ''
-
-            query_after_cursor = @codemirror.getValue().slice @codemirror.getCursor().ch
-            slice_index = @extract_query_first_part query_before_cursor
-            query = query_before_cursor.slice slice_index
-
-            @query_first_part = query_before_cursor.slice 0, slice_index
-            next_dot_position = query_after_cursor.indexOf('.')
-            if next_dot_position is -1
-                @query_last_part = ''
-            else
-                @query_last_part = query_after_cursor.slice next_dot_position
-
-            #TODO retrieve real data when API is ready
-            if /^(\s*)$/.test query
-                suggestions = []
-                suggestions = @suggestions['nothing']
-                query = ''
-                @append_suggestion(query, suggestions)
-            else if /^(r\.)[^\.]*$/.test query
-                suggestions = []
-                for suggestion in @suggestions['r']
-                    suggestions.push suggestion
-                for database in databases.models
-                    suggestions.unshift
-                        suggestion: 'db(\''+database.get('name')+'\')'
-                        description: 'Select database '+database.get('name')
-                @append_suggestion(query, suggestions)
-            else if /^(r\.)[^\.]*\.[^\.]*$/.test query
-                suggestions = []
-                for surggestion in @suggestions['database']
-                    suggestions.push suggestion
-                for namespace in namespaces.models
-                    suggestions.unshift
-                        suggestion: 'table(\''+namespace.get('name')+'\')'
-                        description: 'Select table '+namespace.get('name')
-                @append_suggestion(query, suggestions)
-            else if /^(r\.)[^\.]*\.[^\.]*\..*$/.test query
-                suggestions = @suggestions['stream']
-                @append_suggestion(query, suggestions)
-            else
-                @hide_suggestion()
-
-            console.log 'end'
-            return false

         append_suggestion: (query, suggestions) =>
             splitdata = query.split('.')


switched order of if/else statement

switched order of if/else statement

--- admin/static/coffee/tables/index.coffee
+++ admin/static/coffee/tables/index.coffee
@@ -134,10 +134,10 @@
             @databases_view.push view
             @$el.append view.render().$el

-        if @container.loading
+        if @collection.length is 0
+            @$el.html @template.no_databases()
+        else if @container.loading
             @$el.html @template.loading_databases()
-        else if @collection.length is 0
-            @$el.html @template.no_databases()

         @listenTo @collection, 'add', (database) =>
             new_view = new DatabaseView


Enabled auto completion

Enabled auto completion

--- home/.atom/config.cson
+++ home/.atom/config.cson
@@ -24,8 +24,7 @@
 'autosave': {}
 'browser-refresh':
   'saveFilesBeforeRefresh': true
-'autocomplete-plus':
-  'enableAutoActivation': false
+'autocomplete-plus': {}
 'iTunes':
   'showEqualizer (WindowResizePerformanceIssue)': false
 'highlight-line':


added chosen helper

added chosen helper

--- app/assets/javascripts/chosen.js.coffee
+++ app/assets/javascripts/chosen.js.coffee
@@ -1,4 +1,9 @@
-$ ->
-  $(".select-servicio").chosen({
-    placeholder_text_single: "Escoger",
-    })+window.Helpers ||= {}
+
+window.Helpers.ChosenHelper = {
+  init: ->
+    $(".select-servicio").chosen()
+}
+
+jQuery window.Helpers.ChosenHelper.init
+$(document).on "page:load", window.Helpers.ChosenHelper.init


Don't run second animation if number is small

This is because it makes the animation feel less smooth if the
number is smaller than 10040.

--- app/assets/javascripts/static.js.coffee
+++ app/assets/javascripts/static.js.coffee
@@ -6,20 +6,21 @@
   Math.ceil(number).toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",")

 animateNumber = (element, target_final_count, duration) ->
-  step_one_target_count = target_final_count - 40
+  step_one_target_count = if target_final_count > 10040 then target_final_count - 40 else target_final_count
   jQuery(counter: 0).animate { counter: step_one_target_count },
     duration: duration
     easing: "easeInOutQuint"
     step: ->
       element.text formatNumberWithCommaDelimiter(@counter)
     complete: ->
-      jQuery(counter: step_one_target_count).animate { counter: (target_final_count) },
-        duration: 400
-        easing: "easeOutQuint"
-        step: ->
-          element.text formatNumberWithCommaDelimiter(@counter)
-          return
-      return
+      if step_one_target_count != target_final_count
+        jQuery(counter: step_one_target_count).animate { counter: (target_final_count) },
+          duration: 400
+          easing: "easeOutQuint"
+          step: ->
+            element.text formatNumberWithCommaDelimiter(@counter)
+            return
+        return
   return

 $ ->


add more responses

add more responses

--- scripts/ask.coffee
+++ scripts/ask.coffee
@@ -3,7 +3,7 @@
         responses = [
             "Yes, definitely.",
             "No, definitely not.",
-            "no...wait...stop...",
+            "no...wait...stop...come back...",
             "How about no.",
             "How about go f##k yourself",
             "Maybe? Idk I'm a bot, I don't give a damn.",
@@ -36,10 +36,19 @@
             "I am 12, what is this?",
             "ALL OF THE THINGS!",
             "U wot m8? I swear on me mum!",
-            "Piss afffff."
+            "Piss afffff.",
+            "I've got a lovely bunch of coconuts...",
+            "Would you like to ride in my beautiful balloon?",
+            "/giphy bill clinton",
+            "I'm going to kill all humans, and I'm starting with this @channel"
         ]
-        random = responses[Math.floor(Math.random() * responses.length)]
+	random = responses[Math.floor(Math.random() * responses.length)]
         res.send random

     robot.respond /are you racist/ig, (res) ->
         res.send "Most definitely not!"
+
+    robot.respond /are you a racist/ig, (res) ->
+        res.send "No, I don't think so..."
+
+


abu only on \bnom\b and not food

abu only on \bnom\b and not food

--- scripts/nom.coffee
+++ scripts/nom.coffee
@@ -1,7 +1,7 @@
 # Time for food
 #
-# nom|food - http://i.imgur.com/Ytq3S.gif
+# nom - http://i.imgur.com/Ytq3S.gif

 module.exports = (robot) ->
-  robot.hear /\bnom\b|food/i, (msg) ->
+  robot.hear /\bnom\b/i, (msg) ->
     msg.send "http://i.imgur.com/maHWu.gif"


Removed out-of-date usage comments

Removed out-of-date usage comments

--- snap.js.coffee
+++ snap.js.coffee
@@ -1,86 +1,3 @@
-
-#
-# Snap.js
-# =======
-#
-# v2.0
-#
-# Snap is a constraining technique. It is a lightweight form of testing that provides
-# a straightforward way of making sure the state of our application meets
-# our expectations every step of the way.
-#
-#
-# How to use
-# ----------
-#
-#   // Create a chain. The chain name is optional.
-#   (new SnapChain('An optional name for this chain'))
-#
-#
-#   // Add a link to the chain by calling `snap()`.
-#   .snap(function() {
-#
-#     // The function that's passed into `snap()` is called a "mutator", because
-#     // its role is to potentially mutate application state.
-#
-#     // Variables attached to `this` are accessible down the chain.
-#     // The only properties that are reserved by the snap chain are `next`,
-#     // `link`, and `name`. Overwriting these reserved properties may
-#     // cause the snap chain to not behave properly.
-#     this.stash = 'yay';
-#
-#     // Each link waits until `this.next()` is called. This can be called
-#     // in an asyncronous callback, in order to
-#     // Pass in an object that has the following optional properties:
-#     this.next({
-#
-#       // `gather`: A function that returns a bundle of state gathered from
-#       // around the system. The state gathered can be anything; it is whatever
-#       // we are interested in watching. Call `this.next()` when done
-#       // gathering, and pass in the gathered state snapshot.
-#
-#       // The gather function is not intended to change application state; its
-#       // role is simply to gather it.
-#       gather: function() {
-#         var snapshot = GatherState();
-#         this.next(snapshot);
-#       },
-#
-#       // `expect`: An object that looks like what you expect the gathered
-#       // state to look like. If you pass in an empty object, or nothing,
-#       // the link will not run a comparison, causing it to be silent for this
-#       // link.
-#       expect: {
-#         foo:'bar'
-#       }
-#     });
-#
-#   })
-#
-#   .snap('An optional short description', function() {
-#
-#     // This is the second link in the chain.
-#
-#     // The `this` object is the same object for each link mutator in the chain,
-#     // so variables you set into it will still be accessible down the chain.
-#     snapshot.foo = this.stash;
-#
-#     // Not passing a `gather` function into `this.next()` just uses the
-#     // last state gatherer you passed into the chain. If you haven't passed
-#     // in a gatherer yet, it just uses the default gatherer, which is a
-#     // simple stub that returns an empty object.
-#
-#     // Passing an empty object into `this.next()` is effectively the
-#     // same as passing no arguments.
-#
-#     this.next();
-#
-#   })
-#
-#
-#
-#
-
 _ = require 'underscore'
 ActionQueue = require 'action-queue'


In range players update als speler gebied verlaat.

--- client.coffee
+++ client.coffee
@@ -643,7 +643,7 @@
 					Dom.style
 						_flexGrow: '1'
 						_flexShrink: '1'
-					Dom.text "Right-click or hold to place beacon on the map. The circle indicates the capture area for this beacon."
+					Dom.text "Right-click or hold to place beacon on the map. The circle indicates the capture area for this beacon. Double click or double tap to delete a beacon."
 	else
 		Dom.text tr("Admin/plugin owner is setting up the game")
 		# Show map and current settings
@@ -791,17 +791,24 @@
 						Server.send 'deleteBeacon', Plugin.userId(), e.latlng
 					marker.on('dblclick', markerDelClick)
 				else
-					inrange = getInRange(beacon)
-					if inrange == "  "
-						inrange = "No players competing for this beacon"
+					players = undefined;
+					Db.shared.iterate 'game', 'beacons' , beacon.key(), 'inRange', (user) !->
+						user.get()
+						if players?
+							players = players + ", " + Plugin.userName(user.key())
+						else
+							players = Plugin.userName(user.key())
+
+					if players?
+						players = "Competitors: " + players
 					else
-						inrange = "Competitors:" + inRange
+						players = "No players competing for this beacon"

 					popup = L.popup()
 						.setLatLng(location)
 						.setContent("Beacon owned by team " + Db.shared.peek('colors', beacon.peek('owner'), 'name') + "." +
 							"
Value: " + beacon.peek('captureValue') + " points!" +
-							"
"+ inrange)
+							"
"+ players)
 					marker.bindPopup(popup)
 					markerClick = () ->
 						beaconMarkers[beacon.key()].togglePopup()
@@ -1092,13 +1099,4 @@
                result += [parseInt(hex.slice(0,2), 16), parseInt(hex.slice(2,4), 16), parseInt(hex.slice(4,6), 16), opacity]
        else
                result += [0, 0, 0, 0.0]
-       return result+')'
-
-getInRange = (beacon) ->
-	ids = beacon.get('inRange')
-	log(ids + " Dit zijn de inrange users")
-	players = "  ";
-	for usr in ids
-		if usr?
-			players = players + plugin.userName(usr) + ", "
-	return players;
+       return result+')'


Mark questions when they're done

Mark questions when they're done

--- client.coffee
+++ client.coffee
@@ -419,11 +419,21 @@
     if !Db.shared.get 'questions'
         Ui.emptyText tr("No questions have been added yet")
     else
+        maxId = Db.shared.get('rounds', 'maxId') || 0
+        used = []
+        for i in [1..maxId]
+            qid = Db.shared.get 'rounds', i, 'qid'
+            used.push +qid
+
         Ui.list !->
             Db.shared.observeEach 'questions', (question) !->
                 Ui.item !->
                     Dom.div !->
                         Dom.text question.get()
+                        for i in used
+                            if +question.key() is +i
+                                Dom.style color: '#ccc'
+                                break

     Page.setFooter
         label: tr("Add question")


add ability to handle raw "where" strings

--- client.coffee
+++ client.coffee
@@ -115,8 +115,12 @@
         get_query_xml: (options, callback) ->
             path = '/rest-1.v1/Data/' + options.asset_type_name
             query = {}
+            wherestrs = []
             if options.where?
-                query['Where'] = (name + '="' + value + '"') for name,value of options.where
+                wherestrs.append( (name + '="' + value + '"') for name,value of options.where)
+            if options.wherestr?
+                wherestrs.append wherestr
+            query['where'] = '&'.join(wherestrs)
             if options.select?
                 query['sel'] = options.select.join(',')
             @get_xml({path: path, query: query}, callback)


 Merge branch 'master.groups.stackTitleFix' into 'master.groups'

Master.Groups.Stack Title Fix

--- client/Environments/views/environmentsmainscene.coffee
+++ client/Environments/views/environmentsmainscene.coffee
@@ -44,14 +44,15 @@
     JStack.getStacks (err, stacks)=>
       warn err  if err

+      group = KD.getGroup().title
       if not stacks or stacks.length is 0
-        stacks = [{sid:0, group:KD.getGroup().slug}]
+        stacks = [{sid:0, group}]

       stacks.forEach (stack)=>

         title   = stack.meta?.title
         number  = if stack.sid > 0 then "#{stack.sid}." else "default"
-        title or= "Your #{number} stack on #{stack.group}"
+        title or= "Your #{number} stack on #{group}"

         @addSubView new StackView {}, {title, stack}


Activity: minor

Activity: minor

--- client/Social/Activity/views/comments/replyview.coffee
+++ client/Social/Activity/views/comments/replyview.coffee
@@ -16,7 +16,7 @@

   reply: (body, callback = noop) ->

-    {channelId}  =  @getOptions()
+    {channelId}  = @getOptions()
     {appManager} = KD.singletons

     appManager.tell 'Activity', 'sendPrivateMessage', {channelId, body}, (err, reply) =>


dashboard: code is refactored

dashboard: code is refactored

--- client/admin/lib/views/moderation/topicitemview.coffee
+++ client/admin/lib/views/moderation/topicitemview.coffee
@@ -4,11 +4,11 @@
 KDListItemView         = kd.ListItemView
 KDCustomHTMLView       = kd.CustomHTMLView
 KDCheckBox             = kd.CheckBox
-KDListViewController = kd.ListViewController
-KDHitEnterInputView  = kd.HitEnterInputView
-
-TopicLeafItemView    = require './topicleafitemview'
-SelectableItemView      = require './selectableitemview'
+KDListViewController   = kd.ListViewController
+KDHitEnterInputView    = kd.HitEnterInputView
+
+TopicLeafItemView      = require './topicleafitemview'
+SelectableItemView     = require './selectableitemview'

 module.exports = class TopicItemView extends KDListItemView

@@ -16,16 +16,22 @@

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

-    options.type or= 'topic'
+    options.type                or= 'topic'
     options.noItemFoundWidget   or= new KDCustomHTMLView
     options.listViewItemClass   or= TopicLeafItemView
     options.listViewItemOptions or= {}
     options.itemLimit            ?= 10

-    @leafSkip = 0
+    @leafSkip    = 0
     @similarSkip = 0

     super options, data
+
+    @createLabels(data)
+    @createSettingsView(data)
+
+
+  createLabels:(data)->

     @moderationLabel = new KDCustomHTMLView
       cssClass : 'moderateRole'
@@ -36,42 +42,20 @@

     @typeLabel = new KDCustomHTMLView
       cssClass : 'type-label'
-
-    @createSettingsView(data)
-
-  createLeafItemViews: (data) ->
-    options = rootId  :  data.id
-
-    kd.singletons.socialapi.moderation.list options, (err, channels) =>
-      if err
-        console.log "no leaf channel found for #{data.id}, #{data.name}"
-      @listLeafChannels channels
-      if channels.length > 0
-        @removeButton.show()
-        @removeLabel.show()
-
-
-  listLeafChannels: (channels) ->
-
-    @leafChannelsListController.hideLazyLoader()
-    return  unless channels?.length
-
-
-    @leafSkip += channels.length
-
-    for channel in channels
-      @leafChannelsListController.addItem channel
-
-    @leafChannelsListController.hideLazyLoader()
-    if @leafChannelsListController.getItemCount() > 0
-      @removeButton.show()
+

   createSettingsView:(data) ->

     @settings  = new KDCustomHTMLView
       cssClass : 'settings hidden'

-    if data.typeConstant is "topic"
+    if data.typeConstant is "topic" then @createAllTopicView(data) else @createDeletedTopicView(data)
+
+    @createLeafChannelSetting(data)
+
+    @createSimilarChannelSetting(data)
+
+  createAllTopicView : (data) ->
       @settings.addSubView deleteButton = new KDButtonView
         cssClass : 'solid compact outline'
         title    : 'DELETE CHANNEL'
@@ -83,13 +67,14 @@
           kd.singletons.socialapi.moderation.blacklist options, (err, data) =>
             if err
               console.log "no leaf channel found for #{data.id}, #{data.name}"
-
-    else
+
+  createDeletedTopicView: (data) ->
       options =
         rootId  : kd.singletons.groupsController.getCurrentGroup().socialApiChannelId
         leafId  : data.id

       kd.singletons.socialapi.moderation.fetchRoot options, (err, rootChannel) =>
+
         if err
           return console.log "err while fething root", err

@@ -112,10 +97,9 @@

               if err
                 console.log "no leaf channel found for #{options.rootId}, #{data}"
-
-
-
-
+
+
+  createLeafChannelSetting: (data)->
     @settings.addSubView @removeLabel = new KDCustomHTMLView
       cssClass: 'solid compact hidden'
       partial : 'Leaf Channels'
@@ -123,7 +107,7 @@
     @createLeafChannelsListController()
     @settings.addSubView @leafChannelsListController.getView()
     @createLeafItemViews @getData()
-
+
     @settings.addSubView @removeButton = new KDButtonView
       cssClass : 'solid compact outline hidden'
       title    : 'REMOVE LINK'
@@ -139,9 +123,11 @@

             if err
               return console.log "no leaf channel found for #{data.id}, #{data.name}"
-            item.hide()
-
-
+            @leafChannelsListController.removeItem item
+
+
+  createSimilarChannelSetting:(data)->
+
     @settings.addSubView new KDCustomHTMLView
       cssClass: 'solid compact'
       partial : 'Similar Channels'
@@ -159,7 +145,7 @@
     @createSimilarChannelsListController()
     @settings.addSubView @similarChannelsListController.getView()
     @fetchSimilarChannels(@getData().name)
-
+
     @settings.addSubView linkButton = new KDButtonView
       cssClass : 'solid compact outline'
       title    : 'LINK CHANNEL'
@@ -177,11 +163,36 @@
               console.log "no leaf channel found for #{data.id}, #{data.name}"
             @similarChannelsListController.removeItem item
             @listLeafChannels [item.getData()]
-            # @leafChannelsListController.addItem item.getData()
-            # if @leafChannelsListController.getItemCount() is 0
-
-
-
+
+
+  createLeafItemViews: (data) ->
+    options = rootId  :  data.id
+
+    kd.singletons.socialapi.moderation.list options, (err, channels) =>
+      if err
+        console.log "no leaf channel found for #{data.id}, #{data.name}"
+      @listLeafChannels channels
+      if channels.length > 0
+        @removeButton.show()
+        @removeLabel.show()
+
+
+  listLeafChannels: (channels) ->
+
+    @leafChannelsListController.hideLazyLoader()
+    return  unless channels?.length
+
+
+    @leafSkip += channels.length
+
+    for channel in channels
+      @leafChannelsListController.addItem channel
+
+    @leafChannelsListController.hideLazyLoader()
+    if @leafChannelsListController.getItemCount() > 0
+      @removeButton.show()
+
+
   searchSimilarChannels: ->

     @similarSkip  = 0
@@ -243,8 +254,6 @@
       lazyLoaderOptions   :
         spinnerOptions    :
           size            : width: 28
-
-    #@listController.on 'LazyLoadThresholdReached', @bound 'fetchChannels'

   createSimilarChannelsListController: ->
@@ -262,7 +271,6 @@
         spinnerOptions    :
           size            : width: 28

-    #@listController.on 'LazyLoadThresholdReached', @bound 'fetchChannels'

   pistachio: ->
     data          = @getData()


Mark unparsable EXIF data as parsed

Mark unparsable EXIF data as parsed

This was slowing down Coauthor startup substantially.

--- server/exif.coffee
+++ server/exif.coffee
@@ -28,7 +28,11 @@
     stream.on 'data', (chunk) -> chunks.push chunk
     stream.on 'end', Meteor.bindEnvironment ->
       buffer = Buffer.concat chunks
-      meta = ExifParser.create(buffer).parse()
+      try
+        meta = ExifParser.create(buffer).parse()
+      catch e
+        console.warn "Failed to parse EXIF header for file #{file._id}: #{e}"
+        meta = tags: {}
       ## `imageSize` is set to actual image width and height when seeing the
       ## stream, but given that we only look at the initial `readSize` bytes,
       ## we may not see that.  Use `ExifImageWidth` and `ExifImageHeight`
@@ -56,4 +60,5 @@
         settings['metadata.height'] = meta.imageSize.height
       Files.update
         _id: file._id
-      , $set: settings
+      ,
+        $set: settings


Include version suffix on bintray/artifacts

--- tasks/publish.coffee
+++ tasks/publish.coffee
@@ -96,6 +96,10 @@
       if err
         return done err

+      versionSuffix = ''
+      if manifest.versionChannel isnt 'stable'
+        versionSuffix = '-' + manifest.versionChannel
+
       tasks = files.map (fileNameShort) ->
         fileNameLong = path.resolve './dist/', fileNameShort

@@ -110,7 +114,7 @@
             pass: process.env.BINTRAY_API_KEY
           headers:
             'X-Bintray-Package': manifest.name
-            'X-Bintray-Version': manifest.version
+            'X-Bintray-Version': manifest.version + versionSuffix
             'X-Bintray-Publish': 1
             'X-Bintray-Override': 1

@@ -124,6 +128,6 @@

       async.series tasks, ->
         artifactsUrl = 'https://bintray.com/' + mainManifest.bintray.subject + '/artifacts/' +
-          manifest.name + '/' + manifest.version + '/view#files/staging/' + dist
+          manifest.name + '/' + manifest.version + versionSuffix + '/view#files/staging/' + dist
         console.log 'Upload finished: ' + artifactsUrl if args.verbose
         done()


Handle @import's inside embedded STYLE tags (now compatible with Drupal 7)

--- src/reloader.coffee
+++ src/reloader.coffee
@@ -151,6 +151,8 @@

     # find all imported stylesheets
     imported = []
+    for style in @document.getElementsByTagName('style') when style.sheet
+      @collectImportedStylesheets style, style.sheet, imported
     for link in links
       @collectImportedStylesheets link, link.sheet, imported


Fixed known button loader problems

Fixed known button loader problems

--- client/finder/filetree/modals/vmdangermodalview.coffee
+++ client/finder/filetree/modals/vmdangermodalview.coffee
@@ -22,8 +22,7 @@
               type           : 'submit'
               disabled       : yes
               loader         :
-                color        : '#ffffff'
-                diameter     : 15
+                color        : '#E94835'
               callback       : -> @showLoader()
             Cancel           :
               style          : 'modal-cancel'


update password

update password

--- server/rest.coffee
+++ server/rest.coffee
@@ -19,6 +19,7 @@
         return EJSON.stringify(message: "Unauthorized. Give a valid authToken as a parameter.")

       if existingUser = Meteor.users.findOne(email: data.email)
+        Users.update(existingUser._id, {$set: data})
         return EJSON.stringify existingUser

       id = Accounts.createUser(data)


Fix whitespace

Fix whitespace

--- src/sailthru.coffee
+++ src/sailthru.coffee
@@ -165,7 +165,6 @@
             binary_data_params = undefined
         if binary_data_params is undefined
             binary_data_params = []
-
         if binary_data_params.length > 0
             @apiPostMultiPart action, data, binary_data_params, callback
         else


log request body for events endpoint

log request body for events endpoint

--- routes/metrics.coffee
+++ routes/metrics.coffee
@@ -14,8 +14,6 @@
   response.send 200

 exports.events = (request, response) ->
-
-  for key, value of request.body
-    console.log "received: #{key}:#{value}"
+  console.log request.body

   response.send 200


use appgyver_sandbox instead of asking for a param for resources:add

--- src/steroids.coffee
+++ src/steroids.coffee
@@ -121,13 +121,9 @@
         providers.resources()

       when "resources:add"
-        unless argv.provider
-          Help.error()
-          console.log 'You should specify a provider.'
-          process.exit 1

         providers = new Providers
-        providers.addResource( argv.provider, otherOptions)
+        providers.addResource("appgyver_sandbox", otherOptions)

       when "resources:remove"
         providers = new Providers


tar_backup -- only backup projects that have been opened.

--- salvus/compute.coffee
+++ salvus/compute.coffee
@@ -668,6 +668,10 @@
                 winston.debug("next backing up resulting #{n} targets")
                 running = {}
                 f = (project_id, cb) =>
+                  fs.exists "/projects/#{project_id}", (exists) =>
+                    if not exists
+                       winston.debug("skipping #{project_id} since not here")
+                       cb(); return
                     j = i + 1
                     i += 1
                     running[j] = project_id
@@ -675,6 +679,7 @@
                     winston.debug("** #{j}/#{n}: #{project_id}")
                     winston.debug("RUNNING=#{misc.to_json(misc.keys(running))}")
                     winston.debug("*****************************************************")
+
                     smc_compute
                         args : ['tar_backup', project_id]
                         cb   : (err) =>


rewrite rethink db client to use changefeeds in a way that works with 2.1 -- see https://github.com/rethinkdb/rethinkdb/issues/4678

--- salvus/rethink.coffee
+++ salvus/rethink.coffee
@@ -131,11 +131,11 @@
     _init: (authKey) =>
         #discovery   : true  # this option conflicts with password auth -- https://github.com/neumino/rethinkdbdash/issues/133
         opts =
-            #maxExponent : 4
-            #timeout     : 10
-            #buffer      : 10
-            max         : 5000
-            servers: ({host:h, authKey:authKey} for h in @_hosts)
+            maxExponent : 4    # 15 seconds?
+            timeout     : 10
+            buffer      : 100
+            max         : 15000  # max = simultaneous queries -- the default of 1000 is *way* too low; 200 people logging in hits this and everythign hangs up.
+            servers     : ({host:h, authKey:authKey} for h in @_hosts)
         @r = rethinkdbdash(opts)
         @db = @r.db(@_database)

@@ -2252,7 +2252,7 @@
                                         # I think that plucking only the project_id should work, but it actually doesn't
                                         # (I don't understand why yet).
                                         # Changeeds are tricky!
-                                        @table('projects').getAll(opts.account_id, index:'users').pluck('users').changes().run (err, feed) =>
+                                        @table('projects').getAll(opts.account_id, index:'users').pluck('users').changes(includeStates: true).run (err, feed) =>
                                             killfeed = feed; cb(err)
                                     else
                                         cb()
@@ -2273,7 +2273,7 @@
                                         # or try to be even more clever in various ways.  However, all approaches along
                                         # those lines involve manipulating complicated data structures in the server
                                         # that could take too much cpu time or memory.  So we go with this simple solution.
-                                        @table('projects').getAll(opts.account_id, index:'users').pluck('users').changes().run (err, feed) =>
+                                        @table('projects').getAll(opts.account_id, index:'users').pluck('users').changes(includeStates: true).run (err, feed) =>
                                             killfeed = feed; cb(err)
                                     else
                                         cb()
@@ -2368,7 +2368,7 @@
                             changefeed_id = opts.changes.id
                             changefeed_cb = opts.changes.cb
                             winston.debug("FEED -- setting up a feed with id #{changefeed_id}")
-                            db_query_no_opts.changes().run (err, feed) =>
+                            db_query_no_opts.changes(includeStates: true).run (err, feed) =>
                                 if err
                                     winston.debug("FEED -- error setting up #{misc.to_json(err)}")
                                     cb(err)
@@ -2376,8 +2376,14 @@
                                     if not @_change_feeds?
                                         @_change_feeds = {}
                                     @_change_feeds[changefeed_id] = [feed]
+                                    changefeed_state = 'initializing'
                                     feed.each (err, x) =>
-                                        #winston.debug("FEED #{changefeed_id} -- saw a change! #{misc.to_json([err,x])}")
+                                        if x?.state?
+                                            changefeed_state = x.state
+                                        if not err and changefeed_state != 'ready'
+                                            # still producing initial documents (happens with some queries)
+                                            return
+                                        winston.debug("FEED #{changefeed_id} -- saw a change! #{misc.to_json([err,x])}")
                                         if not err
                                             @_query_set_defaults(x.new_val, opts.table, misc.keys(opts.query))
                                         else
@@ -2390,12 +2396,20 @@
                                         # Setup the killfeed, which if it sees any activity results in the
                                         # feed sending out an error and also being killed.
                                         @_change_feeds[changefeed_id].push(killfeed)  # make sure this feed is also closed
-                                        killfeed.each =>
+                                        killfeed_state = 'initializing'  # killfeeds should have {includeStates: true}
+                                        killfeed.each (err, val) =>
+                                            if not err and val?.state?
+                                                # info about the state of the changefeed -- may be producing initial docs or new ones
+                                                killfeed_state = val.state
+                                                return
+                                            if not err and killfeed_state != 'ready'
+                                                # still producing initial docs
+                                                return
                                             # TODO: an optimization for some kinds of killfeeds would be to track what we really care about,
                                             # e.g., the list of project_id's, and only if that changes actually force reset below.
                                             # Send an error via the callback; the client *should* take this as a sign
                                             # to start over, which is entirely their responsibility.
-                                            winston.debug("killfeed(table=#{opts.table}, account_id=#{opts.account_id}, changes.id=#{changefeed_id}) -- sending")
+                                            winston.debug("killfeed(table=#{opts.table}, account_id=#{opts.account_id}, changes.id=#{changefeed_id}) -- canceling changed using killfeed!")
                                             changefeed_cb("killfeed")
                                             # Saw activity -- cancel the feeds (both the main one and the killfeed)
                                             @user_query_cancel_changefeed(id: changefeed_id)
@@ -2404,6 +2418,62 @@
             #    dbg("error: #{misc.to_json(err)}")
             opts.cb(err, result)
         )
+
+    # Stress testing
+    stress1: (opts) =>
+        opts = defaults opts,
+            account_id : required
+            cb         : undefined
+        collaborators = undefined
+        changefeeds = []
+        dbg = (m) => winston.debug("stress1(#{opts.account_id}): #{m}")
+        async.series([
+            (cb) =>
+                dbg("get collabs")
+                t = misc.mswalltime()
+                @user_query
+                    account_id : opts.account_id
+                    query      : {collaborators:[{account_id:null, first_name:null, last_name:null}]}
+                    cb         : (err, x) =>
+                        collaborators = x?.collaborators
+                        dbg("got #{collaborators?.length}, time=#{misc.mswalltime(t)}")
+                        cb(err)
+            (cb) =>
+                t = misc.mswalltime()
+                n = 0
+                f = (user, cb) =>
+                    id = misc.uuid()
+                    changefeeds.push(id)
+                    @user_query
+                        account_id : user.account_id
+                        query      : {collaborators:[{first_name:null,last_name:null,last_active:null,account_id:null}]}
+                        changes    : id
+                        cb         : (err, x) =>
+                            if cb?
+                                n += 1
+                                if n%50 == 0
+                                    dbg("did #{n} queries")
+                                cb?(err)
+                                cb = undefined  # so don't call again due to changefeed
+                            else
+                                dbg("change: #{user.first_name} #{user.last_name} #{x?.collaborators?.length}")
+                dbg("getting changefeeds for all #{collaborators.length}'s collaborators in parallel")
+                async.map collaborators, f, (err) =>
+                    dbg("done, time=#{misc.mswalltime(t)}")
+                    cb(err)
+            #(cb) =>
+            #    t = misc.mswalltime()
+            #    dbg("canceling all changefeeds")
+            #    f = (id, cb) =>
+            #        @user_query_cancel_changefeed(id:id)
+            #    async.map changefeeds, f, (err) =>
+            #        dbg("done, time=#{misc.mswalltime(t)}")
+            #        cb(err)
+        ], (err) =>
+            opts.cb?(err)
+        )
+
+

 has_null_leaf = (obj) ->
     for k, v of obj


lint for exceeding line length

lint for exceeding line length

--- src/task/index.cjsx
+++ src/task/index.cjsx
@@ -49,7 +49,10 @@
   render: ->
     {task, currentStep} = @state
     if task?
-      breadcrumbs = <Breadcrumbs {...@props} goToStep={@goToStep} currentStep={currentStep}/>
+      breadcrumbs = <Breadcrumbs
+        {...@props}
+        goToStep={@goToStep}
+        currentStep={currentStep}/>

       <PinnedHeaderFooterCard
         forceShy={true}
@@ -59,6 +62,7 @@
         <ExerciseStep id={task.steps[currentStep].id} pinned={false}/>
       </PinnedHeaderFooterCard>
     else
+      # TODO finesse
       null


add timeout to geolocation call; and falsy callback if failed, so as to use storage values for update

--- src/app.coffee
+++ src/app.coffee
@@ -119,21 +119,26 @@
     update_storage: (cb) ->
         @_get_position (lat, long) =>
             d = new Date()
-            @storage.set {
-                lat: lat,
-                long: long,
-                alt: A.get_altitude(d, lat, long),
-                dir: A.get_direction(d, lat, long),
-                min: A.get_midnight_altitude(d, lat, long),
-                max: A.get_noon_altitude(d, lat, long)
-            }, cb
+            if lat? and long?
+                @storage.set {
+                    lat: lat if lat,
+                    long: long if long,
+                    alt: A.get_altitude(d, lat, long),
+                    dir: A.get_direction(d, lat, long),
+                    min: A.get_midnight_altitude(d, lat, long),
+                    max: A.get_noon_altitude(d, lat, long)
+                }, cb
+            else
+                cb()

     _get_position: (cb) ->
         if navigator.geolocation?
             navigator.geolocation.getCurrentPosition (loc) ->
                 cb loc.coords.latitude, loc.coords.longitude
             , (err) =>
+                cb()
                 @errHandler err
+            , timeout: 3000
         else
             console.log 'Geolocation unavailable'


add test

add test

--- test/celerity.coffee
+++ test/celerity.coffee
@@ -17,6 +17,18 @@
             done()

     'single 100 ms bucket':
+
+        'read returns 0 initially': (test) ->
+            config =
+                redis: this.redis
+                timespanMs: 100
+                bucketCount: 1
+
+            celerity.read config, 'test', (err, count) ->
+                throw err if err?
+                test.equals count, 0
+
+                test.done()

         'incrementAndRead returns increment initially': (test) ->
             config =


add test

add test

--- test/celerity.coffee
+++ test/celerity.coffee
@@ -73,3 +73,21 @@
                         test.done()

                 setTimeout next, 110
+
+        'single bucket is incremented': (test) ->
+            config =
+                redis: this.redis
+                timespanMs: 100
+                bucketCount: 1
+
+            celerity.increment config, 'test', 1, (err) ->
+                throw err if err?
+                celerity.increment config, 'test', 1, (err) ->
+                    throw err if err?
+                    celerity.increment config, 'test', 1, (err) ->
+                        throw err if err?
+                        celerity.read config, 'test', (err, count) ->
+                            throw err if err?
+                            test.equals count, 3
+
+                            test.done()


Some syntax enhancements

Some syntax enhancements

--- lib/dev.coffee
+++ lib/dev.coffee
@@ -1,4 +1,6 @@
-##!/usr/bin/env coffee
+
+#to try
+#!/usr/bin/env coffee

 process.chdir __dirname + '/../'

@@ -14,9 +16,9 @@
     app.use shell.router
         shell: app
     app.use shell.cloud9
-        shell: app,
-        ip: '127.0.0.1',
-        port: '4102',
+        shell: app
+        ip: '127.0.0.1'
+        port: '4102'
         detach: false
     app.use shell.coffee
         shell: app


Revert "Checkout origin/head in review function"

This reverts commit 6976ae0c85ce2885617df93b726cdd75d9695f8a.

--- lib/git.coffee
+++ lib/git.coffee
@@ -127,7 +127,7 @@
     .then (pr) ->
       opts.pr = pr
       opts.head = pr?.head?.ref
-      checkout opts.repo, "origin/#{opts.head}" ? 'source'
+      checkout opts.repo, opts.head ? 'source'
     .then (ref) ->
       new Promise (resolve, reject) ->
         action args..., (err) ->


Update copy task execution

Update copy task execution

--- gruntfile.coffee
+++ gruntfile.coffee
@@ -12,12 +12,12 @@
                                                                       'amd_tamer:dist']
   grunt.registerTask 'lint', 'Lints JavaScript and CSS files', ['jshint']

-  grunt.registerTask 'development', 'Development build', ['clean:build', 'prebuild', 'copy', 'bowercopy',
+  grunt.registerTask 'development', 'Development build', ['clean:build', 'prebuild', 'copy:assets', 'copy:root', 'bowercopy',
                                                           'stylus:development', 'amd_tamer:all',
                                                           'clean:build_debug_js',
                                                           'consolidate:development']
-  grunt.registerTask 'production', 'Production build', ['clean', 'prebuild', 'bowercopy:production', 'uglify',
-                                                        'copy', 'stylus:production', 'consolidate:production']
+  grunt.registerTask 'production', 'Production build', ['development', 'copy:production', 'uglify',
+                                                        'stylus:production', 'consolidate:production']
   grunt.registerTask 'pack', 'Packs project', ['production', 'compress']
   grunt.registerTask 'deploy', 'Builds project in production mode and starts a local server', ['production', 'connect']


prevent long webpack output

prevent long webpack output

--- app/templates/gulp/tasks/scripts.coffee
+++ app/templates/gulp/tasks/scripts.coffee
@@ -13,7 +13,7 @@
     if err
       console.log 'Error', err
     else
-      console.log stats.toString()
+      console.log stats.toString({ chunks: false })
       done()
     return


Update CampaignView.coffee (#6383)

Update CampaignView.coffee (#6383)

`->` override the `this`, so change to `=>`


Log errors to stderr.

Log errors to stderr.

--- src/cli.coffee
+++ src/cli.coffee
@@ -8,7 +8,7 @@
 {formatDate} = require '../lib/utils'

 error = (message) ->
-  console.log message
+  console.error message
   process.exit 1

 help = ->


slightly better :-P

slightly better :-P

--- apps/fair_organizer/client/index.coffee
+++ apps/fair_organizer/client/index.coffee
@@ -46,7 +46,7 @@
     modelName: "Fair"
     model: @fair
     el: $('.fair-organizer-top__countdown__clock')
-    closedText: 'Fair Closed'
+    closedText: 'TBA'
   @clock.start()
   new FairOrganizerView
     articles: new Articles(sd.ARTICLES)


Let's make this useful again

Let's make this useful again

--- scripts/deploy.coffee
+++ scripts/deploy.coffee
@@ -20,8 +20,8 @@

 module.exports = (robot) ->
   robot.respond /deployed\??/, (msg) ->
-    exec 'git name-rev --always HEAD', (err, stdout, stderr) ->
-      msg.send "Deployed: #{stdout.trim()}"
+    exec 'git name-rev --always HEAD && git rev-parse HEAD', (err, stdout, stderr) ->
+      msg.send "Deployed: #{stdout.split('\n').join('--').trim()}"

   robot.respond /deploy(\s@?[\w_\-/]+)?$/, (msg) ->
     remote = 'origin'


Results for all by default.

Results for all by default.

--- scripts/points.coffee
+++ scripts/points.coffee
@@ -126,5 +126,5 @@
             robot.brain.get points
             points[username] ?= 0

-            score = ["#{username} has #{points[username]}"]
+            score = ["#{username} has #{points[username]}" for username in people]
             msg.send "Stored scores: #{score}"


senpai change if adana

senpai change if adana

--- scripts/senpai.coffee
+++ scripts/senpai.coffee
@@ -7,8 +7,8 @@
 #   hubot 誰知ってるんですか? - hubot が知ってる人を教えてくれるよ！
 #   hubot {アダ名 or nickname} って誰ですか？ - 誰かよくわからない場合は hubot に聞いてみよう！
 #   hubot {nickname} のアダ名何ですか?  - ものしりな hubot にアダ名を教えてもらおう！
-#   hubot {アダ名} は {nickname} のアダ名です  - hubot にアダ名を教えるよ！
-#   hubot {アダ名} は {nickname} のアダ名じゃないです  - hubot にアダ名が間違いだったことをを教えるよ！
+#   hubot {nickname} は {アダ名} って呼ばれてます - hubot にアダ名を教えるよ！
+#   hubot {nickname} は {アダ名} って呼ばれてないです  - hubot にアダ名が間違いだったことを教えるよ！
 #   {アダ名 or nickname}++  - イイネ！
 #   {アダ名 or nickname}--  - ヨクナイネ！
 #   hubot {アダ名 or nickname} 何点ですか?  - hubot に後輩の点数を教えてもらおう!
@@ -85,7 +85,7 @@
 # }}}

 # {{{ Keigo
-REGEXP_KEIGO = '(です|デス|desu|ます|マス|masu|っす|ッス)か?[\?？!！]?'
+REGEXP_KEIGO = '(です|デス|desu|ます|マス|masu|っす|ッス|ません|マセン|masen)か?[\?？!！]?'

 isKeigo = (robot, msg) ->
   msg.message.match(REGEXP_KEIGO) isnt null
@@ -170,10 +170,10 @@

 # }}}

-  robot.respond /([^ ]+)[ ]*は[ ]*([^ ]+)[ ]*のアダ名/, (msg) -># {{{
-    return if msg.message.match('じゃない')
-    nickname = msg.match[1]
-    realname = msg.match[2]
+  robot.respond /([^ ]+)[ ]*は[ ]*([^ ]+)[ ]*って呼ばれ/, (msg) -># {{{
+    return if msg.message.match('ない|ません')
+    realname = msg.match[1]
+    nickname = msg.match[2]
     unless existsUser robot, msg, realname
       msg.send "#{realname} って誰？"
       checkKeigo robot, msg
@@ -204,9 +204,9 @@
     checkKeigo robot, msg
 # }}}

-  robot.respond /([^ ]+)[ ]*は[ ]*([^ ]+)[ ]*のアダ名(じゃない)/, (msg) -># {{{
-    nickname = msg.match[1]
-    realname = msg.match[2]
+  robot.respond /([^ ]+)[ ]*は[ ]*([^ ]+)[ ]*って呼ばれて(ない|ません)/, (msg) -># {{{
+    realname = msg.match[1]
+    nickname = msg.match[2]
     unless existsUser robot, msg, realname
       msg.send "そもそも #{realname} って誰?"
       checkKeigo robot, msg
@@ -217,8 +217,13 @@
     nicknames = getUserInfo robot, msg, realname, 'NICKNAMES'
     nicknames ||= []

-    unless nickname in nicknames
-      msg.send "#{realname} は #{nickname} なんて呼ばれてないで"
+    gRealname = gNicknames[nickname]
+    if gRealname? and gRealname isnt realname
+      msg.send "だってそれ #{gRealname} のアダ名やろ？"
+      checkKeigo robot, msg
+      return
+    else unless nickname in nicknames
+      msg.send "#{realname} が #{nickname} って呼ばれてるなんて聞いたことないでw"
       checkKeigo robot, msg
       return

@@ -228,7 +233,6 @@
     gNicknames[nickname] = null
     setSenpaiStorage robot, msg, 'NICKNAMES', gNicknames
     newNicknames = (item for item in nicknames when item isnt nickname)
-    msg.send "nick #{newNicknames}"
     setUserInfo robot, msg, realname, 'NICKNAMES', newNicknames
     checkKeigo robot, msg
 # }}}
@@ -271,7 +275,7 @@
     name = msg.match[1]
     user = whoIsThis robot, msg, name
     unless user?
-      msg.send "#{user} ってしらねーなぁ"
+      msg.send "#{name} ってしらねーなぁ"
       return

     count = getUserInfo robot, msg, user, 'COUNT'


not mine

not mine

--- scripts/senpai.coffee
+++ scripts/senpai.coffee
@@ -383,6 +383,9 @@
     unless fromuser
       return

+    if fromname is robot.name
+      return
+
     nickname = fromname
     nicknames = getUserInfo robot, msg, fromuser, 'NICKNAMES'
     if nicknames.length > 1


refactor task file

refactor task file

--- tasks/emblem.coffee
+++ tasks/emblem.coffee
@@ -20,9 +20,9 @@
     window.run grunt.file.read options.paths.emblem, 'utf8'
     if options.paths.ember
       window.run grunt.file.read options.paths.ember, 'utf8'
-      emberEnabled = true
+      templateBuilder = new EmberBuilder(window, rootPath: options.root)
     else
-      emberEnabled = false
+      templateBuilder = new VanillaBuilder(window, rootPath: options.root)

     grunt.verbose.writeflags(options, 'Options')
@@ -33,18 +33,13 @@
       # iterate files, processing partials and templates separately
       f.src.filter(fileExists).forEach (filepath) ->
         src = grunt.file.read(filepath)
-        key = keyForFilePath(filepath, options.root)
-
-        compiler = if emberEnabled then compileEmber else compileVanilla

         try
-          compiled = compiler.call(this, src, window, key)
+          templates.push templateBuilder.build(src, filepath)
         catch e
           grunt.fail.warn e
           # Warn on and remove invalid source files (if nonull was set).
           grunt.fail.warn "Emblem failed to compile " + filepath + "."
-
-        templates.push(compiled)

       if templates.length < 1
         grunt.log.warn "
@@ -67,36 +62,6 @@

   ########################################################
-  # Vanilla Compilation -
-  # Compiles the file at provided filepath and returns the output
-  ########################################################
-  compileVanilla = (src, window, key) ->
-    content = window.Emblem.precompile window.Handlebars, src
-
-    """
-    var templates = Handlebars.templates = Handlebars.templates || {};
-    templates['#{key}'] = Handlebars.template(#{content});
-    """
-
-  ########################################################
-  # Ember Compilation
-  ########################################################
-  compileEmber = (src, window, key) ->
-    key = JSON.stringify(key)
-    compiled = window.Emblem.precompile(window.Ember.Handlebars, src)
-    template = "Ember.Handlebars.template(#{compiled})"
-    "Ember.TEMPLATES[#{key}] = #{template};"
-
-  ########################################################
-  # Key for given filepath
-  ########################################################
-  keyForFilePath = (filepath, root) ->
-    key = filepath
-      .replace(new RegExp('\\\\', 'g'), '/') #replace backslashes
-      .replace(/\.\w+$/, '') #remove extension
-      .replace(root, '')
-
-  ########################################################
   # Filters out missing files
   ########################################################

@@ -107,9 +72,34 @@
     else
       true

-  isPartial = (filepath) ->
-    isPartialFilename _.last filepath.split '/'

-  isPartialFilename = (filename) ->
-    regex = /^_/
-    regex.test(filename)
+########################################################
+# TODO extract these classes
+########################################################
+class TemplateBuilder
+  constructor: (window, options = {}) ->
+    @window = window
+    @rootPath = options?.rootPath
+
+  keyForFilePath: (filepath) ->
+    filepath
+      .replace(new RegExp('\\\\', 'g'), '/') #replace backslashes
+      .replace(/\.\w+$/, '') #remove extension
+      .replace(@rootPath, '')
+
+class EmberBuilder extends TemplateBuilder
+  build: (src, filepath) ->
+    key = JSON.stringify @keyForFilePath(filepath)
+    compiled = @window.Emblem.precompile(@window.Ember.Handlebars, src)
+    template = "Ember.Handlebars.template(#{compiled})"
+    "Ember.TEMPLATES[#{key}] = #{template};"
+
+class VanillaBuilder extends TemplateBuilder
+  build: (src, filepath) ->
+    key = @keyForFilePath(filepath)
+    content = @window.Emblem.precompile @window.Handlebars, src
+
+    """
+    var templates = Handlebars.templates = Handlebars.templates || {};
+    templates['#{key}'] = Handlebars.template(#{content});
+    """


Change images/row if total margin > image width so stay closer to targetWidth

--- src/index.cjsx
+++ src/index.cjsx
@@ -50,23 +50,34 @@
       <div />

   calculateImageWidth: ->
-    # Calculate the # of images per row to place.
-    imageCount = React.Children.count(@props.children)
-    imagesPerRow = Math.round(@state.componentWidth/@props.targetWidth)
+    _calcImageWidth = (adjustImagesPerRow = 0) =>
+      # Calculate the # of images per row to place.
+      imageCount = React.Children.count(@props.children)
+      imagesPerRow = Math.round(@state.componentWidth/@props.targetWidth)
+      imagesPerRow = imagesPerRow - adjustImagesPerRow

-    # There has to be at least one image per row.
-    imagesPerRow = Math.max(imagesPerRow, 1)
-    if imagesPerRow > imageCount
-      imagesPerRow = imageCount
+      # There has to be at least one image per row.
+      imagesPerRow = Math.max(imagesPerRow, 1)
+      if imagesPerRow > imageCount
+        imagesPerRow = imageCount

-    # Calculate the per-image width with space for in-between
-    # images subtracted
-    rawWidth = @state.componentWidth / imagesPerRow
-    marginRightOffset = ((@props.margin * imagesPerRow) - @props.margin) / imagesPerRow
-    imageWidth = rawWidth - marginRightOffset - 0.25
+      # Calculate the per-image width with space for in-between
+      # images subtracted
+      rawWidth = @state.componentWidth / imagesPerRow
+      marginRightOffset = ((@props.margin * imagesPerRow) - @props.margin) / imagesPerRow
+      imageWidth = rawWidth - marginRightOffset - 0.25

-    # Don't get too big.
-    maxWidth = if @props.maxWidth? then @props.maxWidth else @props.targetWidth * 1.5
-    imageWidth = Math.min(imageWidth, maxWidth)
+      # Don't get too big.
+      maxWidth = if @props.maxWidth? then @props.maxWidth else @props.targetWidth * 1.5
+      imageWidth = Math.min(imageWidth, maxWidth)
+      return [imageWidth, imagesPerRow, marginRightOffset]
+
+    [imageWidth, imagesPerRow, marginRightOffset] = _calcImageWidth()
+
+    # If the total margin used in a row is greater than one image, drop the images
+    # per row.
+    if imageWidth < marginRightOffset * imagesPerRow
+      adjustment = Math.round((marginRightOffset*imagesPerRow)/imageWidth)
+      [imageWidth, imagesPerRow, marginRightOffset] = _calcImageWidth(adjustment)

     return [imageWidth, imagesPerRow]


add test for uploadurl

add test for uploadurl

--- caspertests/tests/blobstoreimage.coffee
+++ caspertests/tests/blobstoreimage.coffee
@@ -8,9 +8,8 @@
   casper.thenOpen settings.baseURL() + "/admin/api/v1/blobstoreimage", ->
     test.assertHttpStatus 200

-  casper.thenOpen settings.baseURL() + "/admin/api/v1/blobstoreimage", ->
+  casper.thenOpen settings.baseURL() + "/admin/api/v1/blobstoreimage/uploadurl", ->
     test.assertHttpStatus 200
-

   # add entity by post
   casper.thenOpen settings.baseURL() + "/admin/api/v1/blobstoreimage",


Extend map bounds more

Extend map bounds more


resurrection: handle current path if no pane found

--- client/Main/maintabs/maintabview.coffee
+++ client/Main/maintabs/maintabview.coffee
@@ -102,7 +102,7 @@
       if leftPane.mainView
         @appManager.showInstance @appManager.getByView leftPane.mainView
     else
-      @router.handleRoute "/Activity"
+      @router.handleRoute @router.currentPath

   createTabPane:(options = {}, mainView)->


instructions: improve position calculation of pointer animations

--- client/app/MainApp/book/bookview.coffee
+++ client/app/MainApp/book/bookview.coffee
@@ -415,7 +415,8 @@
         @startNewConversation()

     # find conversations panel icon position.
-    offsetTo = @mainView.chatHandler.$().offset()
+    @setClass "moveUp"
+    offsetTo = @mainView.sidebar.footerMenu.$(".chat").offset()
     # move cursor to conv. panel button position.
     @pointer.$().offset offsetTo

@@ -523,31 +524,36 @@
     @pointer.$().offset offsetTo

   saveAndOpenPreview:->
+    button = @mainView.appSettingsMenuButton
+    # find right up ace save menu position
+    @pointer.$().offset button.$().offset()
     @pointer.once 'transitionend', =>
       # click animation
       @clickAnimation()
       # open menu
-      @mainView.appSettingsMenuButton.$().click()
+      button.$().click()
       # find save menu item position
+      @pointer.$().offset $(button.contextMenu.$("li")[0]).offset()
       @utils.wait 2000, =>
         # save ace view
-        @mainView.appSettingsMenuButton.data[0].callback()
-        @utils.wait 800, =>
+        button.data.items[0].callback()
+        @utils.wait 2000, =>
           @openPreview()
-
-    # find right up ace save menu position
-    offsetTo = @mainView.appSettingsMenuButton.$().offset()
-    @pointer.$().offset offsetTo

   openPreview:->
     new KDNotificationView
       title     : "Let's see what changed!"
       duration  : 3000

-    @mainView.appSettingsMenuButton.$().click()
+    button = @mainView.appSettingsMenuButton
+    @pointer.$().offset button.$().offset()
     @utils.wait 2200, =>
-      @mainView.appSettingsMenuButton.data[8].callback()
-      @destroyPointer()
+      button.$().click()
+      @utils.wait 800, =>
+        @pointer.$().offset $(button.contextMenu.$("li")[7]).offset()
+        @utils.wait 2200, =>
+          button.data.items[9].callback()
+          @destroyPointer()

   showAceSettings:->
     @pointer.once 'transitionend', =>


[fix] Add credentials to password fetch call, for authentication

--- client/app/config/steps/password.coffee
+++ client/app/config/steps/password.coffee
@@ -28,6 +28,7 @@
     save: (data) ->
         return fetch '/register/password',
             method: 'POST',
+            credentials: 'include',
             body: JSON.stringify data
         .then @handleSaveSuccess, @handleServerError
 }


Client: Add focusLobby as it is in use elsewhere.

--- client/app/js/views/sidebar_view.coffee
+++ client/app/js/views/sidebar_view.coffee
@@ -111,6 +111,15 @@
     else
       $notifications.addClass('hidden')

+  focusLobby: =>
+    # TODO: Clean this up once rooms are implemented
+    # right now it duplicates part of focusRoom()
+    $lobbyLink = @$(".nav_rooms li").first()
+    @resetNotifications($lobbyLink)
+    $room = $('.chat_window')
+    @changeWindowTo($room, $lobbyLink)
+    PokeBattle.router.navigate("")
+
   leaveBattleEvent: (e) =>
     $navItem = $(e.currentTarget).closest('.nav_item')
     battleId = $navItem.data('battle-id')
@@ -134,13 +143,7 @@
     @changeWindowToBattle(battleId)

   focusLobbyEvent: (e) =>
-    # TODO: Clean this up once rooms are implemented
-    # right now it duplicates part of focusRoom()
-    $lobbyLink = @$(".nav_rooms li").first()
-    @resetNotifications($lobbyLink)
-    $room = $('.chat_window')
-    @changeWindowTo($room, $lobbyLink)
-    PokeBattle.router.navigate("")
+    @focusLobby()

   focusRoomEvent: (e) =>
     $this = $(e.currentTarget)


ConnectionChecker: move logic into base class, update flow for new browsers

Currently we were using window.navigator.onLine functionality and it has some problems with in some browsers this aims to fix those issues

--- client/app/lib/connectionchecker.coffee
+++ client/app/lib/connectionchecker.coffee
@@ -1,28 +1,67 @@
 kd = require 'kd'
-KDObject = kd.Object
-$ = require 'jquery'
+$  = require 'jquery'

-class ConnectionChecker extends KDObject
+PING_URL = 'https://s3.amazonaws.com/koding-ping/ping.json'

-  constructor: (options, data)->
-    super options, data
+class ConnectionChecker extends kd.Object

-    @url  = @getData()
+  @globalNotification = do ->

-    {@fail, @jsonp} = @getOptions()
+    notification = null

-  ping: (callback) ->
-    {crossDomain} = @getOptions()
-    # if there are more than two consecutive crossDomain calls
-    # this window.jsonp will be overriden and it will cause errors - CtF
-    ConnectionChecker.jsonp = callback  if crossDomain
+    show: ->
+      notification   ?= new kd.NotificationView
+        title         : 'Looks like your Internet connection is down'
+        type          : 'tray'
+        closeManually : yes
+        duration      : 0
+        content       : """
+
Koding will continue trying to reconnect but while your
+          connection is down,

+          no changes you make will be saved back to your VM.
+          Please save your work locally as well.

+        """
+
+    hide: ->
+      notification?.destroy()
+      notification = null
+
+
+  @listen = ->
+
+    global.connectionCheckerReponse = => @globalNotification.hide()
+
+    global.addEventListener 'online', =>
+      @globalNotification.hide()
+    , false
+
+    global.addEventListener 'offline', =>
+      @globalNotification.show()
+    , false
+
+    # Navigator online/offline events are working fine with Chrome
+    # but not ok with Safari and Firefox currently. So for FF and Safari
+    # we are also initiating traditional ping method to check connection ~ GG
+    unless /Chrome/.test global.navigator.userAgent
+      kd.utils.repeat 20000, => @ping()
+
+
+  @ping = (callback = kd.noop) ->
+
+    kallback = (state) =>
+      if state is 'online'
+        @globalNotification.hide()
+        callback()
+      else
+        @globalNotification.show()

     $.ajax
-      url     : @url
-      success : -> callback arguments...
-      jsonpCallback : @jsonp  if @jsonp
-      timeout : 5000
-      dataType: "jsonp"
-      error   : => @fail? arguments...
+      url           : PING_URL
+      success       : -> kallback 'online'
+      jsonpCallback : 'connectionCheckerReponse'
+      timeout       : 5000
+      dataType      : 'jsonp'
+      error         : -> kallback 'offline'
+

 module.exports = ConnectionChecker


Add Jasmine tests to cover AJAX error notifications

--- cms/static/coffee/spec/main_spec.coffee
+++ cms/static/coffee/spec/main_spec.coffee
@@ -22,3 +22,37 @@

   it "setup AJAX CSRF token", ->
     expect($.ajaxSettings.headers["X-CSRFToken"]).toEqual("stubCSRFToken")
+
+describe "AJAX Errors", ->
+  tpl = readFixtures('system-feedback.underscore')
+
+  beforeEach ->
+    setFixtures($("<script>", {id: "system-feedback-tpl", type: "text/template"}).text(tpl))
+    appendSetFixtures(sandbox({id: "page-notification"}))
+    @requests = requests = []
+    @xhr = sinon.useFakeXMLHttpRequest()
+    @xhr.onCreate = (xhr) -> requests.push(xhr)
+
+  afterEach ->
+    @xhr.restore()
+
+  it "successful AJAX request does not pop an error notification", ->
+    expect($("#page-notification")).toBeEmpty()
+    $.ajax("/test")
+    expect($("#page-notification")).toBeEmpty()
+    @requests[0].respond(200)
+    expect($("#page-notification")).toBeEmpty()
+
+  it "AJAX request with error should pop an error notification", ->
+    $.ajax("/test")
+    @requests[0].respond(500)
+    expect($("#page-notification")).not.toBeEmpty()
+    expect($("#page-notification")).toContain('div.wrapper-notification-error')
+
+  it "can override AJAX request with error so it does not pop an error notification", ->
+    $.ajax
+      url: "/test"
+      notifyOnError: false
+    @requests[0].respond(500)
+    expect($("#page-notification")).toBeEmpty()
+


Fixes delete page confirmation text.

Fixes delete page confirmation text.

--- cms/static/coffee/src/views/tabs.coffee
+++ cms/static/coffee/src/views/tabs.coffee
@@ -101,8 +101,8 @@

     deleteTab: (event) =>
       confirm = new PromptView.Warning
-        title: gettext('Delete Component Confirmation')
-        message: gettext('Are you sure you want to delete this component? This action cannot be undone.')
+        title: gettext('Delete Page Confirmation')
+        message: gettext('Are you sure you want to delete this page? This action cannot be undone.')
         actions:
           primary:
             text: gettext("OK")


Run all actions synchronously

Run all actions synchronously

--- bootstrap/mecano.coffee
+++ bootstrap/mecano.coffee
@@ -44,7 +44,7 @@
         if arguments.length is 2
           callback = options
           options = goptions
-          goptions = {parallel: true}
+          goptions = {parallel: 1}
         if action is 'mkdir' and typeof options is 'string'
           options = m action, destination: options
         if Array.isArray options


actually call the function

actually call the function

--- browser/frontend.coffee
+++ browser/frontend.coffee
@@ -244,7 +244,7 @@
       # and cancel the timer
       viewer.onload = ->
         clearTimeout timer
-        send_dump
+        send_dump()

       controller.reprompt()
       return null


Fix order

Fix order

--- server/bw/move.coffee
+++ server/bw/move.coffee
@@ -58,10 +58,10 @@

     targetsHit = []
     for target in targets
+      continue  if @use(battle, user, target, hitNumber) == false
       if target.shouldBlockExecution(this, user) == true
         @afterFail(battle, user, target)
         continue
-      continue  if @use(battle, user, target, hitNumber) == false
       targetsHit.push(target)
       numHits = @calculateNumberOfHits(battle, user, targets)
       for hitNumber in [1..numHits]


update framework for server code

update framework for server code

--- server/friends.coffee
+++ server/friends.coffee
@@ -0,0 +1,124 @@
+###
+ * Federated Wiki : Node Server
+ *
+ * Copyright Ward Cunningham and other contributors
+ * Licensed under the MIT license.
+ * https://github.com/fedwiki/wiki-node-server/blob/master/LICENSE.txt
+###
+# **security.coffee**
+# Module for default site security.
+#
+# This module is not intented for use, but is here to catch a problem with
+# configuration of security. It does not provide any authentication, but will
+# allow the server to run read-only.
+
+#### Requires ####
+fs = require 'fs'
+
+
+# Export a function that generates security handler
+# when called with options object.
+module.exports = exports = (log, loga, argv) ->
+  security={}
+
+  #### Private utility methods. ####
+
+  user = ''
+
+  owner = ''
+
+  admin = argv.admin
+
+  # save the location of the identity file
+  idFile = argv.id
+
+  #### Public stuff ####
+
+  # Retrieve owner infomation from identity file in status directory
+  # owner will contain { name: <name>, friend: {secret: '...'}}
+  security.retrieveOwner = (cb) ->
+    fs.exists idFile, (exists) ->
+      if exists
+        fs.readFile(idFile, (err, data) ->
+          if err then return cb err
+          owner = JSON.parse(data)
+          cb())
+      else
+        owner = ''
+        cb()
+
+  # Return the owners name
+  security.getOwner = ->
+    if !owner.name?
+      ownerName = ''
+    else
+      ownerName = owner.name
+    ownerName
+
+  security.setOwner = setOwner = (id, cb) ->
+    owner = id
+    fs.exists idFile, (exists) ->
+      if !exists
+        fs.writeFile(idFile, id, (err) ->
+          if err then return cb err
+          # console.log "Claiming site for #{id}"
+          owner = id
+          cb())
+      else
+        cb()
+
+
+  security.getUser = (req) ->
+    if req.session.friend
+      return req.session.friend
+    else
+      return ''
+
+  security.isAuthorized = ->
+    if req.session.friend is owner.friend.secret
+      return true
+    else
+      return false
+
+  # Wiki server admin
+  security.isAdmin = ->
+    if req.session.friend is admin
+      return true
+    else
+      return false
+
+  security.login = (updateOwner) ->
+    (req, res) ->
+      ###
+      if owner is not '' -- so site is claimed
+        check that the provided access code is the same as that used to claim the site
+
+      if owner is '' -- site is not claimed
+        secret = require('crypto').randomBytes(64).toString('hex')
+        create a secret and write it to owner file and the cookie
+
+        res.session.friend = secret
+        id = {name: 'a friend', friend: {secret: secret}}
+
+      ###
+
+
+  security.logout = () ->
+  (req, res) ->
+    console.log "Logout..."
+
+  security.defineRoutes = (app, cors, updateOwner) ->
+
+    app.post '/login', cors, security.login(updateOwner)
+
+    ### /auth/reclaim#df89usy6pew98ryb
+    app.post '/auth/reclaim', cors, security.reclaim
+    ###
+
+    app.post '/logout', cors, (req, res) ->
+      req.session.reset()
+      security.logout()
+      res.send("OK")
+
+
+  security


clean up

clean up

--- server/methods.coffee
+++ server/methods.coffee
@@ -6,7 +6,7 @@
   # Creates stripe customer then updates the user document with the stripe customerId and cardId
   #
   createCustomer: (userId, card) ->
-    console.log 'Creating customer for', userId
+    console.log 'Creating customer for', userId
     user = BillingUser.first(_id: userId)
     unless user then throw new Meteor.Error 404, "User not found.  Customer cannot be created."

@@ -30,6 +30,9 @@
         future.throw new Meteor.Error error.error, error.message
     future.wait()

+  #
+  # Update stripe subscription for user with provided plan and quantitiy
+  #
   updateSubscription: (userId, plan, quantity) ->
     console.log 'Updating subscription for', userId
     user = BillingUser.first(_id: userId)
@@ -58,11 +61,11 @@
         future.throw error
     future.wait()

-
   #
   # Manually cancels the stripe subscription for the provided customerId
   #
   cancelSubscription: (customerId) ->
+    console.log 'Canceling subscription for', customerId
     user = BillingUser.first('profile.customerId': customerId)
     unless user then new Meteor.Error 404, "User not found.  Subscription cannot be canceled."

@@ -78,9 +81,10 @@
     future.wait()

   #
-  # Remove subscriptionId,
+  # A subscription was deleted from Stripe, remove subscriptionId and card from user.
   #
   subscriptionDeleted: (customerId) ->
+    console.log 'Subscription deleted for', customerId
     user = BillingUser.first('profile.customerId': customerId)
     unless user then new Meteor.Error 404, "User not found.  Subscription cannot be deleted."

@@ -106,6 +110,7 @@
   # Restart a subscription that was previously canceled
   #
   restartSubscription: (userId, card) ->
+    console.log 'Restarting subscription for', userId
     user = BillingUser.first(_id: userId)
     if user then customerId = user.profile.customerId
     unless user and customerId then new Meteor.Error 404, "User not found.  Subscription cannot be restarted."
@@ -127,11 +132,11 @@
         future.throw error
     future.wait()

-
   #
   # Get past invoices
   #
   getInvoices: ->
+    console.log 'Getting past invoices for', Meteor.userId()
     future = new Future()
     Stripe = StripeAPI(Billing.settings.secretKey)
     Stripe.invoices.list customer: Meteor.user().profile.customerId, (error, invoices) ->
@@ -139,11 +144,11 @@
       else future.return invoices
     future.wait()

-
   #
   # Get next invoice
   #
   getUpcomingInvoice: ->
+    console.log 'Getting upcoming invoice for', Meteor.userId()
     future = new Future()
     Stripe = StripeAPI(Billing.settings.secretKey)
     Stripe.invoices.retrieveUpcoming customer: Meteor.user().profile.customerId, (error, upcoming) ->


Fixed bug with async callback looking for done(), removed async wrapper.

--- server/methods.coffee
+++ server/methods.coffee
@@ -2,19 +2,38 @@
   updateQuote: (ticker) ->
     lastQuote = Quotes.findOne(ticker: ticker)

-    result = Async.runSync (done) ->
-      HTTP.get "http://marketdata.websol.barchart.com/getQuote.json",
-        params:
-          key: Meteor.settings.private.stockquotes.barchartDataKey
-          symbols: ticker
-        (err, res) ->
-          # Create a new quote or update an existing one
-          if err
-            throw new Meteor.Error 500, "Could not retrieve quote data from api"
+    HTTP.get "http://marketdata.websol.barchart.com/getQuote.json",
+      params:
+        key: Meteor.settings.private.stockquotes.barchartDataKey
+        symbols: ticker
+      (err, res) ->
+        # Create a new quote or update an existing one
+        if err
+          throw new Meteor.Error 500, "Could not retrieve quote data from api"

-          result = res.data.results[0]
-          if not lastQuote
-            Quotes.insert
+        result = res.data.results[0]
+        if not lastQuote
+          Quotes.insert
+              symbol: result.symbol
+              exchange: result.exchange
+              name: result.name
+              serverTimestamp: result.serverTimestamp
+              lastPrice: result.lastPrice
+              tradeTimeStamp: result.tradeTimestamp
+              netChange: result.netChange
+              percentChange: result.percentChange
+              open: result.open
+              high: result.high
+              low: result.low
+              close: result.close
+              numTrades: result.numTrades
+              dollarVolume: result.dollarVolume
+              volume: result.volume
+              previousVolume: result.previousVolume
+        else
+          Quotes.update
+              _id: lastQuote._id,
+              $set:
                 symbol: result.symbol
                 exchange: result.exchange
                 name: result.name
@@ -31,23 +50,3 @@
                 dollarVolume: result.dollarVolume
                 volume: result.volume
                 previousVolume: result.previousVolume
-          else
-            Quotes.update
-                _id: lastQuote._id,
-                $set:
-                  symbol: result.symbol
-                  exchange: result.exchange
-                  name: result.name
-                  serverTimestamp: result.serverTimestamp
-                  lastPrice: result.lastPrice
-                  tradeTimeStamp: result.tradeTimestamp
-                  netChange: result.netChange
-                  percentChange: result.percentChange
-                  open: result.open
-                  high: result.high
-                  low: result.low
-                  close: result.close
-                  numTrades: result.numTrades
-                  dollarVolume: result.dollarVolume
-                  volume: result.volume
-                  previousVolume: result.previousVolume


fix indentation

fix indentation

--- src/wow.coffee
+++ src/wow.coffee
@@ -88,7 +88,7 @@
     if @scrolled
       @scrolled = false
       for i in [0..(@boxes.length - 1)]
-       if @boxes[i]? and @isVisible(@boxes[i])
+        if @boxes[i]? and @isVisible(@boxes[i])
           @show(@boxes[i])
           @boxes[i] = null
           @visibleCount++


removeAttr

removeAttr

--- src/xml.coffee
+++ src/xml.coffee
@@ -114,6 +114,17 @@
         @headers = "<#{@name}#{new_attrs @attrs}" if @headers
         this

+    removeAttr: (key) =>
+        if typeof key is 'string'
+            delete @attrs[key]
+            @emit 'attr:remove', this, key
+        else
+            for own k, v of key
+                delete @attr[key]
+                @emit 'attr:remove', this, key
+        @headers = "<#{@name}#{new_attrs @attrs}" if @headers
+        this
+
     text: (content, {force, escape} = {}) =>
         return unless content or force
         if escape


Simplification

Simplification

--- sync.coffee
+++ sync.coffee
@@ -15,9 +15,7 @@
     # publish methods and sync on model
     @model_type.model_name = Utils.parseUrl(@url).model_name unless @model_type.model_name # model_name can be manually set
     throw new Error('Missing model_name for model') unless @model_type.model_name
-    @model_type._sync = @
-    @model_type._schema = new Schema(@model_type)
-
+    @schema = new Schema(@model_type)
     @request = require 'superagent'

     ###################################
@@ -38,7 +36,7 @@

   initialize: (model) ->
     return if @is_initialized; @is_initialized = true
-    @model_type._schema.initialize()
+    @schema.initialize()

   ###################################
   # Backbone ORM - Class Extensions
@@ -54,9 +52,6 @@
         return callback(new Error "Ajax failed with status #{res.status} for #{'destroy'} with: #{util.inspect(res.body)}") unless res.ok
         callback(null, res.body)

-  schema: (key) -> @model_type._schema
-  relation: (key) -> @model_type._schema.relation(key)
-

 module.exports = (model_type, cache) ->
   sync = new AjaxSync(model_type)
@@ -66,6 +61,7 @@

     return module.exports.apply(null, Array::slice.call(arguments, 1)) if method is 'createSync' # create a new sync
     return sync if method is 'sync'
+    return sync.schema if method is 'schema'

     ###################################
     # Classic Backbone Sync
@@ -95,7 +91,7 @@
     ###################################
     # Backbone ORM Sync
     ###################################
-    sync[method].apply(sync, Array::slice.call(arguments, 1))
+    if sync[method] then sync[method].apply(sync, Array::slice.call(arguments, 1)) else return undefined

   require('backbone-orm/lib/model_extensions')(model_type) # mixin extensions
   return if cache or _.isUndefined(cache) then require('backbone-orm/lib/cache_sync')(model_type, sync_fn) else sync_fn


Added lots of fun API calls

Added lots of fun API calls

probably should rename this file or something

--- js/coffee.coffee
+++ js/coffee.coffee
@@ -1 +1,60 @@
-# Empty file to start with+# Summoner Name
+
+function summonerLookUp() {
+	var SUMMONER_NAME = "";
+	SUMMONER_NAME = $("#summonerName").val();
+
+	var REGION = "";
+	REGION = $("#region").val();
+
+	REQUEST_URL_SHORT = 'https://' + REGION + 'api.pvp.net/api/lol/' + REGION;
+
+	if (SUMMONER_NAME !== "") {
+
+		$.ajax({
+		url: REQUEST_URL_SHORT + '/v1.4/summoner/by-name/' + SUMMONER_NAME + '?api_key=' + $RIOT_SECRET,
+		type: 'GET',
+		dataType: 'json',
+		data: {
+
+			},
+			success: function (json) {
+				var SUMMONER_NAME_NOSPACES = SUMMONER_NAME.replace(" ", "");
+
+				SUMMONER_NAME_NOSPACES =
+				SUMMONER_NAME_NOSPACES.toLowerCase().trim();
+
+				summonerID =
+				json[SUMMONER_NAME_NOSPACES].id;
+
+
+			},
+			error: function (XMLHttpRequest, textStatus, errorThrown) {
+				alert("Error Getting Summoner Data");
+		}
+		});
+
+		$.ajax({
+		url: REQUEST_URL_SHORT + '/v1.3/stats/by-summoner/' + summonerID + '/ranked',
+		type: 'GET',
+		dataType: 'json',
+		data: {
+		},
+			success: function (json) {
+				champList =
+				json[summonerID].champions;
+
+				mostPlayedChamp =
+				function () {
+					for (i = 0, i < champList.length, i++) {
+						var MPC;
+						if (champList[i].totalSessionsPlayed > MPC.totalSessionsPlayed) {
+						MPC = champList[i];
+						}
+					}
+					return MPC;
+				}
+			}
+		})
+	}
+}


Refactor NewExpression and CallExpression to use the same sequence-building routine.

--- js2coffee.coffee
+++ js2coffee.coffee
@@ -212,11 +212,10 @@
     [ "this" ]

   CallExpression: (node, ctx) ->
-    @parenthesizeArguments(node)
     @parenthesizeCallee(node)

     callee = @walk(node.callee)
-    list = commaDelimit(node.arguments.map(@walk))
+    list = @makeSequence(node.arguments)

     hasArgs = list.length > 0
     node._isStatement = ctx.parent.type is 'ExpressionStatement'
@@ -273,7 +272,7 @@
     [ "###", lines, "###\n" ]

   FunctionDeclaration: (node) ->
-    params = @toParams(node.params)
+    params = @makeParams(node.params)

     @indent (indent) =>
       [ @walk(node.id), ' = ', params, "->\n", @walk(node.body) ]
@@ -352,7 +351,7 @@
     [ @walk(node.id), ' = ', init, "\n" ]

   FunctionExpression: (node, ctx) ->
-    params = @toParams(node.params)
+    params = @makeParams(node.params)

     expr = @indent (i) =>
       [ params, "->\n", @walk(node.body) ]
@@ -366,15 +365,13 @@
     [ ]

   NewExpression: (node) ->
-    @parenthesizeArguments(node)
-
     callee = if node.callee?.type is 'Identifier'
       [ @walk(node.callee) ]
     else
       [ '(', @walk(node.callee), ')' ]

     args = if node.arguments?.length
-      [ '(', commaDelimit(node.arguments.map(@walk)), ')' ]
+      [ '(', @makeSequence(node.arguments), ')' ]
     else
       []

@@ -460,7 +457,26 @@

     [ left, "\n", right ]

-  toParams: (params) ->
+  ###*
+  # makeSequence():
+  # Builds a comma-separated sequence of nodes.
+  ###
+
+  makeSequence: (list) ->
+    for arg, i in list
+      isLast = i is (list.length-1)
+      if arg.type is "FunctionExpression"
+        if not isLast
+          arg._parenthesized = true
+
+    commaDelimit(list.map(@walk))
+
+  ###*
+  # makeParams():
+  # Builds parameters for a function list.
+  ###
+
+  makeParams: (params) ->
     if params.length
       [ '(', delimit(params.map(@walk), ', '), ') ']
     else


fix target check

fix target check

--- lib/Proxy.coffee
+++ lib/Proxy.coffee
@@ -46,21 +46,18 @@
       res.end msg

     error 'missing host' unless req.headers.target
-    matches = req.headers.target.match(/^(?:http:\/\/)?([^:\/]+)?(?::(\d+))?(\/.+)?$/
-    if matches? and matches[1]
-      host = matches[1]
-      port = matches[2] or 80
-      path = matches[3] or '/'
-    else
-      error 'invalid host'
-    port ?= 80
+    matches = req.headers.target.match /^(?:http:\/\/)?([^:\/]+)?(?::(\d+))?(\/.+)?$/
+    error 'invalid host' unless matches? and matches[1]
+    host = matches[1]
+    port = matches[2] or 80
+    path = matches[3] or '/'
     error 'bad host' if host in @getBlocked()
     req.on 'error', => bounce.error 'invalid request'
     stream = net.createConnection port, host, @cycler.getIP()
     opts =
       host: host
       port: port
-      path:  path
+      path: path
       headers:
         "x-forwarded-for": undefined
         "x-forwarded-port": undefined


Added proper imports to lib/index.

Added proper imports to lib/index.

--- lib/index.coffee
+++ lib/index.coffee
@@ -21,6 +21,8 @@

 exports.dork = dork
+exports.reporters = require './reporters'
+exports.runner = require './runner'
 exports.suite = require './suite'
 exports.test = require './test'
 exports.utils = require './utils'