Update data explorer for new docs (with io field) + fix bugs (r.row, method_var's regex, few other tricky things)

--- admin/static/coffee/dataexplorer.coffee
+++ admin/static/coffee/dataexplorer.coffee
@@ -250,45 +250,63 @@
         descriptions: {}
         suggestions: {} # Suggestions[state] = function for this state

+        types:
+            value: ['number', 'bool', 'string', 'array', 'object']
+            any: ['number', 'bool', 'string', 'array', 'object', 'stream', 'selection', 'table', 'db', 'r', 'error' ]
+            sequence: ['table', 'selection', 'stream', 'array']
+
+        # Convert meta types (value, any or sequence) to an array of types or return an array composed of just the type
+        convert_type: (type) =>
+            if @types[type]?
+                return @types[type]
+            else
+                return [type]
+
+        # Flatten an array
+        expand_types: (ar) =>
+            result = []
+            if _.isArray(ar)
+                for element in ar
+                    result.concat @convert_type element
+            else
+                result.concat @convert_type element
+            return result
+
         # Once we are done moving the doc, we could generate a .js in the makefile file with the data so we don't have to do an ajax request+all this stuff
         set_doc_description: (command, tag, suggestions) =>
-            if tag is 'run' # run is a special case, we don't want use to pass a callback, so we change a little the body
-                full_tag = tag+'(' # full tag is the name plus a parenthesis (we will match the parenthesis too)
+            if command['langs']['js']['body']?
+                if command['langs']['js']['body'].match(/(\)(\s)*)$/)
+                    full_tag = tag+'(' # full tag is the name plus a parenthesis (we will match the parenthesis too)
+                else
+                    full_tag = tag # Here full_tag is just the name of the tag
+
                 @descriptions[full_tag] =
                     name: tag
-                    args: '( )'
+                    dont_need_parenthesis: command['langs']['js']['dont_need_parenthesis']
+                    args: /.*(\(.*\))$/.exec(command['langs']['js']['body'])?[1].replace('$PARENT', 'parentType')
                     description: @description_with_example_template
                         description: command['description']
                         examples: if command['langs']['js']['examples']?.length >1 then command['langs']['js']['examples'].slice(0,1) else command['langs']['js']['examples']
-            else
-                if command['langs']['js']['dont_need_parenthesis'] is true
-                    full_tag = tag # Here full_tag is just the name of the tag
-                else
-                    full_tag = tag+'(' # full tag is the name plus a parenthesis (we will match the parenthesis too)
-
-                @descriptions[full_tag] =
-                    name: tag
-                    dont_need_parenthesis: command['langs']['js']['dont_need_parenthesis']
-                    args: ('( '+command['langs']['js']['body']+' )' if command['langs']['js']['dont_need_parenthesis'] isnt true)
-                    description: @description_with_example_template
-                        description: command['description']
-                        examples: if command['langs']['js']['examples']?.length >1 then command['langs']['js']['examples'].slice(0,1) else command['langs']['js']['examples']
-
-            if command['parent']?
-                parents = command['parent']?.split(' | ')
-            else
-                parents = [null]
-
-            if parents?
-                for parent in parents
-                    if parent is null
-                        parent = ''
-                    if not suggestions[parent]?
-                        suggestions[parent] = []
-                    if tag isnt '' # We don't want to save ()
-                        suggestions[parent].push full_tag
-
-            @map_state[full_tag] = command['returns']?.split(' | ') # We use full_tag because we need to differentiate between r. and r(
+
+            parents = {}
+            returns = []
+            for pair in command.io
+                parent_values = if (pair[0] == null) then '' else pair[0]
+                return_values = pair[1]
+
+                parent_values = @convert_type parent_values
+                return_values = @convert_type return_values
+                returns = returns.concat return_values
+                for parent_value in parent_values
+                    parents[parent_value] = true
+
+            if full_tag isnt '('
+                for parent_value of parents
+                    if not suggestions[parent_value]?
+                        suggestions[parent_value] = []
+                    suggestions[parent_value].push full_tag
+
+            @map_state[full_tag] = returns # We use full_tag because we need to differentiate between r. and r(

         # All the commands we are going to ignore
         # TODO update the set of ignored commands for 1.4
@@ -310,41 +328,11 @@
                     tag = command['langs']['js']['name']
                     if tag of @ignored_commands
                         continue
-                    if tag is 'row' # In case we have row(, we give a better description than just (
-                        new_command = DataUtils.deep_copy command
-                        new_command['langs']['js']['dont_need_parenthesis'] = false
-                        new_command['langs']['js']['body'] = 'attr'
-                        new_command['description'] = 'Return the attribute of the currently visited document'
-                        @set_doc_description new_command, tag, @suggestions
-                    if tag is '()'
+                    if tag is '()' # The parentheses will be added later
                         tag = ''
                     @set_doc_description command, tag, @suggestions

             relations = data['types']
-
-            # For each element, we add its parent's functions to it suggestions
-            # We require the hierarchy to be a DAG else it's going to loop for ever
-            done = false
-            while done is false
-                done = true
-                for element of relations
-                    if not @suggestions[element]?
-                        @suggestions[element] = []
-                    if relations[element]['parents']?
-                        done = false
-
-                        index = 0
-                        while index < relations[element]['parents'].length
-                            parent = relations[element]['parents'][index]
-                            if not relations[parent]['parents']? # parent has all its method available
-                                if @suggestions[parent]?
-                                    for suggestion in @suggestions[parent]
-                                        @suggestions[element].push suggestion
-                                relations[element]['parents'].splice(index, 1)
-                            else # The suggestions for `parent` are not ready yet
-                                index++
-                        if relations[element]['parents'].length is 0
-                            relations[element]['parents'] = null

             for state of @suggestions
                 @suggestions[state].sort()
@@ -868,9 +856,11 @@
                         stack: stack
                         query: query_before_cursor
                         result: result
+                    result.suggestions = @uniq result.suggestions

                     if result.suggestions?.length > 0
                         for suggestion, i in result.suggestions
+                            result.suggestions.sort() # We could eventually sort things earlier with a merge sort but for now that should be enough
                             @current_suggestions.push suggestion
                             @.$('.suggestion_name_list').append @template_suggestion_name
                                 id: i
@@ -1212,6 +1202,7 @@
                 stack: stack
                 query: query_before_cursor
                 result: result
+            result.suggestions = @uniq result.suggestions

             if result.suggestions?.length > 0
                 for suggestion, i in result.suggestions
@@ -1241,13 +1232,27 @@
                     return false
             return true

+        # Similar to underscore's uniq but faster with a hashmap
+        uniq: (ar) ->
+            if not ar? or ar.length is 0
+                return ar
+            result = []
+            hash = {}
+            for element in ar
+                hash[element] = true
+            for key of hash
+                result.push key
+            result.sort()
+            return result
+
         # Extract information from the current query
         # Regex used
         regex:
             anonymous:/^(\s)*function\(([a-zA-Z0-9,\s]*)\)(\s)*{/
             loop:/^(\s)*(for|while)(\s)*\(([^\)]*)\)(\s)*{/
             method: /^(\s)*([a-zA-Z]*)\(/ # forEach( merge( filter(
-            method_var: /^(\s)*([a-zA-Z]+)\./ # r. r.row. (r.count will be caught later)
+            row: /^(\s)*row\(/
+            method_var: /^(\s)*(\d*[a-zA-Z][a-zA-Z0-9]*)\./ # r. r.row. (r.count will be caught later)
             return : /^(\s)*return(\s)*/
             object: /^(\s)*{(\s)*/
             array: /^(\s)*\[(\s)*/
@@ -1372,11 +1377,12 @@
                             if result_regex isnt null
                                 element.type = 'anonymous_function'
                                 list_args = result_regex[2]?.split(',')
-                                element.args = list_args
+                                element.args = []
                                 new_context = DataUtils.deep_copy context
                                 for arg in list_args
                                     arg = arg.replace(/(^\s*)|(\s*$)/gi,"") # Removing leading/trailing spaces
                                     new_context[arg] = true
+                                    element.args.push arg
                                 element.context = new_context
                                 to_skip = result_regex[0].length
                                 body_start = i+result_regex[0].length
@@ -1442,47 +1448,80 @@
                             # Check for a standard method
                             result_regex = @regex.method.exec query.slice new_start
                             if result_regex isnt null
-                                if stack[stack.length-1]?.type is 'function' or stack[stack.length-1]?.type is 'var' # We want the query to start with r. or arg.
-                                    element.type = 'function'
-                                    element.name = result_regex[0]
-                                    element.position = position+new_start
-                                    start += new_start-i
+                                result_regex_row = @regex.row.exec query.slice new_start
+                                if result_regex_row isnt null
+                                    position_opening_parenthesis = result_regex_row[0].indexOf('(')
+                                    element.type = 'function' # TODO replace with function
+                                    element.name = 'row'
+                                    stack.push element
+                                    size_stack++
+                                    if size_stack > @max_size_stack
+                                        return null
+                                    element =
+                                        type: 'function'
+                                        name: '('
+                                        position: position+3+1
+                                        context: context
+                                        complete: 'false'
+                                    stack_stop_char = ['(']
+                                    start += position_opening_parenthesis
                                     to_skip = result_regex[0].length-1+new_start-i
-                                    stack_stop_char = ['(']
                                     continue
+
                                 else
-                                    position_opening_parenthesis = result_regex[0].indexOf('(')
-                                    if position_opening_parenthesis isnt -1 and result_regex[0].slice(0, position_opening_parenthesis) of context
-                                        # Save the var
-                                        element.real_type = 'json'
-                                        element.type = 'var'
-                                        element.name = result_regex[0].slice(0, position_opening_parenthesis)
-                                        stack.push element
-                                        size_stack++
-                                        if size_stack > @max_size_stack
-                                            return null
-                                        element =
-                                            type: 'function'
-                                            name: '('
-                                            position: position+position_opening_parenthesis+1
-                                            context: context
-                                            complete: 'false'
+                                    if stack[stack.length-1]?.type is 'function' or stack[stack.length-1]?.type is 'var' # We want the query to start with r. or arg.
+                                        element.type = 'function'
+                                        element.name = result_regex[0]
+                                        element.position = position+new_start
+                                        start += new_start-i
+                                        to_skip = result_regex[0].length-1+new_start-i
                                         stack_stop_char = ['(']
-                                        start = position_opening_parenthesis
-                                        to_skip = result_regex[0].length-1
                                         continue
+                                    else
+                                        position_opening_parenthesis = result_regex[0].indexOf('(')
+                                        if position_opening_parenthesis isnt -1 and result_regex[0].slice(0, position_opening_parenthesis) of context
+                                            # Save the var
+                                            element.real_type = @types.value
+                                            element.type = 'var'
+                                            element.name = result_regex[0].slice(0, position_opening_parenthesis)
+                                            stack.push element
+                                            size_stack++
+                                            if size_stack > @max_size_stack
+                                                return null
+                                            element =
+                                                type: 'function'
+                                                name: '('
+                                                position: position+position_opening_parenthesis+1
+                                                context: context
+                                                complete: 'false'
+                                            stack_stop_char = ['(']
+                                            start = position_opening_parenthesis
+                                            to_skip = result_regex[0].length-1
+                                            continue
+                                        ###
+                                        # This last condition is a special case for r(expr)
+                                        else if position_opening_parenthesis isnt -1 and result_regex[0].slice(0, position_opening_parenthesis) is 'r'
+                                            element.type = 'var'
+                                            element.name = 'r'
+                                            element.real_type = @types.value
+                                            element.position = position+new_start
+                                            start += new_start-i
+                                            to_skip = result_regex[0].length-1+new_start-i
+                                            stack_stop_char = ['(']
+                                            continue
+                                        ###
+

                             # Check for method without parenthesis r., r.row., doc.
                             result_regex = @regex.method_var.exec query.slice new_start
                             if result_regex isnt null
                                 if result_regex[0].slice(0, result_regex[0].length-1) of context
                                     element.type = 'var'
-                                    element.real_type = 'json'
+                                    element.real_type = @types.value
                                 else
                                     element.type = 'function'
                                 element.position = position+new_start
                                 element.name = result_regex[0].slice(0, result_regex[0].length-1).replace(/\s/, '')
-                                start += new_start-i
                                 element.complete = true
                                 to_skip = element.name.length-1+new_start-i
                                 stack.push element
@@ -1494,6 +1533,7 @@
                                     context: context
                                     complete: false
                                 start = new_start+to_skip+1
+                                start -= new_start-i
                                 continue

                             # Look for a comma
@@ -1842,7 +1882,7 @@
                 if query.slice(start) of element.context
                     element.name = query.slice start
                     element.type = 'var'
-                    element.real_type = 'json'
+                    element.real_type = @types.value
                     element.complete = true
                 else if @regex.number.test(query.slice(start)) is true
                     element.type = 'number'
@@ -1986,24 +2026,22 @@
                 else if result.status is 'look_for_state'
                     if element.type is 'function' and element.complete is true
                         result.state = element.name
-                        if @map_state[element.name]
-                            hash_suggestions = {} # To avoid redundancy. add() can returns string OR number, and add() works on both
+                        if @map_state[element.name]?
                             for state in @map_state[element.name]
                                 if @suggestions[state]?
                                     for suggestion in @suggestions[state]
                                         if result.suggestions_regex.test(suggestion) is true
-                                            hash_suggestions[suggestion] = true
-                            for suggestion of hash_suggestions
-                                result.suggestions.push suggestion
+                                            result.suggestions.push suggestion
                         #else # This is a non valid ReQL function.
                         # It may be a personalized function defined in the data explorer...
                         result.status = 'done'
                     else if element.type is 'var' and element.complete is true
                         result.state = element.real_type
-                        if @suggestions[result.state]?
-                            for suggestion in @suggestions[result.state]
-                                if result.suggestions_regex.test(suggestion) is true
-                                    result.suggestions.push suggestion
+                        for type in result.state
+                            if @suggestions[type]?
+                                for suggestion in @suggestions[type]
+                                    if result.suggestions_regex.test(suggestion) is true
+                                        result.suggestions.push suggestion
                         result.status = 'done'
                     #else # Is that possible? A function can only be preceded by a function (except for r)


Load system tables for DataExplorer auto completion. Fixes #3498.

--- admin/static/coffee/dataexplorer.coffee
+++ admin/static/coffee/dataexplorer.coffee
@@ -529,12 +529,15 @@
             @fetch_data()

         fetch_data: =>
+            # We fetch all "proper" tables from `table_config`. In addition, we need
+            # to fetch the list of system tables separately.
             query = r.db(system_db).table('table_config')
                 .pluck('db', 'name')
                 .group('db')
                 .ungroup()
                 .map((group) -> [group("group"), group("reduction")("name").orderBy( (x) -> x )])
                 .coerceTo "OBJECT"
+                .merge(r.object(system_db, r.db(system_db).tableList().coerceTo("ARRAY")))
             @timer = driver.run query, 5000, (error, result) =>
                 if error?
                     # Nothing bad, we'll try again, let's just log the error


Refer to container.state.history instead of container.history -- See #2389

--- admin/static/coffee/dataexplorer.coffee
+++ admin/static/coffee/dataexplorer.coffee
@@ -89,7 +89,6 @@
         clear_history_view: (event) =>
             that = @
             @clear_history() # Delete from localstorage
-            @history_view.history = @history

             @history_view.clear_history event

@@ -3756,7 +3755,7 @@
             that = @
             event.preventDefault()
             @container.clear_history()
-            @history = @container.history
+            @history = @container.state.history

             @$('.query_history').slideUp 'fast', ->
                 $(@).remove()


added buffering support to logger

added buffering support to logger

--- src/Observatory.coffee
+++ src/Observatory.coffee
@@ -126,10 +126,11 @@
   constructor: (@name, @formatter)->
     @_loggers = []
     @isOn = true
+    @isOff = false

   # only emit messages if we are on
-  turnOn: -> @isOn = true
-  turnOff: -> @isOn = false
+  turnOn: -> @isOn = true; @isOff = false
+  turnOff: -> @isOn = false; @isOff = true

   # add new logger to listen to messages
   subscribeLogger: (logger)->
@@ -143,14 +144,14 @@
   # `logger` has to respond to `addMessage(msg)` call.
   # Normally, only system-wide loggers are used, subscription for specific emitters is to provide
   # finer-grained control.
-  emitMessage: (message)->
+  emitMessage: (message, buffer = false)->
     return unless @isOn
-    l.addMessage message for l in Observatory.getLoggers()
-    l.addMessage message for l in @_loggers if @_loggers.length > 0
+    l.addMessage message, buffer for l in Observatory.getLoggers()
+    l.addMessage message, buffer for l in @_loggers if @_loggers.length > 0
     message

-  emitFormattedMessage: (message)->
-    @emitMessage @formatter message if @isOn and @formatter? and (typeof @formatter is 'function')
+  emitFormattedMessage: (message, buffer = false)->
+    @emitMessage @formatter message, buffer if @isOn and @formatter? and (typeof @formatter is 'function')
     message

 # ### Logger


activity: be more explicit when invalidating cache

--- client/app/lib/socialapicontroller.coffee
+++ client/app/lib/socialapicontroller.coffee
@@ -127,8 +127,10 @@
     cachedItem = kd.singletons.socialapi.retrieveCachedItem typeConstant, plain.id

     if cachedItem
-      return  cachedItem  unless invalidateCache
-      unbindMessageListeners cachedItem
+      if invalidateCache
+        unbindMessageListeners
+      else
+        return cachedItem

     plain._id = plain.id


Collaboration: Create participants list.

--- client/ide/views/chat/settingspane.coffee
+++ client/ide/views/chat/settingspane.coffee
@@ -113,6 +113,17 @@
       endButton.disable()

+  createParticipantsList: ->
+
+    @everyone.unsetClass 'loading'
+    @everyone.destroySubViews()
+
+    participants = @rtm.getFromModel('participants').asArray()
+
+    for participant in participants
+      @everyone.addSubView new IDE.ChatParticipantView {}, participant
+
+
   viewAppended: JView::viewAppended

   setTemplate: JView::setTemplate
@@ -134,5 +145,3 @@
     {{> @everyone}}
     {{> @back}}
     """
-
-


Tests: Helpers: IDEHelpers: Code refactor.

--- client/test/lib/helpers/idehelpers.coffee
+++ client/test/lib/helpers/idehelpers.coffee
@@ -1,12 +1,12 @@
-helpers      = require './helpers.js'
-paneSelector = '.pane-wrapper .kdsplitview-panel.panel-1 .application-tab-handle-holder'
+helpers           = require './helpers.js'
+tabHandleSelector = "#{panelSelector} .application-tab-handle-holder"

 module.exports =

   openNewFile: (browser) ->

     activeEditorSelector = '.pane-wrapper .kdsplitview-panel.panel-1 .kdtabpaneview.active .ace_content'
-    plusSelector         = paneSelector + ' .visible-tab-handle.plus'
+    plusSelector         = tabHandleSelector + ' .visible-tab-handle.plus'

     browser
       .waitForElementVisible  plusSelector, 20000
@@ -18,14 +18,15 @@

   openContextMenu: (browser) ->

-    fileSelector = ' .untitledtxt.active'
+    fileSelector    = "#{tabHandleSelector} .untitledtxt.active"
+    optionsSelector = "#{fileSelector} span.options"

     browser
-      .waitForElementVisible  paneSelector + fileSelector, 20000
-      .moveToElement          paneSelector + fileSelector, 60, 17
-      .moveToElement          paneSelector + fileSelector + ' span.options', 8, 8
-      .waitForElementVisible  paneSelector + fileSelector + ' span.options', 20000
-      .click                  paneSelector + fileSelector + ' span.options'
+      .waitForElementVisible  fileSelector, 20000
+      .moveToElement          fileSelector, 60, 17
+      .moveToElement          optionsSelector, 8, 8
+      .waitForElementVisible  optionsSelector, 20000
+      .click                  optionsSelector
       .waitForElementVisible  '.kdlistview-contextmenu', 20000 # Assertion

@@ -49,7 +50,7 @@
       .clearValue             saveAsInputSelector
       .setValue               saveAsInputSelector, newName
       .click                  saveButtonSelector
-      .waitForElementVisible  "#{paneSelector} div[title='#{newName}']", 20000 # Assertion
+      .waitForElementVisible  "#{tabHandleSelector} div[title='#{newName}']", 20000 # Assertion
       .waitForElementVisible  filesTabSelector, 20000
       .assert.containsText    filesTabSelector, newName # Assertion


Remove the RPi /boot bind mount

Remove the RPi /boot bind mount

--- src/application.coffee
+++ src/application.coffee
@@ -151,14 +151,7 @@
 		'/var/run/docker.sock:/host_run/docker.sock'
 		'/etc/resolv.conf:/etc/resolv.conf:rw'
 	]
-	device.getDeviceType()
-	.then (deviceType) ->
-		if _.startsWith(deviceType, 'raspberry-pi')
-			volumes['/boot'] = {}
-			binds.push('/boot:/boot')
-	.catch (err) ->
-		console.log('Could not determine device type: ', err)
-	.then ->
+	Promise.try ->
 		# Parse the env vars before trying to access them, that's because they have to be stringified for knex..
 		JSON.parse(app.env)
 	.then (env) ->


Improved bad sendwithus error logging.

Improved bad sendwithus error logging.

--- server/users/User.coffee
+++ server/users/User.coffee
@@ -29,7 +29,7 @@
 UserSchema.methods.isAdmin = ->
   p = @get('permissions')
   return p and 'admin' in p
-
+
 emailNameMap =
   generalNews: 'announcement'
   adventurerNews: 'tester'
@@ -39,20 +39,20 @@
   diplomatNews: 'translator'
   ambassadorNews: 'support'
   anyNotes: 'notification'
-
+
 UserSchema.methods.setEmailSubscription = (newName, enabled) ->
   oldSubs = _.clone @get('emailSubscriptions')
   if oldSubs and oldName = emailNameMap[newName]
     oldSubs = (s for s in oldSubs when s isnt oldName)
     oldSubs.push(oldName) if enabled
     @set('emailSubscriptions', oldSubs)
-
+
   newSubs = _.clone(@get('emails') or _.cloneDeep(jsonschema.properties.emails.default))
   newSubs[newName] ?= {}
   newSubs[newName].enabled = enabled
   @set('emails', newSubs)
   @newsSubsChanged = true if newName in mail.NEWS_GROUPS
-
+
 UserSchema.methods.isEmailSubscriptionEnabled = (newName) ->
   emails = @get 'emails'
   if not emails
@@ -74,10 +74,10 @@
   newGroups = []
   for [mailchimpEmailGroup, emailGroup] in _.zip(mail.MAILCHIMP_GROUPS, mail.NEWS_GROUPS)
     newGroups.push(mailchimpEmailGroup) if doc.isEmailSubscriptionEnabled(emailGroup)
-
+
   if (not existingProps) and newGroups.length is 0
     return callback?() # don't add totally unsubscribed people to the list
-
+
   params = {}
   params.id = mail.MAILCHIMP_LIST_ID
   params.email = if existingProps then {leid:existingProps.leid} else {email:doc.get('email')}
@@ -113,7 +113,7 @@
       recipient:
         address: @get 'email'
     sendwithus.api.send data, (err, result) ->
-      log.error 'error', err, 'result', result if err
+      log.error "sendwithus post-save error: #{err}, result: #{result}" if err
   next()
 )


transition to documents route with query params

--- app/assets/javascripts/species/controllers/elibrary_search_controller.js.coffee
+++ app/assets/javascripts/species/controllers/elibrary_search_controller.js.coffee
@@ -4,7 +4,14 @@

   actions:
     openSearchPage:->
-      @transitionToRoute('documents')
+      if @get('autoCompleteTaxonConcept')
+        query = @get('autoCompleteTaxonConcept.fullName')
+      else
+        query = @get('taxonConceptQueryForDisplay')
+      @transitionToRoute('documents', {queryParams: {
+        taxon_concept_query: query
+      }})

     handleTaxonConceptAutoCompleteSelection: (autoCompleteTaxonConcept) ->
       @set('autoCompleteTaxonConcept', autoCompleteTaxonConcept)
+


d3 bezier: fixed mid point position

d3 bezier: fixed mid point position

This prevents U-shapes. Closes #978

--- app/assets/javascripts/d3/bezier.coffee
+++ app/assets/javascripts/d3/bezier.coffee
@@ -149,34 +149,56 @@
     # area function. To do this let's format the data as an array. An
     # interpolated mid-point is added to generate a S-curve.
     prepare_data: =>
+      left_stack  = 0
+      mid_stack   = 0
+      right_stack = 0
+      # The mid point should be between the left and side value, which are
+      # stacked
       series = @model.non_target_series().map (s) =>
-        mid_point = if s.present_value() < s.future_value()
+        # let's calculate the mid point boundaries
+        min_value = Math.min(left_stack + s.present_value(), right_stack + s.future_value())
+        max_value = Math.max(left_stack + s.present_value(), right_stack + s.future_value())
+
+        mid_point = if s.future_value() > s.present_value()
           s.present_value()
         else
           s.future_value()
+
+        mid_point += mid_stack
+
+        mid_point = if mid_point < min_value
+          min_value
+        else if mid_point > max_value
+          max_value
+        else
+          mid_point
+        # the stacking function wants the non-stacked values
+        mid_point -= mid_stack
+
+        mid_stack += mid_point
+        left_stack += s.present_value()
+        right_stack += s.future_value()
+
         mid_year = (@start_year + @end_year) / 2

-        [
+        out = [
           {
             x: @start_year
             y: s.present_value()
             id: "#{s.get 'gquery_key'}"
             color: s.get('color')
-            label: s.get('label')
           },
           {
             x: mid_year
             y: mid_point
             id: "#{s.get 'gquery_key'}"
             color: s.get('color')
-            label: s.get('label')
           },
           {
             x: @end_year
             y: s.future_value()
             id: "#{s.get 'gquery_key'}"
             color: s.get('color')
-            label: s.get('label')
           }
         ]
       _.flatten series


added html5 supported disucssions pagination fix

--- app/assets/javascripts/groups.js.coffee
+++ app/assets/javascripts/groups.js.coffee
@@ -114,32 +114,38 @@

   console.log("3 changing:" + button_url + " to:" + newURL)
   newURL
+
+getPageParam = () ->
+  url = $(location).attr("href")
+  if url.lastIndexOf("?") > -1
+    page = "?" + url.split("?")[1]
+    page
+  else
+    ""

 $ ->
   if $("body.groups.show").length > 0
     idStr = new Array
     idStr = $('#group-discussions').children().attr('class').split('_')
-    $('#group-discussions').load("/groups/#{idStr[1]}/discussions", ->
+    params = getPageParam()
+    $('#group-discussions').load("/groups/#{idStr[1]}/discussions" + params, ->
       Application.convertUtcToRelativeTime()
       $("#group-discussions").removeClass('hidden')
       $("#discussions-loading").hide()
     )
 $ ->
-  if html5.supported
-    console.log("html5 supported")
-    if $("body.groups.show").length > 0
-      $(document).on('click', '#group-discussions .pagination a', (e)->
-        unless $(this).parent().hasClass("gap")
+  if $("body.groups.show").length > 0
+    $(document).on('click', '#group-discussions .pagination a', (e)->
+      unless $(this).parent().hasClass("gap")
+        if html5.supported
           window.history.pushState("stateObj", "title_ignored", getNextURL($(this).attr("href")))
-          $("#discussion-list").hide()
-          $("#discussions-loading").show()
-          $('#group-discussions').load($(this).attr('href'), ->
-            Application.convertUtcToRelativeTime()
-            $("#discussion-list").show()
-            $("#discussions-loading").hide()
-          )
-          e.preventDefault()
-      )
-  else
-    console.log("no html5 sorry")
-    #switch embedded url with groups url and do NOT prevent default action in cs+          console.log("html5 supported")
+        $("#discussion-list").hide()
+        $("#discussions-loading").show()
+        $('#group-discussions').load($(this).attr('href'), ->
+          Application.convertUtcToRelativeTime()
+          $("#discussion-list").show()
+          $("#discussions-loading").hide()
+        )
+        e.preventDefault()
+    )


Forcefully hide the tooltip on paginator buttons when they are disabled

--- src/coffee/cilantro/ui/paginator.coffee
+++ src/coffee/cilantro/ui/paginator.coffee
@@ -51,11 +51,14 @@
             @_changePage = _.debounce(@changePage, @requestDelay)

         onRender: ->
+            # The tooltip call MUST be done before the render calls below or
+            # the tooltip options set here will not be respected because some
+            # of the buttons will be disabled.
+            @ui.buttons.tooltip({animation: false, placement: 'bottom'})
+
             if not @model.pageIsLoading()
                 @renderPageCount(@model, @model.getPageCount())
                 @renderCurrentPage(@model, @model.getCurrentPageStats()...)
-
-            @ui.buttons.tooltip({animation: false, placement: 'bottom'})

         renderPageCount: (model, value, options) ->
             @ui.pageCount.text(value)
@@ -66,6 +69,15 @@
             @ui.prev.prop('disabled', !!options.first)
             @ui.next.prop('disabled', !!options.last)
             @ui.last.prop('disabled', !!options.last)
+
+            # If we have disabled the buttons then we need to force hide the
+            # tooltip to prevent it from being permanently visible
+            if !!options.first
+                @ui.first.tooltip('hide')
+                @ui.prev.tooltip('hide')
+            if !!options.last
+                @ui.next.tooltip('hide')
+                @ui.last.tooltip('hide')

         changePage: (newPage) ->
             switch newPage


fileupload

fileupload

--- app/assets/javascripts/photos.js.coffee
+++ app/assets/javascripts/photos.js.coffee
@@ -1,3 +1,5 @@
 # Place all the behaviors and hooks related to the matching controller here.
 # All this logic will automatically be available in application.js.
 # You can use CoffeeScript in this file: http://jashkenas.github.com/coffee-script/
+jQuery ->
+  # $('#new_photo').fileupload()


Add jQuery on click event handler for search

Just added the click event handler to process metadata record search
requests. Currently, it just parses the data parameter to set if a
record is selected.

Signed-off-by: Konrad Reiche <4ff0213f56e2082fcd8c882d04c546881c579ce4@gmail.com>

--- app/assets/javascripts/report.js.coffee
+++ app/assets/javascripts/report.js.coffee
@@ -1,8 +1,11 @@
 #=require d3
 $ ->

-  $(".report.repository-select").on 'change', (e) =>
+  $(".report.repository-select").on 'change', (event) =>
     repository = $(".report.repository-select").val()
     window.location = "repository?show=#{repository}"

   $(".dropdown-toggle").dropdown()
+
+  $("a.report.search").on 'click', (event) =>
+    parameter = $(event.target).data('parameter')


Disable router logging

Disable router logging

--- app/assets/javascripts/router.js.coffee
+++ app/assets/javascripts/router.js.coffee
@@ -1,7 +1,6 @@
 #= require app

 App.Router = Ember.Router.extend
-  enableLogging: true
   location: 'hash'

   root: Ember.Route.extend


real n-values

real n-values

--- app/assets/javascripts/zvg/point.coffee
+++ app/assets/javascripts/zvg/point.coffee
@@ -209,6 +209,7 @@
     colors = @series_2_colors
     shapes = @series_2_shapes
     @series_2.each((d) ->
+      console.log(d)
       d3.select(this).selectAll('.zvg-point-shape, .zvg-point-label').remove()
       new (shapes[d.key])(this, colors[d.key], "#{d.key}:#{d.values.series_1}:#{d.values.series_3}")
       selection = d3.select(this)
@@ -221,7 +222,7 @@

       selection.append('text')
         .attr('class', 'zvg-point-label-small label-hover series2label n-label')
-        .text("n = ...")
+        .text("n = #{d.values.n}")
         .attr('transform', "translate(9, 12)")
         .style('opacity', 0)
         .datum(d.key)
@@ -253,6 +254,10 @@
   series_2_label_sum: (d) ->
     d3.sum((value.values.n or 0) for value in d.values)

+  point_label_sum: (d) ->
+    console.log(d)
+    return "n = ..."
+
   series_2_label_visibility: (label) ->
     if @series_3_domain().length is 1
       ""


fix migration phase check

fix migration phase check

--- app/coffee/RedisMigrationManager.coffee
+++ app/coffee/RedisMigrationManager.coffee
@@ -114,7 +114,7 @@
 		@rclient_new = redis.createClient(new_settings)
 		@new_key_schema = new_settings.key_schema
 		@migration_phase = new_settings.migration_phase
-		throw new Error("invalid migration phase") unless @migration_phase in ['prepare', 'start', 'switch', 'complete']
+		throw new Error("invalid migration phase") unless @migration_phase in ['prepare', 'switch', 'rollback']

 	getMigrationStatus: (key, migrationKey, callback) ->
 		async.series [
@@ -132,7 +132,7 @@
 		project_id = getProjectId(key)
 		migrationKey = @new_key_schema.projectHistoryMigrationKey({project_id})

-		@getMigrationStatus key, migrationKey, (err, migrationKeyExists, newQueueExists, oldQueueExists) ->
+		@getMigrationStatus key, migrationKey, (err, migrationKeyExists, newQueueExists, oldQueueExists) =>
 			return callback(err) if err?
 			# In all cases, if the migration key exists we must always write to the
 			# new redis, unless we are rolling back.
@@ -189,7 +189,7 @@
 					logger.debug {project_id}, "using old client because migration key does not exist"
 					return callback(null, @rclient_old)
 			else
-				logger.error {key: key}, "unknown migration phase"
+				logger.error {key: key, migration_phase: @migration_phase}, "unknown migration phase"
 				callback(new Error('invalid migration phase'))
 	multi: () ->
 		new Multi(@)


Fixed greenmine issue 72: Now can be correctly created unassigned tasks on milestones

--- app/coffee/controllers/taskboard.coffee
+++ app/coffee/controllers/taskboard.coffee
@@ -88,6 +88,7 @@
         options =
             status: $scope.project.default_task_status
             project: projectId
+            milestone: sprintId

         if us != undefined
             options.user_story = us.id


fix #2094 - restore issue properties spinner

--- app/coffee/modules/issues/detail.coffee
+++ app/coffee/modules/issues/detail.coffee
@@ -207,9 +207,15 @@
             })
             $el.html(html)

-        save = $qqueue.bindAdd (value, issue) =>
+        save = $qqueue.bindAdd (statusId) =>
+            $.fn.popover().closeAll()
+
+            issue = $model.$modelValue.clone()
+            issue.status = statusId
+
             onSuccess = ->
                 $confirm.notify("success")
+                $model.$setViewValue(issue)
                 $rootScope.$broadcast("history:reload")
                 $loading.finish($el.find(".level-name"))
             onError = ->
@@ -220,7 +226,7 @@

             $loading.start($el.find(".level-name"))

-            $repo.save(value).then(onSuccess, onError)
+            $repo.save(issue).then(onSuccess, onError)

         $el.on "click", ".status-data", (event) ->
             event.preventDefault()
@@ -236,13 +242,7 @@

             target = angular.element(event.currentTarget)

-            $.fn.popover().closeAll()
-
-            issue = $model.$modelValue.clone()
-            issue.status = target.data("status-id")
-            $model.$setViewValue(issue)
-
-            save($model.$modelValue, issue)
+            save(target.data("status-id"))

         $scope.$watch $attrs.ngModel, (issue) ->
             render(issue) if issue
@@ -294,12 +294,12 @@
             issue = $model.$modelValue.clone()
             issue.type = type

-            $model.$setViewValue(issue)
-
             onSuccess = ->
                 $confirm.notify("success")
+                $model.$setViewValue(issue)
                 $rootScope.$broadcast("history:reload")
                 $loading.finish($el.find(".level-name"))
+
             onError = ->
                 $confirm.notify("error")
                 issue.revert()
@@ -307,7 +307,7 @@
                 $loading.finish($el.find(".level-name"))
             $loading.start($el.find(".level-name"))

-            $repo.save($model.$modelValue).then(onSuccess, onError)
+            $repo.save(issue).then(onSuccess, onError)

         $el.on "click", ".type-data", (event) ->
             event.preventDefault()
@@ -376,10 +376,10 @@

             issue = $model.$modelValue.clone()
             issue.severity = severity
-            $model.$setViewValue(issue)

             onSuccess = ->
                 $confirm.notify("success")
+                $model.$setViewValue(issue)
                 $rootScope.$broadcast("history:reload")
                 $loading.finish($el.find(".level-name"))
             onError = ->
@@ -387,9 +387,10 @@
                 issue.revert()
                 $model.$setViewValue(issue)
                 $loading.finish($el.find(".level-name"))
+
             $loading.start($el.find(".level-name"))

-            $repo.save($model.$modelValue).then(onSuccess, onError)
+            $repo.save(issue).then(onSuccess, onError)

         $el.on "click", ".severity-data", (event) ->
             event.preventDefault()
@@ -459,10 +460,10 @@

             issue = $model.$modelValue.clone()
             issue.priority = priority
-            $model.$setViewValue(issue)

             onSuccess = ->
                 $confirm.notify("success")
+                $model.$setViewValue(issue)
                 $rootScope.$broadcast("history:reload")
                 $loading.finish($el.find(".level-name"))
             onError = ->
@@ -470,9 +471,10 @@
                 issue.revert()
                 $model.$setViewValue(issue)
                 $loading.finish($el.find(".level-name"))
+
             $loading.start($el.find(".level-name"))

-            $repo.save($model.$modelValue).then(onSuccess, onError)
+            $repo.save(issue).then(onSuccess, onError)

         $el.on "click", ".priority-data", (event) ->
             event.preventDefault()


fix Device.create to create duplicate entries with different regid

--- api/controllers/DeviceController.coffee
+++ api/controllers/DeviceController.coffee
@@ -10,7 +10,7 @@
 		data = actionUtil.parseValues(req)
 		data.createdBy = data.createdBy.id

-		Model.findOrCreate(data)
+		Model.findOrCreate(_.pick(data, 'model', 'version', 'createdBy'), data)
 			.then (newInstance) ->
 				if req._sails.hooks.pubsub and req.isSocket
 					if Model.autoSubscribe


Sim: Reword lock/unlock.

Sim: Reword lock/unlock.

--- server/server.coffee
+++ server/server.coffee
@@ -249,11 +249,11 @@
     @canBattlesStart = false
     for user in @users.values()
       @stopChallenges(user)
-    @announce("The server is locked! No new battles can start at this time.")
+    @announce("The server is restarting! All battles are locked: No new battles may start at this time.")

   unlockdown: ->
     @canBattlesStart = true
-    @announce("The server was unlocked! You may battle again.")
+    @announce("Battles have been unlocked! You may battle again.")

   isLockedDown: ->
     !@canBattlesStart


Data link should work on AJAX'd content too

--- app/assets/javascripts/sheets.js.coffee
+++ app/assets/javascripts/sheets.js.coffee
@@ -27,11 +27,12 @@
     false
   )

-  $("[data-link]").on('click', () ->
-    window.location = $(this).data("link")
-  )
+  $(document)
+    .on('click', '[data-object~="export"]', () ->
+      window.location = $($(this).data('target')).attr('action') + '.' + $(this).data('format') + '?' + $($(this).data('target')).serialize()
+      false
+    )
+    .on('click', "[data-link]", () ->
+      window.location = $(this).data("link")
+    )

-  $(document).on('click', '[data-object~="export"]', () ->
-    window.location = $($(this).data('target')).attr('action') + '.' + $(this).data('format') + '?' + $($(this).data('target')).serialize()
-    false
-  )


Fix for AJAX

Fix for AJAX

--- app/assets/javascripts/swimlanes.coffee
+++ app/assets/javascripts/swimlanes.coffee
@@ -7,33 +7,33 @@
             $('[id^="swimlane_"]').sortable(
                 items: "> :not(.ui-state-disabled)"
                 update: (e, ui) ->
-
-                    ui.item[0].classList.add("ui-state-disabled")
-                    indexAt = 0
-                    id = ui.item[0].firstElementChild.id
-                    parent = ui.item.context.parentNode.id.split('_')[1]
-                    next = -1
-                    console.log
-                    while ui.item.context.parentNode.children[indexAt].firstElementChild.id != id
-                        indexAt++
-                    if indexAt != 0
-                        next = ui.item.context.parentNode.children[indexAt - 1].firstElementChild.id
-                    data = {status_id:parent, issue_id:id,prev_id:next}
-                    console.log data
-                    url = "/api/issues/reorder"
-                    $.ajax
-                        type: 'POST'
-                        headers: {"Authorization": "Token token=" + $("#token").val()}
-                        url: url
-                        data: data
-                        success: (data) ->
-                            #$("#projects-row").append(generateProjectCard(data.name, data.description, '#', '#', '#'))
-                            ui.item[0].classList.remove("ui-state-disabled")
-                            return
-                        error: (req, msg, stat) ->
-                            alert("AJAX Issue");
-                            location.reload();
-                            return
+                    if this == ui.item.parent()[0]
+                        ui.item[0].classList.add("ui-state-disabled")
+                        indexAt = 0
+                        id = ui.item[0].firstElementChild.id
+                        parent = ui.item.context.parentNode.id.split('_')[1]
+                        next = -1
+                        console.log
+                        while ui.item.context.parentNode.children[indexAt].firstElementChild.id != id
+                            indexAt++
+                        if indexAt != 0
+                            next = ui.item.context.parentNode.children[indexAt - 1].firstElementChild.id
+                        data = {status_id:parent, issue_id:id,prev_id:next}
+                        console.log data
+                        url = "/api/issues/reorder"
+                        $.ajax
+                            type: 'POST'
+                            headers: {"Authorization": "Token token=" + $("#token").val()}
+                            url: url
+                            data: data
+                            success: (data) ->
+                                #$("#projects-row").append(generateProjectCard(data.name, data.description, '#', '#', '#'))
+                                ui.item[0].classList.remove("ui-state-disabled")
+                                return
+                            error: (req, msg, stat) ->
+                                alert("AJAX Issue");
+                                location.reload();
+                                return
                     return
                 connectWith: '.connectedSortable'
                 revert: true).disableSelection()


fixed cancel button in groups danger modal used for transfer ownership and remove group

--- client/app/Applications/Groups.kdapplication/Views/groupsdangermodalview.coffee
+++ client/app/Applications/Groups.kdapplication/Views/groupsdangermodalview.coffee
@@ -31,7 +31,7 @@
               callback       : -> @showLoader()
             Cancel           :
               style          : 'modal-cancel'
-              callback       : (event)-> modal.destroy()
+              callback       : (event)=> @destroy()
           fields             :
             groupSlug        :
               label          : 'Confirm'


Teamwork: Added fallback mechanism for curl request attempts.

--- client/landing/app/Applications/Teamwork.kdapplication/Views/teamworkapp.coffee
+++ client/landing/app/Applications/Teamwork.kdapplication/Views/teamworkapp.coffee
@@ -200,11 +200,28 @@
       appStorage = KD.getSingleton("appStorageController").storage "Teamwork", "1.0"
       appStorage.setValue "#{playground}PlaygroundVersion", version

+  requestAttempts = {}
+
   doCurlRequest: (path, callback = noop) ->
-    KD.getSingleton("vmController").run "curl -kLs #{path}", (err, contents) =>
+    vmController = KD.getSingleton "vmController"
+    vmController.run
+      withArgs: "curl -kLs #{path}"
+      vmName  : vmController.defaultVmName
+    , (err, contents) =>
+      if err?.name is "ExitError"
+        numberOfAttemps = requestAttempts[path]
+        if numberOfAttemps is 3
+          return new KDNotificationView
+            type     : "mini"
+            cssClass : "error"
+            title    : "Something went wrong..."
+            duration : 5000
+
+        unless numberOfAttemps then requestAttempts[path] = 1 else requestAttempts[path]++
+        return @doCurlRequest path, callback
+
       extension = FSItem.getFileExtension path
       error     = null
-
       switch extension
         when "json"
           try


Forgot to re-add switch user role.

Forgot to re-add switch user role.

--- picoCTF-web/web/coffee/classroom-management.coffee
+++ picoCTF-web/web/coffee/classroom-management.coffee
@@ -62,6 +62,7 @@
         </Col>
         <Col xs={4}>
           <ButtonGroup vertical>
+            {switchUser}
             <Button onClick={@removeTeam}>Remove User</Button>
           </ButtonGroup>
         </Col>


compile checker merged into one file

compile checker merged into one file

--- client/app/Applications/Ace.kdapplication/AppController.coffee
+++ client/app/Applications/Ace.kdapplication/AppController.coffee
@@ -1,4 +1,7 @@
 class AceAppController extends AppController
+
+  compileCondition = (ace)->
+    KodingAppsController.getManifestFromPath ace.getData().path

   KD.registerAppClass this,
     name          : "Ace"
@@ -22,12 +25,8 @@
       { title     : "Find and Replace",    eventName : "findAndReplace" }
       { title     : "Goto line",           eventName : "gotoLine" }
       { type      : "separator" }
-      { title     : "Compile and Run",     eventName : "compileAndRun", condition: (ace)->
-          KodingAppsController.getManifestFromPath ace.getData().path
-      }
-      { type      : "separator", condition: (ace)->
-          KodingAppsController.getManifestFromPath ace.getData().path
-      }
+      { title     : "Compile and Run",     eventName : "compileAndRun", condition: compileCondition}
+      { type      : "separator",                                        condition: compileCondition}
       { title     : "Preview",             eventName : "preview" }
       { type      : "separator" }
       { title     : "Advanced Settings",   id        : "advancedSettings" }


SidebarMachineList: disable stack modified warning since we have already one http://take.ms/csEYn

--- client/app/lib/activity/sidebar/sidebarstackmachinelist.coffee
+++ client/app/lib/activity/sidebar/sidebarstackmachinelist.coffee
@@ -27,7 +27,6 @@

     computeController
       .on 'StackRevisionChecked', @bound 'onStackRevisionChecked'
-      .on 'StacksInconsistent',   @bound 'addStackModifiedWarning'

   onStackRevisionChecked: (stack) ->
@@ -49,17 +48,3 @@
     super

     @addSubView @warningWrapper = new kd.CustomHTMLView
-
-
-  addStackModifiedWarning: (stack) ->
-
-    return  if stack.getId() isnt @getOption('stack').getId()
-    return  @stackModifiedWarning.show()  if @stackModifiedWarning?
-
-    @stackModifiedWarning = new kd.CustomHTMLView
-      cssClass : 'warning-section re-init'
-      partial  : "You have different resources in your stack.
-                  Click here to re-initialize this stack."
-      click    : -> new EnvironmentsModal
-
-    @warningWrapper.addSubView @stackModifiedWarning


Workspace: Updates for ChatPane to make it floating.

--- client/landing/app/CommonViews/workspace/panes/chatpane.coffee
+++ client/landing/app/CommonViews/workspace/panes/chatpane.coffee
@@ -2,7 +2,8 @@

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

-    options.cssClass = "workspace-chat"
+    floatingCssClass = if options.floating then "floating" else ""
+    options.cssClass = KD.utils.curry "workspace-chat", floatingCssClass

     super options, data

@@ -43,7 +44,7 @@
     @dock.addSubView @title

     @chatRef.on "child_added", (snapshot) =>
-      unless @isVisible()
+      unless @isVisible() or @getOptions().floating
         @title.updatePartial "Chat (#{++@unreadCount})"
         @dock.setClass "pulsing"


Passed missing event parameter.

Passed missing event parameter.

--- client/landing/app/MainApp/maintabs/maintabhandleholder.coffee
+++ client/landing/app/MainApp/maintabs/maintabhandleholder.coffee
@@ -29,7 +29,7 @@
       cssClass : 'kdtabhandle add-editor-menu visible-tab-handle plus first last'
       partial  : "Click here to start"
       delegate : @
-      click    : =>
+      click    : (event) =>
         unless @plusHandle.$().hasClass('first')
           contextMenu = new JContextMenu
             event    : event


activity feed: skip non bucket entries when iterating

--- client/landing/app/Applications/Activity.kdapplication/activitylistcontroller.coffee
+++ client/landing/app/Applications/Activity.kdapplication/activitylistcontroller.coffee
@@ -174,6 +174,10 @@
   updateBuckets:(follower, followee)->
     for item in @itemsOrdered
       data = item.getData()
+
+      continue  unless data.group
+      continue  if typeof data.group is "string"
+
       if data.group[0].constructorName is followee.bongo_.constructorName
         if data.anchor.id is follower.id
           data.group.unshift {


groups list view: lock icon tooltip

groups list view: lock icon tooltip

--- client/landing/app/Applications/Groups.kdapplication/Views/GroupsListItemView.coffee
+++ client/landing/app/Applications/Groups.kdapplication/Views/GroupsListItemView.coffee
@@ -79,6 +79,8 @@
       tagName   : "div"
       cssClass  : "badge private #{if group.privacy is 'private' then '' else 'hidden'}"
       partial   : ""
+      tooltip   :
+        title   : "Restricted access: viewing this groups content requires requesting an invitation"

     @memberCount = new CustomLinkView
       title       : "#{group.counts?.members or 'No'} Members"


Fix mentionables

Fix mentionables

--- lineman/app/modules/discussion_page/comment_form/comment_form_controller.coffee
+++ lineman/app/modules/discussion_page/comment_form/comment_form_controller.coffee
@@ -2,7 +2,6 @@
   $scope.comment = $scope.comment or Records.comments.initialize(discussion_id: $scope.discussion.id)
   group = $scope.discussion.group()

-  $scope.mentionables = group.members()
   $scope.isExpanded = false

   $scope.expand = ->
@@ -18,7 +17,7 @@
     $scope.collapse() if $scope.comment.body.length == 0

   $scope.getMentionables = (fragment) ->
-    Records.members.fetchByNameFragment fragment, group.id, ->
+    Records.memberships.fetchByNameFragment(fragment, group.key).then ->
       $scope.mentionables = _.filter(group.members(), (member) ->
         ~member.name.search(new RegExp(fragment, 'i')) or \
         ~member.label.search(new RegExp(fragment, 'i')))


make command available on visual-mode only depends on vim-mode-plus#147

--- lib/move-selected-text.coffee
+++ lib/move-selected-text.coffee
@@ -16,6 +16,8 @@
 disposableByEditor = new Map
 # -------------------------
 class MoveSelectedText extends TransformString
+  @commandScope: 'atom-text-editor.vim-mode-plus.visual-mode'
+
   countTimes: (fn) ->
     _.times @getCount(), ->
       fn()
@@ -45,8 +47,6 @@
   flashTarget: false

   execute: ->
-    return unless @isMode('visual')
-
     @withUndoJoin =>
       selections = @editor.getSelectionsOrderedByBufferPosition()
       selections.reverse() if @direction is 'down'
@@ -137,8 +137,6 @@
   flashTarget: false

   execute: ->
-    return unless @isMode('visual')
-
     @withUndoJoin =>
       @eachSelection (selection) =>
         @countTimes =>


Remove a few abandonded atom.* functions relying on sendMessageToBrowserProcess.

--- src/app/atom.coffee
+++ src/app/atom.coffee
@@ -192,30 +192,12 @@
   exit: (status) ->
     @sendMessageToBrowserProcess('exit', [status])

-  log: (message) ->
-    @sendMessageToBrowserProcess('log', [message])
-
-  beginTracing: ->
-    @sendMessageToBrowserProcess('beginTracing')
-
-  endTracing: ->
-    @sendMessageToBrowserProcess('endTracing')
-
   toggleFullScreen: ->
     currentWindow = remote.getCurrentWindow()
     currentWindow.setFullscreen(!currentWindow.isFullscreen())

   sendMessageToBrowserProcess: (name, data=[], callbacks) ->
     throw new Error("sendMessageToBrowserProcess no longer works for #{name}")
-
-  receiveMessageFromBrowserProcess: (name, data) ->
-    switch name
-      when 'reply'
-        [messageId, callbackIndex] = data.shift()
-        @pendingBrowserProcessCallbacks[messageId]?[callbackIndex]?(data...)
-      when 'openPath'
-        path = data[0]
-        rootView?.open(path)

   getWindowStatePath: ->
     if not @getPathToOpen()


Add 'Pane.saveItems' and corresponding event

--- src/app/pane.coffee
+++ src/app/pane.coffee
@@ -23,6 +23,7 @@
     @command 'core:close', @destroyActiveItem
     @command 'core:save', @saveActiveItem
     @command 'core:save-as', @saveActiveItemAs
+    @command 'pane:save-items', @saveItems
     @command 'pane:show-next-item', @showNextItem
     @command 'pane:show-previous-item', @showPreviousItem
     @command 'pane:split-left', => @splitLeft()
@@ -137,6 +138,9 @@
         item.saveAs(path)
         nextAction?()

+  saveItems: =>
+    @saveItem(item) for item in @getItems()
+
   removeItem: (item) ->
     index = @items.indexOf(item)
     return if index == -1


Avoid serializing/deserializing RemoteTextBuffer

--- lib/remote-text-buffer.coffee
+++ lib/remote-text-buffer.coffee
@@ -8,6 +8,9 @@

 # Subclass of `TextBuffer` that represents a buffer of text pulled from a
 # remote source.
+#
+# TODO: RemoteTextBuffer doesn't serialize well
+# TODO: Remote script opening needs to check for existing first.
 module.exports=
 class RemoteTextBuffer extends TextBuffer

@@ -34,17 +37,17 @@
   constructor: ({@remoteUri})->
     super({})

-  deserializeParams: (params)->
-    {@remoteUri} = params
-    super
-
-  serializeParams: ->
-    _.extend({@remoteUri}, super)
+  # Prevent the Pane from serializing us (a hack for now, because, being in a
+  # package, this class can't register itself with the deserializer before atom
+  # tries to deserialize the workspace).
+  serialize: null
+

   updateCachedDiskContentsSync: ->
     throw new Error('Unimplemented: updateCachedDiskContentsSync')

   updateCachedDiskContents: ->
+    if @loaded then return # don't request more than once.
     debug('requesting', @remoteUri)
     Q.nfcall(request, @remoteUri).then ([response, body])=>
       debug('got response', response)


Use ReactSigninPopover when adding subcomments

--- core/app/backbone/views/sub_comments/react_sub_comments.coffee
+++ core/app/backbone/views/sub_comments/react_sub_comments.coffee
@@ -1,6 +1,5 @@
 window.ReactSubComments = React.createBackboneClass
   displayName: 'ReactSubComments'
-  mixins: [UpdateOnSignInOrOutMixin]
   propTypes:
     fact_opinionators: React.PropTypes.instanceOf(Opinionators).isRequired
     model: React.PropTypes.instanceOf(SubComments).isRequired
@@ -18,8 +17,7 @@
           model: sub_comment
           key: sub_comment.get('id')
           fact_opinionators: @props.fact_opinionators
-      if FactlinkApp.signedIn()
-        ReactSubCommentsAdd model: @model()
+      ReactSubCommentsAdd model: @model()

 ReactSubCommentsAdd = React.createBackboneClass
   displayName: 'ReactSubCommentsAdd'
@@ -54,11 +52,14 @@
         placeholder: 'Leave a reply'
         storageKey: "add_subcomment_to_comment_#{@model().parentModel.id}"
         onChange: @_onTextareaChange
-        onSubmit: @_submit
+        onSubmit: => @refs.signinPopover.submit()
       if @state.opened
         _button ["button-confirm button-small spec-submit",
           disabled: !@_subComment().isValid()
-          onClick: @_submit
+          onClick: => @refs.signinPopover.submit()
         ],
           Factlink.Global.t.post_subcomment
+          ReactSigninPopover
+            ref: 'signinPopover'
+            onSubmit: @_submit


fix(model/encrypt): removing encrypted data from base container

--- src/model.coffee
+++ src/model.coffee
@@ -300,8 +300,10 @@
   # Debug function
   model.encrypt_container = (data, container) ->
     data[model.encrypted_container] = {}
+
     for index, value of data
       if index in model.encrypted_editable
+        delete data[index]
         data[model.encrypted_container][index] = value

     encdata = encryptData(sessionStorage.getItem('appKey'), JSON.stringify(data[model.encrypted_container]))


CUI

CUI

- Make storage function check if storage is available, use
  fallback (store in window).

--- src/base/CUI.coffee
+++ src/base/CUI.coffee
@@ -492,17 +492,29 @@
 	@clearLocalStorage: ->
 		@__clearStorage("localStorage")

+	@__storage: localStorage: null, sessionStorage: null
+
 	@__setStorage: (skey, key, value) ->
 		data = @__getStorage(skey)
 		if value == undefined
 			delete(data[key])
 		else
 			data[key] = value
-		window[skey].setItem("CUI", JSON.stringify(data))
+		try
+			window[skey].setItem("CUI", JSON.stringify(data))
+		catch e
+			console.warn("CUI.__setStorage: Storage not available.", e)
+			@__storage[skey] = JSON.stringify(data)
 		data

+
 	@__getStorage: (skey, key = null) ->
-		data_json = window[skey].getItem("CUI")
+		try
+			data_json = window[skey].getItem("CUI")
+		catch e
+			console.warn("CUI.__getStorage: Storage not available.", e)
+			data_json = @__storage[skey]
+
 		if data_json
 			data = JSON.parse(data_json)
 		else
@@ -514,7 +526,11 @@
 			data

 	@__clearStorage: (skey) ->
-		window[skey].removeItem("CUI")
+		try
+			window[skey].removeItem("CUI")
+		catch e
+			console.warn("CUI.__clearStorage: Storage not available.", e)
+			@__storage[skey] = null

 	@encodeUrlData: (params, replacer = null, connect = "&", connect_pair = "=") ->
 		url = []


trigger a reflow for safari 5

trigger a reflow for safari 5

--- apps/fair/components/browse/view.coffee
+++ apps/fair/components/browse/view.coffee
@@ -53,9 +53,10 @@
       .addClass('is-active')

   events:
-    'click #fair-filter-all-artists': 'artistsAZ'
-    'click #fair-booths-az-as-list': 'exhibitorsAZ'
-    'click #fair-booths-as-grid': 'exhibitorsGrid'
+    'click #fair-filter-all-artists' : 'artistsAZ'
+    'click #fair-booths-az-as-list'  : 'exhibitorsAZ'
+    'click #fair-booths-as-grid'     : 'exhibitorsGrid'
+    'click'                          : 'triggerReflow'

   artistsAZ: ->
     @router.navigate "#{@profile.get 'id'}/browse/artists"
@@ -71,3 +72,10 @@

   exhibitorsGrid: ->
     @boothParams.trigger 'reset'
+
+  # Safari does not re-render when a data attribute on an element has changed so we manually trigger a reflow
+  triggerReflow: =>
+    @$el.css display: 'none'
+    # no need to store this anywhere, the reference is enough to trigger a reflow
+    @$el.offset().height
+    @$el.css display: 'block'


i18n level edit view add display name field (#342)

--- app/views/i18n/I18NEditLevelView.coffee
+++ app/views/i18n/I18NEditLevelView.coffee
@@ -15,6 +15,8 @@
         @wrapRow 'Level name', ['name'], name, i18n[lang]?.name, []
       if description = @model.get('description')
         @wrapRow 'Level description', ['description'], description, i18n[lang]?.description, []
+      if displayName = @model.get('displayName')
+        @wrapRow 'Display name', ['displayName'], displayName, i18n[lang]?.displayName, []
       if loadingTip = @model.get('loadingTip')
         @wrapRow 'Loading tip', ['loadingTip'], loadingTip, i18n[lang]?.loadingTip, []
       if studentPlayInstructions = @model.get('studentPlayInstructions')


coordinator: Sanity-check queues when connecting edge

--- src/coordinator.coffee
+++ src/coordinator.coffee
@@ -308,7 +308,15 @@
         @connections[edgeId].tgtQueue = findQueue @participants, toId, 'inports', toName
         edgeWithQueues = @connections[edgeId]
         @emit 'graph-changed'
-        @broker.addBinding {type: 'pubsub', src:edgeWithQueues.srcQueue, tgt:edgeWithQueues.tgtQueue}, (err) =>
+        binding =
+          type: 'pubsub'
+          src: edgeWithQueues.srcQueue
+          tgt: edgeWithQueues.tgtQueue
+        if not binding.src
+          return callback new Error "Source queue for connection #{fromId} #{fromPort} not found"
+        if not binding.tgt
+          return callback new Error "Target queue for connection #{toName} #{toPort} not found"
+        @broker.addBinding binding, (err) =>
           return callback err

   disconnect: (fromId, fromPort, toId, toPort, callback) ->


Fix stupid smoothing bug

Fix stupid smoothing bug

--- src/core/shapes.coffee
+++ src/core/shapes.coffee
@@ -292,7 +292,7 @@
         x: x, y: y, size: data.pointSize, color: data.pointColor
         smooth: data.smooth
       }
-    }) for [x, y] in data.pointCoordinatePairs)
+    }) for [x, y] in data.smoothedPointCoordinatePairs)

   return null unless points[0]
   createShape(shapeName, {


Minor fixes

Minor fixes

--- library/nucleus/kernel.coffee
+++ library/nucleus/kernel.coffee
@@ -314,8 +314,8 @@
         tag = nconf.get "NODE_ENV" or undefined
         assert not _.isEmpty(tag), missing
         @scope = scoping.Scope.lookupOrFail tag
-        assert @scope.incorporate this
-        @router = new routing.Router this
+        assert try @scope.incorporate this
+        assert @router = new routing.Router this
         assert @middleware = @router.middleware
         @middleware = @middleware.bind @router
         assert _.isFunction @middleware; this


:green_heart: Fix failing spec

:green_heart: Fix failing spec

--- spec/text-editor-component-spec.coffee
+++ spec/text-editor-component-spec.coffee
@@ -1937,14 +1937,14 @@
             gutterNode.dispatchEvent(buildMouseEvent('mousemove', clientCoordinatesForScreenRowInGutter(11), metaKey: true))
             nextAnimationFrame()
             gutterNode.dispatchEvent(buildMouseEvent('mouseup', clientCoordinatesForScreenRowInGutter(11), metaKey: true))
-            expect(editor.getSelectedScreenRanges()).toEqual [[[7, 4], [7, 6]], [[10, 0], [20, 0]]]
+            expect(editor.getSelectedScreenRanges()).toEqual [[[7, 4], [7, 6]], [[10, 0], [19, 0]]]

           it "merges overlapping selections", ->
             gutterNode.dispatchEvent(buildMouseEvent('mousedown', clientCoordinatesForScreenRowInGutter(17), metaKey: true))
             gutterNode.dispatchEvent(buildMouseEvent('mousemove', clientCoordinatesForScreenRowInGutter(9), metaKey: true))
             nextAnimationFrame()
             gutterNode.dispatchEvent(buildMouseEvent('mouseup', clientCoordinatesForScreenRowInGutter(9), metaKey: true))
-            expect(editor.getSelectedScreenRanges()).toEqual [[[5, 0], [20, 0]]]
+            expect(editor.getSelectedScreenRanges()).toEqual [[[5, 0], [19, 0]]]

       describe "when the gutter is shift-clicked and dragged", ->
         describe "when the shift-click is below the existing selection's tail", ->


hidden unused alert

hidden unused alert

--- index.cjsx
+++ index.cjsx
@@ -617,14 +617,16 @@
             }
             </tbody>
           </Table>
-          <Alert>
           {
             if @state.getShip? && @state.enemyInfo?
-              __("Admiral") + " #{@state.getShip.api_ship_type}「#{@state.getShip.api_ship_name}」" + __("Join fleet")
+              <Alert>
+                __("Admiral") + " #{@state.getShip.api_ship_type}「#{@state.getShip.api_ship_name}」" + __("Join fleet")
+              </Alert>
             else if @state.enemyFormation != 0
-              __("Admiral") + " 「#{formation[@state.enemyFormation]}」「#{intercept[@state.enemyIntercept]} | #{@state.result}」"
+              <Alert>
+                __("Admiral") + " 「#{formation[@state.enemyFormation]}」「#{intercept[@state.enemyIntercept]} | #{@state.result}」"
+              </Alert>
           }
-          </Alert>
         </div>
       else
         <div>
@@ -672,14 +674,16 @@
             }
             </tbody>
           </Table>
-          <Alert>
           {
             if @state.getShip? && @state.enemyInfo?
-              __("Admiral") + " #{@state.getShip.api_ship_type}「#{@state.getShip.api_ship_name}」" + __("Join fleet")
+              <Alert>
+                __("Admiral") + " #{@state.getShip.api_ship_type}「#{@state.getShip.api_ship_name}」" + __("Join fleet")
+              </Alert>
             else if @state.enemyFormation != 0
-              __("Admiral") + " 「#{formation[@state.enemyFormation]}」「#{intercept[@state.enemyIntercept]} | #{@state.result}」"
+              <Alert>
+                __("Admiral") + " 「#{formation[@state.enemyFormation]}」「#{intercept[@state.enemyIntercept]} | #{@state.result}」"
+              </Alert>
           }
-          </Alert>
         </div>
   settingsClass: React.createClass
     getInitialState: ->


Fix https://github.com/yudachi/plugin-prophet/issues/1

--- index.cjsx
+++ index.cjsx
@@ -244,8 +244,8 @@
           shipLv[i] = -1 for i in [6..11]
           if body.api_enemy?
             if enemyInformation[body.api_enemy.api_enemy_id]?
-              [shipName, shipLv, maxHp, nowHp, enemyFormation, enemyTyku] = getMapEnemy shipName, shipLv, maxHp, nowHp, enemyFormation, enemyTyku, enemyInformation[body.api_enemy.api_enemy_id]
-              afterHp = Object.clone nowHp
+              [shipName, shipLv, maxHp, nowHp, enemyFormation, enemyTyku] = getMapEnemy shipName, shipLv, maxHp, afterHp, enemyFormation, enemyTyku, enemyInformation[body.api_enemy.api_enemy_id]
+              afterHp = Object.clone afterHp
             else
               jsonId = body.api_enemy.api_enemy_id


fix bug

fix bug

--- index.cjsx
+++ index.cjsx
@@ -142,11 +142,6 @@
       if nowHp[i] - damageHp[i] <= 0
         enemyDrop += 1
   tmpResult = "不明"
-  console.log friendDamage
-  console.log enemyDamage
-  console.log friendDrop
-  console.log enemyDrop
-  console.log enemyCount
   if enemyDrop == enemyCount
     tmpResult = "S"
   else if enemyDrop >= Math.ceil(enemyCount * 1.0 / 2.0)


Skip this test because it is racy

Skip this test because it is racy

--- spec/Subgraph.coffee
+++ spec/Subgraph.coffee
@@ -445,7 +445,7 @@
           i.send 'Foo'
       return

-    it 'should activate automatically when receiving data', (done) ->
+    it.skip 'should activate automatically when receiving data', (done) ->
       cl.load 'Defaults', (err, inst) ->
         i = noflo.internalSocket.createSocket()
         o = noflo.internalSocket.createSocket()


rs (not b) in generated code.

rs (not b) in generated code.

also fix extra line from last commit

--- src/compiler.coffee
+++ src/compiler.coffee
@@ -361,14 +361,14 @@
   fconst_2: { compile: (b) -> b.push new Primitive "2"; }
   dconst_0: { compile: (b) -> b.push2 new Primitive "0"; }
   dconst_1: { compile: (b) -> b.push2 new Primitive "1"; }
-  iastore: {compile: (b) -> b.add_stmt new Expr "b.check_null($2).array[$1]=$0",b.pop(),b.pop(),b.pop()}
-  lastore: {compile: (b) -> b.add_stmt new Expr "b.check_null($2).array[$1]=$0",b.pop(),b.pop(),b.pop2()}
-  fastore: {compile: (b) -> b.add_stmt new Expr "b.check_null($2).array[$1]=$0",b.pop(),b.pop(),b.pop()}
-  dastore: {compile: (b) -> b.add_stmt new Expr "b.check_null($2).array[$1]=$0",b.pop(),b.pop(),b.pop2()}
-  aastore: {compile: (b) -> b.add_stmt new Expr "b.check_null($2).array[$1]=$0",b.pop(),b.pop(),b.pop()}
-  bastore: {compile: (b) -> b.add_stmt new Expr "b.check_null($2).array[$1]=$0",b.pop(),b.pop(),b.pop()}
-  castore: {compile: (b) -> b.add_stmt new Expr "b.check_null($2).array[$1]=$0",b.pop(),b.pop(),b.pop()}
-  sastore: {compile: (b) -> b.add_stmt new Expr "b.check_null($2).array[$1]=$0",b.pop(),b.pop(),b.pop()}
+  iastore: {compile: (b) -> b.add_stmt new Expr "rs.check_null($2).array[$1]=$0",b.pop(),b.pop(),b.pop()}
+  lastore: {compile: (b) -> b.add_stmt new Expr "rs.check_null($2).array[$1]=$0",b.pop(),b.pop(),b.pop2()}
+  fastore: {compile: (b) -> b.add_stmt new Expr "rs.check_null($2).array[$1]=$0",b.pop(),b.pop(),b.pop()}
+  dastore: {compile: (b) -> b.add_stmt new Expr "rs.check_null($2).array[$1]=$0",b.pop(),b.pop(),b.pop2()}
+  aastore: {compile: (b) -> b.add_stmt new Expr "rs.check_null($2).array[$1]=$0",b.pop(),b.pop(),b.pop()}
+  bastore: {compile: (b) -> b.add_stmt new Expr "rs.check_null($2).array[$1]=$0",b.pop(),b.pop(),b.pop()}
+  castore: {compile: (b) -> b.add_stmt new Expr "rs.check_null($2).array[$1]=$0",b.pop(),b.pop(),b.pop()}
+  sastore: {compile: (b) -> b.add_stmt new Expr "rs.check_null($2).array[$1]=$0",b.pop(),b.pop(),b.pop()}
   pop: {compile: (b) -> b.pop()}
   pop2: {compile: (b) -> b.pop2()}
   # TODO: avoid duplicating non-primitive expressions so as to save on computation


bugfix: pop2 was being put in the wrong place

--- src/compiler.coffee
+++ src/compiler.coffee
@@ -364,9 +364,9 @@
   dconst_0: { compile: (b) -> b.push2 new Primitive "0"; }
   dconst_1: { compile: (b) -> b.push2 new Primitive "1"; }
   iastore: {compile: (b) -> b.add_stmt new Expr "rs.check_null($2).array[$1]=$0",b.pop(),b.pop(),b.pop()}
-  lastore: {compile: (b) -> b.add_stmt new Expr "rs.check_null($2).array[$1]=$0",b.pop(),b.pop(),b.pop2()}
+  lastore: {compile: (b) -> b.add_stmt new Expr "rs.check_null($2).array[$1]=$0",b.pop2(),b.pop(),b.pop()}
   fastore: {compile: (b) -> b.add_stmt new Expr "rs.check_null($2).array[$1]=$0",b.pop(),b.pop(),b.pop()}
-  dastore: {compile: (b) -> b.add_stmt new Expr "rs.check_null($2).array[$1]=$0",b.pop(),b.pop(),b.pop2()}
+  dastore: {compile: (b) -> b.add_stmt new Expr "rs.check_null($2).array[$1]=$0",b.pop2(),b.pop(),b.pop()}
   aastore: {compile: (b) -> b.add_stmt new Expr "rs.check_null($2).array[$1]=$0",b.pop(),b.pop(),b.pop()}
   bastore: {compile: (b) -> b.add_stmt new Expr "rs.check_null($2).array[$1]=$0",b.pop(),b.pop(),b.pop()}
   castore: {compile: (b) -> b.add_stmt new Expr "rs.check_null($2).array[$1]=$0",b.pop(),b.pop(),b.pop()}


Updated documentation for spec helper function.

--- spec/compiler.coffee
+++ spec/compiler.coffee
@@ -22,7 +22,7 @@

 # Helper function for expecting errors to be thrown when parsing.
 #
-# @param source [String] A VFL expression.
+# @param source [String] VFL statements.
 # @param message [String] This should be provided when a rule exists to catch
 # invalid syntax, and omitted when an error is expected to be thrown by the PEG
 # parser.


Use Git::getCachedPathStatus in spec

Use Git::getCachedPathStatus in spec

--- spec/git-spec.coffee
+++ spec/git-spec.coffee
@@ -203,10 +203,9 @@
         statusHandler.callCount > 0

       runs ->
-        statuses = repo.statuses
-        expect(statuses[cleanPath]).toBeUndefined()
-        expect(repo.isStatusNew(statuses[newPath])).toBeTruthy()
-        expect(repo.isStatusModified(statuses[modifiedPath])).toBeTruthy()
+        expect(repo.getCachedPathStatus(cleanPath)).toBeUndefined()
+        expect(repo.isStatusNew(repo.getCachedPathStatus(newPath))).toBeTruthy()
+        expect(repo.isStatusModified(repo.getCachedPathStatus(modifiedPath))).toBeTruthy()

   describe "buffer events", ->
     [originalContent, editor] = []


Limit the sortable objects to just .sortable

--- lib/assets/javascripts/outpost/aggregator/aggregator.js.coffee
+++ lib/assets/javascripts/outpost/aggregator/aggregator.js.coffee
@@ -171,6 +171,9 @@
                 dropped = false

                 @$el.sortable
+                    # Which items are sortable
+                    items: ".sortable",
+
                     # When dragging (sorting) starts
                     start: (event, ui) ->
                         sortIn  = true
@@ -812,6 +815,7 @@
         # representations of a model should inherit
         class @ContentView extends Backbone.View
             tagName: 'li'
+            className: 'sortable'

             #---------------------

@@ -831,16 +835,14 @@
         # A single piece of content in the drop zone!
         # Full with lots of information
         class @ContentFull extends @ContentView
-            attributes:
-                class: "content-full"
+            className: "sortable content-full"
             template: 'content_full'

         #----------------------------------
         # A single piece of recent content!
         # Just the basic info
         class @ContentMinimal extends @ContentView
-            attributes:
-                class: "content-minimal"
+            className: "sortable content-minimal"
             template: 'content_small'

         #---------------------


Update date.coffee

Update date.coffee

added force conversion to the `date` filter (for cases where instead of date passed a number or a some valid string)

--- src/filter/date.coffee
+++ src/filter/date.coffee
@@ -9,6 +9,8 @@
         if not value
             return ''

+        value = new Date(value)
+
         x = [
             [/yyyy/g, value.getFullYear()]
             [/mm/g, d2 value.getMonth() + 1]


point

point

--- src/point.coffee
+++ src/point.coffee
@@ -51,14 +51,14 @@
     @returns {Point} A new point with the same x and y value as this point.

-      point = Point(1, 1)
-      pointCopy = point.copy()
-
-      point.equal(pointCopy)
-      => true
-
-      point == pointCopy
-      => false
+    point = Point(1, 1)
+    pointCopy = point.copy()
+
+    point.equal(pointCopy)
+    # => true
+
+    point == pointCopy
+    # => false

     ###
     copy: ->


hide archived periods by default

hide archived periods by default

--- src/flux/course.coffee
+++ src/flux/course.coffee
@@ -107,9 +107,10 @@
     getName: (courseId) ->
       @_get(courseId)?.name or ""

-    getPeriods: (courseId) ->
-      periods = @_get(courseId).periods or []
-      sortedPeriods = PeriodHelper.sort(periods)
+    getPeriods: (courseId, options = {includeArchived: false}) ->
+      course = @_get(courseId)
+      periods = if options.includeArchived then course.periods else PeriodHelper.activePeriods(course)
+      sortedPeriods = if periods then PeriodHelper.sort(periods) else []

     getTimezone: (courseId) ->
       @_get(courseId)?.time_zone or DEFAULT_TIME_ZONE


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/game_object.coffee
+++ src/game_object.coffee
@@ -178,7 +178,7 @@
       else
         self.bind(eventName, eval( "(function() {#{event}})" ))

-  self
+  self.

 ###*
 Construct an object instance from the given entity data.


Document setupDbClients

Document setupDbClients

--- src/quest.coffee
+++ src/quest.coffee
@@ -125,6 +125,10 @@
         plugin = path.join(@questPath, plugin)
       require(plugin)(@)

+  # Private: Set up connections to databases.
+  #
+  # Sets up the @connections object with db names to affqis connection ids.
+  # There's special handling of postgres until we have support in affqis.
   setupDbClients: ->
     @connections ?= {}
     for db in @databases


Times are usually integers.

Times are usually integers.

--- src/quote.coffee
+++ src/quote.coffee
@@ -110,7 +110,7 @@
       if m?
         [x, speaker, hours, minutes, ampm] = m
         hours += 12 if ampm is 'PM'
-        timestamp = moment {hours: hours, minutes: minutes}
+        timestamp = moment({hours: parseInt(hours), minutes: parseInt(minutes)})
         ts = timestamp.format('h:mm A d MMM YYYY')
       else if speaker?
         processed.push "[#{ts}] #{speaker}: #{line}"


cooldown finilazation

cooldown finilazation

--- src/cooldown.coffee
+++ src/cooldown.coffee
@@ -15,7 +15,7 @@

 # Shoot Timeout
-player = GameObject
+player = GameObject()

 player.cooldown "health",
   target: 100


Populate @commands as we go, instead of when helpCommands are requested

--- src/robot.coffee
+++ src/robot.coffee
@@ -8,6 +8,8 @@
 Response                                                = require './response'
 {Listener,TextListener}                                 = require './listener'
 {TextMessage,EnterMessage,LeaveMessage,CatchAllMessage} = require './message'
+
+inspect = require('util').inspect

 HUBOT_DEFAULT_ADAPTERS = [ 'campfire', 'shell' ]

@@ -25,6 +27,7 @@
     @alias        = false
     @adapter      = null
     @Response     = Response
+    @commands     = []
     @listeners    = []
     @loadPaths    = []
     @enableSlash  = false
@@ -234,18 +237,7 @@
   #
   # Returns an Array of help commands for running scripts.
   helpCommands: ->
-    unless @commands
-      @logger.debug "helpCommands: haven't populated yet"
-      @commands = []
-
-      for script, scriptDocumentation of @documentation
-        @logger.debug "helpCommands: checking #{script} for commands"
-        if scriptDocumentation.commands
-          for command in scriptDocumentation.commands
-            @logger.debug "helpCommands: adding '#{command}' from #{script}"
-            @commands.push(command)
-    @commands
-
+    @commands.sort()

   # Private: load help info from a loaded script.
   #
@@ -281,13 +273,10 @@
           # lines in a section _do_ have leading whitespace
           else
             if currentSection
-              @logger.debug "parseHelp(#{scriptName}) adding #{cleanedLine.trim()} to #{currentSection}"
+              @logger.debug "parseHelp(#{scriptName}) adding '#{cleanedLine.trim()}' to #{currentSection}"
               scriptDocumentation[currentSection].push cleanedLine.trim()
-
-    if not scriptDocumentation.commands
-      scriptDocumentation.commands = []
-
-    scriptDocumentation.commands = scriptDocumentation.commands.sort()
+              if currentSection == 'commands'
+                @commands.push cleanedLine.trim()

   # Public: A helper send function which delegates to the adapter's send
   # function.


Add options argument to all Listener-creation methods

--- src/robot.coffee
+++ src/robot.coffee
@@ -118,37 +118,46 @@

   # Public: Adds a Listener that triggers when anyone enters the room.
   #
+  # options  - An Object of additional parameters keyed on extension name
+  #            (optional).
   # callback - A Function that is called with a Response object.
   #
   # Returns nothing.
-  enter: (callback) ->
+  enter: (options, callback) ->
     @listeners.push new Listener(
       @,
       ((msg) -> msg instanceof EnterMessage),
+      options,
       callback
     )

   # Public: Adds a Listener that triggers when anyone leaves the room.
   #
+  # options  - An Object of additional parameters keyed on extension name
+  #            (optional).
   # callback - A Function that is called with a Response object.
   #
   # Returns nothing.
-  leave: (callback) ->
+  leave: (options, callback) ->
     @listeners.push new Listener(
       @,
       ((msg) -> msg instanceof LeaveMessage),
+      options,
       callback
     )

   # Public: Adds a Listener that triggers when anyone changes the topic.
   #
+  # options  - An Object of additional parameters keyed on extension name
+  #            (optional).
   # callback - A Function that is called with a Response object.
   #
   # Returns nothing.
-  topic: (callback) ->
+  topic: (options, callback) ->
     @listeners.push new Listener(
       @,
       ((msg) -> msg instanceof TopicMessage),
+      options,
       callback
     )

@@ -178,13 +187,16 @@

   # Public: Adds a Listener that triggers when no other text matchers match.
   #
+  # options  - An Object of additional parameters keyed on extension name
+  #            (optional).
   # callback - A Function that is called with a Response object.
   #
   # Returns nothing.
-  catchAll: (callback) ->
+  catchAll: (options, callback) ->
     @listeners.push new Listener(
       @,
       ((msg) -> msg instanceof CatchAllMessage),
+      options,
       ((msg) -> msg.message = msg.message.message; callback msg)
     )


Moved default siteUrl to startHighlighting

--- js-library/app/js/util/highlight.coffee
+++ js-library/app/js/util/highlight.coffee
@@ -12,7 +12,7 @@
 # Function which will collect all the facts for the current page
 # and select them.
 # Returns deferred object
-fetchFacts = (siteUrl=Factlink.siteUrl()) ->
+fetchFacts = (siteUrl) ->
   $.ajax
     # The URL to the Factlink backend
     url: FactlinkConfig.api + "/site?url=" + encodeURIComponent(siteUrl)
@@ -28,13 +28,13 @@
   highlighting = true

   console.info "Factlink:", "startHighlighting"
-  fetchFacts()
+  fetchFacts Factlink.siteUrl()

 # Don't check for highlighting here, as this is a
 # special hacky-patchy method for in the blog
 Factlink.highlightAdditionalFactlinks = (siteUrl) ->
   console.info "Factlink:", "highlightAdditionalFactlinks"
-  fetchFacts(siteUrl)
+  fetchFacts siteUrl

 Factlink.stopHighlighting = ->
   return unless highlighting


Handle errors with assignee and transitions, ensure ticket always displays after creation

--- src/jira/create.coffee
+++ src/jira/create.coffee
@@ -14,20 +14,23 @@
       body: JSON.stringify json

   @with: (project, type, summary, msg, fields, emit=yes) ->
-    if Config.maps.transitions
-      if Config.transitions.shouldRegex.test(summary)
-        [ __, toState] =  summary.match Config.transitions.shouldRegex
-      summary = summary.replace(Config.transitions.shouldRegex, "") if toState
-
-    if Config.mention.regex.test summary
-      assignee = summary.match(Config.mention.regex)[1]
-      summary = summary.replace Config.mention.regex, ""
+    toState = null
+    assignee = null

     User.withEmail(msg.message.user.email_address)
     .then (reporter) ->
       labels = [ msg.message.room ]
       description = summary.match(Config.quote.regex)[1] if Config.quote.regex.test(summary)
       summary = summary.replace(Config.quote.regex, "") if description
+
+      if Config.maps.transitions
+        if Config.transitions.shouldRegex.test(summary)
+          [ __, toState] =  summary.match Config.transitions.shouldRegex
+        summary = summary.replace(Config.transitions.shouldRegex, "") if toState
+
+      if Config.mention.regex.test summary
+        assignee = summary.match(Config.mention.regex)[1]
+        summary = summary.replace Config.mention.regex, ""

       if Config.labels.regex.test summary
         labels = (summary.match(Config.labels.regex).map((label) -> label.replace('#', '').trim())).concat(labels)
@@ -58,11 +61,15 @@
     .then (json) ->
       Create.fromKey(json.key)
       .then (ticket) ->
-        Promise.all [
+        Promise.all([
           Transition.forTicketToState ticket, toState, msg, no, no if toState
           Assign.forTicketToPerson ticket, assignee, msg, no, no if assignee
           ticket
-        ]
+        ])
+        .catch (error) ->
+          Utils.robot.logger.error error
+          [ undefined, text:error, ticket]
+
       .then (results) ->
         [ transition, assignee, ticket ] = results
         roomProject = Config.maps.projects[msg.message.room]


When the user selects a cateogory with multiple subcategories, the first sub-category is automatically selected and shown to the user.

--- bootstrap-magic.coffee
+++ bootstrap-magic.coffee
@@ -62,10 +62,6 @@
   myCat = currentCat.get()
   _.where(bootstrap_magic_variables, { category: myCat })

-showAndRm = ->
-  $('.sub-cat-message h4').remove()
-  $('.sub-cat-item').show()
-
 #Time for Templates

 Template._bootstrap_magic.created = ->
@@ -105,15 +101,8 @@

   'click .menu-secondary-list' : ->
     currentCat.set(@category)
-    if (_.map objByCat(), (obj) -> obj).length is 1
-      currentSubCat.set(objByCat()[0].keyName)
-      showAndRm()
-    else
-      $('.sub-cat-message').html("
Please select a submenu tab
")
-      $('.sub-cat-item').hide()
+    currentSubCat.set(objByCat()[0].keyName)

-  'click .menu-tertiary-list' : ->
-    currentSubCat.set(@keyName)
-    showAndRm()
+  'click .menu-tertiary-list' : -> currentSubCat.set(@keyName)

   # 'click #reset' :-> setDefaults(obj)


Remove mention of Dropbox in external update dialog

--- services/web/public/coffee/editor/Editor.coffee
+++ services/web/public/coffee/editor/Editor.coffee
@@ -224,7 +224,7 @@
 			document.on "externalUpdate", () =>
 				Modal.createModal
 					title: "Document Updated Externally"
-					message: "This document was just updated externally (probably via Dropbox). Any recent changes you have made may have been overwritten. To see previous versions please look in the history."
+					message: "This document was just updated externally. Any recent changes you have made may have been overwritten. To see previous versions please look in the history."
 					buttons:[
 						text: "Ok"
 					]


Add gp10, hp5, and mp5 stats

Add gp10, hp5, and mp5 stats

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

--- src/stats.coffee
+++ src/stats.coffee
@@ -17,6 +17,9 @@
     aPen: "Armor Penetration"
     mPen: "Magic Penetration"
     range: "Range"
+    gp10: "Gold per Ten"
+    hp5: "Health Regen"
+    mp5: "Mana Regen"

   ##
   # Combines multiple stats together.


Fix arguments

Fix arguments

--- src/table.coffee
+++ src/table.coffee
@@ -62,7 +62,7 @@
 #myTable.create([schema], [callback])
 Table::create = (schema, callback) ->
   self = this
-  args = Array::slice.call(arguments_)
+  args = Array::slice.call(arguments)
   schema = (if args.length and typeof args[0] is "object" or typeof args[0] is "string" then args.shift() else {})
   callback = (if args.length then args.shift() else null)
   schema.name = @name


Removed unused require()

Removed unused require()

--- src/io/tty-effects.coffee
+++ src/io/tty-effects.coffee
@@ -2,8 +2,6 @@

 vectorMath = require '../vector-math'
 {bresenhamLine, whilst, arrayRemove} = require '../util'
-
-graphics = require './graphics-ascii'

 module.exports = class TtyEffects
 	constructor: (@renderer) ->


Remove redundant injection.

Remove redundant injection.

--- spec/javascripts/app/controllers_spec.js.coffee
+++ spec/javascripts/app/controllers_spec.js.coffee
@@ -19,13 +19,11 @@

     beforeEach inject ($injector, $rootScope, $controller) ->
       $scope = $rootScope.$new()
-      confirmationDialog = $injector.get("confirmationDialog")
       doAction = jasmine.createSpy("do action")

       $httpBackend.when("GET", "/databases.json").respond([])
       $controller window.DatabasesCtrl,
         $scope: $scope
-        confirmationDialog: confirmationDialog
         doAction: doAction

       $httpBackend.flush()
@@ -64,7 +62,6 @@

     beforeEach inject ($injector, $rootScope, $controller) ->
       $scope = $rootScope.$new()
-      confirmationDialog = $injector.get("confirmationDialog")
       doAction = jasmine.createSpy("do action")

       $element = $("<div/>")
@@ -75,7 +72,6 @@
       $controller window.CollectionsCtrl,
           $scope: $scope
           $element: $element
-          confirmationDialog: confirmationDialog
           doAction: doAction

       $httpBackend.flush()


Added support for cordova console plugin for logging.

--- src/Log.coffee
+++ src/Log.coffee
@@ -121,3 +121,9 @@
     if logLevel?
       console.log('Log level:', logLevel)
       Logger.setLevel(logLevel)
+  else if Meteor.isCordova
+    # Cordova console package exists only once 'deviceready' event is fired.
+    $(document).on 'deviceready', ->
+      console = window.console
+      platform = device.platform.toLowerCase()
+      Logger.info('Cordova logger started on platform:', platform)


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/tween.coffee
+++ src/tween.coffee
@@ -44,7 +44,7 @@
     properties = Object.extend({}, properties) # Make a local copy

     easing = properties.easing || "linear"
-
+    complete = properties.compl
     delete properties.easing
     delete properties.complete


more ctrl keys

more ctrl keys

--- main/pub/sole/src/main.coffee
+++ main/pub/sole/src/main.coffee
@@ -38,6 +38,7 @@
     $el.css {background}
     if background
       setTimeout (-> flash $el,''), 50
+  bell = -> flash ($ 'body'), 'black'

   matr = rend (Matr
     rows:[]
@@ -59,13 +60,13 @@
     switch Object.keys(ruh)[0]
       when 'txt' then update rows: [mapr.rows..., ruh.txt]
       when 'tan' then ruh.tan.split("\n").reverse().map (txt)-> peer {txt}
-      when 'hop' then update cursor: buffer.transpose ruh.hop #; peer act:'bel'
+      when 'hop' then update cursor: ruh.hop; bell() # XX buffer.transpose?
       when 'pro' then update prompt: ruh.pro.cad
       when 'blk' then console.log "Stub #{str ruh}"
       when 'det' then buffer.receive ruh.det; sync ruh.det.ted
       when 'act' then switch ruh.act
         when 'clr' then update rows:[]
-        when 'bel' then flash ($ 'body'), 'black'
+        when 'bel' then bell()
         when 'nex' then update
           input: ""
           cursor: 0
@@ -103,38 +104,9 @@
     sync ted
     sendAction {det}

-
-  Mousetrap.handleKey = (char, mod, e)->
-    norm = {
-      capslock:  'caps'
-      pageup:    'pgup'
-      pagedown:  'pgdn'
-      backspace: 'baxp'
-      enter:     'entr'
-    }
-
-    key = switch
-      when char.length is 1
-        if e.type is 'keypress'
-          chac = char.charCodeAt(0)
-          if chac < 32          # normalize ctrl keys
-            char = String.fromCharCode chac | 96
-          str: char
-      when e.type is 'keydown'
-        if char isnt 'space'
-          act: norm[char] ? char
-      when e.type is 'keyup' and norm[key] is 'caps'
-        act: 'uncap'
-    if !key then return
-    if key.act and key.act in mod
-      return
-    e.preventDefault()
+  yank = ''
+  eatKyev= (mod, key)->
     mapr = matr.props
-    #[fore, aft] = (
-    #  [sli,cur] = [mapr.input.slice, mapr.cursor]
-    #  [sli(0, cur), sli(cur)]
-    #)
-
     switch mod.sort().join '-'
       when '', 'shift'
         if key.str
@@ -168,20 +140,79 @@
       when 'ctrl' then switch key.str || key.act
         when 'a','left'  then update cursor: 0
         when 'e','right' then update cursor: mapr.input.length
+        when 'l' then update rows: []
         when 'entr' then peer act: 'bel'
+        when 'w' then eatKyev ['alt'], act:'baxp'
+        when 'p' then eatKyev [], act: 'up'
+        when 'n' then eatKyev [], act: 'down'
+        when 'b' then eatKyev [], act: 'left'
+        when 'f' then eatKyev [], act: 'right'
+        when 'g' then bell()
+        when 't'
+          if mapr.cursor is 0 or mapr.input.length < 2
+            return bell()
+          cursor = mapr.cursor
+          if cursor < mapr.input.length
+            cursor++
+          doEdit [{del:cursor-1},ins:{at:cursor-2,cha:mapr.input[cursor-1]}]
+          update {cursor}
+        when 'u'
+          yank = mapr.input.slice(0,mapr.cursor)
+          doEdit (del:mapr.cursor - n for n in [1..mapr.cursor])
+        when 'k'
+          yank = mapr.input.slice(mapr.cursor)
+          doEdit (del:mapr.cursor for _ in [mapr.cursor...mapr.input.length])
+        when 'y'
+          doEdit (ins: {cha, at: mapr.cursor + n} for cha,n in yank)
         else console.log mod, str key
       when 'alt' then switch key.str || key.act
         when 'f','right'
           rest = mapr.input.slice(mapr.cursor)
-          rest = rest.match(/[^a-z0-9]*[a-z0-9]*/)[0]
+          rest = rest.match(/\W*\w*/)[0] # XX unicode
           update cursor: mapr.cursor + rest.length
         when 'b','left'
           prev = mapr.input.slice(0,mapr.cursor)
           prev = prev.split('').reverse().join('')  # XX
-          prev = prev.match(/[^a-z0-9]*[a-z0-9]*/)[0]
+          prev = prev.match(/\W*\w*/)[0] # XX unicode
           update cursor: mapr.cursor - prev.length
+        when 'baxp'
+          prev = mapr.input.slice(0,mapr.cursor)
+          prev = prev.split('').reverse().join('')  # XX
+          prev = prev.match(/\W*\w*/)[0] # XX unicode
+          yank = prev
+          doEdit (del: mapr.cursor-1 - n for _,n in prev)
       else console.log mod, str key

+  Mousetrap.handleKey = (char, mod, e)->
+    norm = {
+      capslock:  'caps'
+      pageup:    'pgup'
+      pagedown:  'pgdn'
+      backspace: 'baxp'
+      enter:     'entr'
+    }
+
+    key = switch
+      when char.length is 1
+        if e.type is 'keypress'
+          chac = char.charCodeAt(0)
+          if chac < 32          # normalize ctrl keys
+            char = String.fromCharCode chac | 96
+          str: char
+      when e.type is 'keydown'
+        if char isnt 'space'
+          act: norm[char] ? char
+      when e.type is 'keyup' and norm[key] is 'caps'
+        act: 'uncap'
+    if !key then return
+    if key.act and key.act in mod
+      return
+    e.preventDefault()
+    #[fore, aft] = (
+    #  [sli,cur] = [mapr.input.slice, mapr.cursor]
+    #  [sli(0, cur), sli(cur)]
+    #)
+    eatKyev mod, key

     #amod = (arr)->
     #  for i in arr
@@ -218,3 +249,4 @@
     #   )
     #   document.title = "Matri"  # XX  debug
     # later {mod, key}
+